当前位置:文档之家› 代码逆向(一)――寻找main函数入口

代码逆向(一)――寻找main函数入口

逆向的第一步是什么?这要问你学习C语言的第一步是什么,很自然的,逆向的第一步当然也是大名鼎鼎“HelloWorld!”了。

但是也不要因此就误认为这一节会很简单,如果你是第一次接触逆向的话,那么这一节还是有些难度的。

好的,让我们先写一个世界上最出名的程序:int _tmain(int argc, _TCHAR* argv[]){printf("Hello World!\r\n");return 0;}不错!很好的开始!然后用VS2008以Debug方式编译下,再用OllyDbg打开看看:>JMP Test_0.004117B0DJMP Test_0.00412CC0JMP <JMP.&MSVCR90D._lock>JMP <JMP.&KERNEL32.GetProcAddress>CJMP Test_0.JMP Test_0.JMP <JMP.&MSVCR90D.?terminate@@YAXXZ>BJMP <JMP.&MSVCR90D._exit>004110A0JMP <JMP.&KERNEL32.GetCurrentThreadId>004110A5JMP <JMP.&MSVCR90D._initterm>看看我们的程序停在了什么鬼地方,如果各位初学读者试图从这里就开始分析的话那真的很恐怖,相信30分钟内你的自信心将被打击到零……我们都知道其实编译器在编译我们的程序前会做很多准备工作,而这些准备工作由于涉及的东西较多且每个由此编译器生成的程序都一样,因此我们不必深究,只需快速且准确的找到main函数即可。

但是这对于初学逆向的朋友来说也是最难的,下面我就教各位读者怎样突破这个障碍。

想要找到main函数,那么我们就要从C语言本身讲起,在刚刚开始学习C 语言的时候我们就被不幸的告知,我们的程序中必须要包含一个名字叫做main 的函数,不管你多讨厌它都必须如此,后来便成了习惯……后来查查C99标准,发现“int main(int argc, char *argv[])”与“int main(void)”都是被接受的,然后又查查MSDN,可以清晰看到一句话“The main and main functions ca n take the followingthree optional arguments”,也就是告诉了我们main函数其实是有3个参数的,其后面的例子更是证明了这句话确实是微软写上去的:main( int argc, char *argv[ ], char *envp[ ] )嗯,他们又在标准上较劲了,但是考虑到我们大部分程序都是用vs编译的(而且Borland的C++的参数也是如此),因此我们还是做墙头草,随大流吧……004113E7MOV ESP, EBP004113E9POP EBP004113EARETN我们单击选择函数入口后,可以看到CPU窗格下面的信息窗格中显示如下信息:跳转来自F我们单击选择此信息后,点击鼠标右键,并选择【转到JMP来自F】后即可来到上层调用函数(以后我们将之称为“返回到调用”):AJMP <JMP.&KERNEL32.DebugBreak>FJMP Test_0.004113A0;我们停到这里JMP Test_0.004124E0遇到这种情况直接在返回到调用,此时来到真正调用main函数的地方:FMOV EAX, DWORD PTR DS:[417148]PUSH EAXMOV ECX, DWORD PTR DS:[41714C]BPUSH ECXCMOV EDX, DWORD PTR DS:[417144]PUSH EDXCALL Test_0.F;我们停到这里ADD ESP, 0CBMOV DWORD PTR DS:[41715C], EAXCMP DWORD PTR DS:[417150], 0JNZ SHORT Test_0.MOV EAX, DWORD PTR DS:[41715C]EPUSH EAX; /status => 0FCALL DWORD PTR DS:[<&MSVCR90D.exit>]; \exit通过上面的代码我们便看到了main函数的典型特征,临近exit,且有三个参数。

接下来我们要做的就是不断地重复上面的步骤,一直到找到程序入口点为止。

最后你要做的就是针对不同的版本不同城上的编译器重复上面的步骤,直到收集到你认为足够丰富的信息后结束,从此你就再也不用怕为找不到main函数而苦恼了。

