第一次寫外掛就上手 - 使用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.cpp
DWORD 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:GetModuleHandleGetProcAddress

在本次示範的數據中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寫著可以從調用LoadLibraryLoadLibraryExLoadPackagedLibraryGetModuleHandle的返回值取得。而我們要用的就是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