逆向分析篇二:寻找程序入口点

前言

本次文章只用于技术讨论,学习,切勿用于非法用途,用于非法用途与本人无关!

所有环境均为本地环境分析,且在本机进行学习。

主函数大家应该都知道,他是程序的入口点,但主函数并不一定是main函数。接下来分析都会用到下面的方法,如果连主函数都找不到的话跟谈去进行分析,所以把找主函数放到了正式文章的第一篇,这篇文章因为篇幅我主要写了三种找主函数的方法,着重写了第三种方法,主要就是提供一种思路,如果各位大佬有其它好的方法可以进行交流学习。作者:rkabyss

生成程序

源代码,很短,主要就是学习如何找主函数,不要在意内容。

#include <stdio.h>
#include <string.h>
​
int main()
{
printf("Hello Word!");
return 0;​
}

利用Visual Studio生成可执行文件,我的是最新的2022版本,我与2019版本比较了一下,不同点在于Release上,建议在首次分析时,把符号文件加载上,这样程序会识别出一些函数,看起开会舒服一点,如果不带符号文件,函数名称是IDA给命名的,对于不熟悉的分析起来就很难受。

1654488081_629d7c113435221386796.png!small?1654488081433

我主要使用Visual Studio 2022版本生成下面四个不同类型,建议分析特征时带上符号文件,在正式分析时把符号文件和分析工具中的符号文件删除再进行分析。

建议:在有时间的情况下,建议把VC++、Visual Studio code 、Visual Studio2015~2022平台,在不同平台生成Debug和release版本进行分析,因为编译器、类型、位数都会影响主函数位置,提前分析一下他们之间不同点,在之后遇到会更好的去分析。

1654488121_629d7c39a2fcd9769ac02.png!small?1654488121692

寻找主函数

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、根据字符串找主函数

寻找字符串也是有着很多限制,如果程序中没有进行输出和输入,那么您就没有办法利用字符串去寻找主函数。如果存在字符串的话他也是最快找到入口点的方法之一。

1654488185_629d7c79837014ff4c7e9.png!small?1654488185891

1654488205_629d7c8db6ff0f21aecd4.png!small?1654488206165

3、根据特征找主函数

我主要分析特征的方法是逆推,从主函数不断往上一层推,知道找不到更上一层函数,去发现他们的特征,帮助自己寻找主函数。利用这种方法把不同编译器生成的Hello Word!去分析他的特征,总结不同点,这篇文章主要针对VS2022的分析,没有对其它编译器生成进行分析。

(1)Debug特征分析

下图为主函数的内容,接下来会进行逆推,来分析其特征。1654488265_629d7cc928e361c893d3e.png!small?1654488265407

点击入口函数摁Ctrl+X跳转到上一层函数。1654488290_629d7ce243edc14a73b87.png!small?1654488290548

他是jmp _main跳转到的主函数,这个其实是一个跳转表。1654488316_629d7cfc086f3d570ad30.png!small?1654488316253

一共有四个CALL,进入第四个CALL。1654488338_629d7d127feac84dc6770.png!small?1654488338781

在往上跳转,会发现会有很对代码,主要去寻找他的特征,下图我光标选中的,几乎只出现过一次,没有出现多次,在其它编译器中也没发现过多次重复的。

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

1654488437_629d7d756dbde48e36349.png!small?1654488437797

进入到第二个call中。

1654488455_629d7d874350d368f78cd.png!small?1654488455412

进入到唯一的call中。1654488474_629d7d9ace06507f4a724.png!small?1654488474892

下图已经没有在上一层函数。1654488490_629d7daa9132a91a286c4.png!small?1654488490740

下面总结一下分析的特征

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执行到用户层。1654488533_629d7dd5b0f0581de08c9.png!small?1654488534258

根据上边特征分析,这是第一步,通过跳转表进行跳转,直接执行下一步即可。1654488548_629d7de42776feb98c906.png!small?1654488548598

进入到第一个call中。1654488563_629d7df338e4c005029a8.png!small?1654488563701

进入到第二个call中。1654488580_629d7e0437ddf1e3f9c81.png!small?1654488580647

根据上边分析的,去寻找特征,找到直接下断点跳转过去然后F7进入。1654488600_629d7e18059f3f4329366.png!small?1654488601904

进入到第四个call中。1654488616_629d7e2841a160a61a42d.png!small?1654488616798

