아직 부정확한 부분이 있어서, 정리차원으로 적어놓는다. 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 가 현재 내 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
- http://i5on9i.blogspot.kr/2013/04/breakpoint.html
- Writing a basic Windows debugger, 2011
- Writing a basic Windows debugger Part 2
- 브레이크 포인트 탐지 기법 : Part 1. Hardware Breakpoint Detection & Removing