内网双网卡定位

本文介绍了如何利用OXIDResolver在无需认证的情况下,通过Windows的DCOM接口远程枚举网络接口,特别关注了在实战中定位双网卡的方法,以及通过SocketRAW实现的工具和Wireshark抓包分析。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

内网双网卡定位

在实战过程中,当我们拿到了相关边界机器权限后,可以利用漏洞打下几台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已经内置了,我们可以直接进行抓包分析。

image-20231212212212164

前三个不需要关注,主要是TCP的三次握手,后面的四次交互才是我们需要重点关注的。

第一个数据包 72 Bytes (主要用于协商版本等等):

image-20231212212236885

\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"

image-20231212212402904

第四个数据包返回的永远是不定长的数据,所以需要参考文档进行解析,我下载了一份包含了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()

实现效果

image-20231212212755678

image-20231212212802669

image-20231212212827372

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值