1. 程式人生 > >學習C語言的大家可以可以看看這篇文章可能會對你有幫助哦

學習C語言的大家可以可以看看這篇文章可能會對你有幫助哦

部分 lag follow etc 被調用 ron 指定 ans ofo

為什麽要用”真正”這個詞?因為我們從學C語言開始,都會先明白這個道理,即C語言有且僅有一個main函數,main函數是C語言的入口點和出口點!(可以參考<<一個C語言程序的基本機構>>)不光C語言如此,C++也如此,甚至無論黑窗口的控制臺程序和Windows應用程序,都是從main函數或者WinMain函數開始執行,這當然沒錯,但事實上main函數僅僅是一個C語言語法規定的入口點,而不是真正的程序入口,因為它也有函數返回值!它也需要被調用!所以,今天我們將帶大家去揭秘main函數之前的代碼,去看看真正的啟動函數是什麽!來讓大家深入理解C語言程序,方便大家日後的逆向學習!

由於大多數情況下,我們在VC環境下,常常C/C++混編,或控制臺程序和windows應用程序都有接觸,同時會因為編碼方式的區分如ANSI或者Unicode編碼啟動函數還各不相同,為保持簡單、純粹。我們今天僅僅討論ANSI編碼控制臺程序下純C語言的程序入口分析。

事實上,在VC6編譯器下,ANSI編碼環境下C語言的真正啟動函數名叫做mainCRTStarup,英語好的同學應該可以明白一些,Starup就是初始化、啟動的意思,其實也可以根據這點明白這個函數作用就是在C語言啟動之前做一些必要的工作,如堆棧初始化、獲得主函數的參數等等。

還是本著我們“實踐教學”的原則,我們還是以實踐、做實驗來驗證和理解我們的知識,由於關系到函數間調用的關系,我們應該聯想到VC6編譯器帶給我們的棧回溯功能。有興趣的同學可以參考 VC6斷點調試之窗口監視(內存監視、寄存器和棧回溯)<第五篇>

依次View – Debug Windows-> Call Stack

通過編譯器提供的棧回溯功能可以看到程序啟動後的調用過程,如下如:

C語言程序真正的啟動函數

通過斷點提示,我們看到目前程序位於main函數第四行。可以看到上一次是被mainCRTStartup函數調用,在第206行的25個字節偏移處開始調用,再之前就是KERNEL32了,它是windows系統三大主要文件之一。軟件系統層面的調用就到此為止了。

因為大多數逆向分析工具基本都會從這裏開始,所以我們也重點研究mainCRTStartup函數的原理。幸運的是,VC6編譯器為我們提供了mainCRTStartup函數的源碼,但需要大家安裝完整版才可以看到,不然只能看到反匯編代碼。

這裏我們摘錄一部分主要的mainCRTStartup代碼,供大家參考學習:

//預編譯宏
#else /WINMAIN/
#ifdef WPRFLAG
//寬字符版控制臺啟動函數
void wmainCRTStartup(
#else/WPRFLAG/
//多字節版控制臺啟動函數
void mainCRTStartup(
#endif /WPRFLAG/
#endif /WINMAIN/
void )
{
//獲取系統版本信息
_osver=GetVersion();
_winminor=(_oserver>>8)&0x00FF;
_winmajor=_oserver & 0xFF; //主版本
_winver=(_winmajor<<8)+_winminor; _osver=(_osver>>16) & 0x00FFFF;
//堆空間初始化過程,在這個函數裏,指定了程序中堆空間的起始地址
//_MT是多線程標記
#ifdef _MT
if(!_heap_init(1))
#else /_MT/
if(!_heap_intit(0))
#endif /_MT/
fast_error_exit(_RT_HEAPINIT);
//初始化多線程環境
#ifdef _MT
if(!_mtinit())
fast_error_exit(_RT_THREAD);
#endif /_MT/
_try{
//寬字符處理代碼略
//多字節版獲取命令行
_acmdln=(char)GetCommandLineA();
//多字節版獲取環境變量信息
_aenvptr=(char
)_crtGetEnvironmentStringsA();
//多字節版獲取命令行信息
_setargv();
//多字節版獲取環境變量信息
_setenvp();
#endif /WPRFLAG/
//初始化全局數據和浮點寄存器
_cinit();
//窗口程序處理代碼略
//寬字符串裏代碼略
//獲取環境變量信息
_initenv=_environ;
//調用main函數,傳遞命令行參數信息
mainret=main(_argc,_argv,_environ);
#endif /WPRFLAG/
#endif/WINMAIN/
//檢查main函數返回值執行析構函數或atexit註冊函數指針,並結束程序
exit(mainret)
}
//退出結束代碼略
}

以上語法依舊是C語言,大家可以自行對照註釋進行理解,熟悉main函數在調用前的一些準備工作,可以總結如下:

1.GetVersion函數:獲取當前運行平臺的版本號。控制臺下則為MS-DOS的版本信息。

2._heap_init函數:用於初始化堆空間。在函數實現中使用HeapCreate申請堆空間

3.GetCommandLineA函數:獲取命令行參數信息的首地址

4._crtGetEnvironmentStringA函數:獲取環境變量信息的首地址

5._setargv函數:此函數根據GetCommandLineA獲取命令行參數信息的首地址並進行參數分析 註意主函數的參數就在這裏獲得!

6._setenvp函數:此函數根據_crtGetEnvironmentStringA函數獲取環境變量信息的首地址進行分析。

7._cinit函數:用於全局變量數據和浮點數寄存器的初始化。

大家可以對比代碼加註釋深入理解main函數啟動前的準備工作,來加深程序啟動的機制理解。

通過觀察,在_cinit()函數之後,我們可以看到有主函數的調用語句mainret = main(_argc,_argv,_environ),現在知道主函數的返回值給誰了吧?

至此,我們最熟悉的main函數就出現了,怎麽樣,大家連起來了嗎?

如果還能理解,我們接下來做一個更改入口函數的實驗,來加深大家的學習。如下:

編譯器工具欄 Project – Setting – Link – Output 如下圖:

C語言程序真正的啟動函數

在入口點出輸入你想自定義的函數名,比如起名MyDotcpp,將替換掉mainCRTStartup函數,重新被KERNEL32調用,main函數作為C語言語法入口點,被MyDotcpp調用,如圖:

C語言程序真正的啟動函數

重新打開棧回溯查看調用情況,可以看到入口函數已經被更改掉了

C語言程序真正的啟動函數

當然,這裏我們定義的MyDotcpp函數僅僅用來測試更改入口函數,正如mainCRTStartup之前描述的代碼一般,入口函數擁有更多的比如初始化堆空間、浮點數等功能,如果我們這裏在多加一些如開辟內存等語句,運行將會報錯,大家可以親自上機嘗試。

如果有需要的話請登入網址C語言網

學習C語言的大家可以可以看看這篇文章可能會對你有幫助哦