python RPC Fuzzer
简介
本文主要涉及有SeImpersonatePrivilege时通过pipe管道复制客户端权限的rpc提权漏洞挖掘。
先说rpc,rpc是Remote Procedure Call,windows中许多服务都会提供的rpc接口,例如uac的授权认证RaiLaunchAdminProcess,又如SvcMoveFileInheritSecurity以system权限移动文件,有不少可以被利用于bypass uac,提权,远程下载等等操作。这些函数被隐藏了起来,可通过rpcview或者ntapidotnet等反编译接口为IDL文件,获取某个rpc服务的唯一uuid,版本,和提供的接口函数以及对应的参数。
所以如果想寻找其中可以利用的函数,首先得获取idl文件,再在其中寻找可能存在漏洞的函数,寻找的方法也可以总结为
- 寻找危险函数名
如上文提到过的uac认证中的admin,任意文件移动中的security,从windows SDK中下载symbol,再在rpcview中引入后反编译,就能解析出函数的名字。或是powershell遍历注册表中的clsid,去获取对应的函数名,再寻找危险的函数
- 寻找可能存在漏洞的dll
例如用于验证身份的appinfo.dll,rpc提供的函数只有函数名,需要的参数,并不提供执行过程,所以一些逻辑错误还是需要深入dll逆向。
- fuzz参数
对所有参数尝试一些可能触发漏洞的数据,如一些溢出呀,或是一些格式化字符串漏洞呀,命令注入之类的。
本文提权原理
原理是发起pipe的服务端如果具有SeImpersonatePrivilege权限,则可以模拟连接pipe的客户端的权限,再用连接上的高权限用户生成进程,达到提权的目的。
这里涉及对文件的访问,所以我们把目标放在一些可能存在文件操作的函数上。于是我计划将获取到的函数的string参数用\\\\localhost\\pipe\\testpipe
代替,fuzz出所有访问过该路径的参数。
fuzz参考了rpc-forge,原理也就是通过数据类型,生成对应的用于fuzz的参数,编码为ndr结构,再传给函数进行反复调用。由于rpc-forge需要特定python文件的格式进行输入,而并未给生成文件的代码,秉着造轮子的心态自己写了fuzz
流程
- 解析idl生成的cs文件,使其变为可供python使用的函数集,含有uuid,版本,结构体和结构体内参数,方法和参数
用ntapidotnet遍历出可供c#调用的idl文件
https://github.com/tyranid/WindowsRpcClients
中有对应的导出方法,或直接引用他提供的
但是在c#中调用函数需要using引入,这样就没法遍历所有文件自动fuzz,得引入全部文件,所以我选择用python中的pythonforwindows来调用rpc函数,这样就需要在python中获取cs文件中rpc服务对应的uuid,版本号
https://hakril.github.io/PythonForWindows/build/html/rpc.html
为pythonforwindows的官方文档,供使用参考。
- 获取uuid,版本并尝试连接
c#的整体结构还是相对好解析,非常规整,完全可以通过{ } ;
和 ( ) ,
分别解析类和函数,再删除不必要的# //
行,可以大致将所有类,结构体,union,接口和接口中的参数进行分类,再返回一个列表,方便后面获取值。
- 根据对应类型生成参数
c#生成的idl文件中也将参数用ntapidotnet中的对应函数进行了包装,例如用WriteTerminatedString
包装String
,用WriteInt32
包装int,WriteReferent
将里面的类型转为UniquePTR
,或是自定一个Write_xx
处理一些数组、结构体或union之类的东西。通过c#中的函数得到参数对应的包装方式,再记录为pythonforwindows中对应的类型如NdrWString
,NdrLong
,NdrByte
之类的,会比从函数参数中直接翻译更加准确
还有一些参数嵌套了好几层结构体,写个解析函数在碰到结构体的时候回调解析就行。最后返回一个列表包含了所有参数的类型。
值得注意的是pythonforwindows并没有将所有类型完全解析,不能将c#所有包装函数完全对应上pythonforwindows中的类型,比如union的包装方式就没有在pythonforwindows中提供(可以看ntapidotnet中的源码,union的包装方式是将int类型的selector用NdrLong包装,再拼接上selector对应的参数包装),但作为fuzz,其实我们也不需要对所有类型完全进行处理,遇到不能处理的类型直接用byte数组代替就好。
参数生成可以直接套用rpc-forge中的generator,根据对应参数类型生成对应参数,再将参数用对应类型包装拼接,就是可供函数使用的传参了。
- 循环调用函数
写一个最大循环限制将fuzz方法拿进去循环跑就好。
- 获取结果
rpc返回的结果由out关键字指定的参数接收,其编码格式和我们发送参数的编码格式一样,通过获取out参数返回的结果进行解码可以得到调用rpc接口所返回的结果。
由于我们需要做的只是判断函数对路径的访问,所以更简单的办法是直接用procmon过滤出访问了我们指定路径的进程,一旦有访问记录,就说明函数触发了unc路径。
结果
参数生成不是很到位,会有一部分函数报传输数据不符合格式,但是不影响我们寻找这种函数。
开着procmon监视触发了testpipe目录的行为,很快就找到了目标,例如tellib.dll中的UtcApi_SnapCustomTrace触发了读取目录的行为(这个有师傅已经找到过了),又比如appxdeploymentserver.dll中的AppXApplyTrustLabelToFolder_59
这一系列rpc服务大多都是svchost调用dll,拥有system权限
找到后用c#创建一个pipe管道,再写一个rpc函数调用,然后复制权限创建进程,就得到了一个system权限的客户端