meterpreter 简要分析
2022-10-23 #python #c2 #ratmeterpreter 是 metasploit 里自带的一款远程控制工具,具有正向/反向连接模式,并且功能强大,所以想分析下其实现。
- 2022-10-23 初版博文发布
- 2023-01-21 加入端口转发功能的实现分析
获取代码
meterpreter 的本体有很多种语言的实现,包括 C 实现的 mettle,还有在 metasploit-payloads 仓库里的 Python/PHP/Java 实现。这里选择 Python 实现。并且由于代码块中存在很多等待 Patch 的地方,所以这里直接用 msfvenom 生成一个。
简要分析
分析前准备
在代码中有开启日志的选项,并且还有去除启动时 fork
的选项,为了调试,把这两个选项修改成如下
# these values will be patched, DO NOT CHANGE THEM
= True
= None
= False
接下来启动就可以看到相关的日志了。
~/d/t/meterpreter > python3 download.py
download.py:1713: DeprecationWarning: the imp module is deprecated in favour of importlib and slated for removal in Python 3.12; see the module's documentation for alternative uses
import codecs,imp,base64,zlib
DEBUG:root:[*] running method core_negotiate_tlv_encryption
DEBUG:root:[*] Negotiating TLV encryption
DEBUG:root:[*] RSA key: 30820122300d06092a864886f70d01010105000382010f003082010a02820101009c3090c560a4e0b23b9b2141a48c6cbd42a67bde619931791e76b1a758761e061f2f50cfffe4c6eb47d2d15f07b2b0aed95c7c084d9d6b158e332126efbb4cebefc39979ca7a76fdba1291861e070b669f6febb795ada48b20e84356a6bb3daf7f74b179124ec87b08291e2a8e9f414a27b4e7dbbe3027a8d720cb46f6837f90747982c5b8cfa1ae8ee305203764b606f027da70a83f11a850cd6a4f4052eb85574c934ef5d50455b5cc822a23f0a9e6c44a305bf197216531ad6ac18adf1aa33042441a1fce964ed57da2ad6075d13787f4f9d17e69a35eb0a6fe10872e89eeb230bc0a15183618990933e786b952dd4783d299fd53e147421d442225f4d1470203010001
DEBUG:root:[*] AES key: 0x6ea0dd43076cdf8f560b094816af9fa8daa9bb8e15016f10370c69654970dd20
DEBUG:root:[*] Encrypted AES key: 0x676a4db54a6852f7c50566aceebd4c12f532ae763a9038e8c8c4a8f3f868f03cfd8b264aae861997dfa0eb4127edf2496ba252acfea0ad08f89e9e65ac196dea6b99b5af6982bfcbf77b9f59ef7bf8b59271ece5dc91209055ac9985361edbcd36fcedea212c0bf07adf36d319dfcc040b9a6869695c185a078466f8640f2581135c10ea8aeb2ec13053d5a89ab4b8388cf5df0b2e39671f885188469d5c776a9dded0277017e1bb693965d7eec66ade6df7597206544ad72a5c4ebc199275f40d02e5d270968e4a586946e35f51752a7d267d5e4df783a61fb6a21290f06d2879eec42d30de7e9550d54e73a11d42e42538131c90b7e7a60b6dc751884bb94e
DEBUG:root:[*] TLV encryption sorted
DEBUG:root:[*] sending response packet
DEBUG:root:[*] running method core_set_session_guid
DEBUG:root:[*] sending response packet
DEBUG:root:[*] running method core_enumextcmd
DEBUG:root:[*] sending response packet
DEBUG:root:[*] running method core_enumextcmd
DEBUG:root:[*] sending response packet
DEBUG:root:[*] running method core_loadlib
DEBUG:root:[*] sending response packet
DEBUG:root:[*] running method stdapi_fs_getwd
DEBUG:root:[*] sending response packet
DEBUG:root:[*] running method stdapi_sys_config_getuid
DEBUG:root:[*] sending response packet
DEBUG:root:[*] running method stdapi_sys_config_sysinfo
DEBUG:root:[*] sending response packet
DEBUG:root:[*] running method core_set_uuid
DEBUG:root:[*] sending response packet
DEBUG:root:[*] running method stdapi_net_config_get_interfaces
DEBUG:root:[*] sending response packet
DEBUG:root:[*] running method stdapi_net_config_get_routes
DEBUG:root:[*] sending response packet
DEBUG:root:[*] running method core_channel_eof
DEBUG:root:[-] method core_channel_eof resulted in error: #1
DEBUG:root:[*] running method stdapi_fs_getwd
DEBUG:root:[*] sending response packet
DEBUG:root:[*] running method stdapi_fs_stat
DEBUG:root:[*] sending response packet
DEBUG:root:[*] running method stdapi_fs_ls
DEBUG:root:[*] sending response packet
协议部分
在接收数据时,程序主要是通过 Transport
类进行通信,并对通信数据的解密,得到的二进制数据通过 packet_get_tlv
函数进行解析。在返回数据时,则是通过 tlv_pack
函数将 TLV Packet 转换成二进制数据,并通过 Transport
类对数据进行加密通信。
Transport 类
程序会通过 Transport
类进行通信,这个类会对通信数据使用 xor/AES 进行混淆。而 TcpTransport
/HttpTransport
通过多态,重载 _send_packet
和 _get_packet
方法,来实现在不同的底层协议下的通信。这个协议前 4 字节是 XOR 的密钥,然后后 4 字节是这个 Packet 的长度,通过 XOR 解密长度后,继续从 socket 中读取,就可以取得整个 Packet 的数据。
# ...省略别的方法...
=
= False
return
=
# remote is closed
= True
return None
# 省略设置超时的代码
pass
return None
=
# XOR the whole header first
=
# Extract just the length
=
-= 8
# Read the rest of the packet
=
+=
# return the whole packet, as it's decoded separately
return +
上面重载的 2 个方法都是私有成员,所以在别的类中,调用 get_packet
才能获取 Packet 数据。在 get_packet
中,则是先调用 _get_packet
函数获得数据,然后调用 decrypt_packet
进行解密。
# ...
=
=
= +
=
=
=
return
return
return None
= False
=
return None
return None
=
return
TLV 协议
meterpreter 使用的是 TLV 协议进行通信,整个 Packet 由 3 部分组成,前 4 个字节是 Packet 长度,后 4 个字节是 Packet 类型,剩下的字节则是数据。下面是解析数据的代码。将原始 Packet 数据传递给 packet_get_tlv
函数,返回值是包含解析后数据的 dict。
= 0
=
=
=
=
=
=
pass
yield
+=
return
=
return
return
需要注意的是类型(Type)字段则是由不同 meta-type 组成的,包括 TLV_META_TYPE_STRING
TLV_META_TYPE_UINT
等类型。而在程序通信时真正会用到的类型,则是由 meta-type 和 identifier 组成。这样做的目的是可以让程序对类型进行校验。可以看到下面的代码中,TLV_TYPE_CHANNEL_ID
是由 TLV_META_TYPE_UINT
和标识 50 组成的。
# ...
=
# ...
= | 50
开始部分
下面是代码最先运行的部分,可以看到是进行了 fork
,并 setsid
让程序运行在后台,然后就是使用 socket 监听 4444 端口。收到连接时,使用 TcpTransport
建立一个 Transport
之后进入 PythonMeterpreter
进行主要交互逻辑。
= and
pass
# ...
=
=
, =
=
# PATCH-SETUP-TRANSPORTS #
PythonMeterpreter
这个类是真正负责逻辑交互的类。主要循环在 run
函数中,函数主要是获取 TLV Packet 数据,然后在 create_response
函数中解析数据,调用相关的处理函数,获得返回值,最后发送回复。然后是遍历 channel 读取和发送数据,从 channel 中读取数据后,发送给控制端。channel 的设计在后文中会讲解。
=
=
# iterate over the keys because self.channels could be modified if one is closed
=
=
=
=
# ...
pass
# ...
MeterpreterChannel
Channel 是 meterpreter 运行程序,开启端口转发等功能时,进行交互的“通道”,这样的设计可以让其同时运行不同的功能,而不阻塞和用户之间的交互。在下面的代码中可以看到,Channel 和 meterpreter 之间的读写,也是通过 TLV 协议进行交互的。
不同功能的 Channel 通过重载 MeterpreterChannel
的 read
和 write
等方法,实现不同的交互。
# ...
=
+=
return ,
=
+=
return ,
# ...
return
return
core 和 stdapi
core 是 meterpreter 的基础功能,包括 channel 和 transport 的管理等功能。这些功能的实现是在 PythonMeterpreter
类中以 "_core" 开头的函数,在类初始化时加入到 extension_functions
dict 中供后续调用。而 stdapi 则是平时用到的扩展功能,包括上传/下载文件等功能,通过 core_loadlib
功能进行动态载入。
功能分析
端口转发
开启端口转发后,访问端口可以看到下面的日志,配合日志可以对代码进行分析。
DEBUG:root:[*] running method core_channel_open
DEBUG:root:[*] core_channel_open dispatching to handler: channel_open_stdapi_net_tcp_client
DEBUG:root:[*] added channel id: 2 type: MeterpreterSocketTCPClient
DEBUG:root:[*] sending response packet
DEBUG:root:[*] running method core_channel_write
DEBUG:root:[*] sending response packet
DEBUG:root:[*] running method stdapi_net_socket_tcp_shutdown
DEBUG:root:[*] sending response packet
可以看出先通过 channel_open_stdapi_net_tcp_client
函数创建链接。主要流程就是从请求中获取 socket 连接信息 peer_address_info
,还有可选的 local_address_info
,然后尝试去连接,如果连接成功的话,就创建新的 MeterpreterSocketTCPClient
类型的 channel,在创建成功后返回给控制端相应的信息。
=
, =
return ,
= False
=
# ...
= True
break
pass
return ,
=
+=
+=
return ,
在相应的 channel 创建成功后,下一条指令就是 "core_channel_write",往之前创建的 socket 对应的 channel 里写入数据后,主循环会遍历 channel 列表,如果 channel 是 MeterpreterSocketTCPClient
就尝试从中读取数据并返回。需要注意的是在收发数据的时候对 fd 使用了 select
函数等待读事件,并设置了超时时间,防止单个 channel 长时间阻塞主进程。
=
=
# ...
# iterate over the keys because self.channels could be modified if one is closed
=
=
=
=
=
=
break
+=
# ...
总结
这里只是简单的过了一遍 meterpreter Python 实现的主体代码。加密密钥的传输,stdapi 的添加等细节则是没有提到,等后面有空了可能就会写了(