时间:2022-08-28 01:25:50
摘要:提出了一种对二进制文件中的库函数的展开形式进行识别的方法。首先对二进制文件进行反汇编,然后对反汇编代码中的必然指令进行识别,最后利用IDA Pro的脚本语言IDC编写代码进行验证。
关键词:C语言;展开形式;识别技术;反汇编
中图分类号:TP311文献标识码:A文章编号:1009-3044(2011)12-2829-02
1 库函数展开形式的出现
现代编译器为了加快程序的运行速度,避免调用库函数所带来的开销,在调用库函数时,直接出现库函数的展开形式。编译器有时也出于对所编程序优化的目的,为了提高程序运行时的效率经常在目标代码中出现展开的库函数形式,而且不同的编译器对同一个库函数编译出的汇编指令并不相同,即使是相同的编译器在不同的优化层次上编译出的汇编指令也不相同。展开库函数也有副作用,那就是使目标文件的大小有所增加,所以在C语言中具有这种展开库函数的函数不多,常见的有strcpy、strcat、memset和memcpy,本文只研究对它们的识别。
2 库函数展开形式的识别
2.1 库函数展开形式的特征
选用IDA Pro反汇编工具,IDA Pro是DataRescue公司出品的功能强大的专业级反汇编工具,IDA(The InteractiveDisassembler)的全名是交互式反汇编工具[ ]。首先把要识别的二进制文件进行反汇编,然后对反汇编后的代码进行识别。对于展开库函数的精确识别,是比较复杂的问题,但这些函数的反汇编后具有明显的特征,如strcpy()的展开形式的反汇编为:
1. mov edi,[esp+arg_4]
2. or ecx,0FFFFFFFFh
3. xor eax,eax
4. repne scasb
5. not ecx
6. sub edi,ecx
7. moveax,ecx
8. movesi,edi
9. movedi,[esp+arg_0]
10.shr ecx,2
11.rep movsd
12.movecx,eax
13.and ecx,3
14.repmovsb
此处strcpy()展开后有14条指令,而且随着不同编译器的不同优化,strcpy()展开后的指令条数各不相同。但经过对不同的编译器编译后的指令分析,有些指令是必须出现的指令,如上面提到的2、3、4、7、11和14指令,将展开库函数中必然出现的指令称为必然指令,其余指令称为普通指令。必然指令可以作为识别展开库函数的特征,故将展开库函数的识别转化为必然指令的识别。
2.2 库函数展开形式的识别方法
在具体实现过程中,利用IDA Pro的脚本语言IDC编写检测插件。编写前要先配置编译环境,然后套用模板编写IDA插件,最后复制编译好的插件(确定它在Windows下的扩展名为.plw,Linux下为.plx)到IDA的plugins目录,然后IDA将自动加载它。在VC++中编写一般分以下五部分:预处理、初始化函数、清除函数、主体运行函数和辅助说明[ ]。strcpy、strcat、memset和memcpy的必然指令可以查阅MSDN获知。把所有必然指令放在二维字符型数组中,每一行存放一个函数的必然指令集,中间用星号分隔,其中每个函数我们抽象出四条指令,二维数组的初始化如下:
mustorder[4][80]=
{“repne scasb*mov esi,edi*repmovesd*repmovesb”_//strcpy函数的必然指令
“movsw*rep movsd*movsb*mov edi,eax”//memcpy函数的必然指令
“repne scasb*rep movsd*movs*mov esi,eax”//strcat函数的必然指令
“movse*repne scasb*movsb*mov esi edi” //memset函数的必然指令
};
int flag[4]=0;
char funarray[4][10]={“strcpy”,”strcat”,”memset”,”memcpy”};
同时设置一个标志数组flag[4],其初始值为0。在程序运行过程中,如果所查找的必然指令存在mustorder数组中的一行,就让flag数组中相应下标所对应的数组元素加1,如果检查完程序后flag数组中有值为4的元素,则存在必然函数的展开形式,同时设置数组funarray存放函数名。
void findlongfun (int arg)
{ // 在所有段中查找
FILE *fp;
//打开存库函数的文件
if((fp=fopen(“需要检测得库函数文件”,”w+”))= =NULL{
printf(“ Buffer flow vulnerability file cannot be open\n”);
Exit(1);}
for (int s = 0; s < get_segm_qty(); s++) {
segment_t *seg = getnseg(s);
//仅查找代码段
if (seg->type == SEG_CODE)
{ // 反复执行循环,在所有的调用函数中查找
for (int x = 0; x < get_func_qty(); x++) {
//对标志数组初始化
for(i=0;i
flags[i]=0;
func_t *f = getn_func(x);
//在选中函数中从前到后循环查找
for (ea_t addr = f->startEA; addr < f->endEA; addr++) {
flags_t flags = getFlags(addr);
//仅仅从一行的开头并且这行标记是函数代码所在的行查找
if (isHead(flags) && isCode(flags)) {
char mnem[MAXSTR];
//记下我们这一行所在的汇编代码,放在字符数组mnem中,然后和存放必然指令的数组比较,看是否存在
ua_mnem(addr, mnem, sizeof(mnem)-1);
// 查看是否存在必然指令。
for (int i=0;i
{
//把查到的汇编代码通过match( )函数在mustorder数组的每一行查找
if(match(mnem, mustorder[i])= =0)label[i]++;
}
for(int i = 0;i
if(label[i]= =4){
strcpy(vulervar.funname,funarray [i]);
strcpy(vulervar.linefun,arr);
get_func_name(arr, vulervar.infun,sizeof(vulervar.infun));
ua_mnem(arr, vulervar.linedisa, sizeof(vulervar.linedisa));
fprintf(fp,“第%d个库函数地址:%10s函数名:%10s
所在行汇编代码:%20s所属函数:%10s\n”,n,vulervar.linefun,
vulervar.funname,vulervar.linedisa,vulervar.infun);
n++;
for(int j=0;j
label[j]=0;} } } } }
//关闭漏洞文件
if(fclose(fp)!=0)
{printf(“\n vulnerfile cannot be closed”);
exit(1);}
return; }
3 总结
论文对二进制文件中的库函数的展开形式进行识别,首先对二进制文件进行反汇编,然后对反汇编代码中的必然指令进行识别。最后利用IDA Pro的脚本语言IDC编写代码进行验证。论文的不足点是只对常用库函数进行识别,如何对所有C语言中存在的所有库函数的展开形式进行识别,是今后研究的一个重点。
参考文献:
[1] 飞天诚信,软件加密原理与应用[M].北京:电子工业出版社,2005:131-158.
[2] Micallef S.IDA Plug-in Writing In C/C++[EB/OL].(2005-10-08)[2008-09-25]./idapluginwriting/.