LyScript 插件集成的内置API函数可灵活的实现绕过各类反调试保护机制,前段时间发布的那一篇文章并没有详细讲解各类反调试机制的绕过措施,本次将补充这方面的知识点,运用LyScript实现绕过大多数通用调试机制,实现隐藏调试器的目的。
我们以此实现Patches如下函数:
IsDebuggerPresent
ZwQueryInformationProcess
CheckRemoteDebuggerPresent
PEB.IsDebugged
PEB.ProcessHeap.Flag
PEB.NtGlobalFlag
PEB.Ldr 0xFEEEFEEE filling
GetTickCount
ZwQuerySystemInformation
FindWindowA
FindWindowW
FindWindowExA
FindWindowExW
EnumWindows
首先第一步我们需要自己封装实现一个反汇编转机器码的函数,其作用是当用户传入汇编列表时,自动将其转为机器码并输出为列表格式。
from LyScript32 import MyDebugdef get_opcode_from_assemble (dbg_ptr,asm ): byte_code = bytearray () addr = dbg_ptr.create_alloc(1024 ) if addr != 0 : asm_size = dbg_ptr.assemble_code_size(asm) write = dbg_ptr.assemble_write_memory(addr,asm) if write == True : for index in range (0 ,asm_size): read = dbg_ptr.read_memory_byte(addr + index) byte_code.append(read) dbg_ptr.delete_alloc(addr) return byte_code else : return bytearray (0 ) def GetOpCode (dbg, Code ): ShellCode = [] for index in Code: ref = get_opcode_from_assemble(dbg,index) for opcode in ref: ShellCode.append(opcode) return ShellCode if __name__ == "__main__" : dbg = MyDebug() connect = dbg.connect() ShellCode = GetOpCode(dbg, ["DB 0x64" ,"mov eax,dword ptr ds:[18]" ,"sub eax,eax" ,"ret" ]) print (ShellCode) dbg.close()
输出效果如下:
Patch_PEB
PEB结构存在许多反调试变量,首先我们需要先将这些变量填充为空。
from LyScript32 import MyDebugdef get_opcode_from_assemble (dbg_ptr,asm ): byte_code = bytearray () addr = dbg_ptr.create_alloc(1024 ) if addr != 0 : asm_size = dbg_ptr.assemble_code_size(asm) write = dbg_ptr.assemble_write_memory(addr,asm) if write == True : for index in range (0 ,asm_size): read = dbg_ptr.read_memory_byte(addr + index) byte_code.append(read) dbg_ptr.delete_alloc(addr) return byte_code else : return bytearray (0 ) def GetOpCode (dbg, Code ): ShellCode = [] for index in Code: ref = get_opcode_from_assemble(dbg,index) for opcode in ref: ShellCode.append(opcode) return ShellCode def Patch_PEB (dbg ): PEB = dbg.get_peb_address(dbg.get_process_id()) if PEB == 0 : return 0 dbg.write_memory_byte(PEB + 0x2 ,GetOpCode(dbg,["db 0" ])[0 ]) print ("补丁地址: {}" .format (hex (PEB+0x2 ))) temp = dbg.read_memory_dword(PEB + 0x18 ) temp += 0x10 dbg.write_memory_dword(temp, GetOpCode(dbg,["db 0" ])[0 ]) print (("补丁地址: {}" .format (hex (temp)))) dbg.write_memory_dword(PEB+0x68 , 0 ) print (("补丁地址: {}" .format (hex (PEB+0x68 )))) addr = dbg.read_memory_dword(PEB + 0x0c ) while addr != 0 : addr += 1 try : b = dbg.read_memory_dword(addr) c = dbg.read_memory_dword(addr + 4 ) print (b) if (b == 0xFEEEFEEE ) and (c == 0xFEEEFEEE ): dbg.write_memory_dword(addr,0 ) dbg.write_memory_dword(addr + 4 , 0 ) print ("patch" ) except Exception: break if __name__ == "__main__" : dbg = MyDebug() connect = dbg.connect() Patch_PEB(dbg) dbg.close()
Patch_IsDebuggerPresent
该函数用于检测自身是否处于调试状态,其C系列代码如下所示,绕过此种方式很简单,只需要在函数头部写出ret指令即可。
#include <Windows.h> #include <stdio.h> int _tmain(int argc, _TCHAR* argv[]){ BOOL ref = IsDebuggerPresent(); printf ("是否被调试: %d \n" , ref); getchar(); return 0 ; }
注意:此Api检查PEB中的值,因此如果修补PEB,则无需修补Api,这段绕过代码如下。
from LyScript32 import MyDebugdef get_opcode_from_assemble (dbg_ptr,asm ): byte_code = bytearray () addr = dbg_ptr.create_alloc(1024 ) if addr != 0 : asm_size = dbg_ptr.assemble_code_size(asm) write = dbg_ptr.assemble_write_memory(addr,asm) if write == True : for index in range (0 ,asm_size): read = dbg_ptr.read_memory_byte(addr + index) byte_code.append(read) dbg_ptr.delete_alloc(addr) return byte_code else : return bytearray (0 ) def GetOpCode (dbg, Code ): ShellCode = [] for index in Code: ref = get_opcode_from_assemble(dbg,index) for opcode in ref: ShellCode.append(opcode) return ShellCode def Patch_IsDebuggerPresent (dbg ): ispresent = dbg.get_module_from_function("kernel32.dll" ,"IsDebuggerPresent" ) print (hex (ispresent)) if (ispresent <= 0 ): print ("无法得到模块基地址,请以管理员方式运行调试器." ) return 0 ShellCode = GetOpCode(dbg, ["DB 0x64" , "mov eax,dword ptr ds:[18]" , "sub eax,eax" , "ret" ]) print (ShellCode) flag = 0 for index in range (0 ,len (ShellCode)): flag = dbg.write_memory_byte(ispresent + index,ShellCode[index]) if flag: flag = 1 else : flag = 0 return flag if __name__ == "__main__" : dbg = MyDebug() connect = dbg.connect() ref = Patch_IsDebuggerPresent(dbg) print ("补丁状态: {}" .format (ref)) dbg.close()
当程序运行后会向IsDebuggerPresent
函数写出返回,从而实现绕过调试的目的。
Patch_CheckRemoteDebuggerPresent
此Api调用ZwQueryInformationProcess
因此通常不需要对两者进行修补。
from LyScript32 import MyDebugdef get_opcode_from_assemble (dbg_ptr,asm ): byte_code = bytearray () addr = dbg_ptr.create_alloc(1024 ) if addr != 0 : asm_size = dbg_ptr.assemble_code_size(asm) write = dbg_ptr.assemble_write_memory(addr,asm) if write == True : for index in range (0 ,asm_size): read = dbg_ptr.read_memory_byte(addr + index) byte_code.append(read) dbg_ptr.delete_alloc(addr) return byte_code else : return bytearray (0 ) def GetOpCode (dbg, Code ): ShellCode = [] for index in Code: ref = get_opcode_from_assemble(dbg,index) for opcode in ref: ShellCode.append(opcode) return ShellCode def Patch_CheckRemoteDebuggerPresent (dbg ): ispresent = dbg.get_module_from_function("kernel32.dll" ,"CheckRemoteDebuggerPresent" ) print (hex (ispresent)) ShellCode = GetOpCode(dbg, [ "mov edi,edi" , "push ebp" , "mov ebp,esp" , "mov eax,[ebp+0xc]" , "push 0" , "pop dword ptr ds:[eax]" , "xor eax,eax" , "pop ebp" , "ret 8" ] ) print (ShellCode) flag = 0 for index in range (0 ,len (ShellCode)): flag = dbg.write_memory_byte(ispresent + index,ShellCode[index]) if flag: flag = 1 else : flag = 0 return flag if __name__ == "__main__" : dbg = MyDebug() connect = dbg.connect() ref = Patch_CheckRemoteDebuggerPresent(dbg) print ("写出状态: {}" .format (ref)) dbg.close()
写出效果如下:
Patch_GetTickCount
GetTickCount返回(retrieve)从操作系统启动所经过(elapsed)的毫秒数,常用于定时计数,绕过方式只需初始化即可。
from LyScript32 import MyDebugdef get_opcode_from_assemble (dbg_ptr,asm ): byte_code = bytearray () addr = dbg_ptr.create_alloc(1024 ) if addr != 0 : asm_size = dbg_ptr.assemble_code_size(asm) write = dbg_ptr.assemble_write_memory(addr,asm) if write == True : for index in range (0 ,asm_size): read = dbg_ptr.read_memory_byte(addr + index) byte_code.append(read) dbg_ptr.delete_alloc(addr) return byte_code else : return bytearray (0 ) def GetOpCode (dbg, Code ): ShellCode = [] for index in Code: ref = get_opcode_from_assemble(dbg,index) for opcode in ref: ShellCode.append(opcode) return ShellCode def Patch_GetTickCount (dbg ): ispresent = dbg.get_module_from_function("kernel32.dll" ,"GetTickCount" ) print (hex (ispresent)) ShellCode = GetOpCode(dbg, [ "mov edx,0x7ffe0000" , "sub eax,eax" , "add eax,0xB0B1560D" , "ret" ] ) print (ShellCode) flag = 0 for index in range (0 ,len (ShellCode)): flag = dbg.write_memory_byte(ispresent + index,ShellCode[index]) if flag: flag = 1 else : flag = 0 return flag if __name__ == "__main__" : dbg = MyDebug() connect = dbg.connect() ref = Patch_GetTickCount(dbg) print ("写出状态: {}" .format (ref)) dbg.close()
写出效果如下:
Patch_ZwQueryInformationProcess
此函数打补丁需要跳转两次,原因是因为函数开头部分无法填充更多指令,需要我们自己来申请空间,并实现跳转。
from LyScript32 import MyDebugdef get_opcode_from_assemble (dbg_ptr,asm ): byte_code = bytearray () addr = dbg_ptr.create_alloc(1024 ) if addr != 0 : asm_size = dbg_ptr.assemble_code_size(asm) write = dbg_ptr.assemble_write_memory(addr,asm) if write == True : for index in range (0 ,asm_size): read = dbg_ptr.read_memory_byte(addr + index) byte_code.append(read) dbg_ptr.delete_alloc(addr) return byte_code else : return bytearray (0 ) def GetOpCode (dbg, Code ): ShellCode = [] for index in Code: ref = get_opcode_from_assemble(dbg,index) for opcode in ref: ShellCode.append(opcode) return ShellCode def GetOpCodeSize (dbg,address,index ): ref_size = 0 dasm = dbg.get_disasm_code(address,index) for index in dasm: count = dbg.assemble_code_size(index.get("opcode" )) ref_size += count return ref_size def Patch_ZwQueryInformationProcess (dbg ): ispresent = dbg.get_module_from_function("ntdll.dll" ,"ZwQueryInformationProcess" ) print (hex (ispresent)) create_address = dbg.create_alloc(1024 ) print ("分配空间: {}" .format (hex (create_address))) ShellCode = GetOpCode(dbg, [ "cmp dword [esp + 8],7" , "DB 0x74" , "DB 0x06" , f"push {hex (ispresent)} " , "ret" , "mov eax,dword [esp +0x0c]" , "push 0" , "pop dword [eax]" , "xor eax,eax" , "ret 14" ] ) print (ShellCode) flag = 0 for index in range (0 ,len (ShellCode)): flag = dbg.write_memory_byte(create_address + index,ShellCode[index]) if flag: flag = 1 else : flag = 0 jmp_shellcode = GetOpCode(dbg, [ f"push {hex (create_address)} " , "ret" ] ) for index in range (0 ,len (jmp_shellcode)): flag = dbg.write_memory_byte(ispresent + index,jmp_shellcode[index]) if flag: flag = 1 else : flag = 0 return flag if __name__ == "__main__" : dbg = MyDebug() connect = dbg.connect() ref = Patch_ZwQueryInformationProcess(dbg) print ("补丁状态: {}" .format (ref)) dbg.close()
这段代码运行后,首先会申请内存,然后将特定的一段机器码写出到此内存中。
内存写出以后,再将函数头部替换为跳转,这样一来当函数被调用,也就自动转向了。
Patch_FindWindow
FindWindow函数功能是取窗体句柄,有AW与Ex系列,使用同上方法替代即可。
from LyScript32 import MyDebugimport ctypesdef get_opcode_from_assemble (dbg_ptr,asm ): byte_code = bytearray () addr = dbg_ptr.create_alloc(1024 ) if addr != 0 : asm_size = dbg_ptr.assemble_code_size(asm) write = dbg_ptr.assemble_write_memory(addr,asm) if write == True : for index in range (0 ,asm_size): read = dbg_ptr.read_memory_byte(addr + index) byte_code.append(read) dbg_ptr.delete_alloc(addr) return byte_code else : return bytearray (0 ) def GetOpCode (dbg, Code ): ShellCode = [] for index in Code: ref = get_opcode_from_assemble(dbg,index) for opcode in ref: ShellCode.append(opcode) return ShellCode def Patch_FindWindow (dbg ): FindWindowA = dbg.get_module_from_function("user32.dll" ,"FindWindowA" ) FindWindowW = dbg.get_module_from_function("user32.dll" ,"FindWindowW" ) FindWindowExA = dbg.get_module_from_function("user32.dll" ,"FindWindowExA" ) FindWindowExW = dbg.get_module_from_function("user32.dll" ,"FindWindowExW" ) print ("A = {} w = {} exA = {} exW = {}" .format (hex (FindWindowA),hex (FindWindowW),hex (FindWindowExA),hex (FindWindowExW))) ShellCode = GetOpCode(dbg, [ "xor eax,eax" , "ret 0x8" , ] ) ShellCodeEx = GetOpCode(dbg, [ "xor eax,eax" , "ret 0x10" , ] ) flag = 0 for index in range (0 ,len (ShellCode)): flag = dbg.write_memory_byte(FindWindowA + index,ShellCode[index]) flag = dbg.write_memory_byte(FindWindowW + index,ShellCode[index]) if flag: flag = 1 else : flag = 0 for index in range (0 ,len (ShellCodeEx)): flag = dbg.write_memory_byte(FindWindowExA + index,ShellCodeEx[index]) flag = dbg.write_memory_byte(FindWindowExW + index,ShellCodeEx[index]) if flag: flag = 1 else : flag = 0 return flag if __name__ == "__main__" : dbg = MyDebug() connect = dbg.connect() ref = Patch_FindWindow(dbg) print ("补丁状态: {}" .format (ref)) dbg.close()
补丁应用会分别替换四个函数。
Patch_EnumWindows
枚举窗体的补丁与上方代码一致,此处就不再分析了。
如下案例,实现了在枚举窗体过程中实现弹窗,并不影响窗体的枚举。
from LyScript32 import MyDebugdef get_opcode_from_assemble (dbg_ptr,asm ): byte_code = bytearray () addr = dbg_ptr.create_alloc(1024 ) if addr != 0 : asm_size = dbg_ptr.assemble_code_size(asm) write = dbg_ptr.assemble_write_memory(addr,asm) if write == True : for index in range (0 ,asm_size): read = dbg_ptr.read_memory_byte(addr + index) byte_code.append(read) dbg_ptr.delete_alloc(addr) return byte_code else : return bytearray (0 ) def GetOpCode (dbg, Code ): ShellCode = [] for index in Code: ref = get_opcode_from_assemble(dbg,index) for opcode in ref: ShellCode.append(opcode) return ShellCode def GetOpCodeSize (dbg,address,index ): ref_size = 0 dasm = dbg.get_disasm_code(address,index) for index in dasm: count = dbg.assemble_code_size(index.get("opcode" )) ref_size += count return ref_size def Patch_EnumWindows (dbg ): address = dbg.get_module_from_function("user32.dll" ,"EnumWindows" ) print (hex (address)) msg_box = dbg.get_module_from_function("user32.dll" ,"MessageBoxA" ) print (hex (msg_box)) create_address = dbg.create_alloc(1024 ) print ("分配空间: {}" .format (hex (create_address))) dasm_list = dbg.get_disasm_code(address,20 ) call_addr = 0 call_next_addr = 0 for index in range (0 ,len (dasm_list)): if dasm_list[index].get("opcode" ).split(" " )[0 ] == "call" : call_addr = dasm_list[index].get("addr" ) call_next_addr = dasm_list[index+1 ].get("addr" ) print ("call = {} call_next = {}" .format (hex (call_addr),hex (call_next_addr))) ShellCode = GetOpCode(dbg, [ "push 0" , "push 0" , "push 0" , "push 0" , f"call {hex (msg_box)} " , "mov eax,1" , "pop ebp" , "ret 10" , f"call {hex (call_addr)} " , "pop ebp" , "ret 8" ] ) print (ShellCode) flag = 0 for index in range (0 ,len (ShellCode)): flag = dbg.write_memory_byte(create_address + index,ShellCode[index]) if flag: flag = 1 else : flag = 0 jmp_shellcode = GetOpCode(dbg, [ f"push {hex (create_address)} " , "ret" ] ) for index in range (0 ,len (jmp_shellcode)): flag = dbg.write_memory_byte(call_addr + index,jmp_shellcode[index]) if flag: flag = 1 else : flag = 0 return flag if __name__ == "__main__" : dbg = MyDebug() connect = dbg.connect() ref = Patch_EnumWindows(dbg) dbg.close()
输出效果如下: