文章总结: 文档分析Floxif木马对抗静态分析的技术。该样本利用特定函数混淆控制流,通过栈操作将Call伪装为Jmp,阻碍反编译。作者通过汇编与调试分析还原逻辑,发现其本质是跳转至返回地址加2处。最终建议将此类Call替换为Jmp指令,以绕过对抗机制并还原真实代码。 综合评分: 91 文章分类: 恶意软件,逆向分析
一种对抗静态分析的方式
原创
小和安全
小和安全
2026年1月8日 11:47 江苏
前言
样本信息:
| | | | — | — | | SHA256 | de055a89de246e629a8694bde18af2b1605e4b9b493c7e4aef669dd67acf5085 | | SHA1 | 14ba3fa927a06224dfe587014299e834def4644f | | MD5 | 7574cf2c64f35161ab1292e2f532aabf |
看一个Floxif样本,发现用了一些对抗静态分析的方式,点开一个函数看看:
再点进sub_10001678函数看看:
看出来了吗,sub_10001684这个函数最终就做了一个清理栈帧的操作。
但是我们通过交叉引用看一下这个函数出现了多少次:
这么一个简单逻辑的函数居然被调用了73次,很显然,并没有这么简单,木马的开发者是采用了一些手段让这个函数看起来没有威胁性。
分析
这个木马的脱壳、通信等等就不在这里分析了,就是一个很简单upx加壳,直接工具脱壳就行:
我们直接用IDA打开看一下刚刚的地方,这次不看伪代码,直接看汇编:
点进sub_10001678继续看:
直接把它们拼接起来看看呢,也就是把sub_10001684里面的这一步:
call sub_10001678
替换为:
push 1000168Bjmp 10001678
那我们就得到了:
push eaxpushapush 1000168Bjmp 10001678retn 4
现在从调用10001684函数开始分析。
假设我们执行call 10001684时,下一条指令地址为x,比如:
x-5 call 10001684x 下一条指令是什么不重要,知道地址为x就行
此时就相当于:
x-5 push x jmp 10001684x 下一条指令是什么不重要,知道地址为x就行
这时候当我们执行jmp 10001684时栈里面已经有一个x了,也就是:
| | | | — | — | | x | 栈底+栈顶esp |
然后我们jmp 10001684,继续执行指令:
push eaxpushapush 1000168Bjmp 10001678retn 4
当我们即将执行jmp 10001678时,栈里面的内容如下(假设此时eax的值为y):
| | | | — | — | | x | | | y | | | all | | | 1000168B | 栈顶esp |
接下来jmp到10001678,我们直接跟过去看:
一步一步来,执行push eax后栈内容:
| | | | — | — | | x | | | y | | | all | | | 1000168B | | | eax | 栈顶esp |
执行mov eax,[esp+4]就是取esp+4位置指向的值,赋值给eax,此时eax寄存器值就变成了1000168B,然后add eax,4表示eax值加4,变为1000168F,再执行push eax后栈内容:
| | | | — | — | | x | | | eax | | | all | | | 1000168B | | | eax | | | 1000168F | 栈顶esp |
接下来retn 8表示栈顶地址出栈,跳转到这个地址也就是1000168F,然后清理8字节的内容,栈内容变为:
| | | | — | — | | x | | | eax | | | all | 栈顶esp |
并跳转到1000168F,在IDA里面看这个地址发现被识别错了:
1000168E后面就是10001690,没有我们要的1000168F.
我们需要手动重置这一块区域,让它变成原始数据,快捷键U即可:
然后选中1000168F,快捷键P重新识别为函数:
回到刚才我们的栈内容是:
| | | | — | — | | x | | | y | | | all | 栈顶esp |
现在jmp到了1000168F,执行popa后栈内容:
| | | | — | — | | x | | | y | 栈顶esp |
还剩下这些指令:
两个参数的值也有:
看上去很复杂,其实简单来说就是:
(1)sub esp, 0FFFFFFF8h表示esp的值减-8,也就是加8,此时栈:
| | | | — | — | | | 栈顶esp | | x | | | y | |
(2)mov eax, [esp-28h+arg_20]表示将esp-4的地址保存的值赋值给eax,很绕,其实就是栈里面的x,此时eax=x
(3)add eax, 2就是将eax加2,此时eax=x+2
(4)mov [esp-28h+arg_20], eax又把eax的值写回esp-4的地址,此时栈的内容:
| | | | — | — | | | 栈顶esp | | x+2 | | | y | |
(5)mov eax, [esp-28h+arg_1C]表示取出esp-8地址保存的值,很显然就是y,那此时eax=y
(6)sub esp, 4表示esp-4,栈顶地址减4,栈的内容变为:
| | | | — | — | | x+2 | 栈顶esp | | y | |
栈顶在x+2这里,那y其实就是没有用的数据了,最后,栈的内容就是:
| | | | — | — | | x+2 | 栈顶esp |
(7)retn表示将栈顶地址出栈,然后跳转到这个地址,这个时候栈清空了,然后跳转到x+2这个地址。
这下清楚了,我们调用10001684函数时,表面上执行的是:
x-5 call 10001684x 下一条指令是什么不重要,知道地址为x就行
但是我们执行前后,栈的内容没变,还跳转到了x+2地址,那call 10001684等效替换不就是jmp x+2吗?
验证
动态调试一下就可以验证我们的分析了:
首先在IDA找到一个调用了10001710函数的地方:
首先根据我们的分析,call sub_10001684会跳转到下一条地址加2的位置也就是10001715加2,也就是10001717。我们验证一下:
找到一个10001710函数,、然后打开dbg,在10001710和10001684下断点:
F7单步进入10001684,一直单步执行:
F7进入10001678,再一直单步执行:
一直执行到ret会根据我们的分析跳转到1000168F:
确实如此,根据我们的分析,ret后应该会跳转到10001717,我们单步执行到ret,看看是不是真的跳转到这里:
确实跳转到10001717:
总结
我们只需要将所有的call 10001684改为jmp $+7就可以绕过这个对抗机制了。
比如:
原先的10001710函数:
反编译后根本看不见真实的函数逻辑:
我们将它改为 jmp $+7再反编译:
成功还原真实的函数逻辑!
免责声明:
本文所载程序、技术方法仅面向合法合规的安全研究与教学场景,旨在提升网络安全防护能力,具有明确的技术研究属性。
任何单位或个人未经授权,将本文内容用于攻击、破坏等非法用途的,由此引发的全部法律责任、民事赔偿及连带责任,均由行为人独立承担,本站不承担任何连带责任。
本站内容均为技术交流与知识分享目的发布,若存在版权侵权或其他异议,请通过邮件联系处理,具体联系方式可点击页面上方的联系我。
本文转载自:小和安全 小和安全《一种对抗静态分析的方式》
版权声明
本站仅做备份收录,仅供研究与教学参考之用。
读者将信息用于其他用途的,全部法律及连带责任由读者自行承担,本站不承担任何责任。









评论