重写 OpenProcess:绕过 Win32 API,直接进入 NT 内核(Win7 x86)

环境说明

  • Windows 7 x86

  • 不使用 OpenProcess / NtOpenProcess 等任何 3 环 API

  • 直接通过 sysenter 进入内核

一、背景:OpenProcess 的真实调用链

之前的文章说过,在 Win7 x86 上,一个普通的 OpenProcess 实际会经历如下路径:

cpp 复制代码
OpenProcess (Win32)
→ kernel32.dll
→ API-MS 转发层
→ kernelbase.dll
→ ntdll!NtOpenProcess
→ call [KUSER_SHARED_DATA.SystemCall]
→ ntdll!KiFastSystemCall
→ sysenter   ← Ring3 → Ring0

可以看到:

  • Win32 API 只是参数整理层

  • 真正进入内核的是 sysenter

内核只关心:

  • syscall 号(SSDT index)

  • 用户态栈布局

  • 参数顺序

本文的目标是:

完全绕过 Win32 / ntdll,在用户态手动构造 syscall 所需的一切条件,直接进入内核调用 NtOpenProcess。

二、仿写kernelbase.dll中的OpenProcess函数

kernelbase.dll 是 Win32 API 的主要实现层之一,它负责完成用户态参数整理,并把请求转换成 NT 层可接受的调用形式。

下面是kernelbase.dll中的OpenProcess函数的反汇编:

cpp 复制代码
.text:0DCEB359 ; Exported entry 466. OpenProcess
.text:0DCEB359
.text:0DCEB359 ; =============== S U B R O U T I N E =======================================
.text:0DCEB359
.text:0DCEB359 ; Attributes: bp-based frame
.text:0DCEB359
.text:0DCEB359 ; HANDLE __stdcall OpenProcess(DWORD dwDesiredAccess, BOOL bInheritHandle, DWORD dwProcessId)
.text:0DCEB359                 public _OpenProcess@12
.text:0DCEB359 _OpenProcess@12 proc near               ; CODE XREF: GetProcessVersion(x)+20F12↓p
.text:0DCEB359                                         ; GetProcessVersion(x)+20F28↓p
.text:0DCEB359                                         ; DATA XREF: ...
.text:0DCEB359
.text:0DCEB359 ObjectAttributes= _OBJECT_ATTRIBUTES ptr -20h
.text:0DCEB359 ClientId        = _CLIENT_ID ptr -8
.text:0DCEB359 dwDesiredAccess = dword ptr  8
.text:0DCEB359 bInheritHandle  = dword ptr  0Ch
.text:0DCEB359 dwProcessId     = dword ptr  10h
.text:0DCEB359
.text:0DCEB359 ; FUNCTION CHUNK AT .text:0DCE74A4 SIZE 0000000D BYTES
.text:0DCEB359
.text:0DCEB359                 mov     edi, edi
.text:0DCEB35B                 push    ebp
.text:0DCEB35C                 mov     ebp, esp
.text:0DCEB35E                 sub     esp, 20h
.text:0DCEB361                 mov     eax, [ebp+dwProcessId]
.text:0DCEB364                 mov     [ebp+ClientId.UniqueProcess], eax
.text:0DCEB367                 mov     eax, [ebp+bInheritHandle]
.text:0DCEB36A                 push    esi
.text:0DCEB36B                 xor     esi, esi
.text:0DCEB36D                 neg     eax
.text:0DCEB36F                 sbb     eax, eax
.text:0DCEB371                 and     eax, 2
.text:0DCEB374                 mov     [ebp+ObjectAttributes.Attributes], eax
.text:0DCEB377                 lea     eax, [ebp+ClientId]
.text:0DCEB37A                 push    eax             ; ClientId
.text:0DCEB37B                 lea     eax, [ebp+ObjectAttributes]
.text:0DCEB37E                 push    eax             ; ObjectAttributes
.text:0DCEB37F                 push    [ebp+dwDesiredAccess] ; DesiredAccess
.text:0DCEB382                 lea     eax, [ebp+dwProcessId]
.text:0DCEB385                 push    eax             ; ProcessHandle
.text:0DCEB386                 mov     [ebp+ClientId.UniqueThread], esi
.text:0DCEB389                 mov     [ebp+ObjectAttributes.Length], 18h
.text:0DCEB390                 mov     [ebp+ObjectAttributes.RootDirectory], esi
.text:0DCEB393                 mov     [ebp+ObjectAttributes.ObjectName], esi
.text:0DCEB396                 mov     [ebp+ObjectAttributes.SecurityDescriptor], esi
.text:0DCEB399                 mov     [ebp+ObjectAttributes.SecurityQualityOfService], esi
.text:0DCEB39C                 call    ds:__imp__NtOpenProcess@16 ; NtOpenProcess(x,x,x,x)
.text:0DCEB3A2                 cmp     eax, esi
.text:0DCEB3A4                 pop     esi
.text:0DCEB3A5                 jl      loc_DCE74A4
.text:0DCEB3AB                 mov     eax, [ebp+dwProcessId]
.text:0DCEB3AE
.text:0DCEB3AE locret_DCEB3AE:                         ; CODE XREF: OpenProcess(x,x,x)-3EAD↑j
.text:0DCEB3AE                 leave
.text:0DCEB3AF                 retn    0Ch
.text:0DCEB3AF _OpenProcess@12 endp
.text:0DCEB3AF
.text:0DCEB3AF ; ---------------------------------------------------------------------------

