前言
本次文章只用于技术讨论,学习,切勿用于非法用途,用于非法用途与本人无关!
所有环境均为本地环境分析,且在本机进行学习。
主函数大家应该都知道,他是程序的入口点,但主函数并不一定是main函数。接下来分析都会用到下面的方法,如果连主函数都找不到的话跟谈去进行分析,所以把找主函数放到了正式文章的第一篇,这篇文章因为篇幅我主要写了三种找主函数的方法,着重写了第三种方法,主要就是提供一种思路,如果各位大佬有其它好的方法可以进行交流学习。作者:rkabyss
生成程序
源代码,很短,主要就是学习如何找主函数,不要在意内容。
#include <stdio.h>
#include <string.h>
int main()
{
printf("Hello Word!");
return 0;
}
利用Visual Studio生成可执行文件,我的是最新的2022版本,我与2019版本比较了一下,不同点在于Release上,建议在首次分析时,把符号文件加载上,这样程序会识别出一些函数,看起开会舒服一点,如果不带符号文件,函数名称是IDA给命名的,对于不熟悉的分析起来就很难受。
我主要使用Visual Studio 2022版本生成下面四个不同类型,建议分析特征时带上符号文件,在正式分析时把符号文件和分析工具中的符号文件删除再进行分析。
建议:在有时间的情况下,建议把VC++、Visual Studio code 、Visual Studio2015~2022平台,在不同平台生成Debug和release版本进行分析,因为编译器、类型、位数都会影响主函数位置,提前分析一下他们之间不同点,在之后遇到会更好的去分析。
寻找主函数
1、根据启动函数找入口
开发的程序,在调试时总是从 main 或 WinMain 函数开始,这就让开发者误认为它们是程序的第一条指令执行处,这个认识其实是错误的。main或 WinMain也是一个函数,也需要有一个调用者。在它们被调用前,编译器其实已经做了很多事情,所以main 或 WinMain 应该是"语法规定的用户入口",而不是"应用程序入口"。在应用程序被操作系统加载时,操作系统会分析执行文件内的数据,分配相关资源,读取执行文件中的代码和数据到合适的内存单元,然后才是执行入口代码,人口代码其实并不是main 或WinMain,通常是 mainCRTStartup、wmainCRTStartup、WinMainCRTStartup 或wWinMainCRTStartup,具体视编译选项而定。其中 mainCRTStartup 和 wmainCRTStartup是控制台环境下多字节编码和 Unicode 编码的启动函数,而 WinMainCRTStartup 和 wWinMainCRTStartup则是Windows 环境下多字节编码和 Unicode 编码的启动函数。在开发过程中,允许程序员自己指定入口。
主函数在执行前调用了三个函数,在程序中的特征是三个push跟着一个call,这个方法如今已经不太适用,因为在之前很少会有程序员去写64位程序,随着编译器升级连32位程序利用这种也不太容易找到主函数,这种三个push跟着一个call特征现在限制于编译器,不是所有编译器都适用了,所有没有配图,主要介绍一下又这个方法。
2、根据字符串找主函数
寻找字符串也是有着很多限制,如果程序中没有进行输出和输入,那么您就没有办法利用字符串去寻找主函数。如果存在字符串的话他也是最快找到入口点的方法之一。
3、根据特征找主函数
我主要分析特征的方法是逆推,从主函数不断往上一层推,知道找不到更上一层函数,去发现他们的特征,帮助自己寻找主函数。利用这种方法把不同编译器生成的Hello Word!去分析他的特征,总结不同点,这篇文章主要针对VS2022的分析,没有对其它编译器生成进行分析。
(1)Debug特征分析
下图为主函数的内容,接下来会进行逆推,来分析其特征。
点击入口函数摁Ctrl+X跳转到上一层函数。
他是jmp _main跳转到的主函数,这个其实是一个跳转表。
一共有四个CALL,进入第四个CALL。
在往上跳转,会发现会有很对代码,主要去寻找他的特征,下图我光标选中的,几乎只出现过一次,没有出现多次,在其它编译器中也没发现过多次重复的。
add esp,4 movzx ecx,al test ecx,ecx jz short loc_411E72 mov edx,[ebp+var_24] mov eax,[edx] push eax call j_register_thread_local_exe_atexit_callback add esp,4 call main
进入到第二个call中。
进入到唯一的call中。
下图已经没有在上一层函数。
下面总结一下分析的特征
1、从jmp跳转表进入
2、进入唯一的call中
3、进入到第二个call中
4、进入找到上面分析的的特征
add
movzx
test
jz
mov
mov
push
call
add
call
5、进入到第四个call中,也是倒数第一个call中
6、通过跳转表跳转到函数入口点
下面寻找主函数会根据上边分析的特征进行寻找
(2)Debug x86
首先会进入到系统层,处于ntdll.dll模块中,直接F9执行到用户层。
根据上边特征分析,这是第一步,通过跳转表进行跳转,直接执行下一步即可。
进入到第一个call中。
进入到第二个call中。
根据上边分析的,去寻找特征,找到直接下断点跳转过去然后F7进入。
进入到第四个call中。
通过跳转表jmp进入到主函数。
进入到主函数。
(3)Debug x64
接下来寻找64位程序的主函数,特征跟上边分析出来的几乎一样,不同在于调用约定的不同。
通过jmp跳转表进入,直接F8或F7都可以。
进入到第一个call中。
进入到第二个call中。
根据上边分析的特征,找到下边,可以发现除了没有add进行平栈,其他根上边分析一样,因为64位程序默认用的是fastcall,因为fastcall通过寄存器方法传参,栈由被调方进行平栈,在第四篇我去分析调用约定的特征。
进入到第四个call中。
通过jmp跳转表进入主函数。
进入到主函数。
(4)Release特征分析
Release模式会对程序进行优化,他的优化也是有等级的,release会优化掉一些没有用或者不去执行的代码,用最少的代码表示程序的功能,这样会节省很多时间,如果你的代码百万条,利用debug进行生成可能会生成好几个小时,利用release就会节省很多时间,这也就产生如果去逆向分析release模式生产的代码会很难分析,没有足够经验会非常吃紧。
从主函数跳到上一层函数,找他的特征,为三个push紧跟一个call,因为release不像debug那样层数很多,找起主函数也就容易多了,可以通过上边介绍的第一种方法进行找主函数。
通过一个jmp跳转表跳转到上一步。因为特征比较简单就不总结了,这个只是VS2022的release特征,其它编译器生成的也有很大区别。
(5)Release x86
当前处于系统层,直接F9进入到用户层。
进入到jmp跳转。
找到上边分析特征进入入口点。
进入到主函数。
(6)Release x64
当前处于系统层,直接F9进入到用户层。
根据分析特征,通过jmp进行跳转。
因为64位程序用到fastcall,使用寄存器传参,分别是rcx、rdx、r8、r9寄存器,根据此特征找到主函数入口。
进入到主函数。
总结
这篇文章主要是对VS2022的编译器生成的程序进行分析,其实在逆推完之后在顺着找一下是很简单的。主要是介绍了三种方法,但不全,还有一些其他方法没有写进来,比如存在scanf,其实就可以直接一路F8,在哪里停下来就可以判断他是主函数入口函数。
随着现在编译器不断升级,层数是越来越多,在前几年的一些方法已经不在适用于如今,技术的迭代更新是很快的,只有不断去学习与研究,找到属于自己的方法和套路,他人的文章只是借鉴与思路的学习。最后说一句在别的地方看到的一句话,做安全,不忘初心,与时俱进,方得始终!