大多数恶意代码为了隐藏自己的行踪都会附加到某个进程中,在这个进程内申请一块内存区域来存放它的代码,毕竟隐藏的再好,代码也要有的,今天检测的特征是向YY语音里插入了一段自己的代码(创建了新的线程),而这个新的线程不在原有的模块内,所以思路就是遍历YY.exe这个进程中的所有线程,如果这个线程没有对应的模块,那么就说明这个线程是可疑的。
准备工作,定义一些核心结构体变量。
#pragma region 依赖
typedef enum _THREADINFOCLASS{
ThreadBasicInformation,
ThreadTimes,
ThreadPriority,
ThreadBasePriority,
ThreadAffinityMask,
ThreadImpersonationToken,
ThreadDescriptorTableEntry,
ThreadEnableAlignmentFaultFixup,
ThreadEventPair_Reusable,
ThreadQuerySetWin32StartAddress,
ThreadZeroTlsCell,
ThreadPerformanceCount,
ThreadAmILastThread,
ThreadIdealProcessor,
ThreadPriorityBoost,
ThreadSetTlsArrayAddress,
ThreadIsIoPending,
ThreadHideFromDebugger,
ThreadBreakOnTermination,
MaxThreadInfoClass
}THREADINFOCLASS;
typedef struct _CLIENT_ID{
HANDLE UniqueProcess;
HANDLE UniqueThread;
}CLIENT_ID;
typedef struct _THREAD_BASIC_INFORMATION{
LONG ExitStatus;
PVOID TebBaseAddress;
CLIENT_ID ClientId;
LONG AffinityMask;
LONG Priority;
LONG BasePriority;
}THREAD_BASIC_INFORMATION,*PTHREAD_BASIC_INFORMATION;
extern "C" LONG (__stdcall *ZwQueryInformationThread)(
IN HANDLE ThreadHandle,
IN THREADINFOCLASS ThreadInformationClass,
OUT PVOID ThreadInformation,
IN ULONG ThreadInformationLength,
OUT PULONG ReturnLength OPTIONAL
) = NULL;
#pragma endregion
功能实现核心代码部分如下。
HANDLE hProcessSnap = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS,0); // 进程快照句柄
PROCESSENTRY32 process = {sizeof(PROCESSENTRY32)}; // 进程快照信息
// 遍历进程,找到 YY.exe
while (Process32Next(hProcessSnap,&process)){
string s_szExeFile = process.szExeFile; // char* 转 string
if(s_szExeFile == "YY.exe"){
HANDLE hThreadSnap = INVALID_HANDLE_VALUE; // 线程快照句柄
THREADENTRY32 te32; // 线程快照信息
// 创建线程快照
hThreadSnap = CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, 0);
if (hThreadSnap == INVALID_HANDLE_VALUE){cout << "创建线程快照失败" << endl;}
// 为快照分派内存空间
te32.dwSize = sizeof(THREADENTRY32);
// 获取第一个线程的信息
if (!Thread32First(hThreadSnap, &te32)){cout << "线程信息获取失败" << endl;}
// 遍历线程
while (Thread32Next(hThreadSnap, &te32)){
// 线程属于 YY.exe
if(te32.th32OwnerProcessID == process.th32ProcessID){
// 打开线程
HANDLE hThread = ::OpenThread (
THREAD_ALL_ACCESS, // 访问权限,THREAD_ALL_ACCESS :所有权限
FALSE, // 由此线程创建的进程不继承线程的句柄
te32.th32ThreadID // 线程 ID
);
if(hThread == NULL){cout << "线程打开失败" << endl;}
// 将区域设置设置为从操作系统获取的ANSI代码页
setlocale(LC_ALL,".ACP");
// 获取 ntdll.dll 的模块句柄
HINSTANCE hNTDLL = ::GetModuleHandle("ntdll");
// 从 ntdll.dll 中取出 ZwQueryInformationThread
(FARPROC&)ZwQueryInformationThread = ::GetProcAddress(hNTDLL,"ZwQueryInformationThread");
// 获取线程入口地址
PVOID startaddr; // 用来接收线程入口地址
ZwQueryInformationThread(
hThread, // 线程句柄
ThreadQuerySetWin32StartAddress, // 线程信息类型,ThreadQuerySetWin32StartAddress :线程入口地址
&startaddr, // 指向缓冲区的指针
sizeof(startaddr), // 缓冲区的大小
NULL
);
// 获取线程所在模块
THREAD_BASIC_INFORMATION tbi; // _THREAD_BASIC_INFORMATION 结构体对象
TCHAR modname[MAX_PATH]; // 用来接收模块全路径
ZwQueryInformationThread(
hThread, // 线程句柄
ThreadBasicInformation, // 线程信息类型,ThreadBasicInformation :线程基本信息
&tbi, // 指向缓冲区的指针
sizeof(tbi), // 缓冲区的大小
NULL
);
// 检查入口地址是否位于某模块中
GetMappedFileName(
::OpenProcess( // 进程句柄
PROCESS_ALL_ACCESS, // 访问权限,THREAD_ALL_ACCESS :所有权限
FALSE, // 由此线程创建的进程不继承线程的句柄
(DWORD)tbi.ClientId.UniqueProcess // 唯一进程 ID
),
startaddr, // 要检查的地址
modname, // 用来接收模块名的指针
MAX_PATH // 缓冲区大小
);
// 判断线程是否在模块中
if(modname[0] == '?'){cout << "线程不在模块中" << endl;}
}
}
}
}