通过跳转表jmp进入到主函数。1654488632_629d7e3860fd60b5c4bab.png!small?1654488632874

进入到主函数。1654488647_629d7e472b375e66119c6.png!small?1654488647513

(3)Debug x64

接下来寻找64位程序的主函数,特征跟上边分析出来的几乎一样,不同在于调用约定的不同。1654488675_629d7e63d0ef8641c0f5f.png!small?1654488676292

通过jmp跳转表进入,直接F8或F7都可以。1654488719_629d7e8fbf4ee4271b081.png!small?1654488720427

进入到第一个call中。1654488771_629d7ec3ee82e57bf4d57.png!small?1654488772333

进入到第二个call中。1654488787_629d7ed3d2c6e1e04e7ab.png!small?1654488788319

根据上边分析的特征,找到下边,可以发现除了没有add进行平栈,其他根上边分析一样,因为64位程序默认用的是fastcall,因为fastcall通过寄存器方法传参,栈由被调方进行平栈,在第四篇我去分析调用约定的特征。1654488804_629d7ee4c4714461eb5a5.png!small?1654488805314

进入到第四个call中。1654488819_629d7ef3292efe0cf576c.png!small?1654488819548

通过jmp跳转表进入主函数。1654488833_629d7f01cf0ade8461125.png!small?1654488834298

进入到主函数。1654488853_629d7f1535f63d66257bf.png!small?1654488853757

(4)Release特征分析

Release模式会对程序进行优化,他的优化也是有等级的,release会优化掉一些没有用或者不去执行的代码,用最少的代码表示程序的功能,这样会节省很多时间,如果你的代码百万条,利用debug进行生成可能会生成好几个小时,利用release就会节省很多时间,这也就产生如果去逆向分析release模式生产的代码会很难分析,没有足够经验会非常吃紧。1654488881_629d7f311126ca4d59f85.png!small?1654488881297

从主函数跳到上一层函数,找他的特征,为三个push紧跟一个call,因为release不像debug那样层数很多,找起主函数也就容易多了,可以通过上边介绍的第一种方法进行找主函数。1654488902_629d7f4687109c8b56763.png!small?1654488902678

通过一个jmp跳转表跳转到上一步。因为特征比较简单就不总结了,这个只是VS2022的release特征,其它编译器生成的也有很大区别。1654488917_629d7f558eacff9a042c1.png!small?1654488917642

(5)Release x86

当前处于系统层,直接F9进入到用户层。1654488942_629d7f6e46fae35440aec.png!small?1654488942742

进入到jmp跳转。1654488956_629d7f7cd2a6dfbe1c61b.png!small?1654488957173

找到上边分析特征进入入口点。1654488971_629d7f8b4d617def79a4f.png!small?1654488971717

进入到主函数。

1654488983_629d7f97ce76a940eba02.png!small?1654488984003

(6)Release x64

当前处于系统层,直接F9进入到用户层。1654489002_629d7faaef35c31df54f5.png!small?1654489003568

根据分析特征,通过jmp进行跳转。1654489015_629d7fb7af5f807f4cdb8.png!small?1654489015853

因为64位程序用到fastcall,使用寄存器传参,分别是rcx、rdx、r8、r9寄存器,根据此特征找到主函数入口。1654489031_629d7fc7ecdf09ad21c1c.png!small?1654489032133

进入到主函数。1654489059_629d7fe3a092128145c60.png!small?1654489059885

总结

这篇文章主要是对VS2022的编译器生成的程序进行分析,其实在逆推完之后在顺着找一下是很简单的。主要是介绍了三种方法,但不全,还有一些其他方法没有写进来,比如存在scanf,其实就可以直接一路F8,在哪里停下来就可以判断他是主函数入口函数。

随着现在编译器不断升级,层数是越来越多,在前几年的一些方法已经不在适用于如今,技术的迭代更新是很快的,只有不断去学习与研究,找到属于自己的方法和套路,他人的文章只是借鉴与思路的学习。最后说一句在别的地方看到的一句话,做安全,不忘初心,与时俱进,方得始终!

本文作者:云科攻防实验室-2, 转载请注明来自FreeBuf.COM

本文来自投稿,不代表安强科技社区立场,如若转载,请注明出处:https://community.anqiangkj.com/archives/17821

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022年6月17日 上午11:21
下一篇 2022年6月17日 上午11:23

相关推荐