狸猫换太子----穿别人的鞋,走自己的路(另类思维实现Ring0隐藏文件)
10号的时候接了朋友一个编程方面的订单,要求从头开始写一个精简版的远程控制软件,其他功能不说了,主要是一个,要求设置文件访问权限,要可设置4类(可访问,可写,可删除,可见)
根据字面意思很容易可以理解,可访问就是是否可以读(换句话说,要是不能访问的话,当然也就不能写了),可见的话就是是否可以被看见,可删除就是是否能被删除;
那么第一个想法是使用SSDT HOOK,但是自己重头开始写一个驱动比较累,而且调试比较费时间,特别麻烦;朋友告知想要这个功能是因为网上有一款免费软件Easy File Locker可以实现这个功能,非常的方便;于是就产生了一个想法
能否逆向Easy File Locker,看看它是怎么实现的
于是下载到了这个软件,界面很简单;
支持锁定文件以及锁定文件夹,从左到右依次是可访问,可写,可删除,可见;
简单的看了下,这个程序没有加壳,也没有做什么反调试的防护措施(围观群众:靠,你刚不是说了是免费软件吗,还做个屁的反调试呀)
那么就从这个OK的按钮事件开始逆向之旅吧;找按钮事件之类的就不多说了,相信Team里的好基友们都比我更强大;
经过一段跟踪之后,来到了00410C35函数,之所以锁定到这里是因为…我一路F8,看看哪个CALL以后,文件被锁定了,那么就F7进去,如此反复,最终发现是00410C35里面的2个API函数FLTLIB.FilterConnectCommunicationPort和FLTLIB.FilterSendMessage实现了锁定文件的功能(啥,2个API函数就能实现锁定文件,这不可能吧)
百度一下这两个函数的资料,看看是做什么用的,
http://technet.microsoft.com/zh-cn/ff540460(v=vs.85)
介绍是: FilterConnectCommunicationPort opens a new connection to a communication server port that is created by a file system minifilter.
英文果断看不懂,百度翻译一下
FilterConnectCommunicationPort 开辟了一个新的连接到通信服务器端口,通过文件系统创建的新框架。
虽然…百度翻译翻译出来的东西中国人和外国人都看不太懂,但是大致上我们还是能够了解了, FilterConnectCommunicationPort开辟一个新的连接到通信服务器端口;那么不难理解FilterSendMessage就是通过这个通信服务器端口传输数据了;来了解下参数吧:
HRESULT FilterConnectCommunicationPort(
_In_ LPCWSTR lpPortName, // "\XlkfsPort"
_In_ DWORD dwOptions, // NULL
_In_opt_ LPCVOID lpContext, // NULL
_In_ WORD dwSizeOfContext, // NULL
_In_opt_ LPSECURITY_ATTRIBUTES lpSecurityAttributes, // NULL
_Out_ HANDLE *hPort // 返回句柄的缓冲区指针
);
HRESULT FilterSendMessage(
_In_ HANDLE hPort, // 通信端口句柄
_In_opt_ LPVOID lpInBuffer, // 输入缓冲区
_In_ DWORD dwInBufferSize, // 输入缓冲区大小
_Out_ LPVOID lpOutBuffer, // 输出缓冲区 (可以为NULL)
_In_ DWORD dwOutBufferSize, // 输出缓冲区大小 (可以为NULL)
_Out_ LPDWORD lpBytesReturned // 实际写入数据
);
那么FilterConnectCommunicationPort函数我们显然没有必要去详细了解,只要知道它的lpPortName参数就可以了,因为它就是一个连接函数,换句话说,如果我们要关注文件操作,那么我们应该关注WriteFile和ReadFile,对于CreateFile,我们只要关心FileName就OK了;
那么在FilterSendMessage的FF25位置下断,发现这个函数从开始执行到retn,这个函数共被调用了2次,2次的数据分别是:
第一次(由于该文件的格式都是 tmpXXX.tmp~1,所以将其命名为 tmp0_1)
009847C8 58 66 73 53 03 00 00 00 5C 00 3F 00 3F 00 5C 00 XfsS
...\.?.?.\.
009847D8 43 00 3A 00 5C 00 44 00 4F 00 43 00 55 00 4D 00 C.:.\.D.O.C.U.M.
009847E8 45 00 7E 00 31 00 5C 00 41 00 44 00 4D 00 49 00 E.~.1.\.A.D.M.I.
009847F8 4E 00 49 00 7E 00 31 00 5C 00 4C 00 4F 00 43 00 N.I.~.1.\.L.O.C.
00984808 41 00 4C 00 53 00 7E 00 31 00 5C 00 54 00 65 00 A.L.S.~.1.\.T.e.
00984818 6D 00 70 00 5C 00 74 00 6D 00 70 00 37 00 45 00 m.p.\.t.m.p.7.E.
00984828 2E 00 74 00 6D 00 70 00 7E 00 31 00 00 00 00 00 ..t.m.p.~.1.....
第二次(由于该文件的格式都是 tmpXXX.tmp~0,所以将其命名为 tmp0_0)
009847C8 58 66 73 53 05 00 00 00 5C 00 3F 00 3F 00 5C 00 XfsS...\.?.?.\.
009847D8 43 00 3A 00 5C 00 44 00 4F 00 43 00 55 00 4D 00 C.:.\.D.O.C.U.M.
009847E8 45 00 7E 00 31 00 5C 00 41 00 44 00 4D 00 49 00 E.~.1.\.A.D.M.I.
009847F8 4E 00 49 00 7E 00 31 00 5C 00 4C 00 4F 00 43 00 N.I.~.1.\.L.O.C.
00984808 41 00 4C 00 53 00 7E 00 31 00 5C 00 54 00 65 00 A.L.S.~.1.\.T.e.
00984818 6D 00 70 00 5C 00 74 00 6D 00 70 00 32 00 30 00 m.p.\.t.m.p.2.0.
00984828 46 00 2E 00 74 00 6D 00 70 00 7E 00 30 00 00 00 F...t.m.p.~.0...
那么这里显然是传输了一个临时文件的名称过去,给谁呢?当然是给驱动了;那么我们简单看下这两个tmp,发现这两个tmp里面出现了我们要求锁定的文件的名称,以及一些其他的数据,来做个小实验:
把这个文件名称修改一下,发现被锁定的文件就变了,那么我们大致可以猜出这个程序的结构了:
Ring3程序从用户这里得到了一些信息,比如”需锁定文件的路径”,”要锁定成什么属性”,”是锁定文件还是锁定文件夹”等;然后组合成一个tmp文件,接着把这个tmp文件的路径传输到Ring0下,由Ring0内核模块去实现锁定文件的功能;
那么,嘿嘿,邪恶的我们就考虑,能否分析出这2个tmp的数据结构,由此自己构造2个tmp,然后用FilterConnectCommunicationPort和FilterSendMessage传输给其内核模块,由此来实现一开始说的”狸猫换太子”呢;呵呵,当然是可以的;因为我已经做好了;
具体分析相信各位都会,这里就贴一段分析结果吧:
首先是tmp0_1的数据结构
typedef struct _DATA
{
DWORD Count; // 固定为1
BYTE[80] PathVoluemeName; // Easy File Locker所在磁盘的盘符,通过GetVolumeNameForVolumeMountPoint可以获得,注
意,这里是UNICODE字符串
DWORD VolueNameCheck; // 将VolumeName转换为大写,比如{0171E958-DB33-11E2-8658-806D6172696F},然后累加每个
字符的ASCII值,比如:0x089D
DWORD Zero; // 固定为0
DWORD Zero; // 固定为0
DWORD Zero; // 固定为0
DWORD RelativeRouteEndOffset; // (RelativeRoute数据结束位置)相对于(数据起始地址)的偏移
// 计算方法:strlenW(RelativeRoute) + 0x64(RelativeRoute前所有数据的大小) + 0x02
(RelativeRoute的结束符长度,2个0x00)
BYTE[?] RelativeRoute; // Easy File Locker的相对路径 + \*,但是注意必须全部是大写的,且是UNICODE字符串
// 比如Easy File Locker存放在C:\AAAA下,那么该值就是 UNICODE"\AAAA\*"
WORD strEnd; // 0x0000,UNICODE字符串结束符
BYTE[96] Zero; // 0x60(96)个字节的0x00
DWORD RecyleBinEndOffset; // (RecycleBin数据结束位置)相对于(RelativeRoute的结束位置)的偏移;
// 计算方法:2 * strlenW(RecycleBin) + 0x02(结束符长度) + 0x60(Zero数据的长度) +
0x04(RecyleBinEndOffset的长度);
BYTE[?] RecycleBin; // 固定为UNICODE"\$RECYCLE.BIN\*"
WORD strEnd; // 0x0000,UNICODE字符串结束符
BYTE[96] Zero; // 0x60(96)个字节的0x00
DWORD Buffer3EndOffset; // (Buffer3数据结束位置)相对于(RecycleBin的结束位置)的偏移
// 计算方法:2 * strlenW(Buffer3) + 0x02(结束符长度) + 0x60(Zero数据的长度) +
0x04(Buffer3EndOffset的长度);
BYTE[?] Buffer3; // 固定为UNICODE"\RECYCLER\*",具体含义不明
WORD strEnd; // 0x0000,UNICODE字符串结束符
BYTE[96] Zero; // 0x60(96)个字节的0x00
DWORD Buffer4EndOffset; // (Buffer4数据结束位置)相对于(Buffer3的结束位置)的偏移
// 计算方法:2 * strlenW(Buffer4) + 0x02(结束符长度) + 0x60(Zero数据的长度) +
0x04(Buffer4EndOffset的长度);
BYTE[?] Buffer4; // 固定为UNICODE"\SYSTEM VOLUME INFORMATION\*",具体含义不明
WORD strEnd; // 0x00,UNICODE字符串结束符
BYTE[80] PathVoluemeName1; // 要锁定的文件所在磁盘盘符,通过GetVolumeNameForVolumeMountPoint可以获得,注意,这
里是UNICODE字符串
DWORD VolueName1Check; // 将VolumeName1转换为大写,比如{0171E958-DB33-11E2-8658-806D6172696F},然后累加每
个字符的ASCII值,比如:0x089D
DWORD Zero; // 固定为0
DWORD Zero; // 固定为0
DWORD Access; // 实际上只要4个二进制位就可以了,也就是半个字节,4个二进制位分别代表:可删除 可写
可见 可访问,0表示勾选,1表示未勾选
DWORD FileNameEndOffset; // (FileName数据结束位置)相对于(Buffer4数据结束位置)的偏移
// 计算方法:strlenW(FileName)*2 + 2 + 0x50(PathVoluemeName1的长度) + 5 * 4(5个
DWORD的长度)
BYTE[?] FileName; // 欲锁定文件的相对路径,比如要锁定E:\yy\Delphi7例子.exe,那么该值为
UNICODE"\YY\DELPHI7例子.EXE"
}DATA,*PDATA;
然后是tmp0_0的数据结构:
[Common]
Count = Locker File Count; // 被锁定的文件总数量
[x] // 第x个文件,从0开始计数
Path = Lock File Path
Type = BOOL(File = 0, Folder = 1)
Access = 可删除 可写 可见 可访问
1 0 1 0
那么我们只要根据这个结构,来构造出2个tmp数据,接着传输给Ring0即可;
接着的话是怎么加载它的驱动了,因为我们知道,它主要的实现是驱动代码,那么我们要用FilterConnectCommunicationPort和FilterSendMessage的话,首先是要加载它的驱动,否则一切免谈啦;
刚开始以为是个NT式驱动,拿加载器搞了半天,全部失败..纠结死我了,后来又分析了下它的安装程序做的事情,才发现是个WDM驱动,在安装的时候会在Temp文件夹下生成一个xlkfs.inf,这个inf就是驱动程序的安装inf;
仅仅有这个inf还不够,经调试分析,发现在右键inf->安装之前,还需要在注册表添加2项内容,一项是Ring3下就可以添加,另一项却要Ring0的权限,因为第二项所在位置是锁定权限的;平时只可读,不可写;那么百度了一下相关修改权限的代码,终于找到了,呵呵,具体的请看源码
大致流程是:根据用户的操作(Lock or UnLock),以及参数(Path, Access)来生成一个tmp0_0文件,然后根据这个tmp0_0来生成tmp0_1文件,接着连接驱动
tmp0_0(实际上是一个ini文件)的生成按如下流程:
(1).首先复制C:\WINDOWS\XLKFS.INI到任意一个磁盘暂存(我是复制到C:\下)
(2).取出其Common主项下的Count子项(该子项表示目前已锁定文件的总数量)
(3).如果是锁定文件,则根据Count的值来添加Access,Type,Path三个属性
(4).如果是解锁文件,则根据欲解锁文件的Path值来遍历ini文件,删除与其对应的项
接着按照tmp0_0来生成一份tmp0_1,具体结构在源码及文章中都有写
请注意:源码的OnInitDialog函数中包含了加载驱动的代码,如果测试完毕后需要卸载,请安装Easy File Locker,然后执行其卸载程序,源码中并不包含卸载代码!
201310-狸猫换太子-穿别人的鞋,走自己的路-Spider[4st TeAm]