首页
社区
课程
招聘
[原创]实现简易ARK工具(5) x64信息快查
发表于: 2025-8-6 20:48 1308

[原创]实现简易ARK工具(5) x64信息快查

2025-8-6 20:48
1308

前言
开了这个坑,但前文都是x86的,感觉差点意思,发笔记凑活一下- -

写玩具的时候收集的一些api、结构体、遍历信息的方法,全靠看雪前辈们的文章啊-。-,帮助新同学省去检索信息的时间。能力有限,欢迎大家补充指正。
下文内容在win7 7601和win10 19h1经验证可行。
图片描述

解析pdb

R3:836K9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6Y4K9i4c8Z5N6h3u0Q4x3X3g2U0L8$3#2Q4x3V1k6w2N6$3q4F1M7%4V1&6z5q4)9J5c8V1g2S2M7%4W2b7k6r3t1`.

R0:aadK9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6Y4K9i4c8Z5N6h3u0Q4x3X3g2U0L8$3#2Q4x3V1k6a6P5s2W2Y4k6h3^5I4j5e0q4Q4x3V1k6G2P5r3N6W2L8W2m8V1j5R3`.`. 他用的ksocket库只能发http请求,所以我只用他解析pdb,让R3调UrlDownloadToFile下载。

也可以让ida下pdb 直接使用dia2dump解析即可

大家有什么在R0下载符号的方法吗

GDT

用不了__asm了,用函数拿gdtr,联合编译asm也行

#include <immintrin.h>

extern "C" void _sgdt(void*);

​ GDTR gdtr = { 0 };

​ _sgdt(&gdtr);

我在R3拿居然是错的

在x64下,TSS(任务状态段)和LDT(局部描述符表)等系统段描述符扩展为16字节,占用连续两个GDT项。

前8字节与32位一样,后8字节用于补充TSS/LDT的高32位base地址和保留字段。

所以解析数据的时候遇到s=0且type为TSS/LDT类型(type=2,9,11),和下一项8字节的数据合并一起解析。

平坦模式导致数据段和代码段的base/limit字段失效,只需关注系统段的base/limit。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
//32位的
typedef struct SegmentDescriptor {
    unsigned Limit1 : 16;    // 界限低16位
    unsigned Base1 : 16;     // 基址低16位 
    unsigned Base2 : 8;      // 基址中8位
    unsigned type : 4;       // 段类型
    unsigned s : 1;          // 系统段标志
    unsigned dpl : 2;        // 特权级
    unsigned p : 1;          // 存在位
    unsigned Limit2 : 4;     // 界限高4位
    unsigned avl : 1;        // 软件可用位
    unsigned l : 1;          // 64位代码段标志    以前的保留位
    unsigned db : 1;         // 操作数大小        以前代表段位数,64位代码段也就是l=1时db位必须为0
    unsigned g : 1;          // 粒度位
    unsigned Base3 : 8;      // 基址高8位
} SegmentDescriptor, *PSEGDESC;  // 确保是8字节
 
// 64位系统段描述符(16字节)
typedef struct SystemDescriptor64 {
    SegmentDescriptor low;   // 低8字节
    unsigned Base4 : 32;     // 基址最高32位
    unsigned reserved : 32;  // 保留字段
} SystemDescriptor64;

但是我观察许多ARK并没有合并显示 仍然保留了合并的表项

保留的

我没保留

虽然数值解析的一致 但是openark仍然显示了0x48

IDT

与GDT一样 一个核心一张表

使用KeSetSystemAffinityThreadEx切核心+__sidt(&idtr)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 中断描述符结构 (x64)
typedef struct InterruptDescriptor {
    USHORT OffsetLow;               // 处理程序地址低16位
    USHORT Selector;                // 段选择子
    USHORT IstIndex : 3;            // IST索引
    USHORT Reserved0 : 5;           // 保留
    USHORT Type : 4;                // 门类型
    USHORT Reserved1 : 1;           // 保留
    USHORT Dpl : 2;                 // 描述符特权级
    USHORT Present : 1;             // 存在位
    USHORT OffsetMiddle;            // 处理程序地址中16位
    ULONG OffsetHigh;               // 处理程序地址高32位
    ULONG Reserved2;                // 保留
} *PINTDESC;

SSDT

在 64位Windows 中,SSDT表的结构与32位不同:

  • 32位:SSDT表直接存储函数地址
  • 64位:SSDT表存储的是相对偏移量,需要计算得到真实地址
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
ULONG_PTR SSDT_GetPfnAddr(ULONG dwIndex, PULONG lpBase)
{
    ULONG_PTR lpAddr = NULL;
 
    ULONG dwOffset = lpBase[dwIndex];
 
    //按16位对齐省空间,所以>>4;负偏移有+-问题,所以|0xF00..
    if (dwOffset & 0x80000000)
        dwOffset = (dwOffset >> 4) | 0xF0000000;
    else
        dwOffset >>= 4;
 
    lpAddr = (ULONG_PTR)((PUCHAR)lpBase + (LONG)dwOffset);
 
    return lpAddr;
}
  1. 右移4位:Windows x64中,SSDT偏移量以16字节对齐,所以低4位总是0,可以节省空间
  2. 符号扩展:处理负偏移(向前的地址),通过| 0xF0000000进行符号扩展
  3. 相对地址计算:最终地址 = SSDT基址 + 计算出的偏移量

抄这篇https://bbs.kanxue.com/thread-248117.htm

ShadowSSDT

拿到KeServiceDescriptorTableShadow后找第二张表是Shadow,解析方式与SSDT一致。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
NTSTATUS EnumShadowSSDT(PSSDT_INFO SsdtBuffer, PULONG SsdtCount)
{
    INIT_PDB;
                           
    PSYSTEM_SERVICE_DESCRIPTOR_TABLE ShadowTableArray =
    (PSYSTEM_SERVICE_DESCRIPTOR_TABLE)ntos.GetPointer("KeServiceDescriptorTableShadow");
 
    Log("[XM] KeServiceDescriptorTableShadow Array: %p", ShadowTableArray);
 
    if (!ShadowTableArray) {
        Log("[XM] KeServiceDescriptorTableShadow == null");
        return STATUS_UNSUCCESSFUL;
    }
     
    // 访问数组的第二个元素 [1] - 这才是真正的 ShadowSSDT                                    
    PSYSTEM_SERVICE_DESCRIPTOR_TABLE ShadowTable = &ShadowTableArray[1];
     
    Log("[XM] ShadowSSDT [0]: Base=%p, Count=%d",
        ShadowTableArray[0].Base, ShadowTableArray[0].NumberOfServices);
    Log("[XM] ShadowSSDT [1]: Base=%p, Count=%d",
        ShadowTable->Base, ShadowTable->NumberOfServices);
     
    if (!ShadowTable->Base || ShadowTable->NumberOfServices == 0) {
        Log("[XM] ShadowSSDT not available");
        Log("[XM] ShadowSSDT [1] not available");
    }
 
    ULONG nums = ShadowTable->NumberOfServices;
    PULONG shadowSsdt = ShadowTable->Base;
    *SsdtCount = nums;
 
    Log("[XM] ShadowSSDT found: %d services", nums);
 
    for (ULONG i = 0; i < nums; i++) {
        SsdtBuffer[i].Index = i + 0x1000;  // ShadowSSDT的调用号从0x1000开始
 
         
        ULONG_PTR pfnAddr = SSDT_GetPfnAddr(i, shadowSsdt);
        SsdtBuffer[i].FunctionAddress = (PVOID)pfnAddr;
 
        Log("[XM] ShadowSSDT[%d]: Raw=0x%X, Decoded=0x%p", i, shadowSsdt[i], pfnAddr);
    }
 
    return STATUS_SUCCESS;
}

遍历进程

1.前文讲的遍历进程链表

PsInitialSystemProcess → EPROCESS.ActiveProcessLinks → 下一个EPROCESS → ...

2.用PsLookupProcessByProcessId

函数内部会查句柄表,相比链表更安全

由于pid是四的倍数

for(pid=0;pid<65535;pid+=4) → 句柄表查找 → 返回EPROCESS指针

3.内存特征EPROCESS

有什么表敌我都知道 最后还得暴力搜内存
_DISPATCHER_HEADER.type= 0x3
验证EPROCESS进程链表的后继的前驱是不是自己
页目录表是否有效 低12位是否为0(页对齐)等等方法

4.听闻还有一种从线程调度反推进程的方法

遍历模块

利用PsLoadModuleList 杖举驱动模块 - KTr - 博客园

或者 ZwQueryInformation

枚举常规回调

https://bbs.kanxue.com/thread-148895.htm

**进程 线程 模块 **都用数组存放回调函数指针 ,数组里最多塞下64个回调指针:

PspCreateProcessNotifyRoutine

PspCreateThreadNotifyRoutine

PspLoadImageNotifyRoutine

1
2
3
4
5
6
typedef struct _EX_CALLBACK_ROUTINE_BLOCK {
    EX_RUNDOWN_REF        RundownProtect;   // Rundown protection structure
    PEX_CALLBACK_FUNCTION Function;         // 回调函数地址
    PVOID                 Context;          // 回调上下文参数
 
} EX_CALLBACK_ROUTINE_BLOCK, * PEX_CALLBACK_ROUTINE_BLOCK;

通过索引/函数地址删除回调

PsSetCreateProcessNotifyRoutine(functionAddr, TRUE);

蓝屏 注册表 关机 都用链表存放回调函数指针 :

注册表

6bdK9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6U0L8r3!0#2k6q4)9J5k6i4c8W2L8X3y4W2L8Y4c8Q4x3X3g2U0L8$3#2Q4x3V1k6V1k6i4k6W2L8r3!0H3k6i4u0Q4x3V1k6S2M7Y4c8A6j5$3I4W2i4K6u0r3x3U0x3$3y4U0R3K6y4H3`.`.

1
2
3
4
5
6
7
8
9
//注册表回调结构
typedef struct _CM_NOTIFY_ENTRY {
    LIST_ENTRY    ListEntryHead;        //链表项
    ULONG         UnKnown1;            
    ULONG         UnKnown2;            
    LARGE_INTEGER Cookie;               //回调标识符
    PVOID         Context;              //用户上下文
    PVOID         Function;             //回调函数地址
} CM_NOTIFY_ENTRY, *PCM_NOTIFY_ENTRY;

定位链表遍历就行

注册表回调得用cookie删除

1
2
3
4
LARGE_INTEGER cookie;
cookie.QuadPart = (LONGLONG)deleteKey; 
 
NTSTATUS status = CmUnRegisterCallback(cookie);

**蓝屏 **

结构体Wdm.h都有

标准蓝屏回调 (KeBugCheckCallback)

  • 符号: KeBugCheckCallbackListHead
  • 结构: KBUGCHECK_CALLBACK_RECORD
  • 用途: 在蓝屏时保存数据、记录状态

KeBugCheckCallbackListHead

1
2
3
4
5
6
7
8
9
typedef struct _KBUGCHECK_CALLBACK_RECORD {
LIST_ENTRY Entry;
PKBUGCHECK_CALLBACK_ROUTINE CallbackRoutine;
 _Field_size_bytes_opt_(Length) PVOID Buffer;  // 这个宏指 Buffer大小由Length字段指定
ULONG Length;                                  // 指定Buffer的字节长度
PUCHAR Component;
ULONG_PTR Checksum;
UCHAR State;
} KBUGCHECK_CALLBACK_RECORD, *PKBUGCHECK_CALLBACK_RECORD;

蓝屏回调通过 结构指针 删除

KeDeregisterBugCheckCallback(recordPtr);

蓝屏原因回调 (KeRegisterBugCheckReasonCallback)

  • 符号: KeBugCheckReasonCallbackListHead
  • 结构: KBUGCHECK_REASON_CALLBACK_RECORD
  • 用途: 根据蓝屏原因执行特定处理
1
2
3
4
5
6
7
8
typedef struct _KBUGCHECK_REASON_CALLBACK_RECORD {
LIST_ENTRY Entry;                           // 链表项
PKBUGCHECK_REASON_CALLBACK_ROUTINE CallbackRoutine;  // 回调函数
PUCHAR Component;                           // 组件名
ULONG_PTR Checksum;                        // 校验和
KBUGCHECK_CALLBACK_REASON Reason;          // 回调原因
UCHAR State;                               // 状态
} KBUGCHECK_REASON_CALLBACK_RECORD, * PKBUGCHECK_REASON_CALLBACK_RECORD;

**关机 **

IopNotifyShutdownQueueHead

