C#編寫(xiě)程序的課程學(xué)習(xí)
C#編寫(xiě)程序第一課:C#使用WINDOW API和對(duì)內(nèi)存的操作
這一課是些簡(jiǎn)單的東西,了解的可以直接越過(guò)。考慮到大多數(shù)使用c#的人都是做網(wǎng)站的,可能沒(méi)有機(jī)會(huì)接觸這些,所以我在這里做一下粗略的介紹。
step 1:認(rèn)識(shí)WINAPI
windows系統(tǒng)里提供了很多的函數(shù),我們?nèi)绻鐾鈷斓脑?,就需要用到其中的函?shù)(以下簡(jiǎn)稱API)。(廢話:這些API被封裝在系統(tǒng)路徑下的DLL文件里。事實(shí)上,我們不用關(guān)心它在哪,我們只要知道怎么用就可以了,)用起來(lái)很簡(jiǎn)單,格式如下:
- public partial class Form1 : Form
- {
- [DllImport("kernel32.dll")] \
- public static extern int ReadProcessMemory( |
- int hProcess, |
- int lpBaseAddress, |
- int[] lpBuffer, >代碼段1
- int nSize, |
- int lpNumberOfBytesWritten |
- ); /
- ...
- public Form1()
- {
- InitializeComponent();
- ReadProcessMemory(processhandle,... >代碼2
- ...
- }
- ...
- }
代碼段1就是引用api的代碼。我們引用的函數(shù),是做外掛時(shí)最常用的函數(shù),從它的名字就可以看的出來(lái)它的作用---讀取進(jìn)程內(nèi)存。(廢話:從代碼里,我們很容易看的出來(lái),這個(gè)函數(shù)被封裝在了kernel32.dll這個(gè)文件里。)引用之后,我們就可以在自己的代碼中使用這個(gè)函數(shù)了(如代碼2)。
(廢話:WINDOWS還提供很多的API,如果你有興趣了解的話,可以到網(wǎng)上搜WINAPI手冊(cè)。想深入了解的話,可以看MSDN。)
step 2:讀寫(xiě)內(nèi)存
下面我來(lái)說(shuō)一下,如何使用上一步引用的那個(gè)API讀取游戲的數(shù)據(jù)。先來(lái)看看參數(shù):
- publicstaticexternintReadProcessMemory(
- inthProcess
- //進(jìn)程,如果你是做外掛的話,它代表你要掛的那個(gè)游戲。
- intlpBaseAddress
- //你要讀取的內(nèi)存地址
- int[]lpBuffer
- //從上面那個(gè)參數(shù)地址里讀出來(lái)的東西(調(diào)用這個(gè)函數(shù)的就是為了它)不管這個(gè)參數(shù)是什么類型,它應(yīng)該是一個(gè)數(shù)組,否則讀不出東西來(lái)
- intnSize
- //長(zhǎng)度,上一個(gè)參數(shù),類型是int,那個(gè)長(zhǎng)度應(yīng)該用4
- intlpNumberOfBytesWritten//用0就行了,想知道它是干嘛的,自己去MSND吧
關(guān)于第一個(gè)參數(shù)hProcess如何獲取,我過(guò)會(huì)再說(shuō)。假設(shè)它已經(jīng)搞定了,那么這個(gè)函數(shù),我們需要關(guān)心的只有l(wèi)pBaseAddress和lpBuffer,既讀的地址,和讀出來(lái)的值。(廢話:對(duì)了,這個(gè)函數(shù)貌似還有個(gè)返回值,我們這里用不到它。如果你有興趣了解,MSDN)讀出來(lái)的值out int lpBuffer我們?cè)谝肁PI的時(shí)候聲明為int型了,但是,我們要從內(nèi)存里讀的值不一定總是int。我們可以多次引用這個(gè)API,第3個(gè)參數(shù)分別用不同的類型。
下面,我們結(jié)合實(shí)際,來(lái)寫(xiě)一段讀取誅仙人物HP的代碼。首先,我們需要知道人物HP的地址,(廢話:如何知道這個(gè)地址,用CE還是IE,你自己搞定吧。)我是用IE在這里http://www.ghoffice.com/bbs/read.php?tid-35908-fpage-2.html找到的,它這里是這樣寫(xiě)的:
人物基址:[[&H12F830]+&H28]=base
生命:[base+&H254]
(注:&H表示16進(jìn)制,在C#里我們用0x表示)
一對(duì)[]表示讀一次地址。也就是說(shuō)123表示值123,而[123]就表示從地址123讀出來(lái)的值。幾對(duì)[],就要用幾次
- ReadProcessMemory,我們來(lái)寫(xiě)下代碼:
- int[] Base=new int[1];
- int[] hp=new int[1];
- ReadProcessMemory(process, 0x12F830, Base;, 4, 0);//相當(dāng)于Base=[&H12F830]
- ReadProcessMemory(process, Base+0x28, Base;, 4, 0);//相當(dāng)于Base=[Base+&H28]
- //讀出了人物基址base
- ReadProcessMemory(process, Base+0x254, hp;, 4, 0);//相當(dāng)于hp=[base+&H254]
- //讀出了hp
怎么樣,很簡(jiǎn)單吧。
我們讀HP只用了3行ReadProcessMemory。有的時(shí)候,讀某個(gè)值可能需要很多對(duì)[],就要寫(xiě)N行ReadProcessMemory,這樣寫(xiě)起來(lái)就很麻煩,看起來(lái)也很暈。下面我們來(lái)寫(xiě)個(gè)函數(shù),讓讀內(nèi)存的過(guò)程看起來(lái)和[]表示法差不多。
- //為了看起來(lái)好看,函數(shù)的名字最好短些,所以我們用r,表示read
- public static int r(int add)
- {
- int[] r=new int[1];
- try
- {
- ReadProcessMemory(process, add, r, 4, 0);
- return r[0];
- }
- catch (Exception ex)
- {
- return -1;
- }
- }
這個(gè)函數(shù)很簡(jiǎn)單,不用我多說(shuō)了吧。
有了這個(gè)函數(shù),上面的讀取HP的代碼,我們就可以寫(xiě)成這樣了:
- int Base;
- int hp;
- Base=r(r(0x12F830)+0x28);
- //讀出了人物基址base
- hp=r(base+&H254);
- //讀出了hp
看起來(lái)清晰多了吧。
下面我來(lái)說(shuō)下讀取字符串,首先引用API:
- [DllImport("kernel32.dll")]
- public static extern int ReadProcessMemory(
- int hProcess,
- int lpBaseAddress,
- byte[] lpBuffer,
- int nSize,
- int lpNumberOfBytesRead
- );
然后和上面一樣,寫(xiě)一個(gè)讀字符串的方法。
- public static string rString(IntPtr process, uint add)
- {
- string[] r;
- string temp;
- byte[] b = new byte[256];
- try
- {
- API.ReadProcessMemory(process, (IntPtr)add, b, 256, (IntPtr)0);
- //讀出的byte[]要按Unicode編碼為字符串
- temp = System.Text.Encoding.Unicode.GetString(b);
- //截取第一段字符串
- r = temp.Split(''\0'');
- return r[0];
- }
- catch (Exception ex)
- {
- return "error";
- }
- }
這個(gè)函數(shù)和上面那個(gè)函數(shù)差不多,多的東西注釋里已經(jīng)寫(xiě)了,也很簡(jiǎn)單,不必我廢話了。
下面,我們來(lái)讀人物的名字。還是剛才那個(gè)帖子里得到的,人物名字偏移如下:
人物角色名:[[base+3a4]+0]
代碼如下:
- string name;
- name=rString(r(basse + 0x3a4)+0x0);//+0x0可以去掉
讀其他類型的數(shù)據(jù)和讀INT的雷同,我就不廢話了,大家自己搞定吧。
現(xiàn)在萬(wàn)事俱備,就差這個(gè)process了,下面我來(lái)說(shuō)下,如果獲得游戲的進(jìn)程句柄(廢話:進(jìn)程句柄:一個(gè)用來(lái)表示某進(jìn)程的整形值。推廣到一般,**句柄,就是表示某**的整形值)。分兩步,第一步:
- System.Diagnostics.Process[] GamesProcess
- = System.Diagnostics.Process.GetProcessesByName("elementclient");
這一步用的是.NET本身的方法,System.Diagnostics.Process是.NET里的進(jìn)程類,GetProcessesByName靜態(tài)方法是通過(guò)進(jìn)程的名字獲得進(jìn)程數(shù)組。這行語(yǔ)句執(zhí)行之后,所有游戲進(jìn)程就放在了GamesProcess里面。如果你想做多開(kāi)掛的話,可以通過(guò)數(shù)組GamesProcess的下標(biāo),來(lái)確定你要掛的游戲。
第二步:
- int ProcessID=GamesProcess[0].Id;
- int process = OpenProcess(0x1F0FFF, 0, ProcessID);
第1行是獲得進(jìn)程ID,就是任務(wù)管理器里看到的PID。第2行就是獲得進(jìn)程句柄。OpenProcess也是一個(gè)系統(tǒng)API,也是在kernel32.dll里。他的3個(gè)參數(shù)和返回值都聲明為INT就OK了。如何引用請(qǐng)看step 1。大家應(yīng)該可以看出來(lái)怎么用,第3個(gè)參數(shù)是進(jìn)程ID,返回的就是進(jìn)程句柄(廢話:1,2參數(shù)做何用,想知道的自己看MSDN。懶人直接用示例里的參數(shù)就行了。以后此類廢話不再多說(shuō)了)。
看到這里,大家可以試著寫(xiě)一個(gè)讀取人物資料的小東西試試了。當(dāng)然,前提是你要知道資料的地址。
寫(xiě)內(nèi)存:
- WriteProcessMemory(process, (IntPtr)add, bytes, (UInt32)bytes.Length, 0);
寫(xiě)進(jìn)程內(nèi)存函數(shù)。這個(gè)API的各參數(shù)和ReadProcessMemory是一一對(duì)應(yīng)的。大家自己聲明,用用看吧。喜歡的話,也可以向上面一樣自己寫(xiě)個(gè)函數(shù),以簡(jiǎn)化寫(xiě)內(nèi)存的代碼。在下一課,我們要用這個(gè)函數(shù)來(lái)向游戲里寫(xiě)代碼。
下一課將是些更有趣的東西。我們要通過(guò)外掛讓游戲執(zhí)行一些操作。敬請(qǐng)期待吧,呵呵。
C#編寫(xiě)程序第二課 C#注入
這一課其實(shí)也很簡(jiǎn)單,只不過(guò)知道的人不多而已。
step 3:注入
注入沒(méi)什么復(fù)雜的,它是一個(gè)很簡(jiǎn)單的過(guò)程。用語(yǔ)言描述就一句話:在別的程序里寫(xiě)入你的代碼,并執(zhí)行。
實(shí)現(xiàn)起來(lái)也很簡(jiǎn)單,就幾行代碼:
- byte[] bytes={0xC3};//我們要寫(xiě)入的代碼
- int addr = VirtualAllocEx(process, 0, bytes.Length, 0x1000, 0x40);//一,申請(qǐng)空間
- WriteProcessMemory(process, addr, bytes, bytes.Length, 0);//二,把代碼寫(xiě)進(jìn)去
- int hThread = CreateRemoteThread(process, 0, 0, addr, 0, 0, threadId);//三,執(zhí)行寫(xiě)入的代碼
- WaitForSingleObject(hThread, 0xFFFFFFFF);//等待線程結(jié)束
- VirtualFreeEx(process, addr, 0, 0x8000); //四,釋放申請(qǐng)的空間
- CloseHandle(hThread);//五,關(guān)閉線程句餅
仔細(xì)看一下這幾行代碼,你會(huì)發(fā)現(xiàn)非常簡(jiǎn)單,幾乎不需要我多說(shuō)什么。這幾個(gè)豆耐特里豆不出來(lái)的函數(shù),都是API。根據(jù)上面的使用方法,引用一下,就可以用了。你能看懂的那幾個(gè)參數(shù)和返回值,就是需要你關(guān)心的。不知道的的參數(shù)都不用理會(huì),直接用上面的值就行了。還有疑問(wèn)的話,可以參考WINAPI手冊(cè)。值得注意的地方是,第四步釋放申請(qǐng),如果你看了API手冊(cè),會(huì)發(fā)現(xiàn)第三個(gè)參數(shù)是大小,但如果你用bytes.Length的話就錯(cuò)了,一定要用0。
(廢話:如果你不知道怎么根據(jù)上面的使用方法引用API,我就簡(jiǎn)單說(shuō)兩句。以第二行為例,我們看到VirtualAllocEx的返回值和5個(gè)參數(shù)都是int行,那么這樣聲明就行:
- [DllImport("Kernel32.dll")]//沒(méi)有特殊說(shuō)明的話,API一般都是從這個(gè)DLL引用的
- public static extern int VirtualAllocEx(
- int p1,
- int p2,
- int p3,
- int p4,
- int p5
- );
大家可以看出來(lái),要申明一個(gè)API只要知道各參數(shù)和返回值的類型,以及DLL文件名就可以了。喜歡的話,你可以把參數(shù)的名字起的有意義些。)
簡(jiǎn)簡(jiǎn)單單幾行代碼就實(shí)現(xiàn)了注入,是不是沒(méi)你想像的復(fù)雜?呵呵。
現(xiàn)在的一個(gè)問(wèn)題就是,代碼從何而來(lái)?
大家可以使用我的工具將你找到的CALL轉(zhuǎn)換為機(jī)器碼。(廢話:這個(gè)工具的原理,就是調(diào)用MASM編譯,所以任何你在MASM里能使用的語(yǔ)法和指令(限函數(shù)內(nèi)),都可以在這里用,當(dāng)然,語(yǔ)法和MASM里的語(yǔ)法規(guī)則是一樣的。使用的方法在附件里有詳細(xì)的說(shuō)明,我就不在這里浪費(fèi)篇章了。)
工具轉(zhuǎn)換得到的結(jié)果是型如60b8d0305a00ffd08b561c83c40461c3的字符串,大家可以用下面的方法把它轉(zhuǎn)換為byte[]
- public static byte[] getBytes(string HEX)
- {
- byte[] bytes = new byte[HEX.Length / 2];
- for (int i = 0; i < bytes.Length; i++)
- {
- bytes[i] = Convert.ToByte(Int32.Parse(HEX.Substring(i * 2 , 2),
- System.Globalization.NumberStyles.AllowHexSpecifier));
- }
- return bytes;
- }
OK,C#編寫(xiě)程序的課程就到這里,大家可以著手試著用C#調(diào)用一下游戲里的攻擊CALL了.(如果你不會(huì)找CALL,你可以試著在此論壇里找找)
【編輯推薦】