世上的唯美,是那双清澈的眼眸 ——题记
以前看《自己动手写操作系统》的时候,书中有段代码改变了自身并且完成了正确的跳转,这个想法实在漂亮(记得是从实模式跳转到保护模式,晚了不去查了)。
但当时惊艳之后没做深入考虑,这是个失误。代码可以改变自身,利用这点可以做一点简陋的保护机制(代码加密,当然也可以用于病毒程序)。但是估计保护效果
不会很好,下面分析一下卡巴斯基给出例子。
#include <windows.h>
#define CRYPT_LEN ((int)crypt_end - (int)for_crypt)
mark_begin()//用以标记,待加密函数在文件中的起始位置
{
__asm _emit 'K' __asm _emit 'P' __asm _emit 'N' __asm _emit 'C'
}
for_crypt(int a,int b)//待加密函数
{
return a + b;
}crypt_end(){}
mark_end()//用以标记,待加密函数在文件中的结束位置
{
__asm _emit 'K' __asm _emit 'P' __asm _emit 'N' __asm _emit 'C'
}
crypt_it(unsigned char *p,int c)//解密过程
{
int a;
VirtualProtect(p,c,PAGE_READWRITE,(DWORD*)&a);
for (a = 0;a < c; a++) *p++ ^=0x66;
VirtualProtect(p,c,PAGE_READONLY,(DWORD*)&a);
}
main()
{
crypt_it((unsigned char*)for_crypt,CRYPT_LEN);//调用解密函数,解密被保护的代码
printf("%02Xh\n",for_crypt(0x69,0x66));//函数运行结果
}
这个文件编译连接完成后,肯定是没法运行的。
加密工作是在编译完之后,用十六进制编辑器修改两个KPNC之间部分代码(此处就是^=0x66的逆运算,简单的XOR算法)。
当然完成之后两个KPNC标志也要换成比较迷惑的数值,但这些都是徒劳的,没法抵挡灰阔那双清澈的眼眸。如果为了泄被破解之愤,大可以改成WOCA或者F.UCK等标志。
for (a = 0;a < c; a++) *p++ ^=0x66;试图修改.text段,这回异常的。所以连接obj文件时加上选项/section:.text ERW指定具有读写和执行属性。
而Windows API VirtualProtect(p,c,PAGE_READWRITE,(DWORD*)&a);修改了被保护代码的内存页面属性,指定为可读写。
这种保护对于静态反汇编来说很容易制造混乱。但是在调试面前却不堪一击,那个API可是很显眼啊。另外代码段中多出一堆乱七八糟的东西也是很扎眼的。解密算法也较容易分析出来(及时算法复杂也只是时间问题)。不过跟反调试技术结合一下,应该有点用处。
只是玩味一下这个东西。
另外:也可以在堆栈中分配空间,那是可写的,再把.text段中的负责“修改自身”的函数通过memcpy放到堆中,调用堆中的修改函数来改变自身代码。虽然没有异常,但是可能存在代码重定位问题(毕竟代码执行的地址无法预知了)。