[컴][디버그] 하드웨어 브레이크포인트 - hardware breakpoint

리버싱 / reverse engineering


아직 부정확한 부분이 있어서, 정리차원으로 적어놓는다. PyDbg 부분에 대한 분석 이후에 update 를 해야 할 듯 하다.


Procedure

  1. thread 를 모두 열거하고(enumerate)
  2. 거기서 CPU context record 를 가져온다.

    DR0 ~ DR3 중에 한 register 를 수정할 것이다. 물론 사용 중이지 않은 녀석을 수정해야 한다. 그렇지 않으면 우리가 원하는 곳에서 멈추지 않을 것이다.
  3. DR0~DR3 에 내가 breakpoint 를 설정하길 원하는 address를 적어놓으면 된다.
  4. 그러고 나서 DR7 register 의 bit 을 flip 해서 breakpoint 를 enable 시키고, type 과 length 를 설정한다.[ref. 1]
  5. hardware breakpoint 를 set 하고 나서는 이 breakpoint 가 걸렸을 때(thrown) 우리가 수행하고 싶은 function(handler) 를 만들어야 한다.

    hardware breakpoint 는 INT1 을 발생시킨다. 그러므로 INT1 부분에서 수정을 해주면 된다.[ref. 1]
코드는 debugger 가 현재 내 debugger 하나만 실행되고 있다는 가정으로 되어있다.

그래서 debugger 내부적으로 hardware_breakpoints(이하 hwbp) 라는 배열을 갖고 있어서 지금 hwbp 가 사용되고 있는지 아닌지에 정보를 가지고 있는다.

Debug register 값 가져오기

우리는 이전에 배운 snapshot 으로 thread 들을 traverse 하면서 그 중에 우리가 원하는 process_id(pid) 를 가진 녀석들(즉, 우리가 원하는 process 가 갖고 있는 thread 들) 의 thread_id 를 구한다.이 구한 thread_id 를 가지고 OpenThread(), GetThreadContext() 를 거쳐서 thread 의 context 를 얻게 된다. 이 부분은 아래 포스트를 참고하자.
windows 에서 thread 의 context 를 가져오는 법 - snapshot
thead_context 를 보면 structure 내부에 아래와 같이 Debug register 변수가 있다.
DWORD Dr0;
DWORD Dr1;
DWORD Dr2;
DWORD Dr3;
DWORD Dr6;
DWORD Dr7;


아마도 thead context structure 에 deubg register 부분이 있는 것으로 봐서는 thread 의 context switching 이 일어날 때 debug register 의 값들도 다시 set 되는 것 같다.(추측)

Debug Register 설정


여하튼 이제 thread_context 에 있는 이 값들을 설정해 줘서 breakpoint 를 지정하면 된다. ref.1 의 DR7 부분을 보면 값을 어떤 값을 지정해야 하는 지 알 수 있을 것이다. 아래는 관련된 python 코드이다.
# Enable the appropriate flag in the DR7
# register to set the breakpoint
context.Dr7 |= 1 << (available * 2)

# Save the address of the breakpoint in the
# free register that we found
if   available == 0: context.Dr0 = address
elif available == 1: context.Dr1 = address
elif available == 2: context.Dr2 = address
elif available == 3: context.Dr3 = address

# Set the breakpoint condition
context.Dr7 |= condition << ((available * 4) + 16)

# Set the length
context.Dr7 |= length << ((available * 4) + 18)

# Set this threads context with the debug registers
# set
h_thread = kernel32.OpenThread(THREAD_ALL_ACCESS, None, thread_id)
kernel32.SetThreadContext(h_thread,byref(context))

코드에서도 보는 것처럼 SetThreadContext() 를 통해서 thread context 값을 변경시켜 준다. 여기서는 process 내에서 printf 가 호출될 때마다 interrupt 를 발생시키려고, 모든 thread 의 context 에 있는 debug register 에 printf 주소를 설정해 놓는다.

Demo

windows7 에서 PyDbg 를 가지고 테스트 중인데, hardware breakpoint 는 동작하지 않는다. 이유는 아직인데, process 의 privilege 의 문제이지 않을까? 추측한다. [ref. 4]
# source from Gray Hat Python
# edited by namh

memory_breakpoints = {}
hardware_breakpoints = {}
kernel32 = windll.kernel32
pid = raw_input("Enter the PID of the process to attach to: ")

# attach
kernel32.DebugActiveProcess(int(pid))

# func_resolve
dll = "msvcrt.dll"
function = "printf"

handle  = kernel32.GetModuleHandleA(dll)
address = kernel32.GetProcAddress(handle, function)

kernel32.CloseHandle(handle)



# bp_set_hw
bp_set_hw(address,1,HW_EXECUTE)



