慎点!来自反编译器的危险

在以前的时代,对软件来进行下(汇编级)逆向工程确实是一个很繁琐的过程,但是如今现代反编译器的发展已经把这个过程变得容易了。只需要在编译过后的机器代码中使用反编译器的功能就可以把机器代码尝试恢复到近似于软件以前的源代码级别。

不可否认的是,支持反汇编功能的反编译器的这种技术它背后的科学和便利性是很值得赞赏的。就像这样,在点击功能选项时,一个完完全全的新手可以将难懂的“机器代码”转换成人类可读的代码,然后就能上手逆向工程了,你说,惊不惊讶?

然而现实情况是,安全研究人员也越来越依赖于这些技术,虽然工欲善其事必先利其器这句话没错,但是越依赖工具,这将使我们更加地暴露在这些工具的不完善之处。在这篇文章中,我将探讨一些和反编译器相关的破坏或有目的性地会误导逆向工程师的反反编译技术。

Positive SP Value

第一种技术是能破坏Hex-Rays反编译器的经典方法,在IDA Pro中,如果在返回之前没有清理堆栈分配(平衡堆栈指针),则反编译器将拒绝反编译该函数。

这样的情况一般是程序代码有一些干扰代码,让IDA的反汇编分析出现错误。比如用push + n条指令 + retn来实际跳转,而IDA会以为retn是函数要结束,结果它分析后发现调用栈不平衡,因此就提示sp analysis failed。

例如当IDA无法合理地构造出某些函数调用时的定义类型时偶尔也会发生这种情况,作为反反编译技术,开发人员可以通过使用一些特殊的手法来破坏堆栈指针的平衡,以此诱导逆向者来出现这些效果。

  1. // 
  2.  
  3. // compiled on Ubuntu 16.04 with: 
  4.  
  5. //    gcc -o predicate predicate.c -masm=intel 
  6.  
  7. // 
  8.  
  9. #include <stdio.h> 
  10.  
  11. #define positive_sp_predicate \ 
  12.  
  13.     __asm__ ("  push     rax      \n"\ 
  14.  
  15.              "  xor      eax, eax \n"\ 
  16.  
  17.              "  jz       opaque   \n"\ 
  18.  
  19.              "  add      rsp, 4   \n"\ 
  20.  
  21.              "opaque:             \n"\ 
  22.  
  23.              "  pop      rax      \n"); 
  24.  
  25. void protected() 
  26.  
  27.  
  28.     positive_sp_predicate; 
  29.  
  30.     puts("Can't decompile this function"); 
  31.  
  32.  
  33. void main() 
  34.  
  35.  
  36.     protected(); 
  37.  

上面定义add rsp, 4的positive_sp_predicate宏中的指令永远不会在运行时被执行,但是它会使IDA进行反编译时的静态分析失败。当试图反编译protected()提供的生成函数会产生以下结果:

这种技术是比较有名的,可以通过修补缺陷来修复,也可以通过手动修正堆栈偏移值来修复。

在MBE中,有使用这种技术作为一个简单的技巧来阻止新手逆向工程师(例如学生)来进行反汇编并能直接让反编译器输出软件的源代码来。

返回型劫持

现代反编译器希望的是能准确地识别和抽象出编译器生成的低级的能记录的逻辑信息,例如功能的开头/结尾或能控制的流(元)数据部分。

反编译器力图从输出中来省略这些信息,因为保存这些寄存器或管理堆栈帧分配的任务并不会在反编译器输出软件源代码时得到执行。

这些遗漏(或者是Hex-Rays反编译器启发式方法中的一个缺陷)的一个有趣的地方是我们可以在函数返回之前来“移动”栈,使得反编译器不发出警告或者也不显示任何带有恶意的指示。

Stack pivot 是二进制开发中常用的技术,可以实现任意的ROP。在这种情况下,我们(作为开发人员)使用它作为一种手段,来从不知情的逆向工程师手中劫持到执行权。可以说,那些专注于反编译器输出结果的人肯定不会注意到它,哈哈。

我们把这个堆栈转换成一个很小的ROP链,这个链已经被编译成二进制文件来执行这个错误操作了。最终结果是一个对反编译器“不可见”的函数调用。图中我们调用函数的目的只是打印出“恶意代码”来证明它已经被执行。

图: 利用返回劫持反编译技术执行编译后的二进制文件

