- C和C++安全編碼(原書第2版)
- (美)Robert C.Seacord
- 997字
- 2020-10-30 17:56:49
3.4 修改指令指針
攻擊者要想在x86-32架構上成功地執行任意代碼,必須利用某種方式修改執令指針,使其指向外殼代碼。指令指針寄存器(eip)存儲了將要執行的下一條指令在當前代碼段內的偏移量。
eip寄存器不能被軟件直接訪問。它在順序執行代碼時由一個指令邊界步進到下一條指令,也可以由控制轉移指令(例如jmp、jcc、call和ret等)、中斷以及異常間接修改[Intel 2004]。
以call指令為例,它首先將返回信息存儲于棧中,然后將控制權轉移到由目標操作數指定的被調用函數處。目標操作數指定了被調用函數中的第一條指令的地址。該操作數可以是一個立即數(immediate value)、一個通用寄存器或一個內存位置。
例3.4展示了一個程序,其中使用函數指針funcPtr調用一個函數。第6行聲明了一個函數指針,該指針所指函數為靜態函數,后者接受一個常量字符串參數。在第7行中,該函數指針被賦值為good_function()的地址,因此當第8行調用funcPtr時,實際上調用的是good_function()。作為對比,第9行用靜態方式調用了一次good_function()。
例3.4 使用函數指針的示例程序
01 void good_function(const char *str) { 02 printf("%s", str); 03 } 04 05 int main(void) { 06 static void (*funcPtr)(const char *str); 07 funcPtr = &good_function; 08 (void)(*funcPtr)("hi "); 09 good_function("there!\n"); 10 return 0; 11 }
例3.5展示了例3.4中兩次調用good_function()的反匯編代碼。第一個調用(使用函數指針的形式)發生于0x0042417F處,該地址的機器碼為ff 15 00 84 47 00。在x86-32架構中call指令的形式有多種,本例中,ff操作碼(參見圖3.1)與一個ModR/M(15)結合使用,以指示一個絕對地址的間接調用。
圖3.1 x86-32的call(調用)指令
最后4個字節包含了被調用函數的地址(這里存在一級間接性)。可以在例3.5中的dword ptr[funcPtr(478400h)]調用中看到這個地址。這個地址中所保存的good_function()的實際地址為0x00422479。
例3.5 函數指針反匯編
(void)(*funcPtr)("hi "); 00424178 mov esi, esp 0042417A push offset string "hi" (46802Ch) 0042417F call dword ptr [funcPtr (478400h)] 00424185 add esp, 4 00424188 cmp esi, esp good_function("there!\n"); 0042418F push offset string "there!\n" (468020h) 00424194 call good_function (422479h) 00424199 add esp, 4
對good_function()的第二個調用是個靜態調用,它發生于0x00424194。該位置的機器碼是e8 e0 e2 ff ff。在本例中,調用指令用e8操作碼表示。這種形式的調用指令表示一個近調用相對下一條指令的偏移量。偏移量是一個負數,表示good_funciton()出現在更低的地址處。
這些對good_function()的調用提供了一些可被攻擊和不可被攻擊的調用指令例子。靜態調用使用一個立即數作為相對偏移量,由于該偏移量處于code段中,因此無法被覆寫。而通過函數指針的調用使用一個間接引用,被引用的地址(通常在data或stack段中)則可以被覆寫。這些間接的函數引用與無法在編譯期間決定的函數調用可以被利用,從而使程序的控制權轉移到任意代碼。對于目標是將程序控制權轉移到攻擊者提供的代碼中的任意內存寫技術,參見下文描述。