-
-
[原创]2023年腾讯游戏安全PC决赛复现
-
发表于: 2024-8-13 20:25 3484
-
最近一直在做往年腾讯游戏安全PC端的复现,但是2023年决赛的题做到第三问就开始有点做不动了,想在网上找找题解但是并没有找到,于是便想分享一下自己的解题经验和第三问的思路。鉴于知识的局限性,文中可能存在疏漏或不足之处,如果发现任何错误或不准确之处,请不吝赐教。
第一问让我们杀死进程,可以尝试根据windowsAPI提供的TerminateProcess函数去停止进程,写份代码测试下
以管理员身份启动测试
启动前:
启动后
先DIE查壳发现有VMP,只能动调入手,先观察程序功能。程序启动后会有两个进程启动,并且其中之一占用CPU性能较高;程序目录会创建十六个文件,并会做重复的删除和重新创建的操作;观察任务管理器发现进程会自动重启,手动杀死进程后就会重生一个新的进程。
拖进xdbg里观察符号,发现引入了ShellExcuteA这个函数,正好是启动进程函数,对这个函数下断点,发现确实断了下来,观察传参窗口。
第六个参数不同,对应的是lpParameters(传入进程的参数),一个是working
,一个是daemon restart
根据这个在控制台上运行加上参数working,发现只有一个工作进程,CPU占用100%,同理加上daemon restart,只有一个守护进程CPU占用不到1%。
调试参数为working的进程,观察线程发现有16个线程运行同一个函数,刚好对应16个txt日志文件的输出。
于是进入线程入口开始分析,对所有可能的写入文件的windowsAPI下断,发现都没有断下来,于是向CreateFile等API下断,
所有线程在CreateFileA上成功断下,观察传参确定这是对日志文件的操作函数
有了这个依据,能确认的就是16个线程会循环通过CreateFileA打开文件,这可能是占用CPU的一个主要因素。但是跟之前运行的结果不同,调试发现,写入文件的API一直没有被调用,而正常运行的结果是,十六个文件会循环的做一个过程,文件被创建->文件被写入->文件被删除->新文件创建…。我通过加working参数运行,只会有一个循环过程,即不会有新文件诞生,文件内容也不会被修改。所以,十六个线程循环CreateFileA打开文件过程是无用功,只会徒增CPU负担!那么我们是不是可以HOOK CreateFileA这个函数,然后每个线程调用它的第二次的时候我们进行线程终止,是不是可以减少CPU的压力了呢?写份代码跑一下看看
注入进去查看CPU,与不注入的CPU情况对比如图,进程CPU占比显著下降
现在我们能够确定,是这十六个线程循环打开文件(也可能做了其他的事情)占用CPU大量资源。但是直接线程退出的方式也会导致日志文件被删除,题目意思是说在保持WorkingService.exe正常运行不崩溃、主体功能无损的前提下,使之占用CPU下降到平均5%以下,WorkingService.exe的主体功能为写入信息。在没有源码的情况下提取这个部分,自行编写一个性能更佳的服务,现在直接退出线程的方式会导致日志文件删除过快,虽然写入了信息,但是根本没法看啊,所以我们为了日志文件更方便的浏览,应该在WriteFile之后保存文件。那我们得先hook一下删除文件的函数,看一下在哪调用的,然后选择过滤掉这个函数从而保存日志文件。
尝试hook一下DeleteFileA这个函数
发现没有被调用。
翻阅了下CreateFileA的文档,找到dwFlagsAndAttributes这个参数,发现这个参数包含了FILE_FLAG_DELETE_ON_CLOSE
这个属性,意思是文件将在关闭所有句柄后立即删除
因此,只要我们改了这个属性,日志文件就不会再被删除,写代码测试下
测试得到文件确实没有被删除,并且密文也成功写入文件,CPU占用也少于5%(峰值3%,最后趋近于0)
到这工作进程已经差不多分析完了,但是别忘了还有一个守护进程,守护进程应该也算是正常功能的一部分,那我们现在要开始结合守护进程分析。根据之前分析得到工作进程启动是通过守护进程调用ShellExcuteExA来传入working参数实现的,而这个后面启动的进程才是我们应该去注入的。而且因为程序有循环自生自灭的一个功能,我们可能要考虑循环注入实现对所有工作进程的hook。首先考虑第一个问题,如何去注入ShellExcuteExA启动的工作进程,第一个方式是Hook ShellExcuteExA这个函数,在其启动前获取ShellExcuteExA返回值->进程句柄,然后通过进程句柄进行注入;第二个方式是编写一个辅助程序,循环对每一个WorkingService.exe进程进行注入,这个要考虑到hook是否会影响守护进程的正常功能。我想先试一下第二个方式,较为简单一点,先测试一下守护进程是否会调用CreateFileA函数,如果会的话,我们要进行一下过滤操作。Hook测试了下,守护进程先启动,然后是工作进程,但是迟迟没有调试弹窗出现,说明守护进程没有调用CreateFileA。
因此,采用方式二,只要对所有WorkingService.exe进程进行注入,Hook CreateFileA函数即可在正常程序功能执行的情况下,降低CPU能耗,写一个程序测试一下
这里使用Vector去接受所有WorkingService.exe进程的pid,然后用Map去映射一个bool值来判断是否这个进程被注入过,程序运行起来发现CPU能耗还是100%,但是程序告诉我注入成功。???疑惑住了
因为之前注入的时候可以成功降低CPU能耗的,我很确信我的dll是没问题的,所以问题可能就是注入的方式了。考虑到之前注入使用工具注入,在进程启动的同时注入dll,而现在注入是在程序完全跑起来之后,所以怀疑是被下了钩子破坏了注入。在ARK工具里扫一下钩子看一眼。
豁然开朗!第一个钩子勾住了NtProtectVirtualMemory函数,而我们注入模块的时候就会调用这个内核函数去修改内存权限,第二个钩子勾住了DbgUiRemoteBreakin函数,我也不知道这个函数是干什么的,问一下GPT得知
DbgUiRemoteBreakin
函数是一个Windows API函数,它的作用是向指定的进程发送一个远程调试器的断点请求。当这个函数被调用时,目标进程会触发一个断点异常(通常是访问违规异常),如果目标进程已经有一个调试器附加,那么调试器就会捕获这个异常并进行处理。
这个函数通常用于以下情况:
看来是能附加调试的,勾住这个来反调试,哇哦,不错的方式。但是我们就先不管它了,先成功注入dll再说。所以现在看来还得先还原NtProtectVirtualMemory函数取消钩子,再调用注入函数,修改一下代码跑一下发现日志文件不能循环写入,应该是线程都退出之后,进程并未停止,猜测是线程函数里有计数的东西,到某个值之后会通知进程结束,从而让守护进程再生工作进程,于是我改了一下hook代码,让线程终止16次后终止进程,从而实现日志文件循环再生。
效果图:
题目二的结果应该就是这样了,能保持写入日志文件功能的同时不破坏工作进程循环启动的机制,大功告成!!!
附一份exe源码和一份dll源码
接着是第三题,要求是WorkingService.exe的主体功能为写入信息。在没有源码的情况下提取这个部分,自行编写一个性能更佳的服务,完全替代WorkingService.exe写入同样的密文信息,且运行时平均占用CPU时间低于5%。(2分)
根据之前分析,现在一直的程序功能主要体现在,创建守护进程启动->启动工作进程->创建十六个线程->每个线程创建一个日志文件->向文件写入密文->进程退出->守护进程循环创建工作进程,大致功能应该就是这些,而现在要解决的问题就是日志文件名和日志密文是怎么产生的。
在WriteFile下个断点看一眼密文的大致模样。
猜测有可能是base64加密,因为之前查看字符串有看到base64的码表,还是一个魔改码表,直接丢cyberchef试一下解密
好熟悉的字符串,这不正是初赛题的明文嘛,这个明显就是base64加密明文了,但是还有一个疑问,就是不同的文件会打印出不同的密文,把十六个文件的密文提取出来对比一下
对比发现十六个线程有十一个密文,16:11的不规则性我觉得甚至不止十一个,因为这个采样只是采取了一个进程创建的十六个线程,如果有更多进程可能就意味着更多种密文。
根据base64编码规则,如果码表和明文都一致的话,那密文就只能有一个,说明每个线程的码表和明文至少有一个不一致。那现在只能继续分析他的加密方式,在现有的码表处下断追踪到加密函数
在内存窗口发现传入的第一个参数是一个字符串地址->c tc me an,貌似跟catchmeifyoucan明文很像,所以这是修改了明文,然后再通过正常的码表进行base64加密?提取出这个明文,试一下对着这个码表加密,对比运行后的密文就知道了,
对比了多组,确信了码表是对的ABCDEFGHIJKLMNOPwxyz0123456789+/ghijklmnopqrstuvQRSTUVWXYZabcdef
,但是调试这个加密的过程中发现,一个线程至少会有两组明文,上面就是出自同一个线程。并且这个函数加密后的密文跟实际文件密文不同,下面是提取的两组密文和实际文件密文
现在还有很多问题在里面,首先是明文为什么一个线程有两组,加密得到的两组密文跟文件密文有什么关系?两组密文实在是太奇怪了,我试着把他们联系起来,于是我将两组密文用base64还原得到明文也进行了对比,发现了一个惊喜。
这两个明文的有效字符组合起来刚好是catchmeifyoucan!!! 接着就是其他无效字符了,提取出十六进制看看有没有联系
Fx8C4Wg7ERcwDWdV4WE4->0x15,0x17,0x02,0x63,0x68,0x1b,0x13,0x1f,0x10,0x0f,0x6f,0x75,0x63,0x61,0x18
4WFUFxZt52lm+xkDFx9u->0x63,0x61,0x74,0x15,0x1e,0x6d,0x65,0x69,0x66,0x79,0x19,0x03,0x15,0x17,0x6e
貌似没什么关系,考虑到多个文件的写入密文不同,继续提取其他线程的密文
拿这个线程的文件密文拿去解密,得到十六进制数组:0x15,0x17,0x02,0x15,0x1e,0x1b,0x13,0x1f,0x10,0x0f,0x19,0x03,0x15,0x17,0x18,刚好由34288线程的两组非可视字符十六进制组成。根据这个思路我把之前提取的密文进行了解密,发现线程之间的非字符不一定是成对的,但是非字符的十六进制跟所在索引是一定的,也就是说所有非可视字符的十六进制都是从上面这个数组里取,我试图多运行几遍进程反驳我这个猜想,因为这个十六进制数组并没有什么规律,但是运行结果都显示这个数组是一定的。
所以现在可以确定的是,明文catchmeifyoucan会和这个数组以某种方式进行混合,最后组成真正的明文进行base64换表加密得到文件密文。现在就需要继续研究这个混合的方式,因为进程会启动十六个线程来写日志,而且在之前的观察中看到在CreateFileA函数之前,密文就已经储存在寄存器中,所以是CreateThread和CreateFileA函数之间进行了明文混合和base64加密,对这三个关键地方下断点调试
可以看到,第一个参数即为之前分析的十六进制数组的指针,做一层栈回溯找明文混合方式
对base64的第一个参数位置(明文指针)下硬件访问断点,找到写入明文内存的位置
明文进行了一个字节一个字节的写入,仔细看这段VM给的混淆汇编代码, 其实就是一个
那就意味着我们要追踪[RDI]看看是怎么来的,由于这段代码已经被VM了,控制流混淆导致根本没法手动追踪,用xdbg自动追踪功能打印日志看看
足足有30000行汇编
往上回溯第一层发现[rdi]从RFLAGS寄存器来的,如果真要手动回溯的话,就要回溯十多个标志位,手动分析逻辑写源码不大现实(不禁再次感叹VM的强大)
根据题目要求
提取WorkingService.exe的主体功能可以以二进制指令块的形式,也可以以自写源码的形式
可以考虑提取二进制指令块的形式写功能,那我们就可以把这段追踪得来的汇编代码转成ShellCode写入到自己程序的内存里,然后通过VirtualProtect修改成可执行的运行一下从而提取这份功能。只要获取这份功能就可以完成第三问了,感觉工程量挺大的就不写了。
启动程序后会先默认按照参数daemon restart启动一个守护进程,守护进程通过设置参数working和调用ShellExcuteA来启动工作进程,工作进程会创建16个线程来循环调用CreateFileA保持文件存在(第二问需要通过修改参数实现相同功能并减少CPU占用率)并调用一次WriteFile来写入密文,密文由两个明文和两种加密组成,明文是catchmeifyoucan字符串和{
0x15, 0x17, 0x02, 0x15, 0x1E, 0x1B, 0x13, 0x1F, 0x10, 0x0F, 0x19, 0x03, 0x15, 0x17, 0x18
}数组,第一种加密是某种替换加密,第二种加密是base64变表加密。
bool
KillProcessByName(
const
char
* processName) {
HANDLE
hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
PROCESSENTRY32 pe;
pe.dwSize =
sizeof
(PROCESSENTRY32);
if
(Process32First(hSnapshot, &pe)) {
do
{
if
(
strcmp
(pe.szExeFile, processName) == 0) {
HANDLE
hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, pe.th32ProcessID);
if
(hProcess) {
TerminateProcess(hProcess, 0);
CloseHandle(hProcess);
Num++;
printf
(
"Kill suc:[%d]\n"
, Num);
}
}
}
while
(Process32Next(hSnapshot, &pe));
}
CloseHandle(hSnapshot);
return
true
;
}
int
main()
{
while
(1)
{
KillProcessByName(
"WorkingService.exe"
);
}
return
0;
}
bool
KillProcessByName(
const
char
* processName) {
HANDLE
hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
PROCESSENTRY32 pe;
pe.dwSize =
sizeof
(PROCESSENTRY32);
if
(Process32First(hSnapshot, &pe)) {
do
{
if
(
strcmp
(pe.szExeFile, processName) == 0) {
HANDLE
hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, pe.th32ProcessID);
if
(hProcess) {
TerminateProcess(hProcess, 0);
CloseHandle(hProcess);
Num++;
printf
(
"Kill suc:[%d]\n"
, Num);
}
}
}
while
(Process32Next(hSnapshot, &pe));
}
CloseHandle(hSnapshot);
return
true
;
}
int
main()
{
while
(1)
{
KillProcessByName(
"WorkingService.exe"
);
}
return
0;
}
#include "pch.h"
#include <windows.h>
#include <shellapi.h>
#include <detours.h>
#include <tlhelp32.h>
#pragma comment(lib,"detours.lib")
#define _DEBUG
#define DBGMGEBOX(fmt, ...) \
do
{ \
/* 假设最大长度为1024,根据需要调整大小 */
\
wsprintfA(out, fmt, __VA_ARGS__); \
MessageBoxA(NULL, out,
"提示"
, MB_OK); \
}
while
(0)
char
out[100];
DWORD
tlsIndex;
//tls索引
typedef
BOOL
(WINAPI* ShellExecuteExA_t)(SHELLEXECUTEINFOA*);
typedef
HANDLE
(WINAPI* CreateFileA_t)(
LPCSTR
lpFileName,
DWORD
dwDesiredAccess,
DWORD
dwShareMode,
LPSECURITY_ATTRIBUTES lpSecurityAttributes,
DWORD
dwCreationDisposition,
DWORD
dwFlagsAndAttributes,
HANDLE
hTemplateFile
);
ShellExecuteExA_t TrueShellExecuteExA = NULL;
CreateFileA_t TrueCreateFileA = NULL;
BOOL
WINAPI HookedShellExecuteExA(SHELLEXECUTEINFOA* pExecInfo) {
#if 1
//执行第一个ShellExecuteExA守护进程
static
int
Num = 0;
DBGMGEBOX(
"ShellExecuteExA 被调用:Num = %d\nhProcess = %p"
, Num, pExecInfo->hProcess);
if
(Num == 0)
{
Num++;
return
TrueShellExecuteExA(pExecInfo);
}
else
{
return
TrueShellExecuteExA(pExecInfo);
}
#else
//执行第二个ShellExecuteExA病毒进程
static
int
Num = 0;
DBGMGEBOX(
"ShellExecuteExA 被调用:Num = %d \n调用者窗口句柄 = 0x%p\n"
, Num, pExecInfo->hwnd);
if
(Num == 0)
{
Num++;
DBGMGEBOX(
"[2]:当前线程ID:%d"
, GetCurrentThreadId());
pExecInfo->lpFile =
"C:\\Users\\Administrator\\Desktop\\自动F8直到call.txt"
;
//修改参数导致重启失败;
return
TrueShellExecuteExA(pExecInfo);
}
else
{
DBGMGEBOX(
"[1]:当前线程ID:%d"
, GetCurrentThreadId());
return
TrueShellExecuteExA(pExecInfo);
}
#endif
}
HANDLE
WINAPI HookCreateFileA(
LPCSTR
lpFileName,
DWORD
dwDesiredAccess,
DWORD
dwShareMode,
LPSECURITY_ATTRIBUTES lpSecurityAttributes,
DWORD
dwCreationDisposition,
DWORD
dwFlagsAndAttributes,
HANDLE
hTemplateFile
)
{
//判断线程是否是第一次运行CreateFileA,是的话就放行,不是第一次运行就终止线程
// 获取当前线程的TLS值
LPVOID
tlsValue = TlsGetValue(tlsIndex);
if
(tlsValue == NULL)
{
// 第一次运行,设置TLS值
#ifdef _DEBUG
DBGMGEBOX(
"放行\nlpFileName:%s\n"
, lpFileName);
#endif
TlsSetValue(tlsIndex, (
LPVOID
)1);
}
else
{
// 不是第一次运行,终止线程
#ifdef _DEBUG
DBGMGEBOX(
"终止\nlpFileName:%s\n"
, lpFileName);
#endif
ExitThread(0);
}
return
CreateFileA(lpFileName, dwDesiredAccess, dwShareMode, lpSecurityAttributes, dwCreationDisposition, dwFlagsAndAttributes, hTemplateFile);
}
BOOL
APIENTRY DllMain(
HMODULE
hModule,
DWORD
ul_reason_for_call,
LPVOID
lpReserved) {
switch
(ul_reason_for_call) {
case
DLL_PROCESS_ATTACH:
DetourTransactionBegin();
DetourUpdateThread(GetCurrentThread());
/*TrueShellExecuteExA = (ShellExecuteExA_t)DetourFindFunction("shell32.dll", "ShellExecuteExA");
DetourAttach(&(PVOID&)TrueShellExecuteExA, HookedShellExecuteExA);*/
tlsIndex = TlsAlloc();
//初始化TLS
TrueCreateFileA = (CreateFileA_t)DetourFindFunction(
"kernelbase.dll"
,
"CreateFileA"
);
DetourAttach(&(
PVOID
&)TrueCreateFileA, HookCreateFileA);
DetourTransactionCommit();
break
;
case
DLL_PROCESS_DETACH:
DetourTransactionBegin();
DetourUpdateThread(GetCurrentThread());
DetourDetach(&(
PVOID
&)TrueShellExecuteExA, HookedShellExecuteExA);
DetourDetach(&(
PVOID
&)TrueCreateFileA, HookCreateFileA);
TlsFree(tlsIndex);
//清理TLS
DetourTransactionCommit();
break
;
}
return
TRUE;
}
#include "pch.h"
#include <windows.h>
#include <shellapi.h>
#include <detours.h>
#include <tlhelp32.h>
#pragma comment(lib,"detours.lib")
#define _DEBUG
#define DBGMGEBOX(fmt, ...) \
do
{ \
/* 假设最大长度为1024,根据需要调整大小 */
\
wsprintfA(out, fmt, __VA_ARGS__); \
MessageBoxA(NULL, out,
"提示"
, MB_OK); \
}
while
(0)
char
out[100];
DWORD
tlsIndex;
//tls索引
typedef
BOOL
(WINAPI* ShellExecuteExA_t)(SHELLEXECUTEINFOA*);
typedef
HANDLE
(WINAPI* CreateFileA_t)(
LPCSTR
lpFileName,
DWORD
dwDesiredAccess,
DWORD
dwShareMode,
LPSECURITY_ATTRIBUTES lpSecurityAttributes,
DWORD
dwCreationDisposition,
DWORD
dwFlagsAndAttributes,
HANDLE
hTemplateFile
);
ShellExecuteExA_t TrueShellExecuteExA = NULL;
CreateFileA_t TrueCreateFileA = NULL;
BOOL
WINAPI HookedShellExecuteExA(SHELLEXECUTEINFOA* pExecInfo) {
#if 1
//执行第一个ShellExecuteExA守护进程
static
int
Num = 0;
DBGMGEBOX(
"ShellExecuteExA 被调用:Num = %d\nhProcess = %p"
, Num, pExecInfo->hProcess);
if
(Num == 0)
{
Num++;
return
TrueShellExecuteExA(pExecInfo);
}
else
{
return
TrueShellExecuteExA(pExecInfo);
}
#else
//执行第二个ShellExecuteExA病毒进程
static
int
Num = 0;
DBGMGEBOX(
"ShellExecuteExA 被调用:Num = %d \n调用者窗口句柄 = 0x%p\n"
, Num, pExecInfo->hwnd);
if
(Num == 0)
{
Num++;
DBGMGEBOX(
"[2]:当前线程ID:%d"
, GetCurrentThreadId());
pExecInfo->lpFile =
"C:\\Users\\Administrator\\Desktop\\自动F8直到call.txt"
;
//修改参数导致重启失败;
return
TrueShellExecuteExA(pExecInfo);
}
else
{
DBGMGEBOX(
"[1]:当前线程ID:%d"
, GetCurrentThreadId());
return
TrueShellExecuteExA(pExecInfo);
}
#endif
}
HANDLE
WINAPI HookCreateFileA(
LPCSTR
lpFileName,
DWORD
dwDesiredAccess,
DWORD
dwShareMode,
LPSECURITY_ATTRIBUTES lpSecurityAttributes,
DWORD
dwCreationDisposition,
DWORD
dwFlagsAndAttributes,
HANDLE
hTemplateFile
)
{
//判断线程是否是第一次运行CreateFileA,是的话就放行,不是第一次运行就终止线程
// 获取当前线程的TLS值
LPVOID
tlsValue = TlsGetValue(tlsIndex);
if
(tlsValue == NULL)
{
// 第一次运行,设置TLS值
#ifdef _DEBUG
DBGMGEBOX(
"放行\nlpFileName:%s\n"
, lpFileName);
#endif
TlsSetValue(tlsIndex, (
LPVOID
)1);
}
else
{
// 不是第一次运行,终止线程
#ifdef _DEBUG
DBGMGEBOX(
"终止\nlpFileName:%s\n"
, lpFileName);
#endif
ExitThread(0);
}
return
CreateFileA(lpFileName, dwDesiredAccess, dwShareMode, lpSecurityAttributes, dwCreationDisposition, dwFlagsAndAttributes, hTemplateFile);
}
BOOL
APIENTRY DllMain(
HMODULE
hModule,
DWORD
ul_reason_for_call,
LPVOID
lpReserved) {
switch
(ul_reason_for_call) {
case
DLL_PROCESS_ATTACH:
DetourTransactionBegin();
DetourUpdateThread(GetCurrentThread());
/*TrueShellExecuteExA = (ShellExecuteExA_t)DetourFindFunction("shell32.dll", "ShellExecuteExA");
DetourAttach(&(PVOID&)TrueShellExecuteExA, HookedShellExecuteExA);*/
tlsIndex = TlsAlloc();
//初始化TLS
TrueCreateFileA = (CreateFileA_t)DetourFindFunction(
"kernelbase.dll"
,
"CreateFileA"
);
DetourAttach(&(
PVOID
&)TrueCreateFileA, HookCreateFileA);
DetourTransactionCommit();
break
;
case
DLL_PROCESS_DETACH:
DetourTransactionBegin();
DetourUpdateThread(GetCurrentThread());
DetourDetach(&(
PVOID
&)TrueShellExecuteExA, HookedShellExecuteExA);
DetourDetach(&(
PVOID
&)TrueCreateFileA, HookCreateFileA);
TlsFree(tlsIndex);
//清理TLS
DetourTransactionCommit();
break
;
}
return
TRUE;
}
HANDLE
CreateFileA(
LPCSTR
lpFileName,
// 文件名
DWORD
dwDesiredAccess,
// 访问模式
DWORD
dwShareMode,
// 共享模式
LPSECURITY_ATTRIBUTES lpSecurityAttributes,
// 安全属性
DWORD
dwCreationDisposition,
// 创建或打开文件的方式
DWORD
dwFlagsAndAttributes,
// 文件属性
HANDLE
hTemplateFile
// 模板文件句柄
);
HANDLE
CreateFileA(
LPCSTR
lpFileName,
// 文件名
DWORD
dwDesiredAccess,
// 访问模式
DWORD
dwShareMode,
// 共享模式
LPSECURITY_ATTRIBUTES lpSecurityAttributes,
// 安全属性
DWORD
dwCreationDisposition,
// 创建或打开文件的方式
DWORD
dwFlagsAndAttributes,
// 文件属性
HANDLE
hTemplateFile
// 模板文件句柄
);
FILE_FLAG_DELETE_ON_CLOSE0x04000000 | 文件将在关闭所有句柄后立即删除,其中包括指定的句柄和任何其他打开或重复的句柄。 |
---|
HANDLE
WINAPI HookCreateFileA(
LPCSTR
lpFileName,
DWORD
dwDesiredAccess,
DWORD
dwShareMode,
LPSECURITY_ATTRIBUTES lpSecurityAttributes,
DWORD
dwCreationDisposition,
DWORD
dwFlagsAndAttributes,
HANDLE
hTemplateFile
)
{
//缩小每个线程执行CreateFileA的次数,CPU降低到%5以下
// 获取当前线程的TLS值
LPVOID
tlsValue = TlsGetValue(tlsIndex);
if
((
DWORD_PTR
)tlsValue <= 0x10)
{
#ifdef _KDEBUG
DBGMGEBOX(
"放行\nlpFileName:%s\n"
, lpFileName);
#endif
dwFlagsAndAttributes = FILE_ATTRIBUTE_NORMAL;
TlsSetValue(tlsIndex, (
LPVOID
)((
DWORD_PTR
)tlsValue + 1));
}
else
{
#ifdef _KDEBUG
DBGMGEBOX(
"终止\nlpFileName:%s\n"
, lpFileName);
#endif
ExitThread(0);
}
return
TrueCreateFileA(lpFileName, dwDesiredAccess, dwShareMode, lpSecurityAttributes, dwCreationDisposition, dwFlagsAndAttributes, hTemplateFile);
}
HANDLE
WINAPI HookCreateFileA(
LPCSTR
lpFileName,
DWORD
dwDesiredAccess,
DWORD
dwShareMode,
LPSECURITY_ATTRIBUTES lpSecurityAttributes,
DWORD
dwCreationDisposition,
DWORD
dwFlagsAndAttributes,
HANDLE
hTemplateFile
)
{
//缩小每个线程执行CreateFileA的次数,CPU降低到%5以下
// 获取当前线程的TLS值
LPVOID
tlsValue = TlsGetValue(tlsIndex);
if
((
DWORD_PTR
)tlsValue <= 0x10)
{
#ifdef _KDEBUG
DBGMGEBOX(
"放行\nlpFileName:%s\n"
, lpFileName);
#endif
dwFlagsAndAttributes = FILE_ATTRIBUTE_NORMAL;
TlsSetValue(tlsIndex, (
LPVOID
)((
DWORD_PTR
)tlsValue + 1));
}
else
{
#ifdef _KDEBUG
DBGMGEBOX(
"终止\nlpFileName:%s\n"
, lpFileName);
#endif
ExitThread(0);
}
return
TrueCreateFileA(lpFileName, dwDesiredAccess, dwShareMode, lpSecurityAttributes, dwCreationDisposition, dwFlagsAndAttributes, hTemplateFile);
}
int
main() {
while
(1)
{
int
Num = 0;
ve = GetPidByProcName(
"WorkingService.exe"
);
if
(!ve.empty())
{
printf
(
"There are currently %d processes\n"
, ve.size());
//依次注入并用map标记是否被注入过
for
(
auto
i : ve)
{
auto
it = mp.find(i);
if
(it == mp.end())
//没有被注入过
{
HANDLE
hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, i);
if
(InjectDll(hProcess, dllPath))
{
Num++;
mp.insert({ i,
true
});
printf
(
"Inject process Success!\n"
, Num);
}
}
else
//曾经注入过该进程
{
printf
(
"[%d] Has Been Injected\n"
,i);
continue
;
}
}
}
else
{
printf
(
"Wating WorkingService.exe ...\n"
);
}
system
(
"cls"
);
//Sleep(200);
}
return
0;
}
int
main() {
while
(1)
{
int
Num = 0;
ve = GetPidByProcName(
"WorkingService.exe"
);
if
(!ve.empty())
{
printf
(
"There are currently %d processes\n"
, ve.size());
//依次注入并用map标记是否被注入过
for
(
auto
i : ve)
{
auto
it = mp.find(i);
if
(it == mp.end())
//没有被注入过
{
HANDLE
hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, i);
if
(InjectDll(hProcess, dllPath))
{
Num++;
mp.insert({ i,
true
});
printf
(
"Inject process Success!\n"
, Num);
}
}
else
//曾经注入过该进程
{
printf
(
"[%d] Has Been Injected\n"
,i);
continue
;
}
}
}
else
{
printf
(
"Wating WorkingService.exe ...\n"
);
}
system
(
"cls"
);
//Sleep(200);
}
return
0;
}
//main.cpp
#include <windows.h>
#include <tlhelp32.h>
#include <stdio.h>
#include <vector>
#include <map>
char
dllPath[] = R
"(C:\Users\15386\Desktop\temp\hookDll.dll)"
;
std::vector<
DWORD64
>ve;
std::map<
DWORD64
,
bool
>mp;
BYTE
jmpBytes[] = {
0xE9, 0xE0, 0x26, 0x16, 0x00
};
BYTE
originBytes[] = {
0x4C, 0x8B, 0xD1, 0xB8, 0x50, 0x00, 0x00, 0x00, 0xF6, 0x04, 0x25, 0x08, 0x03, 0xFE, 0x7F, 0x01,
0x75, 0x03, 0x0F, 0x05, 0xC3, 0xCD, 0x2E, 0xC3
};
std::vector<
DWORD64
> GetPidByProcName(
const
char
* processName) {
HANDLE
hProcessSnap = INVALID_HANDLE_VALUE;
PROCESSENTRY32 pe32 = { 0 };
std::vector<
DWORD64
> vec;
vec.clear();
hProcessSnap = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
if
(hProcessSnap == INVALID_HANDLE_VALUE) {
vec;
}
pe32.dwSize =
sizeof
(PROCESSENTRY32);
if
(Process32First(hProcessSnap, &pe32)) {
do
{
if
(
strcmp
(pe32.szExeFile, processName) == 0) {
vec.push_back(pe32.th32ProcessID);
}
}
while
(Process32Next(hProcessSnap, &pe32));
}
CloseHandle(hProcessSnap);
return
vec;
}
DWORD64
GetModuleBase(
DWORD64
pid,
const
char
* ModuleName)
{
HANDLE
hModuleSnap = CreateToolhelp32Snapshot(TH32CS_SNAPMODULE, pid);
if
(hModuleSnap != INVALID_HANDLE_VALUE) {
MODULEENTRY32 me32;
me32.dwSize =
sizeof
(MODULEENTRY32);
if
(Module32First(hModuleSnap, &me32)) {
do
{
if
(_stricmp(me32.szModule, ModuleName) == 0) {
return
(
DWORD64
)me32.modBaseAddr;
}
}
while
(Module32Next(hModuleSnap, &me32));
}
CloseHandle(hModuleSnap);
}
return
0;
}
BOOL
InjectDll(
HANDLE
hProcess,
LPCSTR
dllPath) {
LPVOID
pRemoteDllPath = VirtualAllocEx(hProcess, NULL,
strlen
(dllPath) + 1, MEM_COMMIT, PAGE_READWRITE);
if
(pRemoteDllPath == NULL) {
printf
(
"VirtualAllocEx Failed:[%d]\n"
, GetLastError());
return
FALSE;
}
if
(!WriteProcessMemory(hProcess, pRemoteDllPath, dllPath,
strlen
(dllPath) + 1, NULL)) {
printf
(
"WriteProcessMemory Failed:[%d]\n"
, GetLastError());
VirtualFreeEx(hProcess, pRemoteDllPath, 0, MEM_RELEASE);
return
FALSE;
}
LPTHREAD_START_ROUTINE lpLoadLibrary = (LPTHREAD_START_ROUTINE)GetProcAddress(GetModuleHandleA(
"kernel32.dll"
),
"LoadLibraryA"
);
if
(lpLoadLibrary == NULL) {
printf
(
"GetProcAddress Failed:[%d]\n"
, GetLastError());
VirtualFreeEx(hProcess, pRemoteDllPath, 0, MEM_RELEASE);
return
FALSE;
}
HANDLE
hThread = CreateRemoteThread(hProcess, NULL, 0, lpLoadLibrary, pRemoteDllPath, 0, NULL);
if
(hThread == NULL) {
printf
(
"CreateRemoteThread Failed:[%d]\n"
, GetLastError());
VirtualFreeEx(hProcess, pRemoteDllPath, 0, MEM_RELEASE);
return
FALSE;
}
WaitForSingleObject(hThread, INFINITE);
DWORD
dwExitCode;
if
(GetExitCodeThread(hThread, &dwExitCode) && dwExitCode == 0) {
printf
(
"LoadLibraryA Failed in remote process\n"
);
CloseHandle(hThread);
VirtualFreeEx(hProcess, pRemoteDllPath, 0, MEM_RELEASE);
return
FALSE;
}
CloseHandle(hThread);
VirtualFreeEx(hProcess, pRemoteDllPath, 0, MEM_RELEASE);
return
TRUE;
}
BOOL
RemoveHook(
HANDLE
hProcess,
PVOID
unHookAddr,
BYTE
* originBytes)
{
PDWORD oldProtect = 0;
if
(!WriteProcessMemory(hProcess, (
PVOID
)unHookAddr, originBytes,
sizeof
(originBytes), 0))
{
printf
(
"RemoveHook Failed!!!: [%d]\n"
, GetLastError());
return
FALSE;
}
}
int
main() {
while
(1)
{
int
Num = 0;
ve = GetPidByProcName(
"WorkingService.exe"
);
//获取所有进程pid
if
(!ve.empty())
{
printf
(
"There are currently %d processes\n"
, ve.size());
//依次注入并用map标记是否被注入过
for
(
auto
i : ve)
{
auto
it = mp.find(i);
if
(it == mp.end())
//没有被注入过
{
HANDLE
hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, i);
//要用管理员权限运行,要不然为返回空
if
(!hProcess)
printf
(
"OpenProcess Error:[%d]"
, GetLastError());
DWORD64
unHookAddr = GetModuleBase(i,
"ntdll.dll"
) + 0xA0990;
//NtProtectVirtualMemory地址,ntdll + 0xA0990
if
(RemoveHook(hProcess, (
PVOID
)unHookAddr, originBytes))
//先取消NtProtectVirtualMemory钩子再注入
{
if
(InjectDll(hProcess, dllPath))
{
Num++;
mp.insert({ i,
true
});
//注入成功后进行标记
printf
(
"Inject process Success!\n"
, Num);
}
}
}
else
continue
;
//曾经注入过该进程
}
}
else
printf
(
"Wating WorkingService.exe ...\n"
);
Sleep(1000);
system
(
"cls"
);
}
return
0;
}
//main.cpp
#include <windows.h>
#include <tlhelp32.h>
#include <stdio.h>
#include <vector>
#include <map>
char
dllPath[] = R
"(C:\Users\15386\Desktop\temp\hookDll.dll)"
;
std::vector<
DWORD64
>ve;
std::map<
DWORD64
,
bool
>mp;
BYTE
jmpBytes[] = {
[注意]传递专业知识、拓宽行业人脉——看雪讲师团队等你加入!
赞赏
- [原创]2024年腾讯游戏安全竞赛PC初赛复现 5492
- [原创]2023年腾讯游戏安全PC决赛复现 3485
- 2022腾讯游戏安全PC端初赛复现 6433
- [原创]2023年腾讯游戏安全竞赛PC端初赛复现 5314