* dirty bad mkwinsyscall example
* this feels bad but it works so im ok with it
* start of rewrite
* technically make works, need docs
* hanlde errors better, stage for PR
* no more global
* docs and alt function names
* remove old folder
* minor updates etc
* properly support tracing and raw mode
* update readme
* lazymerge
- `GetPEB` return the memory location of the PEB without performing any API calls. At it's core, just does this: `MOVQ 0x60(GS), AX ; MOVQ AX, ret+0(FP)`(this is the Go ASM syntax, incase you're confused.)
15
15
- `GetNtdllStart` return the start address of ntdll loaded in process memory. Does not make any API calls (see asm_x64.s for details)
16
16
- `WriteMemory` take a byte slice, and write it to a certain memory address (may panic if not writable etc lol)
17
-
- A handful of predefined kernel calls like `NtAllocateVirtualMemory` etc. See source for more details and whatnot.
17
+
- ~A handful of predefined kernel calls like `NtAllocateVirtualMemory` etc. See source for more details and whatnot.~
18
+
- A direct version of `mkwinsyscall` (`mkdirectwinsyscall`in the cmd dir) which should make it easy for you to resolve and use syscalls, and now I don't have to support them :).
18
19
19
20
All of the PE parsing and extraction of interesting information is provided by https://github.com/Binject/debug, which adds on to the stdlib `pe` library in some very cool ways.
20
21
21
22
# Usage
22
23
23
24
See examples in `example/`.
25
+
26
+
See mkdirectwinsyscall readme in `cmd/mkdirectwinsyscall`, and example of use in `example`.
// function name and args (get everything between first set of brackets)
66
+
prefix, body, s, found := extractSection(s, '(', ')')
67
+
if !found || prefix == "" {
68
+
return nil, fmt.Errorf("Could not extract function name and parameters from %s", f.src)
69
+
}
70
+
71
+
f.Name = prefix //this is the name of the function that appears in the final src
72
+
var err error
73
+
f.Params, err = extractParams(body, f) //this is all the params we found
74
+
if err != nil {
75
+
return nil, err
76
+
}
77
+
if f.Propermode == "raw" {
78
+
f.Params = append([]*Param{&Param{
79
+
Name: "sysid",
80
+
Type: "uint16",
81
+
}}, f.Params...)
82
+
}
83
+
84
+
// return values
85
+
// must have *all* return values in ()'s I guess?
86
+
_, body, s, found = extractSection(s, '(', ')')
87
+
if found {
88
+
//we have a bunch of name/type pairs
89
+
r, err := extractParams(body, f)
90
+
if err != nil {
91
+
return nil, err
92
+
}
93
+
switch len(r) {
94
+
case 0: //no return - probs not a good sign tbh
95
+
case 1: //one return, can only be err or some ret val I guess?
96
+
if r[0].IsError() {
97
+
f.Rets.ReturnsError = true
98
+
} else {
99
+
f.Rets.Name = r[0].Name
100
+
f.Rets.Type = r[0].Type
101
+
}
102
+
case 2: //two returns
103
+
if !r[1].IsError() { //this seems kinda cooked, but whatever. second return must be an error type
104
+
return nil, errors.New("Only last windows error is allowed as second return value in \"" + f.src + "\"")
105
+
}
106
+
f.Rets.ReturnsError = true
107
+
f.Rets.Name = r[0].Name
108
+
f.Rets.Type = r[0].Type
109
+
default:
110
+
return nil, errors.New("Too many return values in \"" + f.src + "\"")
111
+
}
112
+
}
113
+
114
+
// success condition
115
+
_, body, s, found = extractSection(s, '[', ']')
116
+
if found {
117
+
f.Rets.SuccessCond = body
118
+
}
119
+
120
+
s = strings.TrimSpace(s)
121
+
if s == "" {
122
+
return f, nil
123
+
}
124
+
if !strings.HasPrefix(s, "=") {
125
+
return nil, errors.New("Could not extract dll name from \"" + f.src + "\"")
126
+
}
127
+
s = strings.TrimSpace(s[1:])
128
+
a := strings.Split(s, ".")
129
+
switch len(a) {
130
+
case 1:
131
+
f.dllfuncname = a[0]
132
+
case 2:
133
+
f.dllname = a[0]
134
+
f.dllfuncname = a[1]
135
+
default:
136
+
return nil, errors.New("Could not extract dll name from \"" + f.src + "\"")
137
+
}
138
+
139
+
return f, nil
140
+
}
141
+
142
+
// HasStringParam is true, if f has at least one string parameter.
143
+
// Otherwise it is false. This requires us to wrap it, and provide a byte pointer - this is done by creating a helper function rather than wrapping it in the func body because?????? idk
144
+
func (f *Fn) HasStringParam() bool {
145
+
for _, p := range f.Params {
146
+
if p.Type == "string" {
147
+
return true
148
+
}
149
+
}
150
+
return false
151
+
}
152
+
153
+
// HelperName returns name of function f helper.
154
+
func (f *Fn) HelperName() string {
155
+
if !f.HasStringParam() {
156
+
return f.Name
157
+
}
158
+
return "_" + f.Name
159
+
}
160
+
161
+
// HelperParamList returns source code for helper function f parameters.
// StrconvType returns Go type name used for OS string for f.
171
+
func (f *Fn) StrconvType() string {
172
+
if f.IsUTF16() {
173
+
return "*uint16"
174
+
}
175
+
return "*byte"
176
+
}
177
+
178
+
// IsUTF16 is true, if f is W (utf16) function. It is false
179
+
// for all A (ascii) functions.
180
+
func (f *Fn) IsUTF16() bool {
181
+
s := f.DLLFuncName()
182
+
return s[len(s)-1] == 'W'
183
+
}
184
+
185
+
// DLLFuncName returns DLL function name for function f.
186
+
func (f *Fn) DLLFuncName() string {
187
+
if f.dllfuncname == "" {
188
+
return f.Name
189
+
}
190
+
return f.dllfuncname
191
+
}
192
+
193
+
//BananaLoader is which technique BananaPhone should use to resolve syscalls. A raw loader does not load syscalls at all (if the user wants to bundle syscalls directly, without resolving dynamic)
194
+
func (f *Fn) BananaLoader() string {
195
+
if f.Propermode == "raw" {
196
+
return `` //no loader, because user indicates they know what they are doing :smirkemoji:
197
+
}
198
+
yaboi := ``
199
+
if f.Global {
200
+
yaboi = `if bpGlobal == nil {` + //check if our bp is nill or not (maybe something broke it during init?)
filename = flag.String("output", "", "output file name (standard output if omitted)")
25
+
printTraceFlag = flag.Bool("trace", false, "generate print statement after every syscall")
26
+
mode = flag.String("mode", "auto", "Which bananaphone mode to use (default auto, anything not in the following options results in auto) Options: disk,memory,raw")
27
+
noglobal = flag.Bool("noglobal", false, "Do not use a global var (embed the bananaphone object into each function)")
28
+
)
29
+
30
+
func main() {
31
+
log.SetFlags(log.LstdFlags | log.Lshortfile)
32
+
flag.Usage = usage
33
+
flag.Parse()
34
+
if len(flag.Args()) <= 0 {
35
+
fmt.Fprintf(os.Stderr, "no files to parse provided\n")
**warning** This code, and interface is likely to change. If you use it and it works, that's rad, but it might not work and/or break interfaces on the next commit. pls no rely on it (yet) thx.
4
+
5
+
This is mostly a re-write of go/src/golang.org/x/sys/windows/mkwinsyscall to fit in with bananaphone good.
6
+
7
+
## Flags
8
+
### Mode
9
+
`-mode`
10
+
Option | Description
11
+
------------- | -------------
12
+
auto | Automatically resolves sysID. First attempts to resolve in-memory, then falls back to disk. This is the default option.
13
+
memory | Resolves sysID's by locating ntdll in memory by parsing the PEB, enumerating exports, and extracting the sysID from the function pointers.
14
+
disk | Resolves sysID's by parsing ntdll on-disk. Uses filesystem read API's built in to go.
15
+
raw | Does not resolve sysID's. Functions must be provided the sysID externally. (this may be useful if you want to hard-code them into the bin to avoid detection on resolution).
16
+
17
+
### Globals
18
+
`-noglobal`
19
+
20
+
This option will not use a global var for sys ID resolution. This means the PEB will be parsed/ntdll.dll on disk will be read *every time* the generated function is called. This might be what you want for some insane reason, no judgement here.
21
+
22
+
### Tracing
23
+
`-trace`
24
+
25
+
Is everything just not really working, and you have no idea what values are being given to your syscalls? This option will print every param + the returns etc for your debugging pleasure. It uses `print(` which I didn't even realise was a thing until I saw it in mkwinsyscall! TIL!
26
+
27
+
### Output
28
+
`-output`
29
+
30
+
Specify the output filename. This overwrites the file if it exists (because that's useful when doing `go generate`).
31
+
32
+
## Usage
33
+
34
+
It's recommended that you have a file specific for your syscalls, but in theory, you only need the following signature:
35
+
36
+
```golang
37
+
//dsys funcname(param type) (err error)
38
+
```
39
+
40
+
You can optionally include a suffix that allows you to have a different go function name, but calls a specified Windows API call. Below will create a func named `whateveryouwant()`, and resolve the `NtAllocateVirtualMemory` syscall ID.
Once you have your syscall.go file (or whatever it's called), you can run this program against it (`mkdirectwinsyscall syscall.go`). By default this will print to stdout.
53
+
54
+
Pro move is to use `go generate` to do it all for you. Include a line like this in `syscall.go` and then run `go generate .` in the package that has the file:
55
+
56
+
```golang
57
+
//go:generate go run github.com/C-Sto/BananaPhone/cmd/mkdirectwinsyscall -output zsyscall_windows.go syscall.go
58
+
```
59
+
60
+
This will run the generator, and replace the zsyscall_windows.go file with a newly generated version.
61
+
62
+
63
+
## Success/Error values:
64
+
65
+
See ntstatus.go
66
+
67
+
As far as I can tell, all Nt function calls (which are the only relevant function calls in this lib besides Zw) have a single return value of type NtStatus. This is a 32 bit value (https://doxygen.reactos.org/d9/d6f/base_2applications_2drwtsn32_2precomp_8h.html#a1b7a3ae6a8dcfbde0f16bfaffa13b024) with a bunch of different possible meanings. The `os` or `syscall` package should probably support these values better than I can.
68
+
69
+
At present, the return value for the `Syscall` function is *always* checked against 0, and if not 0, err is filled with a message that contains the hex representation of NtStatus (incase your func def omitted it, but still had the err value for some reason). If your return is between 0 and 0x3FFFFFFF, you need to check the err value for that, or specify the expected value in `[]`'s, just like the normal mkwinsyscall.
//Syscall calls the system function specified by callid with n arguments. Works much the same as syscall.Syscall - return value is the call error code and optional error text. All args are uintptrs to make it easy.
skipped 71 lines
84
79
return getSysIDFromDisk(funcname, 0, false)
85
80
}
86
81
87
-
const (
88
-
thisThread = uintptr(0xffffffffffffffff) //special macro that says 'use this thread/process' when provided as a handle.
89
-
memCommit = uintptr(0x00001000)
90
-
memreserve = uintptr(0x00002000)
91
-
)
92
-
93
-
//NtAllocateVirtualMemory is the function signature for the as-named syscall. Provide the syscall ID either dynamically (using a GetSysID function or similar), or hard-code it (but be aware that the ID changes in different versions of Windows).
//WriteMemory writes the provided memory to the specified memory address. Does **not** check permissions, may cause panic if memory is not writable etc.
//NtCreateThreadEx is the function signature for the as-named syscall. Provide the syscall ID either dynamically (using a GetSysID function or similar), or hard-code it (but be aware that the ID changes in different versions of Windows).
//CreateRemoteThreadMemory does CreateThread with the specified shellcode and PID, reading sysid's from memory. (See CreateRemoteThread for more info).
CreateThread takes shellcode, and a handle, and performs NtAllocate, NtProtect, and finally an NtCreateThread. Provide Syscall ID's either dynamically (using a GetSysID function or similar), or hard-code it (but be aware that the ID changes in different versions of Windows).
226
-
227
-
**Fair warning**: there is no wait in here. Threads are hard, and creating a thread in a remote process seems to create a race condition of some sort that kills a bunch of stuff. YMMV, use with caution etc.
228
-
229
-
Relevant enums for each of the functions can be found below: