Home Malware Development 9 - Protecting processes as blockdlls (Golang)
Post
Cancel

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

Go back to top

This post is licensed under CC BY 4.0 by the author.