0%

C 語言入口點解析

近期接觸了一些逆向工程的技術,仔細的了解了 C 語言決定入口點的機制,在此作整理。所謂入口點(Entry Point)指的是程式啟動後第一個被執行指令的位址。對一般的 programmar 而言,程式的入口點就是 main 函式,在 Windows 環境下有可能為WinMain,這都是最基本的常識。然而事實上 main 並不是這個程式第一個被執行的函式。

這時我們需要先對入口點做更嚴謹的定義。首先,一個程式是如何啟動的?方法有很多,常見的有:在檔案總管中雙按執行檔、在命令提示字元中輸入程式名稱、在程式中呼叫 CreateProcess 啟動另一個程式,其實這三種方法最終都是透過呼叫 CreateProcess 來啟動程式,而 CreateProcess 中,系統會先將環境建立好,之後在跳轉到程式的入口點。

入口點要如何得知?入口點其實就紀錄在執行檔中。那執行檔的入口點是如何決定? 這就是連結器(Linker)的工作了。以下我就針對兩種最常見的 linker 進行說明。

第一個例子為 Visual C++ 的 linker,預設會根據 /SUBSYSTEM 這個 flag 來選擇進入點,若指定了 /SUBSYSTEM:CONSOLE,linker會選擇 mainCRTStartup 作為進入點,若指定了 /SUBSYSTEM:WINDOWS,linker會選擇 WinMainCRTStartup 作為進入點。若要使用非預設的入口點,則需指定 /ENTRY 這個 flag。

由此可知 VC 的 linker 選擇進入點的順位如下:

  1. /ENTRY 指定的函式
  2. /SUBSYSTEM 的值
  3. 若沒有指定 /SUBSYSTEM,則看 mainWinMain 使否有定義,若main有定義則使用 mainCRTStartup,若 WinMain 有定義則使用 WinMainCRTStartup

第二個例子為 gcc 的 linker,這邊直接列選擇入口點的順位:

  1. -e 選項所指定的位址
  2. linker script 中 ENTRY() 所指定的位址
  3. start 符號有定義,則使用start所指定的位址
  4. 若有 .text 這個 section,則指定 .text 區段第一個byte的位址為入口點
  5. 使用位址 0 作為入口點

以上兩個例子參考了這兩個說明文件:

那實際上該如何驗證? gcc 為了支援多個平台因此原始碼比較複雜,因此這邊選擇對 Visual C++ 的 CRT 原始碼做研究。

VC 的CRT的原始碼就放在VC的安裝資料夾中,我的電腦上的路徑為:C:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\crt\src

其中我們感興趣的程式碼都位於 vcruntime這個資料夾中,其中有三個檔案為我們的目標:

  • exe_common.inl =>實作初始化 C runtime的程式碼
  • vcstartup_internal.h => mainCRTStartupWinMainCRTStartup 的宣告
  • exe_main.cpp => mainCRTStartup的實作(定義)
  • exe_winmain.cpp => WinMainStartup 的實作(定義)

exe_main.cpp 與 exe_winmain.cpp 都非常簡短,直接列出來:

1
2
3
4
5
6
7
8
//exe_main.cpp
#define _SCRT_STARTUP_MAIN //使exe_common.inl能夠正確的呼叫到 main
#include "exe_common.inl" //真正的實作

extern "C" int mainCRTStartup() //入口點!!!
{
return __scrt_common_main(); //真正的實作
}
1
2
3
4
5
6
7
8
//exe_winmain.cpp
#define _SCRT_STARTUP_WINMAIN //使exe_common.inl能夠正確的呼叫到 WinMain
#include "exe_common.inl" //真正的實作

extern "C" int WinMainCRTStartup() //入口點!!!
{
return __scrt_common_main(); //真正的實作
}

exe_common.inl 中實作了CRT的初始化,其實不管入口點為何,CRT初始化都是使用同一段程式碼,差別只在最後呼叫的是 main 還是 WinMain 而已。CRT的初始化又是另一個題目了,往後有機會再研究。

研究CRT原始碼讓人相當過癮,以往使用 OllyDbg 時,往往對main之前的那一大段程式碼不知其所以然,現在配合原始碼一對照,可以清清楚楚地知道程式每一步在做的是什麼,往後也許會寫一篇關於CRT啟動程式碼的文章。