内网双网卡定位
在实战过程中,当我们拿到了相关边界机器权限后,可以利用漏洞打下几台C段机器。注意尽量不要使用fscan的工具去扫描,特征流量较大,在高阶段的攻防对抗中,蓝队在第一时间会将机器关闭。
防止对方不讲武德关掉机器,导致权限丢失。
做好相关权限维持后的工作后, 开始定位双网卡。
在ABC_123师傅的文章中提到,有些攻击队在攻击时有目标的进入工控系统,拿到靶标权限。而多数的攻击队是在喷洒横向时,偶然进入到工控段中拿到靶标权限。这里就涉及到双网卡的判断。
第一时间定位到双网卡机器,然后进入其他的段进行横向变得较为重要。
对于这个问题倾旋师傅在2020-7-16的文章中提到了相关的研究。(通过OXID解析器获取Windows远程主机上网卡地址)
https://payloads.online/archivers/2020/07/16/57d63e22-adee-406a-b76e-c93f9a6129e3
背景
Nicolas Delhaye在AIRBUS上分享了一篇The OXID Resolver [Part 1] – Remote enumeration of network interfaces without any authenticationhttps://airbus-cyber-security.com/the-oxid-resolver-part-1-remote-enumeration-of-network-interfaces-without-any-authentication/),通过这篇文章我们可以掌握通过Windows的一些DCOM接口进行网卡进行信息枚举,它最大的魅力在于无需认证,只要目标的135端口开放即可获得信息。
Resolver - 交互过程分析
OXID Resolver是在支持COM +的每台计算机上运行的服务。
它执行两项重要职责:
-
它存储与远程对象连接所需的RPC字符串绑定,并将其提供给本地客户端。
-
它将ping消息发送到本地计算机具有客户端的远程对象,并接收在本地计算机上运行的对象的ping消息。OXID解析器的此方面支持COM +垃圾回收机制。
Nicolas Delhaye在原文提供的脚本是需要依赖imapcket的,而我只关注在Socket RAW上的实现,这样能够减小工具的体积,并且其他语言也能够轻松复刻整个过程。
这个协议Wireshark已经内置了,我们可以直接进行抓包分析。
前三个不需要关注,主要是TCP的三次握手,后面的四次交互才是我们需要重点关注的。
第一个数据包 72 Bytes (主要用于协商版本等等):
\x05\x00\x0b\x03\x10\x00\x00\x00\x48\x00\x00\x00\x01\x00\x00\x00\xb8\x10\xb8\x10\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x01\x00\xc4\xfe\xfc\x99\x60\x52\x1b\x10\xbb\xcb\x00\xaa\x00\x21\x34\x7a\x00\x00\x00\x00\x04\x5d\x88\x8a\xeb\x1c\xc9\x11\x9f\xe8\x08\x00\x2b\x10\x48\x60\x02\x00\x00\x00
第二个数据包:
这个包无需关注,因为我们最终要获得的是第四个数据包。
"\x05\x00\x0c\x03\x10\x00\x00\x00\x3c\x00\x00\x00\x01\x00\x00\x00" \
"\xb8\x10\xb8\x10\x0a\x13\x00\x00\x04\x00\x31\x33\x35\x00\x00\x00" \
"\x01\x00\x00\x00\x00\x00\x00\x00\x04\x5d\x88\x8a\xeb\x1c\xc9\x11" \
"\x9f\xe8\x08\x00\x2b\x10\x48\x60\x02\x00\x00\x00"
可以选中对应的节点,直接复制... as Escaped String
,这样就能够拿到十六进制Code。
第三个数据包:
"\x05\x00\x00\x03\x10\x00\x00\x00\x18\x00\x00\x00\x01\x00\x00\x00" \
"\x00\x00\x00\x00\x00\x00\x05\x00"
第四个数据包:
"\x05\x00\x02\x03\x10\x00\x00\x00\xec\x00\x00\x00\x01\x00\x00\x00" \
"\xd4\x00\x00\x00\x00\x00\x00\x00\x05\x00\x07\x00\x00\x00\x02\x00" \
"\x5d\x00\x00\x00\x5d\x00\x47\x00\x07\x00\x44\x00\x45\x00\x53\x00" \
"\x4b\x00\x54\x00\x4f\x00\x50\x00\x2d\x00\x41\x00\x44\x00\x47\x00" \
"\x33\x00\x33\x00\x31\x00\x32\x00\x00\x00\x07\x00\x31\x00\x39\x00" \
"\x32\x00\x2e\x00\x31\x00\x36\x00\x38\x00\x2e\x00\x38\x00\x30\x00" \
"\x2e\x00\x31\x00\x00\x00\x07\x00\x31\x00\x39\x00\x32\x00\x2e\x00" \
"\x31\x00\x36\x00\x38\x00\x2e\x00\x32\x00\x30\x00\x31\x00\x2e\x00" \
"\x31\x00\x00\x00\x07\x00\x31\x00\x30\x00\x2e\x00\x32\x00\x30\x00" \
"\x2e\x00\x35\x00\x36\x00\x2e\x00\x38\x00\x33\x00\x00\x00\x07\x00" \
"\x31\x00\x3a\x00\x3a\x00\x32\x00\x35\x00\x36\x00\x3a\x00\x66\x00" \
"\x64\x00\x00\x00\x00\x00\x09\x00\xff\xff\x00\x00\x1e\x00\xff\xff" \
"\x00\x00\x10\x00\xff\xff\x00\x00\x0a\x00\xff\xff\x00\x00\x16\x00" \
"\xff\xff\x00\x00\x1f\x00\xff\xff\x00\x00\x0e\x00\xff\xff\x00\x00" \
"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
第四个数据包返回的永远是不定长的数据,所以需要参考文档进行解析,我下载了一份包含了OXID的文档,看起来非常的吃力,虽然有结构体,但是并没有给出一个通用的解决方案。
数据解析过程
规律:
-
每一个String Binding都以
\x07\x00
开头。 -
每一个StringBinding都以
\x00\x00
分割,一直到第一个Security Binding是\x09\x00
开头。
因此,当recv的数据直到\x09\x00
结束,开头就比较好办了,第四个数据包起始位置往后偏移42个字节就可以到达第一个String Binding。
Github 源代码地址: https://github.com/Rvn0xsy/OXID-Find/
经过测试有一点兼容问题,这里重新修改了一下
#!/usr/bin/env python
# coding: utf-8
from argparse import ArgumentParser, FileType
from queue import Queue
from threading import Thread
import socket
import sys
TIME_OUT = 2
RESULT_LIST = []
def get_ip_list(ip):
ip_list = []
iptonum = lambda x: sum([256**j*int(i) for j, i in enumerate(x.split('.')[::-1])])
numtoip = lambda x: '.'.join([str(x//(256**i)%256) for i in range(3, -1, -1)])
# 修正缩进错误
if '-' in ip:
ip_range = ip.split('-')
ip_start = iptonum(ip_range[0])
ip_end = iptonum(ip_range[1])
ip_count = ip_end - ip_start
if 0 <= ip_count <= 65536:
for ip_num in range(ip_start, ip_end + 1):
ip_list.append(numtoip(ip_num))
else:
print('-i wrong format')
# ...(其他部分不变)...
return ip_list
def str_to_hex(s):
return r"/x" + r'/x'.join([hex(ord(c)).replace('0x', '') for c in s])
def get_address(ip):
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
try:
sock.settimeout(TIME_OUT)
sock.connect((ip, 135))
buffer_v1 = b"\x05\x00\x0b\x03\x10\x00\x00\x00\x48\x00\x00\x00\x01\x00\x00\x00\xb8\x10\xb8\x10\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x01\x00\xc4\xfe\xfc\x99\x60\x52\x1b\x10\xbb\xcb\x00\xaa\x00\x21\x34\x7a\x00\x00\x00\x00\x04\x5d\x88\x8a\xeb\x1c\xc9\x11\x9f\xe8\x08\x00\x2b\x10\x48\x60\x02\x00\x00\x00"
buffer_v2 = b"\x05\x00\x00\x03\x10\x00\x00\x00\x18\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x05\x00"
sock.send(buffer_v1)
packet = sock.recv(1024)
sock.send(buffer_v2)
packet = sock.recv(4096)
packet_v2 = packet[42:]
packet_v2_end = packet_v2.find(b"\x09\x00\xff\xff\x00\x00")
packet_v2 = packet_v2[:packet_v2_end]
hostname_list = packet_v2.split(b"\x00\x00")
result = {ip: []}
print(f"[*] {ip}")
for h in hostname_list:
h = h.replace(b'\x07\x00', b'')
h = h.replace(b'\x00', b'')
if h == b'':
continue
print(f"\t[->] {h.decode('utf-8')}")
result[ip].append(h.decode('utf-8'))
print(result)
return result
except socket.error as e:
print(f"Error: {e}")
return -1
finally:
sock.close()
def worker(q):
while True:
try:
data = q.get()
result = get_address(data)
if result != -1:
RESULT_LIST.append(result)
except Exception as e:
sys.stderr.write(str(e))
finally:
q.task_done()
def main():
parser = ArgumentParser()
parser.add_argument('-i', '--ip', help=u'IP Address', required=True)
parser.add_argument('-t', '--threads', help=u'threads', default=20, type=int)
parser.add_argument('-o', '--output', help=u'Output result', default='log.txt', type=FileType('a+'))
args = parser.parse_args()
if args.ip is None:
print("Some Wrong.")
q = Queue(args.threads)
for _ in range(args.threads):
t = Thread(target=worker, args=(q,))
t.daemon = True
t.start()
ip_list = get_ip_list(args.ip)
for i in ip_list:
q.put(i)
q.join()
for host in RESULT_LIST:
for ip in host.keys():
args.output.write(f"[*] {ip}\n")
for other_ip in host[ip]:
args.output.write(f"\t[->] {other_ip}\n")
if __name__ == '__main__':
main()