mirror of
https://github.com/shadow1ng/fscan.git
synced 2026-09-22 03:10:42 +08:00
230 lines
5.5 KiB
Go
230 lines
5.5 KiB
Go
//go:build plugin_netbios || !plugin_selective
|
|
|
|
package services
|
|
|
|
import (
|
|
"encoding/binary"
|
|
"testing"
|
|
"unicode/utf16"
|
|
)
|
|
|
|
func TestParseNTLMInfoUsesFullTargetInfoOffset(t *testing.T) {
|
|
p := NewNetBIOSPlugin()
|
|
info := &NetBIOSInfo{}
|
|
|
|
targetInfo := appendNTLMAVPair(nil, 0x0003, "HOST.example.local")
|
|
targetInfo = append(targetInfo, 0x00, 0x00, 0x00, 0x00)
|
|
|
|
const targetOffset = 300
|
|
data := make([]byte, targetOffset+len(targetInfo))
|
|
copy(data, "NTLMSSP\x00")
|
|
binary.LittleEndian.PutUint16(data[40:42], uint16(len(targetInfo)))
|
|
binary.LittleEndian.PutUint32(data[44:48], targetOffset)
|
|
copy(data[targetOffset:], targetInfo)
|
|
|
|
p.parseNTLMInfo(data, info)
|
|
if info.ComputerName != "HOST.example.local" {
|
|
t.Fatalf("ComputerName = %q, want HOST.example.local", info.ComputerName)
|
|
}
|
|
}
|
|
|
|
func appendNTLMAVPair(dst []byte, id uint16, value string) []byte {
|
|
encoded := utf16.Encode([]rune(value))
|
|
buf := make([]byte, 4+len(encoded)*2)
|
|
binary.LittleEndian.PutUint16(buf[0:2], id)
|
|
binary.LittleEndian.PutUint16(buf[2:4], uint16(len(encoded)*2))
|
|
for i, r := range encoded {
|
|
binary.LittleEndian.PutUint16(buf[4+i*2:6+i*2], r)
|
|
}
|
|
return append(dst, buf...)
|
|
}
|
|
|
|
// --- NetBIOSInfo.Summary ---
|
|
|
|
func TestNetBIOSInfoSummary(t *testing.T) {
|
|
p := NewNetBIOSPlugin()
|
|
_ = p // 仅用于确认插件可实例化,Summary 是值方法
|
|
|
|
cases := []struct {
|
|
name string
|
|
info NetBIOSInfo
|
|
want string
|
|
}{
|
|
{
|
|
name: "invalid returns empty",
|
|
info: NetBIOSInfo{Valid: false},
|
|
want: "",
|
|
},
|
|
{
|
|
name: "computer + domain no dot",
|
|
info: NetBIOSInfo{Valid: true, ComputerName: "PC01", DomainName: "CORP"},
|
|
want: "CORP\\PC01",
|
|
},
|
|
{
|
|
name: "computer with dot ignores domain prefix",
|
|
info: NetBIOSInfo{Valid: true, ComputerName: "pc01.corp.local", DomainName: "CORP"},
|
|
want: "pc01.corp.local",
|
|
},
|
|
{
|
|
name: "no computer uses server service + domain",
|
|
info: NetBIOSInfo{Valid: true, ServerService: "SRV01", DomainName: "CORP"},
|
|
want: "CORP\\SRV01",
|
|
},
|
|
{
|
|
name: "no computer uses workstation + netbios domain",
|
|
info: NetBIOSInfo{Valid: true, WorkstationService: "WKS01", NetBIOSDomainName: "WORKGROUP"},
|
|
want: "WORKGROUP\\WKS01",
|
|
},
|
|
{
|
|
name: "domain controller prefix",
|
|
info: NetBIOSInfo{Valid: true, ComputerName: "DC1", DomainName: "CORP", DomainControllers: "CORP"},
|
|
want: "DC:CORP\\DC1",
|
|
},
|
|
{
|
|
name: "os version appended",
|
|
info: NetBIOSInfo{Valid: true, ComputerName: "PC01", DomainName: "CORP", OSVersion: "Windows 10"},
|
|
want: "CORP\\PC01 Windows 10",
|
|
},
|
|
}
|
|
|
|
for _, tc := range cases {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
got := tc.info.Summary()
|
|
if got != tc.want {
|
|
t.Errorf("got %q, want %q", got, tc.want)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
// --- parseNetBIOSNames ---
|
|
|
|
func TestParseNetBIOSNames(t *testing.T) {
|
|
p := &NetBIOSPlugin{}
|
|
|
|
t.Run("data too short", func(t *testing.T) {
|
|
_, err := p.parseNetBIOSNames(make([]byte, 40))
|
|
if err == nil {
|
|
t.Fatal("expected error for short data")
|
|
}
|
|
})
|
|
|
|
t.Run("numNames zero", func(t *testing.T) {
|
|
data := make([]byte, 57) // index 56 = 0
|
|
_, err := p.parseNetBIOSNames(data)
|
|
if err == nil {
|
|
t.Fatal("expected error for zero numNames")
|
|
}
|
|
})
|
|
|
|
t.Run("parses workstation and domain records", func(t *testing.T) {
|
|
header := make([]byte, 57)
|
|
header[56] = 2 // 2 records
|
|
|
|
// Record 1: WorkstationService — flagByte=0x00, nameFlags=0x04 (unique, <128)
|
|
rec1 := make([]byte, 18)
|
|
copy(rec1, []byte("TESTPC ")) // 15 bytes
|
|
rec1[15] = 0x00 // flagByte = WorkstationService
|
|
rec1[16] = 0x04 // nameFlags unique
|
|
rec1[17] = 0x00
|
|
|
|
// Record 2: DomainName — flagByte=0x00, nameFlags=0x84 (group, >=128)
|
|
rec2 := make([]byte, 18)
|
|
copy(rec2, []byte("WORKGROUP ")) // 15 bytes
|
|
rec2[15] = 0x00 // flagByte = DomainName for group
|
|
rec2[16] = 0x84 // nameFlags group
|
|
rec2[17] = 0x00
|
|
|
|
data := append(header, rec1...)
|
|
data = append(data, rec2...)
|
|
|
|
info, err := p.parseNetBIOSNames(data)
|
|
if err != nil {
|
|
t.Fatalf("unexpected error: %v", err)
|
|
}
|
|
if !info.Valid {
|
|
t.Fatal("expected Valid=true")
|
|
}
|
|
if info.WorkstationService != "TESTPC" {
|
|
t.Errorf("WorkstationService = %q, want TESTPC", info.WorkstationService)
|
|
}
|
|
if info.DomainName != "WORKGROUP" {
|
|
t.Errorf("DomainName = %q, want WORKGROUP", info.DomainName)
|
|
}
|
|
})
|
|
}
|
|
|
|
// --- cleanOSString ---
|
|
|
|
func TestCleanOSString(t *testing.T) {
|
|
p := &NetBIOSPlugin{}
|
|
|
|
cases := []struct {
|
|
name string
|
|
data []byte
|
|
want string
|
|
}{
|
|
{
|
|
name: "empty",
|
|
data: []byte{},
|
|
want: "",
|
|
},
|
|
{
|
|
name: "plain ascii",
|
|
data: []byte("Windows Server 2019"),
|
|
want: "Windows Server 2019",
|
|
},
|
|
{
|
|
name: "double null splits sections, first is returned",
|
|
data: append([]byte("Windows 10\x00\x00"), []byte("Service Pack 1")...),
|
|
want: "Windows 10",
|
|
},
|
|
}
|
|
|
|
for _, tc := range cases {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
got := p.cleanOSString(tc.data)
|
|
if got != tc.want {
|
|
t.Errorf("got %q, want %q", got, tc.want)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
// --- parseUnicodeString (NetBIOSPlugin) ---
|
|
|
|
func TestNetBIOSParseUnicodeString(t *testing.T) {
|
|
p := &NetBIOSPlugin{}
|
|
|
|
cases := []struct {
|
|
name string
|
|
data []byte
|
|
want string
|
|
}{
|
|
{
|
|
name: "empty",
|
|
data: []byte{},
|
|
want: "",
|
|
},
|
|
{
|
|
name: "odd length returns empty",
|
|
data: []byte{0x41},
|
|
want: "",
|
|
},
|
|
{
|
|
name: "UTF-16LE AB",
|
|
data: []byte{0x41, 0x00, 0x42, 0x00},
|
|
want: "AB",
|
|
},
|
|
}
|
|
|
|
for _, tc := range cases {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
got := p.parseUnicodeString(tc.data)
|
|
if got != tc.want {
|
|
t.Errorf("got %q, want %q", got, tc.want)
|
|
}
|
|
})
|
|
}
|
|
}
|