-
Notifications
You must be signed in to change notification settings - Fork 25
/
gocat.go
305 lines (265 loc) · 9.96 KB
/
gocat.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
package gocat
/*
#cgo CFLAGS: -I/usr/local/include/hashcat -std=c99 -Wall -O0 -g
#cgo linux CFLAGS: -D_GNU_SOURCE
#cgo linux LDFLAGS: -L/usr/local/lib -lhashcat
#cgo darwin LDFLAGS: -L/usr/local/lib -lhashcat.6.1.1
#include "wrapper.h"
*/
import "C"
import (
"errors"
"fmt"
"os"
"sync"
"time"
"unsafe"
"github.com/mandiant/gocat/v6/hcargp"
)
var (
// CompileTime should be set using -ldflags "-X" during compilation. This value is passed into hashcat_session_init
CompileTime = time.Now().UTC().Unix()
// ErrUnableToStopAtCheckpoint is raised whenever hashcat is unable to stop at next checkpoint. This is caused when
// the device status != STATUS_RUNNING or --restore-disable is set
ErrUnableToStopAtCheckpoint = errors.New("gocat: unable to stop at next checkpoint")
// ErrUnableToStop is raised whenever we were unable to stop a hashcat session. At this time, hashcat's hashcat_session_quit
// always returns success so you'll most likely never see this.
ErrUnableToStop = errors.New("gocat: unable to stop task")
)
const (
// sessionCracked indicates all hashes were cracked
sessionCracked int = iota
// sessionExhausted indicates all possible permutations were reached in the session
sessionExhausted
// sessionQuit indicates the session was quit by the user
sessionQuit
// sessionAborted indicates the session was aborted by the user
sessionAborted
// sessionAbortedCheckpoint indicates the session was checkpointed by the user (usually due to an abort signal or temperature limit)
sessionAbortedCheckpoint
// sessionAbortedRuntime indicates the session was stopped by hashcat (usually due to a runtime limit)
sessionAbortedRuntime
)
// EventCallback defines the callback that hashcat/gocat calls
type EventCallback func(Hashcat unsafe.Pointer, Payload interface{})
// Options defines all the configuration options for gocat/hashcat
type Options struct {
// SharedPath should be set to the location where OpenCL kernels and .hcstat/hctune files live
SharedPath string
// ExecutablePath should be set to the location of the binary and not the binary itself.
// Hashcat does some weird logic and will ignore the shared path if this is incorrectly set.
// If ExecutablePath is not set, we'll attempt to calculate it using os.Args[0]
ExecutablePath string
// PatchEventContext if true will patch hashcat's event_ctx with a reentrant lock which will allow
// you to call several hashcat APIs (which fire another callback) from within an event callback.
// This is supported on macOS, Linux, and Windows.
PatchEventContext bool
}
// ErrNoSharedPath is raised whenever Options.SharedPath is not set
var ErrNoSharedPath = errors.New("shared path must be set")
func (o *Options) validate() (err error) {
if o.SharedPath == "" {
return ErrNoSharedPath
}
if o.ExecutablePath == "" {
path, err := os.Executable()
if err != nil {
return err
}
o.ExecutablePath = path
} else {
if _, err = os.Stat(o.ExecutablePath); err != nil {
return err
}
}
return nil
}
// Hashcat is the interface which interfaces with libhashcat to provide password cracking capabilities.
type Hashcat struct {
// unexported fields below
wrapper C.gocat_ctx_t
cb EventCallback
opts Options
isEventPatched bool
l sync.Mutex
// these must be free'd
executablePath *C.char
sharedPath *C.char
}
// New creates a context that can be reused to crack passwords.
func New(opts Options, cb EventCallback) (*Hashcat, error) {
if err := opts.validate(); err != nil {
return nil, err
}
hc := &Hashcat{
executablePath: C.CString(opts.ExecutablePath),
sharedPath: C.CString(opts.SharedPath),
cb: cb,
opts: opts,
}
hc.wrapper = C.gocat_ctx_t{
ctx: C.hashcat_ctx_t{},
gowrapper: unsafe.Pointer(hc),
}
if retval := C.hashcat_init(&hc.wrapper.ctx, (*[0]byte)(unsafe.Pointer(C.event))); retval != 0 {
return nil, getErrorFromCtx(hc.wrapper.ctx)
}
return hc, nil
}
// EventCallbackIsReentrant returns a boolean indicating if hashcat_ctx.event_ctx has been patched to allow an event to fire another event
func (hc *Hashcat) EventCallbackIsReentrant() bool {
return hc.isEventPatched
}
// RunJob starts a hashcat session and blocks until it has been finished.
func (hc *Hashcat) RunJob(args ...string) (err error) {
hc.l.Lock()
defer hc.l.Unlock()
// initialize the default options in hashcat_ctx->user_options
if retval := C.user_options_init(&hc.wrapper.ctx); retval != 0 {
return
}
argc, argv := convertArgsToC(append([]string{hc.opts.ExecutablePath}, args...)...)
defer C.freeargv(argc, argv)
if retval := C.user_options_getopt(&hc.wrapper.ctx, argc, argv); retval != 0 {
return getErrorFromCtx(hc.wrapper.ctx)
}
if retval := C.user_options_sanity(&hc.wrapper.ctx); retval != 0 {
return getErrorFromCtx(hc.wrapper.ctx)
}
if retval := C.hashcat_session_init(&hc.wrapper.ctx, hc.executablePath, hc.sharedPath, argc, argv, C.int(CompileTime)); retval != 0 {
return getErrorFromCtx(hc.wrapper.ctx)
}
defer C.hashcat_session_destroy(&hc.wrapper.ctx)
if hc.opts.PatchEventContext {
isPatchSuccessful, err := patchEventMutex(hc.wrapper.ctx)
if err != nil {
return err
}
hc.isEventPatched = isPatchSuccessful
}
rc := C.hashcat_session_execute(&hc.wrapper.ctx)
switch int(rc) {
case sessionCracked, sessionExhausted, sessionQuit, sessionAborted,
sessionAbortedCheckpoint, sessionAbortedRuntime:
err = nil
default:
return getErrorFromCtx(hc.wrapper.ctx)
}
return
}
// RunJobWithOptions is a convenience function to take a HashcatSessionOptions struct and craft the necessary argvs to use
// for the hashcat session.
// This is NOT goroutine safe. If you are needing to run multiple jobs, create a context for each one.
func (hc *Hashcat) RunJobWithOptions(opts hcargp.HashcatSessionOptions) error {
args, err := opts.MarshalArgs()
if err != nil {
return err
}
return hc.RunJob(args...)
}
// StopAtCheckpoint instructs the running hashcat session to stop at the next available checkpoint
func (hc *Hashcat) StopAtCheckpoint() error {
if retval := C.hashcat_session_checkpoint(&hc.wrapper.ctx); retval != 0 {
return ErrUnableToStopAtCheckpoint
}
return nil
}
// AbortRunningTask instructs hashcat to abruptly stop the running session
func (hc *Hashcat) AbortRunningTask() {
C.hashcat_session_quit(&hc.wrapper.ctx)
}
func getErrorFromCtx(ctx C.hashcat_ctx_t) error {
msg := C.hashcat_get_log(&ctx)
return fmt.Errorf("gocat: %s", C.GoString(msg))
}
//export callback
func callback(id uint32, hcCtx *C.hashcat_ctx_t, wrapper unsafe.Pointer, buf unsafe.Pointer, len C.size_t) {
ctx := (*Hashcat)(wrapper)
var payload interface{}
var err error
switch id {
case C.EVENT_LOG_ERROR:
payload = logMessageCbFromEvent(hcCtx, InfoMessage)
case C.EVENT_LOG_INFO:
payload = logMessageCbFromEvent(hcCtx, InfoMessage)
case C.EVENT_LOG_WARNING:
payload = logMessageCbFromEvent(hcCtx, WarnMessage)
case C.EVENT_BITMAP_INIT_PRE:
payload = logHashcatAction(id, "Generating bitmap tables")
case C.EVENT_BITMAP_INIT_POST:
payload = logHashcatAction(id, "Generated bitmap tables")
case C.EVENT_CALCULATED_WORDS_BASE:
if hcCtx.user_options.keyspace {
payload = logHashcatAction(id, fmt.Sprintf("Calculated Words Base: %d", hcCtx.status_ctx.words_base))
}
case C.EVENT_HASHLIST_SORT_SALT_PRE:
payload = logHashcatAction(id, "Sorting salts...")
case C.EVENT_HASHLIST_SORT_SALT_POST:
payload = logHashcatAction(id, "Sorted salts...")
case C.EVENT_BACKEND_SESSION_PRE:
payload = logHashcatAction(id, "Initializing device kernels and memory")
case C.EVENT_BACKEND_SESSION_POST:
payload = logHashcatAction(id, "Initialized device kernels and memory")
case C.EVENT_AUTOTUNE_STARTING:
payload = logHashcatAction(id, "Starting Autotune threads")
case C.EVENT_AUTOTUNE_FINISHED:
payload = logHashcatAction(id, "Autotune threads have started..")
case C.EVENT_OUTERLOOP_MAINSCREEN:
hashes := ctx.wrapper.ctx.hashes
payload = TaskInformationPayload{
NumHashes: uint32(hashes.hashes_cnt_orig),
NumHashesUnique: uint32(hashes.digests_cnt),
NumSalts: uint32(hashes.salts_cnt),
}
case C.EVENT_MONITOR_PERFORMANCE_HINT:
payload = logHashcatAction(id, "Device performance might be suffering due to a less than optimal configuration")
case C.EVENT_POTFILE_REMOVE_PARSE_PRE:
payload = logHashcatAction(id, "Comparing hashes with potfile entries...")
case C.EVENT_POTFILE_REMOVE_PARSE_POST:
payload = logHashcatAction(id, "Compared hashes with potfile entries")
case C.EVENT_POTFILE_ALL_CRACKED:
payload = logHashcatAction(id, "All hashes exist in potfile")
if ctx.isEventPatched {
C.potfile_handle_show(&ctx.wrapper.ctx)
}
payload = FinalStatusPayload{
Status: nil,
EndedAt: time.Now().UTC(),
AllHashesCracked: true,
}
case C.EVENT_SET_KERNEL_POWER_FINAL:
payload = logHashcatAction(id, "Approaching final keyspace, workload adjusted")
case C.EVENT_POTFILE_NUM_CRACKED:
ctxHashes := hcCtx.hashes
if ctxHashes.digests_done > 0 {
payload = logHashcatAction(id, fmt.Sprintf("Removed %d hash(s) found in potfile", ctxHashes.digests_done))
}
case C.EVENT_CRACKER_HASH_CRACKED, C.EVENT_POTFILE_HASH_SHOW:
// Grab the separator for this session out of user options
userOpts := hcCtx.user_options
sepr := C.GoString(&userOpts.separator)
// XXX(cschmitt): What changed here that this is no longer set?
if sepr == "" {
sepr = ":"
}
msg := C.GoString((*C.char)(buf))
if payload, err = getCrackedPassword(id, msg, sepr); err != nil {
payload = logMessageWithError(id, err)
}
case C.EVENT_OUTERLOOP_FINISHED:
payload = FinalStatusPayload{
Status: ctx.GetStatus(),
EndedAt: time.Now().UTC(),
}
}
// Events we're ignoring:
// EVENT_CRACKER_STARTING
// EVENT_OUTERLOOP_MAINSCREEN
ctx.cb(unsafe.Pointer(hcCtx), payload)
}
// Free releases all allocations. Call this when you're done with hashcat or exiting the application
func (hc *Hashcat) Free() {
C.hashcat_destroy(&hc.wrapper.ctx)
C.free(unsafe.Pointer(hc.executablePath))
C.free(unsafe.Pointer(hc.sharedPath))
}