首页
社区
课程
招聘
[原创] 第五届“鹏城杯”初赛 Reverse-MDriver wp
发表于: 1天前 453

[原创] 第五届“鹏城杯”初赛 Reverse-MDriver wp

1天前
453

驱动被vmp保护,因此首先需要dump驱动以完成脱壳,对KeRevertToUserAffinityThread下断点后dump驱动程序并分别修复SectionAlignment,SizeOfHeaders,PointerToRawData以便其能被ida识别。

脱完壳后首先定位 security_init_cookie找真正的驱动入口点

这里直接用特征码 48 8B 05 ?? ?? ?? ?? 48 85 C0 74 1B 48
图片描述

由此可以得到驱动真实入口点逻辑,我已经恢复部分导入

图片描述

_I@vmravY$@vmrav$mjmp$wqggaww%经xor爆破发现是驱动成功加载的调试信息

*(_QWORD *)(RCX + 104) = FwpmFilterDeleteById0_0; RCX 是 DRIVER_OBJECT 指针。偏移 104 (0x68) 在 x64 DRIVER_OBJECT 结构中对应 DriverUnload 域。它将 FwpmFilterDeleteById0_0 注册为卸载函数。当驱动停止时,这个函数会被调用以清理 WFP 过滤器。

因此必定存在初始化wfp的环境,即函数sub_FFFFF80541012AA0,接下来主要分析这个函数。

可以看到以下都是我恢复符号后的版本

图片描述
FwpmEngineOpen0_0: 建立与过滤引擎的会话。
FwpmSubLayerAdd0_0: 创建一个新的子层 (SubLayer),权重设为 64。所有的过滤器都将在这个子层中运行,这有助于控制过滤器的优先级。
sub_FFFFF80541012C90: 调用封装好的辅助函数来注册 Callout 并添加过滤器。
参数传递了层 GUID (&a1)、Callout GUID (&unk_...141C0) 以及回调函数 sub_FFFFF80541012440
FwpmTransactionCommit0_0: 如果一切顺利,提交事务,使过滤规则生效
图片描述

分析得出sub_FFFFF80541012C90将回调函数注册到内核,并绑定到特定的过滤层

根据前面的分析 我们知道了sub_FFFFF80541012440是wfp注册的回调函数,用来对流量做处理
搜索资料得到其函数原型 为 标准WFPClassifyFn

图片描述

根据layerid的过滤可以确定监听范围为layrid=52
图片描述
即 ALE_FLOW_ESTABLISHED_V4 当一个新的网络流(Flow)建立时触发

inFixedValues->incomingValue[5].value.uint8是过滤协议的操作
图片描述

第五个是protocol所以检查 v9[2] == 1 是过滤icmp的操作,所以此回调函数响应的是ICMP包

图片描述

ICMP的Type=8 对应驱动检查 ptr == 8,意味着ICMP包type需要=8
图片描述
之后解密字符串,打印调试信息并进入最终的工作线程sub_FFFFF805410118A0

图片描述
效验线程内执行了四个用于效验的函数,最后执行资源清理工作
图片描述
sub_FFFFF80541011120取出发送方的pid备用

图片描述
sub_FFFFF80541011DC0实际上为key1的生成逻辑 ,其生成一个key v10 来和传入的明文(a1)异或进行加密

key1的生成逻辑如下

读取 GS 段寄存器 偏移 0x50 处的值。

与常数 0x020B0000 异或。

循环右移 (ROR) 18 位,计算出 MSR 索引。

最后rdmsr 0xC0000082拿到地址,这个寄存器存放的是系统调用入口函数 KiSystemCall64 的内存地址,所以key就是开头的几个字节。

图片描述
sub_FFFFF80541011750调用sub_FFFFF80541011C10生成key,做进一步处理后与明文异或。
图片描述
sub_FFFFF80541011C10实际上为key2的生成逻辑

key2的生成逻辑如下:

根据当前irql去计算一个地址(计算地址的脚本就不放了,irql=0),从中取出字符串最终取出的是 L"C:\Windows",结合前面的部分可以写脚本生成key2并取前0x14字节

图片描述
工作线程的第四次函数调用进入了最终效验环节,将加密后的输入和密文比对。
图片描述
如果成功就将flag做一个异或(虚拟化了),申请一块内存写入到发送包的进程中并打印成功提示(用了 sub_FFFFF80541011120取出的pid)

驱动注册了一个 WFP Callout (sub_FFFFF80541012C90),并在 ClassifyFn (sub_FFFFF80541012440) 中处理网络包。
代码检查 LayerId=52( ALE_FLOW_ESTABLISHED_V4)。

检查 incomingValue[5] == 1,对应 IProtocol == ICMP

驱动读取 Payload 数据,进入密钥校验逻辑。

key1: 通过 __readgsbyte 和 XOR 运算算出 MSR 索引,最终读取 LSTAR (0xC0000082) 寄存器指向的地址。LSTAR指向 KiSystemCall64,这是系统调用的入口。读取该地址的前 4 字节机器码。标准 Windows 环境下为 0F 01 F8 65 (swapgs; mov gs:[10], rsp)。

key2: 读取 KUSER_SHARED_DATANtSystemRoot 字段(即 C:\WINDOWS),转换为大写宽字符。然后进行Key2[i] = SeedBytes[i] ^ (~i)

将输入与key1key2异或后与密文比对

当 Payload 校验通过后,驱动执行以下操作:
PsLookupProcessByProcessId 获取 EPROCESS。
KeStackAttachProcess 挂靠到发送者进程空间。
NtAllocateVirtualMemory 在发送者进程中申请 4KB RW内存。
将黑箱运算后的 flag 写入该内存。
DbgPrint 输出成功信息。

分析完驱动后已经注意到有几处反调试,尝试绕过以便在虚拟机成功加载驱动,打印
[MDriver] Driver init success! 即视为加载成功

图片描述

在初始化回调函数的末尾被调用

图片描述

图片描述

使用dpc广播检查内核 KdDebuggerEnabled 和KdDebuggerNotPresent 标志位,并再次检查KUSER_SHARED_DATA+2d4
图片描述
检测到调试器则0xdeaddb9蓝屏

此反调试位于key2的生成逻辑中

图片描述

正常程序运行在 PASSIVE_LEVEL (0)计算出来的地址是正确的,能成功取出字符串

一旦windbg DbgBreakPoint 触发 则会提升到 DISPATCH_LEVEL 导致地址计算失败,0x9999999蓝屏。这避免了被动态调试出地址计算流程(实际上并不影响动调获取key2)

没什么好说的 base+10A0 直接ret就行,反调试2完全不用管,我们已经分析完了驱动逻辑得到了2个key,接下来加载驱动发包即可。

ret反调试后成功加载驱动

图片描述

已经在上文静态分析得到了key1和key2,写个脚本异或就行

得到payload

图片描述

写个exp去发imcp包,格式按照前面分析的来

打包一下放虚拟机运行即可

图片描述

图片描述

void ClassifyFn(
    const FWPS_INCOMING_VALUES0 *inFixedValues,     // a1
    const FWPS_INCOMING_METADATA_VALUES0 *inMetaValues, // a2
    void *layerData,                                // a3
    const void *classifyContext,                    // a4
    const FWPS_FILTER1 *filter,                     // a5
    UINT64 flowContext,                             // a6 (IDA识别为 _DWORD* 有误)
    FWPS_CLASSIFY_OUT0 *classifyOut                 // a7
);
void ClassifyFn(
    const FWPS_INCOMING_VALUES0 *inFixedValues,     // a1
    const FWPS_INCOMING_METADATA_VALUES0 *inMetaValues, // a2
    void *layerData,                                // a3
    const void *classifyContext,                    // a4
    const FWPS_FILTER1 *filter,                     // a5
    UINT64 flowContext,                             // a6 (IDA识别为 _DWORD* 有误)
    FWPS_CLASSIFY_OUT0 *classifyOut                 // a7
);
1: kd> rdmsr C0000082
msr[c0000082] = fffff801`0c211900
1: kd>  db fffff801`0c211900 l4
fffff801`0c211900  0f 01 f8 65(key1!!)
1: kd> rdmsr C0000082
msr[c0000082] = fffff801`0c211900
1: kd>  db fffff801`0c211900 l4
fffff801`0c211900  0f 01 f8 65(key1!!)
def generate_v4_key():
    # 1. 准备种子字符串 "C:\WINDOWS" (宽字符, 大写)
    # 来源于之前的分析:KUSER_SHARED_DATA.NtSystemRoot
    seed_str = "C:\\WINDOWS"
    seed_bytes = seed_str.encode('utf-16le')
    seed_bytes = seed_bytes[:0x14]
 
    print(f"[*] 种子数据 (Hex): {seed_bytes.hex().upper()}")
    v4 = bytearray(256)
    for k in range(len(seed_bytes)):
        v4[k] = seed_bytes[k]
    print("[*] 开始计算密钥流 v4...")
    for i in range(256):
        not_i = (~i) & 0xFF
         
        v4[i] ^= not_i
    # 5. 输出结果
    print("-" * 30)
    print(f"[+] 最终密钥 v4 (256 bytes) 生成完成。")
    print("-" * 30)
    print("前 40 字节预览 (Hex):")
    print(v4[:40].hex(' ').upper())
    print("\n[C 语言数组格式]:")
    c_array = ', '.join([f'0x{b:02X}' for b in v4])
    print(f"unsigned char v4[256] = {{ {c_array} }};")
    return v4
 
if __name__ == "__main__":
    key_stream = generate_v4_key()
def generate_v4_key():
    # 1. 准备种子字符串 "C:\WINDOWS" (宽字符, 大写)
    # 来源于之前的分析:KUSER_SHARED_DATA.NtSystemRoot
    seed_str = "C:\\WINDOWS"
    seed_bytes = seed_str.encode('utf-16le')
    seed_bytes = seed_bytes[:0x14]
 
    print(f"[*] 种子数据 (Hex): {seed_bytes.hex().upper()}")
    v4 = bytearray(256)
    for k in range(len(seed_bytes)):
        v4[k] = seed_bytes[k]
    print("[*] 开始计算密钥流 v4...")
    for i in range(256):
        not_i = (~i) & 0xFF
         
        v4[i] ^= not_i
    # 5. 输出结果
    print("-" * 30)
    print(f"[+] 最终密钥 v4 (256 bytes) 生成完成。")

传播安全知识、拓宽行业人脉——看雪讲师团队等你加入!

收藏
免费 5
支持
分享
最新回复 (1)
雪    币: 7566
活跃值: (3381)
能力值: (RANK:166 )
在线值:
发帖
回帖
粉丝
2
不过可以分享一下IAT修复就更棒了
18小时前
0
游客
登录 | 注册 方可回帖
返回