[6주] 10장 윈도우 드라이버 퍼징

Posted by dw0rdptr
2015. 2. 23. 09:20 Study/파이썬 해킹 프로그래밍

윈도우 드라이버를 공격에는 드라이버에 대한 원격 공격보단 공격 대상 시스템에 대한 상승된 권한을 획득하기 위해 로컬 드라이버를 공격하는 것이 보편적이다. 오버플로우나 Impersonation 공격 취약점이 있는 드라이버가 로컬에 설치되어 있는 경우 그것을 이용하면 시스템 권한을 획득하기 때문에 원격으로 공격해 제한된 권한만을 얻었을 때보다 훨씬 자유롭게 시스템의 모든 정보에 접근 할 수 있다.

드라이버가 상호작용하려면 유저모드와 커널 모드 간의 변환이 이뤄져야 한다. 

불안정하게 구현된 loctl 핸들러를 이용하면 상승된 권한을 획득하거나 공격 대상 시스템에서 에러가 발생하게 만들 수 있다.


이제 Immunity 디버거의 후킹기능을 이용해 드라이버 퍼징을 해보자

윈도우 시스템에서는 드라이버 통신을 위해 IOCTL을 전달하려면 DeviceIoControl 함수를 사용해야 하는데 이 함수의 호출이 드라이버에 전달되기 전에 중간에 가로채 내용을 변경 할 수 있다. 이런 식으로 Immunity 디버거를 이용해 간단한 퍼저를 만들 수 있다.


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
#ioctl_fuzzer.py
import struct
import random
from immlib import*
 