1.1.2、栈回溯法栈回溯的方法是先找到main函数中的那个“HelloWorld”,下断点并按【F9】键运行后查看堆栈情况,我这里的堆栈情况如下:0012FE9C7C930208ntdll.7C930208;我们停在这里0012FEA0FFFF0012FEA47FFDE0000012FEA8CCCC…………0012FF64CCCC0012FF68/0012FFB80012FF6C|返回到Test_0.来自Test_0.F0012FF70|000010012FF74|003D2C600012FF78|003D2D400012FF7C|0A641DBC0012FF80|7C930208ntdll.7C9302080012FF84|FFFF0012FF88|7FFDE0000012FF8C|00369E990012FF90|00000012FF94|00000012FF98|001300ASCII "Actx "0012FF9C|00000012FFA0|0012FF7C0012FFA4|000200012FFA8|0012FFE0指向下一个SEH记录的指针0012FFAC|DSE处理程序0012FFB0|0A3788D40012FFB4|00000012FFB8]0012FFC00012FFBC|004117BF返回到Test_0.004117BF来自Test_0.004117D00012FFC0\0012FFF00012FFC47C817077返回到kernel32.7C817077对于这些信息我们只需要关注注释前面有“返回到”三个字的,离我们最近是:0012FF6C|返回到Test_0.来自Test_0.F鼠标单击选择该项后,按【Enter】键即可来到返回地址处:F.A1 MOV EAX, DWORD PTR DS:.50PUSH EAX.8B0D 4C714100 MOV ECX, DWORD PTR DS: [41714C]B.51PUSH ECXC.8B15 MOV EDX, DWORD PTR DS: [417144].52PUSH EDX.E8 97F6FFCALL Test_0.F.83C4 0CADD ESP, 0C;我们停在这里B.A3 5C714100MOV DWORD PTR DS: [41715C], EAX.833D >CMP DWORD PTR DS: [417150], 0.75 0CJNZ SHORT Test_0..A1 5C714100MOV EAX, DWORD PTR DS: [41715C]E.50PUSHEAX;/status=>0F.FF15 CALL DWORD PTR DS:90D.exit>]; \exit此时我们又来到了这个熟悉的地方,接下来的事情就要各位读者自己发挥了(重复上面的步骤)。

1.1.3、逐步分析法以上讲的两种方法都是在学习与知识储备时用的,不可能收到什么实战效果。

假如我们现在碰到了一个现在就需要我们分析的软件,而且它的编译环境我们以前没碰到过,这就要求我们纯手工分析并找到main函数了。

之所以将之称为逐步分析法,是因为我们不需要阅读它代码的具体含义,而是只需要以JMP与CALL为单位逐个跟进,从而根据main函数的特征判定main函数的所在位置。

其实这种方法有点类似于文件搜索,先搜索根目录、在逐层加深搜索其子目录,直到找到我们需要的东西。

那我们的程序为例,我们的OEP处就是一个JMP,因此其“根目录”也就是第一层代码里是不可能有我们的main函数了,当我们跟进这个JMP后会发现如下代码:004117B0> \8BFFMOV EDI, EDI004117B2/.55PUSH EBP004117B3|.8BECMOV EBP, ESP004117B5|.E8 96F8FFCALL Test_0.004117BA|.E8 11000CALL Test_0.004117D0004117BF|.5DPOP EBP004117C0\.C3RETN我们发现第二层代码里也没有我们的main函数,但是有两个CALL。

因此我们跟进第一个CALL中,为了节省篇幅,我在这里就不贴出代码了,我在这里并没有发现main函数,但是发现了数个JMP与CALL。

不过需要注意的是,我们一定要注意采用逐层搜索的思想,因此这里的CALL与JMP就不要再继续跟下去了,我们现在要住的是返回上一层,看看第二个CALL里是什么:004117D0MOV EDI, EDI004117D2PUSH EBP004117D3MOV EBP, ESP004117D5PUSH -2…………CALL Test_0.004110FF…………CALLDWORDPTRDS:[<&KERNEL32.Interlocke>;kernel32.InterlockedCompareExchange…………EJMP SHORT Test_0.DPUSH 3E8; /Timeout =1000. msCALL DWORD PTR DS:[<&KERNEL32.Sleep>]; \SleepBJMP SHORT Test_0.…………004118EBPUSH Test_0.004157C8;_004118F0PUSH 0004118F2PUSH 1F4004118F7PUSH Test_0.;f004118FCPUSH 2004118FECALL DWORD PTR DS: [<&MSVCR90D._CrtDbgRep>;MSVCR90D._CrtDbgReportWADD ESP, 14 …………PUSH 0; /NewValue = 0PUSH Test_0.C; |pTarget = Test_0.CACALL DWORD PTR DS:[<&KERNEL32.Interlocke>; \InterlockedExchange…………PUSH Test_0.ECALL Test_0.ADD ESP, 4…………APUSH 0CPUSH 2EPUSH 0CALL DWORD PTR DS:[417590];注意这里,虽然这个CALL也有三个参数,但是仔细分析一下我们就会发现;这并不是main函数,因为main函数的后两个参数是指针,这里的0与2显然;不符合要求。

相关主题