最近一直不務(wù)正,老打算用C#寫(xiě)個(gè)外掛出來(lái)。
這方面對C#來(lái)說(shuō)是個(gè)弱項,但并不表示無(wú)法做到。
下面寫(xiě)個(gè)簡(jiǎn)單的例子,和大家交流一下。
以windows中的掃雷為例,比如說(shuō)讀取雷的數量。
1.首先導入API(對底層的操作都要用API):
[DllImport("kernel32.dll")]private static extern IntPtr OpenProcess(uint flag,bool ihh,int processid);openprocess是用來(lái)打開(kāi)進(jìn)程的,要對系統中的某一個(gè)進(jìn)程進(jìn)行讀寫(xiě),必須先打開(kāi)進(jìn)程。第一個(gè)參數為打開(kāi)的標記,例如全權打開(kāi)進(jìn)程為0x1F0FFF(16進(jìn)制數)。第二個(gè)沒(méi)什么好說(shuō)的,第三個(gè)參數為要打開(kāi)進(jìn)程的PID.現在第一個(gè)參數為常量,就差第三個(gè)參數PID了,PID怎么得到呢?用.net的方法是:
Process[] p = Process.GetProcessesByName("winmine");p[0].Id;這樣就能返回相應進(jìn)程的PID了。
它的返回值為相應進(jìn)程的句柄。它是下面API將要使用的。
IntPtr handle = OpenProcess(0x1F0FFF, false, processid);2.第二步就可以讀取了。
[DllImport("kernel32.dll")]private static extern bool ReadProcessMemory(IntPtr handle,int address,int[] buffer,int size,int[] nor);readrpocessmemory可以用來(lái)讀取某個(gè)進(jìn)程地址的值。第一個(gè)參數就是上面API返回的值;第二個(gè)是要讀取進(jìn)程的地址;第三個(gè)參數為讀取出的內容,要求為指針,它相當于一個(gè)out類(lèi)型的參數,讀出的內容并不是以函數返回值的方式得到;第四個(gè)為讀取值的字節大小,int為4,byte為1,就是這樣;第五個(gè)參數也要求為一個(gè)指針,一般用數組就可以了,數組名相當于指針。
int[] result=new int[1];int[] lpdw=new int[1];//這樣定義就可以了,一個(gè)元素的數組,能起到指針的作用。bool b = ReadProcessMemory(handle, 0x1005194, result, 4, lpdw);讀出的東西到哪去了,result數組中的內容就是了。由于只有一個(gè)元素,result[0]就是你要的東西了。
基本上做外掛,讀出內存是最基本的東西,一個(gè)游戲中人物的生命值,真氣值,等等基礎的信息如果不知道,下一步就更加無(wú)法進(jìn)行了。
除了上面的兩個(gè)API,還可能用到以下幾個(gè):
寫(xiě)內存:
[DllImport("kernel32.dll")]public static extern Int32 WriteProcessMemory(IntPtr hProcess,IntPtr lpBaseAddress, [In, Out] byte[] buffer, int size, out IntPtr lpNumberOfBytesWritten);
創(chuàng )建線(xiàn)程:
[DllImport("kernel32", EntryPoint = "CreateRemoteThread")]public static extern int CreateRemoteThread(int hProcess,int lpThreadAttributes,int dwStackSize,int lpStartAddress,int lpParameter,int dwCreationFlags,ref int lpThreadId);
開(kāi)辟指定進(jìn)程的內存空間:
[DllImport("Kernel32.dll")]public static extern System.Int32 VirtualAllocEx(System.IntPtr hProcess,System.Int32 lpAddress,System.Int32 dwSize,System.Int16 flAllocationType,System.Int16 flProtect);
釋放內存空間:
[DllImport("Kernel32.dll")]public static extern System.Int32 VirtualFreeEx(int hProcess,int lpAddress,int dwSize,int flAllocationType);
關(guān)閉句柄:
[DllImport("kernel32.dll", EntryPoint = "CloseHandle")]public static extern int CloseHandle(int hObject);------------------------------------------------------------------------------------
下面說(shuō)下sendmessage的問(wèn)題,其實(shí)不論用API還是.net的方法,對現在的游戲都難以發(fā)揮作用,為什么呢?因為早就被屏掉了。sendmessge與sendkey其實(shí)本質(zhì)上是同一個(gè)東西?,F在流行的模擬按鍵的庫winio大家可能聽(tīng)說(shuō)過(guò),它應用了驅動(dòng)程序的相關(guān)技術(shù),使自己運行在ring0級別上(一般程序運行在ring3級上,一些驅動(dòng)程序,操作系統核心模塊才運行在ring0級),這樣能繞過(guò)游戲的檢測。不過(guò)由于winio的名氣太大,使用太大眾化,所以最近可能也不能用了。
現在流行的方法是使用CALL,CALL就是調用游戲本身內部的函數,比如攻擊怪物,調用相應的攻擊函數,而不是發(fā)送按鍵。不過(guò)找CALL是個(gè)需要知識的耐心的過(guò)程。流行的工具是OD,本菜鳥(niǎo)還沒(méi)研究透,另外CE這個(gè)工具也是必不可少的,例如上面的地址0x1005194就是用CE找出來(lái)的,CE怎么使用呢,再說(shuō)就扯的太遠了,網(wǎng)上有不少的教程,有興趣的可以查一下。
整理一下,啟動(dòng)VS,創(chuàng )建一個(gè)button和一個(gè)label,在form1中復制以下代碼,然后啟動(dòng)程序,啟動(dòng)掃雷,就可以看到了
using System;using System.Collections.Generic;using System.ComponentModel;using System.Data;using System.Drawing;using System.Text;using System.Windows.Forms;using System.Diagnostics;using System.Runtime.InteropServices;
namespace saolei{ public partial class Form1 : Form { public Form1() { InitializeComponent(); } private const uint PROCESS_ALL_ACCESS = 0x1f0fff; [DllImport("kernel32.dll")] public extern static IntPtr OpenProcess(UInt32 dwdesiredaccess, int binherithandle, Int32 dwprocessid); [DllImport("kernel32.dll")] public extern static bool ReadProcessMemory(IntPtr hprocess, UInt32 lpbaseaddress, int[] plbuffer, UInt32 nsize, Int32[] lpnbr); private void Form1_Load(object sender, EventArgs e) {
}
private void button1_Click(object sender, EventArgs e) { Process[] p = Process.GetProcessesByName("winmine"); IntPtr handle = OpenProcess(0x1F0FFF, 0, p[0].Id); int[] result = new int[1]; int[] lpdw = new int[1]; bool b = ReadProcessMemory(handle, 0x1005194, result, 4, lpdw); this.label1.Text = result[0].ToString(); } }}聯(lián)系客服