demo crackme 链接:https://www.52pojie.cn/thread-1746849-1-1.html (代码是只支持x32,没处理重定位,稍微改一下就可以兼容x64,思路是一样的,简单说下实现思路)
1、读取一个可执行文件,判断是否是PE文件、ELF文件等等,比如用MZ标志和PE00标志判断(PE结构的知识)
2、读取文件后定位到要混淆的代码的位置(也是PE结构的知识,可以先把文件按内存分布读取到缓冲区,需要注意的是文件分布和内存分布是不一样的,对齐的大小不一样)
3、提取二进制数据后用反汇编工具(这里用的是capstone,它提供了强大的分析功能)反汇编
4、然后对汇编代码进行混淆,具体的混淆方法和实现放在下面叙述
5、将混淆后的汇编代码用汇编引擎生成二进制硬编码(这里用的是keystone)
6、新增一个节(注意校验节表是否有足够空间新增),存放混淆后的代码,然后在混淆开始的位置,可以用一个jmp指令跳转到新增加的节里的代码,然后将jmp指令后的原始指令全部抹除
7、保存文件
注意,上述仅为一个思路,事实上你也可以用同样的方法写一个vm出来,只不过要复杂的多,另外,当对多段代码进行混淆时,最好都存在一个节里,不然混淆几处就不够地方新增节了,本项目的代码还没实现。
混淆的方法和具体实现:
如果你看完了以上思路,那你应该能发现,这就将代码混淆变成了纯纯的字符串处理(如果你学过编译原理,那本项目的代码对你来说就是小辣鸡)
常量混淆
mov eax, 0x401000 可变形为
mov eax, 0x401000 + rand
lea eax, [eax-rand]
上述即为一个简单的等价变形,需要注意的是lea指令并不会影响eflags,如果使用sub指令而不是lea指令,你需要保存eflags,比如
mov eax, 0x401000 + rand
pushfd
sub eax, rand
popfd
可以处理的指令包括但不限于
push 立即数
mov 寄存器,立即数
mov 内存,立即数
局部混淆:
添加无用的stc,cmc指令等
比如一条指令为mov eax,123
你可以在此语句前插入一条 neg eax等等
函数调用与跳转混淆
call addr可变形为
push 返回地址
jmp addr
或者
push返回地址
push addr
ret
等等
防静态跟踪:
让ida无法判断堆栈、返回地址的情况
比如你可以使用[esp]的值作为下一条指令的地址
push reg mov reg,addr xchg reg, [esp] ret
或者用jmp eax这样的指令,ida无法判断eax的值是多少,也就找不到下一条指令在哪
代码块拆分乱序(稍微有点复杂)
先将代码块按随机长度分块
1->2->3->4->5->6
然后随机顺序
3 1 4 5 6 2
如果序号左边不等于右边-1,那么代表这一块代码在乱序后还是连接在一起比如4,5,6这块代码
3 1之间就需要插入一个jmp指令,从3跳转到4的jmp
1 4也需要插入一个1跳转到2的jmp
抹除jcc
假设有如下汇编指令
0x400000: je 0x400010
...... //跳转不成立
0x400010: //跳转成立
那么可以发现这条jcc指令要么不成立,执行下一行0x400005,要么跳向0x400010
那么可以通过动态计算,算出一个值,这个值只有两种可能,就是这两个地址,然后用jmp eax,call eax,push ret之类的转移过去
0x400005 0x400010 0x400005 0x400010
0 1 1 0
可以用乘法、and运算之类的实现
整个代码的实现还考虑了可以迭代多层,还可以不同混淆方法之间按自定义顺序套来套去,再加上有代码乱序,用跟混淆同样的模板和方法实际上并不是很好去除混淆,并且如果塞入无用数据,模板还原可能更困难