C#使用Win32 Api实现进程注入到wechat的过程
作者:四处观察
引言
自从上篇使用Flaui实现微信自动化之后,这段时间便一直在瞎研究微信这方面,目前破解了Window微信的本地的Sqlite数据库,使用Openssl,以及Win32Api来获取解密密钥,今天作为第一张,先简单写一下,获取微信的一些静态数据,以及将自己写的c语言dll通过Api注入到微信进程里面去,最后调用我们的dll的方法。话不多说,让我们开始吧。
逆向
静态数据的话,需要用到的软件 CE,全称是Cheat Engine,图标如下所示。接下来我们打开CE,可以看到左上角有一块绿色的按钮,我们点击按钮是附加进程到CE,然后我们点击附加微信到CE,在下面的图中,我们看到已经把微信进程加载到了CE里面去,然后我们要开始获取静态数据了。
在获取静态数据之前,我们先开始讲几个概念,就是内存的概念,我们都知道,在进程启动的时候,操作系统会给我们的进程分配虚拟内存,默认应该是4g,具体是和操作系统位数也有关系,然后在运行时也会动态的分配内存空间,我们学过计算机原理的肯定知道,我们的内存存储结构就像是一个链表或者数组,我们在给这个进程分配内存空间的时候,他的样子也是是类似数组的这种结构,首先假如我们的进程现在有一个主模块,主模块里面又有自己的方法,自己的类,属性等信息,那分配的这个主模块的内存就是一个数组,然后我们主模块有一个基础地址,你可以将这个基础地址看作是这块内存数组的索引0,而我们主模块的其他的方法,类,变量信息,都是在这个0的索引进行移动到指定的地址,这个地址指向我们的内存,这个内存存储着我们要的信息。简而言之,就是主模块是的地址就是索引0,而其他变量信息可能在5,7,9等等,我们就需要判断从0到5有多少间隔,这个就叫偏移量,我们通过属性或者方法的内存地址减去主模块的地址,这个就是我们的偏移量,借这个例子就是5-0就是5,偏移量是5。
然后我们回来,我们加载微信进程到了我们的CE之后,在wechat有一个模块叫Wechatwin,这个是window操作系统下的微信用到的主要模块,我们的和微信相关的基本都在这里,当然不包括一些resource,这个有一个专门的模块,我们在此不多赘述,所以我们假如要找我们的静态数据,例如微信昵称,微信号,或者手机号,所在地区,就需要找到我们的wechatwin的地址,这个就是这个模块的基址,然后我们需要在CE中,检索字符串找到我们要的数据,例如昵称,手机号等信息。然后用他的地址减去基址,得到偏移量。从而我们就可以在代码中获取到这些信息,接下来,我先带大家在CE中找到我们想要找的数据。
在CE上方右侧,有一个输入框,我们在这里输入我们需要检索的信息,支持的格式有byte,string,以及array,double等数据类型,我们需要找到是string,所以在ValueType那里,我们选择string。我的微信昵称是云淡风轻,所以在这里搜索云淡风轻,可以看到,就检索出来我们的昵称信息了,找到了这么多,这里我们往最下面拉,有一个绿色开头的,Address是WechatWin的,就是我们要找的地址了,其他的也有的是绿色基于Wechatwin有的不是,有的就需要一个一个测试修改数据从而得到验证了。
我们双击那条绿色记录,Wechatwin 将他加入到下面的列表去,代表我们选中的检测的内存,接下来我们验证一下,是否是找的正确的,双击Value,云淡风轻,我们修改我们的Value,将云淡风轻,改为good man 点击ok,可以在下面看到,我们的微信昵称已经同步改为了good Man,说明我们找到的是对的,接下来,我们双击Address,
弹出Change address姐妹,我们复制WechatWin.dll,需要我们找到我们这个模块的基址。然后在右边有一个Add Address Manually,手动添加地址,,我们把复制的WeChatWin.dll复制过去,然后点击ok,在下面的列表我们就看到了这个模块的基址,接下来,我们需要判断这个基址和昵称之间的偏移量,按照我们刚才所说的方式计算,转换16进制就是0x7ffd3d668308-0x7ffd39b40000,随便找一个16进制计算器,算下来的结果就是3B28308,也就是Address里面显示的那个,实际上CE已经给我们把偏移算出来了,接下来按照同样的方式,去搜索我们的所在地区,以及手机号,如果有的信息找不到的话,我们选择我们的昵称哪一行数据,右键,选择Browse this Memory region,在内存页显示这个内存记录,然后我们在旁边就可以看到我们的国家,以及省份地区信息了,如果有查看地址,在右侧,选择我们要复制的记录,右键,有一个goto Address,然后就导航到了我们的内存,然后复制地址即可。
c#代码获取数据以及远程注入
在上面我们讲了,如何使用CE,去获取我们微信的一些静态数据,接下来,我们就需要使用c#代码,去实现我们获取静态数据,以及最后写的一个远程注入,来调用我们写的一个库。首先我们需要用到的有几个Api函数,
WaitForSingleObject,等待某个句柄多长时间,在我们创建远程线程的时候需要使用这个函数来等待线程执行结束。参数是等待的句柄,我们填写我们的线程句柄。
GetProcAddress,需要使用这个函数来调用kernel32.dll的LoadLibraryA方法,来加载我们的自己写的dll,因为在每个进程启动的时候,都会去调用这个方法来加载程序所依赖的dll,还有一个方法是LoadLibraryW,和这个方法区别在于不同针对不同的编码来进行调用,W结尾主要是针对UNICODE的编码,A结尾对应Ascii编码,所以各位在调用的时候根据自己的编码去调用,如果一个找不到就试试另一个。
GetModuleHandle,这个函数是用来获取kernel32.dll,结合上面的GetProdAddress来使用。
OpenProcess,这个方法是根据指定的PID,对应就是Process类的Id,打开指定的进程,同时指定以什么权限打开这个进程,参数是三个,第一个是权限,第二个是返回值是否可以被继承,返回的进程句柄是否可以被继承,第三个参数就是我们的PID。
VirtualAllocEx,给指定的进程分配虚拟内存,第一个参数是进程的句柄,OpenProcess返回值,第二个参数指定进程内那个内存地址分配的内存,此处我们只是加载dll调用方法,并不注入到某个方法或者哪里所以是Intptr.Zero,第三个参数是,分配的内存长度,我们加载dll需要dll的路径,这里就选择路径.Length就行,字符串的长度就可以,第三个参数是内存分配的一些配置,可选值在后面会有,此处我们选择Memory_Commit,第四个参数是内存权限相关,内存是只读还是可以读写,以及用来执行代码或者怎么样,这里我们选择可以读写。
ReadProcessMemory,读指定进程的内存,第一个参数进程句柄,OpenProcess返回值,第二个参数是这个进程某个内存的地址,第三个是数据缓冲区,读取之后的内容就在这个缓冲区,我们读取这个缓冲区就可以拿到数据,第四就是缓冲区的长度,第五个就是读取的字节数量。
GetLastError,用来获取Win32api调用的时候的errorcode,错误编码,
CloseHandle,关闭某一个句柄,关闭基础,关闭线程。
WriteProcessMemory,写入内存,我们需要将我们的dll地址写入到指定内存中去,第一个参数进程句柄,OpenProcess返回值,第二个参数,要写入的内存地址基址,例如我们后期需要在某个方法进行注入,这块就需要写入这个方法的内存地址,第三个参数,写入的byte数据,第四个参数是第三个参数的长度,最后一个参数是写入的数据数量。
CreateRemoteThread,在指定的进程中创建远程线程,第一个参数 OpenProcess返回值,第二个参数是线程安全的一些特性描述,按网上所说,一般null或者IntPtr.Zero,第三个参数设置线程堆栈大小,默认是0,即使用默认的大小,第四个参数是线程函数的地址,我们要通过这个方法去调用Kernel32的LoadLibrary方法加载我们的dll,那这个参数就填写我们的GetProcAddress返回值,第四个参数就是创建这个线程的参数,就是分配的远程内存的地址VirtualAllocEx返回值,就是说通过创建远程线程来调用LoadLibrary方法加载我们写入指定内存地址的dll库,来实现注入,是这样一个逻辑,第五个参数是线程创建的一些参数,是创建后挂起还是直接运行等,最后一个参数是输出参数,记录创建的远程线程的ID。
以上是我们所需要用到的所有的Win32Api函数,接下来我们进入代码阶段。
在下面的窗体,窗体会在加载的时候就去调用注入我们的dll,同时界面在加载的时候就获取获取我们的静态信息。我们的dll地址是E盘下面的一个dll,这个Dll使用c语言编写。在启动的时候我们去获取我们的微信进程,拿到的ID,然后去注入我们的Dll,在下面的代码里,我们判断是否模块是WechatWin.dll,如果是,就定义了phone,NickName,Provice,Area等int值,这个其实就是我们在CE拿到的静态数据的内存地址,减去我们的Wechatwin.Dll的出来的偏移量,然后定义了我们各个静态数据的缓冲区,用来读取从微信进程读取的内存数据。然后我们调用了ReadProcessMemory函数读取内存,获取我们需要的静态数据。然后使用Utf8转为字符串,显示到界面上。这就是获取静态数据的源码,然后关闭我们的进程句柄,并不是关闭微信,而是关闭我们获取的这个进程句柄。
string dllpath = @"E:\CoreRepos\ConsoleApplication2\x64\Debug\Inject.dll"; var process = Process.GetProcessesByName("wechat").FirstOrDefault(); InjectDll(process.Id, dllpath); var pid = OpenProcess(ProcessAccessFlags.PROCESS_ALL_ACCESS, false, process.Id); int bytesRead; int bytesWritten; foreach (ProcessModule item in process.Modules) { if (item.ModuleName.ToLower() == "WechatWin.dll".ToLower()) { int phone = 0x3B28248; int NickName = 0x3b28308; int provice = 0x3B282A8; int Area = 0x3B282C8; var Nickbuffer = new byte[12]; var Phonebuffer = new byte[11]; var proviceBuffer= new byte[12]; var areaBuffer=new byte[12]; ReadProcessMemory(process.Handle, item.BaseAddress + NickName, Nickbuffer, Nickbuffer.Length, out bytesRead); ReadProcessMemory(process.Handle, item.BaseAddress + phone, Phonebuffer, Phonebuffer.Length, out bytesRead); ReadProcessMemory(process.Handle, item.BaseAddress + provice, proviceBuffer, proviceBuffer.Length, out bytesRead); ReadProcessMemory(process.Handle, item.BaseAddress + Area, areaBuffer, areaBuffer.Length, out bytesRead); var Nickvalue = Encoding.UTF8.GetString(Nickbuffer); var Phonevalue = Encoding.UTF8.GetString(Phonebuffer); var Provicevalue = Encoding.UTF8.GetString(proviceBuffer); var Areavalue = Encoding.UTF8.GetString(areaBuffer); label1.Text = Nickvalue; label2.Text = Phonevalue; label3.Text = Provicevalue; label4.Text = Areavalue; var buf = Encoding.UTF8.GetBytes("我是你爹"); CloseHandle(process.Handle); } }
然后我们开始看看注入DLL的代码,我们先引入了诸多函数,然后定义了OpenProcess第一个参数权限的枚举,定义了INFINITE 用来WaitForSingleObject等待指定的句柄进行某些操作的执行结束,当然有一些我没有定义完整,只定义我们此处需要的,完整的可以参考官网api去进行看。在刚进入这段代码,我们调用OpenProcess指定最高权限打开这个进程,然后获取我们的dll地址的byte数组,并将分配内存VirtualAllocEx到我们这个进程里面,同时最后两个参数代表分配内存的一些操作,例如内存是Memory_Commit,0x1000,以及内存是可以读写的0x04,分配好内存之后,我们去往我们分配好的内存写入我们的dll路径,调用WriteProcessMemory方法,传入进程句柄,内存地址,写入的数据等,在下面GetProcAddress和GetModuleHandle用来加载kernel32的LoadraryA方法句柄,最后我们调用了CreateRemoteThread函数将我们的dll注入到远程进程中去。
#region 32 api [DllImport("kernel32.dll", SetLastError = true)] public static extern UInt32 WaitForSingleObject(IntPtr hHandle, UInt32 dwMilliseconds); [DllImport("kernel32.dll")] public static extern IntPtr GetProcAddress(IntPtr hModule, string lpProcName); [DllImport("kernel32.dll")] public static extern IntPtr GetModuleHandle(string lpModuleName); [DllImport("kernel32.dll", SetLastError = true)] public static extern IntPtr VirtualAllocEx(IntPtr hProcess, IntPtr lpAddress, int dwSize, int flAllocationType, int flProtect); [System.Runtime.InteropServices.DllImport("kernel32.dll", SetLastError = true)] public static extern bool ReadProcessMemory( IntPtr hProcess, IntPtr lpBaseAddress, byte[] lpBuffer, int dwSize, out int lpNumberOfBytesRead ); [DllImport("kernel32.dll")] public static extern IntPtr OpenProcess( ProcessAccessFlags dwDesiredAccess, bool bInheritHandle, int dwProcessId ); [DllImport("kernel32.dll")] static extern uint GetLastError(); [DllImport("kernel32.dll")] public static extern bool CloseHandle(IntPtr hObject); [DllImport("kernel32.dll")] public static extern bool WriteProcessMemory(IntPtr hProcess, IntPtr lpBaseAddress, byte[] lpBuffer, int nSize, out int lpNumberOfBytesWritten); [DllImport("kernel32.dll")] public static extern IntPtr CreateRemoteThread(IntPtr hProcess, IntPtr lpThreadAttributes, uint dwStackSize, IntPtr lpStartAddress, IntPtr lpParameter, uint dwCreationFlags, IntPtr lpThreadId); // 进程访问权限标志位 [Flags] public enum ProcessAccessFlags : uint { PROCESS_ALL_ACCESS = 0x1F0FFF, PROCESS_CREATE_PROCESS = 0x0080, PROCESS_QUERY_INFORMATION = 0x0400, } const uint INFINITE = 0xFFFFFFFF; #endregionpublic bool InjectDll(int processId, string dllPath) { IntPtr hProcess = OpenProcess(ProcessAccessFlags.PROCESS_ALL_ACCESS, false, processId); if (hProcess == IntPtr.Zero) { Console.WriteLine("打开失败"); return false; } byte[] dllBytes = Encoding.UTF8.GetBytes(dllPath); ; IntPtr remoteMemory = VirtualAllocEx(hProcess, IntPtr.Zero, dllBytes.Length, 0x1000, 0x04); int bytesWritten; if (!WriteProcessMemory(hProcess, remoteMemory, dllBytes, dllBytes.Length, out bytesWritten)) { var ooo = GetLastError(); Console.WriteLine("写入失败"); return false; } IntPtr loadLibraryAddr = GetProcAddress(GetModuleHandle("kernel32.dll"), "LoadLibraryA"); var ooaa = GetLastError(); if (loadLibraryAddr == IntPtr.Zero) { Console.WriteLine("获取LoadraryA失败"); } // 创建远程线程,在目标进程中调用 LoadLibraryA 加载 DLL var hRemoteThread = CreateRemoteThread(hProcess, IntPtr.Zero, 0, loadLibraryAddr, remoteMemory, 0, IntPtr.Zero); var ooaa1 = GetLastError(); if (hRemoteThread == IntPtr.Zero) { Console.WriteLine("目标进程创建远程线程失败"); } // 等待远程线程执行完毕 WaitForSingleObject(hRemoteThread, 0xFFFFFFFF); WaitForSingleObject(hRemoteThread, INFINITE); CloseHandle(hRemoteThread); CloseHandle(hProcess); Console.WriteLine("注入成功"); return true; }
我们看看我写的dll里面是包括了什么内容,我们的dll内容很简单,就是创建一个txt文件,然后写入一个数据就行,这里需要注意的是,在使用vs创建dll的时候 选项必须是选择的是动态链接库,这样才有DLLMain方法,这样在调用LoadraryA方法的时候才会调用我们的dll,自动调用DLLMain方法,同时里面还有一个switch case语句是进程加载线程加载,以及线程卸载,进程卸载的判断 我们可以在这里去去一些我们的逻辑判断,此处我并没有写,只是在外层创建了一个文件夹,接下来运行一下我们的winform,看看有没有获取到静态数据,以及将我们的dll注入进去。马赛克手机号。
可以看到我们启动了界面之后,查看我们的Process.Modules,可以看到我们注入的Inject.dll,那我们看看有没有创建txt呢。在下面可以看到,我们已经成功注入到微信进程并且创建了一个example.txt,并且写入的内容和上图定义的内容是一致的,到此,我们将我们dll注入到了微信进程中去了。
结语
在上面我们讲了一些如何找到静态数据,以及根据基址,偏移量在进程启动的时候找到我们想要的数据,并且将我们的dll成功注入到进程里面去,在后面,我可能还会在深入研究一下逆向,到时候会继续发文,感兴趣的朋友可以关注一波,同时,近期,还破解了微信Sqlite本地数据库获取了一些内容,下面是获取的数据内容,这个我应该不会开源,但是会有一个c语言的写的解密demo开源,同时可能会分享一部分c#获取解密密钥的代码,同时也需要一些逆向的知识,win32api,这个东西由于涉及个人隐私,所以我尚不确定是否开源,因为存在有的人如果挂马,可以窃取他人的隐私,所以后续再说,同时在写的,讲的不对的地方,欢迎各位大佬指正。
到此这篇关于C#使用Win32 Api实现进程注入到wechat的文章就介绍到这了,更多相关c#使用Win32 Api内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!