SMB(Server Message Block)协议,可用于在计算机间共享文件、打印机、串口等,电脑上的网上邻居就是靠它实现的。SMB使用了NetBIOS的应用程序接口 (Application Program Interface,简称API)。另外,它是一个开放性的协议,允许了协议扩展——使得它变得更大而且复杂;大约有65个最上层的作业,而每个作业都超过120个函数,甚至Windows NT也没有全部支持到,最近微软又把 SMB 改名为 CIFS(Common Internet File System),并且加入了许多新的特色。SMB协议一般端口使用为139,445,CIFS协议有三个版本:SMB、SMB2、SMB3。

NTLM

在type2返回Challenge的过程中,同时返回了操作系统类型,主机名,netbios名等等。这也就意味着如果我们在能跟服务器进行NTLM交流中,给服务器发送一个type1的请求,服务器返回type2的响应,这一步,我们就可以得到很多信息。SMBv1和SMBv2的数据包结构是不同的

SMBv1

使用非攻NTLMINFO探测SMB接口,抓包通过wireshark分析,包含操作系统类型的数据包由SMB Header和Response组成:

我们的目的是获取smb数据包的NTLM数据,然后对NTLM数据包解析,NTLM数据包上一层是GSS-API,首先找到GSS-API在整个数据包的偏移量,SMB的数据包结构长度如下:

SMB Header:  32 byte
Word Count:  1 byte
AndXCommand: 1 byte
Reserved:    1 byte
AndXOffset:  2 byte
Action: 	   2 byte
Security Blob Length: 2 byte (表示Security Blob的长度,这里的hex是 0f 10,小端转换为010f,再转换成10进制就是271,对应Security Blob的长度)
Byte Count: 2 byte (表示Security Blob加上NativeOS和Native Lan的长度)
Security Blob: 可变长度,取决于Security Blob Length

上面的数据包结构的关键数据是Security Blob LengthByte Content,前者表示GSS-API的整个数据包长度,后者表示GSS-API和Native OS加上Native LM的数据长度:

GSS-API的长度是271 Byte

Native OS的长度是42 Byte

Native LM的长度是38 Byte

所以数学题来了:
Security Blob Length转换成10进制是271 Byte

Byte Count: 271 + 42 + 38 = 351 Byte
我们的目的是获取NTLM的数据和NativeOS和Native LM,回到代码里面去看看,当我们获取到type2的数据,获取到的数据是什么呢?

NetBIOS Session Service这一层的长度是4 Byte,Security Blob Length的偏移量就出来了:

4 + 32 + 1 + 1 + 1 + 2 + 2 = 43

所以Security Blob Length偏移从43开始,长度是2 Byte, Security Blob跟在后面,偏移从45开始,47结束,go语言伪代码:

blob_length := uint16(bytes2Uint(ret[43:45], '<'))
blob_count := uint16(bytes2Uint(ret[45:47], '<'))
//gss变量表示从Security Blob起始位置到数据包结束,包括了Native OS和Native LM
gss := ret[47:]

//找到NTLMSSP在gss的偏移起始位置
off_ntlm := bytes.Index(gss, []byte("NTLMSSP"))

//Native OS和Native LM数据,对应上面的图
native := gss[int(blob_length):blob_count]

//bs表示ntlm的数据,以NTLMSSP开头
bs := gss[off_ntlm:blob_length]

接下来主要工作就是解析NTLM的数据,感谢iv4n师傅的Windows NTLM协议细节,我fork了一份go-ntlmssp,增加了解析NTLM输出字符串函数,可以直接获取NTLMSSP数据的解析结果:

func TestChallengeMsg_String(t *testing.T) {
	bs, _ := hex.DecodeString("4e544c4d535350xxxxx")
	type2 := ChallengeMsg{}
	info := type2.String(bs)
	fmt.Println(info)
}

Windows10默认使用SMBv2协议,没有打开SMBv1开关,Rcoll师傅的SharpDetectionNTLMSSP只发送了SMBv1的探测,没有探测SMBv2。非攻NTLMINFO师傅就比较完整,先探测SMBv1,失败之后尝试SMBv2。

SMBv2

参考非攻师傅的代码,先发送第一次的探测请求,找到偏移量70的地方,做一次判断是否发送第二个数据包。换成GO代码就比较简单了,这里的偏移70保存的是是SMBv2的Security mode:

Go语言如下:

var NTLMSSPNegotiatev2Data []byte
if hex.EncodeToString(r2[70:71]) == "03" {
	flags := []byte{0x15, 0x82, 0x08, 0xa0}
	NTLMSSPNegotiatev2Data = getNTLMSSPNegotiateData(flags)
} else {
	flags := []byte{0x05, 0x80, 0x08, 0xa0}
	NTLMSSPNegotiatev2Data = getNTLMSSPNegotiateData(flags)
}

_, err = conn2.Write(NegotiateSMBv2Data2)
if err != nil {
	return
}
readBytes(conn2)

_, err = conn2.Write(NTLMSSPNegotiatev2Data)
ret, _ := readBytes(conn2)
ntlmOff := bytes.Index(ret, []byte("NTLMSSP"))

成品主要参考非攻师傅的代码,集成到Cube,完成了winrm、wmi、smb、mssql端口的NTLM信息探测。

参考

⬆︎TOP