逆向的第一步是什么?这要问你学习C语言的第一步是什么,很自然的,逆向的第一步当然也是大名鼎鼎“HelloWorld!”了。但是也不要因此就误认为这一节会很简单,如果你是第一次接触逆向的话,那么这一节还是有些难度的。
好的,让我们先写一个世界上最出名的程序:
int _tmain(int argc, _TCHAR* argv[])
{
printf("Hello World!/r/n");
return 0;
}
不错!很好的开始!然后用VS2008以Debug方式编译下,再用OllyDbg打开看看:
00411078 >JMP Test_0.004117B0
0041107D JMP Test_0.00412CC0
00411082 JMP <JMP.&MSVCR90D._lock>
00411087 JMP <JMP.&KERNEL32.GetProcAddress>
0041108C JMP Test_0.00411440
00411091 JMP Test_0.00413310
00411096 JMP <JMP.&MSVCR90D.?terminate@@YAXXZ>
0041109B JMP <JMP.&MSVCR90D._exit>
004110A0 JMP <JMP.&KERNEL32.GetCurrentThreadId>
004110A5 JMP <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 can take the following three optional arguments”,也就是告诉了我们main函数其实是有3个参数的,其后面的例子更是证明了这句话确实是微软写上去的:
main( int argc, char *argv[ ], char *envp[ ] )
嗯,他们又在标准上较劲了,但是考虑到我们大部分程序都是用vs编译的(而且Borland的C++的参数也是如此),因此我们还是做墙头草,随大流吧……
到这里有的读者可能会感到疑惑,如果我们使用的是符合C99标准的main函数呢?例如我们源码的main函数不就是两个参数吗。但是在这里我要很负责的告诉大家,不管我们代码中实际使用了几个参数,在程序被编译时其main函数肯定是三个参数的,因为这取决于Windows系统的机制。
因此现在已经为我们识别main函数提供了很好的特征,既有三个参数,且前两个参数为地址量的call就应该是我们的main函数了。除此之外,我们通过MSDN可知应用程序会随着main函数结束而退出,这又给了我们第二个有力的特征,既main函数很定是在程序退出代码附近的(而且目前的主流调试、反汇编工具都可以正确识别出退出函数exit)。
有了这些特征,我们再想找到main函数就不难了,目前我为大家提供三种方法:
1.1.1、字符串搜索法
安装完各个版本的C++编译器后,逐个写Hello World,然后用OllyDbg的搜索字符串功能搜索这个字符串,最后逐步回溯即可,下面我为大家演示一下我做的步骤。
用OllyDbg打开目标文件后,先记住程序默认停在哪里,然后在CPU窗格点击右键,依次选择【超级字符串参考】>【查找ASCII字符】,选择我们的“Hello World”后双击即可到main函数中,代码如下:
好的,让我们先写一个世界上最出名的程序:
int _tmain(int argc, _TCHAR* argv[])
{
printf("Hello World!/r/n");
return 0;
}
不错!很好的开始!然后用VS2008以Debug方式编译下,再用OllyDbg打开看看:
00411078 >JMP Test_0.004117B0
0041107D JMP Test_0.00412CC0
00411082 JMP <JMP.&MSVCR90D._lock>
00411087 JMP <JMP.&KERNEL32.GetProcAddress>
0041108C JMP Test_0.00411440
00411091 JMP Test_0.00413310
00411096 JMP <JMP.&MSVCR90D.?terminate@@YAXXZ>
0041109B JMP <JMP.&MSVCR90D._exit>
004110A0 JMP <JMP.&KERNEL32.GetCurrentThreadId>
004110A5 JMP <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 can take the following three optional arguments”,也就是告诉了我们main函数其实是有3个参数的,其后面的例子更是证明了这句话确实是微软写上去的:
main( int argc, char *argv[ ], char *envp[ ] )
嗯,他们又在标准上较劲了,但是考虑到我们大部分程序都是用vs编译的(而且Borland的C++的参数也是如此),因此我们还是做墙头草,随大流吧……
到这里有的读者可能会感到疑惑,如果我们使用的是符合C99标准的main函数呢?例如我们源码的main函数不就是两个参数吗。但是在这里我要很负责的告诉大家,不管我们代码中实际使用了几个参数,在程序被编译时其main函数肯定是三个参数的,因为这取决于Windows系统的机制。
因此现在已经为我们识别main函数提供了很好的特征,既有三个参数,且前两个参数为地址量的call就应该是我们的main函数了。除此之外,我们通过MSDN可知应用程序会随着main函数结束而退出,这又给了我们第二个有力的特征,既main函数很定是在程序退出代码附近的(而且目前的主流调试、反汇编工具都可以正确识别出退出函数exit)。
有了这些特征,我们再想找到main函数就不难了,目前我为大家提供三种方法:
1.1.1、字符串搜索法
安装完各个版本的C++编译器后,逐个写Hello World,然后用OllyDbg的搜索字符串功能搜索这个字符串,最后逐步回溯即可,下面我为大家演示一下我做的步骤。
用OllyDbg打开目标文件后,先记住程序默认停在哪里,然后在CPU窗格点击右键,依次选择【超级字符串参考】>【查找ASCII字符】,选择我们的“Hello World”后双击即可到main函数中,代码如下: