首页
社区
课程
招聘
[原创]火绒剑驱动逆向
发表于: 6天前 871

[原创]火绒剑驱动逆向

6天前
871

原本想在win10环境中通过Ring3程序调用火绒剑驱动实现关闭进程的实验,但以失败告终,火绒剑在驱动的安全保护上确实下了功夫,对驱动打开时的进程进行了验证。虽未成功实现调用驱动关闭进程的功能,但在逆向中还是学到了很多知识,总结以下,供大家批评指正。

1、ZwTerminateProcess

在Ring0层关闭进程的API是ZwTerminateProcess,逆向就从这里开始。查看官方文档,调用ZwTerminateProcess函数需要两个参数,一个是进程句柄ProcessHandle,另一个是退出状态码ExitStatus。图片描述IDA中打开驱动sysdiag_win10.sys,并在导入表Imports中查找ZwTerminateProcess。 图片描述使用交叉引用查看调用该api的情况。 图片描述

2、定位用于终止进程的函数

定位到TerminateProcessById(sub_140029870)函数,功能是通过进程ID来终止进程。 图片描述继续交叉引用查看,直到找到处理DeviceIoControl的函数。 图片描述定位sub_14000C460->DispatchDeviceControl#3、驱动的派遣函数DispatchDeviceControl(sub_14000D0C0)函数是一个Windows内核驱动程序的派遣函数(Dispatch Routine),用于处理来自不同设备对象的IRP(I/O请求包)。其主要目的是根据传入的设备对象和IRP的主功能代码(MajorFunction)进行路由,执行相应的操作,如进程监控、设备控制、数据读写等,并最终完成IRP请求。Ring0与Ring3层的交互方式,一般用DeviceIoControl来控制,所以该函数就是处理驱动IO关键函数。 图片描述通过sub_14000C460可以逆向出调用终止进程功能的IO控制码为0x2200B8,驱动名称为“\.\HR::ActMon” 图片描述编写如下程序测试调用驱动,结果发现打开驱动失败。

BOOL TerminateProcessByPid(DWORD targetPid) {
    wchar_t logMsg[128];
    _snwprintf_s(logMsg, _countof(logMsg), _TRUNCATE, L"Attempting to terminate process with PID: %lu", targetPid);
    WriteLog(logMsg);

    // Correct device symbolic link (from reverse engineering)
    LPCWSTR devicePath = L"\\\\.\\HR::ActMon";
    HANDLE hDevice = CreateFileW(
        devicePath,
        GENERIC_READ | GENERIC_WRITE,
        FILE_SHARE_READ | FILE_SHARE_WRITE,
        NULL,
        OPEN_EXISTING,
        0,
        NULL);

    if (hDevice == INVALID_HANDLE_VALUE) {
        DWORD err = GetLastError();
        _snwprintf_s(logMsg, _countof(logMsg), _TRUNCATE, L"Failed to open device, error code: %lu", err);
        WriteLog(logMsg);
        return FALSE;
    }
    
    WriteLog(L"Device opened successfully");

    // Prepare buffer (16 bytes)
    BYTE buffer[16] = {0};
    DWORD64 pid64 = (DWORD64)targetPid;
    memcpy(buffer, &pid64, sizeof(pid64));  // First 8 bytes for PID

    DWORD bytesReturned;
    BOOL result = DeviceIoControl(
        hDevice,
        SYSDIAG_IOCTL_TERMINATE_PROCESS,
        buffer, sizeof(buffer),
        buffer, sizeof(buffer),
        &bytesReturned,
        NULL);

    if (!result) {
        DWORD err = GetLastError();
        _snwprintf_s(logMsg, _countof(logMsg), _TRUNCATE, L"DeviceIoControl failed, error code: %lu", err);
        WriteLog(logMsg);
        CloseHandle(hDevice);
        return FALSE;
    }
    
    WriteLog(L"DeviceIoControl called successfully");

    // Read return value (offset 8)
    NTSTATUS status = *(DWORD*)(buffer + 8);
    
    _snwprintf_s(logMsg, _countof(logMsg), _TRUNCATE, L"DeviceIoControl returned NTSTATUS: 0x%08lX", status);
    WriteLog(logMsg);
    
    CloseHandle(hDevice);
    WriteLog(L"Device handle closed");
    
    BOOL finalResult = result && (status == 0);
    if (finalResult) {
        WriteLog(L"Termination operation successful (result TRUE and status 0)");
    } else {
        WriteLog(L"Termination operation failed");
    }
    
    return finalResult;
}

失败的原因出在用CreateFileW打开“\.\HR::ActMon”时,驱动程序内部进行了判断。 图片描述程序通过函数FindAndReferenceNodeByKey(sub_1400297A0)查询数据,返回的结构体V10+1360的位置必须为0,否则就会返回错误码STATUS_ACCESS_DENIED(0xC0000022),所以接下来的逆向思路就是如何找到修改该地址存储数据的代码。

4、追踪结构体

驱动中结构体偏移1360(0x550)何时为0,可以通过IDA搜索立即数(Immediate value)的方式来定位。 图片描述虽然调用该偏移的位置很多,但大多指令是读数据,只有两条数据是向该偏移地址写数据,其中一条为写入eax的值,另外一条写入固定值0FFFFFFFFh。我们将重点放在第一条RVA:0x13F38,观察eax何时为0。

下图是RVA:0x13F38所在函数InitializeProcessPathInfo(sub_140013D00)引用图表,第一次分析代码头绪是比较乱的。 图片描述

此时换用动态调试,用windbg调试并下端点,很快就在关键位置上中断下来。观察发现,大部分情况eax的值是0xffffffff,只有打开HRSword.exe时,eax的值为0,调用栈分析如下。 图片描述

5、监控进程的回调函数

找到系统调用该驱动RVA为0xbae00x10350,用WPeChat插件辅助分析。

发现MyCreateProcessNotifyEx(sub_14000BAE0)是驱动注册的监控进程的回调函数。 图片描述ProcessLifecycleEventHandler(sub_140010300)函数负责具体处理回调,它是一个进程/线程生命周期事件处理器,主要用于处理与进程创建或终止相关的事件。根据IsCreationEvent参数的值,函数会执行不同的逻辑分支:当IsCreationEvent为真时处理进程创建事件,为假时处理进程终止事件。 图片描述继续分析标志位设置的条件,发现RVA:0x13F38上面的OpenAndValidatePEFile(sub_1400368B0)是关键验证函数。 图片描述OpenAndValidatePEFile函数通过检查文件扩展名是否为“.exe”(不区分大小写)来设置特定标志,然后调用系统API打开文件,最后调用AnalyzeAndValidatePEFile(sub_140036260)进行PE文件的分析与验证。 图片描述到此,梳理一下逻辑。只要有进程创建,驱动会打开该进程对应的exe,然后进行分析验证,如果条件符合,就会给结构体偏移1360处赋值0。

6、验证逻辑分析

主要逻辑判断分析如下:

(1)文件大小判断

获取文件大小,并检查其是否在0x40 到64MB(0x4000000 字节)范围内。 图片描述

(2)PE文件头验证

检查文件内容是否符合PE文件格式(如DOS头签名"MZ"、PE头签名"PE"),并验证特定字段(如可选头大小)。 图片描述其中一段是验证否为VB程序,但只影响文件标志,不对主判断有影响,无关紧要. 图片描述

(3) 节表遍历与找到最后一个节

计算每个节的大小+地址偏移,最终结果肯定是最后一个节最大(偏移地址最大)。 图片描述程序设计似乎有点繁复,计算每个节的大小+地址偏移,最终结果肯定是最后一个节最大(偏移地址最大)。 图片描述

(4)数据解密与验证

在最后一个节的倒数132字节出处进行异或XOR解密操作,使用硬编码密钥0x900D900D进行异或解密。 图片描述然后验证解密后的数据是否匹配特定魔数("HRBR"和"HROH")。 图片描述

(5)哈希计算与匹配

如果解密数据验证通过,则使用SHA1算法计算文件(文件头到最后一个节的的倒数132字节)的哈希值,并与全局表中的哈希值进行比对。 图片描述 图片描述函数ExtractPaddedData(sub_140007350)实现了校验和哈希算法,通过全局二维私钥数组CurrentHashEntry和倒数132字节解密后的字节计算后得出用于比较的哈希。 图片描述最后,如果匹配成功,就会将匹配的下标赋值给结构体偏移1360。所以要让结构体偏移1360为0,就必须第一个HASH数组就匹配成功。

这样看下来,想破解这个算法很难,由于哈希算法是单向的,真要凑出一个能通过验证的值需要大量计算,所以果断跳车,希望本文能给大家带来启发和帮助。


[培训]Windows内核深度攻防:从Hook技术到Rootkit实战!

收藏
免费 2
支持
分享
最新回复 (0)
游客
登录 | 注册 方可回帖
返回