当前位置: 首页 > news >正文

软件反调试(7)- 基于NtSetInformationThread设置线程信息

反调原理

NtSetInformationThread 和 ZwSetInformationThread 是本质相同的函数,Nt 位于 ntdll.dll 模块,在用户模式下供应用程序调用

Zw 位于内核模式中,供驱动/内核模块使用,它们的调用关系如下

NtSetInformationThread -> syscall -> 内核 -> ZwSetInformationThread(或统一的内核服务处理函数)

通过 NtSetInformationThread 设置线程的某些属性,可以对调试器的行为造成干扰,最常见的反调试用途是 隐藏线程

函数原型如下

__kernel_entry NTSYSCALLAPI NTSTATUS NtSetInformationThread([in] HANDLE          ThreadHandle,[in] THREADINFOCLASS ThreadInformationClass,[in] PVOID           ThreadInformation,[in] ULONG           ThreadInformationLength
);

当 ThreadInformationClass 参数设置为 ThreadHideFromDebugger (0x11) 时,会通知系统 “隐藏当前线程”,对于某些调试器而言

会导致它们在断点设置、单步执行或监视线程行为时失败,某些断点会被绕过,在 “调试时段检测” 或 “异常处理” 类的反调试方法中特别有效

该函数仅对设置的线程生效,但对其他未设置的线程无效,其他线程依旧可被调试,如果需要隐藏所有的线程,需要对每个线程都进行设置

由于一些业务线程的启动时间是不确定的,实现上可以使用循环设置,每次循环都将进程中的所有线程全部设置一遍,对单个线程多次重复设置是不会有任何影响的

不过 x64dbg 已完全绕过或忽略这个反调试标志,NtSetInformationThread 在 x64dbg 中是没有效果的,该设置的主要目的是防止系统在该线程上触发调试事件通知

比如调试器用 WaitForDebugEvent() 时,隐藏线程的异常不会被推送过去,由于 x64dbg 不依赖这种调试事件转发,它是直接去查看内存、断点、指令流的

在 vs2022 的调试器(msvsmon)中可以生效,导致其在调试时无法正确处理该线程的调试事件,从而出现挂起、卡死、无法断点或丢失线程事件的现象

代码实现

完整实现代码如下,在 vs2022 中调试运行的时候,对代码下的断点是无效的,部分情况下会导致 vs2022 在调试的时候卡死

