LyScript 插件集成的内置API函数可灵便的实现绕过各类反调试爱护机制,前段时间公布的那一篇文章并没有具体解说各类反调试机制的绕过措施,本次将补充这方面的知识点,使用LyScript实现绕过大多数通用调试机制,实现暗藏调试器的目标。

  • 插件地址:https://github.com/lyshark/Ly...

咱们以此实现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 MyDebug# 传入汇编代码,失去对应机器码def 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)        # print("汇编代码占用字节: {}".format(asm_size))        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)                # print("{:02x} ".format(read),end="")                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 ShellCodeif __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构造存在许多反调试变量,首先咱们须要先将这些变量填充为空。

# ----------------------------------------------# By: LyShark# Email: me@lyshark.com# Project: https://github.com/lyshark/LyScript# ----------------------------------------------from LyScript32 import MyDebug# 传入汇编代码,失去对应机器码def 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)        # print("汇编代码占用字节: {}".format(asm_size))        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)                # print("{:02x} ".format(read),end="")                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 ShellCodedef Patch_PEB(dbg):    PEB = dbg.get_peb_address(dbg.get_process_id())    if PEB == 0:        return 0    # 写出0 Patching PEB.IsDebugged    dbg.write_memory_byte(PEB + 0x2,GetOpCode(dbg,["db 0"])[0])    print("补丁地址: {}".format(hex(PEB+0x2)))    # 写出0 Patching PEB.ProcessHeap.Flag    temp = dbg.read_memory_dword(PEB + 0x18)    temp += 0x10    dbg.write_memory_dword(temp, GetOpCode(dbg,["db 0"])[0])    print(("补丁地址: {}".format(hex(temp))))    # 写出0 atching PEB.NtGlobalFlag    dbg.write_memory_dword(PEB+0x68, 0)    print(("补丁地址: {}".format(hex(PEB+0x68))))    # 循环替换 Patch PEB_LDR_DATA 0xFEEEFEEE fill bytes about 3000 of them    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:            breakif __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 MyDebug# 传入汇编代码,失去对应机器码def 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)        # print("汇编代码占用字节: {}".format(asm_size))        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)                # print("{:02x} ".format(read),end="")                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 ShellCodedef 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 flagif __name__ == "__main__":    dbg = MyDebug()    connect = dbg.connect()    ref = Patch_IsDebuggerPresent(dbg)    print("补丁状态: {}".format(ref))    dbg.close()

当程序运行后会向IsDebuggerPresent函数写出返回,从而实现绕过调试的目标。

Patch_CheckRemoteDebuggerPresent

此Api调用ZwQueryInformationProcess因而通常不须要对两者进行修补。

from LyScript32 import MyDebug# 传入汇编代码,失去对应机器码def 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)        # print("汇编代码占用字节: {}".format(asm_size))        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)                # print("{:02x} ".format(read),end="")                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 ShellCodedef 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 flagif __name__ == "__main__":    dbg = MyDebug()    connect = dbg.connect()    ref = Patch_CheckRemoteDebuggerPresent(dbg)    print("写出状态: {}".format(ref))    dbg.close()

写出成果如下:

Patch_GetTickCount

GetTickCount返回(retrieve)从操作系统启动所通过(elapsed)的毫秒数,罕用于定时计数,绕过形式只需初始化即可。

from LyScript32 import MyDebug# 传入汇编代码,失去对应机器码def 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)        # print("汇编代码占用字节: {}".format(asm_size))        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)                # print("{:02x} ".format(read),end="")                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 ShellCodedef 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 flagif __name__ == "__main__":    dbg = MyDebug()    connect = dbg.connect()    ref = Patch_GetTickCount(dbg)    print("写出状态: {}".format(ref))    dbg.close()

写出成果如下:

Patch_ZwQueryInformationProcess

此函数打补丁须要跳转两次,起因是因为函数结尾局部无奈填充更多指令,须要咱们本人来申请空间,并实现跳转。

# ----------------------------------------------# By: LyShark# Email: me@lyshark.com# Project: https://github.com/lyshark/LyScript# ----------------------------------------------from LyScript32 import MyDebug# 传入汇编代码,失去对应机器码def 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)        # print("汇编代码占用字节: {}".format(asm_size))        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)                # print("{:02x} ".format(read),end="")                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# 获取指定地位前index条指令的长度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_sizedef 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)    # 把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 flagif __name__ == "__main__":    dbg = MyDebug()    connect = dbg.connect()    ref = Patch_ZwQueryInformationProcess(dbg)    print("补丁状态: {}".format(ref))    dbg.close()

这段代码运行后,首先会申请内存,而后将特定的一段机器码写出到此内存中。

内存写出当前,再将函数头部替换为跳转,这样一来当函数被调用,也就主动转向了。

Patch_FindWindow

FindWindow函数性能是取窗体句柄,有AW与Ex系列,应用同上办法代替即可。

# ----------------------------------------------# By: LyShark# Email: me@lyshark.com# Project: https://github.com/lyshark/LyScript# ----------------------------------------------from LyScript32 import MyDebugimport ctypes# 传入汇编代码,失去对应机器码def 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)        # print("汇编代码占用字节: {}".format(asm_size))        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)                # print("{:02x} ".format(read),end="")                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 ShellCodedef 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 flagif __name__ == "__main__":    dbg = MyDebug()    connect = dbg.connect()    ref = Patch_FindWindow(dbg)    print("补丁状态: {}".format(ref))    dbg.close()

补丁利用会别离替换四个函数。

Patch_EnumWindows

枚举窗体的补丁与上方代码统一,此处就不再剖析了。

如下案例,实现了在枚举窗体过程中实现弹窗,并不影响窗体的枚举。

from LyScript32 import MyDebug# 传入汇编代码,失去对应机器码def 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)        # print("汇编代码占用字节: {}".format(asm_size))        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)                # print("{:02x} ".format(read),end="")                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# 获取指定地位前index条指令的长度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_sizedef 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)))    # 找call地址,找到后取出他的内存地址    dasm_list = dbg.get_disasm_code(address,20)    call_addr = 0    call_next_addr = 0    for index in range(0,len(dasm_list)):        # 如果找到了call,取出call地址以及下一条地址        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)    # 把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 flagif __name__ == "__main__":    dbg = MyDebug()    connect = dbg.connect()    ref = Patch_EnumWindows(dbg)    dbg.close()

输入成果如下: