2011-07-09 44 views
1

我正在爲somthing(我無法訪問源代碼)插入並遇到問題。以下程序集摘錄來自主程序,並且是負責調用我的代碼的過程的開始(整個代碼非常長,因爲它非常通用,可以調用任何函數,最多16個參數相關的字符串內存管理,並支持__stdcall或__cdecl)。不幸的是,一個非常重要的值不是通過這個函數傳遞給我的代碼,這個「this」指針保存在edx寄存器中。將代碼注入到現有的已編譯模塊中

所以我需要在這個函數的開始部分注入某些代碼(提供的API允許我獲取它的地址,在這種情況下爲0x00613ED4),它會將值存儲在edx寄存器中以供我檢索後來。

我之前完成了一些x86程序集,但是我從來不需要在運行時將新代碼注入到現有模塊中,並且我無法找到關於如何去做這些事情的任何信息:(如果這樣的事情實際上可以從C/C++內部沒有我不得不寫在裝配的所有的東西,甚至會更好。

00613ED4 push  ebp 
00613ED5 mov  ebp,esp 
00613ED7 add  esp, 0FFFFFE64h 
00613EDD push  ebx 
00613EDE push  esi 
00613EDF push  edi 
00613EE0 mov  dword ptr [ebp-19Ch],ecx 
00613EE6 mov  ecx,11h 
00613EEB lea  eax, [ebp-198h] 
... 

回答

2

你真的無法插入代碼之間有兩個彙編指令,你必須將所有碼移位達騰出一些空間,這可能會打破所有的硬編碼偏移量(如跳轉目標或數據段中的偏移量)

可能做的是在相關區域設置一個代碼斷點,並將iret放入新代碼中,然後將新代碼ret代入原代碼中。

如果性能是需求,那麼請注意這會損害性能。


另一個類似的選擇是使用INT3調試斷點。它實際上就是爲了這個目的。使用它的方式,調試器用INT3操作碼替換它想要中斷的操作碼,該操作碼(方便地)是單字節操作碼,確保它可以插入到任何地方。這會導致跳轉到異常處理程序,就像您在評論中所建議的那樣。通過檢查被推入堆棧的EIP,您可以知道您來自哪裏,並據此採取行動。

這將與其他選項具有相同的性能損失。


兩點考慮:

  • 我知道沒有辦法在C/C++要做到這一點,但我還是那句話不是專家。
  • 擺弄異常處理程序很可能需要在CPL0上工作。你應該確保你的代碼可以做到這一點。
+0

當時就在想,如果我可以把財產以後到JMP莫名其妙地話,我可以把我的代碼在別的地方,不管我重寫了什麼,然後跳回到下一條指令?不應該太昂貴,特別是它坐在旁邊的腳本解釋器比較:p在如何實際編寫代碼,雖然做替換有一些麻煩...... – Will

2

如果你在Linux上工作,你可以看看kernel probes (KProbes),一個方便的內核設施。它們被設計爲允許在內核中的任意位置執行幾乎任意的代碼。它就像INT 3 /斷點的高級版本。

您可以確定您感興趣的函數的地址,將KProbe放在它們的第一個字節處,並且在這些函數運行時您提供的處理程序將執行。處理程序用C語言編寫。他們也會收到有關寄存器內容的信息,並可能允許更改它(但不確定後者)。

根據具體情況,KProbes在內部使用INT 3或跳轉和迂迴緩衝區。

+0

@會,但在我看來,然而,你的系統是不是Linux。地址0x00613ED4看起來不像Linux內核空間中的地址... – Eugene

+0

我的不好,我沒有意識到你正在談論用戶空間代碼。我提到的系統僅用於內核空間,所以這只是另一個故事。 – Eugene

0

當時就在想,如果我可以把財產以後到JMP莫名其妙地話,我可以把我的代碼其他地方,做任何我重寫了,並跳轉回後的下一個指令?

...這是在運行時修補代碼的好方法,是的。但可能會有很多細節需要考慮,具體取決於被覆蓋的指令是做什麼的。代碼注入可能比您預期的要難,例如,代碼可能只是以只讀方式映射。

這整個代碼是非常非常長,因爲它是非常generall能夠調用任何功能與高達16個參數加上字符串相關的內存管理,以及支持無論是__stdcall或__cdecl)

所以:這段代碼回調你的代碼,並且可以傳遞參數?你能否註冊至少一個指針大小的參數以及將被回調的函數?

如果是這樣,您可以自己傳遞this指針,並帶上一些小竅門。您需要註冊一個函數,該函數確實需要而不是指針(如靜態成員函數)作爲回調函數,以及this指針作爲回調的參數;該回調可以打開參數以調用您真正想要調用的成員函數。

像這樣(與道歉,如果很明顯,我花了我一生中最編寫C而不是C++):

#include <iostream> 

// Simple C callback interface 

extern "C" { 

    typedef void (*tCallbackFn)(void *); 

    static struct { 
    tCallbackFn fn; 
    void *context; 
    } callback; 

    static void set_callback(tCallbackFn fn, void *context) 
    { 
    callback.fn = fn; 
    callback.context = context; 
    } 

    static void call_callback(void) 
    { 
    (callback.fn)(callback.context); 
    } 

} 

// Simple test class 

class MyClass 
{ 
public: 
    // We'll use this for test purposes 
    int id; 

    // This is the actual member function we want to invoke 
    void DoIt(void) 
    { 
    std::cout << "Callback - id = " << id << std::endl; 
    } 

    // Static wrapper which we can pass as the function in the callback interface 
    static void CallbackWrapper(void *context) 
    { 
    static_cast<MyClass *>(context)->DoIt(); 
    } 

    // Register wrapper as callback function, with "this" pointer as context 
    void Test(void) 
    { 
    set_callback(CallbackWrapper, static_cast<void *>(this)); 
    call_callback(); 
    } 
}; 

// Test it 

int main(void) 
{ 
    MyClass m1, m2; 

    m1.id = 1; 
    m2.id = 2; 
    m1.Test(); 
    m2.Test(); 

    return 0; 
} 
+0

不幸的是,在腳本環境中,他們選擇使用id號碼而不是真正的指針值,然後作爲從腳本到那裏的API的調用過程的一部分(比如我有代碼清單的那個),他們查找指針值。這似乎是一個表格,但他們沒有提供API來獲取此表格的地址,並且每隔幾天就會發布新的版本,我懷疑它可能會發生變化(正如我所說的,程序地址由一個記錄的API,就像調用外部本地代碼的過程一樣) – Will

+0

我設法在代碼注入方面取得了一些進展,使用VirtualProject的win32 API來獲得寫權限。 Allthough我碰到一個interting問題,00613ED4是一種只讀字節。我實際上基本上試過「mov dword ptr [00613ED4],11223344h」,它寫下了3個字節,但是隻剩下00613ED4 :(我假設其他平臺有類似的系統? – Will

+0

我不知道問題是什麼(我不是Windows專家),但讀/寫/執行權限是頁面屬性(對於x86),因此頁面中間的「只讀字節」似乎不太可能。我不知道'VirtualProtect()'是否能夠正確地處理代碼映射。 (Linux有'mprotect()',這是一個POSIX指定的函數,做了類似的事情; Linux確實允許它用於更改代碼映射,但這是Linux提供的功能超出了POSIX規範的要求,保證它對'mmap()'返回的區域有效。) –