4.4 x64dbg 绕过反调试保护机制

本文涉及的产品
网络型负载均衡 NLB,每月750个小时 15LCU
公网NAT网关,每月750个小时 15CU
应用型负载均衡 ALB,每月750个小时 15LCU
简介: 在Windows平台下,应用程序为了保护自己不被调试器调试会通过各种方法限制进程调试自身,通常此类反调试技术会限制我们对其进行软件逆向与漏洞分析,我们以第一种`IsDebuggerPresent`反调试为例,该函数用于检查当前程序是否在调试器的环境下运行。函数返回一个布尔值,如果当前程序正在被调试,则返回True,否则返回False。函数通过检查特定的内存地址来判断是否有调试器在运行。具体来说,该函数检查了`PEB(进程环境块)`数据结构中的`_PEB_LDR_DATA`字段,该字段标识当前程序是否处于调试状态。如果该字段的值为1,则表示当前程序正在被调试,否则表示当前程序没有被调试。

在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()

这里笔者继续拓展一个新知识点,如何实现绕过进程枚举功能,病毒会利用进程枚举函数Process32FirstWProcess32NextW枚举所有运行的进程以确认是否有调试器在运行,我们可以在特定的函数开头处写入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()

当上述代码被运行后,则Process32FirstWProcess32FirstW函数位置将被依次写出返回指令,从而让进程枚举失效,输出效果图如下所示;

原文地址

https://www.lyshark.com/post/8b9dc8dc.html

相关实践学习
每个IT人都想学的“Web应用上云经典架构”实战
本实验从Web应用上云这个最基本的、最普遍的需求出发,帮助IT从业者们通过“阿里云Web应用上云解决方案”,了解一个企业级Web应用上云的常见架构,了解如何构建一个高可用、可扩展的企业级应用架构。
高可用应用架构
欢迎来到“高可用应用架构”课程,本课程是“弹性计算Clouder系列认证“中的阶段四课程。本课程重点向您阐述了云服务器ECS的高可用部署方案,包含了弹性公网IP和负载均衡的概念及操作,通过本课程的学习您将了解在平时工作中,如何利用负载均衡和多台云服务器组建高可用应用架构,并通过弹性公网IP的方式对外提供稳定的互联网接入,使得您的网站更加稳定的同时可以接受更多人访问,掌握在阿里云上构建企业级大流量网站场景的方法。 学习完本课程后,您将能够: 理解高可用架构的含义并掌握基本实现方法 理解弹性公网IP的概念、功能以及应用场景 理解负载均衡的概念、功能以及应用场景 掌握网站高并发时如何处理的基本思路 完成多台Web服务器的负载均衡,从而实现高可用、高并发流量架构
目录
相关文章
|
安全 开发者
4.5 x64dbg 探索钩子劫持技术
钩子劫持技术是计算机编程中的一种技术,它们可以让开发者拦截系统函数或应用程序函数的调用,并在函数调用前或调用后执行自定义代码,钩子劫持技术通常用于病毒和恶意软件,也可以让开发者扩展或修改系统函数的功能,从而提高软件的性能和增加新功能。钩子劫持技术的实现一般需要在对端内存中通过`create_alloc()`函数准备一块空间,并通过`assemble_write_memory()`函数,将一段汇编代码转为机器码,并循环写出自定义指令集到堆中,函数`write_opcode_from_assemble()`就是我们自己实现的,该函数传入一个汇编指令列表,自动转为机器码并写出到堆内,函数的核心代码如
129 0
4.5 x64dbg 探索钩子劫持技术
|
网络协议
驱动开发:内核RIP劫持实现DLL注入
本章将探索内核级DLL模块注入实现原理,DLL模块注入在应用层中通常会使用`CreateRemoteThread`直接开启远程线程执行即可,驱动级别的注入有多种实现原理,而其中最简单的一种实现方式则是通过劫持EIP的方式实现,其实现原理可总结为,挂起目标进程,停止目标进程EIP的变换,在目标进程开启空间,并把相关的指令机器码和数据拷贝到里面去,然后直接修改目标进程EIP使其强行跳转到我们拷贝进去的相关机器码位置,执行相关代码后,然后再次跳转回来执行原始指令集。
399 0
|
安全 API
LyScript 实现绕过反调试保护
LyScript插件中内置的方法可实现各类反调试以及屏蔽特定API函数的功能,这类功能在应对病毒等恶意程序时非常有效,例如当程序调用特定API函数时我们可以将其拦截,从而实现保护系统在调试时不被破坏的目的。
242 0
LyScript 实现绕过反调试保护
|
Web App开发 JavaScript 前端开发
网页调试之debugger原理与绕过
当我们调试JS的时候,时常会遇见无限debugger。无限debugger的原理是什么呢?它在何时触发?如何绕过?
1292 0
网页调试之debugger原理与绕过
|
Java
攻防:如何防止动态hook绕过jni签名校验
攻 我们知道jni校验签名也不可靠,可以被动态hook绕过。代码如下:
448 0