#include <iostream>
#include <thread>
#include <string>
#include <windows.h>
#include <TlHelp32.h>
#include <winternl.h>typedef NTSTATUS(WINAPI* FnNtSetInformationThread)(HANDLE          ThreadHandle,THREADINFOCLASS ThreadInformationClass,PVOID           ThreadInformation,ULONG           ThreadInformationLength
);bool SetThreadInfo(HANDLE hThread)
{HMODULE hModule = LoadLibrary("ntdll.dll");FnNtSetInformationThread fn = (FnNtSetInformationThread)GetProcAddress(hModule, "NtSetInformationThread");NTSTATUS status = fn(hThread, (THREADINFOCLASS)0x11, 0, 0);return (status == 0);
}bool SetInformationThread()
{DWORD dwProcessId = GetProcessId(GetCurrentProcess());HANDLE hThreadSnap = CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, 0);if (hThreadSnap == INVALID_HANDLE_VALUE)return false;THREADENTRY32 th32;th32.dwSize = sizeof(THREADENTRY32);BOOL bRet = Thread32First(hThreadSnap, &th32);while (bRet){if (th32.th32OwnerProcessID == dwProcessId){HANDLE hThread = OpenThread(THREAD_ALL_ACCESS, FALSE, th32.th32ThreadID);bool ok = SetThreadInfo(hThread);if (ok) {std::cout << "hide thread succ:" << th32.th32ThreadID << std::endl;}else {std::cout << "hide thread fail:" << th32.th32ThreadID << std::endl;}CloseHandle(hThread);}bRet = Thread32Next(hThreadSnap, &th32);}CloseHandle(hThreadSnap);return false;
}void AntiThreadProc()
{while (true){SetInformationThread(); //设置反调试std::this_thread::sleep_for(std::chrono::milliseconds(5000));}
}void ThreadProc()
{while (true){std::cout << "business Running, thread:" << std::this_thread::get_id() << std::endl;std::this_thread::sleep_for(std::chrono::milliseconds(5000));}
}int main()
{std::thread thrd1(AntiThreadProc); std::thread thrd2(ThreadProc);thrd1.join();thrd2.join();return 0;
}

逆向处理

基于 x64dbg 已经完全绕过了 NtSetInformationThread 的反调试,不需要进行任何设置即可实现绕过

查看效果可以使用 API HOOK 进行用户态劫持 NtSetInformationThread,这里基于开源的 MinHook 库进行实现,下载地址 https://github.com/TsudaKageyu/minhook

注入动态库

创建一个 inject.dll 工程,工程依赖 MinHook 库,包含两个代码文件,dllmain.cpp 代码如下,在 DLL_PROCESS_ATTACH 的时候调用 Hook 实现

#include <windows.h>void Hook();BOOL APIENTRY DllMain( HMODULE hModule,DWORD  ul_reason_for_call,LPVOID lpReserved)
{switch (ul_reason_for_call){case DLL_PROCESS_ATTACH:DisableThreadLibraryCalls(hModule);Hook();break;case DLL_THREAD_ATTACH:case DLL_THREAD_DETACH:case DLL_PROCESS_DETACH:break;}return TRUE;
}

另一个代码文件 hook.cpp 完整代码如下,Hook 函数中将 ntdll.dll 中的 NtSetInformationThread 方法劫持到自定义的实现函数中

Hook 方法调用的时候输出 start to hook...,在自定义的函数中,当调用到该函数的时候打印一句 Hook is running... 信息


#include <windows.h>
#include <winternl.h>
#include <iostream>
#include "MinHook.h"typedef NTSTATUS(WINAPI* pNtSetInformationThread)(HANDLE          ThreadHandle,THREADINFOCLASS ThreadInformationClass,PVOID           ThreadInformation,ULONG           ThreadInformationLength
);pNtSetInformationThread OriginalNtSetInformationThread = nullptr;NTSTATUS NTAPI HookedNtSetInformationThread(HANDLE ThreadHandle,THREADINFOCLASS ThreadInformationClass,PVOID ThreadInformation,ULONG ThreadInformationLength
) {// 拦截 ThreadHideFromDebugger(0x11)if (ThreadInformationClass == (THREADINFOCLASS)0x11) {std::cout << "Hook is running..." << std::endl;return 0; // 伪装调用成功}return OriginalNtSetInformationThread(ThreadHandle, ThreadInformationClass, ThreadInformation, ThreadInformationLength);
}void Hook() {MH_Initialize();std::cout << "start to hook..." << std::endl;HMODULE hNtdll = GetModuleHandleA("ntdll.dll");if (hNtdll) {void* pTarget = GetProcAddress(hNtdll, "NtSetInformationThread");if (pTarget) {MH_CreateHook(pTarget, &HookedNtSetInformationThread, (LPVOID*)&OriginalNtSetInformationThread);MH_EnableHook(pTarget);}}
}

注入程序

创建一个 MyHook 项目,实现将一个动态库注入到目标进程中,运行的时候接受两个参数,目标进程ID,以及待注入的动态库

主要原理如下

1、打开目标进程,获取对目标进程的所有权限(读写内存、创建线程等)

2、使用跨进程分配内存的 VirtualAllocEx 函数,在目标进程内分配一段读写内存,用于存放 DLL 路径字符串

3、把 DLL 路径字符串写入到刚才分配的远程内存中,这就是远程线程中将被 LoadLibraryA 加载的参数

4、获取本进程中 kernel32.dll 中 LoadLibraryA 的地址,因为 kernel32.dll 是系统 DLL,系统中的所有进程是一定会加载的

5、在目标进程中创建一个线程,入口是 LoadLibraryA,参数是我们写入的 DLL 路径,从而把 DLL 加载进自己的地址空间

完整代码如下

