在Windows平臺下,應用程式為了保護自己不被除錯器除錯會通過各種方法限制行程除錯自身,通常此類反除錯技識訓限制我們對其進行軟體逆向與漏洞分析,下面是一些常見的反除錯保護方法:
- IsDebuggerPresent:檢查當前程式是否在除錯器環境下運行,
- OutputDebugString:向除錯器發送特定的字串,以檢查是否有除錯器在運行,
- CloseHandle:檢查特定的句柄是否關閉,以判斷是否有除錯器在運行,
- GetTickCount:檢查程式運行的時間,以判斷是否有除錯器在運行,
- PEB (Process Environment Block):檢查PEB資料結構中的特定欄位,以判斷是否有除錯器在運行,
- SEH (Structured Exception Handling):檢查例外處理程式是否被替換,以判斷是否有除錯器在運行,
我們以第一種IsDebuggerPresent
反除錯為例,該函式用于檢查當前程式是否在除錯器的環境下運行,函式回傳一個布林值,如果當前程式正在被除錯,則回傳True,否則回傳False,
函式通過檢查特定的記憶體地址來判斷是否有除錯器在運行,具體來說,該函式檢查了PEB(行程環境塊)
資料結構中的_PEB_LDR_DATA
欄位,該欄位標識當前程式是否處于除錯狀態,如果該欄位的值為1,則表示當前程式正在被除錯,否則表示當前程式沒有被除錯,
獲取PEB的方式有許多,雖然LyScript插件內提供了get_peb_address(dbg.get_process_id())
系列函式可以直接獲取到行程的PEB資訊,但為了分析實作原理,筆者首先會通過代碼來實作這個功能;
如下代碼,通過在目標程式中創建一個堆空間并向其中寫入匯編指令,最后將程式的EIP暫存器設定為堆空間的首地址,以使得程式運行時執行堆空間中的匯編指令,
具體來說,該代碼通過呼叫MyDebug
類的create_alloc
方法創建一個堆空間,并通過呼叫assemble_at
方法向堆空間寫入匯編指令,該代碼先寫入mov eax,fs:[0x30]
指令,該指令將FS暫存器
的值加上0x30
的偏移量存入EAX
暫存器,從而得到_PEB
資料結構的地址,
然后,代碼再寫入mov eax,[eax+0x0C]
指令,該指令將EAX暫存器加上0x0C
的偏移量后的值存入EAX暫存器,從而得到_PEB_LDR_DATA
資料結構的地址,最后,寫入jmp eip
指令,以使得程式回到原來的EIP
位置,最后,代碼通過呼叫set_register
方法設定EIP暫存器的值為堆空間的首地址,以使得程式運行時執行堆空間中的匯編指令,
from LyScript32 import MyDebug
if __name__ == "__main__":
dbg = MyDebug(address="127.0.0.1")
dbg.connect()
# 保存當前EIP
eip = dbg.get_register("eip")
# 創建堆
heap_addres = dbg.create_alloc(1024)
print("堆空間地址: {}".format(hex(heap_addres)))
# 寫出匯編指令
# mov eax,fs:[0x30] 得到 _PEB
dbg.assemble_at(heap_addres,"mov eax,fs:[0x30]")
asmfs_size = dbg.get_disasm_operand_size(heap_addres)
# 寫出匯編指令
# mov eax,[eax+0x0C] 得到 _PEB_LDR_DATA
dbg.assemble_at(heap_addres + asmfs_size, "mov eax, [eax + 0x0C]")
asmeax_size = dbg.get_disasm_operand_size(heap_addres + asmfs_size)
# 跳轉回EIP位置
dbg.assemble_at(heap_addres+ asmfs_size + asmeax_size , "jmp {}".format(hex(eip)))
# 設定EIP到堆首地址
dbg.set_register("eip",heap_addres)
dbg.close()
當這段讀入匯編指令被執行時,此時PEB入口
地址將被回傳給EAX
暫存器,用戶只需要取出該暫存器中的引數即可實作讀取行程PEB
的功能,
當PEB入口地址得到之后,只需要檢查PEB+2
的位置標志,通過write_memory_byte()
函式向此處寫出0即可繞過反除錯,從而讓程式可以被正常除錯,
from LyScript32 import MyDebug
if __name__ == "__main__":
# 初始化
dbg = MyDebug()
dbg.connect()
# 通過PEB找到除錯標志位
peb = dbg.get_peb_address(dbg.get_process_id())
print("除錯標志地址: 0x{:x}".format(peb+2))
flag = dbg.read_memory_byte(peb+2)
print("除錯標志位: {}".format(flag))
# 將除錯標志設定為0即可過掉反除錯
nop_debug = dbg.write_memory_byte(peb+2,0)
print("反除錯繞過狀態: {}".format(nop_debug))
dbg.close()
這里筆者繼續拓展一個新知識點,如何實作繞過行程列舉功能,病毒會利用行程列舉函式Process32FirstW
及Process32NextW
列舉所有運行的行程以確認是否有除錯器在運行,我們可以在特定的函式開頭處寫入SUB EAX,EAX RET
指令讓其無法呼叫列舉函式從而失效,寫入匯編指令集需要依賴于set_assemble_opcde
函式,只需要向函式內傳入記憶體地址,則自動替換地址處的匯編指令集;
from LyScript32 import MyDebug
# 得到所需要的機器碼
def set_assemble_opcde(dbg,address):
# 得到第一條長度
opcode_size = dbg.assemble_code_size("sub eax,eax")
# 寫出匯編指令
dbg.assemble_at(address, "sub eax,eax")
dbg.assemble_at(address + opcode_size , "ret")
if __name__ == "__main__":
# 初始化
dbg = MyDebug()
dbg.connect()
# 得到函式所在記憶體地址
process32first = dbg.get_module_from_function("kernel32","Process32FirstW")
process32next = dbg.get_module_from_function("kernel32","Process32NextW")
print("process32first = 0x{:x} | process32next = 0x{:x}".format(process32first,process32next))
# 替換函式位置為sub eax,eax ret
set_assemble_opcde(dbg, process32first)
set_assemble_opcde(dbg, process32next)
dbg.close()
當上述代碼被運行后,則Process32FirstW
與Process32FirstW
函式位置將被依次寫出回傳指令,從而讓行程列舉失效,輸出效果圖如下所示;
原文地址
https://www.lyshark.com/post/8b9dc8dc.html
文章作者:lyshark (王瑞)文章出處:https://www.cnblogs.com/LyShark/p/17536708.html
本博客所有文章除特別宣告外,均采用 BY-NC-SA 許可協議,轉載請注明出處!
轉載請註明出處,本文鏈接:https://www.uj5u.com/qiye/556858.html
標籤:其他
下一篇:返回列表