一.引子

鲁迅曾说过:“新时代的逆向分析往往只需要学会使用最简单的ai”。(bushi)

最近浅浅学习了一下windows下的逆向分析。所以随便选了一个内核工具拿来逆向练练手,这边我们选择逆向的是SKT64内核工具 https://github.com/PspExitThread/SKT64 。这个软件是一个大佬自制的。虽然发在GitHub上,但是其实是闭源的。

https://github.com/PspExitThread/SKT64/blob/main/Screenshot/1.png

这个工具里面有很多的功能,我们就随便选一个来逆向,比如我选择的是里面的强制结束进程(KernelForceTerminateProcess)的功能。

二.背景知识

我们先来回顾一下内核态是如何终止进程的:

CPU 的权限分为不同的环(Ring):

  • Ring3:用户态程序运行的环境,受限最多。
  • Ring0:内核态环境,驱动程序(.sys 文件)就在这里执行,拥有最高权限。

大多数普通进程都是运行在 Ring3 下的,所以当你在任务管理器(taskmgr.exe)中尝试结束某些受保护的进程(比如 Windows Defender、360、火绒),会发现根本没权限。

但对于运行在 Ring0 的内核驱动来说,这些保护就形同虚设。

驱动可以直接调用 ZwTerminateProcess 等内核 API,从而强制结束目标进程。换句话说,那些在用户态层面上“顽固”的进程,在内核面前其实就是纸老虎。

三.逆向

1. 分析驱动和exe

现在回归正题,我们逆向的第一步是需要先提取他内部的驱动文件。

我们从github上下载了这个SKT64了之后,发现文件夹内并没有看到我们关心的.sys文件。说明驱动文件是在exe运行的时候动态释放的。

那么这里有一个非常简单(投机取巧)的方法来提取内部的.sys。只需要简单借助一下360拦截驱动加载的功能。

然后我们按照360拦截的位置就可以提取到驱动 .sys 文件了。

拿到驱动文件后我们在ida里面打开,看看能不能逆向出来。这边我们只关心“结束进程”用的zwterminateprocess的函数。所以我们打开imports导入表。

很遗憾的是这里没有找到我们想要的函数。原因我大概推测这个驱动程序是被加密或者加壳过的,人为隐藏了导入表,来使得逆向更艰难。

由于本人学艺不精,不会做脱壳处理。不过俗话说 “上帝关上了一扇门,必然会为你打开另一扇窗”。我们可以走另一条路——直接逆向程序exe本体。我们把下载到的SKT64-Release.exe放在ida里面打开。

我们先主要大概看一遍里面的所有函数。

这里运气比较好,程序刚好导出了这些关键函数,所以直接看到了原名。关于终止进程的函数一共有三个:KernelTerminateProcess,KernelForceTerminateProcess和KernelForceTerminateProcess2。但是这样的话我们还不能确定程序里面哪个按键对应的是哪个函数,所以我们还需要确定一下具体是哪个函数。

这里使用的方法也非常简单,我们只需要确定三个函数分别的位置然后用调试器对他们设置断点,查看运行的时候到达了哪个断点,就可以确定了。

先来计算一下这三个函数的位置

Ida里面的地址默认偏移是0x140000000,所以每个函数都需要去除这个偏移才能得到实际地址。

分别是:0x69E0, 0x6A80, 0x6940

2.动态调试

得到地址之后,我们在x64dbg里面添加断点:

先打开上方的“内存布局”查看程序具体加载在了什么地方,然后找到我们要调试的skt64-release.exe的位置0x00007FF647D80000,这个基址加上函数的位置就是我们需要打断点的位置。

所以需要打断点的位置是:

0x00007FF647D80000 + 0x69E0 = 0x00007FF647D869E0

0x00007FF647D80000 + 0x6A80 = 0x00007FF647D86A80

0x00007FF647D80000 + 0x6940 = 0x00007FF647D86940

在x64dbg里面按ctrl+G可以快捷跳转地址,我们在这三个地方打断点:

