一、 背景:
众所周知,X大师的硬件检测功能很强,于是有了想分析下原理的想法。


二、 逆向:
经过分析,X大师分析硬件的方法有些复杂,要还原其功能,然后自己重写,工作量很大。所以采用调用X大师二进制文件接口的方法,获取硬件信息。下面着重介绍分析思路:
1. 获得”硬件总览信息”:
老规矩,先看X大师的日志文件,找到下面的信息:

有关键字GetOverviewJson,下面的信息是CPU、主板、供应商,说明是”硬件总览信息”对应的日志信息。
然后分析X大师的进程和文件,发现有下面几个进程:

其中ComputerZ_CN.exe是界面进程,ComputerZService.exe是个服务进程:

猜测ComputerZService.exe调用一些dll进行实际的硬件检测等功能,ComputerZ_CN.exe负责展示。
再看文件:

看名字ComputerZ_HardwareDll.dll像是实现硬件检测功能的DLL,需重点分析。
基于前面的日志文件,上IDA打开ComputerZ_HardwareDll.dll,搜索GetOverviewJson字符串:

找到引用的地方:

可以看到Machine、OS、CPU、Motherboard等字符串。说明此处正是”硬件总览信息”对应的代码位置。
首先想到的是直接构造参数调用此函数。但经过尝试,程序各种报错。想到是X大师在调用此函数前做了些初始化工作,而直接调用是没有经过初始化的。后面也分析出了好多初始化操作,补上后,程序还是报错。
于是尝试调用X大师靠上层的一些函数接口,也就是一开始扫描硬件就会调用的函数。
看ComputerZ_HardwareDll.dll的导出函数:

看名称,就一个CreateHdwInfo函数是创建硬件信息的,应该是扫描硬件前调用的。并没有扫描的函数。
ComputerZ_HardwareDll.dll找不到,我们就去调用ComputerZ_HardwareDll.dll的exe,也就是ComputerZService.exe中去找。看它调用了 ComputerZ_HardwareDll.dll哪些函数。IDA中搜“ComputerZ_HardwareDll.dll”关键字:

可以看到并未搜索到,但是这个字符串是存在的:


看到是调用了dll的CreateHdwInfo和hardware_info_main函数。hardware_info_main函数在dll中如下:

我们调用hardware_info_main时,第一个参数需要传this指针,至于this指针应该填充什么,也是在IDA中分析调用的上下文得出的。下图是分析出填充的this指针内容:

ComputerZService.exe最后调用了:


在sub_34D680函数中调用了ComputerZ_HardwareDll.dll扫描函数。为什么是
(*(*v14 + 0x1C))(); 这里调用了扫描函数呢?答案是在ComputerZ_HardwareDll.dll的g_GetOverviewJson(识别出来后重命名的)处下断点,然后栈回溯到ComputerZService.exe的调用空间:

于是写代码依次调用这3个函数,并在g_GetOverviewJson中Hook一下,就能拿到"硬件总览信息”了:


拿到本机的信息如下:

2. 获得GPU使用率和温度:
最简单、最直接的方法是使用Cheat Engine搜索数值,然后对数值下硬件写入断点,就能定位到代码位置。
因为GPU的使用率和温度数值是变化的:

用CE的First Scan、Next Scan可以搜索到变化的值。所以是最简单的方法:

搜索到后定位到下图的代码处:

于是Hook就可以拿到GPU的使用率和温度数据了。
3. 获得CPU使用率和温度:
在CE搜索CPU使用率时,刚开始对搜索结果进行了排除,只留了一个数据。导致定位时没找到地方,只找到了ComputerZService.exe对数据的处理部分:

其实是3个数据:

依次查看这3条数据:在数据位置点击右键->find out what writes to this address.

第一条数据是在ComputerZService.exe中,排除。

第二条数据也是在ComputerZService.exe中,排除。

第三条数据是在ComputerZ_HardwareDll.dll中。于是IDA中定位到代码位置:

Hook之拿数据。
CPU温度的获取,由于数值变化较大,不好用CE精确搜索数值。首次精确搜索后,后续采用变化的数值进行搜索:

定位到如下代码位置:

HOOK之。
4. 获得CPU温度时的问题:
测试自己写的拿X大师CPU温度时遇到个问题:先打开x大师,后运行自己的程序时,CPU温度拿不到。先打开自己运行的程序,X大师拿不到CPU温度。
问题的原因,初步认为时程序独占代打开了CPU温度的资源。由于CPU温度的获取要用驱动程序实现,猜测是打开驱动时做了限制。
从CPU温度赋值的地方,往上查找”v138”的交叉引用:

找到是v84赋值过去的

使用IDA的动态调试跟入(*(*v83 + 128))(v83)这个函数调用:

不打开x大师,运行自己的程序时,this+0x70和this+0x74是有值的,分别指向IntelCpuKernelTemperature和Ref_count_obj@VCIntelCpuKernelTemperature对象地址:

然而打开x大师,运行自己的程序时,this+0x70和this+0x74都是0。
对this+0x70和this+0x74下硬件写入断点,断到如下代码处:

然后sub_7BD28CB0往下翻:

看到是调用了g_GetIntelCpuTemperatureFromDriver(我分析出来后重命名的)函数,然后判断返回值为0则把+0x70、0x74清零了,跟入GetIntelCpuTemperatureFromDriver函数:

调用了g_GetCpuTemperatureFromDriver函数,跟入:

调用了DeviceIoControl,发现x大师开启时开启自己的程序时,GetLastError()返回6:句柄无效。
这里可以看出是和驱动通信了,且句柄无效了。于是定位加载、打开驱动的地方:

此处是打开驱动符号链接"\\\\.\\ComputerZ"的地方,打开的是ComputerZ_x64.sys驱动。经过IDA F5级别的动态调试发现成功CreateFileW后,会调用g_StopAndDeleteService函数给停止并删除驱动,然后又调用CreateFileW,造成打开失败。也就导致上面分析的DeviceIoControl失败,无效句柄。

至于具体为什么要调用g_StopAndDeleteService函数,由于时间原因,我没有分析。
于是自己的程序里屏蔽下StopAndDeleteService函数:Hook下g_StopAndDeleteService函数,入口处retn掉就行。

5. 有些电脑获得CPU温度失效的问题:
上面分析后,写成代码,然后删除无用的x大师文件,最后x大师的二进制文件只保留如下:

整个文件大小是12MB。
但测试时发现,有的电脑获取CPU温度会失败。原因是ComputerZ.set文件内容的如

下字段,若是上面的值,会失效。
改成

就好了。
我们来定位原因,CE搜索131185:

然后把搜到的数据地址在x32dbg中下硬件访问断点,对应到IDA中定位到如下代码处:

然后查找函数调用处:

接着sub_7B01F280函数往下翻:

看到上文的数字:268435456,若是这个数字则直接返回了,不走sub_7B01F280下面的Hook逻辑了:

