PHC/Python2012.08.24 10:07
Python으로 제작된 FileFuzzer입니다. 
=====================================
스크립트 실행 전 해야될 것들 

File_Fuzzer.py가 있는 디렉토리에
crashes, examples, TEST라는 이름의 3가지 폴더를
만들어야 한다.

crashes 폴더는 crash가 발생했을 때의 상태정보가 저장되는 폴더이다.

examples 폴더 내에는 sample.avi를 넣어두자. examples 는 퍼저에 사용될 avi파일을 넣어두는 폴더이다. 

TEST 폴더 내에는 sample.avi의 백업 파일이 저장되는 공간이다. 

스크립트 실행 방법 : FileFuzzer.py -e "C:\Program Files\GRETECH\GomPlayer\GOM.exe" -x "avi"
=================================================================================================

곰플레이어의 crash를 찾아봅시다!!
# -*- coding: cp949 -*-
#file_fuzzer.py

from pydbg import *
from pydbg.defines import *
from ctypes import *
from my_debugger_defines import *
import utils
import random
import sys
import struct
import threading
import os
import shutil
import time
import getopt

kernel32=windll.kernel32

class file_fuzzer:

    def __init__(self, exe_path, ext, notify):
        self.h_process = None
        self.h_thread = None
        self.context = None 
        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.crash = None
        self.send_notify = False
        self.pid = None
        self.in_accessv_handler = False
        self.dbg = None
        self.running = False
        self.ready = False

        self.test_cases = [ "%s%n%s%n%s%n", "\xff", "\x00" , "A" ]

    def file_picker(self): #examples폴더 내에 있는 avi 파일을 리턴 
        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\\test.%s" % self.ext)

        return file

    def fuzz(self):
        while 1:
            if self.running == False:
                # 퍼징을 수행하고 있는지 확인하는 조건
                self.test_file = self.file_picker()
                self.mutate_file() # 변형을 가할 파일을 mutate_file 함수에 전달 


                # 디버거 스레드를 실행시킨다.
                pydbg_thread = threading.Thread(target=self.start_debugger) # 변형한 문서에 대한 파싱을 수행할 애플리케이션을
                pydbg_thread.setDaemon(0)                                   # 실행시키는 디버거 스레드를 생성 
                pydbg_thread.start()                                        # 커맨드라인 파라미터를 이용해 애플리케이션에 파싱할 문서를 전달. 

                while self.pid == None:                                     # 디버거 스레드가 대상 에플리케이션의 PID를 등록할 때까지 대기한다.
                        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 "[*] 디버거가 현재 몇번째 실행 되고있나요?: %d" % self.iteration
        self.runnig = True
        self.dbg = pydbg()

        self.dbg.set_callback(EXCEPTION_ACCESS_VIOLATION,self.check_accessv)
        pid = self.dbg.load(self.exe_path, "TEST\\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 "[*] 크래시를 찾았다!!!@_@"
        self.in_accessv_handler = True
        crash_bin = utils.crash_binning.crash_binning()
        crash_bin.record_crash(dbg)
        self.crash = crash_bin.crash_synopsis()
        print self.crash

        # 에러 정보를 저장한다.
        crash_fd = open("crashes\\crash-%d" % self.iteration,"w")
        crash_fd.write(self.crash)

        # 파일을 백업한다.
        shutil.copy("TEST\\test.%s" % self.ext, "crashes\\%d.%s" % (self.iteration,self.ext))
        shutil.copy("examples\\%s" % self.test_file,"c:\\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 "[*] 지금 모니터링 하고 있는 process pid는 : %d 입니다." % self.pid
        while counter <3:
            time.sleep(1)
            counter+=1
        if self.in_accessv_handler != True:
            list = self.enumerate_threads()
            for thread in list:
                thread_context = self.get_thread_context(thread)
                print "[*] Dumpoing registers for thread ID : 0x%08x" % thread
                print "[*] EIP : 0x%08x" % thread_context.Eip
                print "[*] ESP : 0x%08x" % thread_context.Esp
                print "[*] EBP : 0x%08x" % thread_context.Ebp
                print "[*] EAX : 0x%08x" % thread_context.Eax
                print "[*] EBX : 0x%08x" % thread_context.Ebx
                print "[*] ECX : 0x%08x" % thread_context.Ecx
                print "[*] EDX : 0x%08x" % thread_context.Edx
                print "[*] END DUMP "
                
            time.sleep(1)
            self.dbg.terminate_process()
            self.pid = None
            self.running = False

        else:
            print "[*] 접근 위반 핸들러가 작동중입니다. 기다려주세요."

            while self.running:
                time.sleep(1)

    def mutate_file(self):
        # 파일의 내용을 버퍼로 읽어 들인다.
        fd = open("TEST\\test.%s" % self.ext, "rb")
        stream = fd.read()
        
        fd.close()

        # 퍼징의 가장 핵심적인 부분이다.
        # 임의의 test_case를 선택해 파일 내부의 임의의 위치에 적용한다.
        test_case = self.test_cases[random.randint(0,len(self.test_cases)-1)] # 전역변수인 test_case 리스트에서 임의의 test_case 하나를 선택한다.
        stream_length = len(stream) 
        rand_offset = random.randint(0, stream_length - 1) # 임의의 파일 오프셋과 
        rand_len = random.randint(1,1000)                  # 파일 데이터의 크기를 구한다. 

        # 선택한 test_case를 반복시킨다.
        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\\test.%s" % self.ext, "wb")
        fd.write(fuzz_file)
        fd.close()

        return

  
    def open_thread(self,thread_id): #쓰레드 핸들 구하는 함수 

        h_thread = kernel32.OpenThread(THREAD_ALL_ACCESS,None,thread_id)

        if h_thread is not None:
            return h_thread
        else:
            print "[*] 쓰레드 핸들 얻기 실패 ㅠㅠ"
            return False

    def enumerate_threads(self): # 현재 실행되고 있는 쓰레드 리스트 구하는 함수
        thread_entry=THREADENTRY32()
        thread_list=[]
        snapshot = kernel32.CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD,self.pid)

        if snapshot is not None:
            #먼저 구조체의 크기를 설정해야 한다.
            thread_entry.dwSize = sizeof(thread_entry)
            success=kernel32.Thread32First(snapshot,byref(thread_entry))

            while success:
                if thread_entry.th32OwnerProcessID == self.pid:
                    thread_list.append(thread_entry.th32ThreadID)
                    
                success = kernel32.Thread32Next(snapshot,byref(thread_entry))
            kernel32.CloseHandle(snapshot)
            return thread_list
        else:
            return False 

    def get_thread_context(self,thread_id=None,h_thread=None): # 레지스터값이 들어있는 context 구조체 리턴 
        context=CONTEXT()
        context.ContextFlags = CONTEXT_FULL | CONTEXT_DEBUG_REGISTERS

        # 스레드의 핸들을 구한다.
        h_thread = self.open_thread(thread_id)
        if kernel32.GetThreadContext(h_thread,byref(context)):
            kernel32.CloseHandle(h_thread)
            return context
        else:
            return False
    
def print_usage():
    print "[*]"
    print "[*] file_fuzzer.py -e <excutable path=""> -x <file extension="">"
    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()
Posted by 케데