然后我们把系统自带的断点全部取消掉,减少一些干扰。

打完断点之后我们按快捷键ctrl+f2重新运行。

等待一会看到ui弹出。

然后我们随便选择一个进程结束。

成功命中了调试断点:

打开x64dbg的窗口:

看到这里触发的是KernelForceTerminateProcess2.

命令行里面出现的是FFFFA8061CF1E080,很明显并不是一个pid或者程序名。这个格式其实是程序的EPROCESS。

3. 进一步分析

这里的EPROCESS怎么来的呢?其实我们只需要把程序的反编译代码发给ai工具,让他帮忙查找一下:

……

可以看到ai直接帮我们分析了这个代码。EPROCESS其实是调用函数EnumProcess()填充进程表的时候获得的。

然后我们大致看一下EnumProcess和KernelForceTerminateProcess2的具体实现,比如ioctl码,加载驱动的符号链接这些:

四.编写代码

1. 代码

现在我们就集齐了调用驱动所需要的材料,这样就可以自己写一个代码来调用这个驱动了。

这里我也不废话,直接上代码:
 

// skt64_terminator.cpp
// 编译(x64, VS): cl /EHsc /std:c++17 skt64_terminator.cpp
// 运行: 需要管理员权限,并将 SKT64.sys 放在同目录下

#include <windows.h>
#include <iostream>
#include <string>
#include <vector>
#include <cstdint>
#include <iomanip> // 用于格式化输出

// 全局设备句柄
static HANDLE g_hDevice = INVALID_HANDLE_VALUE;

// 进程信息结构体
struct ProcessInfo
{
    DWORD pid;
    uint64_t eprocess;
    std::string name;
};

void Log(const char* fmt, ...)
{
    va_list ap;
    va_start(ap, fmt);
    vprintf(fmt, ap);
    va_end(ap);
}

std::wstring GetExeDirectory()
{
    wchar_t buf[MAX_PATH];
    DWORD len = GetModuleFileNameW(nullptr, buf, MAX_PATH);
    if (len == 0 || len == MAX_PATH) return L".";

    std::wstring path(buf, buf + len);
    size_t pos = path.find_last_of(L"\\/");

    if (pos == std::wstring::npos) return L".";
    return path.substr(0, pos);
}

bool LoadDriverFromPath(const std::wstring& driverPath, const std::wstring& serviceName)
{
    if (g_hDevice != INVALID_HANDLE_VALUE)
    {
        Log("[*] Driver already opened by this process.\n");
        return true;
    }

    SC_HANDLE hSCM = OpenSCManagerW(nullptr, nullptr, SC_MANAGER_ALL_ACCESS);
    if (!hSCM)
    {
        Log("[ERR] OpenSCManagerW failed (is this running as admin?): %u\n", GetLastError());
        return false;
    }

    SC_HANDLE hService = CreateServiceW(
        hSCM,
        serviceName.c_str(),
        serviceName.c_str(),
        SERVICE_ALL_ACCESS,
        SERVICE_KERNEL_DRIVER,
        SERVICE_DEMAND_START,
        SERVICE_ERROR_IGNORE,
        driverPath.c_str(),
        nullptr, nullptr, nullptr, nullptr, nullptr);

    if (!hService)
    {
        DWORD err = GetLastError();
        if (err == ERROR_SERVICE_EXISTS || err == ERROR_ALREADY_EXISTS)
        {
            hService = OpenServiceW(hSCM, serviceName.c_str(), SERVICE_START | SERVICE_QUERY_STATUS | DELETE | SERVICE_STOP);
            if (!hService)
            {
                Log("[ERR] OpenServiceW(existing) failed: %u\n", GetLastError());
                CloseServiceHandle(hSCM);
                return false;
            }
            else
            {
                Log("[*] Service already exists.\n");
            }
        }
        else
        {
            Log("[ERR] CreateServiceW failed: %u\n", err);
            CloseServiceHandle(hSCM);
            return false;
        }
    }

    if (!StartServiceW(hService, 0, nullptr))
    {
        DWORD err = GetLastError();
        if (err == ERROR_SERVICE_ALREADY_RUNNING || err == ERROR_ALREADY_EXISTS)
        {
            Log("[*] Service already running.\n");
        }
        else
        {
            Log("[ERR] StartServiceW failed: %u\n", err);
            CloseServiceHandle(hService);
            CloseServiceHandle(hSCM);
            return false;
        }
    }

    else
    {
        Log("[+] Driver service started.\n");
    }

    CloseServiceHandle(hService);
    CloseServiceHandle(hSCM);

    g_hDevice = CreateFileA(R"(\\.\ArkDrv64)",
        GENERIC_READ | GENERIC_WRITE,
        0, nullptr,
        OPEN_EXISTING,
        FILE_ATTRIBUTE_NORMAL,
        nullptr);

    if (g_hDevice == INVALID_HANDLE_VALUE)
    {
        Log("[ERR] CreateFileA(\\\\.\\ArkDrv64) failed: %u\n", GetLastError());
        return false;
    }

    Log("[+] Opened device handle \\\\.\\ArkDrv64.\n");
    return true;
}

// 新增:通过驱动终止进程
bool TerminateProcessViaDriver(uint64_t eprocessAddress)
{
    if (g_hDevice == INVALID_HANDLE_VALUE)
    {
        Log("[ERR] Device not opened.\n");
        return false;
    }

    // 该实现严格遵循 KernelForceTerminateProcess2 的反编译代码
    uint64_t inBuffer = eprocessAddress;
    DWORD bytesReturned = 0;

    BOOL ok = DeviceIoControl(
        g_hDevice,
        0x222560u,           // 终止进程的 IOCTL
        &inBuffer,           // 输入:指向 EPROCESS 地址的指针
        sizeof(inBuffer),    // 输入大小:8 字节
        nullptr,             // 无输出缓冲
        0,                   // 输出缓冲大小为 0
        &bytesReturned,
        nullptr
    );

    if (ok)
    {
        Log("[+] Terminate command for EPROCESS 0x%llx sent successfully.\n", eprocessAddress);
    }
    else
    {
        Log("[ERR] Failed to send terminate command. Error: %u\n", GetLastError());
    }

    return ok;
}


// 修改:枚举进程并返回一个列表
std::vector<ProcessInfo> EnumProcessViaDriver()
{
    std::vector<ProcessInfo> processes;

    if (g_hDevice == INVALID_HANDLE_VALUE)
    {
        Log("[ERR] Device not opened.\n");
        return processes;
    }

    const uint64_t requestedSize = 2720000ULL;
    const SIZE_T outBufferAllocSize = 0x298100;
    const DWORD entryStride = 2720;

    LPVOID lpMem = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, outBufferAllocSize);
    if (!lpMem)
    {
        Log("[ERR] HeapAlloc failed\n");
        return processes;
    }

    uint8_t inBuf[16] = { 0 };
    *(uint64_t*)(inBuf + 0) = requestedSize;
    *(uint64_t*)(inBuf + 8) = (uint64_t)lpMem;

    DWORD outCount = 0;
    DWORD bytesReturned = 0;

    BOOL ok = DeviceIoControl(
        g_hDevice,
        0x2220D4u,
        inBuf, sizeof(inBuf),
        &outCount, sizeof(outCount),
        &bytesReturned,
        nullptr
    );

    if (!ok)
    {
        Log("[ERR] DeviceIoControl for enumeration failed: %u\n", GetLastError());
        HeapFree(GetProcessHeap(), 0, lpMem);
        return processes;
    }

    Log("[+] Process enumeration successful. Found %u entries.\n", outCount);

    char* base = reinterpret_cast<char*>(lpMem);
    for (DWORD i = 0; i < outCount; ++i)
    {
        char* entry = base + (SIZE_T)i * entryStride;

        uint64_t eproc = 0;
        DWORD pid = 0;
        const char* namePtr = entry + 380; // offset +380: name

        memcpy(&eproc, entry + 32, sizeof(eproc)); // offset +32 : EPROCESS
        memcpy(&pid, entry + 64, sizeof(pid));     // offset +64 : PID

        if (pid == 0) continue; // 跳过无效或系统占位条目

        char nameBuf[512] = { 0 };
        // 安全地复制进程名
        try {
            strncpy_s(nameBuf, namePtr, sizeof(nameBuf) - 1);
        }
        catch (...) {
            strcpy_s(nameBuf, "<error reading name>");
        }

        processes.push_back({ pid, eproc, std::string(nameBuf) });
    }

    HeapFree(GetProcessHeap(), 0, lpMem);
    return processes;
}