所以把268435456改成131185就好了。
三、 代码实现:
我写了个程序,实现了上述逆向的过程,获取硬件信息概览,以及实时获取CPU、GPU使用率和温度。代码如下:
// LuDaShiTest.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
//
#include <stdio.h>
#include <stdlib.h>
#include <conio.h>
#include <windows.h>
#include <iostream>
#include <string.h>
using namespace std;
#include "utf82ascii.h"
#pragma warning(disable:4996)
//记住要以管理员权限运行,否则取不到CPU温度
DWORD g_JmpOverviewRetn = 0, g_JmpGpuUsageRetn = 0, g_JmpCpuUsageRetn = 0,
g_JmpCpuTemperatureRetn = 0;
const WCHAR* g_pszGpuUsage = L"GPU %s Usage:%d temperature:%d\r\n";
const char* g_pszCpuUsage = "CPU Usage:%d\r\n";
const char* g_pszCpuTemperature = "CPU Temperature:%d\r\n";
void call_back_func()
{
printf("call_back_func");
}
BOOL WriteToFile(char* pszData)
{
string strData = pszData;
//英文系统会有乱码
string strAscii = UTF_82ASCII(strData);
printf(strAscii.c_str());
FILE* fp = fopen("c:\\HostHardware.json" , "w");
if (fp)
{
fwrite(strAscii.c_str(), 1, strAscii.length(), fp);
fclose(fp);
}
return TRUE;
}
__declspec(naked) void HookOverviewStub()
{
__asm
{
pushfd
pushad
push esi
//call printf
call WriteToFile
add esp, 4 //naked必须手动平衡栈
popad
popfd
//HOOK前原始指令
mov edi, [ebp - 0x10C4]
jmp g_JmpOverviewRetn
}
}
__declspec(naked) void HookGpuUsageStub()
{
__asm
{
pushfd
pushad
//push ebx
//push g_pszCoreGpuUsage
//call printf
////call WriteToFile
//add esp, 8 //naked必须手动平衡栈
mov ebx, [ebp - 0x54] //GPU Name
cmp ebx, 0 //ebx有可能是0
jz __exit
push [ebp - 0xBC] //温度 -1表示温度不存在(核显不存在温度,X大师也是这样的)
push [ebp - 0xC0] //GPU Usage
push [ebx]
push g_pszGpuUsage
call wprintf
//call WriteToFile
add esp, 0x10 //naked必须手动平衡栈
__exit:
popad
popfd
//HOOK前原始指令
lea ecx, [ebp - 0xC4]
jmp g_JmpGpuUsageRetn
}
}
__declspec(naked) void HookCpuUsageStub()
{
__asm
{
pushfd
pushad
push eax
push g_pszCpuUsage
call printf
//call WriteToFile
add esp, 8 //naked必须手动平衡栈
popad
popfd
//HOOK前原始指令
mov esi, [ebp + 0xC]
test esi, esi
jmp g_JmpCpuUsageRetn
}
}
__declspec(naked) void HookCpuTemperatureStub()
{
__asm
{
pushfd
pushad
push edi
push g_pszCpuTemperature
call printf
//call WriteToFile
add esp, 8 //naked必须手动平衡栈
popad
popfd
//HOOK前原始指令
lock xadd[edx + 0Ch], eax
jmp g_JmpCpuTemperatureRetn
}
}
int main(void)
{
//typedef char(__stdcall*GetOverviewJson)(PVOID, __int64, int);
typedef char(__stdcall* GetOverviewJson)(__int64, int);
typedef char(__stdcall* GetOverviewJson1)(DWORD*,DWORD*);
typedef DWORD*(__cdecl *CreateHdwInfo)();
typedef int(*Init0)();
typedef char* (*Init1)(unsigned int a1);
DWORD oldflag=0,dwHook=0;
//0xe2080
HMODULE hComputerZ = LoadLibraryA("ComputerZ_HardwareDll.dll");
if (hComputerZ)
{
HMODULE hKernel32 = LoadLibraryA("Kernel32.dll");
//打开驱动符号链接成功后,会关闭删除服务,造成随后的打开、DeviceIoControl操作失败
//Patch g_StopAndDeleteService
dwHook = (ULONG)hComputerZ + 0x1657b0;
if (VirtualProtect((PVOID)dwHook, 5, PAGE_EXECUTE_READWRITE, &oldflag))
{
//改成retn
*(BYTE*)dwHook = 0xC3;
}
//--------------------------------------------------------------------
//Hook拿硬件Overview信息,Hook v13:
//v11 = sub_7AC14BB0(COERCE__INT64((double)v21[1067]));
//sub_7AC14840(v4, "memory_channel", v11);
//sub_7AC14840((int)v3, "overview", v4);
//v12 = sub_7AC14BB0(0x4042000000000000i64);
//sub_7AC14840((int)v3, "type_id", v12);
//v13 = (void*)sub_7AC15140(v3); // 这里返回的是overview的json地址
dwHook = (ULONG)hComputerZ + 0x9950c; //8B BD 3C EF FF FF mov edi,[ebp+var_10C4]
if (VirtualProtect((PVOID)dwHook, 6, PAGE_EXECUTE_READWRITE, &oldflag))
{
printf("VirtualProtect success,hook addr is:0x%x!\r\n", dwHook);
*(BYTE*)dwHook = 0xe9;
*(DWORD*)(dwHook + 1) = (DWORD)HookOverviewStub - dwHook - 5;
*(char*)(dwHook + 5) = 0x90; //nop
g_JmpOverviewRetn = dwHook + 6;
}
else
{
printf("memtype error %d\n", GetLastError());
}
//Hook拿各GPU使用率、温度:
//v59 = sub_7B6B7890(v1 + 4, SubStr); // 这里返回值是CPU Usage, arg2是设备号
//if (*(v78 + 164) != 1)
//{
// v60 = sub_7B6B8070(SubStr); // 温度
// v61 = sub_7B6B65C0(SubStr); // GPU风扇
// sub_7B6BAAC0(&v58);
//}
//v16 = sub_7B6B2FD0();
dwHook = (ULONG)hComputerZ + 0x26a1e5; //8D 8D 3C FF FF FF lea ecx,[ebp+var_C4]
if (VirtualProtect((PVOID)dwHook, 6, PAGE_EXECUTE_READWRITE, &oldflag))
{
printf("VirtualProtect success,hook addr is:0x%x!\r\n", dwHook);
*(BYTE*)dwHook = 0xe9;
*(DWORD*)(dwHook + 1) = (DWORD)HookGpuUsageStub - dwHook - 5;
*(char*)(dwHook + 5) = 0x90; //nop
g_JmpGpuUsageRetn = dwHook + 6;
}
else
{
printf("memtype error %d\n", GetLastError());
}
//
//Hook拿各CPU使用率:
//if (v38 > 100)
// v38 = 100;
//result = 0;
//if (v38 >= 0)
// result = v38;
//v3[1] = result; // 此处得到的CPU Usage 已验证是正确的
dwHook = (ULONG)hComputerZ + 0x1a50ce; //8B 75 0C mov esi,[ebp+arg_4]; 85 F6 test esi, esi
if (VirtualProtect((PVOID)dwHook, 5, PAGE_EXECUTE_READWRITE, &oldflag))
{
printf("VirtualProtect success,hook addr is:0x%x!\r\n", dwHook);
*(BYTE*)dwHook = 0xe9;
*(DWORD*)(dwHook + 1) = (DWORD)HookCpuUsageStub - dwHook - 5;
g_JmpCpuUsageRetn = dwHook + 5;
}
else
{
printf("memtype error %d\n", GetLastError());
}
//Hook拿各CPU温度:
//LOBYTE(v186) = 10;
//v143 = (v160 - 8);
//v174[9] = v137; // 这里写的CPU温度
dwHook = (ULONG)hComputerZ + 0x311093; //F0 0F C1 42 0C lock xadd [edx+0Ch], eax
if (VirtualProtect((PVOID)dwHook, 5, PAGE_EXECUTE_READWRITE, &oldflag))
{
printf("VirtualProtect success,hook addr is:0x%x!\r\n", dwHook);
*(BYTE*)dwHook = 0xe9;
*(DWORD*)(dwHook + 1) = (DWORD)HookCpuTemperatureStub - dwHook - 5;
g_JmpCpuTemperatureRetn = dwHook + 5;
}
else
{
printf("memtype error %d\n", GetLastError());
}
CreateHdwInfo createHdwInfo = (CreateHdwInfo)((ULONG)hComputerZ + 0x9bcb0);
ULONG_PTR ulRetCreateHdwInfo = (ULONG_PTR)createHdwInfo();
/*Init0 init0 = (Init0)((ULONG)hComputerZ + 0x2862f0);
int nTest=init0();
Init0 init2 = (Init0)((ULONG)hComputerZ + 0xe0a60);
init2();
Init0 init3 = (Init0)((ULONG)hComputerZ + 0x22def0);
init3();
Init0 init4 = (Init0)((ULONG)hComputerZ + 0x3f8b70);
init4();*/
try
{
char dwTmp[416] = {0};
DWORD *dwThis = (DWORD*)&dwTmp;
dwThis[2] = ulRetCreateHdwInfo;
dwThis[3] = (ULONG)hComputerZ + 0x9bcb0; //CreateHdwInfo
dwThis[4] = (ULONG)hComputerZ + 0x9c2e0; //ReleaseHdwInfo
dwThis[5] = (ULONG)hComputerZ;
dwThis[6] = (DWORD)&call_back_func;
dwThis[6] = (DWORD)&dwThis[6];
//hardware_info_main
//(*(void(__thiscall**)(int, DWORD*))(*(DWORD*)ulRetCreateHdwInfo + 4))(ulRetCreateHdwInfo, dwThis);
_asm
{
pushfd
pushad
mov ecx, ulRetCreateHdwInfo
mov eax, [ecx]
push dwThis
call dword ptr[eax + 4]
popad
popfd
}
// 调用ComputerZ_HardwareDll.dll扫描函数
//(*(int (**)(void))(*(DWORD*)ulRetCreateHdwInfo + 28))(&dwThis);
//(*(int (__thiscall**)(DWORD*))(*(DWORD*)ulRetCreateHdwInfo + 28))(&dwThis);
_asm
{
pushfd
pushad
mov ecx, ulRetCreateHdwInfo
mov eax, [ecx]
call dword ptr[eax + 0x1C]
popad
popfd
}
}
catch (...)
{
}
printf("请等待开始获取...\r\n");
//system("pause");
while (true)
{
Sleep(1000);
}
}
else
printf("找不到ComputerZ_HardwareDll.dll!");
}
实现的效果如下图:

好了,分析和利用暂时到此了。附件会里有代码和可利用二进制文件,逆向的idb文件太大,就不上传了。
[培训]内核驱动高级班,冲击BAT一流互联网大厂工作,每周日13:00-18:00直播授课
最后于 1天前
被yirucandy编辑
,原因: