본문 바로가기
파이썬 스터디 과제/파이썬 해킹 프로그래밍

3장-2 CPU 레지스터 상태 얻기

by laoching 2015. 1. 14.
728x90
반응형

CPU 레지스터 상태 얻기

디버거는 언제든지 CPU 레지스터의 상태 정보를 캡처할 수 있어야 한다.

그러기 위해 OpenThread() 함수를 이용해 디버깅 대상 프로세스의 핸들을 얻어야 한다.

OpenThread() 함수의 프로토타입

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

HANDLE WINAPI OpneThread(

DWORD dwDesiredAccess,

BOOL bInheritHandle,

DWORD dwThreadId

);

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

OpenProcess() 함수의 프로토타입

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

HANDLE WINAPI OpenProcess(

DWORD dwDesiredAccess,

BOOL bInheritHandle,

DWORD dwProcessId

);

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

차이점이 보이는가?

OpenThread() 함수는 파라미터로 프로세스 ID가 아닌 스레드 ID를 전달한다는 점을 제외하고는 OpenProcess()함수와 상당히 유사하다.


프로세스 내부에서 실행 중인 모든 스레드 리스트 구하기 >> 스레드 리드트에서 원하는 스레드 선택 >> OpenThread() 함수로 핸들 구하기

위에 있는것이 과정이다. 먼저 스레드 리스트를 어떻게 구하는지 알아보자.


스레드 리스트

프로세스에서 레지스터의 상태 정보를 구하려면 해당 프로세스 내부에서 실행 중인 모든 스레드의 리스트를 구해야 한다.

여기서 스레드란? : 프로세스에서 실제적으로 동작 중인 것을 말한다.

스레드의 종류 : 사용자 스레드(커널 영역의 상위에서 지원, 일반적으로 사용자 레벨의 라이브러리를 통해 구현), 커널 스레드(운영체제가 지원하는 스레드 기능으로 구성) 

멀티스레드는 애플리케이션이 아니라도 최소한 하나의 스레드, 메인 스레드가 존재한다.

멀티스레드란? : 프로그램 환경에 따라 둘 이상의 스레드를 동시에 실행할 수 있다. 이러한 실행 방식을 멀티스레드라고 한다.

kernel32.dll에서 익스포트하는 CreateToolhelp32Snapshot()이라는 함수를 이용하면 스레드 리스트를 구할 수 있다.

이 함수는 매우 강력해서 프로세스와 스레드 리스트, 프로세스의 힙 리스트뿐만 아니라 프로세스에 로드된 모듈 리스트를 구할 수 있다.