它的作用可以一句话概括:

把 Win32 参数转换成 OBJECT_ATTRIBUTES + CLIENT_ID,然后调用 NtOpenProcess,成功就返回进程句柄,失败走异常路径。

三、重写代码

cpp 复制代码
#include <iostream>
#include <windows.h>
#include <winternl.h>
using namespace std;

#define OBJ_INHERIT 0x00000002L

typedef struct _CLIENT_ID
{
    HANDLE UniqueProcess;
    HANDLE UniqueThread;
} CLIENT_ID, * PCLIENT_ID;

HANDLE MyOpenProcess(
    DWORD dwDesiredAccess,
    BOOL  bInheritHandle,
    DWORD dwProcessId
)
{
    NTSTATUS status;
    HANDLE   hProcess = NULL;

    CLIENT_ID cid = { 0 };
    OBJECT_ATTRIBUTES oa;

    // ---------- 构造 CLIENT_ID ----------
    cid.UniqueProcess = (HANDLE)dwProcessId;
    cid.UniqueThread = NULL;

    // ---------- 构造 OBJECT_ATTRIBUTES ----------
    oa.Length = sizeof(OBJECT_ATTRIBUTES);
    oa.RootDirectory = NULL;
    oa.ObjectName = NULL;
    oa.SecurityDescriptor = NULL;
    oa.SecurityQualityOfService = NULL;
    oa.Attributes = bInheritHandle ? OBJ_INHERIT : 0;

    __asm
    {
        // NtOpenProcess 参数(右到左压栈)
        // NtOpenProcess(&hProcess, dwDesiredAccess, &oa, &cid)

        lea eax, dword ptr ds : [cid] ;
        push eax;// 参数4:PCLIENT_ID

        lea eax, dword ptr ds : [oa] ;
        push eax;// 参数3:POBJECT_ATTRIBUTES

        mov eax, dword ptr ds : [dwDesiredAccess] ;
        push eax;// 参数2:ACCESS_MASK

        lea eax, dword ptr ds : [hProcess] ;
        push eax; // 参数1:PHANDLE

        // ---------- 关键补齐 ----------
        // 用于模拟缺失的"第二层返回地址"
        // 以匹配 KiFastCallEntry 中的 add edx, 8 行为
        push 0;

        // ---------- 构造 sysenter 返回地址 ----------
        push label;

        // ---------- 设置 syscall 号 ----------
        // 0x0BE 为当前 Win7 x86 环境下 NtOpenProcess 的 SSDT index
        mov eax, 0x0BE;

        // ---------- 进入内核 ----------
        // sysenter 要求:
        // EAX = syscall number
        // EDX = 用户态 ESP
        mov edx, esp;
        _emit 0x0F;        // 由于vs不支持sysenter命令
        _emit 0x34;        //0F34是sysenter的硬编码

            label:
        // ---------- 恢复栈 ----------
        // 共 push 了 6 次(24 字节):
        // 4 个参数 + 0 占位 + label
        add esp, 20       // 参数 + 占位(返回地址由 sysexit 处理)

        // ---------- 保存返回状态 ----------
            mov status, eax
    }

    if (status < 0)
        return NULL;

    return hProcess;
}

int main()
{
    HANDLE h = MyOpenProcess(PROCESS_ALL_ACCESS, FALSE, 3184);
    if (h)
    {
        cout << h << endl;
    }

    system("pause");
    return 0;
}

标准情况下,在执行 sysenter 的那一刻,用户栈长这样(从低地址→高地址):

cpp 复制代码
ESP ->  ; ntdll stub 里 call [KUSER] 压入
      NtOpenProcess      ; 调用 NtOpenProcess 的返回地址
      arg1 = &hProcess
      arg2 = DesiredAccess
      arg3 = &oa
      arg4 = &cid

所以我们自己的代码堆栈也要按照标准的方式补齐,否则0环得到的参数将错位:

cpp 复制代码
ESP ->  MyOpenProcess    ; call [KUSER] 压入
      0                  ;  占位
      arg1 = &hProcess
      arg2 = DesiredAccess
      arg3 = &oa
      arg4 = &cid
相关推荐
怪兽软家1 小时前
DaVinci Resolve/达芬奇 20安装教程及下载
windows·经验分享·生活
chao1898441 小时前
完整MES系统实现 (C# 客户端服务器)
服务器·windows·c#
Hello_Embed3 小时前
Windows 安装 Claude Code 并接入 模型
windows·笔记·ai编程
Muyuan19983 小时前
28.Paper RAG Agent 开发记录:修复 LLM Rerank 的解析、Fallback 与可验证性
linux·人工智能·windows·python·django·fastapi
AxureMost4 小时前
4DDiG DLL Fixe 1.0.8.2 系统DLL修复工具
windows
怣疯knight6 小时前
Windows不安装 Android Studio如何打包安卓软件
android·windows·android studio
空中海7 小时前
02. 静态逆向、Manifest 分析与 Smali 重打包
服务器·网络·windows
一拳一个娘娘腔7 小时前
告别图形化界面:基于CLI的Windows系统入侵排查与防御实战手册
windows·安全
疋瓞8 小时前
批处理_基础补充、文件和文件夹处理_02
windows
nudt_qxx9 小时前
Ubuntu 24.04/26.04 与 Windows 10/11 双系统时间不同步终极解决方案
windows·stm32·ubuntu