class ioctl_hook(LogBpHook):
    
    def __init__(self):
 
        self.imm = Debugger()
        self.logfile = "C:\ioctl_log.txt"
        LogBpHook.__init__(self)
 
    def run(self, regs):
        """
        ESP+4    -> hDevice
        ESP+8    -> IoControlCode
        ESP+C     -> InBuffer
        ESP+10     -> InBufferSize
        ESP+14     -> OutBuffer
        ESP+18    -> OutBufferSize
        ESP+1C    -> pBytesReturned
        ESP+20     -> pOverLapped
        """
 
        in_buf =""
 
        ioctl_code = self.imm.readLong(regs['ESP']+8)
 
        inbuffer_size = self.imm.readLong(regs['ESP'+ 0x10)
 
        inbuffer_ptr = self.imm.readLong(regs['ESP'+ 0xC)
 
        in_buffer = self.imm.readMemory(inbuffer_ptr, inbuffer_size)
        mutated_buffer = self.mutate(inbuffer_size)
 
        self.imm.writeMemory(inbuffer_ptr, mutated_buffer)
 
        self.save_test_case( ioctl_code, inbuffer_size, in_buffer, mutated_buffer )
 
 
    def mutate( self, inbuffer_size ):
 
        counter = 0
        mutated_buffer = ""
 
        while counter < inbuffer_size:
            mutated_buffer += struct.pack( "H", random.randint(0255) )[0]
            counter += 1
 
        return mutated_buffer
 
    def save_test_case( self, ioctl_code, inbuffer_size, in_buffer, mutated_buffer ):
 
        message = "*****\n"
        message += "IOCTL Code: 0x%08x\n" % ioctl_code
        message += "Buffer Size: %d\n" % inbuffer_size
        message += "Original Buffer: %s\n" % in_buffer
        message += "Mutated Buffer: %s\n" % mutated_buffer.encode("HEX")
        message += "*****\n\n"
 
        fd = open(self.logfile, "a")
        fd.write(message)
        fd.close()
 
def main(args):
 
    imm = Debugger()
 
    deviceiocontrol = imm.getAddress( "kernel32.DeviceIoControl" )
        
    ioctl_hooker = ioctl_hook()
    ioctl_hooker.add( "%08x" % deviceiocontrol, deviceiocontrol )
        
    return "[*] IOCTL Fuzzer Ready for Action!"
 
cs


1.ioctl_fuzzer.py 파일을 immunity 디버거의 PyCommand 디렉토리에 넣는다. 

2. Immunity 를 실행하고 Open 메뉴로 와이어샤크를 실행시킨다 .

3. PyCommand로 ioctl_fuzzer를 이용해 퍼징을 수행한다.

4. 퍼징 수행 내용이 C:\ 경로에 로깅된다.


이 기술은 쉽고 효과적인 반면 퍼징 대상이되는 디바이스의 이름을 알지 못하며 유저 모드 소프트웨어가 사용하는 IOCTL 코드만을 알 수 있기 때문에 모든 테스트 케이스를 테스트 할 수 있는 것은 아니다.


Driverlib는 디바이스 이름과 IOCTL 코드 등 드라이버에서 핵심이 되는 정보를 자동으로 추출한다. 드라이버 퍼저에서 사용할 디바이스 이름과 IOCTL 코드를 driverlib를 통해 구한다.

사용법은 간단하다. C:\WINDOWS\System32\drivers\beep.sys 드라이버를 로드한 후 immunity 디버거의 PyShell에서

>>>import driverlib

>>>driver = driverlib.Driver()

>>>driver.getDeviceNames()

['\\Device\\Beep']

>>>

세 줄의 코드만을 작성해 \\Device\\Beep 라는 디바이스 이름을 찾아냈다.


이제 드라이버 퍼저를 작성하자. 먼저 IOCTL 코드 리스트와 디바이스 리스트를 구해 dictionary를 다시 파일에 저장한다.

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
import pickle
import driverlib
from immlib import *
def main( args ):
    ioctl_list = []
    device_list = []
    
    dbg = Debugger()
    driver = driverlib.Driver()
 
    ioctl_list = driver.getIOCTLCodes()
    if not len(ioctl_list):
        return "[*] ERROR! Couldn't find any IOCTL codes."
 
    device_list = driver.getDeviceNames()
    if not len(device_list):
        return "[*] ERROR! Couldn't find any device names."
 
    master_list = {}
    master_list["ioctl_list"= ioctl_list
    master_list["device_list"= device_list
 
    fd = open( args[0], "wb")
    pickle.dump( master_list, fd )
    fd.close()
 
    return "[*] SUCCESS! Saved IOCTL codes and device names to %s" % args[0]
 
cs


디바이스 이름과 지원되는 IOCTL 코드값을 구했으므로 간단한 퍼저를 작성하자.

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
import pickle
import sys
import random
from ctypes import *
 
kernel32 = windll.kernel32
 
GENERIC_READ = 0x80000000
GENERIC_WRITE = 0x40000000
OPEN_EXISTING = 0x3
 
fd = open(sys.argv[1], "rb")
master_list = pickle.load(fd)
ioctl_list = master_list["ioctl_list"]
device_list = master_list["device_list"]
fd.close()
 
valid_devices = []
 
for device_name in device_list:
 
        device_file = u"\\\\.\\%s" % device_name.split("\\")[::-1][0]
        print "[*] Testing for device: %s" % device_file
        
        driver_handle = kernel32.CreateFileW(device_file,GENERIC_READ|
                                    GENERIC_WRITE,0,None,OPEN_EXISTING,0,None)
        if driver_handle:
            print "[*] Success! %s is a valid device!"
                
            if device_file not in valid_devices:
                        valid_devices.append( device_file )
            kernel32.CloseHandle( driver_handle )
        else:
            print "[*] Failed! %s NOT a valid device."
if not len(valid_devices):
        print "[*] No valid devices found. Exiting..."
        sys.exit(0)
 
while 1:
 
        fd = open("my_ioctl_fuzzer.log","a")
 
        current_device = valid_devices[ random.randint(0len(valid_devices)-1 ) ]
        fd.write("[*] Fuzzing: %s" % current_device)
 
        current_ioctl = ioctl_list[ random.randint(0len(ioctl_list)-1)]
        fd.write("[*] With IOCTL: 0x%08x" % current_ioctl)
 
        current_length = random.randint(010000) y
        fd.write("[*] Buffer length: %d" % current_length)
 
        in_buffer = "A" * current_length
 
        out_buf = (c_char * current_length)()
        bytes_returned = c_ulong(current_length)
 
        driver_handle = kernel32.CreateFileW(device_file, GENERIC_READ|
                                GENERIC_WRITE,0,None,OPEN_EXISTING,0,None)
        fd.write("!!FUZZ!!")
 
        kernel32.DeviceIoControl( driver_handle, current_ioctl, in_buffer,
                                current_length, byref(out_buf),
                                current_length, byref(bytes_returned),
                                None )
 
        fd.write( "[*] Test case finished. %d bytes returned.\n" % bytes_returned.value )
 
        kernel32.CloseHandle( driver_handle )
        fd.close()
 
cs


'Study > 파이썬 해킹 프로그래밍' 카테고리의 다른 글

[6주] 11장 IDAPython  (0) 2015.02.23
[5주] 09장 Sulley  (0) 2015.02.23
[5주] 08장 퍼징  (0) 2015.02.16
[4주] 07장 DLL과 코드 인젝션  (0) 2015.02.16
[3주] 05장~06장 후킹  (0) 2015.01.26

[5주] 09장 Sulley

Posted by dw0rdptr
2015. 2. 23. 06:11 Study/파이썬 해킹 프로그래밍

Sulley는 파이썬기반의 강력한 퍼징 프레임워크로서 패킷을 캡쳐할 수 있고 광범위한 에러 보고 기능과 VMWare  자동화를 제공한다.


Sulley는 퍼징 프로토콜을 나타내는 데이터 형식을 제공해 복잡한 프로토콜도 빠르게 만들 수 있다.

이런 각 데이터 컴포넌트들을 프리미티브라 부른다.

기본적인 프리미티브들에는


문자열(string)

가장 자주 사용하게 되는 프리미티브로 s_string() 지시어를 사용해 프리미티브 안에 포함돼 잇는 데이터가 문자열이라는 것을 나타낸다.

구분자(Delimiter)

긴 문자열을 처리하기 쉽게 여러개의 문자열로 나누는데 사용되는 짧은 문자열이다. s_delim()    지시어를 사용한다.

정적,랜덤 프리미티브

정적 문자열은 s_static()지시어를 사용하고 길이가 유동적인 랜덤 데이터는 s_random()      지시어를 이용해 만들어낸다. 랜덤데이터를 만들때는 몇 개의 인자가 사용되는데, min_length와 max_length 인자는 랜덤데이터의 최소, 최대 길이를 나타내고 num_mutations 인자는 원래의 데이터를 사용하기 전까지 Sulley가 문자열을 변형하는 횟수를 나타낸다.

바이너리 데이터(Binary Data) 

바이너리 데이터 프리미티브는 어떤 바이너리 데이터든 복사해 사용할 수 있으며, 이는 알려지지 않은 프로토콜의 패킷을 캡쳐할 때나 형식이 완전하게 갖춰지지 않은 데이터에 대해 서버가 단지 어떻게 응답하는지 보고자 할 때 특히 유용하다. s_binary() 지시어를 사용한다.

정수(Integer)

텍스트나 바이너리 프로토콜 모두에서 길이를 판단하거나 데이터 구조체를 표현하기위해, 그 밖의 모든 종류의 작업을 수행하기 위해 정수가 사용된다.  Sulley는 다음과 같이 주요 정수형을 모두 지원한다

------------------------------------

1 byte - s_byte(), s_char()

2 bytes - s_word(), s_short()

4 bytes - s_dword(), s_long(), s_int()

8 bytes - s_qword(), s_double()

------------------------------------


블록과 그룹은 프리미티브들을 구조적인 방법으로 연결하기 위해 Sulley가 제공하는 특징이다. 


블록 - 각 프리미티브들의 집합을 하나의 구조 안으로 모으는 것

그룹 - 특정 프리미티브들의 집합을 블록에 연결해 해당 블록에 대한 퍼징이 반복적으로 수행되는 동안에 각 프리미티브가 주기적으로 퍼징에 사용되게 하는 것






'Study > 파이썬 해킹 프로그래밍' 카테고리의 다른 글

[6주] 11장 IDAPython  (0) 2015.02.23
[6주] 10장 윈도우 드라이버 퍼징  (0) 2015.02.23
[5주] 08장 퍼징  (0) 2015.02.16
[4주] 07장 DLL과 코드 인젝션  (0) 2015.02.16
[3주] 05장~06장 후킹  (0) 2015.01.26

[5주] 08장 퍼징

Posted by dw0rdptr
2015. 2. 16. 17:30 Study/파이썬 해킹 프로그래밍

퍼징은 소프트웨어에 있는 버그를 찾아내는 가장 효과적인 기술중 하나로, 애플리케이션이 에러를 발생하게 만들기 위해 비정상적인 데이터를 만들어 애플리케이션에 전달하는 방법이다.

퍼저에는 제너레이션 퍼저와 뮤테이션 퍼저 두 가지가 있는데,

제너레이션 퍼저는 새로운 데이터를 생성해 전달하는 반면에 뮤테이션 퍼저는 기존의 데이터를 생성해 대상 애플리케이션에 전달한다.


효과적인 퍼저를 만들기 위해선 먼저 버그의 유형을 간단히 살펴볼 필요가 있다.

버그의 유형으로는 가장 흔한 형태의 소프트웨어 취약점인 버퍼오버플로우로 분류되는 스택기반의 오버플로우, 힙 기반의 오버플로우다. 또 흔하게 볼 수 있는 유형인 정수 오버플로우와 포맷스트링 공격도 있다.

파일 포맷 취약점을 이용하는 공격 벡터는 파일 포맷 파서 자체의 버그를 찾아내는 방향으로 관심을 기울여야 한다. 목적 달성을 위해서는 모든 종류의 다양한 파일 포맷에대한 변형을 만들 수 있어야 한다.

이제 퍼저를 구현해보자

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
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
#file_fuzzer.py
from pydbg import *
from pydbg.defines import *
 
import utils
import random
import sys
import struct
import threading
import os
import shutil
import time
import getopt
 
class file_fuzzer:
    def __init__(self, exe_path, ext, notify):
 
        self.exe_path             = exe_path
        self.ext                 = ext
        self.notify_crash         = notify
        self.orig_file             = None
        self.mutated_file         = None
        self.iteration             = 0
        self.exe_path             = exe_path
        self.orig_file             = None
        self.mutated_file         = None
        self.iteration             = 0
        self.crash                 = None
        self.send_notify         = False
        self.pid                 = None
        self.in_accessv_handler = False
        self.dbg                 = None
        self.running             = False
        self.ready                 = False
 
        #option
        self.smtpserver = 'mail.nostarch.com'
        self.recipients = ['jms@bughunter.ca',]
        self.sender = 'jms@bughunter.ca'
 
        self.test_cases = ["%s%n%s%n%s%n""\xff""\x00""A"]
 
    def file_picker(self):
 
        file_list = os.listdir("examples/")
        list_length = len(file_list)
        file = file_list[random.randint(0, list_length - 1)]
        shutil.copy("examples\\%s" % file"test.%s" % self.ext)
        
        return file
 
    def fuzz(self):
        while 1:
        if not self.running:
 
            self.test_file = self.file_picker()
            self.mutate_file()
 
            pydbg_thread = threading.Thread(target = self.start_debugger)
            pydbg_thread.setDaemon(0)
            pydbg_thread.start()
 
            while self.pid == None:
                time.sleep(1)
 
            monitor_thread = threading.Thread(target = self.monitor_debugger)
            monitor_thread.setDaemon(0)
            monitor_thread.start()
            self.iteration += 1
 
        else:
            time.sleep(1)
 
    def start_debugger(self):
 
        print "[*] Starting debugger for iteration: %d" % self.iteration
        self.running = True
        self.dbg = pydbg()
 
        self.dbg.set_callback(EXCEPTION_ACCESS_VIOLATION, self.check_accessv)
        pid = self.dbg.load(self.exe_path, "test.%s" % self.ext)
        
        self.pid = self.dbg.pid
        self.dbg.run()
 
    def check_accessv(self, dbg):
 
        if dbg.dbg.u.Exception.dwFirstChance:
            return DBG_CONTINUE
 
        print "[*] Woot! Handling an access violation!"
        self.in_accessv_handler = True
        crash_bin = utils.crash_binning.crash_binning()
        crash_bin.record_crash(dbg)
        self.crash = crash_bin.crash_synopsis()
 
        crash_fd = open("crashes\\crash-%d" % self.iteration, "w")
        crash_fd.write(self.crash)
 
        shutil.copy("test.%s" % self.ext, "crashes\\%d.%s" % 
                        (self.iteration, self.ext))
        shutil.copy("examples\\%s" % self.test_file, "crashes\\%d_orig.%s" % 
                        (self.iteration, self.ext))
        
        self.dbg.terminate_process()
        self.in_accessv_handler = False
        self.running = False
        
        return DBG_EXCEPTION_NOT_HANDLED
 
    def monitor_debugger(self):
        counter = 0
        print "[*] Monitor thread for pid: %d waiting." % self.pid,
        
        while counter < 3:
            time.sleep(1)
            print counter,
            counter += 1
        
        if self.in_accessv_handler != True:
            time.sleep(1)
            self.dbg.terminate_process()
            self.pid = None
            self.running = False
        else:
            print "[*] The access violation handler is doing its business. Waiting."
            while self.running:
                time.sleep(1)
 
    def notify(self):
 
        crash_message = "From:%s\r\n\r\nTo:\r\n\r\nIteration:%d\n\nOutput:\n\n %s" % 
                            (self.sender, self.iteration, self.crash)
 
        session = smtplib.SMTP(smtpserver)
        session.sendmail(sender, recipients, crash_message)
        session.quit()
        
        return
 
    def mutate_file(self):
 
        fd = open("test.%s" % self.ext, "rb")
        stream = fd.read()
        fd.close()
 
        test_case = self.test_cases[random.randint(0len(self.test_cases) - 1)]
        stream_length = len(stream)
        rand_offset = random.randint(0, stream_length - 1)
        rand_len = random.randint(11000)
 
        test_case = test_case * rand_len
 
        fuzz_file = stream[0:rand_offset]
        fuzz_file += str(test_case)
        fuzz_file += stream[rand_offset]
 
        fd = open("test.%s" % self.ext, "wb")
        fd.write(fuzz_file)
        fd.close()
 
        return
    def print_usage():
        
        print "[*]"
        print "[*] file_fuzzer.py -e  -x "
        print "[*]"
        
        sys.exit(0)
        
        if __name__ == "__main__":
            print "[*] Generic File Fuzzer."
 
        try:
            opts, argo = getopt.getopt(sys.argv[1:], "e:x:n")
        
        except getopt.GetoptError:
            
            print_usage()
 
        exe_path = None
        ext = None
        notify = False
 
        for o,a in opts:
        
            if o == "-e":
                exe_path = a
            elif o == "-x":
                ext = a
            elif o == "-n":
                notify = True
 
        if exe_path is not None and ext is not None:
            fuzzer = file_fuzzer(exe_path, ext, notify)
            fuzzer.fuzz()
        else:
            print_usage()
 
 
 
 
cs


'Study > 파이썬 해킹 프로그래밍' 카테고리의 다른 글

[6주] 10장 윈도우 드라이버 퍼징  (0) 2015.02.23
[5주] 09장 Sulley  (0) 2015.02.23
[4주] 07장 DLL과 코드 인젝션  (0) 2015.02.16
[3주] 05장~06장 후킹  (0) 2015.01.26
[3주] 05장 - 윈도우 DEP우회  (0) 2015.01.26