Compare commits
8 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
31c28d8111 | ||
|
|
e3cfc67dbd | ||
|
|
ba8e9f7e18 | ||
|
|
cd9095859e | ||
|
|
97277c5e3e | ||
|
|
ddb82e14ec | ||
|
|
5ccf928be8 | ||
|
|
11df32451a |
3
.gitignore
vendored
3
.gitignore
vendored
@@ -360,4 +360,5 @@ MigrationBackup/
|
||||
.ionide/
|
||||
|
||||
# Fody - auto-generated XML schema
|
||||
FodyWeavers.xsd
|
||||
FodyWeavers.xsd
|
||||
/WechatPCMsgBakTool.csproj
|
||||
|
||||
36
Analyse.xaml
Normal file
36
Analyse.xaml
Normal file
@@ -0,0 +1,36 @@
|
||||
<Window x:Class="WechatPCMsgBakTool.Analyse"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:local="clr-namespace:WechatPCMsgBakTool"
|
||||
mc:Ignorable="d"
|
||||
WindowStartupLocation="CenterScreen"
|
||||
Title="溯雪微信备份工具-分析" Height="450" Width="900">
|
||||
<Grid>
|
||||
<ListView Name="list_msg_group" Margin="41,75,0,19" HorizontalAlignment="Left" Width="420" SelectionChanged="list_msg_group_SelectionChanged">
|
||||
<ListView.View>
|
||||
<GridView>
|
||||
<GridViewColumn Header="昵称" Width="120" DisplayMemberBinding="{Binding NickName}" />
|
||||
<GridViewColumn Header="原始ID" Width="120" DisplayMemberBinding="{Binding UserName}" />
|
||||
<GridViewColumn Header="数量" Width="140" DisplayMemberBinding="{Binding MsgCount}" />
|
||||
</GridView>
|
||||
</ListView.View>
|
||||
</ListView>
|
||||
<Button x:Name="btn_analyse" Content="分析" HorizontalAlignment="Left" Margin="42,43,0,0" VerticalAlignment="Top" Width="72" Click="btn_analyse_Click"/>
|
||||
<Button x:Name="btn_copy_id" Content="复制id" HorizontalAlignment="Left" Margin="366,43,0,0" VerticalAlignment="Top" Width="94" Click="btn_copy_id_Click"/>
|
||||
|
||||
<ListView Name="list_msg_search" Margin="500,75,0,19" HorizontalAlignment="Left" Width="350">
|
||||
<ListView.View>
|
||||
<GridView>
|
||||
<GridViewColumn Header="原始ID" Width="120" DisplayMemberBinding="{Binding StrTalker}" />
|
||||
<GridViewColumn Header="消息" Width="200" DisplayMemberBinding="{Binding StrContent}" />
|
||||
</GridView>
|
||||
</ListView.View>
|
||||
</ListView>
|
||||
<TextBox Name="txt_search_text" HorizontalAlignment="Left" Margin="574,43,0,0" TextWrapping="Wrap" Text="" VerticalAlignment="Top" Width="120" Height="20"/>
|
||||
<Label Content="消息搜索:" HorizontalAlignment="Left" Margin="504,41,0,0" VerticalAlignment="Top"/>
|
||||
<Button x:Name="btn_search" Content="搜索" HorizontalAlignment="Left" Margin="708,43,0,0" VerticalAlignment="Top" Width="65" Click="btn_search_Click" />
|
||||
<Button x:Name="btn_search_copy_id" Content="复制id" HorizontalAlignment="Left" Margin="784,43,0,0" VerticalAlignment="Top" Width="65" Click="btn_search_copy_id_Click" />
|
||||
</Grid>
|
||||
</Window>
|
||||
106
Analyse.xaml.cs
Normal file
106
Analyse.xaml.cs
Normal file
@@ -0,0 +1,106 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Data;
|
||||
using System.Windows.Documents;
|
||||
using System.Windows.Input;
|
||||
using System.Windows.Media;
|
||||
using System.Windows.Media.Imaging;
|
||||
using System.Windows.Shapes;
|
||||
using WechatPCMsgBakTool.Model;
|
||||
|
||||
namespace WechatPCMsgBakTool
|
||||
{
|
||||
/// <summary>
|
||||
/// Analyse.xaml 的交互逻辑
|
||||
/// </summary>
|
||||
public partial class Analyse : Window
|
||||
{
|
||||
private UserBakConfig UserBakConfig;
|
||||
private WXUserReader UserReader;
|
||||
public Analyse(UserBakConfig userBakConfig,WXUserReader reader)
|
||||
{
|
||||
UserBakConfig = userBakConfig;
|
||||
UserReader = reader;
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
private void btn_analyse_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
List<WXContact>? contacts = UserReader.GetWXContacts();
|
||||
List<WXMsgGroup> list = UserReader.GetWXMsgGroup().OrderByDescending(x => x.MsgCount).ToList();
|
||||
if(contacts == null)
|
||||
contacts = new List<WXContact>();
|
||||
|
||||
foreach (WXMsgGroup item in list)
|
||||
{
|
||||
WXContact? contact = contacts.Find(x => x.UserName == item.UserName);
|
||||
if (contact != null)
|
||||
{
|
||||
item.NickName = contact.NickName;
|
||||
}
|
||||
else
|
||||
item.NickName = "已删除人员:" + item.UserName;
|
||||
}
|
||||
list_msg_group.ItemsSource = list;
|
||||
}
|
||||
|
||||
private void btn_copy_id_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
WXMsgGroup? msgGroup = list_msg_group.SelectedItem as WXMsgGroup;
|
||||
if(msgGroup == null)
|
||||
{
|
||||
MessageBox.Show("请先选择数据");
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
Clipboard.SetDataObject(msgGroup.UserName);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private void list_msg_group_SelectionChanged(object sender, SelectionChangedEventArgs e)
|
||||
{
|
||||
WXMsgGroup? wXMsgGroup = list_msg_group.SelectedItem as WXMsgGroup;
|
||||
if(wXMsgGroup != null)
|
||||
{
|
||||
List<WXMsg>? wXMsgs = UserReader.GetWXMsgs(wXMsgGroup.UserName);
|
||||
if(wXMsgs != null)
|
||||
{
|
||||
wXMsgs = wXMsgs.OrderByDescending(x => x.CreateTime).ToList();
|
||||
list_msg_search.ItemsSource = wXMsgs;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
private void btn_search_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
List<WXMsg>? wXMsgs = UserReader.GetWXMsgs("",txt_search_text.Text);
|
||||
if (wXMsgs != null)
|
||||
{
|
||||
wXMsgs = wXMsgs.OrderByDescending(x => x.CreateTime).ToList();
|
||||
list_msg_search.ItemsSource = wXMsgs;
|
||||
}
|
||||
}
|
||||
|
||||
private void btn_search_copy_id_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
WXMsg? wxMsg = list_msg_search.SelectedItem as WXMsg;
|
||||
if (wxMsg == null)
|
||||
{
|
||||
MessageBox.Show("请先选择数据");
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
Clipboard.SetDataObject(wxMsg.StrTalker);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -9,6 +9,7 @@ using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
using System.Text.Json.Serialization;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
using WechatPCMsgBakTool.Model;
|
||||
|
||||
namespace WechatPCMsgBakTool.Helpers
|
||||
@@ -22,7 +23,7 @@ namespace WechatPCMsgBakTool.Helpers
|
||||
const int DEFAULT_ITER = 64000;
|
||||
const int DEFAULT_PAGESIZE = 4096; //4048数据 + 16IV + 20 HMAC + 12
|
||||
const string SQLITE_HEADER = "SQLite format 3";
|
||||
public static byte[]? GetWechatKey()
|
||||
public static byte[]? GetWechatKey(bool mem_find_key,string account)
|
||||
{
|
||||
Process? process = ProcessHelper.GetProcess("WeChat");
|
||||
if (process == null)
|
||||
@@ -40,28 +41,56 @@ namespace WechatPCMsgBakTool.Helpers
|
||||
return null;
|
||||
}
|
||||
|
||||
string json = File.ReadAllText("version.json");
|
||||
List<VersionInfo>? info = JsonConvert.DeserializeObject<List<VersionInfo>?>(json);
|
||||
if (info == null)
|
||||
return null;
|
||||
if (info.Count == 0)
|
||||
return null;
|
||||
|
||||
|
||||
VersionInfo? cur = info.Find(x => x.Version == version);
|
||||
if (cur == null)
|
||||
return null;
|
||||
|
||||
//这里加的是版本偏移量,兼容不同版本把这个加给改了
|
||||
long baseAddress = (long)module.BaseAddress + cur.BaseAddr;
|
||||
byte[]? bytes = ProcessHelper.ReadMemoryDate(process.Handle, (IntPtr)baseAddress, 8);
|
||||
if (bytes != null)
|
||||
if (!mem_find_key)
|
||||
{
|
||||
IntPtr baseAddress2 = (IntPtr)(((long)bytes[7] << 56) + ((long)bytes[6] << 48) + ((long)bytes[5] << 40) + ((long)bytes[4] << 32) + ((long)bytes[3] << 24) + ((long)bytes[2] << 16) + ((long)bytes[1] << 8) + (long)bytes[0]);
|
||||
byte[]? twoGet = ProcessHelper.ReadMemoryDate(process.Handle, baseAddress2, 32);
|
||||
if (twoGet != null)
|
||||
List<VersionInfo>? info = null;
|
||||
string json = File.ReadAllText("version.json");
|
||||
info = JsonConvert.DeserializeObject<List<VersionInfo>?>(json);
|
||||
if (info == null)
|
||||
return null;
|
||||
if (info.Count == 0)
|
||||
return null;
|
||||
|
||||
VersionInfo? cur = info.Find(x => x.Version == version);
|
||||
if (cur == null)
|
||||
return null;
|
||||
//这里加的是版本偏移量,兼容不同版本把这个加给改了
|
||||
long baseAddress = (long)module.BaseAddress + cur.BaseAddr;
|
||||
byte[]? bytes = ProcessHelper.ReadMemoryDate(process.Handle, (IntPtr)baseAddress, 8);
|
||||
if (bytes != null)
|
||||
{
|
||||
string key = BytesToHex(twoGet);
|
||||
return twoGet;
|
||||
IntPtr baseAddress2 = (IntPtr)(((long)bytes[7] << 56) + ((long)bytes[6] << 48) + ((long)bytes[5] << 40) + ((long)bytes[4] << 32) + ((long)bytes[3] << 24) + ((long)bytes[2] << 16) + ((long)bytes[1] << 8) + (long)bytes[0]);
|
||||
byte[]? twoGet = ProcessHelper.ReadMemoryDate(process.Handle, baseAddress2, 32);
|
||||
if (twoGet != null)
|
||||
{
|
||||
string key = BytesToHex(twoGet);
|
||||
return twoGet;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
List<int> read = ProcessHelper.FindProcessMemory(process.Handle, module, account);
|
||||
if(read.Count >= 2)
|
||||
{
|
||||
byte[] buffer = new byte[8];
|
||||
int key_offset = read[1] - 64;
|
||||
if (ProcessHelper.ReadProcessMemory(process.Handle, module.BaseAddress + key_offset, buffer, buffer.Length, out _))
|
||||
{
|
||||
ulong addr = BitConverter.ToUInt64(buffer, 0);
|
||||
|
||||
byte[] key_bytes = new byte[32];
|
||||
if(ProcessHelper.ReadProcessMemory(process.Handle, (IntPtr)addr, key_bytes, key_bytes.Length, out _))
|
||||
{
|
||||
return key_bytes;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
MessageBox.Show("搜索不到微信账号,请确认用户名是否正确,如错误请重新新建工作区,务必确认账号是否正确", "错误");
|
||||
}
|
||||
}
|
||||
return null;
|
||||
|
||||
47
Helpers/DevicePathMapper.cs
Normal file
47
Helpers/DevicePathMapper.cs
Normal file
@@ -0,0 +1,47 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace WechatPCMsgBakTool.Helpers
|
||||
{
|
||||
public static class DevicePathMapper
|
||||
{
|
||||
[DllImport("Kernel32.dll", CharSet = CharSet.Unicode)]
|
||||
private static extern uint QueryDosDevice([In] string lpDeviceName, [Out] StringBuilder lpTargetPath, [In] int ucchMax);
|
||||
|
||||
public static string FromDevicePath(string devicePath)
|
||||
{
|
||||
var drive = Array.Find(DriveInfo.GetDrives(), d => devicePath.StartsWith(d.GetDevicePath(), StringComparison.InvariantCultureIgnoreCase));
|
||||
return drive != null ?
|
||||
devicePath.ReplaceFirst(drive.GetDevicePath(), drive.GetDriveLetter()) :
|
||||
null;
|
||||
}
|
||||
|
||||
private static string GetDevicePath(this DriveInfo driveInfo)
|
||||
{
|
||||
var devicePathBuilder = new StringBuilder(128);
|
||||
return QueryDosDevice(driveInfo.GetDriveLetter(), devicePathBuilder, devicePathBuilder.Capacity + 1) != 0 ?
|
||||
devicePathBuilder.ToString() :
|
||||
null;
|
||||
}
|
||||
|
||||
private static string GetDriveLetter(this DriveInfo driveInfo)
|
||||
{
|
||||
return driveInfo.Name.Substring(0, 2);
|
||||
}
|
||||
|
||||
private static string ReplaceFirst(this string text, string search, string replace)
|
||||
{
|
||||
int pos = text.IndexOf(search);
|
||||
if (pos < 0)
|
||||
{
|
||||
return text;
|
||||
}
|
||||
return text.Substring(0, pos) + replace + text.Substring(pos + search.Length);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -7,7 +7,7 @@ namespace WechatPCMsgBakTool.Helpers
|
||||
{
|
||||
public class OpenSSLInterop
|
||||
{
|
||||
private const string Lib = "libcrypto-1_1-x64";
|
||||
private const string Lib = "libcrypto-1_1";
|
||||
internal static unsafe int HMAC_Init(out HMAC_CTX ctx, byte[] key, int key_len, IntPtr md)
|
||||
{
|
||||
return HMAC_InitNative(out ctx, key, key_len, md);
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Net.NetworkInformation;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
@@ -11,7 +12,12 @@ namespace WechatPCMsgBakTool.Helpers
|
||||
{
|
||||
public class ProcessHelper
|
||||
{
|
||||
public static Process? GetProcess(string ProcessName)
|
||||
private const uint STATUS_INFO_LENGTH_MISMATCH = 0xC0000004;
|
||||
private const int DUPLICATE_SAME_ACCESS = 0x2;
|
||||
|
||||
private const int CNST_SYSTEM_HANDLE_INFORMATION = 0x10;
|
||||
|
||||
public static Process GetProcess(string ProcessName)
|
||||
{
|
||||
Process[] processes = Process.GetProcessesByName(ProcessName);
|
||||
if (processes.Length == 0)
|
||||
@@ -31,7 +37,6 @@ namespace WechatPCMsgBakTool.Helpers
|
||||
else
|
||||
return processes[0];
|
||||
}
|
||||
|
||||
public static ProcessModule? FindProcessModule(int ProcessId, string ModuleName)
|
||||
{
|
||||
Process process = Process.GetProcessById(ProcessId);
|
||||
@@ -43,16 +48,211 @@ namespace WechatPCMsgBakTool.Helpers
|
||||
return null;
|
||||
}
|
||||
|
||||
public static List<int> FindProcessMemory(IntPtr processHandle, ProcessModule module, string content)
|
||||
{
|
||||
byte[] buffer = new byte[module.ModuleMemorySize];
|
||||
byte[] search = Encoding.ASCII.GetBytes(content);
|
||||
// 逐页读取数据
|
||||
|
||||
List<int> offset = new List<int>();
|
||||
int readBytes;
|
||||
bool success = ReadProcessMemory(processHandle, module.BaseAddress, buffer, buffer.Length,out readBytes);
|
||||
|
||||
if (!success || readBytes == 0)
|
||||
{
|
||||
int error = Marshal.GetLastWin32Error();
|
||||
Console.WriteLine($"ReadProcessMemory failed. GetLastError: {error}");
|
||||
}
|
||||
else
|
||||
{
|
||||
for (int i = 0; i < buffer.Length; i++)
|
||||
{
|
||||
if (buffer[i] == search[0])
|
||||
{
|
||||
for (int s = 1; s < search.Length; s++)
|
||||
{
|
||||
if (buffer[i + s] != search[s])
|
||||
break;
|
||||
if (s == search.Length - 1)
|
||||
offset.Add(i);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
return offset;
|
||||
}
|
||||
|
||||
public static List<SYSTEM_HANDLE_INFORMATION> GetHandles(Process process)
|
||||
{
|
||||
List<SYSTEM_HANDLE_INFORMATION> aHandles = new List<SYSTEM_HANDLE_INFORMATION>();
|
||||
int handle_info_size = Marshal.SizeOf(new SYSTEM_HANDLE_INFORMATION()) * 20000;
|
||||
IntPtr ptrHandleData = IntPtr.Zero;
|
||||
try
|
||||
{
|
||||
ptrHandleData = Marshal.AllocHGlobal(handle_info_size);
|
||||
int nLength = 0;
|
||||
|
||||
while (NtQuerySystemInformation(CNST_SYSTEM_HANDLE_INFORMATION, ptrHandleData, handle_info_size, ref nLength) == STATUS_INFO_LENGTH_MISMATCH)
|
||||
{
|
||||
handle_info_size = nLength;
|
||||
Marshal.FreeHGlobal(ptrHandleData);
|
||||
ptrHandleData = Marshal.AllocHGlobal(nLength);
|
||||
}
|
||||
|
||||
long handle_count = Marshal.ReadIntPtr(ptrHandleData).ToInt64();
|
||||
IntPtr ptrHandleItem = ptrHandleData + Marshal.SizeOf(ptrHandleData);
|
||||
|
||||
for (long lIndex = 0; lIndex < handle_count; lIndex++)
|
||||
{
|
||||
SYSTEM_HANDLE_INFORMATION? oSystemHandleInfo = new SYSTEM_HANDLE_INFORMATION();
|
||||
oSystemHandleInfo = Marshal.PtrToStructure(ptrHandleItem, oSystemHandleInfo.GetType()) as SYSTEM_HANDLE_INFORMATION?;
|
||||
if (oSystemHandleInfo == null)
|
||||
throw new Exception("获取SYSTEM_HANDLE_INFORMATION失败");
|
||||
ptrHandleItem += Marshal.SizeOf(new SYSTEM_HANDLE_INFORMATION());
|
||||
if (oSystemHandleInfo.Value.ProcessID != process.Id) { continue; }
|
||||
aHandles.Add(oSystemHandleInfo.Value);
|
||||
}
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
throw;
|
||||
}
|
||||
finally
|
||||
{
|
||||
Marshal.FreeHGlobal(ptrHandleData);
|
||||
}
|
||||
return aHandles;
|
||||
}
|
||||
public static string FindHandleName(SYSTEM_HANDLE_INFORMATION systemHandleInformation, Process process)
|
||||
{
|
||||
IntPtr ipHandle = IntPtr.Zero;
|
||||
IntPtr openProcessHandle = IntPtr.Zero;
|
||||
IntPtr hObjectName = IntPtr.Zero;
|
||||
try
|
||||
{
|
||||
PROCESS_ACCESS_FLAGS flags = PROCESS_ACCESS_FLAGS.DupHandle | PROCESS_ACCESS_FLAGS.VMRead;
|
||||
openProcessHandle = OpenProcess(flags, false, process.Id);
|
||||
// 通过 DuplicateHandle 访问句柄
|
||||
if (!DuplicateHandle(openProcessHandle, new IntPtr(systemHandleInformation.Handle), GetCurrentProcess(), out ipHandle, 0, false, DUPLICATE_SAME_ACCESS))
|
||||
{
|
||||
return "";
|
||||
}
|
||||
|
||||
int nLength = 0;
|
||||
hObjectName = Marshal.AllocHGlobal(256 * 1024);
|
||||
|
||||
// 查询句柄名称
|
||||
while ((uint)(NtQueryObject(ipHandle, (int)OBJECT_INFORMATION_CLASS.ObjectNameInformation, hObjectName, nLength, ref nLength)) == STATUS_INFO_LENGTH_MISMATCH)
|
||||
{
|
||||
Marshal.FreeHGlobal(hObjectName);
|
||||
if (nLength == 0)
|
||||
{
|
||||
Console.WriteLine("Length returned at zero!");
|
||||
return "";
|
||||
}
|
||||
hObjectName = Marshal.AllocHGlobal(nLength);
|
||||
}
|
||||
OBJECT_NAME_INFORMATION? objObjectName = new OBJECT_NAME_INFORMATION();
|
||||
objObjectName = Marshal.PtrToStructure(hObjectName, objObjectName.GetType()) as OBJECT_NAME_INFORMATION?;
|
||||
if (objObjectName == null)
|
||||
return "";
|
||||
if (objObjectName.Value.Name.Buffer != IntPtr.Zero)
|
||||
{
|
||||
string? strObjectName = Marshal.PtrToStringUni(objObjectName.Value.Name.Buffer);
|
||||
if (strObjectName != null)
|
||||
return strObjectName;
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine(ex.Message);
|
||||
}
|
||||
finally
|
||||
{
|
||||
Marshal.FreeHGlobal(hObjectName);
|
||||
CloseHandle(ipHandle);
|
||||
CloseHandle(openProcessHandle);
|
||||
}
|
||||
return "";
|
||||
}
|
||||
// 这里开始下面是对Windows API引用声明
|
||||
public static byte[]? ReadMemoryDate(IntPtr hProcess, IntPtr lpBaseAddress, int nSize = 100)
|
||||
{
|
||||
byte[] array = new byte[nSize];
|
||||
if (ReadProcessMemory(hProcess, lpBaseAddress, array, nSize, 0) == 0)
|
||||
int readByte;
|
||||
if (ReadProcessMemory(hProcess, lpBaseAddress, array, nSize, out readByte))
|
||||
return null;
|
||||
else
|
||||
return array;
|
||||
}
|
||||
|
||||
[DllImport("kernel32.dll", SetLastError = true)]
|
||||
public static extern bool ReadProcessMemory(IntPtr hProcess, IntPtr lpBaseAddress, [Out] byte[] lpBuffer, int nSize, out int lpNumberOfBytesRead);
|
||||
[DllImport("ntdll.dll")]
|
||||
private static extern uint NtQuerySystemInformation(int SystemInformationClass, IntPtr SystemInformation, int SystemInformationLength, ref int returnLength);
|
||||
|
||||
[DllImport("kernel32.dll")]
|
||||
public static extern int ReadProcessMemory(IntPtr hProcess, IntPtr lpBaseAddress, byte[] lpBuffer, int nSize, int lpNumberOfBytesRead);
|
||||
private static extern IntPtr OpenProcess(PROCESS_ACCESS_FLAGS dwDesiredAccess, [MarshalAs(UnmanagedType.Bool)] bool bInheritHandle, int dwProcessId);
|
||||
|
||||
[DllImport("kernel32.dll", SetLastError = true)]
|
||||
[return: MarshalAs(UnmanagedType.Bool)]
|
||||
private static extern bool DuplicateHandle(IntPtr hSourceProcessHandle, IntPtr hSourceHandle, IntPtr hTargetProcessHandle, out IntPtr lpTargetHandle, uint dwDesiredAccess, [MarshalAs(UnmanagedType.Bool)] bool bInheritHandle, uint dwOptions);
|
||||
|
||||
[DllImport("kernel32.dll")]
|
||||
private static extern IntPtr GetCurrentProcess();
|
||||
|
||||
[DllImport("ntdll.dll")]
|
||||
private static extern int NtQueryObject(IntPtr ObjectHandle, int ObjectInformationClass, IntPtr ObjectInformation, int ObjectInformationLength, ref int returnLength);
|
||||
|
||||
[DllImport("kernel32.dll")]
|
||||
private static extern bool CloseHandle(IntPtr hObject);
|
||||
|
||||
[StructLayout(LayoutKind.Sequential, Pack = 1)]
|
||||
public struct SYSTEM_HANDLE_INFORMATION
|
||||
{ // Information Class 16
|
||||
public ushort ProcessID;
|
||||
public ushort CreatorBackTrackIndex;
|
||||
public byte ObjectType;
|
||||
public byte HandleAttribute;
|
||||
public ushort Handle;
|
||||
public IntPtr Object_Pointer;
|
||||
public IntPtr AccessMask;
|
||||
}
|
||||
private enum OBJECT_INFORMATION_CLASS : int
|
||||
{
|
||||
ObjectBasicInformation = 0,
|
||||
ObjectNameInformation = 1,
|
||||
ObjectTypeInformation = 2,
|
||||
ObjectAllTypesInformation = 3,
|
||||
ObjectHandleInformation = 4
|
||||
}
|
||||
[StructLayout(LayoutKind.Sequential, Pack = 1)]
|
||||
private struct OBJECT_NAME_INFORMATION
|
||||
{
|
||||
public UNICODE_STRING Name;
|
||||
}
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
private struct UNICODE_STRING
|
||||
{
|
||||
public ushort Length;
|
||||
public ushort MaximumLength;
|
||||
public IntPtr Buffer;
|
||||
}
|
||||
[Flags]
|
||||
private enum PROCESS_ACCESS_FLAGS : uint
|
||||
{
|
||||
All = 0x001F0FFF,
|
||||
Terminate = 0x00000001,
|
||||
CreateThread = 0x00000002,
|
||||
VMOperation = 0x00000008,
|
||||
VMRead = 0x00000010,
|
||||
VMWrite = 0x00000020,
|
||||
DupHandle = 0x00000040,
|
||||
SetInformation = 0x00000200,
|
||||
QueryInformation = 0x00000400,
|
||||
Synchronize = 0x00100000
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
using System;
|
||||
using K4os.Compression.LZ4.Encoders;
|
||||
using K4os.Compression.LZ4;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
@@ -6,6 +8,7 @@ using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using WechatPCMsgBakTool.Interface;
|
||||
using WechatPCMsgBakTool.Model;
|
||||
using System.Xml;
|
||||
|
||||
namespace WechatPCMsgBakTool
|
||||
{
|
||||
@@ -86,6 +89,59 @@ namespace WechatPCMsgBakTool
|
||||
}
|
||||
HtmlBody += string.Format("<p class=\"content\"><video controls style=\"max-height:300px;max-width:300px;\"><source src=\"{0}\" type=\"video/mp4\" /></video></p></div>", path);
|
||||
}
|
||||
else if(msg.Type== 49)
|
||||
{
|
||||
using (var decoder = LZ4Decoder.Create(true, 64))
|
||||
{
|
||||
byte[] target = new byte[10240];
|
||||
int res = 0;
|
||||
if(msg.CompressContent != null)
|
||||
res = LZ4Codec.Decode(msg.CompressContent, 0, msg.CompressContent.Length, target, 0, target.Length);
|
||||
|
||||
byte[] data = target.Skip(0).Take(res).ToArray();
|
||||
string xml = Encoding.UTF8.GetString(data);
|
||||
if (!string.IsNullOrEmpty(xml))
|
||||
{
|
||||
xml = xml.Replace("\n", "");
|
||||
XmlDocument xmlObj = new XmlDocument();
|
||||
xmlObj.LoadXml(xml);
|
||||
if(xmlObj.DocumentElement != null)
|
||||
{
|
||||
string title = "";
|
||||
string appName = "";
|
||||
string url = "";
|
||||
XmlNodeList? findNode = xmlObj.DocumentElement.SelectNodes("/msg/appmsg/title");
|
||||
if(findNode != null)
|
||||
{
|
||||
if(findNode.Count > 0)
|
||||
{
|
||||
title = findNode[0]!.InnerText;
|
||||
}
|
||||
}
|
||||
findNode = xmlObj.DocumentElement.SelectNodes("/msg/appmsg/sourcedisplayname");
|
||||
if (findNode != null)
|
||||
{
|
||||
if (findNode.Count > 0)
|
||||
{
|
||||
appName = findNode[0]!.InnerText;
|
||||
}
|
||||
}
|
||||
findNode = xmlObj.DocumentElement.SelectNodes("/msg/appmsg/url");
|
||||
if (findNode != null)
|
||||
{
|
||||
if (findNode.Count > 0)
|
||||
{
|
||||
url = findNode[0]!.InnerText;
|
||||
}
|
||||
}
|
||||
HtmlBody += string.Format("<p class=\"content\">{0}|{1}</p><p><a href=\"{2}\">点击访问</a></p></div>", appName, title, url);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
else if (msg.Type == 34)
|
||||
{
|
||||
string? path = reader.GetAttachment(WXMsgType.Audio, msg);
|
||||
|
||||
14
Main.xaml
14
Main.xaml
@@ -18,9 +18,9 @@
|
||||
</ListView>
|
||||
<Label Content="工作区:" HorizontalAlignment="Left" Margin="15,15,0,0" VerticalAlignment="Top" Height="25" Width="58"/>
|
||||
<Button Content="新增" Width="50" HorizontalAlignment="Left" Margin="194,20,0,0" VerticalAlignment="Top" RenderTransformOrigin="0.343,0.521" Height="19" Click="Button_Click_1"/>
|
||||
<Label Content="用户路径:-" Name="user_path" HorizontalAlignment="Left" Margin="278,50,0,0" VerticalAlignment="Top" Height="25" Width="500"/>
|
||||
<Label Content="用户路径:-" Name="user_path" HorizontalAlignment="Left" Margin="278,68,0,0" VerticalAlignment="Top" Height="25" Width="500"/>
|
||||
<Button Content="解密" IsEnabled="False" Width="50" HorizontalAlignment="Left" Margin="285,20,0,0" VerticalAlignment="Top" RenderTransformOrigin="0.343,0.521" Name="btn_decrypt" Click="btn_decrypt_Click" Height="19"/>
|
||||
<Button Content="读取" IsEnabled="False" Width="50" HorizontalAlignment="Left" Margin="365,20,0,0" VerticalAlignment="Top" RenderTransformOrigin="0.343,0.521" Name="btn_read" Click="btn_read_Click" Height="19" />
|
||||
<Button Content="读取" IsEnabled="False" Width="50" HorizontalAlignment="Left" Margin="285,47,0,0" VerticalAlignment="Top" RenderTransformOrigin="0.343,0.521" Name="btn_read" Click="btn_read_Click" Height="19" />
|
||||
|
||||
<ListView Name="list_sessions" Margin="278,130,0,20" HorizontalAlignment="Left" Width="290" MouseDoubleClick="list_sessions_MouseDoubleClick">
|
||||
<ListView.View>
|
||||
@@ -30,9 +30,13 @@
|
||||
</GridView>
|
||||
</ListView.View>
|
||||
</ListView>
|
||||
<Button Content="导出所选人员聊天记录" HorizontalAlignment="Left" Margin="609,130,0,0" VerticalAlignment="Top" Width="142" Click="Button_Click"/>
|
||||
<Button Content="导出所选人员聊天记录" HorizontalAlignment="Left" Margin="609,130,0,0" VerticalAlignment="Top" Width="140" Click="Button_Click"/>
|
||||
<Label Content="搜索:" HorizontalAlignment="Left" Margin="278,92,0,0" VerticalAlignment="Top"/>
|
||||
<TextBox Name="find_user" HorizontalAlignment="Left" Margin="323,96,0,0" TextWrapping="Wrap" Text="" VerticalAlignment="Top" Width="120" Height="20"/>
|
||||
<Button Name="btn_search" Content="搜索" HorizontalAlignment="Left" Margin="451,96,0,0" VerticalAlignment="Top" Width="43" Click="btn_search_Click"/>
|
||||
<TextBox Name="find_user" HorizontalAlignment="Left" Margin="323,96,0,0" TextWrapping="Wrap" Text="" VerticalAlignment="Top" Width="194" Height="20"/>
|
||||
<Button Name="btn_search" Content="搜索" HorizontalAlignment="Left" Margin="525,96,0,0" VerticalAlignment="Top" Width="43" Click="btn_search_Click"/>
|
||||
<Button Name="btn_analyse" Content="消息分析工具" HorizontalAlignment="Left" Margin="609,160,0,0" VerticalAlignment="Top" Width="140" Click="btn_analyse_Click"/>
|
||||
<CheckBox Name="cb_del_search" Content="已删除人员强制从记录搜索" HorizontalAlignment="Left" Margin="610,99,0,0" VerticalAlignment="Top"/>
|
||||
<RadioButton Name="rb_find_file" GroupName="find_addr_function" Content="使用version.json进行基址查找" HorizontalAlignment="Left" Margin="348,22,0,0" VerticalAlignment="Top" IsChecked="True"/>
|
||||
<RadioButton Name="rb_find_mem" GroupName="find_addr_function" Content="使用推定进行基址查找【推荐】" HorizontalAlignment="Left" Margin="546,22,0,0" VerticalAlignment="Top" Checked="rb_find_mem_Checked"/>
|
||||
</Grid>
|
||||
</Window>
|
||||
|
||||
104
Main.xaml.cs
104
Main.xaml.cs
@@ -1,4 +1,7 @@
|
||||
using Newtonsoft.Json;
|
||||
using K4os.Compression.LZ4;
|
||||
using K4os.Compression.LZ4.Encoders;
|
||||
using K4os.Compression.LZ4.Streams;
|
||||
using Newtonsoft.Json;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
@@ -30,8 +33,19 @@ namespace WechatPCMsgBakTool
|
||||
private ObservableCollection<UserBakConfig> userBakConfigs = new ObservableCollection<UserBakConfig>();
|
||||
public Main()
|
||||
{
|
||||
Application.Current.DispatcherUnhandledException += Current_DispatcherUnhandledException;
|
||||
InitializeComponent();
|
||||
LoadWorkspace();
|
||||
this.Title += $" {Application.ResourceAssembly.GetName().Version}";
|
||||
}
|
||||
|
||||
private void Current_DispatcherUnhandledException(object sender, System.Windows.Threading.DispatcherUnhandledExceptionEventArgs e)
|
||||
{
|
||||
MessageBox.Show("发生了未知错误,记录已写入到根目录err.log,如果可以,欢迎反馈给开发人员,非常感谢", "错误");
|
||||
File.AppendAllText("err.log", "\r\n\r\n\r\n=============================\r\n");
|
||||
File.AppendAllText("err.log", string.Format("异常时间:{0}\r\n", DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")));
|
||||
File.AppendAllText("err.log", e.Exception.ToString());
|
||||
return;
|
||||
}
|
||||
|
||||
private void LoadWorkspace()
|
||||
@@ -47,7 +61,15 @@ namespace WechatPCMsgBakTool
|
||||
if(type == ".json")
|
||||
{
|
||||
string jsonString = File.ReadAllText(file);
|
||||
UserBakConfig? userBakConfig = JsonConvert.DeserializeObject<UserBakConfig>(jsonString);
|
||||
UserBakConfig? userBakConfig = null;
|
||||
try
|
||||
{
|
||||
userBakConfig = JsonConvert.DeserializeObject<UserBakConfig>(jsonString);
|
||||
}
|
||||
catch
|
||||
{
|
||||
MessageBox.Show("读取到异常工作区文件,请确认备份数据是否正常\r\n文件路径:" + file,"错误");
|
||||
}
|
||||
if(userBakConfig != null)
|
||||
{
|
||||
userBakConfigs.Add(userBakConfig);
|
||||
@@ -64,12 +86,36 @@ namespace WechatPCMsgBakTool
|
||||
{
|
||||
if (!CurrentUserBakConfig.Decrypt)
|
||||
{
|
||||
byte[]? key = DecryptionHelper.GetWechatKey();
|
||||
bool? mem_find_key = rb_find_mem.IsChecked;
|
||||
if(mem_find_key == null)
|
||||
{
|
||||
MessageBox.Show("请选择key获取方式");
|
||||
return;
|
||||
}
|
||||
byte[]? key = null;
|
||||
try
|
||||
{
|
||||
key = DecryptionHelper.GetWechatKey((bool)mem_find_key,CurrentUserBakConfig.Account);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
if(ex.Source == "Newtonsoft.Json")
|
||||
{
|
||||
MessageBox.Show("版本文件读取失败,请检查版本文件内容是否为正确的json格式", "错误");
|
||||
}
|
||||
else
|
||||
{
|
||||
MessageBox.Show(ex.Message);
|
||||
}
|
||||
return;
|
||||
}
|
||||
//byte[]? key = DecryptionHelper.GetWechatKey();
|
||||
if (key == null)
|
||||
{
|
||||
MessageBox.Show("微信密钥获取失败,请检查微信是否打开,或者版本不兼容");
|
||||
return;
|
||||
}
|
||||
string key_string = BitConverter.ToString(key, 0).Replace("-", string.Empty).ToLower().ToUpper();
|
||||
string source = Path.Combine(CurrentUserBakConfig.UserWorkspacePath, "OriginalDB");
|
||||
string to = Path.Combine(CurrentUserBakConfig.UserWorkspacePath, "DecDB");
|
||||
try
|
||||
@@ -80,9 +126,9 @@ namespace WechatPCMsgBakTool
|
||||
WXWorkspace.SaveConfig(CurrentUserBakConfig);
|
||||
LoadWorkspace();
|
||||
}
|
||||
catch (Exception)
|
||||
catch (Exception ex)
|
||||
{
|
||||
MessageBox.Show("解密过程出现错误,请检查是否秘钥是否正确,如果有多开微信,请确保当前微信是选择的用户");
|
||||
MessageBox.Show("解密过程出现错误:" + ex.Message);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -137,7 +183,6 @@ namespace WechatPCMsgBakTool
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
IExport export = new HtmlExport();
|
||||
export.InitTemplate(wXContact);
|
||||
export.SetMsg(UserReader, wXContact);
|
||||
@@ -146,7 +191,6 @@ namespace WechatPCMsgBakTool
|
||||
string path = Path.Combine(CurrentUserBakConfig.UserWorkspacePath, wXContact.UserName + ".html");
|
||||
export.Save(path);
|
||||
MessageBox.Show("导出完成");
|
||||
|
||||
}
|
||||
|
||||
private void Button_Click_1(object sender, RoutedEventArgs e)
|
||||
@@ -158,7 +202,7 @@ namespace WechatPCMsgBakTool
|
||||
string path = selectWechat.SelectProcess.DBPath.Replace("\\Msg\\MicroMsg.db", "");
|
||||
try
|
||||
{
|
||||
WXWorkspace wXWorkspace = new WXWorkspace(path);
|
||||
WXWorkspace wXWorkspace = new WXWorkspace(path, selectWechat.SelectProcess.Account);
|
||||
wXWorkspace.MoveDB();
|
||||
MessageBox.Show("创建工作区成功");
|
||||
LoadWorkspace();
|
||||
@@ -167,7 +211,6 @@ namespace WechatPCMsgBakTool
|
||||
{
|
||||
MessageBox.Show("创建工作区失败,请检查路径是否正确");
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -178,7 +221,48 @@ namespace WechatPCMsgBakTool
|
||||
MessageBox.Show("请先读取工作区数据");
|
||||
return;
|
||||
}
|
||||
list_sessions.ItemsSource = UserReader.GetWXContacts(find_user.Text);
|
||||
if(cb_del_search.IsChecked != null)
|
||||
{
|
||||
if (!(bool)cb_del_search.IsChecked)
|
||||
list_sessions.ItemsSource = UserReader.GetWXContacts(find_user.Text);
|
||||
else
|
||||
{
|
||||
List<WXMsg>? wXMsgs = UserReader.GetWXMsgs(find_user.Text);
|
||||
if(wXMsgs != null)
|
||||
{
|
||||
if(wXMsgs.Count > 0)
|
||||
{
|
||||
List<WXContact> wXContacts = new List<WXContact>() { new WXContact() { NickName = wXMsgs[0].StrTalker, UserName = wXMsgs[0].StrTalker } };
|
||||
list_sessions.ItemsSource = wXContacts;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private void btn_analyse_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if(UserReader == null || CurrentUserBakConfig == null)
|
||||
{
|
||||
MessageBox.Show("请先读取数据");
|
||||
return;
|
||||
}
|
||||
Analyse analyse = new Analyse(CurrentUserBakConfig, UserReader);
|
||||
analyse.Show();
|
||||
}
|
||||
|
||||
private void rb_find_mem_Checked(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if(CurrentUserBakConfig!= null)
|
||||
{
|
||||
if (string.IsNullOrEmpty(CurrentUserBakConfig.Account))
|
||||
{
|
||||
MessageBox.Show("使用该功能需要填写用户名,请务必确认用户名已经正确填写,否则请重建工作区");
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@ namespace WechatPCMsgBakTool.Model
|
||||
public string ProcessName { get; set; } = "";
|
||||
public string ProcessId { get; set; } = "";
|
||||
public string DBPath { get; set; } = "";
|
||||
public string Account { get; set; } = "";
|
||||
}
|
||||
public class DBInfo
|
||||
{
|
||||
|
||||
@@ -16,6 +16,7 @@ namespace WechatPCMsgBakTool.Model
|
||||
public string Hash { get; set; } = "";
|
||||
public string NickName { get; set; } = "";
|
||||
public string UserName { get; set; } = "";
|
||||
public string Account { get; set; } = "";
|
||||
|
||||
public event PropertyChangedEventHandler? PropertyChanged;
|
||||
private void OnPropertyChanged(string propertyName)
|
||||
@@ -24,6 +25,16 @@ namespace WechatPCMsgBakTool.Model
|
||||
}
|
||||
}
|
||||
|
||||
public class WXMsgGroup
|
||||
{
|
||||
[Column("StrTalker")]
|
||||
public string UserName { get; set; } = "";
|
||||
|
||||
[Column("MsgCount")]
|
||||
public int MsgCount { get; set; }
|
||||
public string NickName { get; set; } = "";
|
||||
}
|
||||
|
||||
public class WXUserInfo
|
||||
{
|
||||
public string UserName { get; set; } = "";
|
||||
@@ -100,6 +111,8 @@ namespace WechatPCMsgBakTool.Model
|
||||
public string StrTalker { get; set; } = "";
|
||||
[Column("StrContent")]
|
||||
public string StrContent { get; set; } = "";
|
||||
[Column("CompressContent")]
|
||||
public byte[]? CompressContent { get; set; }
|
||||
}
|
||||
|
||||
[Table("Media")]
|
||||
|
||||
52
README.md
52
README.md
@@ -1,29 +1,43 @@
|
||||
# WechatPCMsgBakTool
|
||||
微信PC聊天记录备份工具,仅支持Windows
|
||||
微信PC聊天记录备份工具,仅支持Windows
|
||||
|
||||
- 支持3.9.6.33版本后,若版本更新可在version.json添加版本号和地址即可完成新版本支持
|
||||
- 导出图片、视频、音频
|
||||
- 导出Html文件
|
||||
- 支持3.9.6.33版本后,若版本更新可在version.json添加版本号和地址即可完成新版本支持
|
||||
- 导出图片、视频、音频、分享链接
|
||||
- 导出Html文件
|
||||
- 支持聊天频率分析,全消息库内容搜索
|
||||
|
||||
本项目仅做学习使用,供个人备份自己的微信,请勿做其他用途使用。
|
||||
**本项目仅做学习使用,主要供个人备份自己的微信记录,请勿用于非法用途。**
|
||||
|
||||
本项目严禁商用。
|
||||
**本项目严禁商用**
|
||||
|
||||
#### 使用
|
||||
<p>1.打开微信,并登录。</p>
|
||||
<p>2.在工作区上方点击新增,选择要创建的工作区微信</p>
|
||||
<p>3.选中刚刚创建的工作区,点击解密。(如当前多开微信,请选择对应的微信进行解谜)</p>
|
||||
<p>4.选中刚刚创建的工作区,点击读取</p>
|
||||
<p><b>尽情使用吧!</b></p>
|
||||
如果有什么好的建议或意见,或者遇到什么问题,欢迎提issue,看到会回。
|
||||
|
||||
#### 注意
|
||||
<p>如果使用过程中发生崩溃,请删除工作区试一下,工作区即根据用户名在运行目录下生成的md5文件夹。</p>
|
||||
<p>已解谜的工作区可以直接读取</p>
|
||||
<p>再次强调,仅供个人备份自己微信使用 </p>
|
||||
|
||||
#### 参考/引用
|
||||
都是站在大佬们的肩膀上完成的项目,本项目 参考/引用 了以下 项目/文章 内代码。
|
||||
> [!NOTE]
|
||||
> 反馈群:815054692<br/>
|
||||
> 如果觉得不错,欢迎右上角点个star!这是对作者的鼓励,谢谢!
|
||||
<br/>
|
||||
|
||||
### 使用
|
||||
<p>1.打开微信,并登录。</p>
|
||||
<p>2.在工作区上方点击新增,选择要创建的工作区微信</p>
|
||||
<p>3.如同时运行多个微信,请选择微信,请注意通过路径进行区别</p>
|
||||
<p>4.选中刚刚创建的工作区,点击解密。(如当前多开微信,请选择对应的微信进行解密)</p>
|
||||
<p>5.选中刚刚创建的工作区,点击读取</p>
|
||||
<p><b>尽情使用吧!</b></p>
|
||||
<br/>
|
||||
|
||||
### 注意
|
||||
<p>本项目基于.NET开发,需要安装.NET Desktop Runtime,如未安装,双击EXE时会提示。</p>
|
||||
<p>如果使用过程中发生崩溃,请删除工作区试一下,工作区即根据用户名在运行目录下生成的md5文件夹。</p>
|
||||
<p>已解密的工作区可以直接读取。</p>
|
||||
<p>再次强调,主要用于个人备份自己微信使用,请勿用于非法用途,严禁商用!</p>
|
||||
<br/>
|
||||
|
||||
### 参考/引用
|
||||
都是站在大佬们的肩膀上完成的项目,本项目 参考/引用 了以下 项目/文章 内代码。
|
||||
##### [Mr0x01/WXDBDecrypt.NET](https://github.com/Mr0x01/WXDBDecrypt.NET)
|
||||
##### [AdminTest0/SharpWxDump](https://github.com/AdminTest0/SharpWxDump)
|
||||
##### [kn007/silk-v3-decoder](https://github.com/kn007/silk-v3-decoder)
|
||||
##### [吾爱破解chenhahacjl/微信 DAT 图片解密 (C#)](https://www.52pojie.cn/forum.php?mod=viewthread&tid=1507922)
|
||||
##### [吾爱破解chenhahacjl/微信 DAT 图片解密 (C#)](https://www.52pojie.cn/forum.php?mod=viewthread&tid=1507922)
|
||||
##### [huiyadanli/RevokeMsgPatcher](https://github.com/huiyadanli/RevokeMsgPatcher)
|
||||
|
||||
@@ -6,10 +6,10 @@
|
||||
xmlns:local="clr-namespace:WechatPCMsgBakTool"
|
||||
mc:Ignorable="d"
|
||||
WindowStartupLocation="CenterScreen"
|
||||
Title="选择微信" Height="300" Width="600">
|
||||
Title="选择微信" Height="310" Width="600">
|
||||
<Grid>
|
||||
<Label Content="请选择您要打开的微信:" HorizontalAlignment="Left" Margin="29,27,0,0" VerticalAlignment="Top"/>
|
||||
<ListView Name="list_process" Margin="32,55,32,67" SelectionChanged="list_process_SelectionChanged">
|
||||
<ListView Name="list_process" Margin="32,55,32,110" SelectionChanged="list_process_SelectionChanged" >
|
||||
<ListView.View>
|
||||
<GridView>
|
||||
<GridViewColumn Header="进程名" Width="80" DisplayMemberBinding="{Binding ProcessName}" />
|
||||
@@ -18,7 +18,10 @@
|
||||
</GridView>
|
||||
</ListView.View>
|
||||
</ListView>
|
||||
<Button Name="btn_close" Content="确定并返回" HorizontalAlignment="Left" Margin="240,231,0,0" VerticalAlignment="Top" Width="97" Click="btn_close_Click"/>
|
||||
<Button Name="btn_close" Content="确定并返回" HorizontalAlignment="Left" Margin="241,245,0,0" VerticalAlignment="Top" Width="97" Click="btn_close_Click"/>
|
||||
<Label Content="用户名:" HorizontalAlignment="Left" Margin="34,190,0,0" VerticalAlignment="Top"/>
|
||||
<TextBox Name="txt_username" HorizontalAlignment="Left" Margin="95,195,0,0" TextWrapping="Wrap" Text="" VerticalAlignment="Top" Width="297"/>
|
||||
<Label Content="注意确认用户名是否正确,如修改过用户名,请自行填写!如果需要使用推定方式获取Key必须正确!" HorizontalAlignment="Left" Margin="35,215,0,0" VerticalAlignment="Top" FontWeight="Bold"/>
|
||||
|
||||
</Grid>
|
||||
</Window>
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Management;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading.Tasks;
|
||||
@@ -13,6 +16,8 @@ using System.Windows.Input;
|
||||
using System.Windows.Media;
|
||||
using System.Windows.Media.Imaging;
|
||||
using System.Windows.Shapes;
|
||||
using System.Xml.Linq;
|
||||
using WechatPCMsgBakTool.Helpers;
|
||||
using WechatPCMsgBakTool.Model;
|
||||
|
||||
namespace WechatPCMsgBakTool
|
||||
@@ -27,7 +32,39 @@ namespace WechatPCMsgBakTool
|
||||
public SelectWechat()
|
||||
{
|
||||
InitializeComponent();
|
||||
GetWechatProcess();
|
||||
//GetWechatProcess();
|
||||
GetWechatProcessInfos();
|
||||
list_process.ItemsSource = processInfos;
|
||||
}
|
||||
|
||||
private void GetWechatProcessInfos()
|
||||
{
|
||||
processInfos.Clear();
|
||||
Process[] processes = Process.GetProcessesByName("wechat");
|
||||
foreach (Process p in processes)
|
||||
{
|
||||
var h_list = ProcessHelper.GetHandles(p);
|
||||
foreach (var h in h_list)
|
||||
{
|
||||
string name = ProcessHelper.FindHandleName(h, p);
|
||||
if (name != "")
|
||||
{
|
||||
// 预留handle log
|
||||
if (File.Exists("handle.log"))
|
||||
{
|
||||
File.AppendAllText("handle.log", string.Format("{0}|{1}|{2}|{3}\n", p.Id, h.ObjectType, h.Handle, name));
|
||||
}
|
||||
if (name.Contains("\\MicroMsg.db") && name.Substring(name.Length - 3, 3) == ".db")
|
||||
{
|
||||
ProcessInfo info = new ProcessInfo();
|
||||
info.ProcessId = p.Id.ToString();
|
||||
info.ProcessName = p.ProcessName;
|
||||
info.DBPath = DevicePathMapper.FromDevicePath(name);
|
||||
processInfos.Add(info);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void GetWechatProcess()
|
||||
@@ -39,7 +76,17 @@ namespace WechatPCMsgBakTool
|
||||
p.StartInfo.CreateNoWindow = true;
|
||||
p.StartInfo.RedirectStandardOutput = true;
|
||||
p.Start();
|
||||
|
||||
string i = p.StandardOutput.ReadToEnd();
|
||||
if (i.Contains("SYSINTERNALS SOFTWARE LICENSE TERMS"))
|
||||
{
|
||||
MessageBox.Show("请先同意Handle64的使用协议,同意后关闭弹窗重新打开新增工作区即可");
|
||||
Process p1 = new Process();
|
||||
p1.StartInfo.FileName = "tools/handle64.exe";
|
||||
p1.StartInfo.Arguments = "-p wechat.exe";
|
||||
p1.Start();
|
||||
}
|
||||
|
||||
string[] lines = i.Split(new string[] { "\r\n" }, StringSplitOptions.None);
|
||||
bool hitFind = false;
|
||||
ProcessInfo processInfo = new ProcessInfo();
|
||||
@@ -75,10 +122,21 @@ namespace WechatPCMsgBakTool
|
||||
private void list_process_SelectionChanged(object sender, SelectionChangedEventArgs e)
|
||||
{
|
||||
SelectProcess = list_process.SelectedItem as ProcessInfo;
|
||||
if(SelectProcess != null)
|
||||
{
|
||||
string[] name_raw = SelectProcess.DBPath.Split("\\");
|
||||
txt_username.Text = name_raw[name_raw.Length - 3];
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private void btn_close_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (SelectProcess != null)
|
||||
{
|
||||
SelectProcess.Account = txt_username.Text;
|
||||
}
|
||||
Close();
|
||||
}
|
||||
}
|
||||
|
||||
15
Tools.xaml
Normal file
15
Tools.xaml
Normal file
@@ -0,0 +1,15 @@
|
||||
<Window x:Class="WechatPCMsgBakTool.Tools"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:local="clr-namespace:WechatPCMsgBakTool"
|
||||
mc:Ignorable="d"
|
||||
Title="资源回退工具" Height="450" Width="800" WindowStartupLocation="CenterScreen">
|
||||
<Grid>
|
||||
<Button Name="back_video_file" Content="回退视频文件" HorizontalAlignment="Left" Margin="191,39,0,0" VerticalAlignment="Top" Height="28" Width="96" Click="back_video_file_Click"/>
|
||||
<TextBox Name="txt_log" VerticalScrollBarVisibility="Auto" Margin="40,88,40,31" TextWrapping="Wrap" Text="" AcceptsReturn="True" IsReadOnly="True"/>
|
||||
<ComboBox Name="list_workspace" DisplayMemberPath="UserName" HorizontalAlignment="Left" Margin="40,43,0,0" VerticalAlignment="Top" Width="120"/>
|
||||
|
||||
</Grid>
|
||||
</Window>
|
||||
167
Tools.xaml.cs
Normal file
167
Tools.xaml.cs
Normal file
@@ -0,0 +1,167 @@
|
||||
using Newtonsoft.Json;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Data;
|
||||
using System.Windows.Documents;
|
||||
using System.Windows.Input;
|
||||
using System.Windows.Media;
|
||||
using System.Windows.Media.Imaging;
|
||||
using WechatPCMsgBakTool.Model;
|
||||
|
||||
namespace WechatPCMsgBakTool
|
||||
{
|
||||
/// <summary>
|
||||
/// Tools.xaml 的交互逻辑
|
||||
/// </summary>
|
||||
public partial class Tools : Window
|
||||
{
|
||||
public Tools()
|
||||
{
|
||||
InitializeComponent();
|
||||
LoadWorkspace();
|
||||
}
|
||||
|
||||
private void LoadWorkspace()
|
||||
{
|
||||
list_workspace.Items.Clear();
|
||||
string path = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "workspace");
|
||||
if (Directory.Exists(path))
|
||||
{
|
||||
string[] files = Directory.GetFiles(path);
|
||||
foreach (string file in files)
|
||||
{
|
||||
string type = file.Substring(file.Length - 5, 5);
|
||||
if (type == ".json")
|
||||
{
|
||||
string jsonString = File.ReadAllText(file);
|
||||
UserBakConfig? userBakConfig = null;
|
||||
try
|
||||
{
|
||||
userBakConfig = JsonConvert.DeserializeObject<UserBakConfig>(jsonString);
|
||||
}
|
||||
catch
|
||||
{
|
||||
MessageBox.Show("读取到异常工作区文件,请确认备份数据是否正常\r\n文件路径:" + file, "错误");
|
||||
}
|
||||
if (userBakConfig != null)
|
||||
{
|
||||
list_workspace.Items.Add(userBakConfig);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void back_video_file_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
Task.Run(() => {
|
||||
UserBakConfig? selectConfig = null;
|
||||
Dispatcher.Invoke(() => {
|
||||
selectConfig = list_workspace.SelectedItem as UserBakConfig;
|
||||
});
|
||||
if (selectConfig != null)
|
||||
{
|
||||
if (!selectConfig.Decrypt)
|
||||
{
|
||||
MessageBox.Show("工作区未解密,请先用主程序进行解密");
|
||||
return;
|
||||
}
|
||||
|
||||
// 检查工作区视频文件夹
|
||||
string video_dir = Path.Combine(selectConfig.UserWorkspacePath, "Video");
|
||||
string[] files = Directory.GetFiles(video_dir);
|
||||
if (!Directory.Exists(video_dir))
|
||||
{
|
||||
Dispatcher.Invoke(() => {
|
||||
txt_log.Text += video_dir + "不存在\r\n";
|
||||
txt_log.ScrollToEnd();
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
WXUserReader UserReader = new WXUserReader(selectConfig);
|
||||
// 获取用户
|
||||
var atc_list = UserReader.GetWXMsgAtc();
|
||||
if(atc_list == null)
|
||||
{
|
||||
Dispatcher.Invoke(() => {
|
||||
txt_log.Text += "视频列表没有内容,无法回退\r\n";
|
||||
txt_log.ScrollToEnd();
|
||||
});
|
||||
return;
|
||||
}
|
||||
foreach (string file in files)
|
||||
{
|
||||
FileInfo fileInfo = new FileInfo(file);
|
||||
var search = atc_list.FindAll(x => x.attachPath.Contains(fileInfo.Name));
|
||||
if (search != null)
|
||||
{
|
||||
WXSessionAttachInfo? select_atc = null;
|
||||
if (search.Count > 1)
|
||||
{
|
||||
foreach (var s in search)
|
||||
{
|
||||
Dispatcher.Invoke(() =>
|
||||
{
|
||||
txt_log.Text += s + "\r\n";
|
||||
txt_log.ScrollToEnd();
|
||||
});
|
||||
if (s.attachPath.Contains("_raw"))
|
||||
select_atc = s;
|
||||
}
|
||||
}
|
||||
else if (search.Count == 1)
|
||||
select_atc = search[0];
|
||||
else
|
||||
{
|
||||
Dispatcher.Invoke(() =>
|
||||
{
|
||||
txt_log.Text += "匹配不到文件\r\n";
|
||||
txt_log.ScrollToEnd();
|
||||
});
|
||||
continue;
|
||||
}
|
||||
|
||||
if (select_atc == null)
|
||||
{
|
||||
Dispatcher.Invoke(() =>
|
||||
{
|
||||
txt_log.Text += "匹配失败\r\n";
|
||||
txt_log.ScrollToEnd();
|
||||
});
|
||||
continue;
|
||||
}
|
||||
|
||||
// 建立路径
|
||||
string source_video_file = Path.Combine(selectConfig.UserResPath, select_atc.attachPath);
|
||||
if (File.Exists(source_video_file))
|
||||
{
|
||||
Dispatcher.Invoke(() => {
|
||||
txt_log.Text += source_video_file + "已经存在\r\n";
|
||||
txt_log.ScrollToEnd();
|
||||
});
|
||||
|
||||
continue;
|
||||
}
|
||||
else
|
||||
{
|
||||
Dispatcher.Invoke(() => {
|
||||
txt_log.Text += source_video_file + "开始发起回退\r\n";
|
||||
txt_log.ScrollToEnd();
|
||||
});
|
||||
File.Copy(fileInfo.FullName, source_video_file);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
Binary file not shown.
@@ -46,13 +46,13 @@ namespace WechatPCMsgBakTool
|
||||
string query = "select * from contact";
|
||||
if (name != null)
|
||||
{
|
||||
query = "select * from contact where username = ? or alias = ?";
|
||||
return con.Query<WXContact>(query, name, name);
|
||||
query = "select * from contact where username like ? or alias like ? or nickname like ? or remark like ?";
|
||||
return con.Query<WXContact>(query, $"%{name}%", $"%{name}%", $"%{name}%", $"%{name}%");
|
||||
}
|
||||
return con.Query<WXContact>(query);
|
||||
}
|
||||
|
||||
public List<WXMsg>? GetWXMsgs(string uid)
|
||||
public List<WXMsg>? GetWXMsgs(string uid,string msg = "")
|
||||
{
|
||||
List<WXMsg> tmp = new List<WXMsg>();
|
||||
for (int i = 0; i <= 99; i++)
|
||||
@@ -63,8 +63,23 @@ namespace WechatPCMsgBakTool
|
||||
if (con == null)
|
||||
return tmp;
|
||||
|
||||
string query = "select * from MSG where StrTalker=?";
|
||||
List<WXMsg> wXMsgs = con.Query<WXMsg>(query, uid);
|
||||
List<WXMsg>? wXMsgs = null;
|
||||
if (msg == "")
|
||||
{
|
||||
string query = "select * from MSG where StrTalker=?";
|
||||
wXMsgs = con.Query<WXMsg>(query, uid);
|
||||
}
|
||||
else if(uid == "")
|
||||
{
|
||||
string query = "select * from MSG where StrContent like ?";
|
||||
wXMsgs = con.Query<WXMsg>(query, string.Format("%{0}%", msg));
|
||||
}
|
||||
else
|
||||
{
|
||||
string query = "select * from MSG where StrTalker=? and StrContent like ?";
|
||||
wXMsgs = con.Query<WXMsg>(query, uid, string.Format("%{0}%", msg));
|
||||
}
|
||||
|
||||
foreach (WXMsg w in wXMsgs)
|
||||
{
|
||||
tmp.Add(w);
|
||||
@@ -73,7 +88,21 @@ namespace WechatPCMsgBakTool
|
||||
}
|
||||
return tmp;
|
||||
}
|
||||
public List<WXSessionAttachInfo>? GetWXMsgAtc()
|
||||
{
|
||||
SQLiteConnection con = DBInfo["MultiSearchChatMsg"];
|
||||
if (con == null)
|
||||
return null;
|
||||
|
||||
string query = "select * from SessionAttachInfo";
|
||||
List<WXSessionAttachInfo> list = con.Query<WXSessionAttachInfo>(query);
|
||||
if (list.Count != 0)
|
||||
{
|
||||
return list;
|
||||
}
|
||||
else
|
||||
return null;
|
||||
}
|
||||
public WXSessionAttachInfo? GetWXMsgAtc(WXMsg msg)
|
||||
{
|
||||
SQLiteConnection con = DBInfo["MultiSearchChatMsg"];
|
||||
@@ -101,7 +130,6 @@ namespace WechatPCMsgBakTool
|
||||
else
|
||||
return null;
|
||||
}
|
||||
|
||||
public WXMediaMsg? GetVoiceMsg(WXMsg msg)
|
||||
{
|
||||
for (int i = 0; i <= 99; i++)
|
||||
@@ -120,7 +148,6 @@ namespace WechatPCMsgBakTool
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public string? GetAttachment(WXMsgType type, WXMsg msg)
|
||||
{
|
||||
if (UserBakConfig == null)
|
||||
@@ -175,14 +202,14 @@ namespace WechatPCMsgBakTool
|
||||
// 视频的路径是相对路径,需要加上资源目录
|
||||
path = Path.Combine(UserBakConfig.UserResPath, path);
|
||||
if(!File.Exists(video_file_path))
|
||||
File.Move(path, video_file_path);
|
||||
File.Copy(path, video_file_path);
|
||||
path = video_file_path;
|
||||
}
|
||||
|
||||
if (path == null)
|
||||
return null;
|
||||
|
||||
// 该相对路径
|
||||
// 改相对路径
|
||||
path = path.Replace(UserBakConfig.UserWorkspacePath + "\\", "");
|
||||
return path;
|
||||
|
||||
@@ -217,6 +244,31 @@ namespace WechatPCMsgBakTool
|
||||
}
|
||||
return file_path;
|
||||
}
|
||||
public List<WXMsgGroup> GetWXMsgGroup()
|
||||
{
|
||||
List<WXMsgGroup> g = new List<WXMsgGroup>();
|
||||
for (int i = 0; i <= 99; i++)
|
||||
{
|
||||
if (DBInfo.ContainsKey("MSG" + i.ToString()))
|
||||
{
|
||||
SQLiteConnection con = DBInfo["MSG" + i.ToString()];
|
||||
if (con == null)
|
||||
return g;
|
||||
|
||||
string query = "select StrTalker,Count(localId) as MsgCount from MSG GROUP BY StrTalker";
|
||||
List<WXMsgGroup> wXMsgs = con.Query<WXMsgGroup>(query);
|
||||
foreach (WXMsgGroup w in wXMsgs)
|
||||
{
|
||||
WXMsgGroup? tmp = g.Find(x => x.UserName == w.UserName);
|
||||
if (tmp == null)
|
||||
g.Add(w);
|
||||
else
|
||||
tmp.MsgCount += g.Count;
|
||||
}
|
||||
}
|
||||
}
|
||||
return g;
|
||||
}
|
||||
}
|
||||
|
||||
public enum WXMsgType
|
||||
|
||||
@@ -14,8 +14,8 @@ namespace WechatPCMsgBakTool
|
||||
public class WXWorkspace
|
||||
{
|
||||
private UserBakConfig UserBakConfig = new UserBakConfig();
|
||||
public WXWorkspace(string path) {
|
||||
string checkResult = Init(path);
|
||||
public WXWorkspace(string path,string account = "") {
|
||||
string checkResult = Init(path, account);
|
||||
if (checkResult != "")
|
||||
new Exception(checkResult);
|
||||
}
|
||||
@@ -64,7 +64,7 @@ namespace WechatPCMsgBakTool
|
||||
}
|
||||
}
|
||||
}
|
||||
private string Init(string path)
|
||||
private string Init(string path,string account = "")
|
||||
{
|
||||
string curPath = AppDomain.CurrentDomain.BaseDirectory;
|
||||
string md5 = GetMd5Hash(path);
|
||||
@@ -74,6 +74,7 @@ namespace WechatPCMsgBakTool
|
||||
UserBakConfig.UserWorkspacePath = Path.Combine(curPath, "workspace", md5);
|
||||
UserBakConfig.Hash = md5;
|
||||
UserBakConfig.UserName = username;
|
||||
UserBakConfig.Account = account;
|
||||
|
||||
if (!Directory.Exists(UserBakConfig.UserResPath))
|
||||
{
|
||||
|
||||
@@ -6,28 +6,28 @@
|
||||
<Nullable>enable</Nullable>
|
||||
<UseWPF>true</UseWPF>
|
||||
<AllowUnsafeBlocks>True</AllowUnsafeBlocks>
|
||||
<AssemblyVersion>0.2.1.0</AssemblyVersion>
|
||||
<FileVersion>0.2.1.0</FileVersion>
|
||||
<AssemblyVersion>0.5.0.1</AssemblyVersion>
|
||||
<FileVersion>0.5.0.1</FileVersion>
|
||||
<Version>0.5.0.1</Version>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="K4os.Compression.LZ4.Streams" Version="1.3.6" />
|
||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
|
||||
<PackageReference Include="sqlite-net-pcl" Version="1.8.116" />
|
||||
<PackageReference Include="System.Management" Version="7.0.2" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<None Update="libcrypto-1_1-x64.dll">
|
||||
<None Update="libcrypto-1_1.dll">
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="libssl-1_1-x64.dll">
|
||||
<None Update="libssl-1_1.dll">
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="Tools\ffmpeg.exe">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="Tools\handle64.exe">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="Tools\silk_v3_decoder.exe">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
|
||||
Binary file not shown.
BIN
libcrypto-1_1.dll
Normal file
BIN
libcrypto-1_1.dll
Normal file
Binary file not shown.
Binary file not shown.
BIN
libssl-1_1.dll
Normal file
BIN
libssl-1_1.dll
Normal file
Binary file not shown.
@@ -5,5 +5,11 @@
|
||||
},{
|
||||
"Version":"3.9.7.25",
|
||||
"BaseAddr": 63484032
|
||||
},{
|
||||
"Version":"3.9.7.29",
|
||||
"BaseAddr": 63488256
|
||||
},{
|
||||
"Version":"3.9.8.15",
|
||||
"BaseAddr": 64997904
|
||||
}
|
||||
]
|
||||
Reference in New Issue
Block a user