2024腾讯游戏安全PC初赛复现
本帖最后由 Kvancy 于 2024-9-23 14:03 编辑## 题目
## (一)解题过程
拿到hack.exe,浅分析一下发现加了VM,并且有检测黑客工具的行为,检测到了之后即使关闭黑客程序也会影响程序正常运行,但是xdbg稍微改一下还是可以动调的,在xdbg里下一些可能的函数断点,我这里在这些地方下了断点
运行发现程序会多次在`WriteProcessMemory`下断下,hook一下观察传参
```c
//dllmain.cpp
#include "pch.h"
#include <windows.h>
#include <shellapi.h>
#include <detours.h>
#include <tlhelp32.h>
#include <stdlib.h>
#include <stdio.h>
#pragma comment(lib,"detours.lib")
#define _KDEBUG
#define DBGMGEBOX(fmt, ...) \
do { \
/* 假设最大长度为1024,根据需要调整大小 */ \
wsprintfA(out, fmt, __VA_ARGS__); \
MessageBoxA(NULL, out, "提示", MB_OK); \
} while(0)
char out;
typedef BOOL(WINAPI* WriteProcessMemory_t)(
_In_ HANDLE hProcess,
_In_ LPVOID lpBaseAddress,
_In_reads_bytes_(nSize) LPCVOID lpBuffer,
_In_ SIZE_T nSize,
_Out_opt_ SIZE_T* lpNumberOfBytesWritten
);
WriteProcessMemory_t TrueWriteProcessMemory = NULL;
BOOL
WINAPI
HookWriteProcessMemory(
_In_ HANDLE hProcess,
_In_ LPVOID lpBaseAddress,
_In_reads_bytes_(nSize) LPCVOID lpBuffer,
_In_ SIZE_T nSize,
_Out_opt_ SIZE_T * lpNumberOfBytesWritten
)
{
char fileName = { 0 };
sprintf(fileName, "out%d.txt", (int)hProcess % 1000);
HANDLE hFile = CreateFile(fileName, FILE_APPEND_DATA, FILE_SHARE_READ, NULL, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
if (hFile == INVALID_HANDLE_VALUE)
{
DBGMGEBOX("CreateFile Fail");
return TrueWriteProcessMemory(hProcess, lpBaseAddress, lpBuffer, nSize, lpNumberOfBytesWritten);
}
SetFilePointer(hFile, 0, NULL, FILE_END);
DWORD bytesWritten;
BOOL result = WriteFile(hFile, lpBuffer, nSize, &bytesWritten, NULL);
CloseHandle(hFile);
DBGMGEBOX("findProcess WriteProcessMemory:%p,size:%d\n", hProcess,nSize);
return TrueWriteProcessMemory(hProcess, lpBaseAddress, lpBuffer, nSize, lpNumberOfBytesWritten);
}
BOOL APIENTRY DllMain(HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved) {
switch (ul_reason_for_call) {
case DLL_PROCESS_ATTACH:
DetourTransactionBegin();
DetourUpdateThread(GetCurrentThread());
TrueWriteProcessMemory = (WriteProcessMemory_t)DetourFindFunction("kernel32.dll", "WriteProcessMemory");
DetourAttach(&(PVOID&)TrueWriteProcessMemory, HookWriteProcessMemory);
DetourTransactionCommit();
break;
case DLL_PROCESS_DETACH:
DetourTransactionBegin();
DetourUpdateThread(GetCurrentThread());
DetourDetach(&(PVOID&)TrueWriteProcessMemory, HookWriteProcessMemory);
DetourTransactionCommit();
break;
}
return TRUE;
}
```
这里输出了三个txt文件
其中out200.txt文件有明显的PE头
去除前面的字节,把文件丢到DIE里分析一下发现是dll64文件并且貌似没加壳,所以hack.exe通过`WriteProcessMemory`往某个进程写入了一个dll?怀疑是远程注入,至于做了什么,有可能跟token有关,继续分析。
ida64打开发现程序的dllMain入口还是被加密了
还是继续动调,随便找了个64位的可执行文件,拖到X64dbg里运行,直接用xdbg的注入方式将out200.dll注入进程,在
入口点下断点,并且对一些可疑的WINDOWS API下断观察进程行为
这里我对下面这几个API下了断点
运行,第一次成功在openProcess函数断下
观察传参窗口,找到了一个系统进程名称字符串`winlogon.exe`,而这里调用的是openProcess,疑似是对系统进程winlogon.exe做了一些操作。继续分析,运行到返回,回溯一层函数,找到一段没有被加密的代码
汇编代码不是很好看,根据偏移在IDA里反汇编看看
```c
__int64 __fastcall sub_1800063D0(_DWORD *Dst, DWORD dwProcessId)
{
__m128i si128; // xmm0
__m128i v6; // xmm0
__int64 result; // rax
__m128i v10; // xmm2
size_t v14; // rbx
__int64 v15; // rax
int v16; // r14d
HANDLE Toolhelp32Snapshot; // rsi
HANDLE v18; // rax
__int64 v21; // rbx
CHAR Caption; // BYREF
_RBP = (unsigned __int64)Caption & 0xFFFFFFFFFFFFFFE0ui64;
if ( !dwProcessId )
{
*(_QWORD *)(((unsigned __int64)Caption & 0xFFFFFFFFFFFFFFE0ui64) + 0x28) = 0xC65BF3E99CAA093Cui64;
*(_QWORD *)(((unsigned __int64)Caption & 0xFFFFFFFFFFFFFFE0ui64) + 8) = 0xC65BF3E99CAA093Cui64;
*(_QWORD *)_RBP = 0xE795A71250E2465Aui64;
si128 = _mm_load_si128((const __m128i *)_RBP);
*(_QWORD *)(((unsigned __int64)Caption & 0xFFFFFFFFFFFFFFE0ui64) + 0x20) = 0xE795A7603F90341Fui64;
v6 = _mm_xor_si128(si128, *(__m128i *)(((unsigned __int64)Caption & 0xFFFFFFFFFFFFFFE0ui64) + 0x20));
*(_QWORD *)(((unsigned __int64)Caption & 0xFFFFFFFFFFFFFFE0ui64) + 0x20) = 0x84FAD5301FE45158ui64;
*(__m128i *)_RBP = v6;
*(_QWORD *)(((unsigned __int64)Caption & 0xFFFFFFFFFFFFFFE0ui64) + 0x28) = 0xBF19D3ADD5D97A59ui64;
*(_QWORD *)(((unsigned __int64)Caption & 0xFFFFFFFFFFFFFFE0ui64) + 0x1A0) = 0xE795A7603F90341Fui64;
*(_QWORD *)(((unsigned __int64)Caption & 0xFFFFFFFFFFFFFFE0ui64) + 0x30) = 0xBD1C9CA3F4C12EC9ui64;
*(_QWORD *)(((unsigned __int64)Caption & 0xFFFFFFFFFFFFFFE0ui64) + 0x38) = 0xA727C05763438E84ui64;
*(_QWORD *)(((unsigned __int64)Caption & 0xFFFFFFFFFFFFFFE0ui64) + 0x1A8) = 0xC65BF3E99CAA093Cui64;
*(_QWORD *)(((unsigned __int64)Caption & 0xFFFFFFFFFFFFFFE0ui64) + 0x1B0) = 0xCF59BCC699A060E9ui64;
*(_QWORD *)(((unsigned __int64)Caption & 0xFFFFFFFFFFFFFFE0ui64) + 0x1B8) = 0xA727C0574231E1F6ui64;
__asm
{
vmovdqu ymm0,
vpxor ymm1, ymm0, ymmword ptr
vmovdqa ymmword ptr , ymm1
vzeroupper
}
MessageBoxA(0i64, (LPCSTR)(_RBP + 32), (LPCSTR)((unsigned __int64)Caption & 0xFFFFFFFFFFFFFFE0ui64), 0);
LABEL_3:
GetLastError();
*(_QWORD *)(((unsigned __int64)Caption & 0xFFFFFFFFFFFFFFE0ui64) + 0x20) = 0xE795A7603F90341Fui64;
*(_QWORD *)_RBP = 0x88D6871250E2465Aui64;
*(_QWORD *)(((unsigned __int64)Caption & 0xFFFFFFFFFFFFFFE0ui64) + 0x28) = 0xC65BF3E99CAA093Cui64;
*(_QWORD *)(((unsigned __int64)Caption & 0xFFFFFFFFFFFFFFE0ui64) + 8) = 0xC65BF98DB9906C58ui64;
*(__m128i *)_RBP = _mm_xor_si128(
_mm_load_si128((const __m128i *)_RBP),
*(__m128i *)(((unsigned __int64)Caption & 0xFFFFFFFFFFFFFFE0ui64) + 0x20));
return sub_180006A00((void *)((unsigned __int64)Caption & 0xFFFFFFFFFFFFFFE0ui64));
}
if ( !(unsigned __int8)sub_180006FC0() )
{
*(_QWORD *)(((unsigned __int64)Caption & 0xFFFFFFFFFFFFFFE0ui64) + 0x1A0) = 0xE795A7603F90341Fui64;
*(_QWORD *)(((unsigned __int64)Caption & 0xFFFFFFFFFFFFFFE0ui64) + 0x48) = 0x44F651D568826090i64;
*(_QWORD *)(((unsigned __int64)Caption & 0xFFFFFFFFFFFFFFE0ui64) + 0x1C0) = 0x52C9FCDC77FF5FC3i64;
*(_QWORD *)(((unsigned __int64)Caption & 0xFFFFFFFFFFFFFFE0ui64) + 0x20) = 0xDDD2E92971C27548ui64;
*(_QWORD *)(((unsigned __int64)Caption & 0xFFFFFFFFFFFFFFE0ui64) + 0x28) = 0xA43EB7C9E8CF4E1Cui64;
*(_QWORD *)(((unsigned __int64)Caption & 0xFFFFFFFFFFFFFFE0ui64) + 0x30) = 0xA62FD5B4C980079Cui64;
*(_QWORD *)(((unsigned __int64)Caption & 0xFFFFFFFFFFFFFFE0ui64) + 0x38) = 0xD55585772756849Aui64;
*(_QWORD *)(((unsigned __int64)Caption & 0xFFFFFFFFFFFFFFE0ui64) + 0x40) = 0x52C9FCDC7DDE2DACi64;
v10 = _mm_load_si128((const __m128i *)(_RBP + 64));
*(_QWORD *)(((unsigned __int64)Caption & 0xFFFFFFFFFFFFFFE0ui64) + 0x1C8) = 0x44F651D568826090i64;
_XMM2 = _mm_xor_si128(v10, *(__m128i *)(((unsigned __int64)Caption & 0xFFFFFFFFFFFFFFE0ui64) + 0x1C0));
*(_QWORD *)(((unsigned __int64)Caption & 0xFFFFFFFFFFFFFFE0ui64) + 0x1A8) = 0xC65BF3E99CAA093Cui64;
*(_QWORD *)(((unsigned __int64)Caption & 0xFFFFFFFFFFFFFFE0ui64) + 0x1B0) = 0xCF59BCC699A060E9ui64;
*(_QWORD *)(((unsigned __int64)Caption & 0xFFFFFFFFFFFFFFE0ui64) + 0x1B8) = 0xA727C0574231E1F6ui64;
__asm
{
vmovdqu ymm0,
vpxor ymm1, ymm0, ymmword ptr
vmovdqa , xmm2
vmovdqa ymmword ptr , ymm1
vzeroupper
}
sub_180006A00((void *)(_RBP + 32));
}
v14 = -1i64;
if ( dwProcessId == -1 )
{
Dst = GetCurrentProcessId();
*((_QWORD *)Dst + 13) = -1i64;
}
else
{
Dst = dwProcessId;
v18 = OpenProcess(0x1FFFFFu, 0, dwProcessId);
*((_QWORD *)Dst + 13) = v18;
if ( !v18 )
{
*(_QWORD *)(((unsigned __int64)Caption & 0xFFFFFFFFFFFFFFE0ui64) + 0x38) = 0xA727C0574231E1F6ui64;
*(_QWORD *)(((unsigned __int64)Caption & 0xFFFFFFFFFFFFFFE0ui64) + 0x20) = 0x84FAD53051F54450ui64;
*(_QWORD *)(((unsigned __int64)Caption & 0xFFFFFFFFFFFFFFE0ui64) + 0x1A0) = 0xE795A7603F90341Fui64;
*(_QWORD *)(((unsigned __int64)Caption & 0xFFFFFFFFFFFFFFE0ui64) + 0x28) = 0xA92981ACBCD97A59ui64;
*(_QWORD *)(((unsigned __int64)Caption & 0xFFFFFFFFFFFFFFE0ui64) + 0x30) = 0xCF59BCC699AA419Bui64;
*(_QWORD *)(((unsigned __int64)Caption & 0xFFFFFFFFFFFFFFE0ui64) + 0x1A8) = 0xC65BF3E99CAA093Cui64;
*(_QWORD *)(((unsigned __int64)Caption & 0xFFFFFFFFFFFFFFE0ui64) + 0x1B0) = 0xCF59BCC699A060E9ui64;
*(_QWORD *)(((unsigned __int64)Caption & 0xFFFFFFFFFFFFFFE0ui64) + 0x1B8) = 0xA727C0574231E1F6ui64;
__asm
{
vmovdqu ymm0,
vpxor ymm1, ymm0, ymmword ptr
vmovdqa ymmword ptr , ymm1
vzeroupper
}
sub_180006A00((void *)(_RBP + 32));//打印报错信息
goto LABEL_3;
}
}
Dst = 0x1FFFFF;
v15 = -1i64;
do
++v15;
while ( *((_BYTE *)Dst + v15) );
if ( !v15 )
{
v16 = Dst;
Toolhelp32Snapshot = CreateToolhelp32Snapshot(2u, 0);
if ( Toolhelp32Snapshot != (HANDLE)-1i64 )
{
*(_DWORD *)(((unsigned __int64)Caption & 0xFFFFFFFFFFFFFFE0ui64) + 0x60) = 304;
memset((void *)(_RBP + 100), 0, 0x12Cui64);
if ( Process32First(Toolhelp32Snapshot, (LPPROCESSENTRY32)(_RBP + 96)) )
{
while ( *(_DWORD *)(((unsigned __int64)Caption & 0xFFFFFFFFFFFFFFE0ui64) + 0x68) != v16 )
{
if ( !Process32Next(Toolhelp32Snapshot, (LPPROCESSENTRY32)(_RBP + 96)) )
goto LABEL_20;
}
do
++v14;
while ( *(_BYTE *)(_RBP + 140 + v14) );
memmove(Dst, (const void *)(_RBP + 140), v14);
}
LABEL_20:
CloseHandle(Toolhelp32Snapshot);
}
}
*((_QWORD *)Dst + 18) = sub_1800068D0(Dst, Dst);
v21 = 0i64;
*(_DWORD *)(((unsigned __int64)Caption & 0xFFFFFFFFFFFFFFE0ui64) + 8) = Dst;
*(_QWORD *)_RBP = 0i64;
EnumWindows(EnumFunc, (unsigned __int64)Caption & 0xFFFFFFFFFFFFFFE0ui64);
result = *(_QWORD *)_RBP;
if ( *(_QWORD *)_RBP )
v21 = *(_QWORD *)_RBP;
*((_QWORD *)Dst + 16) = v21;
return result;
}
```
这段代码一次进行了获取进程pid,打开进程,遍历模块等操作,并且在函数失败后做了一些奇奇怪怪的东西,对一些地址赋上了一些64位的值,猜测是隐藏字符串来打印调试信息用的,再通过messagebox和outputDebugString给出调试信息,显示打开进程失败,猜测是因为hack.exe启动是管理员启动,这里失去了管理员权限。
分析完这个函数,继续回溯一层,运行到返回,定位到这个地方
继续根据偏移转到IDA里看反汇编
```c
int sub_180001990()
{
size_t v0; // rbx
DWORD v1; // eax
__m128i Dst; // BYREF
__int64 v4; //
__int64 v5; //
__m128i Src; // BYREF
Dst.m128i_i64 = 0xE795A7603F90341Fui64;
Dst.m128i_i64 = 0xC65BF3E99CAA093Cui64;
Src.m128i_i64 = 0x89FAC00F53FE5D68ui64;
Src.m128i_i64 = 0xC65BF3E9F9D26C12ui64;
Src = _mm_xor_si128(_mm_load_si128(&Src), Dst);
v0 = -1i64;
do
++v0;
while ( Src.m128i_i8 );
memmove(dword_1800349A0, &Src, v0);
Dst.m128i_i64 = 0i64;
v4 = 0i64;
v5 = 15i64;
sub_180004770(&Dst, &Src, v0);
v1 = sub_1800070A0(&Dst);
sub_1800063D0(dword_1800349A0, v1);
return atexit(sub_180020C90);
}
```
前面应该是一个加密的字符串操作,用python打印出字符串
```py
def hex_xor_to_string(a, b):
result = a ^ b
hex_str = hex(result)
if len(hex_str) % 2 != 0:
hex_str = '0' + hex_str
result_str = ''.join(chr(int(hex_str, 16)) for i in range(0, len(hex_str), 2))
return result_str
x1 = 0xE795A7603F90341F
x2 = 0xC65BF3E99CAA093C
y1 = 0x89FAC00F53FE5D68
y2 = 0xC65BF3E9F9D26C12
result1 = hex_xor_to_string(x1, y1)
result2 = hex_xor_to_string(x2, y2)
print("Result 1:", result1)
print("Result 2:", result2)
#Result 1: nogolniw
#Result 2: exe.
```
得到的刚好是winlogon.exe字符串,然后程序将这个字符串转移到了dword_1800349A0全局变量中,目的应该是隐藏字符串,接着sub_180004770函数也是一个类似memmove操作,把这个字符串传到了Dst局部变量中,接着在sub_1800070A0中传入这个字符串,貌似是在根据字符串获取进程PID,接着调用sub_1800063D0函数根据pid打开进程,并将进程句柄存储到了某个地方
```c
v18 = OpenProcess(0x1FFFFFu, 0, dwProcessId);
*((_QWORD *)Dst + 13) = v18;
```
随后return exit退出。
随后我在退出函数传参的时候看到了一个hProcess
一个全局变量,很有可能在其他地方对句柄进行了读取,交叉引用一下定位到如下函数
```c
void sub_180007C10()
{
HANDLE v1; // rcx
void *v2; // rdx
__int128 v3; // xmm0
__int128 v4; // xmm1
HANDLE FileA; // rbx
__int64 v8; // rcx
_BYTE *v9; // rdx
unsigned __int64 v10; // rdx
_QWORD *v11; // rcx
char Buffer; // BYREF
_RBP = (unsigned __int64)&Buffer & 0xFFFFFFFFFFFFFFE0ui64;
while ( byte_180032C00 )
{
if ( !byte_180034961 && !byte_180034960 )
sub_1800041D0();
v1 = hProcess;
v2 = (void *)(qword_180034968 + 2766);
*(_BYTE *)_RBP = 15;
WriteProcessMemory(v1, v2, (LPCVOID)((unsigned __int64)&Buffer & 0xFFFFFFFFFFFFFFE0ui64), 1ui64, 0i64);
byte_180032C00 = 0;
*(_QWORD *)(((unsigned __int64)&Buffer & 0xFFFFFFFFFFFFFFE0ui64) + 0x20) = 0xAA32D3B2B7C50388ui64;
*(_QWORD *)(((unsigned __int64)&Buffer & 0xFFFFFFFFFFFFFFE0ui64) + 0x10) = 0xA0A195500DCC0E5Cui64;
*(_QWORD *)(((unsigned __int64)&Buffer & 0xFFFFFFFFFFFFFFE0ui64) + 0x18) = 0x943E9588CFCF645Dui64;
v3 = *(_OWORD *)(((unsigned __int64)&Buffer & 0xFFFFFFFFFFFFFFE0ui64) + 0x10);
*(_QWORD *)(((unsigned __int64)&Buffer & 0xFFFFFFFFFFFFFFE0ui64) + 0x28) = 0xA727C0574231D098ui64;
v4 = *(_OWORD *)(((unsigned __int64)&Buffer & 0xFFFFFFFFFFFFFFE0ui64) + 0x20);
*(_QWORD *)(((unsigned __int64)&Buffer & 0xFFFFFFFFFFFFFFE0ui64) + 0x80) = 0xE795A7603F90341Fui64;
*(_OWORD *)(((unsigned __int64)&Buffer & 0xFFFFFFFFFFFFFFE0ui64) + 0x60) = v3;
*(_QWORD *)(((unsigned __int64)&Buffer & 0xFFFFFFFFFFFFFFE0ui64) + 0x88) = 0xC65BF3E99CAA093Cui64;
*(_OWORD *)(((unsigned __int64)&Buffer & 0xFFFFFFFFFFFFFFE0ui64) + 0x70) = v4;
*(_QWORD *)(((unsigned __int64)&Buffer & 0xFFFFFFFFFFFFFFE0ui64) + 0x90) = 0xCF59BCC699A060E9ui64;
*(_QWORD *)(((unsigned __int64)&Buffer & 0xFFFFFFFFFFFFFFE0ui64) + 0x98) = 0xA727C0574231E1F6ui64;
__asm
{
vmovdqu ymm0,
vpxor ymm1, ymm0, ymmword ptr
vmovdqa ymmword ptr , ymm1
vzeroupper
}
FileA = CreateFileA((LPCSTR)(_RBP + 96), 0x40000000u, 0, 0i64, 3u, 0x80u, 0i64);
if ( FileA != (HANDLE)-1i64 )
{
((void (__fastcall *)(unsigned __int64))loc_180007A20)(_RBP + 48);
v8 = -1i64;
if ( *(_QWORD *)(((unsigned __int64)&Buffer & 0xFFFFFFFFFFFFFFE0ui64) + 0x48) < 0x10ui64 )
{
do
++v8;
while ( *(_BYTE *)(_RBP + 48 + v8) );
v9 = (_BYTE *)(_RBP + 48);
}
else
{
v9 = *(_BYTE **)(((unsigned __int64)&Buffer & 0xFFFFFFFFFFFFFFE0ui64) + 0x30);
do
++v8;
while ( v9 );
}
WriteFile(FileA, v9, v8, (LPDWORD)(_RBP + 8), 0i64);
CloseHandle(FileA);
v10 = *(_QWORD *)(((unsigned __int64)&Buffer & 0xFFFFFFFFFFFFFFE0ui64) + 0x48);
if ( v10 >= 0x10 )
{
v11 = *(_QWORD **)(((unsigned __int64)&Buffer & 0xFFFFFFFFFFFFFFE0ui64) + 0x30);
if ( v10 + 1 >= 0x1000 )
{
v11 = (_QWORD *)*(v11 - 1);
if ( (unsigned __int64)(*(_QWORD *)(((unsigned __int64)&Buffer & 0xFFFFFFFFFFFFFFE0ui64) + 0x30)
- (_QWORD)v11
- 8i64) > 0x1F )
invalid_parameter_noinfo_noreturn();
}
j_j_free(v11);
}
}
}
}
```
看到了有WriteProcessMemory写入hProcess内存操作,CreateFileA,WriteFile,打开和写入文件操作,但是并没有找到hProcess的赋值语句,也就是说这个进程句柄还不知道是谁的,猜测赋值被隐藏了,但是可以猜测可能是winlogon.exe进程句柄。byte_180032C00是一个全局的标志变量,强制函数只能执行一次,对应的是运行程序时仅一次的初始化操作。接着看一下CreateFileA函数,同样的文件名被隐藏了,python解析一下
```py
def hex_xor_to_string(a, b):
result = a ^ b
hex_str = hex(result)
if len(hex_str) % 2 != 0:
hex_str = '0' + hex_str
result_str = ''.join(chr(int(hex_str, 16)) for i in range(0, len(hex_str), 2))
return result_str
x1 = 0xA0A195500DCC0E5C
x2 = 0x943E9588CFCF645D
x3 = 0xAA32D3B2B7C50388
x4 = 0xA727C0574231D098
y1 = 0xE795A7603F90341F
y2 = 0xC65BF3E99CAA093C
y3 = 0xCF59BCC699A060E9
y4 = 0xA727C0574231E1F6
result1 = hex_xor_to_string(x1, y1)
result2 = hex_xor_to_string(x2, y2)
result3 = hex_xor_to_string(x3, y3)
result4 = hex_xor_to_string(x4, y4)
print("Result 1:", result1)
print("Result 2:", result2)
print("Result 3:", result3)
print("Result 4:", result4)
res = result1[::-1] + result2[::-1] + result3[::-1] + result4[::-1]
print(res)
```
整个拼起来是字符串`C:\2024GameSafeRace.token1`,应该是创建了一个文件,然后向这个文件写入了token1了,接着往下
loc_180007A20这个函数内部被加密了,猜测是对token1的解密过程,然后通过WriteFile写入`C:\2024GameSafeRace.token1`中,并不是很像去分析这个函数,直接加载驱动看看能不能直接运行得到2024GameSafeRace.token1文件。
找到下没找到,回头看看CreateFileA函数,核查一下后面几个参数
```C
FileA = CreateFileA((LPCSTR)(_RBP + 96), 0x40000000u, 0, 0i64, 3u, 0x80u, 0i64);
```
看来是参数在作怪,CreateFileA函数传入**OPEN_EXISTING**参数,如果没有指定文件,则函数会返回失败,那也好办,自己创建一个就好了。
C:\2024GameSafeRace.token1成功被写入
010打开找到token1:`757F4749AEBB1891EF5AC2A9B5439CEA`
token2的寻找就偏简单了,加载驱动后留意一下dbgView的打印信息就可以获取
组合一下就是token2:`803f14a24d64f3e697957c252e3a5686`
## (二)解题过程
题目要求:
编写程序,运行时修改尽量少的内存,让两段token输出成功。(满分2分)
根据之前分析的token1,我们可以知道程序会在CreateFileA后解密token1然后写入到`C:\2024GameSafeRace.token1`中,但是会因为CreateFileA参数`OPEN_EXISTING`条件不满足而失败,所以我们只需要修改这个传参,改成`OPEN_ALWAYS`,即可实现输出token1,那我们只需要hook `CreateFileA`函数修改传参即可,但是有个问题,因为不是hack.exe本身调用`CreateFileA`函数,而是hack.exe注入了一个dll到winlogon.exe,然后再winlogon.exe里调用`CreateFileA`函数,所以我们可以考虑在注入前修改`WriteProcessMemory`函数参数buffer,从而在注入前patch dll,或者编写代码直接注入winlogon.exe,hook CreateFileA函数修改传参,但是考虑到第二种方式可能不被允许,winlogon.exe毕竟是系统进程,题目应该是要我们通过patch dll的方式解题。
这里我们要patch 传参,通过ida找到传参的汇编代码
在winhex里找到对应所在文件偏移
也就是在0x7171处OPEN_EXISTING:0x3是要patch的地方,把这个参数修改成OPEN_ALWAYS:0x4即可。下面编写代码实现。
```c
//dllmain.cpp
#include "pch.h"
#include <windows.h>
#include <shellapi.h>
#include <detours.h>
#include <tlhelp32.h>
#include <stdlib.h>
#include <stdio.h>
#pragma comment(lib,"detours.lib")
#define _KDEBUG
#define DBGMGEBOX(fmt, ...) \
do { \
/* 假设最大长度为1024,根据需要调整大小 */ \
wsprintfA(out, fmt, __VA_ARGS__); \
MessageBoxA(NULL, out, "提示", MB_OK); \
} while(0)
char out;
typedef BOOL(WINAPI* WriteProcessMemory_t)(
_In_ HANDLE hProcess,
_In_ LPVOID lpBaseAddress,
_In_reads_bytes_(nSize) LPCVOID lpBuffer,
_In_ SIZE_T nSize,
_Out_opt_ SIZE_T* lpNumberOfBytesWritten
);
WriteProcessMemory_t TrueWriteProcessMemory = NULL;
BOOL
WINAPI
HookWriteProcessMemory(
_In_ HANDLE hProcess,
_In_ LPVOID lpBaseAddress,
_In_reads_bytes_(nSize) LPCVOID lpBuffer,
_In_ SIZE_T nSize,
_Out_opt_ SIZE_T* lpNumberOfBytesWritten
)
{
if (nSize == 4506624 && *((PUCHAR)lpBuffer + 0x7171) == 0x3)
{
*((PUCHAR)lpBuffer + 0x7171) = 0x4;
DBGMGEBOX("Hook Success!\n");
}
return TrueWriteProcessMemory(hProcess, lpBaseAddress, lpBuffer, nSize, lpNumberOfBytesWritten);
}
BOOL APIENTRY DllMain(HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved) {
switch (ul_reason_for_call) {
case DLL_PROCESS_ATTACH:
DetourTransactionBegin();
DetourUpdateThread(GetCurrentThread());
TrueWriteProcessMemory = (WriteProcessMemory_t)DetourFindFunction("kernel32.dll", "WriteProcessMemory");
DetourAttach(&(PVOID&)TrueWriteProcessMemory, HookWriteProcessMemory);
DetourTransactionCommit();
break;
case DLL_PROCESS_DETACH:
DetourTransactionBegin();
DetourUpdateThread(GetCurrentThread());
DetourDetach(&(PVOID&)TrueWriteProcessMemory, HookWriteProcessMemory);
DetourTransactionCommit();
break;
}
return TRUE;
}
```
成功输出token1文件:
然后是token2,既然是内核输出,那只能是在ace.sys里做点手脚,DIE查壳发现ace.sys的大部分代码都被加壳过了,静态代码不好看,只能先猜测token2的输出调用了DbgPrint或者DbgPrintEx,因为之前输出token2的时候开启了Verbose Kernel outPut,猜测之所以正常输出失败是因为DbgPrintEx的level值太低,仅将字符串传递给内核调试器,不执行输出操作。
hook DbgPrintEx函数看一眼传参。
```c
#include <ntifs.h>
#include <ntdef.h>
#include <ntstatus.h>
#include <ntddk.h>
#include <stdarg.h>
#include "R0Hook.h"
#define dbgFilter "Kvancy:"
typedef ULONG(*FuncPtr) (ULONG ComponentId, ULONG Level, PCSTR Format, ...);
HOOK_MANAGER hookManager;
ULONG myDbgPrintEx(ULONG ComponentId, ULONG Level, PCSTR Format, ...) {
Unhook(&hookManager);
FuncPtr func = (FuncPtr)hookManager.target;
kPrint("%s DbgPrintEx ComponentId:%lu,Level:%lu\n",dbgFilter, ComponentId, Level);
va_list args;
va_start(args, Format);
NTSTATUS s = func(ComponentId, Level, Format, args);
va_end(args);
ApplyHook(&hookManager);
return s;
}
void DriverUnload(PDRIVER_OBJECT pDriver) {
kPrint("%s DriverUnload\n", dbgFilter);
Unhook(&hookManager);
}
NTSTATUS DriverEntry(
_In_ PDRIVER_OBJECT DriverObject, _In_ PUNICODE_STRING RegistryPath
) {
DriverObject->DriverUnload = DriverUnload;
PVOID dbgPrintEx = DbgPrintEx;
InitializeHookManager(&hookManager, dbgPrintEx, myDbgPrintEx);
ApplyHook(&hookManager);
DbgPrintEx(DPFLTR_IHVDRIVER_ID, DPFLTR_ERROR_LEVEL, "%s DriverEntry\n",dbgFilter);
return STATUS_SUCCESS;
}
```
发现加载ace驱动后,有大量的level:5的调试信息输出
也就是说,程序通过设置调试信息的重要级别来控制调试信息是否正常输出,于是可以提高level级别来输出token2,那么最简单的方式就是hook之后修改level后传回去,编写代码hook测试下。
```c
#include <ntifs.h>
#include <ntdef.h>
#include <ntstatus.h>
#include <ntddk.h>
#include <stdarg.h>
#include <stdio.h>
#include "R0Hook.h"
#define dbgFilter "Kvancy:"
typedef ULONG(*FuncPtr) (ULONG ComponentId, ULONG Level, PCSTR Format, ...);
HOOK_MANAGER hookManager;
char buffer;
ULONG myDbgPrintEx(ULONG ComponentId, ULONG Level, PCSTR Format, ...) {
Unhook(&hookManager);
FuncPtr func = (FuncPtr)hookManager.target;
va_list args;
va_start(args, Format);
vsprintf(buffer, Format, args);
va_end(args);
NTSTATUS s = func(ComponentId, 0, "%s", buffer);//修改level为0
ApplyHook(&hookManager);
return s;
}
void DriverUnload(PDRIVER_OBJECT pDriver) {
kPrint("Kvancy: DriverUnload\n");
Unhook(&hookManager);
}
NTSTATUS DriverEntry(
_In_ PDRIVER_OBJECT DriverObject, _In_ PUNICODE_STRING RegistryPath
) {
DriverObject->DriverUnload = DriverUnload;
PVOID dbgPrintEx = DbgPrintEx;
InitializeHookManager(&hookManager, dbgPrintEx, myDbgPrintEx);
ApplyHook(&hookManager);
DbgPrintEx(DPFLTR_IHVDRIVER_ID, DPFLTR_ERROR_LEVEL, "Kvancy: DriverEntry\n");
return STATUS_SUCCESS;
}
```
成功输出token2,但是根据题目要求是不能修改系统模块代码的,也就是说hook内核函数的方法不能过这道题,还是得想想别的方法。现在已知的ace.sys的行为就是驱动会在被加载之后做了某些操作会使得系统持续调用DbgPrintEx来输出token2,但是ace.sys其实做了某种操作后就被卸载掉了,如下图所示。
可以想到就是说驱动启动了一个线程或者进程,让该任务持续输出token2,创建完随后再卸载自己并且不停止这个线程或者进程。先枚举进程看看有没有奇怪的进程出现。
```c
VOID WriteToFile(PUNICODE_STRING FilePath, PCHAR Data)
{
OBJECT_ATTRIBUTES objAttr;
IO_STATUS_BLOCK ioStatusBlock;
HANDLE fileHandle;
NTSTATUS status;
UNICODE_STRING unicodeFilePath;
RtlInitUnicodeString(&unicodeFilePath, FilePath->Buffer);
InitializeObjectAttributes(&objAttr, &unicodeFilePath, OBJ_CASE_INSENSITIVE | OBJ_KERNEL_HANDLE, NULL, NULL);
status = ZwCreateFile(
&fileHandle,
FILE_APPEND_DATA | SYNCHRONIZE,
&objAttr,
&ioStatusBlock,
NULL,
FILE_ATTRIBUTE_NORMAL,
0,
FILE_OPEN_IF,
FILE_SYNCHRONOUS_IO_NONALERT,
NULL,
0
);
if (NT_SUCCESS(status)) {
size_t dataLength = strlen(Data);
ZwWriteFile(fileHandle, NULL, NULL, NULL, &ioStatusBlock, Data, (ULONG)dataLength, NULL, NULL);
ZwClose(fileHandle);
}
else {
DbgPrint("Failed to create file: %08X\n", status);
}
}
VOID EnumProcesses()
{
NTSTATUS status;
PVOID buffer;
ULONG bufferSize = 0x10000; // Initial buffer size, can grow if needed
ULONG returnLength;
CHAR logBuffer;
UNICODE_STRING filePath;
RtlInitUnicodeString(&filePath, L"\\??\\C:\\Users\\15386\\Desktop\\1.txt");
buffer = ExAllocatePoolWithTag(NonPagedPool, bufferSize, 'proc');
if (!buffer) {
DbgPrint("Failed to allocate buffer for process information\n");
return;
}
status = ZwQuerySystemInformation(SystemProcessInformation, buffer, bufferSize, &returnLength);
if (status == STATUS_INFO_LENGTH_MISMATCH) {
ExFreePool(buffer);
bufferSize = returnLength;
buffer = ExAllocatePoolWithTag(NonPagedPool, bufferSize, 'proc');
if (!buffer) {
DbgPrint("Failed to allocate larger buffer for process information\n");
return;
}
status = ZwQuerySystemInformation(SystemProcessInformation, buffer, bufferSize, &returnLength);
}
if (NT_SUCCESS(status)) {
PSYSTEM_PROCESS_INFORMATION processInfo = (PSYSTEM_PROCESS_INFORMATION)buffer;
while (TRUE) {
if (processInfo->ImageName.Buffer) {
_snprintf(logBuffer, sizeof(logBuffer), "Process ID: %lu, Name: %wZ\n", (ULONG)(ULONG_PTR)processInfo->ProcessId, &processInfo->ImageName);
}
else {
_snprintf(logBuffer, sizeof(logBuffer), "Process ID: %lu, Name: \n", (ULONG)(ULONG_PTR)processInfo->ProcessId);
}
WriteToFile(&filePath, logBuffer);
if (processInfo->NextEntryOffset == 0)
break;
processInfo = (PSYSTEM_PROCESS_INFORMATION)((PUCHAR)processInfo + processInfo->NextEntryOffset);
}
}
ExFreePool(buffer);
}
```
结果发现好像没有奇怪的进程被创建出来,那么有可能是驱动利用`PsCreateSystemThread`创建了一个内核线程。hook`PsCreateSystemThread`函数看看驱动加载时是否调用了这个函数。
```c
#include <ntifs.h>
#include <ntdef.h>
#include <ntstatus.h>
#include <ntddk.h>
#include <stdarg.h>
#include "R0Hook.h"
#define dbgFilter "Kvancy:"
typedef ULONG(*FuncPtr) (
PHANDLE ThreadHandle,
ULONG DesiredAccess,
POBJECT_ATTRIBUTES ObjectAttributes,
HANDLE ProcessHandle,
PCLIENT_ID ClientId,
PKSTART_ROUTINE StartRoutine,
PVOID StartContext);
HOOK_MANAGER hookManager;
NTSTATUS myPsCreateSystemThread(
PHANDLE ThreadHandle,
ULONG DesiredAccess,
POBJECT_ATTRIBUTES ObjectAttributes,
HANDLE ProcessHandle,
PCLIENT_ID ClientId,
PKSTART_ROUTINE StartRoutine,
PVOID StartContext
)
{
Unhook(&hookManager);
FuncPtr func = (FuncPtr)hookManager.target;
kPrint("%s myPsCreateSystemThread StartRoutine:%p\n", dbgFilter, StartRoutine);
NTSTATUS s = func(ThreadHandle, DesiredAccess, ObjectAttributes, ProcessHandle, ClientId, StartRoutine, StartContext);
ApplyHook(&hookManager);
return s;
}
void DriverUnload(PDRIVER_OBJECT pDriver) {
kPrint("%s DriverUnload\n", dbgFilter);
Unhook(&hookManager);
}
NTSTATUS DriverEntry(
_In_ PDRIVER_OBJECT DriverObject, _In_ PUNICODE_STRING RegistryPath
) {
DriverObject->DriverUnload = DriverUnload;
PVOID dbgPrintEx = PsCreateSystemThread;
InitializeHookManager(&hookManager, dbgPrintEx, myPsCreateSystemThread);
ApplyHook(&hookManager);
DbgPrintEx(DPFLTR_IHVDRIVER_ID, DPFLTR_ERROR_LEVEL, "%s DriverEntry\n", dbgFilter);
return STATUS_SUCCESS;
}
```
发现在token2输出前确实有PsCreateSystemThread函数调用,虽然不确定是不是ace.sys创建的。在windbg里反汇编看看线程函数
```asm
0: kd> u FFFFBA0729013DB0 l 100
ffffba07`29013db0 488bc4 mov rax,rsp
ffffba07`29013db3 48895808 mov qword ptr ,rbx
ffffba07`29013db7 48897818 mov qword ptr ,rdi
ffffba07`29013dbb 4c897020 mov qword ptr ,r14
ffffba07`29013dbf 55 push rbp
ffffba07`29013dc0 488d68a1 lea rbp,
ffffba07`29013dc4 4881eca0000000sub rsp,0A0h
ffffba07`29013dcb 48bf4e93328b546b331e mov rdi,1E336B548B32934Eh
ffffba07`29013dd5 49bed520794add1d6d4b mov r14,4B6D1DDD4A7920D5h
ffffba07`29013ddf 0f57c0 xorps xmm0,xmm0
ffffba07`29013de2 488d4d37 lea rcx,
ffffba07`29013de6 0f114537 movupsxmmword ptr ,xmm0
ffffba07`29013dea e8d1030000 call ffffba07`290141c0
ffffba07`29013def 48b8a14f122fb3276d4b mov rax,4B6D27B32F124FA1h
ffffba07`29013df9 4c8d45e7 lea r8,
ffffba07`29013dfd 4889456f mov qword ptr ,rax
ffffba07`29013e01 ba05000000 mov edx,5
ffffba07`29013e06 488b456f mov rax,qword ptr
ffffba07`29013e0a 33c9 xor ecx,ecx
ffffba07`29013e0c 488945e7 mov qword ptr ,rax
ffffba07`29013e10 48897d6f mov qword ptr ,rdi
ffffba07`29013e14 488b456f mov rax,qword ptr
ffffba07`29013e18 488945ef mov qword ptr ,rax
ffffba07`29013e1c 4c89756f mov qword ptr ,r14
ffffba07`29013e20 488b456f mov rax,qword ptr
ffffba07`29013e24 48894517 mov qword ptr ,rax
ffffba07`29013e28 48897d6f mov qword ptr ,rdi
ffffba07`29013e2c 488b456f mov rax,qword ptr
ffffba07`29013e30 660f6f45e7 movdqaxmm0,xmmword ptr
ffffba07`29013e35 4889451f mov qword ptr ,rax
ffffba07`29013e39 660fef4517 pxor xmm0,xmmword ptr
ffffba07`29013e3e 488b0543330000mov rax,qword ptr
ffffba07`29013e45 660f7f45e7 movdqaxmmword ptr ,xmm0
ffffba07`29013e4a ff15c8210000 call qword ptr
ffffba07`29013e50 33db xor ebx,ebx
ffffba07`29013e52 48b8f0104b32dd1d6d4b mov rax,4B6D1DDD324B10F0h
ffffba07`29013e5c 4c8d45f7 lea r8,
ffffba07`29013e60 4889456f mov qword ptr ,rax
ffffba07`29013e64 ba05000000 mov edx,5
ffffba07`29013e69 488b456f mov rax,qword ptr
ffffba07`29013e6d 33c9 xor ecx,ecx
ffffba07`29013e6f 488945f7 mov qword ptr ,rax
ffffba07`29013e73 48897d6f mov qword ptr ,rdi
ffffba07`29013e77 488b456f mov rax,qword ptr
ffffba07`29013e7b 488945ff mov qword ptr ,rax
ffffba07`29013e7f 4c89756f mov qword ptr ,r14
ffffba07`29013e83 488b456f mov rax,qword ptr
ffffba07`29013e87 48894527 mov qword ptr ,rax
ffffba07`29013e8b 48897d6f mov qword ptr ,rdi
ffffba07`29013e8f 488b456f mov rax,qword ptr
ffffba07`29013e93 660f6f45f7 movdqaxmm0,xmmword ptr
ffffba07`29013e98 440fb64c1d37 movzx r9d,byte ptr
ffffba07`29013e9e 4889452f mov qword ptr ,rax
ffffba07`29013ea2 660fef4527 pxor xmm0,xmmword ptr
ffffba07`29013ea7 488b05da320000mov rax,qword ptr
ffffba07`29013eae 660f7f45f7 movdqaxmmword ptr ,xmm0
ffffba07`29013eb3 ff155f210000 call qword ptr
ffffba07`29013eb9 48ffc3 inc rbx
ffffba07`29013ebc 4883fb10 cmp rbx,10h
ffffba07`29013ec0 7c90 jl ffffba07`29013e52
ffffba07`29013ec2 48b8df20794add1d6d4b mov rax,4B6D1DDD4A7920DFh
ffffba07`29013ecc 4c8d4507 lea r8,
ffffba07`29013ed0 4889456f mov qword ptr ,rax
ffffba07`29013ed4 ba05000000 mov edx,5
ffffba07`29013ed9 488b456f mov rax,qword ptr
ffffba07`29013edd 33c9 xor ecx,ecx
ffffba07`29013edf 48894507 mov qword ptr ,rax
ffffba07`29013ee3 48897d6f mov qword ptr ,rdi
ffffba07`29013ee7 488b456f mov rax,qword ptr
ffffba07`29013eeb 4889450f mov qword ptr ,rax
ffffba07`29013eef 4c89756f mov qword ptr ,r14
ffffba07`29013ef3 488b456f mov rax,qword ptr
ffffba07`29013ef7 48894547 mov qword ptr ,rax
ffffba07`29013efb 48897d6f mov qword ptr ,rdi
ffffba07`29013eff 488b456f mov rax,qword ptr
ffffba07`29013f03 660f6f4507 movdqaxmm0,xmmword ptr
ffffba07`29013f08 4889454f mov qword ptr ,rax
ffffba07`29013f0c 660fef4547 pxor xmm0,xmmword ptr
ffffba07`29013f11 488b0570320000mov rax,qword ptr
ffffba07`29013f18 660f7f4507 movdqaxmmword ptr ,xmm0
ffffba07`29013f1d ff15f5200000 call qword ptr
ffffba07`29013f23 b9ce0a0000 mov ecx,0ACEh
ffffba07`29013f28 e833e9ffff call ffffba07`29012860
ffffba07`29013f2d e9adfeffff jmp ffffba07`29013ddf
ffffba07`29013f32 cc int 3
ffffba07`29013f33 cc int 3
ffffba07`29013f34 4152 push r10
```
导入到ida里看伪代码
```c
void __noreturn sub_180005148()
{
__m128i v0; // xmm0
__int64 i; // rbx
__m128i v2; // xmm0
__int64 v3; // r9
__m128i v4; // xmm0
__m128i v5; // BYREF
__m128i v6; // BYREF
__m128i v7; // BYREF
__m128i v8; //
__m128i v9; //
__int128 v10; // BYREF
__m128i v11; //
while ( 1 )
{
v10 = 0i64;
((void (__fastcall *)(__int128 *))((char *)&loc_180005556 + 2))(&v10);
v5.m128i_i64 = 0x4B6D27B32F124FA1i64;
v5.m128i_i64 = 0x1E336B548B32934Ei64;
v8.m128i_i64 = 0x4B6D1DDD4A7920D5i64;
v0 = _mm_load_si128(&v5);
v8.m128i_i64 = 0x1E336B548B32934Ei64;
v5 = _mm_xor_si128(v0, v8);
MEMORY(0i64, 5i64, &v5);
for ( i = 0i64; i < 16; ++i )
{
v6.m128i_i64 = 0x4B6D1DDD324B10F0i64;
v6.m128i_i64 = 0x1E336B548B32934Ei64;
v9.m128i_i64 = 0x4B6D1DDD4A7920D5i64;
v2 = _mm_load_si128(&v6);
v3 = *((unsigned __int8 *)&v10 + i);
v9.m128i_i64 = 0x1E336B548B32934Ei64;
v6 = _mm_xor_si128(v2, v9);
MEMORY(0i64, 5i64, &v6, v3);
}
v7.m128i_i64 = 0x4B6D1DDD4A7920DFi64;
v7.m128i_i64 = 0x1E336B548B32934Ei64;
v11.m128i_i64 = 0x4B6D1DDD4A7920D5i64;
v4 = _mm_load_si128(&v7);
v11.m128i_i64 = 0x1E336B548B32934Ei64;
v7 = _mm_xor_si128(v4, v11);
MEMORY(0i64, 5i64, &v7);
sub_180003BF8(2766i64);
}
}
```
好像是做了一个字符串解密然后输出的操作,浅浅用python跑一下解析字符串验证猜想。
```py
def hex_xor_to_string(a, b):
result = a ^ b
hex_str = hex(result)
if len(hex_str) % 2 != 0:
hex_str = '0' + hex_str
result_str = ''.join(chr(int(hex_str, 16)) for i in range(0, len(hex_str), 2))
return result_str
x1 = 0x4B6D27B32F124FA1
x2 = 0x1E336B548B32934E
y1 = 0x4B6D1DDD4A7920D5
y2 = 0x1E336B548B32934E
result1 = hex_xor_to_string(x1, y1)
result2 = hex_xor_to_string(x2, y2)
print("Result 1:", result1)
print("Result 2:", result2)
res = result1[::-1] + result2[::-1]
print(res)
```
打印出了`token`基本上确定了这个线程就是打印token的线程,现在就是要想怎么patch这个线程函数使得token能够输出出来。
这里有个`mov edx,5`语句,将DbgPrintEx函数的level设置成5,可以考虑patch这个语句,将5改成0,那么只需要patch一个字节,共三处。但是又要怎么patch呢,首先不能通过现在这种方式hook PsCreateSystemThread函数调用来确定StartRoutine地址(题目要求不能修改系统模块代码),也就是说得想另外一个办法确定这个线程的地址,然后通过偏移来确定需要patch的地址。
那么怎么确定这个线程地址呢,如果通过`ZwQuerySystemInformation`枚举内核模块然后枚举模块下的所有线程的话,已经卸载了的ace.sys模块还能被枚举到么?问了下GPT好像是不能的,还可以考虑用StartRoutine地址的后几位做特征,匹配所有线程的开始地址的后几位,但是这种方式又感觉怕遇到地址特征一模一样的,感觉还是不大行。又问GPT怎么寻找到某个内核线程,得到答复是除了`ZwQuerySystemInformation`枚举,还有通过`PsLookupThreadByThreadId`函数从进程id和线程id查找的。
那么线程id和进程id又从哪获取呢?因为之前hook过PsCreateSystemThread函数,翻阅文档找到了一个ClientId参数,这个参数指向接收新线程的客户端标识符的结构,即一个pid,一个tid,但是pid,tid应该都是系统分配的吧,能是一个固定值么?hook一下看看输出
诶,tid貌似是系统分配的,但是pid一直都是4,很奇怪,pid=4代表的是什么进程呢?之前刚好枚举过进程来找有没有新进程创建,现在正好能派上用场。
貌似是一个系统进程,GPT了一下发现原来如果驱动程序通过内核模式创建系统线程(使用`PsCreateSystemThread`),这些线程通常会在系统进程下运行,PID为4。原来如此,驱动程序和进程是一个级别的,但是驱动程序创建的这个线程是在系统进程之下的,而不是属于驱动模块,只是线程起始地址隶属于模块地址空间的,驱动卸载并不影响线程的运行。
这样的话,我们要找的线程因为模块被卸载了,所以它不在所有模块地址空间内,只要枚举所有系统进程pid=4下的所有线程,然后通过判断线程的起始地址是否在所有模块地址之内,即可判断它是否是我们要找的线程。这下思路就通了,开始编写代码实现patch。
```c
#include "header.h"
PVOID MoudleBaseAddress;
ULONG64 MoudleSize;
ULONG ModuleCount = 0;
ULONG Offset1 = 0x52, Offset2 = 0xB5, Offset3 = 0x125;
BOOLEAN IsAddressInKnownModules(PVOID Address, PVOID* ModuleBaseAddresses, ULONG64* ModuleSize, ULONG ModuleCount)
{
for (size_t i = 0; i < ModuleCount; i++)
{
if (Address >= ModuleBaseAddresses && Address < (ULONG64)ModuleBaseAddresses + ModuleSize)
{
return TRUE;
}
}
return FALSE;
}
PVOID EnumSystemModulesForProcess(HANDLE TargetProcessId)
{
NTSTATUS status;
ULONG bufferSize = 0x10000;
PVOID processBuffer = NULL;
PVOID moduleBuffer = NULL;
ULONG returnLength;
// 查询进程信息
processBuffer = ExAllocatePoolWithTag(NonPagedPool, bufferSize, 'proc');
if (!processBuffer) {
DbgPrint("Failed to allocate buffer for process information\n");
return;
}
status = ZwQuerySystemInformation(SystemProcessInformation, processBuffer, bufferSize, &returnLength);
if (status == STATUS_INFO_LENGTH_MISMATCH) {
ExFreePool(processBuffer);
bufferSize = returnLength;
processBuffer = ExAllocatePoolWithTag(NonPagedPool, bufferSize, 'proc');
if (!processBuffer) {
DbgPrint("Failed to allocate larger buffer for process information\n");
return;
}
status = ZwQuerySystemInformation(SystemProcessInformation, processBuffer, bufferSize, &returnLength);
}
// 查询模块信息
moduleBuffer = ExAllocatePoolWithTag(NonPagedPool, bufferSize, 'modl');
if (!moduleBuffer) {
DbgPrint("Failed to allocate buffer for module information\n");
ExFreePool(processBuffer);
return;
}
status = ZwQuerySystemInformation(SystemModuleInformation, moduleBuffer, bufferSize, &returnLength);
if (status == STATUS_INFO_LENGTH_MISMATCH) {
ExFreePool(moduleBuffer);
bufferSize = returnLength;
moduleBuffer = ExAllocatePoolWithTag(NonPagedPool, bufferSize, 'modl');
if (!moduleBuffer) {
DbgPrint("Failed to allocate larger buffer for module information\n");
ExFreePool(processBuffer);
return;
}
status = ZwQuerySystemInformation(SystemModuleInformation, moduleBuffer, bufferSize, &returnLength);
}
// 遍历模块信息
if (NT_SUCCESS(status)) {
PSYSTEM_MODULE_INFORMATION moduleInfo = (PSYSTEM_MODULE_INFORMATION)moduleBuffer;
ModuleCount = moduleInfo->ModulesCount;
for (ULONG i = 0; i < moduleInfo->ModulesCount; i++) {
PSYSTEM_MODULE_INFORMATION_ENTRY moduleEntry = &moduleInfo->Modules;
MoudleBaseAddress = moduleEntry->Base;
MoudleSize = moduleEntry->Size;
}
}
// 遍历进程信息
if (NT_SUCCESS(status)) {
PSYSTEM_PROCESS_INFORMATION processInfo = (PSYSTEM_PROCESS_INFORMATION)processBuffer;
while (TRUE) {
if (processInfo->ProcessId == TargetProcessId) {
PSYSTEM_THREAD_INFORMATION threadInfo = (PSYSTEM_THREAD_INFORMATION)(processInfo + 1);
for (ULONG i = 0; i < processInfo->NumberOfThreads; i++) {
if (!IsAddressInKnownModules(threadInfo.StartAddress, MoudleBaseAddress, MoudleSize, ModuleCount))
{
DbgPrint("Find it:%p\n",threadInfo.StartAddress);
return threadInfo.StartAddress;
}
}
break;
}
if (processInfo->NextEntryOffset == 0)
break;
processInfo = (PSYSTEM_PROCESS_INFORMATION)((PUCHAR)processInfo + processInfo->NextEntryOffset);
}
}
// 清理分配的内存
ExFreePool(processBuffer);
ExFreePool(moduleBuffer);
return 0;
}
VOID UnloadDriver(PDRIVER_OBJECT DriverObject) {
KdPrint(("Driver Unloaded\n"));
}
NTSTATUS DriverEntry(PDRIVER_OBJECT DriverObject, PUNICODE_STRING RegistryPath) {
DriverObject->DriverUnload = UnloadDriver;
KdPrint(("Driver Loaded\n"));
HANDLE targetPid = (HANDLE)4; // 系统进程的 PID
PVOID targetAddress = EnumSystemModulesForProcess(targetPid);
UCHAR valueToWrite = 0x00; // 要写入的字节值
UCHAR valueToRead = 0x05; // 要写入的字节值
ULONG numHasChanged = 0x00;
if (*(PUCHAR)((ULONG64)targetAddress + Offset1) == valueToRead)
{
*(PUCHAR)((ULONG64)targetAddress + Offset1) = valueToWrite;
numHasChanged++;
}
if (*(PUCHAR)((ULONG64)targetAddress + Offset2) == valueToRead)
{
*(PUCHAR)((ULONG64)targetAddress + Offset2) = valueToWrite;
numHasChanged++;
}
if (*(PUCHAR)((ULONG64)targetAddress + Offset3) == valueToRead)
{
*(PUCHAR)((ULONG64)targetAddress + Offset3) = valueToWrite;
numHasChanged++;
}
DbgPrint("numHasChanged:%d\n", numHasChanged);
return STATUS_SUCCESS;
}
```
成功输出token2
## (三)解题过程
题目要求:
编写程序,运行时修改尽量少的内存,让shellcode 往自行指定的位置写入token1成功。(满分3分)
要求任意位置,也就是要修改CreateFileA函数的第一个参数的值,根据之前分析的`C:\2024GameSafeRace.token1`字符串是由十六进制异或得到的,也就是下面这些
可以考虑的是patch这些十六进制数据,把异或的key改成0,然后密文改成明文即可,因为明文异或0还是明文,但是考虑到要尽量修改少量的内存,我们最好还是保持key不变,自定义密文解密到我们所要的文件地址。跑个python脚本解出新的密文,得到新的密文,接着找到密文所在文件的偏移然后patch即可,给出解题代码。
```c
//dllmain.cpp
#include "pch.h"
#include <windows.h>
#include <shellapi.h>
#include <detours.h>
#include <tlhelp32.h>
#include <stdlib.h>
#include <stdio.h>
#pragma comment(lib,"detours.lib")
#define _KDEBUG
#define DBGMGEBOX(fmt, ...) \
do { \
/* 假设最大长度为1024,根据需要调整大小 */ \
wsprintfA(out, fmt, __VA_ARGS__); \
MessageBoxA(NULL, out, "提示", MB_OK); \
} while(0)
char out;
typedef BOOL(WINAPI* WriteProcessMemory_t)(
_In_ HANDLE hProcess,
_In_ LPVOID lpBaseAddress,
_In_reads_bytes_(nSize) LPCVOID lpBuffer,
_In_ SIZE_T nSize,
_Out_opt_ SIZE_T* lpNumberOfBytesWritten
);
WriteProcessMemory_t TrueWriteProcessMemory = NULL;
BOOL
WINAPI
HookWriteProcessMemory(
_In_ HANDLE hProcess,
_In_ LPVOID lpBaseAddress,
_In_reads_bytes_(nSize) LPCVOID lpBuffer,
_In_ SIZE_T nSize,
_Out_opt_ SIZE_T* lpNumberOfBytesWritten
)
{
if (nSize == 4506624 && *((PUCHAR)lpBuffer + 0x7171) == 0x3)
{
*((PUCHAR)lpBuffer + 0x7171) = 0x4;
*(PULONG64)((ULONG64)lpBuffer + 0x7082) = 0x94e7c2136acc0e5c;//
*(PULONG64)((ULONG64)lpBuffer + 0x7093) = 0x8207c5d1af9f3860;
*(PULONG64)((ULONG64)lpBuffer + 0x70F1) = 0xa905cca9edcb138c;
*(PULONG64)((ULONG64)lpBuffer + 0x7108) = 0xa753b8236c56809a;
//C:\Users\15386\Desktop\flag.txt
DBGMGEBOX("Hook Success!\n");
}
return TrueWriteProcessMemory(hProcess, lpBaseAddress, lpBuffer, nSize, lpNumberOfBytesWritten);
}
BOOL APIENTRY DllMain(HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved) {
switch (ul_reason_for_call) {
case DLL_PROCESS_ATTACH:
DetourTransactionBegin();
DetourUpdateThread(GetCurrentThread());
TrueWriteProcessMemory = (WriteProcessMemory_t)DetourFindFunction("kernel32.dll", "WriteProcessMemory");
DetourAttach(&(PVOID&)TrueWriteProcessMemory, HookWriteProcessMemory);
DetourTransactionCommit();
break;
case DLL_PROCESS_DETACH:
DetourTransactionBegin();
DetourUpdateThread(GetCurrentThread());
DetourDetach(&(PVOID&)TrueWriteProcessMemory, HookWriteProcessMemory);
DetourTransactionCommit();
break;
}
return TRUE;
}
```
注入hook成功后,成功在桌面的`flag.txt`输出token1
虽然我看不懂,但是还是感觉很牛 有没有从基础到高阶的合适的推荐课程或推荐的师傅,小白一枚 就喜欢这种喜欢看,又看不懂的感觉 {:1_921:}{:1_921:}优秀 很详细 太牛批了 。点赞👍🏻 太赞了,感谢分享!
太赞了,感谢分享! 大佬大佬 感谢大佬分享 分析的很细