穿越火线辅助都在用的读写驱动样本全逆向+功能分析
该样本于去年6月份绝地求生wg泛滥时搜集到,根据pdb路径(ReadCF\x64\Win7Release\ReadCF.pdb)推测该样本至少2016年PUBG没火时就已经出现而且当时是针对CF写的,直到2018年5月居然还有人把该样本用在自己的绝地求生wg上,曾经用过该样本的wg多达几十款(真的)。涉及各种原因这里不细说了,我们只谈技术:
先从DriverEntry开始
先从ntos导出了一系列API,存放到g_Context结构中,这里g_Context被优化成了一个个独立的全局变量,实际上作者写的时候是用类似g_Context.PsGetCurrentProcess = MmGetSystemRoutineAddress( 这样的形式
然后是InitAll,先搜索 mouseservicecallback 回调函数
先找到mouhid / i8042和mouclass的驱动对象然后遍历mouhid / i8042 的设备栈
在其设备扩展中找到 servicecallback(特征是地址在DriverStart 到DriverStart+DriverSize 指定的驱动Image区域内并且可以访问)
存放进 g_Context.MouseServiceCallback 中,将来用于模拟鼠标操作
↓ pContext = MakeContext(RtlCopyMemory, 0i64, 1);的作用是复制当前驱动的g_Context结构到一片pool内存中,g_Context的大小是0x1E8。
因为该读写驱动加载完即释放,所以需要把本驱动Image空间中的 g_Context结构放到不会被释放的pool内存中。
↑这个WHAT变量也是分配到一块大小0x400的pool内存中,看起来是一个类似key或者key table的东西,后面在给进程加上OB进程回调保护的时候校验调用者会用到,这里先跳过
routine = FixCode(ProcessObCallbackTemplate, 0x90u, pContext);
call [r?x+变量偏移]
mov qword ptr [r?x+变量偏移], yyyyy
具体的模板函数这里只贴两个关键的:
跨进程读写内存
和在WOW64PEB->LDR中查找模块
↓然后用ZwQuerySystemInformation功能号11 SystemModuleInformation 枚举驱动找到Fs_Rec.sys驱动,并返回他的ImageBase
FsRecImageBase = LookupDriver("Fs_Rec.sys");
找到后设置其LdrEntry的Flags字段,为了将来安装进程OB回调时绕过 MmVerifyCallbackFunction 限制
FixLdrEntry(MainDriverObject, FsRecImageBase);
再取到Fs_Rec的驱动对象,将其 IRP_MJ_CREATE、IRP_MJ_CLOSE和IRP_MJ_DEVICE_CONTROL 三个IRP派遣函数保存进g_Context并替换为自己往fs_rec.sys驱动Image空间中写入的代码MyCode,这个MyCode怎么来的下面会讲。
RtlInitUnicodeString(&DestinationString, L"\\filesystem\\Fs_Rec");
v14 = ObReferenceObjectByName(
&DestinationString,
0x240u,
0i64,
0,
IoDriverObjectType,
0,
0i64,
&FsRecDriverObject);
RtlInitUnicodeString(&DeviceName, L"\\Device\\iubesks");
RtlInitUnicodeString(&SymbolicLinkName, L"\\DosDevices\\iubesks");
v14 = IoCreateDevice(FsRecDriverObject, 0, &DeviceName, 0x22u, 0, 0, &DeviceObject);
FsRecCreate = FsRecDriverObject->MajorFunction[IRP_MJ_CREATE];
FsRecClose = FsRecDriverObject->MajorFunction[IRP_MJ_CLOSE];
FsRecIoctl = FsRecDriverObject->MajorFunction[14];
FsRecDriverObject->MajorFunction[IRP_MJ_CREATE] = MyCode;
FsRecDriverObject->MajorFunction[IRP_MJ_CLOSE] = MyCode;
FsRecDriverObject->MajorFunction[IRP_MJ_DEVICE_CONTROL] = MyCode;
现在讲MyCode是怎么来的:
↑先找到fs_rec.sys的驱动Image的.text段起始地址
↓再往.text段起始地址上用mdl方式写入字节"0x48,0xB8,8字节的函数地址,0xFF,0xE0"
翻译成汇编就是
mov rax, 函数地址
jmp rax
第一个 mov rax 的函数地址是从 ProcessObCallbackTemplate 模板复制出来的ProcessObCallback 回调
在
RegisterProcessObCallback(1, routine);
中将其安装为进程Ob回调
同理mov rax, CreateProcessNotifyRoutine和mov rax, LoadImageNotifyRoutine 也被安装
这两个回调的功能分别是:
根据游戏进程名 保存游戏进程的EPROCESS,进程名由某个IOCTL指定。
根据游戏进程名 备份游戏进程的页表(为后面读写CF内存做准备)
同理,DispatchIrp派遣函数的模板也被复制到pool并用mov rax的形式设置为Fs_Rec驱动对象的真实派遣函数
此时fs_rec.sys的.text段起始地址的代码如下:
mov rax, pool内存中的进程OB回调
jmp rax
jmp rax, pool内存中的进程创建回调
jmp rax
jmp rax, pool内存中的线程创建回调
jmp rax
jmp rax, pool内存中的DispatchIrp派遣函数
jmp rax
至此该驱动样本初始化完成,固定返回错误码 0xC0000001 强行加载失败,以让其被系统自动卸载。
下面简要分析该样本DispatchIrp派遣函数中的内容:
首先判断访问该派遣函数的设备对象是否是我们刚才创建的\Device\iubesks设备对象,如果不是,根据 MajorFunction 调用 g_pContext-> 中对应的原始函数,如FsRecDisptchCreate 、 FsRecDisptchClose、 FsRecDisptchIOCTL
并且如果 MajorFunction 不是 IRP_MJ_DEVICE_CONTROL 则直接返回成功并完成该IRP
再根据不同的 Parameters.DeviceIoControl.IoControlCode 执行不同的操作
如:IoControlCode=0x223BD8,执行g_pContext->MouseServiceCallback 模拟鼠标操作
如:IoControlCode= 0x223BE4 ,保存用户传来的进程名到自己分配的pool内存中并存到 g_pContext->process_name 中
如:IoControlCode= 0x223BE0 0x223BF4,则遍历进程的Wow64Peb->Ldr查找指定模块的ImageBase和ImageSize,具体代码如下:
IoControlCode= 0x223BE8,使用KeStackAttachProcess+MDL 方式跨进程读写内存(这里其实不用MDL也可以,直接memcpy就行了)
IoControlCode= 0x223BFC 绕过 CF/DNF PageFault 内存保护使用自己备份的页表读写内存
这里和上面正常读写方法唯一的区别就是使用了自己在LoadImage回调中备份的页表
IoControlCode= 0x223BF8,设置保护进程,打开被保护的进程时在我们刚才安装的进程OB回调中会被摘掉VM_READ VM_WRITE等权限,使其无法被(两年前的)CF游戏进程读取内存,以避开内存特征扫描
样本总结:
1、利用 DriverEntry 返回失败的 STATUS 码来让系统自动卸载,免去了 sc stop / ZwUnloadDriver 的麻烦。
2、把代码放在pool内存中以达到(游戏运行期间)无驱动(也就是内存加载)的效果
3、把各个回调入口放到了fs_rec.sys的.text可执行段中,使其在ARK工具中不易被发现,还可以躲过ntos的guardcall检查
4、自建设备对象让其可以被应用层的程序正常打开其符号链接并进行交互
5、备份了CF游戏进程的页表使其可以正常读写CF的游戏内存
6、进程OB回调保护自己的wg程序不被内存特征扫描
至此样本全部分析完毕,具体的样本利用代码这里就不放了,样本我已经放出来了,有兴趣的读者可以自己逆一下。
本文由看雪论坛 hzqst 原创
往期热门阅读:
看雪学院招募看雪讲师
[新手向]ret2dl-resolve详解
新手玩转Linux Kernel漏洞之Null Pointer Dereference
驱动调试—还原 QQ 过滤驱动对关键内核设施所做的修改(Part II)
- 搜索
- 标签列表