1
0
mirror of https://github.com/sairson/Yasso.git synced 2026-02-03 18:43:38 +08:00
Files
Yasso/example/tcp_smb_test.go

594 lines
16 KiB
Go

package example
import (
"bytes"
crand "crypto/rand"
"encoding/binary"
"encoding/hex"
"fmt"
"io"
"net"
"strings"
"testing"
"time"
)
func Test(t *testing.T) {
res, _, err := smb2("192.168.248.224", 445, 1*time.Second)
if err != nil || res["ntlmssp.Version"] == "" {
return
} else {
banner := fmt.Sprintf("Version:%s||DNSComputer:%s||TargetName:%s||NetbiosComputer:%s",
res["ntlmssp.Version"],
res["ntlmssp.DNSComputer"],
res["ntlmssp.TargetName"],
res["ntlmssp.NetbiosComputer"],
)
fmt.Println(banner)
return
}
}
// smb2 from https://github.com/RumbleDiscovery/rumble-tools/blob/main/cmd/rumble-smb2-sessions/main.go
func smb2(host string, port int, timeout time.Duration) (map[string]string, []byte, error) {
// SMB1NegotiateProtocolRequest is a SMB1 request that advertises support for SMB2
var smb1NegotiateProtocolRequest = []byte{
0x00, 0x00, 0x00, 0xd4, 0xff, 0x53, 0x4d, 0x42,
0x72, 0x00, 0x00, 0x00, 0x00, 0x18, 0x43, 0xc8,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfe, 0xff,
0x00, 0x00, 0x00, 0x00, 0x00, 0xb1, 0x00, 0x02,
0x50, 0x43, 0x20, 0x4e, 0x45, 0x54, 0x57, 0x4f,
0x52, 0x4b, 0x20, 0x50, 0x52, 0x4f, 0x47, 0x52,
0x41, 0x4d, 0x20, 0x31, 0x2e, 0x30, 0x00, 0x02,
0x4d, 0x49, 0x43, 0x52, 0x4f, 0x53, 0x4f, 0x46,
0x54, 0x20, 0x4e, 0x45, 0x54, 0x57, 0x4f, 0x52,
0x4b, 0x53, 0x20, 0x31, 0x2e, 0x30, 0x33, 0x00,
0x02, 0x4d, 0x49, 0x43, 0x52, 0x4f, 0x53, 0x4f,
0x46, 0x54, 0x20, 0x4e, 0x45, 0x54, 0x57, 0x4f,
0x52, 0x4b, 0x53, 0x20, 0x33, 0x2e, 0x30, 0x00,
0x02, 0x4c, 0x41, 0x4e, 0x4d, 0x41, 0x4e, 0x31,
0x2e, 0x30, 0x00, 0x02, 0x4c, 0x4d, 0x31, 0x2e,
0x32, 0x58, 0x30, 0x30, 0x32, 0x00, 0x02, 0x44,
0x4f, 0x53, 0x20, 0x4c, 0x41, 0x4e, 0x4d, 0x41,
0x4e, 0x32, 0x2e, 0x31, 0x00, 0x02, 0x4c, 0x41,
0x4e, 0x4d, 0x41, 0x4e, 0x32, 0x2e, 0x31, 0x00,
0x02, 0x53, 0x61, 0x6d, 0x62, 0x61, 0x00, 0x02,
0x4e, 0x54, 0x20, 0x4c, 0x41, 0x4e, 0x4d, 0x41,
0x4e, 0x20, 0x31, 0x2e, 0x30, 0x00, 0x02, 0x4e,
0x54, 0x20, 0x4c, 0x4d, 0x20, 0x30, 0x2e, 0x31,
0x32, 0x00, 0x02, 0x53, 0x4d, 0x42, 0x20, 0x32,
0x2e, 0x30, 0x30, 0x32, 0x00, 0x02, 0x53, 0x4d,
0x42, 0x20, 0x32, 0x2e, 0x3f, 0x3f, 0x3f, 0x00,
}
fmt.Println(string(smb1NegotiateProtocolRequest))
info := make(map[string]string)
conn, err := net.DialTimeout("tcp", fmt.Sprintf("%v:%v", host, port), timeout)
if err != nil {
return info, []byte{}, err
}
err = SMBSendData(conn, smb1NegotiateProtocolRequest, timeout)
if err != nil {
return info, []byte{}, err
}
data, err := SMBReadFrame(conn, timeout)
if err != nil {
return info, []byte{}, err
}
err = SMBSendData(conn, SMB2NegotiateProtocolRequest(host), timeout)
if err != nil {
return info, []byte{}, err
}
data, _ = SMBReadFrame(conn, timeout)
SMB2ExtractFieldsFromNegotiateReply(data, info)
// SMB2SessionSetupNTLMSSP is a SMB2 SessionSetup NTLMSSP request
var smb2SessionSetupNTLMSSP = []byte{
0x00, 0x00, 0x00, 0xa2, 0xfe, 0x53, 0x4d, 0x42,
0x40, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00,
0x01, 0x00, 0x21, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x19, 0x00, 0x00, 0x01,
0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x58, 0x00, 0x4a, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x60, 0x48, 0x06, 0x06,
0x2b, 0x06, 0x01, 0x05, 0x05, 0x02, 0xa0, 0x3e,
0x30, 0x3c, 0xa0, 0x0e, 0x30, 0x0c, 0x06, 0x0a,
0x2b, 0x06, 0x01, 0x04, 0x01, 0x82, 0x37, 0x02,
0x02, 0x0a, 0xa2, 0x2a, 0x04, 0x28, 0x4e, 0x54,
0x4c, 0x4d, 0x53, 0x53, 0x50, 0x00, 0x01, 0x00,
0x00, 0x00, 0x97, 0x82, 0x08, 0xe2, 0x00, 0x00,
0x00, 0x00, 0x28, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x28, 0x00, 0x00, 0x00, 0x0a, 0x00,
0xba, 0x47, 0x00, 0x00, 0x00, 0x0f,
}
setup := make([]byte, len(smb2SessionSetupNTLMSSP))
copy(setup, smb2SessionSetupNTLMSSP)
// Set the ProcessID
binary.LittleEndian.PutUint16(setup[4+32:], 0xfeff)
err = SMBSendData(conn, setup, timeout)
if err != nil {
return info, []byte{}, err
}
data, err = SMBReadFrame(conn, timeout)
SMB2ExtractSIDFromSessionSetupReply(data, info)
SMBExtractFieldsFromSecurityBlob(data, info)
return info, data, err
}
// RandomBytes generates a random byte sequence of the requested length
func RandomBytes(numBytes int) []byte {
randBytes := make([]byte, numBytes)
err := binary.Read(crand.Reader, binary.BigEndian, &randBytes)
if err != nil {
return nil
}
return randBytes
}
// SMBReadFrame reads the netBios header then the full response
func SMBReadFrame(conn net.Conn, t time.Duration) ([]byte, error) {
timeout := time.Now().Add(time.Duration(t) * time.Second)
res := []byte{}
nbh := make([]byte, 4)
err := conn.SetReadDeadline(timeout)
if err != nil {
return res, err
}
// Read the NetBIOS header
n, err := conn.Read(nbh[:])
if err != nil {
// Return if EOF is reached
if err == io.EOF {
return res, nil
}
// Return if timeout is reached
if err, ok := err.(net.Error); ok && err.Timeout() {
return res, nil
}
// If we have data and received an error, it was probably a reset
if len(res) > 0 {
return res, nil
}
return res, err
}
if n != 4 {
return res, nil
}
res = append(res[:], nbh[:n]...)
dlen := binary.BigEndian.Uint32(nbh[:]) & 0x00ffffff
buf := make([]byte, dlen)
n, err = conn.Read(buf[:])
if err != nil {
// Return if EOF is reached
if err == io.EOF {
return res, nil
}
// Return if timeout is reached
if err, ok := err.(net.Error); ok && err.Timeout() {
return res, nil
}
// If we have data and received an error, it was probably a reset
if len(res) > 0 {
return res, nil
}
return res, err
}
res = append(res[:], buf[:n]...)
return res, nil
}
// SMB2ExtractFieldsFromNegotiateReply extracts useful fields from the SMB2 negotiate response
func SMB2ExtractFieldsFromNegotiateReply(blob []byte, info map[string]string) {
smbOffset := bytes.Index(blob, []byte{0xfe, 'S', 'M', 'B'})
if smbOffset < 0 {
return
}
data := blob[smbOffset:]
// Basic sanity check
if len(data) < (64 + 8 + 16 + 36) {
return
}
switch binary.LittleEndian.Uint16(data[64+2:]) {
case 0:
info["smb.Signing"] = "disabled"
case 1:
info["smb.Signing"] = "enabled"
case 2, 3:
info["smb.Signing"] = "required"
}
info["smb.Dialect"] = fmt.Sprintf("0x%.4x", binary.LittleEndian.Uint16(data[64+4:]))
//info["smb.GUID"] = uuid.FromBytesOrNil(data[64+8 : 64+8+16]).String()
info["smb.Capabilities"] = fmt.Sprintf("0x%.8x", binary.LittleEndian.Uint32(data[64+8+16:]))
negCtxCount := int(binary.LittleEndian.Uint16(data[64+6:]))
negCtxOffset := int(binary.LittleEndian.Uint32(data[64+8+16+36:]))
if negCtxCount == 0 || negCtxOffset == 0 || negCtxOffset+(negCtxCount*8) > len(data) {
return
}
negCtxData := data[negCtxOffset:]
idx := 0
for {
if idx+8 > len(negCtxData) {
break
}
negType := int(binary.LittleEndian.Uint16(negCtxData[idx:]))
negLen := int(binary.LittleEndian.Uint16(negCtxData[idx+2:]))
idx += 8
if idx+negLen > len(negCtxData) {
break
}
negData := negCtxData[idx : idx+negLen]
SMB2ParseNegotiateContext(negType, negData, info)
// Move the index to the next context
idx += negLen
// Negotiate Contexts are aligned on 64-bit boundaries
for idx%8 != 0 {
idx++
}
}
}
// SMB2ParseNegotiateContext decodes fields from the SMB2 Negotiate Context values
func SMB2ParseNegotiateContext(t int, data []byte, info map[string]string) {
switch t {
case 1:
// SMB2_PREAUTH_INTEGRITY_CAPABILITIES
if len(data) < 6 {
return
}
hashCount := int(binary.LittleEndian.Uint16(data[:]))
// MUST only be one in responses
if hashCount != 1 {
return
}
hashSaltLen := int(binary.LittleEndian.Uint16(data[2:]))
hashType := int(binary.LittleEndian.Uint16(data[4:]))
hashName := "sha512"
if hashType != 1 {
hashName = fmt.Sprintf("unknown-%d", hashType)
}
info["smb.HashAlg"] = hashName
info["smb.HashSaltLen"] = fmt.Sprintf("%d", hashSaltLen)
case 2:
// SMB2_ENCRYPTION_CAPABILITIES
if len(data) < 4 {
return
}
cipherCount := int(binary.LittleEndian.Uint16(data[:]))
if len(data) < 2+(2*cipherCount) {
return
}
// MUST only be one in responses
if cipherCount != 1 {
return
}
cipherList := []string{}
for i := 0; i < cipherCount; i++ {
cipherID := int(binary.LittleEndian.Uint16(data[2+(i*2):]))
cipherName := ""
switch cipherID {
case 1:
cipherName = "aes-128-ccm"
case 2:
cipherName = "aes-128-gcm"
default:
cipherName = fmt.Sprintf("unknown-%d", cipherID)
}
cipherList = append(cipherList, cipherName)
}
info["smb.CipherAlg"] = strings.Join(cipherList, "\t")
case 3:
// SMB2_COMPRESSION_CAPABILITIES
if len(data) < 10 {
return
}
compCount := int(binary.LittleEndian.Uint16(data[:]))
if len(data) < 2+2+4+(2*compCount) {
return
}
// MUST only be one in responses
if compCount != 1 {
return
}
compList := []string{}
for i := 0; i < compCount; i++ {
compID := int(binary.LittleEndian.Uint16(data[8+(i*2):]))
compName := ""
switch compID {
case 0:
compName = "none"
case 1:
compName = "lznt1"
case 2:
compName = "lz77"
case 3:
compName = "lz77+huff"
case 4:
compName = "patternv1"
default:
compName = fmt.Sprintf("unknown-%d", compID)
}
compList = append(compList, compName)
}
info["smb.CompressionFlags"] = fmt.Sprintf("0x%.4x", binary.LittleEndian.Uint32(data[4:]))
info["smb.CompressionAlg"] = strings.Join(compList, "\t")
}
}
// SMBSendData writes a SMB request to a socket
func SMBSendData(conn net.Conn, data []byte, timeout time.Duration) error {
err := conn.SetWriteDeadline(time.Now().Add(time.Duration(timeout) * time.Second))
if err != nil {
return err
}
n, err := conn.Write(data)
if err != nil {
return err
}
_ = n
return nil
}
// SMB2ExtractSIDFromSessionSetupReply tries to extract the SessionID and Signature from a SMB2 reply
func SMB2ExtractSIDFromSessionSetupReply(blob []byte, info map[string]string) {
smbOffset := bytes.Index(blob, []byte{0xfe, 'S', 'M', 'B'})
if smbOffset < 0 {
return
}
smbData := blob[smbOffset:]
if len(smbData) < 48 {
return
}
status := binary.LittleEndian.Uint32(smbData[8:])
info["smb.Status"] = fmt.Sprintf("0x%.8x", status)
sessID := binary.LittleEndian.Uint64(smbData[40:])
info["smb.SessionID"] = fmt.Sprintf("0x%.16x", sessID)
if len(smbData) >= 64 {
sigData := hex.EncodeToString(smbData[48:64])
if sigData != "00000000000000000000000000000000" {
info["smb.Signature"] = sigData
}
}
}
// SMBExtractValueFromOffset peels a field out of a SMB buffer
func SMBExtractValueFromOffset(blob []byte, idx int) ([]byte, int, error) {
res := []byte{}
if len(blob) < (idx + 6) {
return res, idx, fmt.Errorf("data truncated")
}
len1 := binary.LittleEndian.Uint16(blob[idx:])
idx += 2
// len2 := binary.LittleEndian.Uint16(blob[idx:])
idx += 2
off := binary.LittleEndian.Uint32(blob[idx:])
idx += 4
// Allow zero length values
if len1 == 0 {
return res, idx, nil
}
if len(blob) < int(off+uint32(len1)) {
return res, idx, fmt.Errorf("data value truncated")
}
res = append(res, blob[off:off+uint32(len1)]...)
return res, idx, nil
}
// SMBExtractFieldsFromSecurityBlob extracts fields from the NTLMSSP response
func SMBExtractFieldsFromSecurityBlob(blob []byte, info map[string]string) {
var err error
ntlmsspOffset := bytes.Index(blob, []byte{'N', 'T', 'L', 'M', 'S', 'S', 'P', 0x00, 0x02, 0x00, 0x00, 0x00})
if ntlmsspOffset < 0 {
return
}
data := blob[ntlmsspOffset:]
// Basic sanity check
if len(data) < (12 + 6 + 12 + 8 + 6 + 8) {
return
}
idx := 12
targetName, idx, err := SMBExtractValueFromOffset(data, idx)
if err != nil {
return
}
// Negotiate Flags
negotiateFlags := binary.LittleEndian.Uint32(data[idx:])
info["ntlmssp.NegotiationFlags"] = fmt.Sprintf("0x%.8x", negotiateFlags)
idx += 4
// NTLM Server Challenge
idx += 8
// Reserved
idx += 8
// Target Info
targetInfo, idx, err := SMBExtractValueFromOffset(data, idx)
if err != nil {
return
}
// Version
versionMajor := uint8(data[idx])
idx++
versionMinor := uint8(data[idx])
idx++
versionBuild := binary.LittleEndian.Uint16(data[idx:])
idx += 2
ntlmRevision := binary.BigEndian.Uint32(data[idx:])
// macOS reverses the endian order of this field for some reason
if ntlmRevision == 251658240 {
ntlmRevision = binary.LittleEndian.Uint32(data[idx:])
}
info["ntlmssp.Version"] = fmt.Sprintf("%d.%d.%d", versionMajor, versionMinor, versionBuild)
info["ntlmssp.NTLMRevision"] = fmt.Sprintf("%d", ntlmRevision)
info["ntlmssp.TargetName"] = TrimName(string(targetName))
idx = 0
for {
if idx+4 > len(targetInfo) {
break
}
attrType := binary.LittleEndian.Uint16(targetInfo[idx:])
idx += 2
// End of List
if attrType == 0 {
break
}
attrLen := binary.LittleEndian.Uint16(targetInfo[idx:])
idx += 2
if idx+int(attrLen) > len(targetInfo) {
// log.Printf("too short: %d/%d", idx+int(attrLen), len(targetInfo))
break
}
attrVal := targetInfo[idx : idx+int(attrLen)]
idx += int(attrLen)
switch attrType {
case 1:
info["ntlmssp.NetbiosComputer"] = TrimName(string(attrVal))
case 2:
info["ntlmssp.NetbiosDomain"] = TrimName(string(attrVal))
case 3:
info["ntlmssp.DNSComputer"] = TrimName(string(attrVal))
case 4:
info["ntlmssp.DNSDomain"] = TrimName(string(attrVal))
case 7:
ts := binary.LittleEndian.Uint64(attrVal[:])
info["ntlmssp.Timestamp"] = fmt.Sprintf("0x%.16x", ts)
}
// End of List
if attrType == 0 {
break
}
}
}
// TrimName removes null bytes and trims leading and trailing spaces from a string
func TrimName(name string) string {
return strings.TrimSpace(strings.Replace(name, "\x00", "", -1))
}
// SMB2NegotiateProtocolRequest generates a new Negotiate request with the specified target name
func SMB2NegotiateProtocolRequest(dst string) []byte {
base := []byte{
0xfe, 0x53, 0x4d, 0x42,
0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0xff, 0xfe, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x24, 0x00, 0x05, 0x00,
0x01, 0x00, 0x00, 0x00, 0x7f, 0x00, 0x00, 0x00,
}
// Client GUID (16)
base = append(base[:], RandomBytes(16)...)
base = append(base[:], []byte{
0x70, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00,
0x02, 0x02, 0x10, 0x02, 0x00, 0x03, 0x02, 0x03,
0x11, 0x03, 0x00, 0x00, 0x01, 0x00, 0x26, 0x00,
0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x20, 0x00,
0x01, 0x00,
}...)
// SHA-512 Salt (32)
base = append(base[:], RandomBytes(32)...)
base = append(base[:], []byte{
0x00, 0x00, 0x02, 0x00, 0x06, 0x00,
0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x02, 0x00,
0x01, 0x00, 0x00, 0x00, 0x03, 0x00, 0x0e, 0x00,
0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x03, 0x00,
0x01, 0x00, 0x00, 0x00,
}...)
encodedDst := make([]byte, len(dst)*2)
for i, b := range []byte(dst) {
encodedDst[i*2] = b
}
netname := []byte{0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}
binary.LittleEndian.PutUint16(netname[2:], uint16(len(encodedDst)))
netname = append(netname, encodedDst...)
base = append(base, netname...)
nbhd := make([]byte, 4)
binary.BigEndian.PutUint32(nbhd, uint32(len(base)))
nbhd = append(nbhd, base...)
fmt.Println(string(nbhd))
return nbhd
}