1
2
3
4
5
6
//关机回调结构
typedef struct _SHUTDOWN_PACKET {
    LIST_ENTRY ListEntry;               // +0x00 链表项
    PDEVICE_OBJECT DeviceObject;        // +0x10 设备对象
    PIRP Irp;                          // +0x18 IRP
} SHUTDOWN_PACKET, *PSHUTDOWN_PACKET;
注册 → IoRegisterShutdownNotification → 创建SHUTDOWN_PACKET → 插入IopNotifyShutdownQueueHead
关机触发 → 遍历链表 → 向每个设备发送IRP_MJ_SHUTDOWN
删除 → IoUnregisterShutdownNotification → 从链表移除SHUTDOWN_PACKET

枚举对象回调

ObTypeIndexTable→ _OBJECT_TYPE → CallbackList → CALLBACK_BODY → Pre/PostCallbackRoutine

1: kd>   x nt!ObTypeIndexTable
fffff806`25e1bd70 nt!ObTypeIndexTable = <no type information>

*************************************************************************
1: kd>   dt nt!_OBJECT_TYPE
   +0x000 TypeList         : _LIST_ENTRY
   +0x010 Name             : _UNICODE_STRING
   +0x020 DefaultObject    : Ptr64 Void
   +0x028 Index            : UChar
   +0x02c TotalNumberOfObjects : Uint4B
   +0x030 TotalNumberOfHandles : Uint4B
   +0x034 HighWaterNumberOfObjects : Uint4B
   +0x038 HighWaterNumberOfHandles : Uint4B
   +0x040 TypeInfo         : _OBJECT_TYPE_INITIALIZER
   +0x0b8 TypeLock         : _EX_PUSH_LOCK
   +0x0c0 Key              : Uint4B
   +0x0c8 CallbackList     : _LIST_ENTRY

测试1 获取ObTypeIndexTable中每个ObType并打印出Name

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
void ForTest() {
    INIT_PDB;
    ULONG_PTR ObTypeIndexTable = ntos.GetPointer("ObTypeIndexTable");
    Log("[XM] ObTypeIndexTable地址: %p", ObTypeIndexTable);
 
    //遍历 先打印出所有的对象名称看看
    int maxType = 100;
    for (int i = 0; i < maxType; i++) {
        // 取出POBJECT_TYPE指针
        ULONG_PTR objTypeAddr = *(ULONG_PTR*)(ObTypeIndexTable + i * sizeof(ULONG_PTR));
        if (!objTypeAddr) continue;
 
        // OBJECT_TYPE结构体Name字段偏移
        size_t nameoffset = ntos.GetOffset("_OBJECT_TYPE", "Name");
        Log("[XM] nameoffset: %p", nameoffset);
        ULONG_PTR nameAddr = objTypeAddr + nameoffset;
 
        // 读取UNICODE_STRING
        UNICODE_STRING* pName = (UNICODE_STRING*)nameAddr;
        if (!MmIsAddressValid(pName) || !MmIsAddressValid(pName->Buffer)) continue;
 
        // 打印对象类型名
        Log("[XM] 对象类型[%d] 地址: %p 名称: %ws", i, objTypeAddr, pName->Buffer);
    }
}

CallbackList的结构体未公开 网上各种叫法 给我整蒙了

这篇有讲:https://bbs.kanxue.com/thread-277238.htm

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
typedef struct _CALLBACK_NODE {
 
    USHORT Version;                 // 版本号,目前是0x100, 可通过ObGetFilterVersion获取该值
    USHORT CallbackBodyCount;       // 本节点上CallbackBody的数量
    PVOID Context;                  // 注册回调时设定的0B_CALLBACK_REGISTRATION.RegistrationContext
    UNICODE_STRING Altitude;        // 指向Altitude字符串
    char CallbackBody[1];           // 原本是CALLBACK_BODY CallbackBody[1] -> CALLBACK_BODY数组, 其元素个数为CallbackCount 
                                    // 我用不到 改成了char
} CALLBACK_NODE, * PCALLBACK_NODE;
 
 
typedef struct _CALLBACK_BODY {
 
    LIST_ENTRY ListEntry;
    /* 系统中同类型对象的的CALLBACK_NODE通过这个链表串在一起, 对应于_OBJECT_TYPE->TypeList */
 
    OB_OPERATION Operations;
    /* 注册回调时设定的OB_OPERATION_REGISTRATION.Operations成员(OB_OPERATION_HANDLE_CREATE... ) */
 
    ULONG Active;
 
    PCALLBACK_NODE CallbackNode; // 指向该CallbackBody
 
    POBJECT_TYPE ObjectType;
 
    POB_PRE_OPERATION_CALLBACK PreCallbackRoutine;
 
    POB_POST_OPERATION_CALLBACK PostCallbackRoutine;
 
    EX_RUNDOWN_REF RundownProtection; // Run-down Protection
 
} CALLBACK_BODY, * PCALLBACK_BODY;

遍历ObTypeIndexTable得到_OBJECT_TYPE

拿到_OBJECT_TYPE->CallbackList之后遍历链表 每个节点按CALLBACK_BODY解析。

PreCallbackRoutinePostCallbackRoutine是对象回调函数数组。

卸载对象回调传CALLBACK_BODY->CallbackNodeObUnRegisterCallbacks即可。

IO派遣函数/设备栈

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
3: kd> dt nt!_DRIVER_OBJECT -o
   Type             : Int2B
   Size             : Int2B
   DeviceObject     : Ptr64 _DEVICE_OBJECT
   Flags            : Uint4B
   DriverStart      : Ptr64 Void
   DriverSize       : Uint4B
   DriverSection    : Ptr64 Void
   DriverExtension  : Ptr64 _DRIVER_EXTENSION
   DriverName       : _UNICODE_STRING
   HardwareDatabase : Ptr64 _UNICODE_STRING
   FastIoDispatch   : Ptr64 _FAST_IO_DISPATCH
   DriverInit       : Ptr64     long
   DriverStartIo    : Ptr64     void
   DriverUnload     : Ptr64     void
   MajorFunction    : [28] Ptr64     long

遍历驱动对象方法 有代码: https://bbs.kanxue.com/thread-276245.htm

得知道这一段连续的数据结构

_OBJECT_HEADER_NAME_INFO (size: 0x20)

_OBJECT_HEADER (offset: 0x30)

Driver Object

实测能跑

拿到驱动对象就可以拿设备对象 然后遍历attachdevice 和MajorFunction

查被过滤的设备 检测派遣函数指针是否在xx模块内

网络端口

R3有API 可以拿到端口pid

有示例代码

getTcpTable 函数 (iphlpapi.h) - Win32 apps

getExtendedTcpTable 函数 (iphlpapi.h) - Win32 apps

openark的kernel api-network.cpp中也有R3实现

解除文件占用

强制关闭文件句柄

在目标进程上下文中调用 ZwClose() 关闭文件句柄

R3获取文件路径 转换成设备路径 传给R0
      ↓
  ObQueryNameString(文件对象) 获取完整文件路径
      ↓
  RtlCompareUnicodeString(对象路径, 目标路径) 字符串匹配
      ↓
PsLookupProcessByProcessId  获取EPROCESS对象 注意这个函数使用完要减少引用计数
      ↓
  KeStackAttachProcess 切换到目标进程上下文
      ↓
  ZwClose(句柄值)  在目标进程中关闭文件句柄
      ↓
  KeUnstackDetachProcess 恢复原始进程上下文
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
NTSTATUS UnlockFile(WCHAR* filePath) {
 
    //遍历所有进程句柄表
    NTSTATUS Status;
    PSYSTEM_HANDLE_INFORMATION_EX      HandlesEx;
    PSYSTEM_HANDLE_TABLE_ENTRY_INFO_EX HandleInfoEx;
    POBJECT_NAME_INFORMATION  ObjectNameInfo;
    PVOID Buffer;
    ULONG BufferSize = 4096;
    ULONG ReturnLength;
    ULONG_PTR i;
    UNICODE_STRING ustrName;
 
    RtlInitUnicodeString(&ustrName, filePath);
    Log("[XM] UnlockFile ustr: %wZ", &ustrName);
 
    ObjectNameInfo = (POBJECT_NAME_INFORMATION)ExAllocatePoolWithTag(NonPagedPool, 4096, 'ULFL');
    if (!ObjectNameInfo) {
        return STATUS_NO_MEMORY;
    }
 
retry:
    Buffer = ExAllocatePoolWithTag(NonPagedPool, BufferSize, 'ULFL');
    if (!Buffer) {
        ExFreePool(ObjectNameInfo);
        return STATUS_NO_MEMORY;
    }
    Status = ZwQuerySystemInformation(SystemExtendedHandleInformation,
        Buffer,
        BufferSize,
        &ReturnLength
    );
 
    if (Status == STATUS_INFO_LENGTH_MISMATCH) {
        ExFreePool(Buffer);
        BufferSize = ReturnLength;
        goto retry;
    }
 
    if (NT_SUCCESS(Status)) {
 
        HandlesEx = (PSYSTEM_HANDLE_INFORMATION_EX)Buffer;
 
        Log("[XM] 开始遍历句柄,总数: %llu", HandlesEx->NumberOfHandles);
 
        for (i = 0; i < HandlesEx->NumberOfHandles; i++) {
            HandleInfoEx = &(HandlesEx->Handles[i]);
            Status = ObReferenceObjectByPointer(HandleInfoEx->Object, 0,
                *IoFileObjectType, KernelMode);//*IoFileObjectType
 
            if (NT_SUCCESS(Status)) {
                Status = ObQueryNameString(HandleInfoEx->Object, ObjectNameInfo, 4096, &ReturnLength);
 
                if (NT_SUCCESS(Status)) {
                                       
                    if (RtlCompareUnicodeString(&ObjectNameInfo->Name, &ustrName, TRUE) == 0) {
                        Log("[XM] 找到匹配句柄: PID=%llu, Handle=%llu, Path=%wZ",
                            HandleInfoEx->UniqueProcessId,
                            HandleInfoEx->HandleValue,
                            &ObjectNameInfo->Name);
 
                        //切换进程
                        PEPROCESS Process = NULL;
                        Status = PsLookupProcessByProcessId((HANDLE)HandleInfoEx->UniqueProcessId, &Process);
 
                        if (NT_SUCCESS(Status)) {
                            KAPC_STATE ApcState;
                            KeStackAttachProcess(Process, &ApcState);
                            Status = ZwClose((HANDLE)HandleInfoEx->HandleValue);
 
                            Log("[XM] unlock UniqueProcessId:%llu HandleValue:%llu Name:%wZ",
                                HandleInfoEx->UniqueProcessId,
                                HandleInfoEx->HandleValue,
                                &ObjectNameInfo->Name);
 
                            KeUnstackDetachProcess(&ApcState);
                            ObDereferenceObject(Process);
                        }
                    }
                }
                ObDereferenceObject(HandleInfoEx->Object);
            }
        }
 
    }
 
    ExFreePool(ObjectNameInfo);
    ExFreePool(Buffer);
    return Status;
}

文件粉碎

先解锁后删除

ZwDeleteFile(&ustrName);

路径转换

  1. C:\Users\XiaM\Desktop\1.txt
    • 名称: DOS路径 (DOS Path) / Win32路径
    • 用途: 用户和应用程序使用的标准路径
  2. ??\C:\Users\XiaM\Desktop\1.txt
  • 名称: NT路径 (NT Path) / 符号链接路径
  • 用途: NT内核的路径表示,?? 是符号链接目录
  1. \Device\HarddiskVolume1\Users\XiaM\Desktop\1.txt
  • 名称: 设备路径 (Device Path) / 物理设备路径

  • 用途: 内核对象管理器中的真实设备路径

    DOS路径 → NT路径 → 设备路径

    C:... → ??\C:... → \Device\HarddiskVolume1...

像UnlockFile功能 需要在R3获取文件路径 转换为设备路径

1
2
3
4
5
6
7
8
9
10
11
12
DOS路径到设备路径转换
std::wstring ConvertToDevicePath(const std::wstring& dosPath) {
    WCHAR driveLetter[3] = {dosPath[0], L':', L'\0'};
    WCHAR deviceName[MAX_PATH];    // 查询DOS设备对应的真实设备
    if (QueryDosDeviceW(driveLetter, deviceName, MAX_PATH)) {
        // 拼接:设备名 + 路径部分
        std::wstring result = deviceName;          // \Device\HarddiskVolume3
        result += dosPath.substr(2);               // + \Users\XiaM\Desktop\1.txt
        return result;
    }
    return L"";
}

强制关闭进程

PspTerminateProcess

PspTerminateThreadByPointer

https://bbs.kanxue.com/thread-270012.htm


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

最后于 2025-8-10 18:49 被X66iaM编辑 ,原因:
收藏
免费 4
支持
分享
最新回复 (2)
雪    币: 1272
活跃值: (1820)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
2

棒棒滴,继续加油,期待对ObjectHandle枚举的实现。

最后于 2025-8-10 00:44 被AL10000编辑 ,原因:
2025-8-10 00:26
0
雪    币: 205
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
3
谢谢分享
2025-12-24 18:53
0
游客
登录 | 注册 方可回帖
返回