[2024]基于C++的混淆壳(三) shellcode与oep编写 huoji 壳 2024-03-13 236 次浏览 0 次点赞 ### 做壳步骤 创建区段 遍历所有指令 识别指令类型 加花/混淆/处理跳转表 处理原来老的区段 处理PE入口点 <-先说到这里 处理PE头 ### OEP编写 这块是比较麻烦的,总的来说,先保存老的OEP,然后复制shellcode到OEP里面,然后这个shellcode的OEP负责处理IAT处理加密并且重定位,这部分有三个shellcode完成 ### shellcode_entry 这部分是入口点shellcode,负责处理各种事务 先找到duck区域,通过PEB得到base ```cpp auto peb = (uint64_t)__readgsqword(0x60); auto base = *(uint64_t*)(peb + 0x10); PIMAGE_NT_HEADERS nt = (PIMAGE_NT_HEADERS)(base + ((PIMAGE_DOS_HEADER)base)->e_lfanew); PIMAGE_SECTION_HEADER section = nullptr; auto first = IMAGE_FIRST_SECTION(nt); char duck[] = { '.', 'd', 'u', 'c', 'k' }; for (int i = 0; i < nt->FileHeader.NumberOfSections; i++) { auto currsec = first[i]; if (!_my_strcmp((char*)currsec.Name, duck)) { section = &currsec; break; } } ``` 然后定位到存放在区段头的inittable: ```cpp _initTable* initTable = (_initTable*)(base + section->VirtualAddress); ShellCode_GetProcAddressR fnGetProcAddress = (ShellCode_GetProcAddressR)((uint64_t)initTable + initTable->fnGetProcAddressOffset); ShellCode_GetKernel32Base fnGetKernel32Base = (ShellCode_GetKernel32Base)((uint64_t)initTable + initTable->fnGetKernel32BaseOffset); ``` 之后就是修复IAT 跳转到真头了: ```cpp fnGetProcAddressA = (GetProcAddressAT)fnGetProcAddress(kernel32Handle, strGetProcAddress); fnLoadLibraryA = (LoadLibraryAT)fnGetProcAddress(kernel32Handle, strLoadlibraryA); VirtualProtectT fnVirtualProtect = (VirtualProtectT)fnGetProcAddress(kernel32Handle, strVirtualProtect); DWORD oldProtect = 0; fnVirtualProtect(initTable->iatTable, initTable->iatTableSize * sizeof(_iatTable), PAGE_EXECUTE_READWRITE, &oldProtect); for (int i = 0; i < initTable->iatTableSize; i++) { const auto iatTable = &initTable->iatTable[i]; const auto DllName = iatTable->dllName; const auto FunctionName = iatTable->functionName; const auto dllBase = (uint64_t)fnLoadLibraryA(DllName); auto functionAddr = fnGetProcAddress(dllBase, FunctionName); if (functionAddr == NULL) { //前置导出 functionAddr = fnGetProcAddressA((HMODULE)dllBase, FunctionName); } iatTable->functionAddress = functionAddr; } fnVirtualProtect(initTable->iatTable, initTable->iatTableSize * sizeof(_iatTable), oldProtect, &oldProtect); uint32_t real_entry = initTable->entryPoint; return reinterpret_cast(base + real_entry)(argc, argv); ``` 这边前置导出我直接不管了,直接用系统的getprocaddress ### shellcode-getprocaddr 这部分是寻找导出表获得函数地址.标准shellcode操作了: ```cpp ULONG_PTR __stdcall shellCode_GetProcAddressR(uint64_t PeData, const char* ExportName) { PIMAGE_DOS_HEADER DosHeaderPtr = NULL; PIMAGE_NT_HEADERS NtHeaderPtr = NULL; PIMAGE_DATA_DIRECTORY DataDirPtr = NULL; PIMAGE_EXPORT_DIRECTORY ExportDirPtr = NULL; ULONG ExportDirRva = 0; ULONG ExportDirSize = 0; ULONG* AddressOfFunctions = NULL; USHORT* AddressOfNameOrdinals = NULL; ULONG* AddressOfNames = NULL; ULONG64 FuncAddr = NULL; do { DosHeaderPtr = (PIMAGE_DOS_HEADER)PeData; if (DosHeaderPtr->e_magic != IMAGE_DOS_SIGNATURE) { break; } NtHeaderPtr = (PIMAGE_NT_HEADERS)((ULONG_PTR)PeData + DosHeaderPtr->e_lfanew); if (NtHeaderPtr->Signature != IMAGE_NT_SIGNATURE) { break; } if (NtHeaderPtr->OptionalHeader.Magic == IMAGE_NT_OPTIONAL_HDR64_MAGIC) { DataDirPtr = ((PIMAGE_NT_HEADERS64)NtHeaderPtr)->OptionalHeader.DataDirectory; } else if (NtHeaderPtr->OptionalHeader.Magic == IMAGE_NT_OPTIONAL_HDR32_MAGIC) { DataDirPtr = ((PIMAGE_NT_HEADERS32)NtHeaderPtr)->OptionalHeader.DataDirectory; } else { break; } ExportDirRva = DataDirPtr[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress; ExportDirSize = DataDirPtr[IMAGE_DIRECTORY_ENTRY_EXPORT].Size; ExportDirPtr = (PIMAGE_EXPORT_DIRECTORY)((ULONG_PTR)PeData + ExportDirRva); if (ExportDirPtr == NULL || ExportDirPtr->NumberOfNames == 0 || ExportDirPtr->AddressOfFunctions == 0 || ExportDirPtr->AddressOfNameOrdinals == 0 || ExportDirPtr->AddressOfNames == 0) { break; } AddressOfFunctions = (ULONG*)((ULONG_PTR)PeData + ExportDirPtr->AddressOfFunctions); AddressOfNameOrdinals = (USHORT*)((ULONG_PTR)PeData + ExportDirPtr->AddressOfNameOrdinals); AddressOfNames = (ULONG*)((ULONG_PTR)PeData + ExportDirPtr->AddressOfNames); for (ULONG i = 0; i < ExportDirPtr->NumberOfNames; i++) { if (AddressOfNames[i] == 0) continue; ULONG CurrentFuncRva = AddressOfFunctions[AddressOfNameOrdinals[i]]; PCSTR CurrentFuncName = (PCSTR)((ULONG_PTR)PeData + AddressOfNames[i]); PVOID CurrentExportFuncAddr = (PVOID)((ULONG_PTR)PeData + CurrentFuncRva); if (CurrentFuncRva >= ExportDirRva && CurrentFuncRva <= ExportDirRva + ExportDirSize) { continue; // we ignore forwarded exports } if (_my_strcmp(CurrentFuncName, ExportName) == 0) { FuncAddr = (ULONG64)CurrentExportFuncAddr; break; } } } while (false); return FuncAddr; } ``` ### shellcode-getkernel32base 也是标准shellcode操作了: ```cpp uint64_t __stdcall shellcode_GetKernel32Base(uint64_t peb) { // __inline失效了 USHORT ldrCounter; uint64_t ldrTable; uint64_t ldrDllName; uint64_t ldrHashName; uint64_t kernel32Handle; auto base_address = (uint64_t)((_PEB*)peb)->pLdr; ldrTable = (uint64_t)((PPEB_LDR_DATA)base_address)->InMemoryOrderModuleList.Flink; while (ldrTable) { ldrDllName = (uint64_t)((PLDR_DATA_TABLE_ENTRY)ldrTable)->BaseDllName.pBuffer; ldrCounter = ((PLDR_DATA_TABLE_ENTRY)ldrTable)->BaseDllName.Length; ldrHashName = 0; do { ldrHashName = ror((DWORD)ldrHashName); if (*((BYTE*)ldrDllName) >= 'a') ldrHashName += *((BYTE*)ldrDllName) - 0x20; else ldrHashName += *((BYTE*)ldrDllName); ldrDllName++; } while (--ldrCounter); if ((DWORD)ldrHashName == 0x6A4ABC5B) { kernel32Handle = (uint64_t)((PLDR_DATA_TABLE_ENTRY)ldrTable)->DllBase; break; } ldrTable = DEREF(ldrTable); } return kernel32Handle; }; ``` ### 初始化这些shellcode 先寻找IAT: ```cpp PIMAGE_DATA_DIRECTORY importDirectory = &((PIMAGE_NT_HEADERS64)(peBase + ((PIMAGE_DOS_HEADER)peBase)->e_lfanew)) ->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT]; if (importDirectory->Size > 0) { // 获取第一个导入描述符 PIMAGE_IMPORT_DESCRIPTOR importDescriptor = (PIMAGE_IMPORT_DESCRIPTOR)(peBase + importDirectory->VirtualAddress); while (importDescriptor->Name != 0) { // 获取IAT PIMAGE_THUNK_DATA64 thunk = (PIMAGE_THUNK_DATA64)(peBase + importDescriptor->FirstThunk); while (thunk->u1.AddressOfData != 0) { LPCSTR function_name; bool isOrdinal = (thunk->u1.Ordinal & IMAGE_ORDINAL_FLAG); if (isOrdinal) { //function_name = (LPCSTR)(thunk->u1.Ordinal & 0xFFFF); function_name = NULL; } else { PIMAGE_IMPORT_BY_NAME functionName = (PIMAGE_IMPORT_BY_NAME)(peBase + thunk->u1.AddressOfData); function_name = (LPCSTR)functionName->Name; } if (function_name) { char* dllName = (char*)(peBase + importDescriptor->Name); // 将信息添加到IatTable _BuildIAT entry; entry.functionName = std::string(function_name); entry.dllName = std::string(dllName); entry.inCodeSectionAddress = (uint64_t)thunk->u1.Function; IatTable.push_back(entry); } // 移动到下一个thunk thunk++; } // 移动到下一个导入描述符 importDescriptor++; } } ``` 复制iat到区段头部: ```cpp // 复制初始化信息到壳段头部 const auto copyiedSize = sizeof(_initTable) + (sizeof(_iatTable) * IatTable.size()); _initTable* initTable = (_initTable*)malloc(copyiedSize); if (initTable == nullptr) { // 这不是 驱动,没有低资源模式. __debugbreak(); } memset(initTable, 0x0, copyiedSize); auto startBase = (uint64_t)sectionBase; /* initTable shellcode_init */ _initTable* insertTable = (_initTable*)(sectionBase); for (size_t i = 0; i < IatTable.size(); i++) { const auto entry = &IatTable[i]; // 不可能大于64字节 if (entry->dllName.size() > sizeof(initTable->iatTable[i].dllName)) { __debugbreak(); } memcpy(initTable->iatTable[i].dllName, entry->dllName.c_str(), entry->dllName.size()); memcpy(initTable->iatTable[i].functionName, entry->functionName.c_str(), entry->functionName.size()); entry->buildAddress = (uint64_t) & (&insertTable->iatTable[i])->functionAddress; //printf("build address :%p \n", entry->buildAddress); } ``` 保存真实入口点: ```cpp // initTable initTable->iatTableSize = IatTable.size(); initTable->entryPoint = peFile->get_nt()->OptionalHeader.AddressOfEntryPoint; printf("[+] save old ep: %p \n",(uint64_t)peBase + initTable->entryPoint); memcpy((void*)startBase, initTable, copyiedSize); startBase += copyiedSize; ``` 复制shellcode到壳段: ```cpp // 复制初始化函数到壳段 // 初始化函数 auto shellcodeStartAddress = (uint64_t)shellcode_init; auto shellcodeSize = calcShellCodeSizeForMsvcBuged(shellcodeStartAddress); // shellcode_init memcpy((void*)startBase, shellcode_init, shellcodeSize); shellEntryPointAddress = (uint64_t)startBase; startBase += shellcodeSize; usedSectionOffset += shellcodeSize; // getprocaddress函数 shellcodeStartAddress = (uint64_t)shellCode_GetProcAddressR; shellcodeSize = calcShellCodeSizeForMsvcBuged(shellcodeStartAddress); // shellcode_init memcpy((void*)startBase, shellCode_GetProcAddressR, shellcodeSize); insertTable->fnGetProcAddressOffset = (uint32_t)((uint64_t)startBase - (uint64_t)sectionBase); startBase += shellcodeSize; usedSectionOffset += shellcodeSize; // getkernel32base函数 shellcodeStartAddress = (uint64_t)shellcode_GetKernel32Base; shellcodeSize = calcShellCodeSizeForMsvcBuged(shellcodeStartAddress); // shellcode_init memcpy((void*)startBase, shellcode_GetKernel32Base, shellcodeSize); insertTable->fnGetKernel32BaseOffset = (uint32_t)((uint64_t)startBase - (uint64_t)sectionBase); startBase += shellcodeSize; usedSectionOffset += shellcodeSize; usedSectionOffset += copyiedSize + (sizeof(void*) * IatTable.size()); printf("[+] init StartUpShellCodeat at 0x%llx Size: %d \n", startBase, shellcodeSize); ``` 之前遇到个头疼问题,如果是以前的操作,end-start 地址,单个shellcode还好,多个总是失效(msvc) 无论怎么操作都会失效,所以自己写了个函数计算长度: ```cpp auto calcShellCodeSizeForMsvcBuged(uint64_t functionAddress) -> size_t { int ccNum = 0; size_t theSize = 0; while (true) { auto sigCode = ((unsigned char*)functionAddress)[theSize]; if (sigCode == 0xcc) { ccNum++; } else { ccNum == 0; } if (ccNum > 2) { break; } theSize++; } return theSize; } ``` ### 结尾 未完待续... 本文由 huoji 创作,采用 知识共享署名 3.0,可自由转载、引用,但需署名作者且注明文章出处。 点赞 0
还不快抢沙发