題:
結構如何位於反彙編程序中?
Ken Bellows
2013-03-20 00:24:54 UTC
view on stackexchange narkive permalink

幾年前,我參加了40小時的逆向工程基礎課程。在教我們使用IDAPro的過程中,講師相當迅速地演示瞭如何將ASM中的某些變量標記為結構的成員,基本上等同於C /中的老式 struct C ++,並在代碼的其餘部分中將它們視為可見的地方。這對我來說似乎很有用。

但是,他沒有介紹的是如何識別結構。您如何知道一組變量實際上是一個結構而不只是一組相關變量?您如何確定作者在那裡使用了 struct (或類似名稱)?

出於好奇,那是我的(Rolf Rolles)課嗎?
不,那不是。
我相信我已經講授了您所指課程的這一部分(儘管您上這門課時可能沒有)。現在,我們現在在該主題上花費的時間通常比以前更多。抱歉造成混亂!
這是我公司內部的一門私人課程,因此除非您碰巧被同一個人僱用並積極從事該領域的工作,否則這不太可能。我想周圍有很多。
七 答案:
Andrew
2013-03-20 00:54:04 UTC
view on stackexchange narkive permalink

不能。在C中,為C程序的讀者提供了結構,它們在程序映像中的使用是可選的。完全有可能在原始程序中,一個瘋狂的混蛋決定使用大小合適的char *緩衝區來做所有事情,並適當地進行強制轉換和添加,而您永遠不會知道其中的區別。

“結構”標籤完全是為了您作為代碼查看器的利益。很有可能是您應用於程序的結構標籤實際上是兩個始終彼此相鄰存儲的變量。只要這不會導致您對程序執行錯誤的結論,這將無關緊要。

很有意思。當定義一個結構可能導致錯誤的結論時,您可以舉一個例子嗎?
-1
我能想到的一個(類)人為的例子是通過參數類型進行函數識別。有一個調用eax,您知道參數是作為參數傳遞的指針,並且因為您認為該指針指向baz類型的結構,因此您認為'eax'可以包含帶有簽名'void(* )(struct baz *);'。但是如果您的結構標識不正確,則不必是這種情況。
與@Gilles響應相同,如果結構中存在未使用的變量組,則無法獲得完整的結構。否則,自動化工具將根據使用情況和堆棧訪問模式將程序員定義的單個大型結構視為一組較小的結構。
可惜的是,這個答案被太快地接受了,現在正因為如此而被否決。儘管並非總是能夠可靠地進行操作,但肯定可以在*許多*情況下恢復或至少猜測結構佈局,如其他答案所示。
我認為說不可能是沒有建設性的。儘管您不能確定它是否是結構,但是正如您所說的那樣,它可以帶來巨大的“代碼查看器收益”。您必須對得出的結論持保守態度-首先遵循執行路徑! -但它可能是一個非常有用的工具。
伊戈爾(Igor)和羅伯特(Robert)-考慮到我的最終問題是“你怎麼能*確定*作者使用了'結構'?”,這個答案是絕對正確的。我已經知道可以做出有用的猜測。我只是想知道是否有一種方法可以確定地確定作者使用的是“結構”和“結構”的性質。
user1354557
2013-03-22 05:00:44 UTC
view on stackexchange narkive permalink

在代碼中可以找到表示結構用法的非常常見的模式。

取消引用偏移量:

如果您的指針在某個非零偏移量處被取消引用,您可能正在處理結構。尋找類似這樣的模式:

  mov eax,[ebp-8];將局部變量加載到eaxmov ecx [eax + 8]中; **在eax + 8處取消對雙字的引用**  

在此示例中,我們有一個包含指針的變量,但是我們關心內存內容在某個特定偏移量指針之前。這正是使用結構的方式:我們得到一個指向該結構的指針,然後取消對該指針的引用以及一些偏移量以訪問特定成員。在C語言中,其語法為: pMyStruct->member_at_offset_8

旁注:不要混淆在某些變量的偏移量處的引用與在位置處的引用堆棧指針或幀指針的偏移量( esp ebp )。當然,您可以將局部變量和函數參數視為一個大結構,但是在C語言中,它們並未明確定義為此類。

更多細微的指針偏移量:

實際上,您不需要取消引用即可檢測結構成員。例如:

  mov eax,[ebp-8];將局部變量加載到eaxpush 30h中; num = 30hpush aSampleString; src =“樣本字符串”添加eax,0Chpush eax; dst = eax + 0xCcall strncpy  

