Initial commit
This commit is contained in:
+181
@@ -0,0 +1,181 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<ItemGroup Label="ProjectConfigurations">
|
||||
<ProjectConfiguration Include="Debug|Win32">
|
||||
<Configuration>Debug</Configuration>
|
||||
<Platform>Win32</Platform>
|
||||
</ProjectConfiguration>
|
||||
<ProjectConfiguration Include="Release|Win32">
|
||||
<Configuration>Release</Configuration>
|
||||
<Platform>Win32</Platform>
|
||||
</ProjectConfiguration>
|
||||
<ProjectConfiguration Include="Debug|x64">
|
||||
<Configuration>Debug</Configuration>
|
||||
<Platform>x64</Platform>
|
||||
</ProjectConfiguration>
|
||||
<ProjectConfiguration Include="Release|x64">
|
||||
<Configuration>Release</Configuration>
|
||||
<Platform>x64</Platform>
|
||||
</ProjectConfiguration>
|
||||
</ItemGroup>
|
||||
<PropertyGroup Label="Globals">
|
||||
<VCProjectVersion>16.0</VCProjectVersion>
|
||||
<Keyword>Win32Proj</Keyword>
|
||||
<ProjectGuid>{707f2dcd-1001-42a7-b20e-b85b1bbab228}</ProjectGuid>
|
||||
<RootNamespace>SDK</RootNamespace>
|
||||
<WindowsTargetPlatformVersion>10.0</WindowsTargetPlatformVersion>
|
||||
</PropertyGroup>
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration">
|
||||
<ConfigurationType>DynamicLibrary</ConfigurationType>
|
||||
<UseDebugLibraries>true</UseDebugLibraries>
|
||||
<PlatformToolset>v142</PlatformToolset>
|
||||
<CharacterSet>Unicode</CharacterSet>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="Configuration">
|
||||
<ConfigurationType>DynamicLibrary</ConfigurationType>
|
||||
<UseDebugLibraries>false</UseDebugLibraries>
|
||||
<PlatformToolset>v142</PlatformToolset>
|
||||
<WholeProgramOptimization>true</WholeProgramOptimization>
|
||||
<CharacterSet>Unicode</CharacterSet>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="Configuration">
|
||||
<ConfigurationType>DynamicLibrary</ConfigurationType>
|
||||
<UseDebugLibraries>true</UseDebugLibraries>
|
||||
<PlatformToolset>v142</PlatformToolset>
|
||||
<CharacterSet>Unicode</CharacterSet>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="Configuration">
|
||||
<ConfigurationType>DynamicLibrary</ConfigurationType>
|
||||
<UseDebugLibraries>false</UseDebugLibraries>
|
||||
<PlatformToolset>v142</PlatformToolset>
|
||||
<WholeProgramOptimization>true</WholeProgramOptimization>
|
||||
<CharacterSet>Unicode</CharacterSet>
|
||||
</PropertyGroup>
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
|
||||
<ImportGroup Label="ExtensionSettings">
|
||||
</ImportGroup>
|
||||
<ImportGroup Label="Shared">
|
||||
</ImportGroup>
|
||||
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
|
||||
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
|
||||
</ImportGroup>
|
||||
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
|
||||
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
|
||||
</ImportGroup>
|
||||
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
|
||||
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
|
||||
</ImportGroup>
|
||||
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
|
||||
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
|
||||
</ImportGroup>
|
||||
<PropertyGroup Label="UserMacros" />
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
|
||||
<LinkIncremental>true</LinkIncremental>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
|
||||
<LinkIncremental>false</LinkIncremental>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
|
||||
<LinkIncremental>true</LinkIncremental>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
|
||||
<LinkIncremental>false</LinkIncremental>
|
||||
</PropertyGroup>
|
||||
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
|
||||
<ClCompile>
|
||||
<WarningLevel>Level3</WarningLevel>
|
||||
<SDLCheck>true</SDLCheck>
|
||||
<PreprocessorDefinitions>WIN32;_DEBUG;SDK_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<ConformanceMode>true</ConformanceMode>
|
||||
<PrecompiledHeader>NotUsing</PrecompiledHeader>
|
||||
<PrecompiledHeaderFile>pch.h</PrecompiledHeaderFile>
|
||||
<AdditionalOptions> /utf-8 %(AdditionalOptions)</AdditionalOptions>
|
||||
<AdditionalIncludeDirectories>$(SolutionDir)Rpc</AdditionalIncludeDirectories>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<SubSystem>Windows</SubSystem>
|
||||
<GenerateDebugInformation>true</GenerateDebugInformation>
|
||||
<EnableUAC>false</EnableUAC>
|
||||
<ModuleDefinitionFile>sdk.def</ModuleDefinitionFile>
|
||||
</Link>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
|
||||
<ClCompile>
|
||||
<WarningLevel>Level3</WarningLevel>
|
||||
<FunctionLevelLinking>true</FunctionLevelLinking>
|
||||
<IntrinsicFunctions>true</IntrinsicFunctions>
|
||||
<SDLCheck>true</SDLCheck>
|
||||
<PreprocessorDefinitions>WIN32;NDEBUG;SDK_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<ConformanceMode>true</ConformanceMode>
|
||||
<PrecompiledHeader>NotUsing</PrecompiledHeader>
|
||||
<PrecompiledHeaderFile>pch.h</PrecompiledHeaderFile>
|
||||
<AdditionalIncludeDirectories>$(SolutionDir)Rpc</AdditionalIncludeDirectories>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<SubSystem>Windows</SubSystem>
|
||||
<EnableCOMDATFolding>true</EnableCOMDATFolding>
|
||||
<OptimizeReferences>true</OptimizeReferences>
|
||||
<GenerateDebugInformation>true</GenerateDebugInformation>
|
||||
<EnableUAC>false</EnableUAC>
|
||||
<ModuleDefinitionFile>sdk.def</ModuleDefinitionFile>
|
||||
</Link>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
|
||||
<ClCompile>
|
||||
<WarningLevel>Level3</WarningLevel>
|
||||
<SDLCheck>true</SDLCheck>
|
||||
<PreprocessorDefinitions>_DEBUG;SDK_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<ConformanceMode>true</ConformanceMode>
|
||||
<PrecompiledHeader>Use</PrecompiledHeader>
|
||||
<PrecompiledHeaderFile>pch.h</PrecompiledHeaderFile>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<SubSystem>Windows</SubSystem>
|
||||
<GenerateDebugInformation>true</GenerateDebugInformation>
|
||||
<EnableUAC>false</EnableUAC>
|
||||
<ModuleDefinitionFile>sdk.def</ModuleDefinitionFile>
|
||||
</Link>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
|
||||
<ClCompile>
|
||||
<WarningLevel>Level3</WarningLevel>
|
||||
<FunctionLevelLinking>true</FunctionLevelLinking>
|
||||
<IntrinsicFunctions>true</IntrinsicFunctions>
|
||||
<SDLCheck>true</SDLCheck>
|
||||
<PreprocessorDefinitions>NDEBUG;SDK_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<ConformanceMode>true</ConformanceMode>
|
||||
<PrecompiledHeader>Use</PrecompiledHeader>
|
||||
<PrecompiledHeaderFile>pch.h</PrecompiledHeaderFile>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<SubSystem>Windows</SubSystem>
|
||||
<EnableCOMDATFolding>true</EnableCOMDATFolding>
|
||||
<OptimizeReferences>true</OptimizeReferences>
|
||||
<GenerateDebugInformation>true</GenerateDebugInformation>
|
||||
<EnableUAC>false</EnableUAC>
|
||||
<ModuleDefinitionFile>sdk.def</ModuleDefinitionFile>
|
||||
</Link>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemGroup>
|
||||
<ClInclude Include="..\Rpc\rpc_h.h" />
|
||||
<ClInclude Include="framework.h" />
|
||||
<ClInclude Include="injector.h" />
|
||||
<ClInclude Include="sdk.h" />
|
||||
<ClInclude Include="util.h" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClCompile Include="..\Rpc\rpc_c.c" />
|
||||
<ClCompile Include="..\Rpc\rpc_memory.cpp" />
|
||||
<ClCompile Include="dllmain.cpp" />
|
||||
<ClCompile Include="injector.cpp" />
|
||||
<ClCompile Include="sdk.cpp" />
|
||||
<ClCompile Include="util.cpp" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Include="sdk.def" />
|
||||
</ItemGroup>
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
|
||||
<ImportGroup Label="ExtensionTargets">
|
||||
</ImportGroup>
|
||||
</Project>
|
||||
@@ -0,0 +1,59 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<ItemGroup>
|
||||
<Filter Include="源文件">
|
||||
<UniqueIdentifier>{4FC737F1-C7A5-4376-A066-2A32D752A2FF}</UniqueIdentifier>
|
||||
<Extensions>cpp;c;cc;cxx;c++;cppm;ixx;def;odl;idl;hpj;bat;asm;asmx</Extensions>
|
||||
</Filter>
|
||||
<Filter Include="头文件">
|
||||
<UniqueIdentifier>{93995380-89BD-4b04-88EB-625FBE52EBFB}</UniqueIdentifier>
|
||||
<Extensions>h;hh;hpp;hxx;h++;hm;inl;inc;ipp;xsd</Extensions>
|
||||
</Filter>
|
||||
<Filter Include="资源文件">
|
||||
<UniqueIdentifier>{67DA6AB6-F800-4c08-8B7A-83BB121AAD01}</UniqueIdentifier>
|
||||
<Extensions>rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms</Extensions>
|
||||
</Filter>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClInclude Include="framework.h">
|
||||
<Filter>头文件</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="sdk.h">
|
||||
<Filter>头文件</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="..\Rpc\rpc_h.h">
|
||||
<Filter>头文件</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="util.h">
|
||||
<Filter>头文件</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="injector.h">
|
||||
<Filter>头文件</Filter>
|
||||
</ClInclude>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClCompile Include="dllmain.cpp">
|
||||
<Filter>源文件</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="sdk.cpp">
|
||||
<Filter>源文件</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="..\Rpc\rpc_c.c">
|
||||
<Filter>源文件</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="..\Rpc\rpc_memory.cpp">
|
||||
<Filter>源文件</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="util.cpp">
|
||||
<Filter>源文件</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="injector.cpp">
|
||||
<Filter>源文件</Filter>
|
||||
</ClCompile>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Include="sdk.def">
|
||||
<Filter>源文件</Filter>
|
||||
</None>
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
@@ -0,0 +1,4 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project ToolsVersion="Current" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<PropertyGroup />
|
||||
</Project>
|
||||
@@ -0,0 +1,21 @@
|
||||
// dllmain.cpp : 定义 DLL 应用程序的入口点。
|
||||
#include "framework.h"
|
||||
#include <rpc.h>
|
||||
|
||||
extern RPC_STATUS RpcConnectServer();
|
||||
extern RPC_STATUS RpcDisconnectServer();
|
||||
|
||||
BOOL APIENTRY DllMain(HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved)
|
||||
{
|
||||
switch (ul_reason_for_call) {
|
||||
case DLL_PROCESS_ATTACH:
|
||||
case DLL_THREAD_ATTACH:
|
||||
case DLL_THREAD_DETACH:
|
||||
break;
|
||||
case DLL_PROCESS_DETACH: {
|
||||
RpcDisconnectServer();
|
||||
break;
|
||||
}
|
||||
}
|
||||
return TRUE;
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
#pragma once
|
||||
|
||||
#define WIN32_LEAN_AND_MEAN // 从 Windows 头文件中排除极少使用的内容
|
||||
// Windows 头文件
|
||||
#include <windows.h>
|
||||
@@ -0,0 +1,653 @@
|
||||
#ifndef _CRT_SECURE_NO_WARNINGS
|
||||
#define _CRT_SECURE_NO_WARNINGS
|
||||
#endif
|
||||
|
||||
#include "injector.h"
|
||||
#include <malloc.h>
|
||||
#include <stdarg.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
|
||||
typedef BOOL(WINAPI *IsWow64Process2_t)(HANDLE hProcess, USHORT *pProcessMachine, USHORT *pNativeMachine);
|
||||
|
||||
static DWORD page_size = 0;
|
||||
static size_t func_LoadLibraryW;
|
||||
static size_t func_FreeLibrary;
|
||||
static size_t func_GetLastError;
|
||||
static char errmsg[512];
|
||||
static injector_t *g_injector;
|
||||
|
||||
#ifdef _M_AMD64
|
||||
static const char x64_code_template[] =
|
||||
// ---------- call LoadLibraryW ----------
|
||||
/* 0000: */ "\x48\x83\xEC\x28" // sub rsp,28h
|
||||
/* 0004: */ "\xFF\x15\x3E\x00\x00\x00" // call LoadLibraryW
|
||||
// 0x0000003e = X64_ADDR_LoadLibraryW - (0x0004 + 6)
|
||||
/* 000A: */ "\x48\x85\xC0" // test rax,rax
|
||||
/* 000D: */ "\x74\x0B" // je L1
|
||||
/* 000F: */ "\x48\x89\x05\xEA\x0F\x00\x00" // mov [load_address], rax
|
||||
// 0x00000fea = 0x1000 - (0x000F + 7)
|
||||
/* 0016: */ "\x33\xC0" // xor eax,eax
|
||||
/* 0018: */ "\xEB\x06" // jmp L2
|
||||
/* 001A: L1: */ "\xFF\x15\x38\x00\x00\x00" // call GetLastError
|
||||
// 0x00000038 = X64_ADDR_GetLastError - (0x001A + 6)
|
||||
/* 0020: L2: */ "\x48\x83\xC4\x28" // add rsp,28h
|
||||
/* 0024: */ "\xC3" // ret
|
||||
|
||||
// ---------- call FreeLibrary ----------
|
||||
#define X64_UNINJECTION_CODE_OFFSET 0x25
|
||||
/* 0025: */ "\x48\x83\xEC\x28" // sub rsp,28h
|
||||
/* 0029: */ "\xFF\x15\x21\x00\x00\x00" // call FreeLibrary
|
||||
// 0x00000021 = X64_ADDR_FreeLibrary - (0x0029 + 6)
|
||||
/* 002F: */ "\x85\xC0" // test eax,eax
|
||||
/* 0031: */ "\x74\x04" // je L1
|
||||
/* 0033: */ "\x33\xC0" // xor eax,eax
|
||||
/* 0035: */ "\xEB\x06" // jmp L2
|
||||
/* 0037: L1: */ "\xFF\x15\x1B\x00\x00\x00" // call GetLastError
|
||||
// 0x0000001B = X64_ADDR_GetLastError - (0x0037 + 6)
|
||||
/* 003D: L2: */ "\x48\x83\xC4\x28" // add rsp,28h
|
||||
/* 0041: */ "\xC3" // ret
|
||||
|
||||
// padding
|
||||
/* 0042: */ "\x90\x90\x90\x90\x90\x90"
|
||||
|
||||
// ---------- literal pool ----------
|
||||
#define X64_ADDR_LoadLibraryW 0x0048
|
||||
/* 0048: */ "\x90\x90\x90\x90\x90\x90\x90\x90"
|
||||
#define X64_ADDR_FreeLibrary 0x0050
|
||||
/* 0050: */ "\x90\x90\x90\x90\x90\x90\x90\x90"
|
||||
#define X64_ADDR_GetLastError 0x0058
|
||||
/* 0058: */ "\x90\x90\x90\x90\x90\x90\x90\x90";
|
||||
|
||||
#define X64_CODE_SIZE 0x0060
|
||||
#endif
|
||||
|
||||
#if defined(_M_AMD64) || defined(_M_IX86)
|
||||
static const char x86_code_template[] =
|
||||
// ---------- call LoadLibraryW ----------
|
||||
/* 0000: */ "\xFF\x74\x24\x04" // push dword ptr [esp+4]
|
||||
#define X86_CALL_LoadLibraryW 0x0004
|
||||
/* 0004: */ "\xE8\x00\x00\x00\x00" // call LoadLibraryW@4
|
||||
/* 0009: */ "\x85\xC0" // test eax,eax
|
||||
/* 000B: */ "\x74\x09" // je L1
|
||||
#define X86_MOV_EAX 0x000D
|
||||
/* 000D: */ "\xA3\x00\x00\x00\x00" // mov dword ptr [load_address], eax
|
||||
/* 0012: */ "\x33\xC0" // xor eax,eax
|
||||
/* 0014: */ "\xEB\x05" // jmp L2
|
||||
#define X86_CALL_GetLastError1 0x0016
|
||||
/* 0016: L1: */ "\xE8\x00\x00\x00\x00" // call GetLastError@0
|
||||
/* 001B: L2: */ "\xC2\x04\x00" // ret 4
|
||||
|
||||
// ---------- call FreeLibrary ----------
|
||||
#define X86_UNINJECTION_CODE_OFFSET 0x001E
|
||||
/* 001E: */ "\xFF\x74\x24\x04" // push dword ptr [esp+4]
|
||||
#define X86_CALL_FreeLibrary 0x0022
|
||||
/* 0022: */ "\xE8\x00\x00\x00\x00" // call FreeLibrary@4
|
||||
/* 0027: */ "\x85\xC0" // test eax,eax
|
||||
/* 0029: */ "\x74\x04" // je L1
|
||||
/* 002B: */ "\x33\xC0" // xor eax,eax
|
||||
/* 002D: */ "\xEB\x05" // jmp L2
|
||||
#define X86_CALL_GetLastError2 0x002F
|
||||
/* 002F: L1: */ "\xE8\x00\x00\x00\x00" // call GetLastError@0
|
||||
/* 0034: L2: */ "\xC2\x04\x00" // ret 4
|
||||
;
|
||||
|
||||
#define X86_CODE_SIZE 0x0037
|
||||
#endif
|
||||
|
||||
#ifdef _M_AMD64
|
||||
#define CURRENT_ARCH "x64"
|
||||
#define CODE_SIZE X64_CODE_SIZE
|
||||
#endif
|
||||
|
||||
#ifdef _M_IX86
|
||||
#define CURRENT_ARCH "x86"
|
||||
#define CODE_SIZE X86_CODE_SIZE
|
||||
#endif
|
||||
|
||||
static void set_errmsg(const char *format, ...);
|
||||
static const char *w32strerr(DWORD err);
|
||||
static USHORT process_arch(HANDLE hProcess);
|
||||
static const char *arch_name(USHORT arch);
|
||||
|
||||
struct injector {
|
||||
HANDLE hProcess;
|
||||
char *remote_mem;
|
||||
char *injection_code;
|
||||
char *uninjection_code;
|
||||
};
|
||||
|
||||
static BOOL init(void)
|
||||
{
|
||||
SYSTEM_INFO si;
|
||||
HANDLE hToken;
|
||||
LUID luid;
|
||||
TOKEN_PRIVILEGES tp;
|
||||
HMODULE kernel32 = GetModuleHandleA("kernel32");
|
||||
if (kernel32 == 0) {
|
||||
return FALSE;
|
||||
}
|
||||
GetSystemInfo(&si);
|
||||
page_size = si.dwPageSize;
|
||||
func_LoadLibraryW = (size_t)GetProcAddress(kernel32, "LoadLibraryW");
|
||||
func_FreeLibrary = (size_t)GetProcAddress(kernel32, "FreeLibrary");
|
||||
func_GetLastError = (size_t)GetProcAddress(kernel32, "GetLastError");
|
||||
|
||||
if (!OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES, &hToken)) {
|
||||
return FALSE;
|
||||
}
|
||||
if (!LookupPrivilegeValue(0, SE_DEBUG_NAME, &luid)) {
|
||||
CloseHandle(hToken);
|
||||
return FALSE;
|
||||
}
|
||||
tp.PrivilegeCount = 1;
|
||||
tp.Privileges[0].Luid = luid;
|
||||
tp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
|
||||
if (!AdjustTokenPrivileges(hToken, FALSE, &tp, 0, NULL, NULL)) {
|
||||
CloseHandle(hToken);
|
||||
return FALSE;
|
||||
}
|
||||
CloseHandle(hToken);
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
#if defined(_M_AMD64)
|
||||
static int cmp_func(const void *context, const void *key, const void *datum)
|
||||
{
|
||||
ptrdiff_t rva_to_va = (ptrdiff_t)context;
|
||||
const char *k = (const char *)key;
|
||||
const char *d = (const char *)(rva_to_va + *(const DWORD *)datum);
|
||||
return strcmp(k, d);
|
||||
}
|
||||
|
||||
static int funcaddr(DWORD pid, size_t *load_library, size_t *free_library, size_t *get_last_error)
|
||||
{
|
||||
HANDLE hSnapshot;
|
||||
MODULEENTRY32W me;
|
||||
BOOL ok;
|
||||
HANDLE hFile = INVALID_HANDLE_VALUE;
|
||||
HANDLE hFileMapping = NULL;
|
||||
void *base = NULL;
|
||||
IMAGE_NT_HEADERS *nt_hdrs;
|
||||
ULONG exp_size;
|
||||
const IMAGE_EXPORT_DIRECTORY *exp;
|
||||
const DWORD *names, *name, *funcs;
|
||||
const WORD *ordinals;
|
||||
ptrdiff_t rva_to_va;
|
||||
int rv = INJERR_OTHER;
|
||||
|
||||
/* Get the full path of kernel32.dll. */
|
||||
retry:
|
||||
hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPMODULE | TH32CS_SNAPMODULE32, pid);
|
||||
if (hSnapshot == INVALID_HANDLE_VALUE) {
|
||||
DWORD err = GetLastError();
|
||||
switch (err) {
|
||||
case ERROR_BAD_LENGTH:
|
||||
goto retry;
|
||||
case ERROR_ACCESS_DENIED:
|
||||
rv = INJERR_PERMISSION;
|
||||
break;
|
||||
case ERROR_INVALID_PARAMETER:
|
||||
rv = INJERR_NO_PROCESS;
|
||||
break;
|
||||
default:
|
||||
rv = INJERR_OTHER;
|
||||
}
|
||||
set_errmsg("CreateToolhelp32Snapshot error: %s", w32strerr(err));
|
||||
return rv;
|
||||
}
|
||||
me.dwSize = sizeof(me);
|
||||
for (ok = Module32FirstW(hSnapshot, &me); ok; ok = Module32NextW(hSnapshot, &me)) {
|
||||
if (wcsicmp(me.szModule, L"kernel32.dll") == 0) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
CloseHandle(hSnapshot);
|
||||
if (!ok) {
|
||||
set_errmsg("kernel32.dll could not be found.");
|
||||
return INJERR_OTHER;
|
||||
}
|
||||
|
||||
/* Get the export directory in the kernel32.dll. */
|
||||
hFile = CreateFileW(me.szExePath, GENERIC_READ, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
|
||||
if (hFile == INVALID_HANDLE_VALUE) {
|
||||
set_errmsg("failed to open file %s: %s", me.szExePath, w32strerr(GetLastError()));
|
||||
goto exit;
|
||||
}
|
||||
hFileMapping = CreateFileMappingA(hFile, NULL, PAGE_READONLY, 0, 0, NULL);
|
||||
if (hFileMapping == NULL) {
|
||||
set_errmsg("failed to create file mapping of %s: %s", me.szExePath, w32strerr(GetLastError()));
|
||||
goto exit;
|
||||
}
|
||||
base = MapViewOfFile(hFileMapping, FILE_MAP_READ, 0, 0, 0);
|
||||
if (base == NULL) {
|
||||
set_errmsg("failed to map file %s to memory: %s", me.szExePath, w32strerr(GetLastError()));
|
||||
goto exit;
|
||||
}
|
||||
nt_hdrs = ImageNtHeader(base);
|
||||
if (nt_hdrs == NULL) {
|
||||
set_errmsg("ImageNtHeader error: %s", w32strerr(GetLastError()));
|
||||
goto exit;
|
||||
}
|
||||
exp = (const IMAGE_EXPORT_DIRECTORY *)ImageDirectoryEntryToDataEx(base, FALSE, IMAGE_DIRECTORY_ENTRY_EXPORT,
|
||||
&exp_size, NULL);
|
||||
if (exp == NULL) {
|
||||
set_errmsg("ImageDirectoryEntryToDataEx error: %s", w32strerr(GetLastError()));
|
||||
goto exit;
|
||||
}
|
||||
if (exp->NumberOfNames == 0) {
|
||||
set_errmsg("No export entires are not found.");
|
||||
goto exit;
|
||||
}
|
||||
names = (const DWORD *)ImageRvaToVa(nt_hdrs, base, exp->AddressOfNames, NULL);
|
||||
if (names == NULL) {
|
||||
set_errmsg("ImageRvaToVa error: %s", w32strerr(GetLastError()));
|
||||
goto exit;
|
||||
}
|
||||
ordinals = (const WORD *)ImageRvaToVa(nt_hdrs, base, exp->AddressOfNameOrdinals, NULL);
|
||||
if (ordinals == NULL) {
|
||||
set_errmsg("ImageRvaToVa error: %s", w32strerr(GetLastError()));
|
||||
goto exit;
|
||||
}
|
||||
funcs = (const DWORD *)ImageRvaToVa(nt_hdrs, base, exp->AddressOfFunctions, NULL);
|
||||
if (funcs == NULL) {
|
||||
set_errmsg("ImageRvaToVa error: %s", w32strerr(GetLastError()));
|
||||
goto exit;
|
||||
}
|
||||
rva_to_va = (size_t)ImageRvaToVa(nt_hdrs, base, names[0], NULL) - (size_t)names[0];
|
||||
|
||||
/* Find the address of LoadLibraryW */
|
||||
name = bsearch_s((void *)"LoadLibraryW", names, exp->NumberOfNames, sizeof(DWORD), cmp_func, (void *)rva_to_va);
|
||||
if (name == NULL) {
|
||||
set_errmsg("Could not find the address of LoadLibraryW");
|
||||
goto exit;
|
||||
}
|
||||
*load_library = (size_t)me.modBaseAddr + funcs[ordinals[name - names]];
|
||||
|
||||
/* Find the address of FreeLibrary */
|
||||
name = bsearch_s((void *)"FreeLibrary", names, exp->NumberOfNames, sizeof(DWORD), cmp_func, (void *)rva_to_va);
|
||||
if (name == NULL) {
|
||||
set_errmsg("Could not find the address of FreeLibrary");
|
||||
goto exit;
|
||||
}
|
||||
*free_library = (size_t)me.modBaseAddr + funcs[ordinals[name - names]];
|
||||
|
||||
/* Find the address of GetLastError */
|
||||
name = bsearch_s((void *)"GetLastError", names, exp->NumberOfNames, sizeof(DWORD), cmp_func, (void *)rva_to_va);
|
||||
if (name == NULL) {
|
||||
set_errmsg("Could not find the address of GetLastError");
|
||||
goto exit;
|
||||
}
|
||||
*get_last_error = (size_t)me.modBaseAddr + funcs[ordinals[name - names]];
|
||||
rv = 0;
|
||||
exit:
|
||||
if (base != NULL) {
|
||||
UnmapViewOfFile(base);
|
||||
}
|
||||
if (hFileMapping != NULL) {
|
||||
CloseHandle(hFileMapping);
|
||||
}
|
||||
if (hFile != INVALID_HANDLE_VALUE) {
|
||||
CloseHandle(hFile);
|
||||
}
|
||||
return rv;
|
||||
}
|
||||
#endif
|
||||
|
||||
int cki_attach(injector_t **injector_out, DWORD pid)
|
||||
{
|
||||
injector_t *injector;
|
||||
DWORD dwDesiredAccess = PROCESS_QUERY_LIMITED_INFORMATION | /* for IsWow64Process() */
|
||||
PROCESS_CREATE_THREAD | /* for CreateRemoteThread() */
|
||||
PROCESS_VM_OPERATION | /* for VirtualAllocEx() */
|
||||
PROCESS_VM_READ | /* for ReadProcessMemory() */
|
||||
PROCESS_VM_WRITE; /* for WriteProcessMemory() */
|
||||
USHORT arch;
|
||||
DWORD old_protect;
|
||||
SIZE_T written;
|
||||
int rv;
|
||||
char code[CODE_SIZE];
|
||||
size_t code_size;
|
||||
size_t load_library, free_library, get_last_error;
|
||||
|
||||
if (page_size == 0) {
|
||||
init();
|
||||
}
|
||||
|
||||
load_library = func_LoadLibraryW;
|
||||
free_library = func_FreeLibrary;
|
||||
get_last_error = func_GetLastError;
|
||||
|
||||
injector = (injector_t *)calloc(1, sizeof(injector_t));
|
||||
if (injector == NULL) {
|
||||
set_errmsg("malloc error: %s", strerror(errno));
|
||||
return INJERR_NO_MEMORY;
|
||||
}
|
||||
injector->hProcess = OpenProcess(dwDesiredAccess, FALSE, pid);
|
||||
if (injector->hProcess == NULL) {
|
||||
DWORD err = GetLastError();
|
||||
set_errmsg("OpenProcess error: %s", w32strerr(err));
|
||||
switch (err) {
|
||||
case ERROR_ACCESS_DENIED:
|
||||
rv = INJERR_PERMISSION;
|
||||
break;
|
||||
case ERROR_INVALID_PARAMETER:
|
||||
rv = INJERR_NO_PROCESS;
|
||||
break;
|
||||
default:
|
||||
rv = INJERR_OTHER;
|
||||
}
|
||||
goto error_exit;
|
||||
}
|
||||
|
||||
arch = process_arch(injector->hProcess);
|
||||
switch (arch) {
|
||||
#ifdef _M_AMD64
|
||||
case IMAGE_FILE_MACHINE_AMD64:
|
||||
break;
|
||||
case IMAGE_FILE_MACHINE_I386:
|
||||
rv = funcaddr(pid, &load_library, &free_library, &get_last_error);
|
||||
if (rv != 0) {
|
||||
goto error_exit;
|
||||
}
|
||||
break;
|
||||
#endif
|
||||
|
||||
#ifdef _M_IX86
|
||||
case IMAGE_FILE_MACHINE_I386:
|
||||
break;
|
||||
#endif
|
||||
default:
|
||||
set_errmsg("%s target process isn't supported by %s process.", arch_name(arch), CURRENT_ARCH);
|
||||
rv = INJERR_UNSUPPORTED_TARGET;
|
||||
goto error_exit;
|
||||
}
|
||||
|
||||
injector->remote_mem
|
||||
= (char *)VirtualAllocEx(injector->hProcess, NULL, 2 * page_size, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
|
||||
if (injector->remote_mem == NULL) {
|
||||
set_errmsg("VirtualAllocEx error: %s", w32strerr(GetLastError()));
|
||||
rv = INJERR_OTHER;
|
||||
goto error_exit;
|
||||
}
|
||||
|
||||
injector->injection_code = injector->remote_mem;
|
||||
switch (arch) {
|
||||
#ifdef _M_AMD64
|
||||
case IMAGE_FILE_MACHINE_AMD64: /* x64 */
|
||||
memcpy(code, x64_code_template, X64_CODE_SIZE);
|
||||
code_size = X64_CODE_SIZE;
|
||||
*(size_t *)(code + X64_ADDR_LoadLibraryW) = load_library;
|
||||
*(size_t *)(code + X64_ADDR_FreeLibrary) = free_library;
|
||||
*(size_t *)(code + X64_ADDR_GetLastError) = get_last_error;
|
||||
injector->uninjection_code = injector->remote_mem + X64_UNINJECTION_CODE_OFFSET;
|
||||
break;
|
||||
#endif
|
||||
|
||||
#if defined(_M_AMD64) || defined(_M_IX86)
|
||||
case IMAGE_FILE_MACHINE_I386: /* x86 */
|
||||
memcpy(code, x86_code_template, X86_CODE_SIZE);
|
||||
code_size = X86_CODE_SIZE;
|
||||
#define FIX_CALL_RELATIVE(addr, offset) \
|
||||
*(uint32_t *)(code + offset + 1) = addr - ((uint32_t)(size_t)injector->remote_mem + offset + 5)
|
||||
FIX_CALL_RELATIVE(load_library, X86_CALL_LoadLibraryW);
|
||||
FIX_CALL_RELATIVE(free_library, X86_CALL_FreeLibrary);
|
||||
FIX_CALL_RELATIVE(get_last_error, X86_CALL_GetLastError1);
|
||||
FIX_CALL_RELATIVE(get_last_error, X86_CALL_GetLastError2);
|
||||
*(uint32_t *)(code + X86_MOV_EAX + 1) = (uint32_t)(size_t)injector->remote_mem + page_size;
|
||||
injector->uninjection_code = injector->remote_mem + X86_UNINJECTION_CODE_OFFSET;
|
||||
break;
|
||||
#endif
|
||||
default:
|
||||
set_errmsg("Never reach here: arch=0x%x", arch);
|
||||
rv = INJERR_OTHER;
|
||||
goto error_exit;
|
||||
}
|
||||
|
||||
if (!WriteProcessMemory(injector->hProcess, injector->remote_mem, code, code_size, &written)) {
|
||||
set_errmsg("WriteProcessMemory error: %s", w32strerr(GetLastError()));
|
||||
rv = INJERR_OTHER;
|
||||
goto error_exit;
|
||||
}
|
||||
|
||||
if (!VirtualProtectEx(injector->hProcess, injector->remote_mem, page_size, PAGE_EXECUTE_READ, &old_protect)) {
|
||||
set_errmsg("VirtualProtectEx error: %s", w32strerr(GetLastError()));
|
||||
rv = INJERR_OTHER;
|
||||
goto error_exit;
|
||||
}
|
||||
|
||||
*injector_out = injector;
|
||||
return 0;
|
||||
|
||||
error_exit:
|
||||
cki_detach(injector);
|
||||
return rv;
|
||||
}
|
||||
|
||||
int cki_inject(injector_t *injector, const char *path, void **handle)
|
||||
{
|
||||
DWORD pathlen = (DWORD)strlen(path);
|
||||
wchar_t *wpath;
|
||||
DWORD wpathlen;
|
||||
|
||||
if (pathlen == 0) {
|
||||
set_errmsg("The specified path is empty.");
|
||||
return INJERR_FILE_NOT_FOUND;
|
||||
}
|
||||
if (pathlen > MAX_PATH) {
|
||||
set_errmsg("too long file path: %s", path);
|
||||
return INJERR_FILE_NOT_FOUND;
|
||||
}
|
||||
|
||||
wpath = (wchar_t *)_alloca((pathlen + 1) * sizeof(wchar_t));
|
||||
wpathlen = MultiByteToWideChar(CP_ACP, MB_PRECOMPOSED, path, pathlen, wpath, pathlen + 1);
|
||||
wpath[wpathlen] = L'\0';
|
||||
return cki_inject_w(injector, wpath, handle);
|
||||
}
|
||||
|
||||
int cki_inject_w(injector_t *injector, const wchar_t *path, void **handle)
|
||||
{
|
||||
struct {
|
||||
void *load_address;
|
||||
wchar_t fullpath[MAX_PATH];
|
||||
} data = {
|
||||
NULL,
|
||||
};
|
||||
DWORD pathlen;
|
||||
SIZE_T written;
|
||||
HANDLE hThread;
|
||||
DWORD err;
|
||||
|
||||
pathlen = GetFullPathNameW(path, MAX_PATH, data.fullpath, NULL);
|
||||
if (pathlen > MAX_PATH) {
|
||||
set_errmsg("too long file path: %S", path);
|
||||
return INJERR_FILE_NOT_FOUND;
|
||||
}
|
||||
if (pathlen == 0) {
|
||||
set_errmsg("failed to get the full path: %S", path);
|
||||
return INJERR_FILE_NOT_FOUND;
|
||||
}
|
||||
if (!WriteProcessMemory(injector->hProcess, injector->remote_mem + page_size, &data, sizeof(data), &written)) {
|
||||
set_errmsg("WriteProcessMemory error: %s", w32strerr(GetLastError()));
|
||||
return INJERR_OTHER;
|
||||
}
|
||||
hThread = CreateRemoteThread(injector->hProcess, NULL, 0, (LPTHREAD_START_ROUTINE)injector->injection_code,
|
||||
injector->remote_mem + page_size + sizeof(void *), 0, NULL);
|
||||
if (hThread == NULL) {
|
||||
set_errmsg("CreateRemoteThread error: %s", w32strerr(GetLastError()));
|
||||
return INJERR_OTHER;
|
||||
}
|
||||
WaitForSingleObject(hThread, INFINITE);
|
||||
GetExitCodeThread(hThread, &err);
|
||||
CloseHandle(hThread);
|
||||
if (err != 0) {
|
||||
set_errmsg("LoadLibrary in the target process failed: %s", w32strerr(err));
|
||||
return INJERR_ERROR_IN_TARGET;
|
||||
}
|
||||
if (!ReadProcessMemory(injector->hProcess, injector->remote_mem + page_size, &data, sizeof(void *), &written)) {
|
||||
set_errmsg("ReadProcessMemory error: %s", w32strerr(GetLastError()));
|
||||
return INJERR_OTHER;
|
||||
}
|
||||
if (handle != NULL) {
|
||||
*handle = data.load_address;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int cki_uninject(injector_t *injector, void *handle)
|
||||
{
|
||||
HANDLE hThread;
|
||||
DWORD err;
|
||||
|
||||
hThread = CreateRemoteThread(injector->hProcess, NULL, 0, (LPTHREAD_START_ROUTINE)injector->uninjection_code,
|
||||
handle, 0, NULL);
|
||||
if (hThread == NULL) {
|
||||
set_errmsg("CreateRemoteThread error: %s", w32strerr(GetLastError()));
|
||||
return INJERR_OTHER;
|
||||
}
|
||||
WaitForSingleObject(hThread, INFINITE);
|
||||
GetExitCodeThread(hThread, &err);
|
||||
CloseHandle(hThread);
|
||||
if (err != 0) {
|
||||
set_errmsg("FreeLibrary in the target process failed: %s", w32strerr(err));
|
||||
return INJERR_ERROR_IN_TARGET;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int cki_detach(injector_t *injector)
|
||||
{
|
||||
if (injector->remote_mem != NULL) {
|
||||
VirtualFreeEx(injector->hProcess, injector->remote_mem, 0, MEM_RELEASE);
|
||||
}
|
||||
if (injector->hProcess != NULL) {
|
||||
CloseHandle(injector->hProcess);
|
||||
}
|
||||
free(injector);
|
||||
return 0;
|
||||
}
|
||||
|
||||
const char *cki_error(void) { return errmsg; }
|
||||
|
||||
static void set_errmsg(const char *format, ...)
|
||||
{
|
||||
va_list ap;
|
||||
int rv;
|
||||
|
||||
va_start(ap, format);
|
||||
rv = vsnprintf(errmsg, sizeof(errmsg), format, ap);
|
||||
va_end(ap);
|
||||
if (rv == -1 || rv >= sizeof(errmsg)) {
|
||||
errmsg[sizeof(errmsg) - 1] = '\0';
|
||||
}
|
||||
}
|
||||
|
||||
static const char *w32strerr(DWORD err)
|
||||
{
|
||||
static char errmsg[512];
|
||||
DWORD len;
|
||||
|
||||
len = FormatMessageA(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, NULL, err,
|
||||
MAKELANGID(LANG_ENGLISH, SUBLANG_ENGLISH_US), errmsg, sizeof(errmsg), NULL);
|
||||
if (len > 0) {
|
||||
while (len > 0) {
|
||||
char c = errmsg[len - 1];
|
||||
if (c == ' ' || c == '\n' || c == '\r') {
|
||||
len--;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
errmsg[len] = '\0';
|
||||
} else if ((int)err >= 0) {
|
||||
sprintf(errmsg, "win32 error code %d", err);
|
||||
} else {
|
||||
sprintf(errmsg, "win32 error code 0x%x", err);
|
||||
}
|
||||
return errmsg;
|
||||
}
|
||||
|
||||
static USHORT process_arch(HANDLE hProcess)
|
||||
{
|
||||
static IsWow64Process2_t IsWow64Process2_func = (IsWow64Process2_t)-1;
|
||||
if (IsWow64Process2_func == (IsWow64Process2_t)-1) {
|
||||
IsWow64Process2_func = (IsWow64Process2_t)GetProcAddress(GetModuleHandleA("kernel32"), "IsWow64Process2");
|
||||
}
|
||||
if (IsWow64Process2_func != NULL) {
|
||||
/* Windows 10 */
|
||||
USHORT process_machine;
|
||||
USHORT native_machine;
|
||||
if (IsWow64Process2_func(hProcess, &process_machine, &native_machine)) {
|
||||
if (process_machine != IMAGE_FILE_MACHINE_UNKNOWN) {
|
||||
return process_machine;
|
||||
} else {
|
||||
return native_machine;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
/* Windows 8.1 or earlier */
|
||||
/* arch will be either x86 or x64. */
|
||||
#ifdef _M_AMD64
|
||||
BOOL is_wow64_proc;
|
||||
if (IsWow64Process(hProcess, &is_wow64_proc)) {
|
||||
if (is_wow64_proc) {
|
||||
return IMAGE_FILE_MACHINE_I386;
|
||||
} else {
|
||||
return IMAGE_FILE_MACHINE_AMD64;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
#ifdef _M_IX86
|
||||
BOOL is_wow64_proc;
|
||||
if (IsWow64Process(GetCurrentProcess(), &is_wow64_proc)) {
|
||||
if (!is_wow64_proc) {
|
||||
/* Run on 32-bit Windows */
|
||||
return IMAGE_FILE_MACHINE_I386;
|
||||
}
|
||||
/* Run on Windows x64 */
|
||||
if (IsWow64Process(hProcess, &is_wow64_proc)) {
|
||||
if (is_wow64_proc) {
|
||||
return IMAGE_FILE_MACHINE_I386;
|
||||
} else {
|
||||
return IMAGE_FILE_MACHINE_AMD64;
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
return IMAGE_FILE_MACHINE_UNKNOWN;
|
||||
}
|
||||
|
||||
static const char *arch_name(USHORT arch)
|
||||
{
|
||||
switch (arch) {
|
||||
case IMAGE_FILE_MACHINE_AMD64:
|
||||
return "x64";
|
||||
case IMAGE_FILE_MACHINE_I386:
|
||||
return "x86";
|
||||
default:
|
||||
return "unknown";
|
||||
}
|
||||
}
|
||||
|
||||
BOOL InjectDll(DWORD pid, const WCHAR *dllpath)
|
||||
{
|
||||
if (cki_attach(&g_injector, pid) != 0) {
|
||||
printf("%s\n", cki_error());
|
||||
return FALSE;
|
||||
}
|
||||
if (cki_inject_w(g_injector, dllpath, NULL) == 0) {
|
||||
return TRUE;
|
||||
} else {
|
||||
fprintf(stderr, " %s\n", cki_error());
|
||||
return FALSE;
|
||||
}
|
||||
}
|
||||
|
||||
BOOL EnjectDll(DWORD pid, const WCHAR *dllname)
|
||||
{
|
||||
if (cki_detach(g_injector) == 0) {
|
||||
return TRUE;
|
||||
}
|
||||
return FALSE;
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
#ifndef __INJECTOR_H__
|
||||
#define __INJECTOR_H__
|
||||
|
||||
#include <windows.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#define INJERR_SUCCESS 0
|
||||
#define INJERR_OTHER -1
|
||||
#define INJERR_NO_MEMORY -2
|
||||
#define INJERR_NO_PROCESS -3
|
||||
#define INJERR_NO_LIBRARY -4
|
||||
#define INJERR_NO_FUNCTION -4
|
||||
#define INJERR_ERROR_IN_TARGET -5
|
||||
#define INJERR_FILE_NOT_FOUND -6
|
||||
#define INJERR_INVALID_MEMORY_AREA -7
|
||||
#define INJERR_PERMISSION -8
|
||||
#define INJERR_UNSUPPORTED_TARGET -9
|
||||
#define INJERR_INVALID_ELF_FORMAT -10
|
||||
#define INJERR_WAIT_TRACEE -11
|
||||
|
||||
typedef struct injector injector_t;
|
||||
|
||||
int cki_attach(injector_t **injector, DWORD pid);
|
||||
int cki_inject(injector_t *injector, const char *path, void **handle);
|
||||
int cki_inject_w(injector_t *injector, const wchar_t *path, void **handle);
|
||||
int cki_uninject(injector_t *injector, void *handle);
|
||||
int cki_detach(injector_t *injector);
|
||||
const char *cki_error(void);
|
||||
|
||||
BOOL InjectDll(DWORD pid, const WCHAR *dllpath);
|
||||
BOOL EnjectDll(DWORD pid, const WCHAR *dllname);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}; /* extern "C" */
|
||||
#endif
|
||||
|
||||
#endif
|
||||
+282
@@ -0,0 +1,282 @@
|
||||
#include "Shlwapi.h"
|
||||
#include "framework.h"
|
||||
#include <process.h>
|
||||
#include <queue>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <tlhelp32.h>
|
||||
#include <vector>
|
||||
|
||||
#include "../Rpc/rpc_h.h"
|
||||
#pragma comment(lib, "Rpcrt4.lib")
|
||||
|
||||
#include "injector.h"
|
||||
#include "rpc_types.h"
|
||||
#include "sdk.h"
|
||||
#include "util.h"
|
||||
|
||||
static HANDLE hEvent;
|
||||
static std::queue<RpcMessage_t> MsgQueue;
|
||||
static RPC_WSTR pszStringBinding = NULL;
|
||||
static std::function<int(WxMessage_t)> cbReceiveTextMsg;
|
||||
static const MsgTypesMap_t WxMsgTypes = MsgTypesMap_t { { 0x01, L"文字" },
|
||||
{ 0x03, L"图片" },
|
||||
{ 0x22, L"语音" },
|
||||
{ 0x25, L"好友确认" },
|
||||
{ 0x28, L"POSSIBLEFRIEND_MSG" },
|
||||
{ 0x2A, L"名片" },
|
||||
{ 0x2B, L"视频" },
|
||||
{ 0x2F, L"石头剪刀布 | 表情图片" },
|
||||
{ 0x30, L"位置" },
|
||||
{ 0x31, L"共享实时位置、文件、转账、链接" },
|
||||
{ 0x32, L"VOIPMSG" },
|
||||
{ 0x33, L"微信初始化" },
|
||||
{ 0x34, L"VOIPNOTIFY" },
|
||||
{ 0x35, L"VOIPINVITE" },
|
||||
{ 0x3E, L"小视频" },
|
||||
{ 0x270F, L"SYSNOTICE" },
|
||||
{ 0x2710, L"红包、系统消息" },
|
||||
{ 0x2712, L"撤回消息" } };
|
||||
|
||||
RPC_STATUS RpcConnectServer()
|
||||
{
|
||||
RPC_STATUS status = 0;
|
||||
// Creates a string binding handle.
|
||||
status = RpcStringBindingCompose(NULL, // UUID to bind to
|
||||
reinterpret_cast<RPC_WSTR>((RPC_WSTR)L"ncalrpc"), // Use TCP/IP protocol
|
||||
NULL, // TCP/IP network address to use
|
||||
reinterpret_cast<RPC_WSTR>((RPC_WSTR)L"tmp_endpoint"), // TCP/IP port to use
|
||||
NULL, // Protocol dependent network options to use
|
||||
&pszStringBinding); // String binding output
|
||||
|
||||
if (status)
|
||||
return status;
|
||||
|
||||
/* Validates the format of the string binding handle and converts it to a binding handle.
|
||||
pszStringBinding: The string binding to validate
|
||||
hSpyBinding: Put the result in the implicit binding(defined in the IDL file)
|
||||
*/
|
||||
status = RpcBindingFromStringBinding(pszStringBinding, &hSpyBinding);
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
RPC_STATUS RpcDisconnectServer()
|
||||
{
|
||||
RPC_STATUS status;
|
||||
// Free the memory allocated by a string
|
||||
status = RpcStringFree(&pszStringBinding);
|
||||
if (status)
|
||||
return status;
|
||||
|
||||
// Releases binding handle resources and disconnects from the server
|
||||
status = RpcBindingFree(&hSpyBinding);
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
int WxInitSDK()
|
||||
{
|
||||
int loginFlag = 0;
|
||||
unsigned long ulCode = 0;
|
||||
DWORD status = 0;
|
||||
DWORD pid = 0;
|
||||
WCHAR DllPath[MAX_PATH] = { 0 };
|
||||
|
||||
GetModuleFileNameW(GetModuleHandleW(WECHATSDKDLL), DllPath, MAX_PATH);
|
||||
PathRemoveFileSpecW(DllPath);
|
||||
PathAppendW(DllPath, WECHATINJECTDLL);
|
||||
|
||||
if (!PathFileExistsW(DllPath)) {
|
||||
return ERROR_FILE_NOT_FOUND;
|
||||
}
|
||||
|
||||
status = OpenWeChat(&pid);
|
||||
if (status != 0) {
|
||||
return status;
|
||||
}
|
||||
|
||||
if (!InjectDll(pid, DllPath)) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
RpcConnectServer();
|
||||
|
||||
while (!loginFlag) {
|
||||
RpcTryExcept
|
||||
{
|
||||
// 查询登录状态
|
||||
loginFlag = client_IsLogin();
|
||||
}
|
||||
RpcExcept(1)
|
||||
{
|
||||
ulCode = RpcExceptionCode();
|
||||
printf("Runtime reported exception 0x%lx = %ld\n", ulCode, ulCode);
|
||||
}
|
||||
RpcEndExcept
|
||||
|
||||
Sleep(1000);
|
||||
}
|
||||
|
||||
return ERROR_SUCCESS;
|
||||
}
|
||||
|
||||
static unsigned int __stdcall waitForMsg(void *p)
|
||||
{
|
||||
RpcMessage_t *rpcMsg;
|
||||
while (true) {
|
||||
// 中断式,兼顾及时性和CPU使用率
|
||||
WaitForSingleObject(hEvent, INFINITE); // 等待消息
|
||||
while (!MsgQueue.empty()) {
|
||||
rpcMsg = (RpcMessage_t *)&MsgQueue.front();
|
||||
WxMessage_t msg;
|
||||
msg.id = wstring(rpcMsg->id);
|
||||
msg.self = rpcMsg->self;
|
||||
msg.type = rpcMsg->type;
|
||||
msg.source = rpcMsg->source;
|
||||
msg.xml = wstring(rpcMsg->xml);
|
||||
msg.wxId = wstring(rpcMsg->wxId);
|
||||
msg.roomId = wstring(rpcMsg->roomId);
|
||||
msg.content = wstring(rpcMsg->content);
|
||||
|
||||
try {
|
||||
cbReceiveTextMsg(msg); // 调用接收消息回调
|
||||
} catch (...) {
|
||||
printf("callback error...\n");
|
||||
}
|
||||
MsgQueue.pop();
|
||||
}
|
||||
ResetEvent(hEvent);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static unsigned int __stdcall innerWxSetTextMsgCb(void *p)
|
||||
{
|
||||
unsigned long ulCode = 0;
|
||||
RpcTryExcept
|
||||
{
|
||||
// 建立RPC通道,让服务端能够调用客户端的回调函数。(该接口会被服务端阻塞直到异常退出)
|
||||
client_EnableReceiveMsg();
|
||||
}
|
||||
RpcExcept(1)
|
||||
{
|
||||
ulCode = RpcExceptionCode();
|
||||
printf("Runtime reported exception 0x%lx = %ld\n", ulCode, ulCode);
|
||||
}
|
||||
RpcEndExcept
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int WxSetTextMsgCb(const std::function<int(WxMessage_t)> &onMsg)
|
||||
{
|
||||
if (onMsg) {
|
||||
HANDLE msgThread;
|
||||
cbReceiveTextMsg = onMsg;
|
||||
hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
|
||||
msgThread = (HANDLE)_beginthreadex(NULL, 0, waitForMsg, NULL, 0, NULL);
|
||||
if (msgThread == NULL) {
|
||||
printf("Failed to create message listening thread.\n");
|
||||
return -2;
|
||||
}
|
||||
CloseHandle(msgThread);
|
||||
|
||||
msgThread = (HANDLE)_beginthreadex(NULL, 0, innerWxSetTextMsgCb, NULL, 0, NULL);
|
||||
if (msgThread == NULL) {
|
||||
printf("Failed to create innerWxRecvTextMsg.\n");
|
||||
return -2;
|
||||
}
|
||||
CloseHandle(msgThread);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
printf("Empty Callback.\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
int server_ReceiveMsg(RpcMessage_t *rpcMsg)
|
||||
{
|
||||
MsgQueue.push(*rpcMsg); // 发送消息
|
||||
SetEvent(hEvent); // 发送消息通知
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int innerWxSendTextMsg(const wchar_t *wxid, const wchar_t *at_wxid, const wchar_t *msg)
|
||||
{
|
||||
int ret = 0;
|
||||
unsigned long ulCode = 0;
|
||||
|
||||
RpcTryExcept { ret = client_SendTextMsg(wxid, at_wxid, msg); }
|
||||
RpcExcept(1)
|
||||
{
|
||||
ulCode = RpcExceptionCode();
|
||||
printf("Runtime reported exception 0x%lx = %ld\n", ulCode, ulCode);
|
||||
}
|
||||
RpcEndExcept
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
int WxSendTextMsg(wstring wxid, wstring at_wxid, wstring msg)
|
||||
{
|
||||
return innerWxSendTextMsg(wxid.c_str(), at_wxid.c_str(), msg.c_str());
|
||||
}
|
||||
|
||||
static int getAddrHandle(DWORD *addr, HANDLE *handle)
|
||||
{
|
||||
DWORD processID = 0;
|
||||
wstring processName = L"WeChat.exe";
|
||||
wstring moduleName = L"WeChatWin.dll";
|
||||
|
||||
HANDLE hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
|
||||
PROCESSENTRY32 pe32 = { sizeof(PROCESSENTRY32) };
|
||||
while (Process32Next(hSnapshot, &pe32)) {
|
||||
wstring strProcess = pe32.szExeFile;
|
||||
if (strProcess == processName) {
|
||||
processID = pe32.th32ProcessID;
|
||||
break;
|
||||
}
|
||||
}
|
||||
CloseHandle(hSnapshot);
|
||||
if (processID == 0) {
|
||||
printf("Failed to get Process ID\r\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
HANDLE hProcessSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPMODULE | TH32CS_SNAPMODULE32, processID);
|
||||
if (hProcessSnapshot == INVALID_HANDLE_VALUE) {
|
||||
printf("Failed to get Process Snapshot\r\n");
|
||||
return -2;
|
||||
}
|
||||
|
||||
MODULEENTRY32 me32;
|
||||
SecureZeroMemory(&me32, sizeof(MODULEENTRY32));
|
||||
me32.dwSize = sizeof(MODULEENTRY32);
|
||||
while (Module32Next(hProcessSnapshot, &me32)) {
|
||||
me32.dwSize = sizeof(MODULEENTRY32);
|
||||
|
||||
if (!wcscmp(me32.szModule, moduleName.c_str())) {
|
||||
*addr = (DWORD)me32.modBaseAddr;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
CloseHandle(hProcessSnapshot);
|
||||
if (*addr == 0) {
|
||||
printf("Failed to get Module Address\r\n");
|
||||
return -3;
|
||||
}
|
||||
|
||||
*handle = OpenProcess(PROCESS_VM_READ, FALSE, processID);
|
||||
if (*handle == 0) {
|
||||
printf("Failed to open Process\r\n");
|
||||
return -4;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
MsgTypesMap_t WxGetMsgTypes() { return WxMsgTypes; }
|
||||
@@ -0,0 +1,5 @@
|
||||
EXPORTS
|
||||
WxInitSDK
|
||||
WxSetTextMsgCb
|
||||
WxSendTextMsg
|
||||
WxGetMsgTypes
|
||||
@@ -0,0 +1,25 @@
|
||||
#pragma once
|
||||
|
||||
#include <functional>
|
||||
#include <map>
|
||||
#include <string>
|
||||
|
||||
using namespace std;
|
||||
|
||||
typedef struct WxMessage {
|
||||
int self; // 是否自己发的消息:0=否,1=是
|
||||
int type; // 消息类型
|
||||
int source; // 消息来源:0=好友消息,1=群消息
|
||||
wstring id; // 消息ID
|
||||
wstring xml; // 群其他消息
|
||||
wstring wxId; // 发送人微信ID
|
||||
wstring roomId; // 群ID
|
||||
wstring content; // 消息内容,MAC版最大:16384,即16KB
|
||||
} WxMessage_t;
|
||||
|
||||
typedef map<int, wstring> MsgTypesMap_t;
|
||||
|
||||
int WxInitSDK();
|
||||
int WxSetTextMsgCb(const std::function<int(WxMessage_t)> &onMsg);
|
||||
int WxSendTextMsg(wstring wxid, wstring at_wxid, wstring msg);
|
||||
MsgTypesMap_t WxGetMsgTypes();
|
||||
+191
@@ -0,0 +1,191 @@
|
||||
#include "Shlwapi.h"
|
||||
#include "framework.h"
|
||||
#include <string.h>
|
||||
#include <strsafe.h>
|
||||
#include <wchar.h>
|
||||
|
||||
#include "util.h"
|
||||
|
||||
#pragma comment(lib, "shlwapi")
|
||||
#pragma comment(lib, "Version.lib")
|
||||
|
||||
using namespace std;
|
||||
|
||||
int GetWeChatPath(wchar_t *path);
|
||||
int GetWeChatWinDLLPath(wchar_t *path);
|
||||
int GetWeChatVersion(wchar_t *version);
|
||||
bool GetFileVersion(const wchar_t *filePath, wchar_t *version);
|
||||
|
||||
int GetWeChatPath(wchar_t *path)
|
||||
{
|
||||
int ret = -1;
|
||||
HKEY hKey = NULL;
|
||||
// HKEY_CURRENT_USER\Software\Tencent\WeChat InstallPath = xx
|
||||
if (ERROR_SUCCESS != RegOpenKey(HKEY_CURRENT_USER, L"Software\\Tencent\\WeChat", &hKey)) {
|
||||
ret = GetLastError();
|
||||
return ret;
|
||||
}
|
||||
|
||||
DWORD Type = REG_SZ;
|
||||
DWORD cbData = MAX_PATH * sizeof(WCHAR);
|
||||
if (ERROR_SUCCESS != RegQueryValueEx(hKey, L"InstallPath", 0, &Type, (LPBYTE)path, &cbData)) {
|
||||
ret = GetLastError();
|
||||
goto __exit;
|
||||
}
|
||||
|
||||
if (path != NULL) {
|
||||
PathAppend(path, WECHAREXE);
|
||||
}
|
||||
|
||||
__exit:
|
||||
if (hKey) {
|
||||
RegCloseKey(hKey);
|
||||
}
|
||||
|
||||
return ERROR_SUCCESS;
|
||||
}
|
||||
|
||||
int GetWeChatWinDLLPath(wchar_t *path)
|
||||
{
|
||||
int ret = GetWeChatPath(path);
|
||||
if (ret != ERROR_SUCCESS) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
PathRemoveFileSpecW(path);
|
||||
PathAppendW(path, WECHATWINDLL);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
int GetWeChatVersion(wchar_t *version)
|
||||
{
|
||||
WCHAR Path[MAX_PATH] = { 0 };
|
||||
|
||||
int ret = GetWeChatWinDLLPath(Path);
|
||||
if (ret != ERROR_SUCCESS) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = GetFileVersion(Path, version);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool GetFileVersion(const wchar_t *filePath, wchar_t *version)
|
||||
{
|
||||
if (wcslen(filePath) > 0 && PathFileExists(filePath)) {
|
||||
VS_FIXEDFILEINFO *pVerInfo = NULL;
|
||||
DWORD dwTemp, dwSize;
|
||||
BYTE *pData = NULL;
|
||||
UINT uLen;
|
||||
|
||||
dwSize = GetFileVersionInfoSize(filePath, &dwTemp);
|
||||
if (dwSize == 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
pData = new BYTE[dwSize + 1];
|
||||
if (pData == NULL) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!GetFileVersionInfo(filePath, 0, dwSize, pData)) {
|
||||
delete[] pData;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!VerQueryValue(pData, TEXT("\\"), (void **)&pVerInfo, &uLen)) {
|
||||
delete[] pData;
|
||||
return false;
|
||||
}
|
||||
|
||||
DWORD verMS = pVerInfo->dwFileVersionMS;
|
||||
DWORD verLS = pVerInfo->dwFileVersionLS;
|
||||
DWORD major = HIWORD(verMS);
|
||||
DWORD minor = LOWORD(verMS);
|
||||
DWORD build = HIWORD(verLS);
|
||||
DWORD revision = LOWORD(verLS);
|
||||
delete[] pData;
|
||||
|
||||
StringCbPrintf(version, 0x20, TEXT("%d.%d.%d.%d"), major, minor, build, revision);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
int OpenWeChat(DWORD *pid)
|
||||
{
|
||||
int ret = -1;
|
||||
STARTUPINFO si = { sizeof(si) };
|
||||
PROCESS_INFORMATION pi = { 0 };
|
||||
|
||||
WCHAR Path[MAX_PATH] = { 0 };
|
||||
ret = GetWeChatPath(Path);
|
||||
if (ERROR_SUCCESS != ret) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
if (!CreateProcess(NULL, Path, NULL, NULL, FALSE, CREATE_NEW_CONSOLE, NULL, NULL, &si, &pi)) {
|
||||
ret = GetLastError();
|
||||
return ret;
|
||||
}
|
||||
|
||||
CloseHandle(pi.hThread);
|
||||
CloseHandle(pi.hProcess);
|
||||
|
||||
*pid = pi.dwProcessId;
|
||||
|
||||
ret = ERROR_SUCCESS;
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
int GetWstringByAddress(DWORD address, wchar_t *buffer, DWORD buffer_size)
|
||||
{
|
||||
DWORD strLength = GET_DWORD(address + 4);
|
||||
if (strLength == 0) {
|
||||
return 0;
|
||||
} else if (strLength > buffer_size) {
|
||||
strLength = buffer_size - 1;
|
||||
}
|
||||
|
||||
wmemcpy_s(buffer, strLength + 1, GET_WSTRING(address), strLength + 1);
|
||||
|
||||
return strLength;
|
||||
}
|
||||
|
||||
DWORD GetMemoryIntByAddress(HANDLE hProcess, DWORD address)
|
||||
{
|
||||
DWORD value = 0;
|
||||
|
||||
unsigned char data[4] = { 0 };
|
||||
if (ReadProcessMemory(hProcess, (LPVOID)address, data, 4, 0)) {
|
||||
value = data[0] & 0xFF;
|
||||
value |= ((data[1] << 8) & 0xFF00);
|
||||
value |= ((data[2] << 16) & 0xFF0000);
|
||||
value |= ((data[3] << 24) & 0xFF000000);
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
wstring GetUnicodeInfoByAddress(HANDLE hProcess, DWORD address)
|
||||
{
|
||||
wstring value = L"";
|
||||
|
||||
DWORD strAddress = GetMemoryIntByAddress(hProcess, address);
|
||||
DWORD strLen = GetMemoryIntByAddress(hProcess, address + 0x4);
|
||||
if (strLen > 500)
|
||||
return value;
|
||||
|
||||
wchar_t cValue[500] = { 0 };
|
||||
memset(cValue, 0, sizeof(cValue) / sizeof(wchar_t));
|
||||
if (ReadProcessMemory(hProcess, (LPVOID)strAddress, cValue, (strLen + 1) * 2, 0)) {
|
||||
value = wstring(cValue);
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
|
||||
#define WECHAREXE L"WeChat.exe"
|
||||
#define WECHATWINDLL L"WeChatWin.dll"
|
||||
#define WECHATSDKDLL L"SDK.dll"
|
||||
#define WECHATINJECTDLL L"Spy.dll"
|
||||
|
||||
#define GET_DWORD(addr) ((DWORD) * (DWORD *)(addr))
|
||||
#define GET_STRING(addr) ((CHAR *)(addr))
|
||||
#define GET_WSTRING(addr) ((WCHAR *)(*(DWORD *)(addr)))
|
||||
|
||||
int OpenWeChat(DWORD *pid);
|
||||
int GetWeChatPath(wchar_t *path);
|
||||
int GetWeChatWinDLLPath(wchar_t *path);
|
||||
int GetWeChatVersion(wchar_t *version);
|
||||
bool GetFileVersion(const wchar_t *filePath, wchar_t *version);
|
||||
int GetWstringByAddress(DWORD address, wchar_t *buffer, DWORD buffer_size);
|
||||
DWORD GetMemoryIntByAddress(HANDLE hProcess, DWORD address);
|
||||
std::wstring GetUnicodeInfoByAddress(HANDLE hProcess, DWORD address);
|
||||
Reference in New Issue
Block a user