在用Hopper反轉32位Mach-O二進製文件時,我注意到了這種奇特的方法。 0x0000e506上的指令似乎正在調用指令下方的地址。
這是什麼原因?這是套票清潔的騙術嗎?
在用Hopper反轉32位Mach-O二進製文件時,我注意到了這種奇特的方法。 0x0000e506上的指令似乎正在調用指令下方的地址。
這是什麼原因?這是套票清潔的騙術嗎?
這是與位置無關的代碼。 call 0xe50b
指令壓入下一條指令的地址,然後跳轉。跳轉到緊隨其後的指令,該指令無效。下一條指令 pop eax
將其自己的地址加載到 eax
中(因為它是 call
推送的值)。
進一步,它使用距eax的偏移量:
mov eax,dword [ds:eax-0xe50b + objc_msg_close]
要減去的值, 0xe50b
,是我們移入 eax
的地址。如果代碼沒有移到任何地方,則 eax-0xe50b
將為零,但是如果代碼已移到其他位置,則為偏移量。然後,我們添加地址 objc_msg_close
,因此即使代碼已在內存中移動,我們也可以引用它。
Hopper實際上非常聰明,因為該指令只是說(來自ndisasm):
移動eax,[eax + 0x45fe75]
,但是Hopper知道 eax
包含 0xe50b
處的指令指針的值,因此使用該偏移量為您找到符號。
這是一個常用的“技巧”,用於確定調用
之後的指令的地址,即,調用指令將返回地址壓入堆棧,在這種情況下,該地址對應於 0xe50b
。在pop指令之後,eax包含該地址。例如,此慣用法用於位置無關代碼(pic),但在混淆代碼中也很常見。
其他反彙編程序通常將此代碼序列顯示為致電$ + 5
(例如IDA)。
現在我可能不知道確切的原因是什麼,但是還有另一個很好的原因(到目前為止尚未提及)使用這種方法:在靜態分析過程中拋出反彙編程序。
已經討論了 call $ + 5
的機制,所以我假設它們現在是已知的,否則請參考其他答案。基本上就像IA-32上的任何 call
一樣,返回地址( call
之後的指令的地址)被 push
壓入堆棧並且假設堆棧沒有同時被砸,那麼調用的函數中的 ret
指令可能會返回該地址。
甚至IDA之類的複雜反彙編程序在看到 ret
操作碼時也會做什麼?好吧,我們假設已經達到了功能邊界。這是一個示例:
現在,這不是我第一次看到這樣的東西,我繼續刪除了該函數,因此IDA不再假設它是功能邊界。如果我然後告訴它反彙編下一個字節( 0Fh
),我會得到:
反彙編器無法實現的功能以及什麼是像Hopper和IDA這樣的交互式反彙編程序如此之多的原因在於,這裡發生了一些特別的事情。讓我們看一下指令:
51 push rcx53 push rbx52 push rdxE8 00 00 00 00 call $ + 55A pop rdx48 83 C2 08 add rdx,852 push rdxC3 retn0F 5A 5B 59 cvtps2pd xmm3,qword ptr [rbx + 59h] 89 DF mov edi,ebx52 push rdx
48 31 D2 xor rdx,rdx
前導字節是二進制中的實際字節,後跟助記符。但是要特別注意這一部分:
call $ + 5pop rdx; <- = ADDRadd rdx,8push rdxretn
我們在 pop
之後在 rdx
中獲得地址 ADDR
指令已執行。我們從其他答案中對機制的描述中了解到了很多。但這又變得奇怪了: / code>),然後我們將其 push
放入堆棧並調用 ret
:
push rdxretn
如果您還記得 call
的工作方式,那麼您會記得它會將返回地址壓入堆棧,然後將執行傳遞給被調用的函數,然後該函數隨後調用 ret 代碼>,以便返回到堆棧上找到的地址。這種知識在這裡被利用。它在“返回”之前操縱“返回地址”。但是回頭看一下我們的反彙編,我們會感到驚訝(或不是;)):
E8 00 00 00 00電話$ + 55A pop rdx48 83 C2 08 add rdx,852 push rdxC3 retn0F 5A 5B 59 cvtps2pd xmm3,qword ptr [rbx + 59h]
讓我們計算操作碼字節數(如果您願意,也可以通過偏移量進行數學運算):
5A
48
83
C2
08
52
C3
0F
但是等一下,這意味著我們實際上是將執行傳遞給這個特殊的 cvtps2pd xmm3,qword ptr [rbx + 59h]
的中間嗎?那就對了。因為 0Fh
是在IA-32上對指令進行編碼時使用的前綴之一。因此,程序員欺騙了我們的反彙編程序,但他不會欺騙我們。取消定義該代碼,然後跳過 0Fh
前綴,我們得到:
51 push rcx53 push rbx52 push rdxE8 00 00 00 00 call $ + 55A pop rdx48 83 C2 08 add rdx,852 push rdxC3 retn0F db 0Fh5A pop rdx5B pop rbx59 pop rcx89 DF mov edi,ebx52 push rdx48 31 D2 xor rdx,rdx
or:
現在發現明顯的單四字節指令 0F 5A 5B 59
是偽造的,而是我們必須忽略 0F
,然後從 5A
恢復,該解碼為 pop rdx
。
簽出 Ange的出色操作碼表,以了解有關如何在IA-32上對指令進行編碼的更多信息。
CALL
指令具有在將控制轉移到調用目標之前將返回地址壓入堆棧的作用。
在上面的示例中,在將控制權轉移到0x0000E50B之前, CALL
指令會將值0x0000E50B壓入堆棧。然後,位於0x0000E50B的 POP
指令會將最後一個值從堆棧頂部彈出到EAX中。由於 CALL
指令推送返回值,因此該值將是 POP
指令自身的地址。
這是獲取指令的一種簡單方法在運行時位於內存中的位置。
由於地址空間佈局隨機化(ASLR),二進製文件可能會在內存中重新定位,因此鏈接器在編譯時無法始終計算指令位置。
正如其他人所說,這是為了獲取當前指令的地址。但是不建議這樣做,因為它會損害性能,因為它不會在任何地方返回,從而導致數據堆棧和CPU內部調用堆棧中的返回地址不一致
推薦的方法是
GetCurrentAddress:mov eax,[esp] ret ...調用GetCurrentAddress mov [currentInstruction],eax
http://blogs.msdn.com/b/ oldnewthing / archive / 2004/12/16 / 317157.aspx
原因是處理器內部的“隱藏變量”。所有現代處理器所包含的狀態遠遠超出了從指令序列中可以看到的狀態。有TLB,L1和L2緩存,各種您看不到的東西。
最新的奔騰(我相信也是Athlon)處理器維護著一個內部堆棧,該堆棧由每條CALL和RET指令更新。
強>。執行CALL時,返回地址既被推入實際堆棧(ESP寄存器指向的那個),又被推入內部返回地址預測變量 ;一條RET指令會彈出返回地址預測變量堆棧以及實際堆棧的頂部地址。
當處理器解碼RET指令時,將使用返回地址預測變量堆棧。它位於返回地址預測變量堆棧的頂部,並說:“我敢打賭RET指令將返回該地址。” 然後,它推測性地執行了該地址處的指令。由於程序很少在堆棧上擺弄返回地址,因此這些預測趨向於高度準確。