在此示例中,我們將最多0x30個字符從某個源字符串複製到 eax + 0xC (請參閱 strncpy)。這告訴我們 eax 可能指向一個偏移量為0xC的字符串緩衝區(至少0x30字節)的結構。例如,該結構可能類似於:

  struct _MYSTRUCT {DWORD a; // + 0x0 DWORD b; // + 0x4 DWORD c; // + 0x8 CHAR d [0x30]; // + 0xC ...}  

在這種情況下,示例代碼如下:

  strncpy(&pMyStruct->d,“示例字符串”,sizeof(pMyStruct->d));  
>

旁注::可能(儘管不太可能)我們可以將文件複製到偏移量為+ 0xC的大字符串緩衝區中,但是您可以通過上下文確定這一點。例如,如果offset + 0x8是一個整數,那麼它絕對是一個結構。但是,如果我們將固定長度為0xC的字符串複製到地址 eax ,然後將另一個字符串複製到地址 eax + 0xC ,則可能是一個巨大的字符串。

所有讀取/全部寫入:

比方說,您有一個結構體( not 指向該結構體的指針)作為堆棧的局部變量。大多數時候,IDA不知道堆棧上的結構或一堆單獨的局部變量之間的區別。但是,要處理結構的一個巨大提示是,如果您僅從變量中讀取而不寫入數據,或者(如果不是這樣,則)僅在不讀取變量的情況下寫入變量。這是每個示例:

  lea eax,[ebp + var_58];將局部變量的地址加載到eaxpush eaxcall some_functionmov eax,[ebp + var_54]中;假設我們從未接觸過var_54,而是... test eax,eax; ...但是我們正在檢查它的值!jz在某處...  

在此示例中,我們從 var_54 中讀取數據,而沒有向其寫入任何內容(在此函數內)。 可能表示它是從其他函數調用訪問的結構的成員。在此示例中,暗示 var_58 可能是該結構的開始,因為其地址被推入 some_function 的參數中。您可以通過遵循 some_function 的邏輯並檢查其參數是否已在偏移量+ 0x4處取消引用(並修改)來驗證這一點。當然,這不一定必須在 some_function 中發生-它可以在其子功能之一或子功能之一中發生,等等。

存在用於編寫的類似示例:

  xor eax,eaxmov [ebp + var_28],eax;假設這是var_28被觸摸的唯一時間,[ebp + var_30]按下eaxcall some_other_function ...  

當您看到設置了局部變量然後再也沒有引用它時,您不能僅僅忘記它們,因為它們很可能是傳遞給另一個函數的結構的成員。此示例暗示在將結構的地址傳遞給 some_other_function 之前,將結構(從 var_30 開始)寫入偏移量+ 0x8。

這兩個用C編寫的示例都可能像這樣:

  some_function(&myStruct); if(myStruct.member_at_offset_4)...  

and

  myStruct.member_at_offset_8 = 0; some_other_function(&myStruct);  

側面說明:儘管這些示例中的每個示例都使用局部變量,但相同邏輯適用於全局變量。

需要結構的文檔化函數:

這可能很明顯,IDA幾乎總是在為您處理此問題,但是,知道代碼中何時具有結構的一種簡單方法是,如果調用需要某種結構的文檔化函數。例如, CreateProcessW 需要一個指向 STARTUPINFOW 結構的指針。

我怎麼知道這些模式是否實際上表示結構用途?

我要說的最後一點是在所有這些情況下,從技術上講,是的,程序的作者可以編寫其代碼而無需使用結構。他們還可以通過將每個函數定義為帶有大內聯 __ asm __ declspec(naked)來編寫其代碼。您將永遠無法分辨。但是可以說,這並不重要。如果有邏輯值組連續存儲在內存中並從一個函數傳遞到另一個函數,則將它們註釋為結構仍然有意義。幾乎所有時候,這都是作者編寫代碼的方式。

如果您需要我詳細介紹任何內容,請告訴我。

哇。這令人印象深刻。
**“當然,您可以將局部變量和函數參數視為一個大結構,但是在C中,它們並未明確定義為這樣。” **但是,也值得指出的是,IDA的接口用於指定函數中的堆棧變量與用於定義結構的堆棧變量幾乎相同。在C中將兩者融合起來並沒有多大幫助,但是在IDA中,認識到它們的相似性是一件好事。
Yifan
2013-03-20 01:32:14 UTC
view on stackexchange narkive permalink

查找結構很棘手,但是可以幫助您更好地理解代碼。正如安德魯所說,結構只是C的抽象,在彙編中,它只是內存的一滴而已,沒有可靠的方法來識別結構。但是,對於較簡單的程序,一些啟發式方法可能會有所幫助。例如,“小”大小的數組比大數組更可能是結構。例如,看到從循環讀取的整數將使其看起來像是一個數組,而以恆定的偏移量讀取幾個整數將使其看起來更像一個結構。另一種方法是看到同一組取消引用發生在代碼的不同區域。如果兩個不同的函數將某個指針用作參數,並且都嘗試遵循偏移量0x10、0x18、0x14之類的值,則可能是結構的代碼設置字段。同樣,從單個輸入的指針解引用的對不同大小數據的任何訪問都是一個很好的指示。

Jesper.Reenberg
2013-03-20 01:10:18 UTC
view on stackexchange narkive permalink

了解何時處理結構的最簡單方法是,代碼在調用已知(或文檔狀態)將結構作為參數的函數時。

例如 inet_ntoa 函數的code> in_addr 結構。

鑑於IDA最初並未弄清楚這一點。

很好。
Wesley McGrew
2013-03-20 04:45:15 UTC
view on stackexchange narkive permalink

我嘗試尋找一種情況,其中將指向數據塊的指針傳遞到函數中,然後在該函數中使用它時,與其不同的偏移量將被視為不同的數據類型。這向我表明1)它不是單一數據類型的數組,2)它是從基址中引用而不是作為單獨的參數傳遞的。當您看到它被動態分配(malloc或new)然後被各種類型的數據填充時,它也有幫助。

在IDA中,我總是建議我的學生繼續為他們懷疑是結構的事物創建IDA結構,並在他們看到被引用的元素時填充它們,即使後來他們發現它們也是如此。是錯的。這是一個反复的過程,隨著您對程序的了解以及在使用過程中如何使用這些數據,您將對其進行更詳細的填充,因此標記出要找出的內容很重要。

Robert Mason
2013-03-20 17:29:35 UTC
view on stackexchange narkive permalink

您可以使用多種方法(大多數方法已由先前的答案提示),但出於完整性考慮,我將在此處列出一些方法。

  1. 查找電話到需要結構的庫。除非可執行文件確實執行了它實際上不應該做的事情,並且將其轉換為指針,然後將其提供給庫函數並希望它不會崩潰(不太可能),那麼您可以在這裡很好地了解哪些內存部分是結構。

  2. 查看如何從函數傳遞/返回部分內存。一些編譯器/調用約定/平台與其他類型的數據不同,傳遞和返回小的結構(可以容納幾個寄存器)。例如,如果您看到數據同時在 eax edx 中返回,那麼您可能正在處理一個小的結構。

  3. 查看內存的尋址方式。如果似乎內存的一部分已通過指針修改為超過一個寄存器的大小,則很可能是(同樣,除非它們做了異常的事情)數組或結構。另外,就像Yifan所說的那樣,如果有很多通過常量偏移量進行的訪問,則您可能正在查看一個結構,或者一個以類似於該結構的方式使用的小數組(例如 unsigned char rgb [3] )。而且,如果您要查看的是不同大小的數據塊,那麼它幾乎肯定是一個結構。

  4. ol>

    但是,在任何假設情況下,只要使用數據與您的模型一致,在原始代碼中它是否只是字節數組,並帶有關於行進方向或成熟結構的約定,這無關緊要。使用任何可以幫助您推理代碼的模型。

    要解決有關在不同代碼分支中具有不同內存佈局的結構的聯合問題,我將介紹兩種最常見的聯合用法(以我的經驗):

  • 類型校正:在這種情況下,您甚至可能在編譯後的代碼中看不到它。儘管它在技術上是行為未定義的AFAIK,但大多數編譯器會將其視為 reinterpret_cast<>()

  • 代數類型:通常,此標記帶有一個易於識別。如果您在多個位置看到代碼,這些代碼檢查某個整數,然後根據該值將內存視為不同,那麼您可以猜測這是怎麼回事。如果某些函數不檢查標記,那麼這還會告訴您一些有關函數的信息-假設作者不是很懶,並且想破壞他們的代碼,他們可能認為該函數只會在分支中被調用是不變的。已經知道它是某種類型。

FWIW,eax:edx return *通常*表示64位整數,而不是小結構。
SW_user2953243
2015-01-19 20:19:29 UTC
view on stackexchange narkive permalink

正如其他人所說,我通常會在傳遞引用的情況下尋找函數調用-但我還會尋找返回malloc的緩衝區的實例(這是您知道/確認結構大小的方式)-並設置/初始化了“緩衝區”的各種成員。



該問答將自動從英語翻譯而來。原始內容可在stackexchange上找到,我們感謝它分發的cc by-sa 3.0許可。
Loading...