A from-scratch, userland SMB1 / SMB2 file server written in Go. It is a lightweight SMB server for ad-hoc file sharing, NTLM auth capture, and protocol experiment ground, with no Python runtime required.
The binary name is smbserver; the module path is
github.com/jimmexploit/badsmb.
There are a handful of SMB servers in Go (for example hugelgupf/smbsrv,
Adjsky/smbserver, and the many one-file utility SMB servers). This
project deliberately occupies a different niche:
-
Focused scope. A lightweight SMB1/SMB2 file server: SMB1 core file commands, SMB2 equivalents, anonymous and NTLM auth, read-only or read-write shares, and auth / session logging. It does not aim to be a production SMB implementation. Out of scope by design: Kerberos as a server, SMB3 AES encryption, DFS, VSS, and Windows ACL semantics.
-
Single static binary.
go buildproduces one no-cgo, statically linked executable with no Python and nopip install. Drop it on a box and run it. -
Small memory footprint. A long-running or embedded SMB listener stays light compared with a Python process plus interpreter.
-
Easy cross-compilation. One
GOOS/GOARCHchange builds for Windows, Linux, macOS, and ARM targets (see "Cross-compile"). -
Library-first design for deep customization. Most Go SMB servers are a single binary with no sane import surface. Here every package (
smb1,smb2,ntlm,vfs,server,config,rpc,srvsvc,wire,logx) is a public, importable Go package. There is nointernal/boundary. That is intentional: people routinely want custom NTLMv2 handling, a capture-only authenticator, a different logging backend, or their own VFS layer, and the publicntlm.Validatorinterface is the extension point for that. See "Use as a package". -
Network share browsing works. Because real
smbclient -Ldepends on it, the server ships with the named-pipe plumbing plus loopback DCERPC (rpc) and the SRVSVC / WKSTSRVC callbacks (srvsvc) that serveIPC$, so clients can enumerate shares over\srvsvc/\wkssvc.
If you need a hardened, enterprise SMB server (Kerberos, SMB3 encryption, full ACLs), a different project is the right fit. This one exists to provide a lightweight SMB server in a single deployable Go binary.
- SMB1 (Core + NT): NEGOTIATE, SESSION_SETUP_ANDX (classic and extended SPNEGO/NTLM), TREE_CONNECT/DISCONNECT, LOGOFF, NT_CREATE_ANDX, READ/WRITE/CLOSE, QUERY_INFORMATION / QUERY_INFORMATION2 / QUERY_INFORMATION_DISK, CREATE_DIRECTORY / DELETE / DELETE_DIRECTORY / RENAME, FIND_FIRST2 / FIND_NEXT2, and the TRANSACTION2 subset.
- SMB2 / SMB3: NEGOTIATE (dialect 0x0202), SESSION_SETUP, TREE_CONNECT/DISCONNECT, CREATE, READ, WRITE, CLOSE, QUERY_DIRECTORY, QUERY_INFO, SET_INFO, IOCTL (VALIDATE_NEGOTIATE_INFO and PIPE_TRANSCEIVE), ECHO, CHANGE_NOTIFY, LOGOFF.
- Auth: anonymous/guest logons and NTLM (username + password, or
username +
LMHASH:NTHASH). Message signing is supported opportunistically. - Named pipes / DCERPC:
IPC$share with\srvsvcand\wkssvcserved through loopback DCERPC, so network share enumeration works.
Needs the -smb2support flag (or SMB2Support = yes in a config file) to
answer SMB2; otherwise the server stays SMB1 only.
Requires Go 1.24 or newer (module targets go 1.24.4). No cgo, no
third-party runtime dependencies beyond golang.org/x/crypto (indirect).
go build ./cmd/smbserverThis produces ./smbserver in the current directory. Verify it:
./smbserver -hInstall the binary directly to $GOPATH/bin/smbserver:
go install github.com/jimmexploit/badsmb/cmd/smbserver@latestSanity-check the whole module:
go vet ./...
go build ./...Set GOOS and GOARCH at build time. The code has no cgo, so any target
is a straight cross-build:
GOOS=windows GOARCH=amd64 go build ./cmd/smbserver
GOOS=linux GOARCH=arm64 go build ./cmd/smbserver
GOOS=darwin GOARCH=arm64 go build ./cmd/smbserverTo list all targets Go supports on this host:
go tool dist listShare a local directory as tmp, listening on all interfaces:
./smbserver tmp /tmpConnect from smbclient:
smbclient //127.0.0.1/tmp -U guest
smbclient //127.0.0.1/tmp -U SOMEUSER%PASSWORD -L //127.0.0.1usage: smbserver [options] shareName sharePath
Launch an SMB server sharing sharePath as shareName. Alternatively
load a .conf file with -conf instead of positional share arguments.
| Option | Description |
|---|---|
-comment string |
Share comment reported during enumeration. |
-username string |
Authenticate clients against this username. |
-password string |
Password for -username. |
-hashes string |
NTLM hashes for -username, format LMHASH:NTHASH. |
-ip string |
Listen interface (default 0.0.0.0, or :: with -6). |
-interface-address |
Alias for -ip. |
-port int |
TCP port (default 445). |
-readonly |
Only allow reads. |
-smb2support |
Enable SMB2 negotiation (experimental). |
-disablentlm |
Do not offer NTLM authentication. |
-disablekerberos |
Parsed for parity; Kerberos is a non-goal, so inert. |
-dropssp |
Disable NTLM ESS/SSP during negotiation. |
-6, -ipv6 |
Listen on IPv6. |
-ts |
Add a timestamp to every log line. |
-debug |
Print DEBUG output. |
-outputfile string |
Write log output to this file. |
-conf string |
Load an INI .conf file instead of positionals. |
-username requires either -password or -hashes. The server never
prompts for a missing password; it exits with status 2 instead. -password
is resolved to an NT hash (MD4 of the UTF-16LE password) at startup.
Without -username, the server runs in capture mode: any user is accepted
and the NTLM challenge/response material is logged for downstream
analysis.
The -conf flag loads an INI-style config. It is mutually exclusive with
the positional share arguments and the -username / -password /
-hashes / -comment flags. -ip and -port still apply on top.
[global]
server_name = BADSMBSRV
server_os = Windows 7 Ultimate 7601
server_domain = WORKGROUP
log_file = None
credentials_file = /path/to/creds.txt
[share1]
comment = Read-only share
read only = yes
path = /srv/share1Global options: server_name, server_os, server_domain, log_file,
credentials_file (required); challenge, jtr_dump_path, dump_hashes,
SMB2Support, DropSSP, KerberosSupport, NTLMSupport,
anonymous_logon, rpc_apis, and the computer_account_* block
(optional). Share sections take comment, read only, share type
(0 disk, 1 printer, 3 IPC), and path (required). A read only value of
exactly yes makes a share read-only; any other value (including 1 or
true) does not.
Port 445 is privileged on many systems. Start as root or grant the
capability, for example on Linux:
sudo setcap 'cap_net_bind_service=+ep' ./smbserverEvery package is public and importable; there is no internal/ boundary.
This is the documented extension point for custom NTLMv2 handling,
capture-only logging, alternate VFS backends, and custom logging.
package main
import (
"log"
"github.com/jimmexploit/badsmb/config"
"github.com/jimmexploit/badsmb/server"
)
func main() {
cfg := config.Default()
cfg.AddShare("tmp", "/tmp", "temporary share", /* readOnly */ false)
srv, err := server.New(server.WithConfig(cfg))
if err != nil {
log.Fatal(err)
}
defer srv.Close()
if err := srv.Listen(); err != nil {
log.Fatal(err)
}
if err := srv.Serve(); err != nil {
log.Fatal(err)
}
}The server package uses functional options:
server.WithListenAddr(addr)- listen address inhost:portform, e.g."127.0.0.1:445"or":445".server.WithShares(shares)- shares from a[]config.Share.server.WithConfig(cfg)- a resolvedconfig.Config(convenience path the CLI uses).server.WithNTLMValidator(v)- custom NTLM auth logic; defaults tontlm.DefaultValidator{}.server.WithLogger(l)/server.WithLeveledLogger(l)- logging backends.
The server depends on the ntlm.Validator interface, not on any concrete
auth implementation:
type Validator interface {
Validate(challenge []byte, resp *AuthenticateMessage, creds Credentials) (bool, error)
}Implement it directly, or embed ntlm.DefaultValidator and override only
what you need:
type LoggingValidator struct{ ntlm.DefaultValidator }
func (v *LoggingValidator) Validate(challenge []byte, resp *ntlm.AuthenticateMessage, creds ntlm.Credentials) (bool, error) {
ok, err := v.DefaultValidator.Validate(challenge, resp, creds)
log.Printf("auth attempt: user=%s ok=%v", resp.UserName, ok)
return ok, err
}
srv, _ := server.New(server.WithNTLMValidator(&LoggingValidator{}))A capture-only validator that always succeeds (and logs the material) is a common pattern enabled by this interface.
ntlm- exported NEGOTIATE / CHALLENGE / AUTHENTICATE message structs (all fields public), SPNEGO tokens, NTLMv2 response parsing and NT hash computation, plus John-the-Ripper hash formatting helpers (JohnFormat,WriteJohnOutputToFile,JtrLogon).config-Config, share definitions, auth mode,Default(),AddShare,SetHashes, and the.confINI parser (Load).vfs- share-to-path mapping, path jail guard, stat-to-FILE_* information encoders.smb1,smb2- the two dialects' headers, constants, and command handlers.rpc,srvsvc- loopback DCERPC transport and the SRVSVC/WKSTSRVC share-addressing callbacks.wire,logx- shared codec helpers and the leveled logging package.
- Microsoft Open Specifications: MS-SMB, MS-CIFS, MS-SMB2, MS-FSCC, MS-NLMP, MS-ERREF, MS-SRVS, MS-WKST.
- Impacket
The common SMB client used for manual validation is samba-client
smbclient, plus mount -t cifs and Windows Explorer for connect,
authenticate, list, read, and write checks.
Use this only on systems you own or have explicit authorization to test.
This project is released under the MIT License.