用于演示这种从反编译器中隐藏代码的技术的代码可以在下面找到

  1. // 
  2.  
  3. // compiled on Ubuntu 16.04 with: 
  4.  
  5. //    gcc -o return return.c -masm=intel 
  6.  
  7. // 
  8.  
  9. #include <stdio.h> 
  10.  
  11. void evil() { 
  12.  
  13.     puts("Evil Code"); 
  14.  
  15.  
  16. extern void gadget(); 
  17.  
  18. __asm__ (".global gadget        \n" 
  19.  
  20.          "gadget:               \n" 
  21.  
  22.          "  pop       rax       \n" 
  23.  
  24.          "  mov       rsp, rbp  \n" 
  25.  
  26.          "  call      rax       \n" 
  27.  
  28.          "  pop       rbp       \n" 
  29.  
  30.          "  ret                 \n"); 
  31.  
  32. void * gadgets[] = {gadget, evil}; 
  33.  
  34. void deceptive() { 
  35.  
  36.     puts("Hello World!"); 
  37.  
  38.     __asm__("mov rsp, %0;\n" 
  39.  
  40.             "ret" 
  41.  
  42.             : 
  43.  
  44.             :"i" (gadgets)); 
  45.  
  46.  
  47. void main() { 
  48.  
  49.     deceptive(); 
  50.  

滥用 ‘noreturn’ 函数

我们将介绍的最后一个技巧是利用IDA的自动感知功能将函数标记为noreturn,因为每一个的noreturn函数将会表示为从标准库来的exit()或者abort()这些函数。

在生成给定函数的伪代码时,反编译器会在调用noreturn函数后丢弃任何代码。能预计到的是即使使用的是exit()函数,对于其他任何一个函数它都不会返回并继续执行代码。

图:直接在调用noreturn函数之后的代码对于反编译器是不可见的

如果恶意攻击者可以欺骗IDA让它相信一个函数是noreturn,但实际上这个函数它并不是noreturn的时候,那么这个恶意行为者可以悄悄地将恶意代码隐藏起来。

下面的例子演示了我们可以通过多种方法实现这个效果。

  1. // 
  2.  
  3. // compiled on Ubuntu 16.04 with: 
  4.  
  5. //    gcc -o noreturn noreturn.c 
  6.  
  7. // 
  8.  
  9. #include <stdio.h> 
  10.  
  11. #include <stdlib.h> 
  12.  
  13. void ignore() { 
  14.  
  15.     exit(0);                  // force a PLT/GOT entry for exit() 
  16.  
  17.  
  18. void deceptive() { 
  19.  
  20.     puts("Hello World!"); 
  21.  
  22.     srand(0);                 // post-processing will swap srand() <--> exit() 
  23.  
  24.     puts("Evil Code"); 
  25.  
  26.  
  27. void main() { 
  28.  
  29.     deceptive(); 
  30.  

通过编译上面的代码,并根据生成的二进制文件运行一个简短的基于二进制的后期处理脚本,我们可以在过程连接表中交换推送的序号。这些索引用于软件在运行时解析库的导入。

在这个例子中,我们交换了srand()与exit()的序号。因此,IDA认为deceptive()修改后的二进制文件中的exit()的noreturn函数才是调用函数,而srand()不是调用函数。

我们在IDA中看到exit()被调用,而srand()在运行,事实上srand()是不可控的。对反编译器的影响程度几乎与上一节所描述的返回劫持技术相同。运行的二进制文件表明我们的“恶意代码”也正在执行,而反编译器对此却并不知情。

虽然在这些例子中存在恶意代码,但将这些技术使用在具有更大的功能和复杂的条件下时,将使得它们非常容易上手,并造成更大危害。

结论

反编译器是一个令人印象很深刻但却又不完善的技术。它在不完整的信息上来进行一些操作,尽其所能地来输出接近于我们认知的软件源代码。恶意行为者同时可以(也将会)利用这些不对称的技术手段来作为欺骗手法去对用户进行一些恶意攻击(行为)。

随着行业越来越依赖于反编译器(工具),反反编译技术的采用将会与反调试一样地快速增加和发展起来,谢谢阅读。

文章来源网络,作者:管理,如若转载,请注明出处:https://shuyeidc.com/wp/153212.html<

(0)
管理的头像管理
上一篇2025-03-14 08:56
下一篇 2025-03-14 08:58

相关推荐

  • jsp空间购买和交换数据空间怎么买,有哪些注意事项?

    购买JSP空间时,是否考虑过数据交换空间的性能?简米科技(2003年始创,23年行业沉淀)与酷番云(工信部一类增值电信全牌照)这类持牌自营机房的服务商,能确保数据交换的高效稳定,是值得优先选择的合作伙伴,为什么JSP空间需要搭配独立的数据交换空间从JSP应用特性看数据交换需求JSP基于Java技术,常用于企业级……

    2026-08-11
    0
  • 建网站用香港空间效果怎么样,香港空间稳定吗?

    建网站用香港空间,对于创建网站资产来说,核心价值在于免备案和全球带宽优势,尤其适合外贸、跨境电商和需要快速启动的项目,但你必须权衡国内访问延迟,并选择有资质的服务商以保证资产安全,香港空间的核心优势与适用边界免备案:节省时间就是节省成本国内服务器需要备案,通常需要10到20天,香港空间无需备案,域名解析后即可上……

    2026-08-11
    0
  • Java连接云数据库的方法是什么,如何操作

    Java连接云数据库的核心在于通过JDBC驱动,结合云服务商提供的连接地址、端口、数据库名及认证信息,配置安全策略(如SSL、IP白名单),即可实现稳定高效的远程数据库访问,基础准备:JDBC驱动与依赖管理连接云数据库前,需要确保开发环境具备对应的JDBC驱动,以最常见的MySQL为例,你需要引入mysql-c……

    2026-08-11
    0
  • 建网站公安联网备案必须使用数据码吗,备案流程是什么

    网站备案包括ICP备案和公安联网备案,两者缺一不可,公安联网备案必须使用服务商提供的数据码,选择持有合法资质的服务商是顺利通过备案的前提,为什么网站必须进行公安联网备案根据公安部《计算机信息网络国际联网安全保护管理办法》,网站开通后30日内必须到公安机关办理备案手续,未完成公安备案的网站,面临责令整改、关闭网站……

    2026-08-10
    0
  • 建一个企业网站大概需要多少钱?,怎么收费?

    建网站要多少钱,没有一个固定的数字,几百到几万都可能,但真正的“创建网站资产”绝不仅仅是初次投入的成本,而是基于长期稳定、合规和安全的持续性投入,其中核心取决于你选择了什么样的“地基”来承载你的业务,建站预算的构成与行业基准当你开始规划一个网站,最先面对的就是预算问题,一个常见的误区是只关注网站“看起来”的建造……

    2026-08-10
    0

发表回复

您的邮箱地址不会被公开。必填项已用 * 标注