首页
社区
课程
招聘
[原创]某被外挂用烂了的读写驱动样本全逆向+功能分析
发表于: 2018-6-8 15:54 43278

[原创]某被外挂用烂了的读写驱动样本全逆向+功能分析

2018-6-8 15:54
43278
该样本于去年6月份绝地求生wg泛滥时搜集到,根据pdb路径(ReadCF\x64\Win7Release\ReadCF.pdb)推测该样本至少2016年PUBG没火时就已经出现而且当时是针对CF写的,直到2018年5月居然还有人把该样本用在自己的绝地求生wg上,曾经用过该样本的wg多达几十款(真的)。涉及各种原因这里不细说了,我们只谈技术:





先从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 = AllocCode(&unk_140005140, 0x400u);
↑这个WHAT变量也是分配到一块大小0x400的pool内存中,看起来是一个类似key或者key table的东西,后面在给进程加上OB进程回调保护的时候校验调用者会用到,这里先跳过
WHAT = AllocCode(&unk_140005140, 0x400u);
↑这个WHAT变量也是分配到一块大小0x400的pool内存中,看起来是一个类似key或者key table的东西,后面在给进程加上OB进程回调保护的时候校验调用者会用到,这里先跳过

 qword_140006148 = FixCode(ReadWriteProcessMemoryTemplate, 0x250u, pContext);
          qword_140006150 = FixCode(FindModuleByNameTemplate, 0x350u, pContext);
          qword_140006138 = FixCode(sub_14000432C, 0x100u, pContext);
          qword_140006140 = FixCode(sub_1400041F0, 0x100u, pContext);
          qword_140006188 = FixCode(NewWPONTemplate, 0x50u, pContext);
          qword_140006180 = FixCode(NewWPOFFTemplate, 0x50u, pContext);
          qword_140006190 = FixCode(BypassTesvboxReadWriteProcessMemoryTemplate, 0x280u, pContext);
          qword_140006198 = FixCode(BackupPageTableTemplate, 0xB0u, pContext);
          qword_1400061A0 = FixCode(sub_1400039CC, 0x350u, pContext);
          routine = FixCode(ProcessObCallbackTemplate, 0x90u, pContext);

↑这里把一些“ 模板函数 ”的代码复制到pool内存中,“模板函数”的意思是先用正常代码写对应的功能,再把里面可能出现的引用全局变量、调用函数替换为引用g_Context中的函数,比如PsGetCurrentProcess()替换为 g_pContext->PsGetCurrentProcess()。令  g_pContext = 0x3737373737373737i64,在FixCode中讲模板中出现的所有 0x3737373737373737i64 替换为之前 MakeContext中分配的pool内存的 g_Context。
 qword_140006148 = FixCode(ReadWriteProcessMemoryTemplate, 0x250u, pContext);
          qword_140006150 = FixCode(FindModuleByNameTemplate, 0x350u, pContext);
          qword_140006138 = FixCode(sub_14000432C, 0x100u, pContext);
          qword_140006140 = FixCode(sub_1400041F0, 0x100u, pContext);
          qword_140006188 = FixCode(NewWPONTemplate, 0x50u, pContext);
          qword_140006180 = FixCode(NewWPOFFTemplate, 0x50u, pContext);
          qword_140006190 = FixCode(BypassTesvboxReadWriteProcessMemoryTemplate, 0x280u, pContext);
          qword_140006198 = FixCode(BackupPageTableTemplate, 0xB0u, pContext);
          qword_1400061A0 = FixCode(sub_1400039CC, 0x350u, pContext);
          routine = FixCode(ProcessObCallbackTemplate, 0x90u, pContext);

↑这里把一些“ 模板函数 ”的代码复制到pool内存中,“模板函数”的意思是先用正常代码写对应的功能,再把里面可能出现的引用全局变量、调用函数替换为引用g_Context中的函数,比如PsGetCurrentProcess()替换为 g_pContext->PsGetCurrentProcess()。令  g_pContext = 0x3737373737373737i64,在FixCode中讲模板中出现的所有 0x3737373737373737i64 替换为之前 MakeContext中分配的pool内存的 g_Context。

这么做的理由在于: g_pContext = 0x3737373737373737i64之后访问 g_pContext->中的成员变量,会被编译为
mov r?x, 3737373737373737h
call [r?x+变量偏移]
mov qword ptr [r?x+变量偏移], yyyyy
的形式,其中r?x代表rax rbx rcx之类的64位通用寄存器,这样编译出来的代码只需要无脑替换掉里面的 0x3737373737373737即可正常使用,而不会出现64位汇编跨4GB地址访问用4字节偏移访问不了的尴尬情况(64位下 E8 call, E9 jmp都只能跳转4GB以内地址)。
mov r?x, 3737373737373737h
call [r?x+变量偏移]
mov qword ptr [r?x+变量偏移], yyyyy
的形式,其中r?x代表rax rbx rcx之类的64位通用寄存器,这样编译出来的代码只需要无脑替换掉里面的 0x3737373737373737即可正常使用,而不会出现64位汇编跨4GB地址访问用4字节偏移访问不了的尴尬情况(64位下 E8 call, E9 jmp都只能跳转4GB以内地址)。



具体的模板函数这里只贴两个关键的:
跨进程读写内存
和在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程序不被内存特征扫描

至此样本全部分析完毕,具体的样本利用代码这里就不放了,样本我已经放出来了,有兴趣的读者可以自己逆一下。
FsRecImageBase = LookupDriver("Fs_Rec.sys");

找到后设置其LdrEntry的Flags字段 为了将来安装进程OB回调时绕过MmVerifyCallbackFunction限制
FixLdrEntry(MainDriverObject, FsRecImageBase);

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(&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);

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];

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;

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回调
mov rax, 函数地址
jmp rax
第一个mov rax的函数地址是从ProcessObCallbackTemplate模板复制出来的 ProcessObCallback回调

[注意]传递专业知识、拓宽行业人脉——看雪讲师团队等你加入!

最后于 2018-6-8 16:36 被hzqst编辑 ,原因: 重新传了下样本,把分析的idb也带上了
上传的附件:
收藏
免费 16
支持
分享
打赏 + 6.00雪花
打赏次数 2 雪花 + 6.00
 
赞赏  一位没有留下痕迹的看雪读者   +5.00 2018/09/06
赞赏  killpy   +1.00 2018/06/14
最新回复 (73)
雪    币: 24
活跃值: (1353)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
2
好帖
2018-6-8 16:22
0
雪    币: 3738
活跃值: (3872)
能力值: ( LV4,RANK:50 )
在线值:
发帖
回帖
粉丝
3
感谢大表哥分享!
2018-6-8 16:22
0
雪    币: 47
活跃值: (15)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
4
感谢大表哥分享!
2018-6-8 16:46
0
雪    币: 665
活跃值: (1051)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
5
可惜还是拉闸..
2018-6-8 17:00
0
雪    币: 12848
活跃值: (9147)
能力值: ( LV9,RANK:280 )
在线值:
发帖
回帖
粉丝
6
qdjytony 可惜还是拉闸..
代理表示:拉闸?不存在的。我们百万驱动+引擎绘制+独立特征一人一马你蓝洞拿头检测?封号都是人工检测或者同行举报,蓝洞临时工7天24小时64块4k大屏幕人肉分析把你们安排上了 
2018-6-8 17:07
0
雪    币: 940
活跃值: (1053)
能力值: ( LV3,RANK:20 )
在线值:
发帖
回帖
粉丝
7
怎么搜的PDB?
2018-6-8 17:20
0
雪    币: 248
活跃值: (3789)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
8
分析得很到位,其实读写驱动套路都差不多,剩下的就是体力活了
2018-6-8 17:38
0
雪    币: 248
活跃值: (3789)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
9
hzqst 代理表示:拉闸?不存在的。我们百万驱动+引擎绘制+独立特征一人一马你蓝洞拿头检测?封号都是人工检测或者同行举报,蓝洞临时工7天24小时64块4k大屏幕人肉分析把你们安排上了  ...
独立特征一人一马+在线云更,确实拿不到特征
2018-6-8 18:09
0
雪    币: 433
活跃值: (1910)
能力值: ( LV4,RANK:40 )
在线值:
发帖
回帖
粉丝
10
hzqst 代理表示:拉闸?不存在的。我们百万驱动+引擎绘制+独立特征一人一马你蓝洞拿头检测?封号都是人工检测或者同行举报,蓝洞临时工7天24小时64块4k大屏幕人肉分析把你们安排上了  ...
666
2018-6-8 18:09
0
雪    币: 204
活跃值: (911)
能力值: (RANK:1324 )
在线值:
发帖
回帖
粉丝
11
感谢分享,学习
2018-6-8 18:28
0
雪    币: 144
活跃值: (335)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
12
好贴  支持下
2018-6-8 19:26
0
雪    币: 300
活跃值: (2477)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
13
感谢分享,学习
2018-6-8 21:07
0
雪    币: 3
活跃值: (202)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
14
感谢分享,学习 
2018-6-8 22:51
0
雪    币: 368
活跃值: (431)
能力值: ( LV2,RANK:140 )
在线值:
发帖
回帖
粉丝
15
感谢楼主分享
最后于 2018-6-9 01:58 被又出bug了编辑 ,原因:
2018-6-9 01:38
0
雪    币: 665
活跃值: (1051)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
16
你又皮了,我的黄
2018-6-9 05:55
0
雪    币: 665
活跃值: (1051)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
17
yy虫子yy 独立特征一人一马+在线云更,确实拿不到特征
全自动样本分析平台了解下,我发图给大表哥来着
最后于 2018-6-9 05:57 被qdjytony编辑 ,原因:
2018-6-9 05:56
0
雪    币: 5676
活跃值: (1303)
能力值: ( LV17,RANK:1185 )
在线值:
发帖
回帖
粉丝
18
666膜拜
2018-6-9 07:57
0
雪    币: 14
活跃值: (66)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
19
666真是厉害
2018-6-9 09:22
0
雪    币: 257
活跃值: (11)
能力值: ( LV4,RANK:50 )
在线值:
发帖
回帖
粉丝
20
使其无法被(两年前的)CF游戏进程读取内存

括起两年前是什么意思啊,
是对现在的不管用呢?
2018-6-9 09:33
0
雪    币: 12848
活跃值: (9147)
能力值: ( LV9,RANK:280 )
在线值:
发帖
回帖
粉丝
21
ViperDodge 使其无法被(两年前的)CF游戏进程读取内存 括起两年前是什么意思啊, 是对现在的不管用呢?
现在肯定不行了啊,这个样本鹅厂16年就给安排得明明白白了
2018-6-9 09:45
0
雪    币: 711
活跃值: (253)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
22
厉害了,我刚刚了解驱动,刚刚在想可以直接用驱动写,结果人家2年前都已经使用了。。。。
2018-6-9 10:02
0
雪    币: 22
活跃值: (443)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
23
确实厉害~
2018-6-9 10:33
0
雪    币: 58
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
24
驱动不加vmp壳也是心大,
要我弄的话,先将sys驱动文件膨胀到1MB那么大.
2018-6-9 11:50
0
雪    币: 58
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
25
现在都是x64的驱动了,不知道都有些什么套路了...
2018-6-9 11:52
0
游客
登录 | 注册 方可回帖
返回
//