引言
自从上篇使用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,这个东西由于涉及个人隐私,所以我尚不确定是否开源,因为存在有的人如果挂马,可以窃取他人的隐私,所以后续再说,同时在写的,讲的不对的地方,欢迎各位大佬指正。