#include <windows.h>
#include <tlhelp32.h>
#include <tchar.h>
#include <iostream>BOOL InjectDLL(DWORD dwPID, const char* dllPath) 
{HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwPID);if (!hProcess) {std::cerr << "OpenProcess failed\n";return FALSE;}LPVOID pRemoteMemory = VirtualAllocEx(hProcess, nullptr, strlen(dllPath) + 1, MEM_COMMIT, PAGE_READWRITE);if (!pRemoteMemory){std::cerr << "VirtualAllocEx failed\n";CloseHandle(hProcess);return FALSE;}WriteProcessMemory(hProcess, pRemoteMemory, dllPath, strlen(dllPath) + 1, nullptr);LPTHREAD_START_ROUTINE pLoadLibrary = (LPTHREAD_START_ROUTINE)GetProcAddress(GetModuleHandleA("kernel32.dll"), "LoadLibraryA");HANDLE hThread = CreateRemoteThread(hProcess, nullptr, 0, pLoadLibrary, pRemoteMemory, 0, nullptr);if (!hThread) {std::cerr << "CreateRemoteThread failed\n";VirtualFreeEx(hProcess, pRemoteMemory, 0, MEM_RELEASE);CloseHandle(hProcess);return FALSE;}WaitForSingleObject(hThread, INFINITE);VirtualFreeEx(hProcess, pRemoteMemory, 0, MEM_RELEASE);CloseHandle(hThread);CloseHandle(hProcess);return TRUE;
}int main(int argc, char** argv)
{if (argc != 3) {std::cout << "Usage: <MyHook.exe> <pid> <inject.dll>";return -1;}DWORD pid = atoi(argv[1]);const char* dllPath = argv[2];if (InjectDLL(pid, dllPath)){std::cout << "DLL injected successfully!\n";}else{std::cerr << "DLL injection failed.\n";}return 0;
}

逆向效果

将前面的动态库编译为 inject.dll,执行文件编译为 MyHook.exe,然后运行反调试的 anti07.exe 执行文件

开始的时候 anti07.exe 正常运行,接着运行 MyHook.exe 37080 inject.dll 将动态库进行注入,输出 Hook is running... 信息

在目标进程的模块列表中,也可以看到 inject.dll 动态库记录项,说明已经达到了预期的效果

http://www.lryc.cn/news/606924.html

相关文章:

  • 邮件系统哪个好?3种类型邮件系统详细对比
  • 阿里ai流式输出
  • OpenAI ChatGPT Agent横空出世:全能工具+实时交互,重新定义AI智能体的终极形态
  • java的冒泡排序算法
  • 多人命题系统
  • ⭐ Unity 实现UI视差滚动效果(Parallax)鼠标控制、可拓展陀螺仪与脚本控制
  • linux81 shell通配符:[list],‘‘ ``““
  • React Refs:直接操作DOM的终极指南
  • flutter——ColorScheme
  • DM8达梦数据库错误码信息汇编-8.1.4.80 20250430-272000-20149 Pack1
  • 【3】交互式图表制作及应用方法
  • 38译码器工作原理
  • 肖特基二极管MBR0540T1G 安森美ON 低电压 高频率 集成电路IC 芯片
  • 三十九、【扩展工具篇】Allpairspy 组合用例生成器:智能设计高效测试集
  • 德国威乐集团亚太中东非洲PMO负责人和继明受邀为PMO大会主持人
  • 【AMD | Docker】超级全面版本:在Docker中验证AMD GPU移动显卡可用性的方法
  • n8n】n8n的基础概念
  • 海外商城 app 系统架构分析
  • CMake Debug/Release配置生成器表达式解析
  • Kafka Streams 并行处理机制深度解析:任务(Task)与流线程(Stream Threads)的协同设计
  • 调试 Rust 生成的 WebAssembly
  • 阻塞队列特性
  • K-Means聚类:当数据没有标签时,如何让计算机自动“物以类聚”?
  • 字符串的高效处理String‘Builder类(高频率的字符串拼接)
  • 每日面试题18:基本数据类型和引用数据类型的区别
  • 转换图(State Transition Diagram)和时序图(Sequence Diagram)画图流程图工具
  • PHP在现代Web开发中的应用与优势分析
  • 汽车EDI:Vitesco EDI 项目案例
  • 在Centos7中安装gitlab
  • flutter-boilerplate-project 学习笔记