第一次寫外掛就上手 - 使用Visual C++ 2010(三)
這次我要來教大家如果遇到數據中有API的話該怎麼辦。
這次選用的範例是TwMS v181.3 ICS 終極攻擊無延遲 (50W數據)
。
ICS 終極攻擊無延遲 (50W數據)// TwMS v181.3 ICS 終極攻擊無延遲 (aka. NoDelayFinalAttack, NDFA) [Enable] Alloc(SkillID,4) Alloc(FinalAttack,64) Label(Return) Label(DoFinalAttack)
SkillID: DD #95001001
FinalAttack: Cmp [Esp+20], 00F5D858 Jne Return Mov [Esp+20], DoFinalAttack Return: Jmp kernel32.InterlockedDecrement
DoFinalAttack: Cmp [SkillID], 0 Je 00F5D85E Mov Ebx, [SkillID] Mov [Esi+B468], Ebx Xor Ebx, Ebx Cmp [Esi+B468], Ebx Je 00F5D85E Push [Esi+524] Call 0050D906 Lea Esp, [Esp+4] Mov [Esi+B46C], Eax Jmp 00F5D864
0132F084: DD FinalAttack [Disable] DeAlloc(SkillID) DeAlloc(FinalAttack)
0132F084: DD kernel32.InterlockedDecrement
|
可以發現這次的數據變得複雜了許多!
其中我們說的API
就是指Windows API
,也就是本次範例中的kernel32.InterlockedDecrement
這部分。
複習一下之前的教學,試試看目前為止我們能轉換多少呢?
FormMain.cppDWORD NoDelayFinalAttackAddress = 0x0132F084; DWORD NoDelayFinalAttack_Disable = kernel32.InterlockedDecrement; DWORD DoFinalAttack_Je = 0x00F5D85E; DWORD DoFinalAttack_Call = 0x0050D906; DWORD DoFinalAttack_Jmp = 0x00F5D864; void __declspec(naked) __stdcall DoFinalAttack() { __asm { Cmp dword ptr[SkillID], 0 Je DoFinalAttack_Je Mov Ebx, dword ptr[SkillID] Mov [Esi+0xB468], Ebx Xor Ebx, Ebx Cmp [Esi+0xB468], Ebx Je DoFinalAttack_Je Push [Esi+0x524] Call DoFinalAttack_Call Lea Esp, [Esp+0x4] Mov [Esi+0xB46C], Eax Jmp DoFinalAttack_Jmp } } DWORD DoFinalAttack_Address = (DWORD)DoFinalAttack;
void __declspec(naked) __stdcall NoDelayFinalAttack() { __asm { Cmp dword ptr[Esp+0x20], 0x00F5D858 Jne Return Push [DoFinalAttack_Address] Pop [Esp+0x20] Return: Jmp kernel32.InterlockedDecrement } }
|
大家是不是都能做到這樣了呢?
可是當嘗試要編譯時,跑出了好多錯誤,看來還有一些地方沒學過呢!
首先數據一開始Alloc(SkillID,4)
申請了一塊大小為4 Bytes的記憶體空間命名為SkillID
,所以我們也要在程式中為SkillID
配置一樣的空間大小,而在C++中還必須指定這塊記憶體空間是儲存什麼類型的資料,稱為型態(type)
,而配置這塊固定的記憶體空間的專有名詞就是宣告(declare)
。
至於我們要選用什麼型態呢?在此剛好DWORD
的大小就是4 Bytes所以我們就直接用DWORD
吧!
另外CE數據中#
代表的是十進位數字,所以直接轉成C++時只需要將#
拿掉保留後面的內容即可:
DWORD SkillID = 95001001;
|
再來會發現Je DoFinalAttack_Je
竟然也是錯誤的!
原因在於組合語言中並沒有提供除了jmp
以外的跳轉指令能直接指定一個指向目標位址的指標(pointer)。
所以我們必須自己增加標籤(Label)來讓編譯器能夠處理程式邏輯。
我們在Jmp DoFinalAttack_Jmp
下一行加入一行DoFinalAttack_Label:
,並將原本的Je DoFinalAttack_Je
都改成Je DoFinalAttack_Label
,然後在DoFinalAttack_Label:
的下一行加入Jmp DoFinalAttack_Je
即可。
void __declspec(naked) __stdcall DoFinalAttack() { __asm { Cmp dword ptr[SkillID], 0 Je DoFinalAttack_Label Mov Ebx, dword ptr[SkillID] Mov [Esi+0xB468], Ebx Xor Ebx, Ebx Cmp [Esi+0xB468], Ebx Je DoFinalAttack_Label Push [Esi+0x524] Call DoFinalAttack_Call Lea Esp, [Esp+0x4] Mov [Esi+0xB46C], Eax Jmp DoFinalAttack_Jmp DoFinalAttack_Label: Jmp DoFinalAttack_Je } }
|
接著就是今天的重頭戲了:Windows API。
數據中直接使用的Windows API的位址,但是由於這些地址在每次開機後都是不同的,更不用說是不同電腦,所以我們不可能直接使用固定的位址。
要取得某一個API的位址須要用到兩個Windows API:GetModuleHandle
和GetProcAddress
。
在本次示範的數據中kernel32.InterlockedDecrement
表示kernel32
這個DLL中的InterlockedDecrement
函數。
我們想要取得某個DLL中的某個函數就要透過GetProcAddress
。
GetProcAddress
在MSDN上這樣說明:
Retrieves the address of an exported function or variable from the specified dynamic-link library (DLL).
FARPROC WINAPI GetProcAddress( _In_ HMODULE hModule, _In_ LPCSTR lpProcName );
|
可以發現這個函數的原型宣告(Function Prototype)帶有兩個參數,也就是說我們在調用時也必須要填入兩個參數。
而第一個參數HMODULE hModule
要填的是DLL的HANDLE,MSDN寫著可以從調用LoadLibrary
、LoadLibraryEx
、LoadPackagedLibrary
或GetModuleHandle
的返回值取得。而我們要用的就是GetModuleHandle
這個API。
第二個參數LPCSTR lpProcName
就比較簡單了,他就是要填入我們要找的函數的名稱。
GetModuleHandle
在MSDN上的說明:
Retrieves a module handle for the specified module. The module must have been loaded by the calling process.
HMODULE WINAPI GetModuleHandle( _In_opt_ LPCTSTR lpModuleName );
|
這個API只需要一個參數,需要填的是DLL的檔案名稱,調用之後返回DLL的HANDLE。
因此如果我們要找kernel32
這個DLL中的InterlockedDecrement
函數的位址,就可以這樣寫:
GetProcAddress(GetModuleHandleA("kernel32"), "InterlockedDecrement");
|
但由於GetProcAddress
返回值的型態是FARPROC
不方便我們直接在數據中使用,所以一樣將它轉型為DWORD
:
DWORD InterlockedDecrement_Address = (DWORD)GetProcAddress(GetModuleHandleA("kernel32"), "InterlockedDecrement");
|
接著就直接把數據中的kernel32.InterlockedDecrement
替換成InterlockedDecrement_Address
就好囉!
完整程式碼:
FormMain.cpp DWORD InterlockedDecrement_Address = (DWORD)GetProcAddress(GetModuleHandleA("kernel32"), "InterlockedDecrement"); DWORD NoDelayFinalAttackAddress = 0x0132F084; DWORD NoDelayFinalAttack_Disable = InterlockedDecrement_Address; DWORD DoFinalAttack_Je = 0x00F5D85E; DWORD DoFinalAttack_Call = 0x0050D906; DWORD DoFinalAttack_Jmp = 0x00F5D864; DWORD SkillID = 95001001; void __declspec(naked) __stdcall DoFinalAttack() { __asm { Cmp dword ptr[SkillID], 0 Je DoFinalAttack_Label Mov Ebx, dword ptr[SkillID] Mov [Esi + 0xB468], Ebx Xor Ebx, Ebx Cmp [Esi + 0xB468], Ebx Je DoFinalAttack_Label Push [Esi + 0x524] Call DoFinalAttack_Call Lea Esp, [Esp + 0x4] Mov [Esi + 0xB46C], Eax Jmp DoFinalAttack_Jmp DoFinalAttack_Label : Jmp DoFinalAttack_Je } } DWORD DoFinalAttack_Address = (DWORD)DoFinalAttack;
void __declspec(naked) __stdcall NoDelayFinalAttack() { __asm { Cmp dword ptr[Esp + 0x20], 0x00F5D858 Jne Return Push [DoFinalAttack_Address] Pop [Esp + 0x20] Return: Jmp InterlockedDecrement_Address } } DWORD NoDelayFinalAttack_Enable = (DWORD)NoDelayFinalAttack;
System::Void FormMain::checkBox1_CheckedChanged(System::Object^ sender, System::EventArgs^ e) { if (this->checkBox1->Checked) { memcpy((void *)NoDelayFinalAttackAddress, &NoDelayFinalAttack_Enable, sizeof(NoDelayFinalAttack_Enable)); } else { memcpy((void *)NoDelayFinalAttackAddress, &NoDelayFinalAttack_Disable, sizeof(NoDelayFinalAttack_Disable)); } }
|
至於加入開關就當作是各位的回家作業囉!
還有本範例使用的數據很貼心的在API前附帶所在的DLL檔案,可是網路上的數據幾乎都沒有寫DLL怎麼辦?
此時請善用Google:假如有一個數據中使用了VariantClear
這個API,請先Google搜詢此API名稱,接著一定會看到來自MSDN的搜尋結果,請點進去MSDN的網頁,如https://msdn.microsoft.com/zh-tw/library/windows/desktop/ms221165(v=vs.85).aspx,接著向下滑找到Requirements
的部分,其中DLL
就是這個API所在的DLL囉!好好利用MSDN的資源吧!
#第 1 2 3 頁