# run
debug_event    = DEBUG_EVENT()
continue_status = DBG_CONTINUE

while True :

    if kernel32.WaitForDebugEvent(byref(debug_event),100):
        # grab various information with regards to the current exception.
        thread_id = debug_event.dwThreadId
        h_thread = kernel32.OpenThread(THREAD_ALL_ACCESS, None, thread_id)
        
        context = CONTEXT()
        context.ContextFlags = CONTEXT_FULL | CONTEXT_DEBUG_REGISTERS
                        
        context = kernel32.GetThreadContext(h_thread, byref(context))

        
        
                   
        print "Event Code: %d Thread ID: %d" % \
            (debug_event.dwDebugEventCode,debug_event.dwThreadId)
        
        if debug_event.dwDebugEventCode == EXCEPTION_DEBUG_EVENT:
            exception = debug_event.u.Exception.ExceptionRecord.ExceptionCode
            exception_address = debug_event.u.Exception.ExceptionRecord.ExceptionAddress
            
            # call the internal handler for the exception event that just occured.
            if exception == EXCEPTION_ACCESS_VIOLATION:
                print "Access Violation Detected."

            elif exception == EXCEPTION_BREAKPOINT:
                print "[*] Exception address: 0x%08x" % exception_address

                # check if the breakpoint is one that we set
                if not memory_breakpoints.has_key(exception_address):
                    continue_status = DBG_CONTINUE

            elif exception == EXCEPTION_GUARD_PAGE:
                print "Guard Page Access Detected."

            elif exception == EXCEPTION_SINGLE_STEP:
                exception_handler_single_step()
            
        kernel32.ContinueDebugEvent(debug_event.dwProcessId,\
                                 debug_event.dwThreadId, continue_status)



def bp_set_hw(address, length, condition):
    # Check for a valid length value
    if length not in (1, 2, 4):
        return False
    else:
        length -= 1
        
    # Check for a valid condition
    if condition not in (HW_ACCESS, HW_EXECUTE, HW_WRITE):
        return False

    # Check for available slots
    if not hardware_breakpoints.has_key(0):
        available = 0
    elif not hardware_breakpoints.has_key(1):
        available = 1
    elif not hardware_breakpoints.has_key(2):
        available = 2
    elif not hardware_breakpoints.has_key(3):
        available = 3
    else:
        return False

    # We want to set the debug register in every thread
    for thread_id in enumerate_threads(int(pid)):
     h_thread = kernel32.OpenThread(THREAD_ALL_ACCESS, None, thread_id)
     context = CONTEXT()
        context.ContextFlags = CONTEXT_FULL | CONTEXT_DEBUG_REGISTERS

        kernel32.GetThreadContext(h_thread, byref(context))
        


        # Enable the appropriate flag in the DR7
        # register to set the breakpoint
        context.Dr7 |= 1 << (available * 2)

        # Save the address of the breakpoint in the
        # free register that we found
        if   available == 0: context.Dr0 = address
        elif available == 1: context.Dr1 = address
        elif available == 2: context.Dr2 = address
        elif available == 3: context.Dr3 = address

        # Set the breakpoint condition
        context.Dr7 |= condition << ((available * 4) + 16)

        # Set the length
        context.Dr7 |= length << ((available * 4) + 18)

        # Set this threads context with the debug registers
        # set
        h_thread = kernel32.OpenThread(THREAD_ALL_ACCESS, None, thread_id)
        kernel32.SetThreadContext(h_thread,byref(context))

    # update the internal hardware breakpoint array at the used slot index.
    hardware_breakpoints[available] = (address,length,condition)


def enumerate_threads(pid):
              
    thread_entry     = THREADENTRY32()
    thread_list      = []
    snapshot         = kernel32.CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, pid)
    
    if snapshot is not None:
    
        # You have to set the size of the struct
        # or the call will fail
        thread_entry.dwSize = sizeof(thread_entry)

        success = kernel32.Thread32First(snapshot, byref(thread_entry))

        while success:
            if thread_entry.th32OwnerProcessID == pid:
                thread_list.append(thread_entry.th32ThreadID)

            success = kernel32.Thread32Next(snapshot, byref(thread_entry))
        
        # No need to explain this call, it closes handles
        # so that we don't leak them.
        kernel32.CloseHandle(snapshot)
        return thread_list
    else:
        return False



References



  1. http://i5on9i.blogspot.kr/2013/04/breakpoint.html
  2. Writing a basic Windows debugger, 2011
  3. Writing a basic Windows debugger Part 2
  4. 브레이크 포인트 탐지 기법 :  Part 1. Hardware Breakpoint Detection &amp; Removing

댓글 없음:

댓글 쓰기