自修改代码(Self Modifying Code)的详解(2)_Oracle数据库_黑客防线网安服务器维护基地--Powered by WWW.RONGSEN.COM.CN

自修改代码(Self Modifying Code)的详解(2)

作者:黑客防线网安Oracle维护基地 来源:黑客防线网安Oracle维护基地 浏览次数:0

黑客防线网安网讯:  _Demo(_printf); return 0; } 如果有人告诉你高级语言无法在堆栈上执行代码那么请不要相信.  需要的一些优化 首先你要考虑使用哪种编译器,如果你打算使用SMC或者要在堆栈上执行代码,你一定...

  _Demo(_printf);
return 0;
}

如果有人告诉你高级语言无法在堆栈上执行代码那么请不要相信.

 

需要的一些优化

首先你要考虑使用哪种编译器,如果你打算使用SMC或者要在堆栈上执行代码,你一定要好好的研究编译器的使用向导.很多人第一次失败的原因都是因为没有使编译器"最优化".

怎么会发生这种现象呢?因为在纯粹的高级语言中,例如C和PASCAL,曾经被谴责无法拷贝函数的代码到堆栈和其他地方,程序员可以获得一个函数的指针,但没有它的标准,我们的程序员称它为"魔数",因为只有编译器知道.

幸运的是,几乎所有的编译器使用同样的逻辑产生代码,这样程序可以假设编译的代码,程序员也可以假设.

我们返回去看看例程2.假设指向函数的指针和函数的首地址一致,主体在首地址的后面,大多数编译器使用这种"common sense compiling",许多大的编译器都使用这种规则(VC++, Borland, 等等).所以如果你没使用一些不知名的编译器,那就不要担心这些.

需要注意的是VC++,如果在Debug模式,编译器就会插入一个"适配器"并且将函数分配到其他一些地方,谴责MS,但是不要担心,只要在编译器的选项里选中"Link Incrementally"就可以了.如果你的编译器没有这样的选项,或者类似的事情,你要么不使用SMC要么改用其他的编译器.

决定函数长度是另一个问题,但这是需要技巧的.在C++里,sizeof结构不返回函数自身的长度,而是指向函数的指针的大小.但按照规则:编译器是按源代码中代码出现的顺序分配内存.所以,函数主体的长度就等于指向函数的指针和指向函数后面的指针之间的距离.容易吧!

 

优化编译器还有另一件事情去做:删除那些他们认为不再使用的变量.返回去看例程2.一些东西被写到了buff缓存.但是没有从那个地方读任何东西,大多数的编译器无法是被传递到缓存的控制.所以他们删除了正在拷贝的代码.这就是为什么控制被传递到了未被初使化的缓存.接着程序崩溃了,如果出现这种问题,请取消"Global optimization"选项.

 

如果你的程序仍然不工作,别放弃,原因很可能是编译器在每个函数的末尾插入了常规调用来监控堆栈.Microshoft' VC++就是这么做的.它在debug项目里放置了__chkesp调用.不要打算到文档里找到它,那里面没有---想一想,这种调用是相关的,没办法屏蔽它,然而,在release版本里,VC++不检查堆栈的状态.所以就不会出现这种问题.

 

在自己的APPS中使用SMC

现在你可以问你自己(或者问我)"在堆栈上执行代码的好处是什么?"回答是:堆栈上的函数的代码可以被很灵巧的改变.

即使笨人的加密代码也使解密高手变的难堪.当然,如果调试的话会变的容易一点.但还是很难.

 

这个简单的加密算法将用异或连续的处理代码的每一行,并且重新执行一遍将产生我们需要的目标代码.

这儿是个例子用来读我们的DEMO函数的内容,并把它加密,把结果写到文件.

 

例程3:怎样加密DEMO函数

void _bild()
{
FILE *f;
char buff[1000];
void (*_Demo) (int (*) (const char *,...));
void (*_Bild) ();
_Demo=Demo;
_Bild=_bild;

int func_len = (unsigned int) _Bild - (unsigned int) _Demo;
f=fopen("Demo32.bin", "wb");
for (int a=0; a<func_len; a++)
fputc(((int) buff[a]) ^ 0x77, f);
fclose(f);
}


在加密完成之后,内容被放到了字符串中,现在Demo函数可以从初始化代码中被移除了,然后当我们需要的时候,它可以被加密,可以被拷贝到本地缓存,可以被执行调用.

这是我们如何实现的例子:

 

例程4:加密程序

int main(int argc, char* argv[])
{
char buff[1000];
int (*_printf) (const char *,...);
void (*_Demo) (int (*) (const char *,...));
char code[]="x22xFCx9BxF4x9Bx67xB1x32x87
x3FxB1x32x86x12xB1x32x85x1BxB1
x32x84x1BxB1x32x83x18xB1x32x82
x5BxB1x32x81x57xB1x32x80x20xB1
x32x8Fx18xB1x32x8Ex05xB1x32x8D
x1BxB1x32x8Cx13xB1x32x8Bx56xB1
x32x8Ax7DxB1x32x89x77xFAx32x87
x27x88x22x7FxF4xB3x73xFCx92x2A
xB4";

_printf=printf;
int code_size=strlen(&code[0]);
strcpy(&buff[0], &code[0]);

for (int a=0; a<code_size; a++)
buff[a] = buff[a] ^ 0x77;
_Demo = (void (*) (int (*) (const char *,...))) &buff[0];
_Demo(_printf);
return 0;
}


注意printf函数显示一个祝贺.第一眼你可能注意不到什么是没用的,但是如果找一下字符串"Hello, OSIX!" 所在的位置,它不应当在代码段(borland把它放到那是有原因的)--因此检查数据段,你会发现它原来因该在那.

现在,即使攻击者查看源代码,也会迷惑不解的,我用这种方法隐藏所有的信息(一串数字,我的程序的发生器,等等).

如果你想用这种方法检验序列号,检验方法要有组织,以便解压的时候还能用到,下一个例子我会讲这些.

记住,实现SMC的时候需要知道你要改变的字节的确切的位置.因此,需要用汇编来代替高级语言.

 

用汇编语言来实现有一个问题要注意,MOV指令需要通过传递绝对的线性地址来改变确切的字节.在程序运行期间我们能找到这些信息.调用 $+5POP REGMOV [reg+relative_address], xx状态已经取得.可以工作了,插入下面声明,它执行CALL指令,并且从堆栈弹出返回地址(或者这个指令的绝对地址).这被用来作为堆栈函数代码的基地址.

下面是序列好代码的例子:

例程5:产生一个序列号,并在堆栈运行.

MyFunc:
push esi ; Saving the esi register on the stack
mov esi, [esp+8] ; ESI = &username[0]
push ebx ; Saving other registers on the stack

    黑客防线网安服务器维护方案本篇连接:http://www.rongsen.com.cn/show-13345-1.html
网站维护教程更新时间:2012-03-23 00:53:47  【打印此页】  【关闭
我要申请本站N点 | 黑客防线官网 |  
专业服务器维护及网站维护手工安全搭建环境,网站安全加固服务。黑客防线网安服务器维护基地招商进行中!QQ:29769479

footer  footer  footer  footer