프로세스의 힙이란? : 프로세스에 기본적으로 할당되는 힙 (http://dakuo.tistory.com/128참고)

CreateToolhelp32Snapshot() 함수의 프로토타입

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

HANDLE WINAPI CreateToolhelp32Snapshot(

DWORD dwFlags,

DWORD th32ProcessID

);

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

dwFlags 파라미테어 의해 구할 정보의 종류(스레드, 프로세스, 모듈, 힙)가 결정된다.

dwFlags 파라미터에 TH32CS_SNAPTHREAD(0x00000004)를 전달하면 스레드의 리스트를 구할 수 있다.

th32ProcessID 파라미터는 단순히 스냅샷을 구하고자 하는 프로세스의 PID를 의미한다.

하지만 이것은 사용될 수 있는 모드가 한정되어 있다.

TH32CS_SNAPMODULE, TH32CS_SNAPMODULE32, TH32CS_SNAPHEAPLIST, TH32CS_SNAPALL

따라서 구한 스레드가 어느 프로세스에 속한것인지 추가적으로 판단해야 한다.

CreateToolhelp32Snapshot()함수가 성공하면 스냅샷 객체에 대한 핸들을 반환하며, 해당 핸들을 이용해 추가적인 정보를 구할 수 있다.

스레드 리스트를 구했다면 그 리스트를 열거해야 한다고한다.

이를 위해 사용하는 함수가 Thread32First() 함수다.

Thread32First() 함수의 프로토타입

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

BOOL WINAPI Thread32First(

HANDLE hSnapshot,

LPTHREADENTRY32 lpte

);

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

hSnapshot 파라미터에는 CreateToolhelp32Snapshot() 함수가 반환한 핸들을 전달하고, lpte 파라미터에는 THREADENTY32 구조체의 포인터를 전달한다. Thread32First() 함수가 성공하면 첫 번째 스레드에 대한 정보가 lpte파라미터를 통해 전달된다.

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

typedef struct THREADENTRY32{

DWORD dwSize;

DWORD cntUsage;

DWORD th32ThreadID;

DWORD th32OwnerPRocessID;

LONG tpBasePri;

LONG tpDeltaPri;

DWORD dwFlags;

};

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

dwSIze, th32ThreadID, th32OwnerPRocessID <<- 이 필드 세 개를 잘 살펴 보자.

dwSize 필드

Thread32First() 함수를 호출하기 전에 구조체 자체의 크기 값으로 초기화돼야 한다.

th32ThreadID 필드

TID를 의미하며, 이 TID 값을 앞에서 살펴본 OpentThread() 함수의 dwThreadId 파라미터 값으로 이용할 수 있다.

th32OwnerProcessID 필드

스레드가 속한 프로세스의 PID를 의미한다.

스레드 리스트 중에 어느 스레드가 디버깅 대상 프로세스에 속하는 것인지 판단하기 위해 디버깅 대상 프로세스의 PID와 스레드의 th32OwnerPRocessID를 비교해야 한다.

두 PID가 일치한다면 해당 스레드가 디버깅 대상 프로세스의 스레드임을 알 수 있다.

일단 첫 번째 스레드 정보를 구했으면 Thread32Next() 함수를 이용해 계속해서 스냅샷 내의 다음 스레드 엔트리 정보를 구할 수 있다.

Thread32Next() 함수의 파라미터는 Thread32First() 함수의 파라미터와 동일하다.

스레드 리스트의 마지막 스레드 정보에 도달할때까지 루프를 돌면서 스레드 정보를 살펴보면 된다.


종합

스레드의 핸들을 구할 수 있게 됐으니 모든 레지스터의 내용을 구하면 된다. 이를 위해서는 GetThreadContext() 함수를 사용한다.

SetThreadContext() 함수를 사용하면 GetThreadContext() 함수를 통해 얻은 컨텍스트 레코드의 값을 변경할 수 있다.

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

BOOL WINAPI GetThreadContext(

HANDLE hTread,

LPCONTEXT lpContext

);

BOOL WINAPI SetThreadContext(

HANDLE hTread,

LPCONTEXT lpContext

);

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

hThread 파라미터는 OpenThread() 함수를 통해 얻은 스레드 핸들이다.

lpContext 파라미터는 모든 레지스터의 값을 담고 있는 CONTEXT 구조체에 대한 포인터다.


CONTEXT 구조체

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

typedef struct CONTEXT {

DWORD ContextFlangs;

DWORD Dr0;

DWORD Dr1;

DWORD Dr2;

DWORD Dr3;

DWORD Dr4;

DWORD Dr5;

DWORD Dr6;

DWORD Dr7;

FLOATING_SAVE_AREA FloatSave;

DWORD SegGs;

DWORD SegFs;

DWORD SegEs;

DWORD SegDs;

DWORD Edi;

DWORD Esi;

DWORD Ebx;

DWORD Edx;

DWORD Ecx;

DWORD Eax;

DWORD Ebp;

DWORD Eip;

DWORD SegCs;

DWORD EFlags;

DWORD Esp;

DWORD SegSs;

BYTE ExtendedRegisters[MAXIMUM_SUPPORTED_EXTENSION];

};

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

위의 정의를 보면 디버그 레지스터와 세그먼트 레지스터를 포함한 모든 레지스터가 이 구조체 안에 포함된다.

이 구조체는 매우 중요하다고 한다.

이제 저번에 짯던 my_debugger.py에 스레드 리스트와 레지스터 값을 구하는 기능을 추가해보자.

my_debugger.py

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

# -*- coding: cp949 -*-
from ctypes import *
from my_debugger_defines import *

kernel32 = windll.kernel32

class debugger():

    def __init__(self):
        self.h_process       =     None
        self.pid             =     None
        self.debugger_active =     False
        self.h_thread        =     None
        self.context         =     None

    def load(self,path_to_exe):

        creation_flags = DEBUG_PROCESS

        startupinfo         = STARTUPINFO()
        process_information = PROCESS_INFORMATION()

        startupinfo.dwFlags     = 0x1
        startupinfo.wShowWindow = 0x0

        startupinfo.cb = sizeof(startupinfo)
       
        if kernel32.CreateProcessA(path_to_exe,
                                   None,
                                   None,
                                   None,
                                   None,
                                   creation_flags,
                                   None,
                                   None,
                                   byref(startupinfo),
                                   byref(process_information)):
           
            print "[*] We have successfully launched the process!"
            print "[*] PID : %d" % process_information.dwProcessId"

            self.h_process = self.open_process(process_information.dwProcessId)

        else:   
            print "[*] Error : 0x%08x." % kernel32.GetLastError()


    def open_process(self,pid):

        h_process = kernel32.OpenProcess(PROCESS_ALL_ACCESS,False,pid)
        return h_process
   
   
    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 "[*] Could not obtain a valid thread handle."
            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.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 attach(self,pid):
       
        self.h_process = self.open_process(pid)

        if kernel32.DebugActiveProcess(pid):
            self.debugger_active = True
            self.pid             = int(pid)
                                 
        else:
            print "[*] Unable to attach to the process."
                
    def run(self):

        while self.debugger_active == True:
            self.get_debug_event()
           
   
    def get_debug_event(self):
       
        debug_event    = DEBUG_EVENT()
        continue_status = DBG_CONTINUE
       
        if kernel32.WaitForDebugEvent(byref(debug_event),INFINITE):

            raw_input("Press a key to continue...")
            self.debugger_active = False
            kernel32.ContinueDebugEvent(
                    debug_event.dwProcessId,
                    debug_event.dwThreadId,
                    continue_status )
           
    def detach(self):
       
        if kernel32.DebugActiveProcessStop(self.pid):
            print "[*] Finished debugging. Exiting..."
            return True
        else:
            print "There was an error"
            return False

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

my_test도 변형해야 한다.

my_test.py

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

# -*- coding: cp949 -*-
import my_debugger

debugger = my_debugger.debugger()
pid = raw_input("Enter the PID of the process to attach to: ")
debugger.attach(int(pid))

list = debugger.enumerate_threads()

for thread in list:
   
    thread_context = debugger.get_thread_context(thread)
   
    # 레지스터의 내용을 출력한다.
    print "[*] Dumping registers for thread ID: 0x%08x" % thread
    print "[**] EIP: 0x%08x" % thread_context.Eip
    print "[**] EIP: 0x%08x" % thread_context.Esp
    print "[**] EIP: 0x%08x" % thread_context.Ebp
    print "[**] EIP: 0x%08x" % thread_context.Eax
    print "[**] EIP: 0x%08x" % thread_context.Ebx
    print "[**] EIP: 0x%08x" % thread_context.Ecx
    print "[**] EIP: 0x%08x" % thread_context.Edx
    print "[*] END DUMP"

debugger.detach()

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

실행결과 :

.

.

.

이제는 CPU의 레지스터 값을 알아낼 수 있게 됐다.

728x90
반응형

댓글