C语言程序入口
首先明确一点,这里所指的C语言程序入口为C语言的控制台程序的入口,和WIN32等其它类型的程序入口并不相同,主要为学习如何寻找程序入口
首先我们都知道C语言的控制台程序中,我们都将代码写在了main函数里,但是这个main函数不过是我们编写代码的入口,而不是真正程序的入口,如何找寻真正的程序入口?
在main函数的头部下断点,然后观察其堆栈的调用情况,通过前面的学习,我们知道在调用子函数即CALL XXXX后,会将要返回的地址压入堆栈中,我们可以通过这个返回地址来一层一层往上找到真正的程序入口
寻找程序入口
首先写一个之前调用空函数的例子,然后设置断点,断下以后,打开Call Stack(调用堆栈)窗口,可以看到如下画面
我们可以通过这个知道函数的调用顺序为:
KERNEL32!7c817077→mainCRTStartup→main
通常情况下,在调试程序的时候,比如通过OD或者x32dbg等调试工具来调试时,我们都希望程序能够在main函数这里断下,但实际上,它断在了mainCRTStartup这里
修改入口程序
为了进一步理解程序入口,我们可以手动修改程序入口,来观察其与main函数的不同
我们随便新建一个空函数
void Test(){
}
然后工程→设置(也可以使用快捷键ALT+F7)
然后在弹出来的新窗口中选中连接(Link)选项卡
接着再将分类里的常规改成输出
最后在入口点里填上我们前面新建的空函数的函数名即可
修改完入口程序以后,我们在Test函数那下个断点,观察其堆栈调用情况
我们可以发现入口点确实被修改为了Test并且mainCRTStartup也没了
但是实际上mainCRTStartup极其重要,我们不能没有它,因为它做了很多初始化的工作,关系到我们后续代码的正常运转
关于mainCRTStartup
- mainCRTStartup 和 wmainCRTStartup 是控制台环境下多字节编码和Unicode 编码的启动函数
- 而WinMainCRTStartup 和wWinMainCRTStartup 是windows 环境下多字节编码和Unicode 编码的启动函数
mainCRTStartup做了哪些事:
函数 |
功能 |
GetVersion() |
获取操作系统版本 |
_heap_init() |
初始化堆空间,和malloc相关 |
GetCommandLineA() |
获取命令行参数 |
_crtGetEnvironmentStringsA() |
获取环境变量 |
_setargv() |
设置argv |
_setenvp() |
设置envp |
_cinit() |
c初始化 |
寻找main函数入口
理论
前面我们已经知道了mainCRTStartup也就是程序入口,那么如何通过mainCRTStartup来找到main函数入口?
根据函数的参数来进行判断
乍看之下,main函数貌似只有两个参数,但实际上main函数一共有三个参数,只不过一般第三个参数我们并没有用到,于是在使用main函数时并没有加上,完整的main函数原型如下:
int main(int argc,char *argv[],char *envp[]){}
这里的argv和envp对应mainCRTStartup里_setargv()和_setenvp()
main函数的三个参数:
参数 |
含义 |
argc |
用于存放命令行参数的个数 |
argv |
是个字符指针的数组,每个元素都是一个字符指针,指向一个字符串,即命令行中的每一个参数 |
envp |
也是一个字符指针的数组,这个数组的每一个元素是指向一个环境变量的字符指针 |
我们这里无需关注这三个参数的具体细节,只用记住main函数具有三个参数的这个特征即可,然后通过这个特征我们来找main函数
实践
载入程序
我们拿OD随便拉一个简单的控制台程序来寻找其main函数
程序停在了入口处,也就是mainCRTStartup处,我们从这里出发开始寻找main函数
如果OD并没有停在mainCRTStartup这里,可以进行如下设置,然后重新加载程序即可
准备工作完成后,就开始从这里向下寻找main函数
寻找main函数
004011B0 >/$ 55 push ebp
004011B1 |. 8BEC mov ebp,esp
004011B3 |. 6A FF push -0x1
004011B5 |. 68 10F14100 push CallingC.0041F110
004011BA |. 68 A42C4000 push CallingC.00402CA4 ; SE 处理程序安装
004011BF |. 64:A1 0000000>mov eax,dword ptr fs:[0]
004011C5 |. 50 push eax
004011C6 |. 64:8925 00000>mov dword ptr fs:[0],esp
004011CD |. 83C4 F0 add esp,-0x10
004011D0 |. 53 push ebx
004011D1 |. 56 push esi
004011D2 |. 57 push edi
004011D3 |. 8965 E8 mov [local.6],esp
004011D6 |. FF15 3C414200 call dword ptr ds:[<&KERNEL32.GetVersion>; kernel32.GetVersion
004011DC |. A3 E0254200 mov dword ptr ds:[0x4225E0],eax
004011E1 |. A1 E0254200 mov eax,dword ptr ds:[0x4225E0]
004011E6 |. C1E8 08 shr eax,0x8
004011E9 |. 25 FF000000 and eax,0xFF
004011EE |. A3 EC254200 mov dword ptr ds:[0x4225EC],eax
004011F3 |. 8B0D E0254200 mov ecx,dword ptr ds:[0x4225E0]
004011F9 |. 81E1 FF000000 and ecx,0xFF
004011FF |. 890D E8254200 mov dword ptr ds:[0x4225E8],ecx
00401205 |. 8B15 E8254200 mov edx,dword ptr ds:[0x4225E8]
0040120B |. C1E2 08 shl edx,0x8
0040120E |. 0315 EC254200 add edx,dword ptr ds:[0x4225EC]
00401214 |. 8915 E4254200 mov dword ptr ds:[0x4225E4],edx ; ntdll.KiFastSystemCallRet
0040121A |. A1 E0254200 mov eax,dword ptr ds:[0x4225E0]
0040121F |. C1E8 10 shr eax,0x10
00401222 |. 25 FFFF0000 and eax,0xFFFF
00401227 |. A3 E0254200 mov dword ptr ds:[0x4225E0],eax
0040122C |. 6A 00 push 0x0
0040122E |. E8 8D180000 call CallingC.00402AC0
00401233 |. 83C4 04 add esp,0x4
00401236 |. 85C0 test eax,eax
00401238 |. 75 0A jnz short CallingC.00401244
0040123A |. 6A 1C push 0x1C
0040123C |. E8 CF000000 call CallingC.00401310
00401241 |. 83C4 04 add esp,0x4
00401244 |> C745 FC 00000>mov [local.1],0x0
0040124B |. E8 00150000 call CallingC.00402750
00401250 |. FF15 38414200 call dword ptr ds:[<&KERNEL32.GetCommand>; [GetCommandLineA
00401256 |. A3 2C3F4200 mov dword ptr ds:[0x423F2C],eax
0040125B |. E8 D0120000 call CallingC.00402530
00401260 |. A3 C4254200 mov dword ptr ds:[0x4225C4],eax
00401265 |. E8 B60D0000 call CallingC.00402020
0040126A |. E8 610C0000 call CallingC.00401ED0
0040126F |. E8 7C080000 call CallingC.00401AF0
00401274 |. 8B0D FC254200 mov ecx,dword ptr ds:[0x4225FC]
0040127A |. 890D 00264200 mov dword ptr ds:[0x422600],ecx
00401280 |. 8B15 FC254200 mov edx,dword ptr ds:[0x4225FC]
00401286 |. 52 push edx ; ntdll.KiFastSystemCallRet
00401287 |. A1 F4254200 mov eax,dword ptr ds:[0x4225F4]
0040128C |. 50 push eax
0040128D |. 8B0D F0254200 mov ecx,dword ptr ds:[0x4225F0]
00401293 |. 51 push ecx
00401294 |. E8 7BFDFFFF call CallingC.00401014
00401299 |. 83C4 0C add esp,0xC
0040129C |. 8945 E4 mov [local.7],eax
0040129F |. 8B55 E4 mov edx,[local.7]
004012A2 |. 52 push edx ; ntdll.KiFastSystemCallRet
004012A3 |. E8 88080000 call CallingC.00401B30
004012A8 |. 8B45 EC mov eax,[local.5]
004012AB |. 8B08 mov ecx,dword ptr ds:[eax]
004012AD |. 8B11 mov edx,dword ptr ds:[ecx] ; ntdll.7C92DC9C
004012AF |. 8955 E0 mov [local.8],edx ; ntdll.KiFastSystemCallRet
004012B2 |. 8B45 EC mov eax,[local.5]
004012B5 |. 50 push eax
004012B6 |. 8B4D E0 mov ecx,[local.8]
004012B9 |. 51 push ecx
004012BA |. E8 010A0000 call CallingC.00401CC0
004012BF |. 83C4 08 add esp,0x8
004012C2 \. C3 retn
我们从OD的断点处一路向下寻找具有三个参数的函数,最终找到了这里
00401286 |. 52 push edx ; ntdll.KiFastSystemCallRet
00401287 |. A1 F4254200 mov eax,dword ptr ds:[0x4225F4]
0040128C |. 50 push eax
0040128D |. 8B0D F0254200 mov ecx,dword ptr ds:[0x4225F0]
00401293 |. 51 push ecx
00401294 |. E8 7BFDFFFF call CallingC.00401014
00401299 |. 83C4 0C add esp,0xC
寻找技巧:虽然说push的个数并不一定代表就是参数个数,但是通过push确实可以过滤不少
我们找到的这里这个call下面有个add esp,0xC,非常典型的一个堆栈外平衡,由此我们可以推断它采用的调用协定是cdecl,再观察其压入的参数的数据宽度为4字节(压入的是三个32位通用寄存器),所以我们可以计算出参数的个数为C/4=3个参数
所以我们可以认为这里便是main函数的入口