아직 부정확한 부분이 있어서, 정리차원으로 적어놓는다. PyDbg 부분에 대한 분석 이후에 update 를 해야 할 듯 하다.
Procedure
- thread 를 모두 열거하고(enumerate)
- 거기서 CPU context record 를 가져온다.
DR0 ~ DR3 중에 한 register 를 수정할 것이다. 물론 사용 중이지 않은 녀석을 수정해야 한다. 그렇지 않으면 우리가 원하는 곳에서 멈추지 않을 것이다. - DR0~DR3 에 내가 breakpoint 를 설정하길 원하는 address를 적어놓으면 된다.
- 그러고 나서 DR7 register 의 bit 을 flip 해서 breakpoint 를 enable 시키고, type 과 length 를 설정한다.[ref. 1]
- hardware breakpoint 를 set 하고 나서는 이 breakpoint 가 걸렸을 때(thrown) 우리가 수행하고 싶은 function(handler) 를 만들어야 한다.
hardware breakpoint 는 INT1 을 발생시킨다. 그러므로 INT1 부분에서 수정을 해주면 된다.[ref. 1]
그래서 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 를 가져오는 법 - snapshotthead_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
댓글 없음:
댓글 쓰기