Go研究卡住了,看代码看得头晕。休息一会看看其他的。
反-反汇编分析
反汇编器类型
线性扫描
Ollydbg
,x64dbg
这样的基本都是线性反汇编器。capstone
更不用说了。
http://sf.net/projects/bastard/files/libdisasm/
实现例子:
1 | char buffer[BUF_SIZE]; |
这种反汇编器的实现简单,但是缺点也十分大。比如数据段和代码段难以区分。
递归下降扫描器
IDA
这样比较智能的就是使用这种反汇编器。毕竟它都会分析近似的执行流来生成伪代码。
这种反汇编器会近似地跟踪执行流,从而绕开一些花指令和数据段。
显然它还是有局限性的,比如jz
+jnz
组合;以及在call
指令后立刻跟随一串字符串;字符串开头字节要是能代表某个汇编指令的开头,也有可能会被误认为是汇编指令(比如0x68是push dword
的开头,对应字符h
)
破解反反汇编
IDA
上处理这个还是很简单的,对着有问题的指令按D
变成字节码数据,然后跳过垃圾数据(如果完全没用还可以patch
掉),对着实际执行指令代码的开头按C
返回变成正确指令。
Ollydbg
,x64dbg
上遇到这个还真不好处理。自动的analyze
选项也经常不靠谱,容易搞错。不过这种纯线性汇编动态调试器,我不经常用,通常也就使用它的万能插件来处理一些特殊情形。
后面讲x64平台上的常见类型
jmp+call
1 | .text:401568 EB 09 jmp short near ptr loc_401572+1 |
特征
jmp loc+1
特意跳转过一个字节,到下面一个奇奇怪怪的call
上。且指向了一个很诡异的地址。
构造方法
B8 00 00 00
就是个mov eax, 0
。修改前面的一个字节为0xE8
,这是call
指令的特征码之一。
在前面编一个jmp
,特意跳过0xE8
,到原来正常应该执行的地方去。
虽然实际上0xE8
并不会执行,但是这样会让线性扫描反汇编器误认为这里有个call
指令。
效果
对于OD这种线性扫描的挺好的,对于IDA这种不是很好。不过在jmp
和0xE8
之间的空隙中搞点乱数据似乎也能影响IDA的分析。
jz+jnz
1 | 74 03 jz short near ptr loc_4011C4+1 |
jz
+jnz
就是jmp
,但是IDA还是会把jnz
后面理应从不会执行的假执行流给反汇编,从而上当,导致0xE8
被反汇编进去,变成call
指令。
0+jz
1 | xor eax, eax |
在前面设置一个恒真条件,然后再去“条件”跳转。这样会使得IDA也把假分支给反汇编了。
效果
一般情况下不靠谱,IDA现在的分支判断已经很成熟了,能够发现xor eax, eax
干了啥的。
改进
添加指令混淆Pass,MBA Pass,能立刻提升效果。
重叠花指令
垃圾代码也会影响到具体执行的特殊形式。非常特别的构造形式。以下字节码:
1 | 66 B8 EB 05 31 C0 74 FA E8 |
直接得到:
1 | loc_40155D: |
xor
+jz
绝对执行,那么会跳到EB
那里。
1 | .text:000000000040155D 66 db 66h ; f |
x86和x64似乎都能用
总结
核心代码就是原来mov ax, 5EBh
中的EB 05
。这玩意就是jmp $+7
。
重叠花指令2
1 | EB FF C0 48 |
得到
1 | .text:004132D4 loc_4132D4: |
而jmp
会跳转到自身的第二个字节上,从而实际上得到
1 | .text:004132D4 db 0EBh |
不要最后的0x48大抵也是可以的,那么这一块代码的意思就是将eax
递增1。若不删除,那就是一个复杂4字节的nop
指令。
利用异常处理
老生常谈了。单独分一个博客来研究。
里面有关于x86 SEH的。
栈分析混淆
大抵是用于针对IDA这种智能分析工具,否则的话也用不着这么高雅的技术。作用挺大的,那就是IDA会无法正常分析变量,以及没法生成函数伪代码。
举个例子:
1 | 00401543 sub_401543 proc near ; CODE XREF: sub_4012D0+3Cp |
会发现后面的栈分析出了错误,这是由于IDA将永远不会执行的分支add esp, 104h
给判断进了栈分析中。至于为什么不会执行,是因为esp
的值至少在Windows中必然是不可能小于0x1000的。
注意:栈分析混淆十分依赖于高级语言编译器实现,如果是使用高级语言编程恶俗程序的话。直接汇编写的话灵活性就会高很多,恶俗操作也会更多。
消除
alt+k
手动更改栈指针,或者直接patch
掉让IDA产生混淆的指令。
控制流混淆
间接跳转
IDA的交叉引用都是通过直接调用来获知的,比如jmp 0x400000
。
若强行改成jmp rax
,call rax
这种基于函数指针的形式,那么交叉引用就会失效。
实现
这一块纯脑洞实验内容,强度应该很弱
如何去写这个我倒是没啥想法。可以试试宏替换。
实验环境:Visual Studio 2019,不喜欢
gcc
内联汇编风格。
1.无混淆
1 |
|
可执行。
2.lea
+call eax
先lea
获得函数地址于eax
寄存器中,然后再call
。
1 |
|
可执行,但是混淆力度不大,轻松被IDA的常量传递操作识别出来。
3.间接调用+加解密
1 |
|
大概的方法很迫真,但是确实起了一定的作用。也就是消除了直接的调用。
1 | int __cdecl main_0(int argc, const char **argv, const char **envp) |
当然看汇编的话还是有lea eax, sub_41116D
这样直接引用函数地址的操作,所以交叉引用还是有效的。
可见在IDA自动生成的情况下,反编译中,EncAddr()
函数是并不会显示出传入的地址的。
4.before main
https://stackoverflow.com/questions/10897552/call-a-function-before-main
给了很多方法。其中有几个是基于C++特性实现的。msvc
中不提供对main
前面start
等初始化函数进行修改的宏;gcc
有修改main
前面函数的,但是我现在不想换汇编格式。
换个博客仔细讲这个骚操作。总而言之,目的就是要解密函数离main
函数远一点。
1 | bool foo() { |
foo()
,foo2()
会依次提前运行,因为这2个类对象的生成必然得提前于main
。
那么配合控制流混淆:
1 |
|
1 | 0xe8118b |
5.完全去除具体地址
上面的玩意无论怎么说都还是有具体函数地址的。消除也是完全可行的。
1 | void CallMe(void) { |
这样能保证绝绝对对得到的不是具体地址了。虽然看上去还是很简单,因为相隔距离太近了;不过要是配合其他几个手法的话,再加上指令替换Pass,就会比较厉害了。
还有一种想法
1 | call $+5 |
用这种代替jmp
指令的操作。即将CallMe
函数地址直接拆成add
指令所在地址+0x????
(相对偏移),更加具有迷惑性。不过直接源码上似乎无法实现,我也暂时不打算在汇编层面上魔改甚至直接patch二进制程序了。以后有机会的话看看别人的开源混淆器实现后,再来找思路。
6.其他脑洞(未实现)
比如在main
函数里面lea eax, A
,那么可能会被认为这是在调用A
,但是实际上进行了一次解密转换后,变成了实际调用函数B
。
消除
- 若是很明显的宏替换,那么可以选择直接Python脚本替换字节码。
- 若没什么规则,则得考虑使用模拟执行。比如使用
Unicorn
或者angr
,模拟执行相关跳转块,然后保存具体跳转地址,然后再patch
。
利用ret
一般而言ret == pop + jmp
,所以可以利用这一点来进行骚操作。
上面实现的第五点已经用过了
1 | call $+5 |
反Patch(没写完)
反混淆难免得需要魔改程序,修改几个字节码。那么通过CRC
自校验算法,则可以检测自身是否被patch
过。
我一开始的想法是,开个线程,然后读取内存或者硬盘上本可执行程序的字节码,然后哈希或者CRC梭哈一番,然后比对未patch时的哈希码,看看有没有篡改。但是这个手段似乎太容易被日了
https://www.codeproject.com/Articles/18961/Tamper-Aware-and-Self-Healing-Code#pre0
提供了一个不错的思路,学完后再补充。
缺陷
这玩意相关文章都是05-09年左右的了,技术古老。本地自校验确实太容易被patch了。
进阶
2011年的时候就有一个人写过Windows上可以说最详细的反调试手段。
https://anti-reversing.com/Downloads/Anti-Reversing/The_Ultimate_Anti-Reversing_Reference.pdf
内容过多不Copy了,不过可能会自己尽量尝试实现一番。
IDAPython脚本
nop
指令
1 | import idaapi |
(IDAPython
的API似乎已经有变化了,留在这做个参考罢了)
添加xref
交叉引用
1 | AddCodeXref(0x004011DE, 0x004011C0, fl_CF); |
fl_CF
是call
fl_JF
是jmp
参考
代码混淆/程序保护(对抗反汇编)原理与实践_pianogirl123的博客-CSDN博客_防止反汇编
在 VS2008 下用 CRC32 算法实现程序自校验_蜡笔小辛的专栏-CSDN博客
https://anti-reversing.com/Downloads/Anti-Reversing/The_Ultimate_Anti-Reversing_Reference.pdf
- 本文作者: Taardis
- 本文链接: https://taardisaa.github.io/2022/03/08/AntiDisassembly/
- 版权声明: 本博客所有文章除特别声明外,均采用 Apache License 2.0 许可协议。转载请注明出处!