Files
mandiant-gopacket/pkg/third_party/smb2
psycep 100500df41 smbclient: add list_snapshots command
Fixes #8. smbclient's interactive shell now supports list_snapshots,
which enumerates VSS shadow copies on the currently selected share
via FSCTL_SRV_ENUMERATE_SNAPSHOTS. Matches Impacket smbclient.py's
list_snapshots output format.

The response follows MS-SMB2 2.2.32 and requires a two-call size
probe: the first ioctl uses a 16-byte output buffer to read the
SRV_SNAPSHOT_ARRAY header (SnapShotArraySize tells us how large the
second buffer needs to be), then a second ioctl asks for the full
payload and parses the UTF-16LE NUL-separated @GMT-... tokens.

Changes:
- pkg/third_party/smb2/client.go: add an exported Ioctl method on
  *File as a thin wrapper over the internal ioctl helper, so FSCTL
  operations beyond FSCTL_PIPE_TRANSCEIVE don't need their own
  dedicated method in the vendored library.
- pkg/smb/client.go: EnumerateSnapshots() method on *Client. Handles
  the size-probe dance, parses the UTF-16LE token list via
  pkg/utf16le, returns []string. Empty slice when no snapshots exist.
- tools/smbclient/main.go: wire list_snapshots into the shell's
  command switch and help text.

Unblocks the VSS-based NTDS extraction path:
  use c$
  list_snapshots      # get @GMT-YYYY.MM.DD-HH.MM.SS
  get @GMT-...\Windows\NTDS\ntds.dit
  secretsdump -ntds ntds.dit -system SYSTEM
2026-04-22 13:46:16 -05:00
..
2026-04-17 14:20:41 -05:00
2026-04-17 14:20:41 -05:00
2026-04-17 14:20:41 -05:00
2026-04-17 14:20:41 -05:00
2026-04-17 14:20:41 -05:00
2026-04-17 14:20:41 -05:00
2026-04-17 14:20:41 -05:00
2026-04-17 14:20:41 -05:00
2026-04-17 14:20:41 -05:00
2026-04-17 14:20:41 -05:00
2026-04-17 14:20:41 -05:00
2026-04-17 14:20:41 -05:00
2026-04-17 14:20:41 -05:00
2026-04-17 14:20:41 -05:00
2026-04-17 14:20:41 -05:00
2026-04-17 14:20:41 -05:00

smb2

Build Status Go Reference

Description

SMB2/3 client implementation.

Installation

go get github.com/hirochachacha/go-smb2

Documentation

http://godoc.org/github.com/hirochachacha/go-smb2

Examples

List share names

package main

import (
	"fmt"
	"net"

	"github.com/hirochachacha/go-smb2"
)

func main() {
	conn, err := net.Dial("tcp", "SERVERNAME:445")
	if err != nil {
		panic(err)
	}
	defer conn.Close()

	d := &smb2.Dialer{
		Initiator: &smb2.NTLMInitiator{
			User:     "USERNAME",
			Password: "PASSWORD",
		},
	}

	s, err := d.Dial(conn)
	if err != nil {
		panic(err)
	}
	defer s.Logoff()

	names, err := s.ListSharenames()
	if err != nil {
		panic(err)
	}

	for _, name := range names {
		fmt.Println(name)
	}
}

File manipulation

package main

import (
	"io"
	"io/ioutil"
	"net"

	"github.com/hirochachacha/go-smb2"
)

func main() {
	conn, err := net.Dial("tcp", "SERVERNAME:445")
	if err != nil {
		panic(err)
	}
	defer conn.Close()

	d := &smb2.Dialer{
		Initiator: &smb2.NTLMInitiator{
			User:     "USERNAME",
			Password: "PASSWORD",
		},
	}

	s, err := d.Dial(conn)
	if err != nil {
		panic(err)
	}
	defer s.Logoff()

	fs, err := s.Mount("SHARENAME")
	if err != nil {
		panic(err)
	}
	defer fs.Umount()

	f, err := fs.Create("hello.txt")
	if err != nil {
		panic(err)
	}
	defer fs.Remove("hello.txt")
	defer f.Close()

	_, err = f.Write([]byte("Hello world!"))
	if err != nil {
		panic(err)
	}

	_, err = f.Seek(0, io.SeekStart)
	if err != nil {
		panic(err)
	}

	bs, err := ioutil.ReadAll(f)
	if err != nil {
		panic(err)
	}

	fmt.Println(string(bs))
}

Check error types

package main

import (
	"context"
	"fmt"
	"net"
	"os"

	"github.com/hirochachacha/go-smb2"
)

func main() {
	conn, err := net.Dial("tcp", "SERVERNAME:445")
	if err != nil {
		panic(err)
	}
	defer conn.Close()

	d := &smb2.Dialer{
		Initiator: &smb2.NTLMInitiator{
			User:     "USERNAME",
			Password: "PASSWORD",
		},
	}

	s, err := d.Dial(conn)
	if err != nil {
		panic(err)
	}
	defer s.Logoff()

	fs, err := s.Mount("SHARENAME")
	if err != nil {
		panic(err)
	}
	defer fs.Umount()

	_, err = fs.Open("notExist.txt")

	fmt.Println(os.IsNotExist(err)) // true
	fmt.Println(os.IsExist(err))    // false

	fs.WriteFile("hello2.txt", []byte("test"), 0444)
	err = fs.WriteFile("hello2.txt", []byte("test2"), 0444)
	fmt.Println(os.IsPermission(err)) // true

	ctx, cancel := context.WithTimeout(context.Background(), 0)
	defer cancel()

	_, err = fs.WithContext(ctx).Open("hello.txt")

	fmt.Println(os.IsTimeout(err)) // true
}

Glob and WalkDir through FS interface

package main

import (
	"fmt"
	"net"
	iofs "io/fs"

	"github.com/hirochachacha/go-smb2"
)

func main() {
	conn, err := net.Dial("tcp", "SERVERNAME:445")
	if err != nil {
		panic(err)
	}
	defer conn.Close()

	d := &smb2.Dialer{
		Initiator: &smb2.NTLMInitiator{
			User:     "USERNAME",
			Password: "PASSWORD",
		},
	}

	s, err := d.Dial(conn)
	if err != nil {
		panic(err)
	}
	defer s.Logoff()

	fs, err := s.Mount("SHARENAME")
	if err != nil {
		panic(err)
	}
	defer fs.Umount()

	matches, err := iofs.Glob(fs.DirFS("."), "*")
	if err != nil {
		panic(err)
	}
	for _, match := range matches {
		fmt.Println(match)
	}

	err = iofs.WalkDir(fs.DirFS("."), ".", func(path string, d iofs.DirEntry, err error) error {
		fmt.Println(path, d, err)

		return nil
	})
	if err != nil {
		panic(err)
	}
}