【 聲明:著作權,歡迎轉載,請勿用於商業用途。 聯絡信箱:feixiaoxing @163.com】
這裡說的函數主要指的是inline函數、static函數。inline函數比較特殊,它既具有宏的性質,同時也能讓編譯器對它進行函數檢查。static函數同樣也比較特殊,它只可以被同檔案的函數使用。如果static函數在include檔案中,那麼這個標頭檔只要被使用一次,那麼這個函數就要在exec檔案中重新出現一次。現在大家可能理解起來有點困難,但是請大家稍微等待一下,下面我們將會用樣本進行說明。最後,我們用一個替換的技巧對函數指標進行修改,讓你調用的函數發生修改,這樣給大家都函數的定義加深一下印象。
(1)內嵌函式
inline int add(int a, int b){return a + b;}
那麼這個函數在應用的時候,會怎麼編譯呢,可以看一下?
0040114A mov eax,10040114F add eax,200401152 mov dword ptr [ebp-4],eax
inline函數是一種特殊的函數。在進行函數編譯的時候,編譯器會對內嵌函式這段代碼按照函數的要求進行格式檢查。但是編譯產生執行代碼的過程中,編譯器會把這段代碼按照宏的性質複製到call的函數當中。所以在call函數中,我們發現這段調用代碼並不是call的形式,而是直接按照語句的形式。但是這種inline函數中的程式碼數不能過多,因為我們內聯的目的就是就是減少call的機會。
注意:
a) 函數在編譯的時候需要開啟INLINE最佳化開關,【PROJECT】->【setting】->【C/C++】->【optimizations】,在內聯擴充中選擇第二項
b)編譯的時候會建置錯誤,那麼刪除編譯指令/ZI即可,結果是源碼無法單步調試,只能彙編級單步調試
(2) static函數是什麼屬性
static int add(int a, int b){return a + b;}
a) 如果在不同的源檔案都有這樣一個add函數呢 ?
如果在不同的檔案裡面函式宣告為static函數,那麼沒有關係,各個static函數只為各個檔案使用,不存在multi definition的問題。
b)如果標頭檔有這樣一個static函式宣告和定義?
標頭檔中有一個static函數的話,那麼調用這個函數的每個檔案都為這個static函數重新編譯一下。結果和a)的結果是一樣的,大家可以自己試試看一下,對static函數地址列印一下,看看是不是add函數的地址是一樣的。
(3)一個修改函數地址的範例
#include <windows.h>int add(int a, int b){return a + b;}int sub(int a, int b){return a - b;}void set(){HANDLE hProcess = GetCurrentProcess();DWORD pOldFlag = 0;BOOL result = 0;result = VirtualProtectEx(hProcess, (LPVOID)add, 0x10, PAGE_EXECUTE_READWRITE, &pOldFlag);if(result != 0){printf("%d\n", GetLastError());}}void process(){char* n = (char*) add;char* t = (char*) sub;*n = 0xFF;*(n+1) = 0x25;*(int*)(n +2) = (int)&t;int data = add(3,2);assert(1 == data);return;}
簡單介紹一下,上面的程式碼封裝括四個函數,add函數和sub函數主要為了替換測試使用,set函數是修改程式碼片段訪問屬性的一段代碼,而process函數就是我們測試使用的一段代碼。其實這段代碼的意思不難,目的在於你在call add函數,發現實際上在call的是sub函數。那麼我們是怎麼做到的呢,關鍵在兩個方面:(1)修改add 函數程式碼片段的訪問屬性;(2)修改add函數第一個位元組的內容,那麼我們需要把函數add處地內容修改為jmp sub,那麼就要先修改屬性,後修改內容。
【預告: 下面一片部落客要涉及class記憶體分布問題】