0x00 背景
在某个环境中,遇到了一个奇怪的事情:使用 nbtscan 扫描机器,发现与 ICMP 协议探测的结果有点差异,因此为了搞清楚为什么 nbtscan 会遗漏机器的情况,使用 Windows 自带的 nbtstat 对遗漏机器进行探测,发现成功探测出 Unique name
及 Group name
。
情况说明: 目标机器与跳板机为同段机器,对目标机进行 Ping 操作,发现 TTL 存活。使用 Nmap 对目标机进行全端口扫描(-sT/-sU),发现无端口存活。基于这个情况,本地对这两款工具进行抓包测试(但由于不清楚实际环境的情况,因此本地并不能完全复现)。发现返回结果一致。但仔细观察发送包,发现 nbtstat 的 Src Port
和 Dst Port
端口都是 135,而 nbtscan 的 Src Port
端口为随机端口。因此为了验证是否是端口的问题,扣取了两款工具的数据包,使用 C# Socket raw
的方式进行探测(程序已进行验证 -> 普通环境可正常探测信息)。
程序探测结果:
•nbtscan 与 nbtstat 数据包都提示: 连接尝试失败,因为一段时间后连接方未正确响应,或者建立的连接失败,因为连接的主机未能响应。
因此我想知道具体是为什么?
0x01 本地分析
先来看看下图,这是正常情况下的探测情况:
这个协议 Wireshark 已经内置了,我们可以直接进行抓包分析。
NBTScan 的数据包为:
char peer0_0[] = { /* Packet 3246 */
0x03, 0xe8, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x20, 0x43, 0x4b, 0x41,
0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41,
0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41,
0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41,
0x41, 0x41, 0x41, 0x41, 0x41, 0x00, 0x00, 0x21,
0x00, 0x01 };
nbtstat 的数据包为:
char peer0_0[] = { /* Packet 97971 */
0xee, 0x33, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x20, 0x43, 0x4b, 0x41,
0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41,
0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41,
0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41,
0x41, 0x41, 0x41, 0x41, 0x41, 0x00, 0x00, 0x21,
0x00, 0x01 };
从抓包的结果来看,整个数据报,报头的差异仅为一个端口的差异。而数据本身虽然有些差别,但是从返回的结果来看,是一样的:
•nbtstat
的 Source Port
与 Destination Prot
都是 137;•而 NBTScan
的客户端 Port 不是 137。
但是本质上为什么探测的结果不一致,就不明白了。
到了这里,没了头绪,索性就讲一下怎么用 C# 写一个 nbtscan。但有种可能就是那边也限制了端口。
0x02 数据解析过程
我们来看看包的情况:
规律:
•没有开头标志;•结尾无规律;•每个 Name
以 16个字节为一组。
起始位置往后偏移 57 个字节就可以到达第一个 Name
,然后一个 Name
的数据长度为 16,外加一个 Name flags
为 2 个字节。因此自 57 个字节之后,每18个字节为一个 Name。将第 57 个字节后的数据包转成 raw
,则更好解析。18 个字节转成 raw,长度为 36,因此以 36 的字符长度拆分字符最合适。以 36 字符长度拆分字符串:
/// <summary>
/// 以固定长度拆分字符串
/// </summary>
public static ArrayList SplitLength(string SourceString, int Length)
{
ArrayList list = new ArrayList();
for (int i = 0; i < SourceString.Trim().Length; i += Length)
{
if ((SourceString.Trim().Length - i) >= Length)
list.Add(SourceString.Trim().Substring(i, Length));
else
list.Add(SourceString.Trim().Substring(i, SourceString.Trim().Length - i));
}
return list;
}
转换及输出:
IPAddress ipAddress = IPAddress.Parse(host);
IPEndPoint remoteEP = new IPEndPoint(ipAddress, 137);
response = String.Format("n[*] Detecting Remote Computer of {0}n", host);
byte[] response_v0 = new byte[1024];
using (var sock = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp))
{
sock.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReceiveTimeout, 3000);
sock.Connect(remoteEP);
sock.Send(nbtstat);
sock.Receive(response_v0);
}
string NumberName = Convert.ToString(response_v0[56], 10);
response += String.Format(" [+] Data length: {0}n [+] Number of Names: {1}", Convert.ToString(response_v0[55], 10), NumberName);
// 开始处理数据内容(这种解析方式属于取巧,不耐用):每个 Name 都是 18 个字节数组,如果转为 String 则为 36 个字符
string[] response_v1 = BitConverter.ToString(response_v0.Skip(57).ToArray()).Replace("-", "").Split(new String[] { "00000000" }, StringSplitOptions.RemoveEmptyEntries);
ArrayList strList = SplitLength(response_v1[0], 36);
foreach (string str in strList)
{
String Flags = str.Substring(str.Length - 6, 2);
String NameFlags = str.Substring(str.Length - 4);
if (Flags == "00" && NameFlags == "0400")
{
response += String.Format("n [>] Name type: Unique name -> (Workstation/Redirector) -> Name: {0}", Conversion(str, 0, 30));
}
else if (Flags == "00" && NameFlags == "8400")
{
response += String.Format("n [>] Name type: Group name -> (Workstation/Redirector) -> Name: {0}", Conversion(str, 0, 30));
}
else if(str.Length == 12)
{
String uintid = String.Empty;
for (int i = 0; i < str.Length / 2; i++)
{
uintid += str.Substring(i * 2, 2) + "-";
}
response += String.Format("n [>] Uint ID(...MAC...): {0}", uintid.Substring(0, uintid.LastIndexOf('-')));
}
}
Console.WriteLine(response);
更多的规则可以自己定义。
0x03 结果
号外号外
这文章是 2020年 9月发在星球的文章了。
原文始发于微信公众号(RowTeam):【渗透工具】SharpNBTScan
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论