【正文】
_ B 的指令 )….. .( m a in 的函數(shù)指令 )….. .……(其他指令 )…( f u n _ A 的指令 )…….. . 圖 函數(shù)代碼在代碼區(qū)的分布示意圖 在執(zhí)行取指調用的過程 處理器在調用 fun_A 函數(shù)時,會 main 函數(shù)對應的機器指令的區(qū)域,跳轉到 fun_A 代碼中對應代碼部分,從那段代碼 中取指執(zhí)行;當 fun_A 代碼取指之后,執(zhí)行 call fun_B 指令,并跳轉至 fun_B 代碼,返回時,先跳轉到 fun_A 地址,逐步跳轉到 main 函數(shù)的后續(xù)代碼部分,接著取指 [1]。 CPU 的取指軌跡,如圖 所示: 天津理工大學 2022 屆本科 畢業(yè)論文 17 Ma i n函數(shù)入口……C a l l f u n _ A后續(xù)的指令………返回指令Fu n _ B函數(shù)入口……返回指令Fun _ A函數(shù)入口……C a l l f u n _ B后續(xù)的指令………返回指令返回到上層函數(shù)的代碼空間緊接著執(zhí)行函數(shù)調用的后續(xù)指令跳轉去 f u n _ B 代碼空間取指執(zhí)行處理器跳轉去 f u n _ A 的代碼空間去繼續(xù)執(zhí)行返回 f u n _ A 函數(shù)繼續(xù)執(zhí)行 圖 CPU 的取指軌跡示意圖 在函數(shù)調用中,系統(tǒng)棧的操作過程: 第一步:在 main 函數(shù)調用 fun_A 的時候,一開始在自身的棧中, push 返回地址,然后fun_A 函數(shù)再創(chuàng)建新棧并 push 系統(tǒng)棧。 第二步:在 fun_A 調用 fun_B 的時候,也是將自 身的棧 push 函數(shù)返回地址,然后為 fun_B函數(shù)創(chuàng)建新棧幀,最后 push 系統(tǒng)棧。 第三步: fun_B 返回, fun_B 函數(shù)棧幀被 pop 系統(tǒng)棧,棧頂露出 fun_A 函數(shù)棧幀的返回地址,處理器繼續(xù)按照該返回地址,重新跳到 fun_A 代碼區(qū)進行執(zhí)行 [1]。 第四步:在函數(shù) fun_A 返回時, fun_A 的??臻g被釋放,棧頂露出 main 部分的返回地址,處理器將跳到 main 函數(shù)代碼區(qū)接著執(zhí)行 [1]。詳細解釋,如圖 所示: 天津理工大學 2022 屆本科 畢業(yè)論文 18 M a i n函數(shù)棧幀其它函數(shù)棧幀M a i n函數(shù)棧幀局部變量其它函數(shù)棧幀返回地址其它函數(shù)棧幀M a i n函數(shù)棧幀返回地址f un _ A棧幀返回地址局部變量f un _ A棧幀局部變量f u n _ B棧幀其它函數(shù)棧幀M a i n函數(shù)棧幀返回地址局部變量f un _ A棧幀其它函數(shù)棧幀M a i n函數(shù)棧幀用彈出的返回地址回溯出上一個函數(shù)的代碼空間用彈出的返回地址回溯出上一個函數(shù)的代碼空間棧頂方向( T O P )棧底方向( B A S E )代碼空間 : 程序被裝入 , 由 m a i n函數(shù)代碼空間依次取指執(zhí)行系統(tǒng)??臻g : 系統(tǒng)棧棧頂為當前正在執(zhí)行的 m a i n函數(shù)棧幀代碼空間 : 執(zhí)行到m a i n 代碼區(qū)的 c a l l指令時 , 跳轉到f u n _ A 的代碼區(qū)繼續(xù)執(zhí)行系統(tǒng)??臻g :為配合 f u n _ A 的執(zhí)行 , 在系統(tǒng)棧中為其開辟新的棧幀并壓入代碼空間 : 執(zhí)行到 f u n _ A 代碼區(qū)的 c a l l 指令時 ,跳轉到 f u n _ B 的代碼區(qū)繼續(xù)執(zhí)行系統(tǒng)棧空間 :為配合 f u n _ B的執(zhí)行 , 在系統(tǒng)中為其開辟新的棧幀并壓入代碼空間 :f u n _ B 代碼執(zhí)行完畢 , 彈出自己的棧幀 , 并從中或的返回地址 , 跳回f u n _ A 代碼區(qū)繼續(xù)執(zhí)行系統(tǒng)棧空間 : 彈出 f u n _ B 的棧幀 , 對應于當前正在執(zhí)行的函數(shù) , 當前棧頂棧幀恢復成 f u n _ A函數(shù)棧幀代碼空間 :f u n _ A 代碼執(zhí)行完畢 , 彈出自己的棧幀 , 并從中或的返回地址 ,跳回 m a i n 代碼區(qū)繼續(xù)執(zhí)行系統(tǒng)??臻g : 彈出 f u n _ A 的棧幀 , 對應于當前正在執(zhí)行的函數(shù) , 當前棧頂棧幀恢復成 m a i n 函數(shù)棧幀 圖 系統(tǒng)棧在函數(shù)調用時的變化 天津理工大學 2022 屆本科 畢業(yè)論文 19 寄存器與函數(shù)棧幀 每一個運行 的函數(shù)占據(jù)各自專屬的一段內存中的堆棧空間,棧頂是目前一直執(zhí)行任務的函數(shù)的棧幀的頂部指針,特別的棧幀是系統(tǒng)用來特別標注的函數(shù)特殊位置的標志 [1]。基本的重要的幾個指針,如表 所示: 表 特殊的指針寄存器 ESP ( extended stack pointer) 棧指針寄存器,存儲一個一直指向系統(tǒng)棧,幀棧頂部的指針 [1]。 EBP ( extended base pointer) 基址指針寄存器,存儲一個一直指向系統(tǒng)棧最上面的一個棧幀底部的指針 [1]。 EIP ( Extended Instruction Pointer) 指令寄存器,存儲一個一直指向待執(zhí)行的指令地址的指針 [1]。 寄存器對棧幀的標示作用,如圖 所示:棧頂以上的未使用的內存空間棧幀 1棧幀 2棧幀 3棧幀棧幀 2棧幀 3棧幀棧幀 1 被釋放棧頂以上的未使用的內存空間棧幀 3棧幀棧幀 2 被釋放棧頂以上的未使用的內存空間E B PE B PE S PE B PE B PE S P 圖 棧幀寄存器 ESP 與 EBP 的作用 天津理工大學 2022 屆本科 畢業(yè)論文 20 不同的函數(shù)需要開辟自己單獨的一段空間, EBP 是目前執(zhí)行函數(shù)調用的堆棧的指針,標示其底部,而同一當前函數(shù)的頂部由 ESP 指示,這就是普遍函數(shù)棧在調用過程中,比較特別的標志函數(shù)的堆棧位置的指針 [9]。 在函數(shù)棧幀中,包含著的重要內容,如表 所示: 表 函數(shù)棧幀包含內容 局部變量 裝載局部變量的內存空間 [1]。 棧幀 狀態(tài)標示 保存前棧幀底部和頂部的位置,運算獲得函數(shù)頂部的狀態(tài)位置,以便在當前棧幀空間被釋放之后,能夠正常獲得上一個棧幀的位置 [1]。 函數(shù) 返回地址 保存當前函數(shù)調用前的指令位置,即返回地址,這樣在上層函數(shù)釋放后,函數(shù)返回時,回到被調用前的函數(shù)代碼區(qū)中,接著執(zhí)行后續(xù)的指令 [1]。 函數(shù)調用約定與相關指令 每一個調用約定描述了函數(shù)傳遞參數(shù)方式和棧協(xié)同工作的技術細節(jié) [1]。不同的編譯器、不同的語言、不同的操作系統(tǒng)實現(xiàn)函數(shù)調用的原理基本相同,可實際的跳轉過程還是不 一致的,其中包括函數(shù)返回時恢復棧平衡時的操作,參數(shù)入棧順序是從右向左還是從左向右,跳轉到還是在母函數(shù)中,還是接著子函數(shù)繼續(xù)執(zhí)行,這些不一致導致的結果就是跳轉的實際的位置的不同 [1]。調用方式之間的差異,如表 所示: 表 函數(shù)調用方式的差異 SysCall BASIC PASCAL StdCall C FORTRAN 參數(shù)入棧 順序 右 → 左 左 → 右 左 → 右 右 → 左 右 → 左 左 → 右 恢復平衡操作的位置 子函數(shù) 子函數(shù) 子函數(shù) 子函數(shù) 母函數(shù) 子函數(shù) 舉例看,其中 C++語言,主要是按照以下函數(shù)的調用約定去執(zhí)行,如表 所示: 表 函數(shù)調用約定 調用約定的聲明 恢復棧平衡的位置 參數(shù)入棧順序 _cdecl 母函數(shù) 右 → 左 _fastcall 子函數(shù) 右 → 左 _stdcall 子函數(shù) 右 → 左 天津理工大學 2022 屆本科 畢業(yè)論文 21 ,如表 所示 : 表 函數(shù)調用的具體步驟 參數(shù)入棧 參數(shù)壓入系統(tǒng)棧中。 返回地址 入棧 目前執(zhí)行的函數(shù)的下一條指令 push 進入,返回指令被執(zhí)行時,就會調用返回地址去執(zhí)行后續(xù)地址指令 [9]。 代碼區(qū) 跳轉 CPU 從目 前區(qū)域 jump 到被調用的下一條函數(shù)的啟動執(zhí)行地址處,繼續(xù)執(zhí)行后續(xù)的指令的跳轉過程。 棧幀 調整 保存當前棧幀狀態(tài)值, 當前棧幀,切換到新棧幀。 給新棧幀分配空間。 ,如表 所示 : 表 函數(shù)返回的具體步驟 保存 返回值 函數(shù)的返回值保存在寄存器 EAX 中,以便后續(xù)的指令去進行調用的過程,利用寄存器來存儲返回值是基本的函數(shù)返回保存的方式 [1]。 彈出函數(shù)棧幀, 恢復上一棧幀 首先保持棧的平衡,然后將 ESP 抬高,減小 EBP 的知識位置,將剩余的函數(shù)空間釋 放掉,以便后續(xù)的利用。 把存儲在棧中的前 EBP 值取出并放入 EBP 寄存器進行存儲,并釋放空間,執(zhí)行至上一棧幀的位置。 EIP 寄存器繼續(xù)執(zhí)行跳轉的函數(shù)的返回地址,并成功進行了跳轉指令,棧幀的恢復過程執(zhí)行完畢。 跳轉 在最后階段,能夠按照剛才取出的函數(shù)返回地址成功得跳回到母函數(shù)的地址中繼續(xù)執(zhí)行 [9]。 函數(shù)調用的過程中,首先按照后面的參數(shù)先壓入,前面的參數(shù)后壓入的規(guī)律進行入棧操作,接下來壓入的是函數(shù)的參數(shù)返回地址,能夠控制程序返回時的跳轉位置,接下來是函數(shù)的局部變量,這個是按 照指令的先后順序而執(zhí)行的,將其局部變量入棧之后,繼續(xù)執(zhí)行后續(xù)的指令。函數(shù)的棧幀,就是通過逐層的入棧的順序來執(zhí)行的,而后當執(zhí)行跳轉指令時,就會跳轉到棧幀里存儲的數(shù)值地址,這樣,就能執(zhí)行上層函數(shù)的指令了。 不同的棧幀就是通過這樣的一種壓棧執(zhí)行的方式而將其組織形成系統(tǒng)棧幀的過程。這個具體的過程,如圖 所示: 天津理工大學 2022 屆本科 畢業(yè)論文 22 其他數(shù)據(jù). . .. . .. . .Ma i n 棧幀局部變量 v a r _ B 2局部變量 v a r _ B 1前棧幀的 E B P返回地址fu n _ B 的第一個參數(shù)fu n _ B 的第二個參數(shù)局部變量 v a r _ A前棧幀的 E B P返回地址fu n _ A 第一個參數(shù)的a r g _ A 1fu n _ A 第二個參數(shù)的a r g _ A 2局部變量 v a r _ m a in前棧幀的 E B P返回地址M a in 第一個參數(shù) in t a r g cM a in 第二個參數(shù) c h a r a r g vM a in 第三個參數(shù) c h a r e n v pE SPE B P( Fu n _ A 代碼區(qū) )…C a l l fu n _ BM o v v a r _ A , e a x… ( m a in 代碼區(qū) )…C a l l fu n _ AM o v v a r _ m a in , e a x. . .f u n _ B 棧幀f u n _ A 棧幀 圖 函數(shù)調用的實現(xiàn) 天津理工大學 2022 屆本科 畢業(yè)論文 23 利用 OD 查看 PE 文件裝載進入內存的效果 在編譯器中,在程序被編譯以后,函數(shù)會自動分解調用 invoke 指令。其中,會有操作數(shù)的執(zhí)行,而當內存調用 PE 文件的時刻,就會變成導入函數(shù)真實的偏移地址,繼續(xù)執(zhí)行指令的跳轉。 用 OD 打開 PE 文件顯示的效果,如圖 所示: 圖 OD 打開 PE 文件的效果 其中,內存與地址的映射和 PE 文件各個組成結構的關系,如圖 所示: 圖 內存與地址映射關系 天津理工大學 2022 屆本科 畢業(yè)論文 24 invoke 指令分解 對 invoke 指令分解步驟,如表 所示: 表 invoke 指令分解步驟 壓棧 首先要調用所有參數(shù) push 到棧中,壓棧時按照先推后參數(shù),再推前參數(shù)的規(guī)則,即第一個推入棧的應該是調 用的最后一個參數(shù),而最后一個推入棧的參數(shù)應該是調用的第一個參數(shù) [1]。 段內調用 由 Call 調用段內地址。 無條件轉移 不考慮任何問題,直接跳轉執(zhí)行 Call 以后的地址 [13]。 shellcode 的開發(fā) shellcode 的概念 shellcode:其代碼能夠實現(xiàn)緩沖區(qū)溢出的現(xiàn)象,有的是攻擊目標主機進入主機系統(tǒng)、上傳木馬、修改文件數(shù)據(jù)、運行惡意軟件,或者致使目標主機癱瘓或藍屏等,其中,代碼部分需要用匯編語言來編寫,并轉換成二進制語言,大小和攻擊方式受到緩沖區(qū)的空間的限制,由此,調 試和開發(fā)的難度都相當?shù)母?[1]。 exploit:代碼植入的過程,就是 shellcode 執(zhí)行的過程,一般出現(xiàn)形式是代碼攻擊的形式,用于生成攻擊性的網(wǎng)絡數(shù)據(jù)包,或者是其他形式的攻擊,覆蓋返回地址是重點內容,從而劫持進程的控制權,讓程序按照額定的方式去執(zhí)行,之后跳轉,去執(zhí)行自身編寫的 shellcode[1]。