void Cleanup()
{
    if (g_hDevice != INVALID_HANDLE_VALUE)
    {
        CloseHandle(g_hDevice);
        g_hDevice = INVALID_HANDLE_VALUE;
        Log("[+] Device handle closed.\n");
    }
}

int wmain(int argc, wchar_t* argv[])
{
    std::wstring exeDir = GetExeDirectory();
    std::wstring driverPath = exeDir + L"\\" + L"SKT64.sys";
    std::wstring serviceName = L"SKT64-Terminator-Service";

    Log("[*] Attempting to load driver from: %ls\n", driverPath.c_str());

    if (!LoadDriverFromPath(driverPath, serviceName))
    {
        Log("[ERR] Failed to load driver. Exiting.\n");
        system("pause");
        return 1;
    }

    Log("\n[*] Enumerating processes via driver...\n");
    auto processes = EnumProcessViaDriver();

    if (processes.empty())
    {
        Log("[ERR] No processes were enumerated. Cannot proceed.\n");
        Cleanup();
        system("pause");
        return 1;
    }

    // 打印进程列表
    std::cout << "---------------------------------------------------------------------\n";
    std::cout << std::left << std::setw(10) << "PID"
        << std::setw(20) << "EPROCESS"
        << "Name" << std::endl;
    std::cout << "---------------------------------------------------------------------\n";
    for (const auto& p : processes)
    {
        std::cout << std::left << std::setw(10) << p.pid
            << "0x" << std::hex << std::setw(18) << p.eprocess << std::dec
            << p.name << std::endl;
    }
    std::cout << "---------------------------------------------------------------------\n\n";

    // 用户交互
    DWORD targetPid = 0;
    std::cout << "Enter the PID of the process to terminate (0 to exit): ";
    std::cin >> targetPid;

    if (targetPid == 0)
    {
        Log("[*] User chose to exit.\n");
    }
    else
    {
        uint64_t targetEprocess = 0;
        bool found = false;
        for (const auto& p : processes)
        {
            if (p.pid == targetPid)
            {
                targetEprocess = p.eprocess;
                found = true;
                Log("[*] Found process %s (PID: %u) with EPROCESS 0x%llx.\n", p.name.c_str(), p.pid, p.eprocess);
                break;
            }
        }

        if (found)
        {
            Log("[!] Attempting to terminate process with PID %u...\n", targetPid);
            TerminateProcessViaDriver(targetEprocess);
        }
        else
        {
            Log("[ERR] Process with PID %u was not found in the list.\n", targetPid);
        }
    }

    Log("\n[*] Program finished. Press any key to exit.\n");
    system("pause");
    return 0;
}

代码的作用就是手动加载SKT64.sys,然后打印所有的进程,接下来用户可以随意选择一个进程从内核关闭。

2. 测试

这里我们先拿windows defender的进程MsMpEng.exe来开刀:

输入MsMpEng.exe的pid:

可以看到成功结束了它的进程。非常完美。

Logo

开源鸿蒙跨平台开发社区汇聚开发者与厂商,共建“一次开发,多端部署”的开源生态,致力于降低跨端开发门槛,推动万物智联创新。

更多推荐