偶然从万能的某宝得到了一个HWID欺骗样本,看大小20KB,1(咦)5!(无壳),直接拖入IDA分析一下,看看如何实现的,仅为练习ida使用方法进行分析。
看一下驱动入口函数,一共四个调用,先看第一个sub_140001178();
如图所示,一共12个8字节地址赋值,都调用了一个sub_14000112C()函数,怎么看的这么眼熟?很明显,是为了规避对导入表(IAT)可能的检测点自己手动导入相关API地址,然后call(相关开源项目有个LAZY_IMPORT挺好的,使用十分方便),进sub_14000112C()看一下;
哦吼,经典组合,MmGetSystemRoutineAddress 例程返回指向 SystemRoutineName 指定的函数的指针。不过好像只对ssdt内的函数有效,SSSDT内函数需要附加到UI进程才行,否则蓝屏,好像跟会话空间有关系。
第一个函数的作用就是初始化API导入表了,来看第二个函数sub_1400016DC();
IDA直接给出伪代码,好长,慢慢分析。
第一段伪代码,定义了一个符号链接,看来是硬盘相关驱动,自己构造call了一个api,回顾上边发现是ObReferenceObjectByName,可以根据驱动设备名得到PDRIVER_OBJECT,进而得到该驱动的PDEVICE_OBJECT指针。v20是128位,还有个没见过的LODWORD,经查询,
LOWORD为取低4字节(16位)值
LOWORD(0XAABBCCDDEEFF)返回的结果就是0xEEFF,也正好是一个word值.
初步判定,是IDA错误认定了字符串变量,应该是定义了一个char[16]数组,看一下ObReferenceObjectByName用法
NTSTATUS
ObReferenceObjectByName (
__in PUNICODE_STRING ObjectName,
__in ULONG Attributes,
__in_opt PACCESS_STATE AccessState,
__in_opt ACCESS_MASK DesiredAccess,
__in POBJECT_TYPE ObjectType,
__in KPROCESSOR_MODE AccessMode,
__inout_opt PVOID ParseContext,
__out PVOID *Object
);
//函数声明
PDRIVER_OBJECT driverObject;
UNICODE_STRING DeviceName;
RtlInitUnicodeString(&DeviceName, L"\\Driver\\Disk");
status = ObReferenceObjectByName(&DeviceName,//设备名
OBJ_CASE_INSENSITIVE,
NULL, // access state
FILE_ALL_ACCESS, // 权限
*IoDriverObjectType,
KernelMode,//内核模式
NULL, // parse context
&driverObject);//获取的驱动对象指针
获取DISK.SYS的驱动对象后,调用了_InterlockedExchange64,赋了一个值,函数原型如下
LONG64 InterlockedExchange64(
[in, out] LONG64 volatile *Target,//指向要交换的值的指针。 函数将此变量设置为 Value,并返回其先前值。
[in] LONG64 Value//要与 Target 指向的值交换的值。
);//函数声明,函数返回 Target 参数的初始值。
赋值的指针是sub_14000109C函数,跟进去看一下内容;
__int64 __fastcall sub_14000109C(__int64 a1, __int64 a2)
{
__int64 v4; // rcx
int v5; // eax
__int64 (__fastcall *v6)(); // r8
v4 = *(_QWORD *)(a2 + 184);
v5 = *(_DWORD *)(v4 + 24);
if ( v5 == 315436 )
{
v6 = sub_140001000;
goto LABEL_8;
}
if ( v5 == 508040 )
{
v6 = sub_140001650;
goto LABEL_8;
}
if ( v5 == 2954240 && !**(_DWORD **)(a2 + 24) )
{
v6 = sub_140001CEC;
LABEL_8:
sub_140001DA8(v4, a2, v6);
}
return qword_140004FF8(a1, a2);
}
结合上文,V5=v24+224,此时v3=&sub_14000109C,而qword_140004FF8内存储的是原V3值,即V24+224,在sub_14000109C()中最后调用了V24+224,有点混乱,先往下看,看到了经典特征码扫描函数,还是带模式串的,看来不是暴力比对,就不进入分析了,qword_140002710内存储的是特征码0x48 0x89,明显是一个函数,这个函数应该是disk中与查询硬件id相关的函数,众所周知,特征码搜索函数一般为如下写法(直接给出自己用的一份源码,其实大家好像都一样)
DWORD64 find_pattern_mask(DWORD64 addr, DWORD32 size, const char* pattern, const char* mask)
{
size -= (DWORD32)strlen(mask);
for (DWORD32 i = 0; i < size; i++)
{
if (pattern_check((const char*)addr + i, pattern, mask))
return addr + i;
}
return 0;
}
DWORD64 find_pattern(DWORD64 addr, const char* pattern, const char* mask)
{
PIMAGE_DOS_HEADER dos = (PIMAGE_DOS_HEADER)addr;
if (dos->e_magic != IMAGE_DOS_SIGNATURE) return 0;
PIMAGE_NT_HEADERS64 nt = (PIMAGE_NT_HEADERS64)(addr + dos->e_lfanew);
if (nt->Signature != IMAGE_NT_SIGNATURE) return 0;
PIMAGE_SECTION_HEADER section = IMAGE_FIRST_SECTION(nt);
for (unsigned short i = 0; i < nt->FileHeader.NumberOfSections; i++)
{
PIMAGE_SECTION_HEADER p = §ion[i];
if (strstr((const char*)p->Name, ".text") || 'EGAP' == *reinterpret_cast<int*>(p->Name))
{
DWORD64 res = find_pattern_mask(addr + p->VirtualAddress, p->Misc.VirtualSize, pattern, mask);
if (res) return res;
}
}
return 0;
}
参数 *(_QWORD *)(v6 + 24)确定为搜索开始的地址,再向上一行,V6=V24,v24是一个指针地址,结构体指针,还不能确定是哪个结构体,因为ObReferenceObjectByName调用的参数明显不对,IDA中还出现了UNK字段,F5也不是那么好用啊,先向下看qword_140007050对应的函数应该是IoEnumerateDeviceObjectList,调用例子
status = IoEnumerateDeviceObjectList(driver_object, device_object_list, sizeof(device_object_list), &number_of_device_objects);
if (!NT_SUCCESS(status))
{
ObDereferenceObject(driver_object);
return false;
}
V24结构体应该就是PDRIVER_OBJECT了,定义如下
typedef struct _DRIVER_OBJECT {
CSHORT Type;
CSHORT Size;
PDEVICE_OBJECT DeviceObject;
ULONG Flags;
PVOID DriverStart;
ULONG DriverSize;
PVOID DriverSection;
PDRIVER_EXTENSION DriverExtension;
PUNICODE_STRING HardwareDatabase;
PFAST_IO_DISPATCH FastIoDispatch;
PDRIVER_INITIALIZE DriverInit;
PDRIVER_STARTIO DriverStartIo;
PDRIVER_UNLOAD DriverUnload;
PDRIVER_DISPATCH MajorFunction[IRP_MJ_MAXIMUM_FUNCTION + 1];
} DRIVER_OBJECT;
typedef struct _DRIVER_OBJECT *PDRIVER_OBJECT;
2+2+8+4+8=24,V24+24对应的结构体成员即为DriverStart,是disk驱动对象的地址。继续向下看,qword_1400070A0对应的函数为ExAllocatePoolWithTag,分配了一块内存,结合IoEnumerateDeviceObjectList的参数,分配的内存是给OBJECTLIST[]准备的,到此,大致代码还原如下
PDEVICE_OBJECT Device_Object_List[100]{ 0 };
RtlZeroMemory(Device_Object_List, sizeof(Device_Object_List));
ULONG objects_number = 0;
status = IoEnumerateDeviceObjectList(Driver_Object, Device_Object_List, sizeof(Device_Object_List), &objects_number);
接下来的FOR循环就是遍历OBJ链,
if ( v9 )
{
if ( (int)IoEnumerateDeviceObjectList(v24, v9, v8, &v23) >= 0 )
{
if ( v23 )
{
for ( i = 0i64; (unsigned int)i < v23; i = (unsigned int)(i + 1) )
{
v12 = *(_QWORD *)(v10 + 8 * i);
v13 =IoGetAttachedDeviceReference(v12);
if ( v13 )
{
v22 = 0i64;
v21 = 0i64;
KeInitializeEvent(&v21, 0i64, 0i64);
LOBYTE(v19) = 0;
v14 = IoBuildDeviceIoControlRequest(459072i64, v13, 0i64, 0i64, 0i64, 0, v19, &v21, 0i64);
if ( v14 && (unsigned int)IofCallDriver(v13, v14) == 259 )
KeWaitForSingleObject(&v21, 0i64, 0i64, 0i64, 0i64);
ObfDereferenceObject(v13);
}
v15 = *(_QWORD *)(v12 + 64);
if ( v15 )
{
v16 = (char *)&unk_140005028;
v17 = (_BYTE *)(*(_QWORD *)(v15 + 520) + *(unsigned int *)(*(_QWORD *)(v15 + 520) + 24i64));
do
{
v18 = *v16++;
*v17++ = v18;
}
while ( v18 );
v7(v15, 0i64, v16);
}
ObfDereferenceObject(v12);
}
}
}
qword_140007080(v10, 0i64);
}
IoBuildDeviceIoControlRequest 例程为同步处理的设备控制请求分配和设置 IRP。IofCallDriver将 IRP 发送到与指定设备对象关联的驱动程序。v7为我们要call的用特征码从disk驱动中搜索到的函数。经过查找资料,DiskEnableDisableFailurePrediction是符合我们要求的,作用为关闭硬盘的S.M.A.R.T功能。
分析到这里可以确定这个驱动为开源的写法,通过关闭smart,hookirp函数达到hwid欺骗的目的,再查询过程中直接找到了个项目的开源地址,哈哈,不过我是为了熟悉ida操作来分析的,无所谓咯。某宝上真的是奸商拿垃圾来骗人哈哈哈。