diff --git a/.github/workflows/cd.yml b/.github/workflows/cd.yml index 7800ed2..8ae5d4d 100644 --- a/.github/workflows/cd.yml +++ b/.github/workflows/cd.yml @@ -27,6 +27,8 @@ jobs: run: | git clone https://github.com/Microsoft/vcpkg.git cd vcpkg + # Dirty hack + git checkout a325228200d7f229f3337e612e0077f2a5307090 .\bootstrap-vcpkg.bat # Install vcpkg dependencies diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 69617e6..c1c6da4 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -23,6 +23,7 @@ jobs: run: | git clone https://github.com/Microsoft/vcpkg.git cd vcpkg + git checkout a325228200d7f229f3337e612e0077f2a53 .\bootstrap-vcpkg.bat # Install vcpkg dependencies diff --git a/README.md b/README.md index 1bd8642..02b5f59 100644 --- a/README.md +++ b/README.md @@ -114,4 +114,3 @@ Please open an issue or pull request for any changes you would like to make. This cool mascot image was inspired on Bleach and generated by [Dall-E](https://openai.com/product/dall-e-2). - diff --git a/Shinigami/Ichigo/Mem.h b/Shinigami/Ichigo/Mem.h index f849390..e9d511f 100644 --- a/Shinigami/Ichigo/Mem.h +++ b/Shinigami/Ichigo/Mem.h @@ -22,6 +22,7 @@ struct Memory && cAlloc == other.cAlloc; } + ULONG_PTR IP; // If this memory is being executed, this value holds the offset of the current execution, must be set manually uint8_t* Addr; ULONG_PTR End; SIZE_T Size; diff --git a/Shinigami/Ichigo/PEDumper.cpp b/Shinigami/Ichigo/PEDumper.cpp index 789e649..e55c6b5 100644 --- a/Shinigami/Ichigo/PEDumper.cpp +++ b/Shinigami/Ichigo/PEDumper.cpp @@ -60,30 +60,179 @@ Memory* PEDumper::DumpPE(ULONG_PTR* Address) return mem; } - PIMAGE_DOS_HEADER PEDumper::FindPE(Memory* Mem) { PIMAGE_DOS_HEADER pDosHeader; PIMAGE_NT_HEADERS pNtHeader; + MEMORY_BASIC_INFORMATION mbi; - for (uint8_t* Curr = reinterpret_cast(Mem->Addr); (ULONG_PTR)Curr < Mem->End; Curr++) + for (uint8_t* Curr = reinterpret_cast(Mem->Addr); (ULONG_PTR)Curr < Mem->End - sizeof(IMAGE_DOS_HEADER); Curr++) { pDosHeader = reinterpret_cast(Curr); + if (pDosHeader->e_magic == IMAGE_DOS_SIGNATURE) { pNtHeader = reinterpret_cast((ULONG_PTR)pDosHeader + pDosHeader->e_lfanew); - if ((ULONG_PTR)pNtHeader <= Mem->End - sizeof(pNtHeader) && - pNtHeader->Signature == IMAGE_NT_SIGNATURE) + + if ((ULONG_PTR)pNtHeader <= Mem->End) { - return pDosHeader; + if (!VirtualQuery((LPCVOID)pNtHeader, &mbi, 0x1000)) + continue; + + if (pNtHeader->Signature == IMAGE_NT_SIGNATURE) + return pDosHeader; } } } + // Search for detached headers + return PEDumper::HeuristicSearch(Mem); +} + +PIMAGE_DOS_HEADER PEDumper::HeuristicSearch(Memory* Mem) +{ + PipeLogger::LogInfo(L"Starting heuristic scan for detached headers at 0x%p...", Mem->Addr); + // Search for NT headers + // If found, validated sections offset and if it has code + PIMAGE_NT_HEADERS pNtHeader; + MEMORY_BASIC_INFORMATION mbi; + + for (uint8_t* Curr = reinterpret_cast(Mem->Addr); (ULONG_PTR)Curr < Mem->End - sizeof(IMAGE_NT_HEADERS); Curr++) + { + pNtHeader = reinterpret_cast(Curr); + if (IsValidNT(pNtHeader)) + { + // So far so good + PipeLogger::LogInfo(L"Found possible NT header at 0x%p", Curr); + + if (!VirtualQuery((LPCVOID)&pNtHeader->OptionalHeader, &mbi, sizeof(IMAGE_OPTIONAL_HEADER))) + continue; + + if ((ULONG_PTR) Mem->Addr + pNtHeader->OptionalHeader.AddressOfEntryPoint == Mem->IP) + { + // We are at this executable entrypoint, rebuild the DOS header + if (Mem->Addr - Curr <= sizeof(IMAGE_DOS_HEADER)) + { + // We dont have space + // TODO: Resize Mem struct + } + + PIMAGE_DOS_HEADER DosHdr = RebuildDOSHeader(Mem, (ULONG_PTR) Curr); + if (DosHdr != nullptr) + { + PipeLogger::LogInfo(L"DOS header rebuilded!"); + return DosHdr; + } + } + else + { + // Parse each section in this possible header, verify if is at a valided the section struct data (permissions, offset...) + // If looks valid verify if the current EIP is between some of them + // If it is, log that, else log that it found valid sections mapped but the code might be hidden somehow + // Save the current NT address and proceed to the brute-force part of this code, if the brute-force fail + // use this saved NT address and tell the user + IMAGE_SECTION_HEADER* sectionHeader = IMAGE_FIRST_SECTION(pNtHeader); + bool Invalid = true; + bool IPInBetween = false; + ULONG_PTR VirtualMemAddr; + for (int i = 0; i < pNtHeader->FileHeader.NumberOfSections; i++, sectionHeader++) { + // Invalid memory area + if (Utils::IsReadWritable((ULONG_PTR*)sectionHeader) == INVALID_MEMORY_AREA) { + Invalid = true; + break; + } + + VirtualMemAddr = sectionHeader->VirtualAddress + (ULONG_PTR)Mem->Addr; + // Check if there is any overflow here + if (sectionHeader->PointerToRawData + sectionHeader->SizeOfRawData + (ULONG_PTR) Mem->Addr >= Mem->End || + sectionHeader->VirtualAddress + sectionHeader->Misc.VirtualSize + (ULONG_PTR) Mem->Addr >= Mem->End) + { + Invalid = true; + break; + } + + // Check if the Instruction pointer is between this image + if (Mem->IP >= VirtualMemAddr && Mem->IP <= VirtualMemAddr + sectionHeader->Misc.VirtualSize) + IPInBetween = true; + + } + + if (Invalid) + continue; + + if (!Invalid && IPInBetween) + { + PipeLogger::LogInfo(L"Possible NT found at 0x%p! Trying to rebuild...", pNtHeader); + PIMAGE_DOS_HEADER DosHdr = RebuildDOSHeader(Mem, (ULONG_PTR)Curr); + if (DosHdr != nullptr) + { + PipeLogger::LogInfo(L"DOS header rebuilded!"); + return DosHdr; + } + } + } + } + } + + + // We failed to search detached DOS headers + // Search for common sections names such: .text, .data, .rdata + // If found, walk back and try to rebuild the DOS headers and NT headers + + + + // We failed, return nullptr return nullptr; } +// Verify NT headers fields to validate if is valid +BOOL PEDumper::IsValidNT(PIMAGE_NT_HEADERS pNtHeader) +{ + return pNtHeader->Signature == IMAGE_NT_SIGNATURE && + ( + pNtHeader->OptionalHeader.Subsystem == IMAGE_SUBSYSTEM_UNKNOWN || + pNtHeader->OptionalHeader.Subsystem == IMAGE_SUBSYSTEM_NATIVE || + pNtHeader->OptionalHeader.Subsystem == IMAGE_SUBSYSTEM_WINDOWS_GUI || + pNtHeader->OptionalHeader.Subsystem == IMAGE_SUBSYSTEM_WINDOWS_CUI || + pNtHeader->OptionalHeader.Subsystem == IMAGE_SUBSYSTEM_OS2_CUI || + pNtHeader->OptionalHeader.Subsystem == IMAGE_SUBSYSTEM_POSIX_CUI || + pNtHeader->OptionalHeader.Subsystem == IMAGE_SUBSYSTEM_WINDOWS_CE_GUI || + pNtHeader->OptionalHeader.Subsystem == IMAGE_SUBSYSTEM_UNKNOWN + ) + && + ( + pNtHeader->OptionalHeader.Magic == IMAGE_NT_OPTIONAL_HDR32_MAGIC || + pNtHeader->OptionalHeader.Magic == IMAGE_NT_OPTIONAL_HDR64_MAGIC + ); +} + + +PIMAGE_DOS_HEADER PEDumper::RebuildDOSHeader(Memory* Mem, ULONG_PTR NtHeaderOffset) +{ + // This function rely in the fact that we already have space allocated, if there is no space in the beginning of this file + // the memory must be resized calling the mem.IncreaseSize(sizeof(IMAGE_DOS_HEADER), SHIFT_BEGIN) before call this function + // Check if we have space + DWORD OldProt; + + if (Mem->Size < sizeof(IMAGE_DOS_HEADER) || NtHeaderOffset >= Mem->End) return nullptr; + + PIMAGE_DOS_HEADER DosHdr = reinterpret_cast(Mem->Addr); + // Rebuild basic fields only + + if (!VirtualProtect(Mem->Addr, sizeof(IMAGE_DOS_HEADER), PAGE_READWRITE, &OldProt)) + return nullptr; + + DosHdr->e_magic = IMAGE_DOS_SIGNATURE; + DosHdr->e_lfanew = NtHeaderOffset - (ULONG_PTR) DosHdr; + + VirtualProtect(Mem->Addr, sizeof(IMAGE_DOS_HEADER), OldProt, &OldProt); + FixPESections(Mem); + + return DosHdr; +} + + SIZE_T PEDumper::GetPESize(PIMAGE_NT_HEADERS pNTHeader) { // Get the first section header @@ -128,7 +277,6 @@ VOID PEDumper::FixPESections(Memory* mem) if (Utils::IsReadWritable((ULONG_PTR*)pNtHeader) == INVALID_MEMORY_AREA) { return; } - IMAGE_SECTION_HEADER* sectionHeaders = IMAGE_FIRST_SECTION(pNtHeader); // Modify the section headers diff --git a/Shinigami/Ichigo/PEDumper.h b/Shinigami/Ichigo/PEDumper.h index ae1aaa6..1e06882 100644 --- a/Shinigami/Ichigo/PEDumper.h +++ b/Shinigami/Ichigo/PEDumper.h @@ -8,7 +8,9 @@ namespace PEDumper Memory* FindRemotePE(HANDLE hProcess, const Memory* mem); Memory* DumpPE(ULONG_PTR* Address); PIMAGE_DOS_HEADER FindPE(Memory* Mem); - + PIMAGE_DOS_HEADER HeuristicSearch(Memory* Mem); + PIMAGE_DOS_HEADER RebuildDOSHeader(Memory* Mem, ULONG_PTR NtHeaderOffset); + BOOL IsValidNT(PIMAGE_NT_HEADERS pNtHeader); SIZE_T GetPESize(PIMAGE_NT_HEADERS pNTHeader); VOID FixPESections(Memory* pNTHeader); }; diff --git a/Shinigami/Ichigo/ProcessUnhollow.cpp b/Shinigami/Ichigo/ProcessUnhollow.cpp index aea6d82..1c27355 100644 --- a/Shinigami/Ichigo/ProcessUnhollow.cpp +++ b/Shinigami/Ichigo/ProcessUnhollow.cpp @@ -8,7 +8,6 @@ NTSTATUS WINAPI Unhollow::hkNtAllocateVirtualMemory(HANDLE ProcessHandle, PVOID* { NTSTATUS status = ProcessInformation.Win32Pointers.NtAllocateVirtualMemory(ProcessHandle, BaseAddress, ZeroBits, RegionSize, AllocationType, Protect); DWORD ProcessPID = GetProcessId(ProcessHandle); - if (ProcessPID == Unhollow::ProcessInformation.pi.dwProcessId && NT_SUCCESS(status)) { if (BaseAddress == nullptr) diff --git a/Shinigami/Ichigo/Unpacker.cpp b/Shinigami/Ichigo/Unpacker.cpp index 1f7831d..99b340f 100644 --- a/Shinigami/Ichigo/Unpacker.cpp +++ b/Shinigami/Ichigo/Unpacker.cpp @@ -23,8 +23,8 @@ NTSTATUS WINAPI GenericUnpacker::hkNtAllocateVirtualMemory(HANDLE ProcessHandle, } NTSTATUS status = GenericUnpacker::cUnpacker.Win32Pointers.NtAllocateVirtualMemory(ProcessHandle, BaseAddress, ZeroBits, RegionSize, AllocationType, Protect); - - if (status == STATUS_SUCCESS && Track) + + if (status == STATUS_SUCCESS) { GenericUnpacker::cUnpacker.Watcher.push_back({}); Memory& memory = GenericUnpacker::cUnpacker.Watcher.back(); @@ -32,7 +32,7 @@ NTSTATUS WINAPI GenericUnpacker::hkNtAllocateVirtualMemory(HANDLE ProcessHandle, memory.End = reinterpret_cast(memory.Addr + AllocatedSize); memory.Size = AllocatedSize; memory.prot = Protect; - PipeLogger::LogInfo(L"Tracking newly allocated memory 0x%lx with protections 0x%x", *BaseAddress, Protect); + PipeLogger::LogInfo(L"Tracking newly allocated memory 0x%p with protections 0x%x", *BaseAddress, Protect); } return status; @@ -51,12 +51,12 @@ NTSTATUS WINAPI GenericUnpacker::hkNtWriteVirtualMemory(HANDLE ProcessHandle, PV VirtualQuery(BaseAddress, &mbi, NumberOfBytesToWrite); DWORD OldProtection = mbi.Protect; - if ((ProcessHandle == NULL || GetProcessId(ProcessHandle) == GenericUnpacker::IchigoOptions->PID) && (mbi.Protect & PAGE_GUARD) && GenericUnpacker::cUnpacker.IsBeingMonitored((ULONG_PTR) BaseAddress)) + if ((ProcessHandle == NULL || GetProcessId(ProcessHandle) == GenericUnpacker::IchigoOptions->PID) && (mbi.Protect & PAGE_GUARD) && GenericUnpacker::cUnpacker.IsBeingMonitored((ULONG_PTR)BaseAddress)) { // Remove the PAGE_GUARD bit - IgnoreMap[(ULONG_PTR) BaseAddress] = TRUE; + IgnoreMap[(ULONG_PTR)BaseAddress] = TRUE; VirtualProtect(BaseAddress, NumberOfBytesToWrite, mbi.Protect & ~PAGE_GUARD, &OldProtection); - IgnoreMap[(ULONG_PTR) BaseAddress] = FALSE; + IgnoreMap[(ULONG_PTR)BaseAddress] = FALSE; } NTSTATUS status = GenericUnpacker::cUnpacker.Win32Pointers.NtWriteVirtualMemory(ProcessHandle, BaseAddress, Buffer, NumberOfBytesToWrite, NumberOfBytesWritten); @@ -69,14 +69,14 @@ NTSTATUS WINAPI GenericUnpacker::hkNtProtectVirtualMemory(HANDLE ProcessHandle, { // Verify that shit if (!GenericUnpacker::Ready) - ignore: - return GenericUnpacker::cUnpacker.Win32Pointers.NtProtectVirtualMemory(ProcessHandle, BaseAddress, RegionSize, NewProtect, OldProtect); + ignore: + return GenericUnpacker::cUnpacker.Win32Pointers.NtProtectVirtualMemory(ProcessHandle, BaseAddress, RegionSize, NewProtect, OldProtect); if (IgnoreMap.size() > 0) { auto IgnoreIter = IgnoreMap.find((ULONG_PTR)*BaseAddress); if (IgnoreIter != IgnoreMap.end() && IgnoreIter->second) - goto ignore; + goto ignore; } // Detect if it will change to a executable memory @@ -104,7 +104,7 @@ NTSTATUS WINAPI GenericUnpacker::hkNtProtectVirtualMemory(HANDLE ProcessHandle, memory.End = reinterpret_cast(memory.Addr + *RegionSize); memory.Size = *RegionSize; memory.prot = NewProtect; - PipeLogger::LogInfo(L"VirtualProtect: Tracking memory at 0x%lx with protections 0x%x", *BaseAddress, NewProtect); + PipeLogger::LogInfo(L"NtProtectVirtualMemory: Tracking memory at 0x%p with protections 0x%x", *BaseAddress, NewProtect); } } @@ -119,9 +119,10 @@ LONG WINAPI GenericUnpacker::VEHandler(EXCEPTION_POINTERS* pExceptionPointers) return EXCEPTION_CONTINUE_SEARCH; DWORD dwOldProt; + ULONG_PTR GuardedAddress; + static ULONG_PTR LastValidExceptionAddress; MEMORY_BASIC_INFORMATION mbi; PEXCEPTION_RECORD ExceptionRecord = pExceptionPointers->ExceptionRecord; - //PipeLogger::Log(L"Exception at 0x%x code %lx\n", ExceptionRecord->ExceptionAddress, ExceptionRecord->ExceptionCode); switch (ExceptionRecord->ExceptionCode) { @@ -129,31 +130,38 @@ LONG WINAPI GenericUnpacker::VEHandler(EXCEPTION_POINTERS* pExceptionPointers) // // Verify if it's being monitored and executing // - if (GenericUnpacker::cUnpacker.IsBeingMonitored((ULONG_PTR)ExceptionRecord->ExceptionAddress) && - GenericUnpacker::cUnpacker.IsBeingMonitored((ULONG_PTR)pExceptionPointers->ContextRecord->XIP)) + GuardedAddress = ExceptionRecord->ExceptionInformation[1]; + if (GenericUnpacker::cUnpacker.IsBeingMonitored((ULONG_PTR)pExceptionPointers->ContextRecord->XIP)) { - PipeLogger::LogInfo(L"STATUS_GUARD_PAGE_VIOLATION: Attempt to execute a monitored memory area at address 0x%lx, starting dumping...", ExceptionRecord->ExceptionAddress); + PipeLogger::LogInfo(L"STATUS_GUARD_PAGE_VIOLATION: Attempt to execute a monitored memory area at address 0x%p, starting dumping...", ExceptionRecord->ExceptionAddress); ULONG_PTR StartAddress = (ULONG_PTR)pExceptionPointers->ContextRecord->XIP; Memory* Mem = GenericUnpacker::cUnpacker.IsBeingMonitored(StartAddress); + Mem->IP = (ULONG_PTR) pExceptionPointers->ContextRecord->XIP; if (GenericUnpacker::cUnpacker.Dump(Mem)) { PipeLogger::Log(L"Saved stage %d as %s ", GenericUnpacker::cUnpacker.StagesPath.size(), GenericUnpacker::cUnpacker.StagesPath.back().c_str()); GenericUnpacker::cUnpacker.RemoveMonitor(Mem); } - // TODO: Check user arguments if we should continue here + } - pExceptionPointers->ContextRecord->EFlags |= 0x100; + // An exception happened, but we are not monitoring this code and this code is operating inside our monitored memory + // like an shellcode decryption process, we need to save this address to place the page_guard bit again + else if (GenericUnpacker::cUnpacker.IsBeingMonitored(GuardedAddress)) + { + LastValidExceptionAddress = GuardedAddress; + } + + pExceptionPointers->ContextRecord->EFlags |= TF; return EXCEPTION_CONTINUE_EXECUTION; case STATUS_SINGLE_STEP: // Add the PAGE_GUARD again - if (GenericUnpacker::cUnpacker.IsBeingMonitored((ULONG_PTR)ExceptionRecord->ExceptionAddress) && - GenericUnpacker::cUnpacker.IsBeingMonitored((ULONG_PTR)pExceptionPointers->ContextRecord->XIP)) + if (GenericUnpacker::cUnpacker.IsBeingMonitored(LastValidExceptionAddress)) { - VirtualQuery(ExceptionRecord->ExceptionAddress, &mbi, 0x1000); + VirtualQuery((LPCVOID) LastValidExceptionAddress, &mbi, PAGE_SIZE); mbi.Protect |= PAGE_GUARD; - VirtualProtect(ExceptionRecord->ExceptionAddress, 0x1000, mbi.Protect, &dwOldProt); + VirtualProtect((LPVOID) LastValidExceptionAddress, PAGE_SIZE, mbi.Protect, &dwOldProt); } return EXCEPTION_CONTINUE_EXECUTION; } @@ -216,8 +224,9 @@ BOOL GenericUnpacker::Unpacker::Dump(Memory* Mem) if (Mem == nullptr) return FALSE; std::wstring suffix = L"_shellcode." + std::to_wstring(StagesPath.size() + 1) + L".bin"; - PIMAGE_DOS_HEADER dosHeader= reinterpret_cast(Mem->Addr); + PIMAGE_DOS_HEADER dosHeader = reinterpret_cast(Mem->Addr); PIMAGE_NT_HEADERS NTHeaders; + if (dosHeader->e_magic == IMAGE_DOS_SIGNATURE) { // Check NT @@ -225,36 +234,37 @@ BOOL GenericUnpacker::Unpacker::Dump(Memory* Mem) if (NTHeaders->Signature == IMAGE_NT_SIGNATURE) { PEDumper::FixPESections(Mem); - suffix = L"_stage_" + std::to_wstring(StagesPath.size() + 1) + std::wstring(L".exe"); + suffix = L"_stage_" + std::to_wstring(StagesPath.size() + 1); + suffix += (NTHeaders->FileHeader.Characteristics & IMAGE_FILE_DLL) ? L".dll" : L".exe"; } } else { // Search a PE file within the region PIMAGE_DOS_HEADER pDosHeader = PEDumper::FindPE(Mem); - if (pDosHeader != nullptr) { // Found it, save as part of this stage as well Memory PeMem; PIMAGE_NT_HEADERS pNtHeaders = reinterpret_cast((ULONG_PTR)pDosHeader + pDosHeader->e_lfanew); - std::wstring EmbededPESuffix = L"_artefact_inside_stage_" + std::to_wstring(StagesPath.size() + 1) + L"_area.exe"; + std::wstring EmbededPESuffix = L"_artefact_inside_stage_" + std::to_wstring(StagesPath.size() + 1) + L"_area"; + EmbededPESuffix += (pNtHeaders->FileHeader.Characteristics & IMAGE_FILE_DLL) ? L".dll" : L".exe"; + std::wstring FileName = Utils::BuildFilenameFromProcessName(EmbededPESuffix.c_str()); std::wstring SaveName = Utils::PathJoin(GenericUnpacker::IchigoOptions->WorkDirectory, FileName); - + PeMem.Addr = reinterpret_cast(pDosHeader); PeMem.Size = PEDumper::GetPESize(pNtHeaders); PeMem.End = reinterpret_cast(PeMem.Size + PeMem.Addr); - if (Utils::SaveToFile(SaveName.c_str(), &PeMem, FALSE)) + + if (Utils::SaveToFile(SaveName.c_str(), &PeMem, TRUE)) { PipeLogger::Log(L"Found a embedded PE file inside the newly executed memory are, saved as %s!", SaveName.c_str()); - if (Ichigo::Options.OnlyPE) - { - StagesPath.push_back(SaveName); - return TRUE; - } + + StagesPath.push_back(SaveName); + return TRUE; } } } diff --git a/Shinigami/Ichigo/Unpacker.h b/Shinigami/Ichigo/Unpacker.h index cb97d9d..fad2ac1 100644 --- a/Shinigami/Ichigo/Unpacker.h +++ b/Shinigami/Ichigo/Unpacker.h @@ -14,6 +14,9 @@ #include "defs.h" #include "PEDumper.h" +#define TF 0x100 +#define PAGE_SIZE 0x1000 + namespace GenericUnpacker { static NTSTATUS WINAPI hkNtAllocateVirtualMemory( diff --git a/Shinigami/Ichigo/Utils.cpp b/Shinigami/Ichigo/Utils.cpp index 35d5a1f..893a54a 100644 --- a/Shinigami/Ichigo/Utils.cpp +++ b/Shinigami/Ichigo/Utils.cpp @@ -4,36 +4,25 @@ BOOL Utils::SaveToFile(const wchar_t* filename, Memory* data, BOOL Paginate) { - HANDLE hFile = CreateFileW(filename, FILE_APPEND_DATA, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL); + HANDLE hFile = CreateFileW(filename, GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL); BOOL success = TRUE; - + if (hFile == INVALID_HANDLE_VALUE) { return false; } - DWORD BytesWritten = 0; + DWORD BytesWritten; + DWORD OldProt; - if (Paginate) - { - // Write based on the VirtualQuery output - MEMORY_BASIC_INFORMATION mbi; - DWORD Written = 0; - while (Written < data->Size) - { - VirtualQuery(data->Addr + Written, &mbi, sizeof(mbi)); - WriteFile(hFile, data->Addr + Written, mbi.RegionSize, &BytesWritten, NULL); - Written += BytesWritten; - } - } - else - { - success = WriteFile(hFile, data->Addr, data->Size, &BytesWritten, NULL) && (BytesWritten == data->Size); - } + VirtualProtect(data->Addr, data->Size, PAGE_READWRITE, &OldProt); + + success = WriteFile(hFile, data->Addr, data->Size, &BytesWritten, NULL) && (BytesWritten == data->Size); + + VirtualProtect(data->Addr, data->Size, OldProt, &OldProt); - CloseHandle(hFile); - return success; + return TRUE; } @@ -59,7 +48,7 @@ std::wstring Utils::BuildFilenameFromProcessName(const wchar_t* suffix) // Get filename // wchar_t* exeName = PathFindFileNameW(exePath); - + // // Get the . pos // @@ -85,6 +74,5 @@ MEM_ERROR Utils::IsReadWritable(ULONG_PTR* Address) return INVALID_MEMORY_AREA; } - return (MEM_ERROR) (mbi.Protect == PAGE_EXECUTE_READWRITE || mbi.Protect == PAGE_READWRITE); + return (MEM_ERROR)(mbi.Protect == PAGE_EXECUTE_READWRITE || mbi.Protect == PAGE_READWRITE); } - diff --git a/Shinigami/Ichigo/dllmain.cpp b/Shinigami/Ichigo/dllmain.cpp index bdc7561..d1c8eae 100644 --- a/Shinigami/Ichigo/dllmain.cpp +++ b/Shinigami/Ichigo/dllmain.cpp @@ -6,7 +6,7 @@ #include "Unpacker.h" #include "Logger.h" -#define DLL_NAME "Ichigo v0.1" +#define DLL_NAME "Ichigo v1.2" #define MESSAGEBOX_ERROR_TITLE "Ichigo error" #define ERR_ICON MB_OK | MB_ICONERROR #define DLL_EXPORT __declspec(dllexport) @@ -51,10 +51,9 @@ extern "C" Ichigo::Options.Quiet = args->Quiet; Ichigo::Options.OnlyPE = args->OnlyPE; Ichigo::Options.PID = args->PID; - + PipeLogger::BeQuiet(args->Quiet); PipeLogger::LogInfo(L"Loaded user aguments"); - } DLL_EXPORT void StartIchigo() diff --git a/Shinigami/Shinigami/EncodingUtils.cpp b/Shinigami/Shinigami/EncodingUtils.cpp index 5d15b28..3134cf9 100644 --- a/Shinigami/Shinigami/EncodingUtils.cpp +++ b/Shinigami/Shinigami/EncodingUtils.cpp @@ -30,4 +30,27 @@ namespace EncodingUtils { return str; } + std::vector SplitWide(std::wstring String, const std::wstring& delimiter) + { + std::vector SplitedString; + std::wstring token; + size_t pos = 0; + + if ((pos = String.find(delimiter) == std::wstring::npos)) + { + SplitedString.push_back(String); + goto ret; + } + + while ((pos = String.find(delimiter)) != std::wstring::npos) + { + token = String.substr(0, pos); + SplitedString.push_back(token); + String.erase(0, pos + delimiter.length()); + } + + ret: + return SplitedString; + } + } diff --git a/Shinigami/Shinigami/EncodingUtils.h b/Shinigami/Shinigami/EncodingUtils.h index d19471e..9b8600f 100644 --- a/Shinigami/Shinigami/EncodingUtils.h +++ b/Shinigami/Shinigami/EncodingUtils.h @@ -1,5 +1,6 @@ #pragma once #include +#include #include namespace EncodingUtils { @@ -16,4 +17,6 @@ namespace EncodingUtils { // Convert wchar_t* to char* char* WcharToChar(const wchar_t* wstr); + // Split string + std::vector SplitWide(std::wstring String, const std::wstring& delimiter); } diff --git a/Shinigami/Shinigami/Injector.cpp b/Shinigami/Shinigami/Injector.cpp index 86c6c37..2282955 100644 --- a/Shinigami/Shinigami/Injector.cpp +++ b/Shinigami/Shinigami/Injector.cpp @@ -13,7 +13,7 @@ VOID __stdcall LoadDLL(ULONG_PTR Params) ThData->ExitProcess(ThData->GetLastError()); } SetIchigoArguments SetArgsFunc = reinterpret_cast(ThData->GetProcAddress(hModule, ThData->SetArgumentsFuncName)); - StartIchigo StartIchigoFunc = reinterpret_cast(ThData->GetProcAddress(hModule, ThData->StartIchigoFuncName)); + StartIchigo StartIchigoFunc = reinterpret_cast(ThData->GetProcAddress(hModule, ThData->StartIchigoFuncName)); if (!SetArgsFunc || !StartIchigoFunc) { ThData->ExitProcess(1); @@ -23,16 +23,27 @@ VOID __stdcall LoadDLL(ULONG_PTR Params) StartIchigoFunc(); } + +Injector::Injector(const std::wstring& ProcName) + : ProcName(ProcName) +{} + // Create a suspended process // Inject the DLL using APC callbacks -BOOL Injector::InjectSuspended(_In_ const std::wstring& DLLPath, _In_ const IchigoArguments& DLLArguments) +BOOL Injector::InjectSuspended(_In_ const std::wstring& DLLPath, _In_ const IchigoArguments& DLLArguments, BOOL IsDLL, const std::wstring& ExportedFunction) { STARTUPINFOW si; PROCESS_INFORMATION pi; - + DWORD ExitCode; ZeroMemory(&si, sizeof(si)); ZeroMemory(&si, sizeof(pi)); si.cb = sizeof(si); + + if (IsDLL) + { + ProcName = BuildRunDLLCommand(ProcName, ExportedFunction); + Logger::LogInfo(L"Target is a DLL, injecting into the rundll32 process!"); + } // Create process suspended bool status = CreateProcess( @@ -69,10 +80,9 @@ BOOL Injector::InjectSuspended(_In_ const std::wstring& DLLPath, _In_ const Ichi ResumeThread(pi.hThread); WaitForSingleObject(pi.hThread, INFINITE); - DWORD ExitCode; GetExitCodeProcess(pi.hProcess, &ExitCode); - Logger::LogInfo(L"Child process exited with code %d, closing...", ExitCode); + Logger::LogInfo(L"Child process exited with code %d, closing...", ExitCode); quit: CloseHandle(pi.hThread); CloseHandle(pi.hProcess); @@ -138,3 +148,13 @@ BOOL Injector::APCLoadDLL(_In_ const PROCESS_INFORMATION& pi, _In_ const std::ws return TRUE; } + +std::wstring Injector::BuildRunDLLCommand(const std::wstring& DLLPath, const std::wstring& ExportedFunction) +{ + // Hack to fix std::wstring behaviour on concating wide strings, when calling c_str() it dont come complete + std::wstring RunDLL32 = L"rundll32.exe " + std::wstring(DLLPath.c_str()) + L" " + std::wstring(ExportedFunction.c_str()); + return RunDLL32; +} + + + diff --git a/Shinigami/Shinigami/Injector.h b/Shinigami/Shinigami/Injector.h index e607d34..17b15fb 100644 --- a/Shinigami/Shinigami/Injector.h +++ b/Shinigami/Shinigami/Injector.h @@ -11,6 +11,7 @@ #define INJECTED_SIZE 0x100 #define SET_ICHIGO_ARGS_FUNC_NAME "SetIchigoArguments" #define START_ICHIGO_FUNC_NAME "StartIchigo" +#define DEFAULT_TARGET_NAME L"rundll32.exe" typedef void (*SetIchigoArguments)(const IchigoArguments*); typedef void (*StartIchigo)(); @@ -34,11 +35,13 @@ struct ThreadData { class Injector { public: - Injector(_In_ const std::wstring& ProcName) : ProcName(ProcName) {} - BOOL InjectSuspended(_In_ const std::wstring& DLLPath, _In_ const IchigoArguments& DLLArguments); + Injector(_In_ const std::wstring& ProcName); + BOOL InjectSuspended(_In_ const std::wstring& DLLPath, _In_ const IchigoArguments& DLLArguments, _In_ BOOL IsDLL, _In_ const std::wstring& ExportedFunction); BOOL APCLoadDLL(_In_ const PROCESS_INFORMATION& pi, _In_ const std::wstring& DLLName, _In_ const IchigoArguments& DLLArguments) const; - +private: + std::wstring BuildRunDLLCommand(const std::wstring& DLLPath, const std::wstring& ExportedFunction); private: std::wstring ProcName; + BOOL IsDLL; }; diff --git a/Shinigami/Shinigami/Shinigami.cpp b/Shinigami/Shinigami/Shinigami.cpp index e0c78db..dc3c111 100644 --- a/Shinigami/Shinigami/Shinigami.cpp +++ b/Shinigami/Shinigami/Shinigami.cpp @@ -9,6 +9,7 @@ #include "argparse.h" #include "ShinigamiArguments.h" #include "EncodingUtils.h" +#include "SimplePE.h" #pragma comment(lib, "Shlwapi.lib") @@ -39,7 +40,7 @@ int PrintError() int main(int argc, char** argv) { ShinigamiArguments Arguments; - + try { Arguments.ParseArguments(argc, argv, PROG_NAME); @@ -51,9 +52,32 @@ int main(int argc, char** argv) return EXIT_FAILURE; } - Injector injector(Arguments.GetTarget()); + const std::wstring& Target = Arguments.GetTarget(); + SimplePE PE(Target); + + if (!PE.IsValid()) + { + std::cerr << "Is not a PE file\n"; + return EXIT_FAILURE; + } + +#ifdef _WIN64 + if (PE.Is32Bit()) + { + std::cerr << "Please use Shinigami 32-bit to execute this file!\n"; + return EXIT_FAILURE; + } +#else + if (!PE.Is32Bit()) + { + std::cerr << "Please use Shinigami 64-bit to execute this file!\n"; + return EXIT_FAILURE; + } +#endif + + Injector injector(Arguments.TargetExecutableName); - if (!injector.InjectSuspended(DLL_NAME, Arguments.GetIchigoArguments())) + if (!injector.InjectSuspended(DLL_NAME, Arguments.GetIchigoArguments(), PE.IsDLL(), Arguments.ExportedFunction)) return PrintError(); return EXIT_SUCCESS; diff --git a/Shinigami/Shinigami/Shinigami.vcxproj b/Shinigami/Shinigami/Shinigami.vcxproj index 91c5db9..9a04598 100644 --- a/Shinigami/Shinigami/Shinigami.vcxproj +++ b/Shinigami/Shinigami/Shinigami.vcxproj @@ -136,6 +136,7 @@ + @@ -143,6 +144,7 @@ + diff --git a/Shinigami/Shinigami/Shinigami.vcxproj.filters b/Shinigami/Shinigami/Shinigami.vcxproj.filters index 4c42c31..0cf6a80 100644 --- a/Shinigami/Shinigami/Shinigami.vcxproj.filters +++ b/Shinigami/Shinigami/Shinigami.vcxproj.filters @@ -30,6 +30,9 @@ Source Files + + Source Files + @@ -47,5 +50,8 @@ Header Files + + Header Files + \ No newline at end of file diff --git a/Shinigami/Shinigami/ShinigamiArguments.cpp b/Shinigami/Shinigami/ShinigamiArguments.cpp index 3ce8f56..16d5009 100644 --- a/Shinigami/Shinigami/ShinigamiArguments.cpp +++ b/Shinigami/Shinigami/ShinigamiArguments.cpp @@ -8,6 +8,13 @@ ShinigamiArguments::ShinigamiArguments() WorkDirectory = cwd; } +const std::wstring& ShinigamiArguments::GetTarget() const +{ + // First verify if there is arguments here + + return TargetArguments[0]; +} + void ShinigamiArguments::ParseArguments(int argc, char* argv[], const char* ProgamName) { argparse::ArgumentParser parser(ProgamName); @@ -27,6 +34,8 @@ void ShinigamiArguments::ParseArguments(int argc, char* argv[], const char* Prog .implicit_value(true) .default_value(false) .help("Only extract PE artefacts"); + parser.add_argument("--exported", "-e") + .help("Exported Function: Choose a exported function to execute if the target is a DLL (rundll will be used)"); try { parser.parse_args(argc, argv); @@ -36,14 +45,19 @@ void ShinigamiArguments::ParseArguments(int argc, char* argv[], const char* Prog } TargetExecutableName = EncodingUtils::StringToWstring(parser.get("program_name")); + TargetArguments = EncodingUtils::SplitWide(TargetExecutableName, L" "); if (parser.is_used("--output")) WorkDirectory = EncodingUtils::StringToWstring(parser.get("--output")); + if (parser.is_used("--exported")) + ExportedFunction = EncodingUtils::StringToWstring(parser.get("--exported")); wcsncpy_s(IchiArguments.WorkDirectory, MAX_PATH, WorkDirectory.c_str(), _TRUNCATE); + IchiArguments.Unhollow.StopAtWrite = parser.get("--stop-at-write"); IchiArguments.Quiet = !parser.get("--verbose"); IchiArguments.OnlyPE = parser.get("--only-executables"); + } \ No newline at end of file diff --git a/Shinigami/Shinigami/ShinigamiArguments.h b/Shinigami/Shinigami/ShinigamiArguments.h index 6a5dd25..5069ed7 100644 --- a/Shinigami/Shinigami/ShinigamiArguments.h +++ b/Shinigami/Shinigami/ShinigamiArguments.h @@ -23,7 +23,7 @@ struct IchigoArguments class ShinigamiArguments { public: ShinigamiArguments(); - const std::wstring& GetTarget() const { return TargetExecutableName; } + const std::wstring& GetTarget() const; const std::wstring& GetWorkDirectory() const { return WorkDirectory; } const std::vector& GetTargetArgs() const { return TargetArguments; } const IchigoArguments& GetIchigoArguments() const { return IchiArguments; } @@ -31,16 +31,16 @@ class ShinigamiArguments { void ParseArguments(int argc, char* argv[], const char* ProgramName); -private: +public: // Shinigami specific arguments for process creation and so on std::wstring TargetExecutableName; std::wstring WorkDirectory; std::wstring OutputDirectory; + std::wstring ExportedFunction; std::vector TargetArguments; // Ichigo arguments that will be sent to the injected code - +private: IchigoArguments IchiArguments; -}; - +}; \ No newline at end of file diff --git a/Shinigami/Shinigami/SimplePE.cpp b/Shinigami/Shinigami/SimplePE.cpp new file mode 100644 index 0000000..beb2210 --- /dev/null +++ b/Shinigami/Shinigami/SimplePE.cpp @@ -0,0 +1,67 @@ +#include "SimplePE.h" + +SimplePE::SimplePE(const std::wstring& Path) + : Path(Path) +{ + Valid = Load(); +} + +BOOL SimplePE::Load() +{ + HANDLE fileHandle = CreateFileW(Path.c_str(), GENERIC_READ, FILE_SHARE_READ, nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr); + if (fileHandle == INVALID_HANDLE_VALUE) { + std::printf("Unable to open PE\n"); + return FALSE; + } + + // Read the DOS header + DWORD bytesRead = 0; + if (!ReadFile(fileHandle, &DosHdr, sizeof(IMAGE_DOS_HEADER), &bytesRead, nullptr) || bytesRead != sizeof(IMAGE_DOS_HEADER)) { + CloseHandle(fileHandle); + std::printf("Unable to read IMAGE_DOS_HEADER\n"); + return FALSE; + } + + if (DosHdr.e_magic != IMAGE_DOS_SIGNATURE) { + CloseHandle(fileHandle); + std::puts("IMAGE_DOS_SIGNATURE mismatch"); + return FALSE; + } + + // Move to the NT headers + SetFilePointer(fileHandle, DosHdr.e_lfanew, nullptr, FILE_BEGIN); + + // Read the NT headers + if (!ReadFile(fileHandle, &NtHeader, sizeof(IMAGE_NT_HEADERS), &bytesRead, nullptr) || bytesRead != sizeof(IMAGE_NT_HEADERS)) { + CloseHandle(fileHandle); + std::puts("Unable to read NT signature"); + return FALSE; + } + + if (NtHeader.Signature != IMAGE_NT_SIGNATURE) { + CloseHandle(fileHandle); + std::puts("NT signature mismatch"); + return FALSE; + } + + // Check if it is a DLL + DLL = (NtHeader.OptionalHeader.Magic == IMAGE_NT_OPTIONAL_HDR32_MAGIC || + NtHeader.OptionalHeader.Magic == IMAGE_NT_OPTIONAL_HDR64_MAGIC) && + (NtHeader.FileHeader.Characteristics & IMAGE_FILE_DLL) != 0; + + CloseHandle(fileHandle); + + return TRUE; + +} + + +BOOL SimplePE::IsDLL() const +{ + return DLL; +} + +BOOL SimplePE::Is32Bit() const +{ + return NtHeader.FileHeader.Machine == IMAGE_FILE_32BIT_MACHINE || NtHeader.FileHeader.Machine == IMAGE_FILE_MACHINE_I386; +} diff --git a/Shinigami/Shinigami/SimplePE.h b/Shinigami/Shinigami/SimplePE.h new file mode 100644 index 0000000..4a42997 --- /dev/null +++ b/Shinigami/Shinigami/SimplePE.h @@ -0,0 +1,21 @@ +#pragma once +#include +#include + +class SimplePE +{ +public: + SimplePE(const std::wstring& Path); + BOOL IsValid() const { return Valid; } + BOOL IsDLL() const; + BOOL Is32Bit() const; +private: + BOOL Load(); +private: + IMAGE_DOS_HEADER DosHdr; + IMAGE_NT_HEADERS NtHeader; + BYTE* Buff; + BOOL Valid; + BOOL DLL; + std::wstring Path; +};