├── .gitignore ├── assets ├── screenshots │ ├── pe.png │ ├── emotet.png │ ├── test.png │ └── unpack_example.png └── mascot │ └── shinigami.png ├── Shinigami ├── Ichigo │ ├── framework.h │ ├── Ichigo.vcxproj.user │ ├── pch.cpp │ ├── PEDumper.h │ ├── Utils.h │ ├── pch.h │ ├── Ichigo.h │ ├── Logger.h │ ├── Mem.h │ ├── HookManager.h │ ├── Logger.cpp │ ├── Unpacker.h │ ├── Utils.cpp │ ├── ProcessUnhollow.h │ ├── dllmain.cpp │ ├── Ichigo.vcxproj.filters │ ├── defs.h │ ├── Ichigo.vcxproj │ ├── HookManager.cpp │ ├── ProcessUnhollow.cpp │ ├── PEDumper.cpp │ └── Unpacker.cpp ├── Shinigami │ ├── Shinigami.vcxproj.user │ ├── SimplePE.h │ ├── EncodingUtils.h │ ├── PipeLogger.h │ ├── ShinigamiArguments.h │ ├── Injector.h │ ├── PipeLogger.cpp │ ├── EncodingUtils.cpp │ ├── SimplePE.cpp │ ├── Shinigami.vcxproj.filters │ ├── Shinigami.cpp │ ├── ShinigamiArguments.cpp │ ├── Injector.cpp │ ├── Shinigami.vcxproj │ └── argparse.h └── Shinigami.sln ├── LICENSE ├── .github └── workflows │ ├── ci.yml │ └── cd.yml └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | x64/ 2 | .vs/ 3 | *.pdb 4 | *.exe 5 | Release/ -------------------------------------------------------------------------------- /assets/screenshots/pe.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/buzzer-re/Shinigami/HEAD/assets/screenshots/pe.png -------------------------------------------------------------------------------- /assets/mascot/shinigami.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/buzzer-re/Shinigami/HEAD/assets/mascot/shinigami.png -------------------------------------------------------------------------------- /assets/screenshots/emotet.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/buzzer-re/Shinigami/HEAD/assets/screenshots/emotet.png -------------------------------------------------------------------------------- /assets/screenshots/test.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/buzzer-re/Shinigami/HEAD/assets/screenshots/test.png -------------------------------------------------------------------------------- /assets/screenshots/unpack_example.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/buzzer-re/Shinigami/HEAD/assets/screenshots/unpack_example.png -------------------------------------------------------------------------------- /Shinigami/Ichigo/framework.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #define WIN32_LEAN_AND_MEAN // Exclude rarely-used stuff from Windows headers 4 | // Windows Header Files 5 | #include 6 | -------------------------------------------------------------------------------- /Shinigami/Ichigo/Ichigo.vcxproj.user: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /Shinigami/Shinigami/Shinigami.vcxproj.user: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /Shinigami/Ichigo/pch.cpp: -------------------------------------------------------------------------------- 1 | // pch.cpp: source file corresponding to the pre-compiled header 2 | 3 | #include "pch.h" 4 | 5 | // When you are using pre-compiled headers, this source file is necessary for compilation to succeed. 6 | -------------------------------------------------------------------------------- /Shinigami/Shinigami/SimplePE.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | 5 | class SimplePE 6 | { 7 | public: 8 | SimplePE(const std::wstring& Path); 9 | BOOL IsValid() const { return Valid; } 10 | BOOL IsDLL() const; 11 | BOOL Is32Bit() const; 12 | private: 13 | BOOL Load(); 14 | private: 15 | IMAGE_DOS_HEADER DosHdr; 16 | IMAGE_NT_HEADERS NtHeader; 17 | BYTE* Buff; 18 | BOOL Valid; 19 | BOOL DLL; 20 | std::wstring Path; 21 | }; 22 | -------------------------------------------------------------------------------- /Shinigami/Ichigo/PEDumper.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include "Mem.h" 4 | #include "Utils.h" 5 | 6 | namespace PEDumper 7 | { 8 | Memory* FindRemotePE(HANDLE hProcess, const Memory* mem); 9 | Memory* DumpPE(ULONG_PTR* Address); 10 | PIMAGE_DOS_HEADER FindPE(Memory* Mem); 11 | PIMAGE_DOS_HEADER HeuristicSearch(Memory* Mem); 12 | PIMAGE_DOS_HEADER RebuildDOSHeader(Memory* Mem, ULONG_PTR NtHeaderOffset); 13 | BOOL IsValidNT(PIMAGE_NT_HEADERS pNtHeader); 14 | SIZE_T GetPESize(PIMAGE_NT_HEADERS pNTHeader); 15 | VOID FixPESections(Memory* pNTHeader); 16 | }; 17 | 18 | -------------------------------------------------------------------------------- /Shinigami/Ichigo/Utils.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "Mem.h" 3 | #include 4 | #include 5 | 6 | #pragma comment(lib, "Shlwapi.lib") 7 | 8 | enum MEM_ERROR 9 | { 10 | ERROR_NO_ERROR = TRUE, 11 | INVALID_MEMORY_AREA 12 | }; 13 | 14 | namespace Utils 15 | { 16 | BOOL SaveToFile(const wchar_t* filename, Memory* data, BOOL Paginate); 17 | SIZE_T GetPESize(PIMAGE_NT_HEADERS pDOSHeader); 18 | std::wstring PathJoin(const std::wstring& BasePath, const std::wstring& FileName); 19 | 20 | std::wstring BuildFilenameFromProcessName(const wchar_t* suffix); 21 | 22 | MEM_ERROR IsReadWritable(ULONG_PTR* Address); 23 | } -------------------------------------------------------------------------------- /Shinigami/Ichigo/pch.h: -------------------------------------------------------------------------------- 1 | // pch.h: This is a precompiled header file. 2 | // Files listed below are compiled only once, improving build performance for future builds. 3 | // This also affects IntelliSense performance, including code completion and many code browsing features. 4 | // However, files listed here are ALL re-compiled if any one of them is updated between builds. 5 | // Do not add files here that you will be updating frequently as this negates the performance advantage. 6 | 7 | #ifndef PCH_H 8 | #define PCH_H 9 | 10 | #include 11 | // add headers that you want to pre-compile here 12 | #include "framework.h" 13 | 14 | #endif //PCH_H 15 | -------------------------------------------------------------------------------- /Shinigami/Ichigo/Ichigo.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include "HookManager.h" 4 | 5 | // Global options 6 | namespace Ichigo 7 | { 8 | enum State 9 | { 10 | IDLE = 0, 11 | RUNNING, 12 | FINISHED 13 | }; 14 | 15 | #pragma pack(push, 1) 16 | struct Arguments 17 | { 18 | wchar_t WorkDirectory[MAX_PATH]; 19 | BOOL Quiet; 20 | BOOL OnlyPE; 21 | DWORD PID; 22 | struct 23 | { 24 | BOOL StopAtWrite; 25 | } Unhollow; 26 | }; 27 | #pragma pack(pop) 28 | 29 | static HookManager hkManager; 30 | static Arguments Options; 31 | } 32 | 33 | -------------------------------------------------------------------------------- /Shinigami/Shinigami/EncodingUtils.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | #include 5 | 6 | namespace EncodingUtils { 7 | 8 | // Convert std::string to std::wstring 9 | std::wstring StringToWstring(const std::string& str); 10 | 11 | // Convert std::wstring to std::string 12 | std::string WstringToString(const std::wstring& wstr); 13 | 14 | // Convert char* to wchar_t* 15 | wchar_t* CharToWchar(const char* str); 16 | 17 | // Convert wchar_t* to char* 18 | char* WcharToChar(const wchar_t* wstr); 19 | 20 | // Split string 21 | std::vector SplitWide(std::wstring String, const std::wstring& delimiter); 22 | } 23 | -------------------------------------------------------------------------------- /Shinigami/Shinigami/PipeLogger.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | #define MAX_MESSAGE_SIZE 1024 7 | #define PIPE_NAME L"\\\\.\\pipe\\PipeLogger" 8 | 9 | enum Messages 10 | { 11 | INFO_LOG, 12 | USER_LOG, 13 | ERROR_LOG, 14 | INPUT_LOG, 15 | LOG_SUCCESS, 16 | EXITING 17 | }; 18 | 19 | 20 | struct LogMsg 21 | { 22 | BYTE MessageType; 23 | wchar_t message[MAX_MESSAGE_SIZE]; 24 | }; 25 | 26 | 27 | namespace PipeLogger 28 | { 29 | static HANDLE hPipe; 30 | static HANDLE hThread; 31 | static wchar_t* PipeName; 32 | 33 | BOOL InitPipe(); 34 | VOID LoggerThread(ULONG_PTR* params); 35 | VOID ClosePipe(); 36 | }; 37 | 38 | 39 | namespace Logger 40 | { 41 | VOID LogInfo(const wchar_t* message, ...); 42 | } -------------------------------------------------------------------------------- /Shinigami/Ichigo/Logger.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | #include "Ichigo.h" 5 | 6 | #define MAX_MESSAGE_SIZE 1024 7 | #define PIPE_NAME L"\\\\.\\pipe\\PipeLogger" 8 | 9 | enum Messages 10 | { 11 | INFO_LOG, 12 | USER_LOG, 13 | ERROR_LOG, 14 | INPUT_LOG, 15 | LOG_SUCCESS, 16 | EXITING 17 | }; 18 | 19 | 20 | struct LogMsg 21 | { 22 | BYTE MessageType; 23 | wchar_t message[MAX_MESSAGE_SIZE]; 24 | }; 25 | 26 | 27 | namespace PipeLogger 28 | { 29 | static HANDLE hPipe; 30 | static HANDLE hThread; 31 | static wchar_t* PipeName; 32 | static BOOL Quiet; 33 | 34 | VOID BeQuiet(BOOL quiet); 35 | BOOL InitPipe(); 36 | 37 | BOOL Log(const wchar_t* message, ...); 38 | BOOL LogInfo(const wchar_t* message, ...); 39 | BOOL SendMsg(Messages level, const wchar_t* message, va_list args); 40 | BOOL WriteToPipe(const LogMsg& logMsg); 41 | 42 | VOID ClosePipe(); 43 | }; 44 | -------------------------------------------------------------------------------- /Shinigami/Ichigo/Mem.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | 4 | // Unpack structures 5 | struct Memory 6 | { 7 | ~Memory() 8 | { 9 | if (safe) 10 | { 11 | if (cAlloc) 12 | delete Addr; 13 | else 14 | VirtualFree(Addr, NULL, MEM_RELEASE); 15 | } 16 | } 17 | 18 | bool operator==(const Memory& other) const 19 | { 20 | return Addr == other.Addr && End == other.End && Size == other.Size 21 | && prot == other.prot && safe == other.safe && ProcessID == other.ProcessID 22 | && cAlloc == other.cAlloc; 23 | } 24 | 25 | ULONG_PTR IP; // If this memory is being executed, this value holds the offset of the current execution, must be set manually 26 | uint8_t* Addr; 27 | ULONG_PTR End; 28 | SIZE_T Size; 29 | DWORD prot; 30 | bool safe; 31 | DWORD ProcessID; 32 | bool cAlloc; 33 | }; 34 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Buzzer 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: Build 2 | 3 | on: [push] 4 | 5 | jobs: 6 | build: 7 | runs-on: windows-latest 8 | strategy: 9 | matrix: 10 | platform: [x86, x64] 11 | steps: 12 | - uses: actions/checkout@v2 13 | 14 | 15 | - name: Install Visual Studio Build Tools 16 | uses: microsoft/setup-msbuild@v1.0.2 17 | with: 18 | vs-version: '17.0' 19 | include-prerelease: true 20 | 21 | # Install vcpkg 22 | - name: Install vcpkg 23 | run: | 24 | git clone https://github.com/Microsoft/vcpkg.git 25 | cd vcpkg 26 | git checkout a325228200d7f229f3337e612e0077f2a53 27 | .\bootstrap-vcpkg.bat 28 | 29 | # Install vcpkg dependencies 30 | - name: Install vcpkg dependencies 31 | run: | 32 | cd vcpkg 33 | .\vcpkg install zydis:${{ matrix.platform }}-windows 34 | .\vcpkg integrate install 35 | 36 | # Build the Ichigo and Shinigami projects 37 | - name: Build the Ichigo and Shinigami projects 38 | run: | 39 | cd Shinigami 40 | msbuild Shinigami.sln /t:Ichigo`;Shinigami /p:Configuration=Release /p:Platform=${{ matrix.platform }} 41 | -------------------------------------------------------------------------------- /Shinigami/Shinigami/ShinigamiArguments.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "argparse.h" 4 | #include "EncodingUtils.h" 5 | #include 6 | #include 7 | #include 8 | 9 | #pragma pack(push, 1) 10 | struct IchigoArguments 11 | { 12 | wchar_t WorkDirectory[MAX_PATH]; 13 | BOOL Quiet; 14 | BOOL OnlyPE; 15 | DWORD PID; 16 | struct 17 | { 18 | BOOL StopAtWrite; 19 | } Unhollow; 20 | }; 21 | #pragma pack(pop) 22 | 23 | class ShinigamiArguments { 24 | public: 25 | ShinigamiArguments(); 26 | const std::wstring& GetTarget() const; 27 | const std::wstring& GetWorkDirectory() const { return WorkDirectory; } 28 | const std::vector& GetTargetArgs() const { return TargetArguments; } 29 | const IchigoArguments& GetIchigoArguments() const { return IchiArguments; } 30 | 31 | 32 | void ParseArguments(int argc, char* argv[], const char* ProgramName); 33 | 34 | public: 35 | // Shinigami specific arguments for process creation and so on 36 | std::wstring TargetExecutableName; 37 | std::wstring WorkDirectory; 38 | std::wstring OutputDirectory; 39 | std::wstring ExportedFunction; 40 | std::vector TargetArguments; 41 | 42 | // Ichigo arguments that will be sent to the injected code 43 | private: 44 | IchigoArguments IchiArguments; 45 | 46 | }; -------------------------------------------------------------------------------- /Shinigami/Ichigo/HookManager.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | #define X86_TRAMPOLINE_SIZE 5 8 | #define X64_TRAMPOLINE_SIZE 13 9 | #define X64_JUMP_BACK_SIZE X64_TRAMPOLINE_SIZE 10 | 11 | #define HOOK_MAX_SIZE 2*5 12 | #define NOP_SLIDE 32 // Nop slide helps with the HookChain structure 13 | #define TRAMPOLINE_SIZE 5 14 | 15 | #define NOP 0x90 16 | #define JUMP 0xE9 17 | 18 | enum HOOK_STATUS { 19 | HOOK_STATUS_SUCCESS, 20 | HOOK_STATUS_ALREADY_HOOKED, 21 | HOOK_STATUS_NOT_ENOUGH_MEMORY 22 | }; 23 | 24 | struct Hook { 25 | LPVOID OriginalAddr; 26 | LPVOID HookAddr; 27 | LPVOID GatewayAddr; 28 | 29 | DWORD NumInstLeftToExec; 30 | // Only used for 32 bit hooks 31 | DWORD Trampoline32Delta; 32 | DWORD Gateway32Delta; 33 | }; 34 | 35 | 36 | class HookManager 37 | { 38 | public: 39 | HookManager(); 40 | LPVOID AddHook(_In_ BYTE* Src, _In_ BYTE* Dst,_In_ BOOL IgnoreProt); 41 | VOID DisassambleAt(_In_ ULONG_PTR* Address, _In_ SIZE_T NumberOfInstructions); 42 | 43 | private: 44 | Hook* Hook64(_In_ BYTE* Src, _In_ BYTE* Dst, _In_ BOOL IgnoreProt); 45 | Hook* Hook32(_In_ BYTE* Src, _In_ BYTE* Dst, _In_ BOOL IgnoreProt); 46 | private: 47 | ZydisDecoder ZDecoder; 48 | std::unordered_map> HookChain; 49 | }; 50 | 51 | -------------------------------------------------------------------------------- /Shinigami/Shinigami/Injector.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #include "PipeLogger.h" 9 | #include "ShinigamiArguments.h" 10 | 11 | #define INJECTED_SIZE 0x100 12 | #define SET_ICHIGO_ARGS_FUNC_NAME "SetIchigoArguments" 13 | #define START_ICHIGO_FUNC_NAME "StartIchigo" 14 | #define DEFAULT_TARGET_NAME L"rundll32.exe" 15 | 16 | typedef void (*SetIchigoArguments)(const IchigoArguments*); 17 | typedef void (*StartIchigo)(); 18 | 19 | #pragma pack(push, 1) 20 | struct ThreadData { 21 | decltype(LoadLibraryW) *LoadLibraryW; 22 | decltype(GetProcAddress) *GetProcAddress; 23 | decltype(ExitProcess) *ExitProcess; 24 | decltype(GetLastError) *GetLastError; 25 | 26 | wchar_t DllName[MAX_PATH]; 27 | 28 | char SetArgumentsFuncName[MAX_PATH]; 29 | char StartIchigoFuncName[MAX_PATH]; 30 | IchigoArguments Arguments; 31 | }; 32 | #pragma pack(pop) 33 | 34 | 35 | class Injector 36 | { 37 | public: 38 | Injector(_In_ const std::wstring& ProcName); 39 | BOOL InjectSuspended(_In_ const std::wstring& DLLPath, _In_ const IchigoArguments& DLLArguments, _In_ BOOL IsDLL, _In_ const std::wstring& ExportedFunction); 40 | BOOL APCLoadDLL(_In_ const PROCESS_INFORMATION& pi, _In_ const std::wstring& DLLName, _In_ const IchigoArguments& DLLArguments) const; 41 | private: 42 | std::wstring BuildRunDLLCommand(const std::wstring& DLLPath, const std::wstring& ExportedFunction); 43 | private: 44 | std::wstring ProcName; 45 | BOOL IsDLL; 46 | }; 47 | 48 | -------------------------------------------------------------------------------- /Shinigami/Shinigami/PipeLogger.cpp: -------------------------------------------------------------------------------- 1 | #include "PipeLogger.h" 2 | 3 | using namespace PipeLogger; 4 | 5 | 6 | // 7 | // Create a named pipe and a connection handling thread 8 | // 9 | BOOL PipeLogger::InitPipe() 10 | { 11 | hPipe = CreateNamedPipe(PIPE_NAME, 12 | PIPE_ACCESS_DUPLEX, 13 | PIPE_TYPE_MESSAGE | PIPE_READMODE_MESSAGE | PIPE_WAIT, 14 | PIPE_UNLIMITED_INSTANCES, 15 | sizeof(LogMsg), 16 | sizeof(LogMsg), 17 | 0, 18 | NULL); 19 | 20 | 21 | if (hPipe == INVALID_HANDLE_VALUE) return false; 22 | 23 | 24 | hThread = CreateThread(NULL, NULL, (LPTHREAD_START_ROUTINE)LoggerThread, hPipe, NULL, NULL); 25 | 26 | if (hThread == INVALID_HANDLE_VALUE) 27 | { 28 | CloseHandle(hPipe); 29 | return false; 30 | } 31 | 32 | return true; 33 | } 34 | 35 | VOID PipeLogger::ClosePipe() 36 | { 37 | if (hPipe != INVALID_HANDLE_VALUE) 38 | CloseHandle(hPipe); 39 | 40 | if (hThread != INVALID_HANDLE_VALUE) 41 | CloseHandle(hThread); 42 | } 43 | 44 | 45 | VOID PipeLogger::LoggerThread(ULONG_PTR* params) 46 | { 47 | HANDLE hPipe = reinterpret_cast(params); 48 | BYTE* Data = new BYTE[sizeof(LogMsg)]; 49 | DWORD dwRead; 50 | LogMsg* msg; 51 | bool first = true; 52 | while (true) 53 | { 54 | if (ReadFile(hPipe, Data, sizeof(LogMsg), &dwRead, NULL)) 55 | { 56 | msg = reinterpret_cast(Data); 57 | 58 | if (msg->MessageType == EXITING) break; 59 | 60 | if (first) 61 | { 62 | Logger::LogInfo(L"Connected with the remote process! processing logs..."); 63 | first = false; 64 | } 65 | 66 | switch (msg->MessageType) 67 | { 68 | case EXITING: break; 69 | case USER_LOG: 70 | case INFO_LOG: 71 | Logger::LogInfo(msg->message); 72 | break; 73 | } 74 | } 75 | } 76 | 77 | delete[] Data; 78 | } 79 | 80 | VOID Logger::LogInfo(const wchar_t* message, ...) 81 | { 82 | va_list args; 83 | va_start(args, message); 84 | 85 | fputws(L"[+] Shinigami Info: ", stdout); 86 | vfwprintf(stdout, message, args); 87 | fputws(L" [+] \n", stdout); 88 | 89 | va_end(args); 90 | } 91 | -------------------------------------------------------------------------------- /Shinigami/Ichigo/Logger.cpp: -------------------------------------------------------------------------------- 1 | #include "pch.h" 2 | #include "Logger.h" 3 | 4 | 5 | using namespace PipeLogger; 6 | 7 | VOID PipeLogger::BeQuiet(BOOL quiet) 8 | { 9 | Quiet = quiet; 10 | } 11 | 12 | BOOL 13 | PipeLogger::InitPipe() 14 | { 15 | hPipe = CreateFile( 16 | PIPE_NAME, 17 | GENERIC_WRITE, 18 | 0, 19 | NULL, 20 | OPEN_EXISTING, 21 | 0, 22 | NULL 23 | ); 24 | 25 | return TRUE; 26 | } 27 | 28 | 29 | VOID PipeLogger::ClosePipe() 30 | { 31 | LogMsg msg; 32 | msg.MessageType = Messages::EXITING; 33 | 34 | WriteToPipe(msg); 35 | 36 | if (hPipe != INVALID_HANDLE_VALUE) 37 | CloseHandle(hPipe); 38 | } 39 | 40 | // 41 | // Write the message into the pipe 42 | // 43 | BOOL 44 | PipeLogger::WriteToPipe(const LogMsg& logMsg) 45 | { 46 | if (hPipe == INVALID_HANDLE_VALUE) return false; 47 | 48 | DWORD dwWritten, dwRead; 49 | LogMsg response; 50 | 51 | if (!WriteFile(hPipe, &logMsg, sizeof(logMsg), &dwWritten, NULL)) return false; 52 | if (!ReadFile(hPipe, &response, sizeof(response), &dwRead, NULL)) return false; 53 | 54 | 55 | return response.MessageType == LOG_SUCCESS; 56 | } 57 | 58 | 59 | BOOL 60 | PipeLogger::Log(const wchar_t* message, ...) 61 | { 62 | va_list args; 63 | 64 | va_start(args, message); 65 | BOOL status = SendMsg(USER_LOG, message, args); 66 | va_end(args); 67 | 68 | return status; 69 | } 70 | 71 | BOOL 72 | PipeLogger::LogInfo(const wchar_t* message, ...) 73 | { 74 | if (Quiet) 75 | return TRUE; 76 | 77 | va_list args; 78 | 79 | va_start(args, message); 80 | BOOL status = SendMsg(USER_LOG, message, args); 81 | va_end(args); 82 | 83 | return status; 84 | } 85 | 86 | BOOL 87 | PipeLogger::SendMsg(Messages level, const wchar_t* message, va_list args) 88 | { 89 | LogMsg logMsg; 90 | logMsg.MessageType = level; 91 | 92 | ZeroMemory(logMsg.message, MAX_MESSAGE_SIZE); 93 | size_t msgLen = wcslen(message); 94 | 95 | vswprintf(logMsg.message, MAX_MESSAGE_SIZE, message, args); 96 | return WriteToPipe(logMsg); 97 | } 98 | 99 | 100 | -------------------------------------------------------------------------------- /Shinigami/Shinigami/EncodingUtils.cpp: -------------------------------------------------------------------------------- 1 | #include "EncodingUtils.h" 2 | 3 | namespace EncodingUtils { 4 | 5 | std::wstring StringToWstring(const std::string& str) { 6 | int wstrSize = MultiByteToWideChar(CP_UTF8, 0, str.c_str(), -1, nullptr, 0); 7 | std::wstring wstr(wstrSize, 0); 8 | MultiByteToWideChar(CP_UTF8, 0, str.c_str(), -1, &wstr[0], wstrSize); 9 | return wstr; 10 | } 11 | 12 | std::string WstringToString(const std::wstring& wstr) { 13 | int strSize = WideCharToMultiByte(CP_UTF8, 0, wstr.c_str(), -1, nullptr, 0, nullptr, nullptr); 14 | std::string str(strSize, 0); 15 | WideCharToMultiByte(CP_UTF8, 0, wstr.c_str(), -1, &str[0], strSize, nullptr, nullptr); 16 | return str; 17 | } 18 | 19 | wchar_t* CharToWchar(const char* str) { 20 | int wstrSize = MultiByteToWideChar(CP_UTF8, 0, str, -1, nullptr, 0); 21 | wchar_t* wstr = new wchar_t[wstrSize]; 22 | MultiByteToWideChar(CP_UTF8, 0, str, -1, wstr, wstrSize); 23 | return wstr; 24 | } 25 | 26 | char* WcharToChar(const wchar_t* wstr) { 27 | int strSize = WideCharToMultiByte(CP_UTF8, 0, wstr, -1, nullptr, 0, nullptr, nullptr); 28 | char* str = new char[strSize]; 29 | WideCharToMultiByte(CP_UTF8, 0, wstr, -1, str, strSize, nullptr, nullptr); 30 | return str; 31 | } 32 | 33 | std::vector SplitWide(std::wstring String, const std::wstring& delimiter) 34 | { 35 | std::vector SplitedString; 36 | std::wstring token; 37 | size_t pos = 0; 38 | 39 | if ((pos = String.find(delimiter) == std::wstring::npos)) 40 | { 41 | SplitedString.push_back(String); 42 | goto ret; 43 | } 44 | 45 | while ((pos = String.find(delimiter)) != std::wstring::npos) 46 | { 47 | token = String.substr(0, pos); 48 | SplitedString.push_back(token); 49 | String.erase(0, pos + delimiter.length()); 50 | } 51 | 52 | ret: 53 | return SplitedString; 54 | } 55 | 56 | } 57 | -------------------------------------------------------------------------------- /Shinigami/Ichigo/Unpacker.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | #include "Ichigo.h" 10 | #include "HookManager.h" 11 | #include "Logger.h" 12 | #include "Mem.h" 13 | #include "Utils.h" 14 | #include "defs.h" 15 | #include "PEDumper.h" 16 | 17 | #define TF 0x100 18 | #define PAGE_SIZE 0x1000 19 | 20 | namespace GenericUnpacker 21 | { 22 | static NTSTATUS WINAPI hkNtAllocateVirtualMemory( 23 | HANDLE ProcessHandle, 24 | PVOID* BaseAddress, 25 | ULONG_PTR ZeroBits, 26 | PSIZE_T RegionSize, 27 | ULONG AllocationType, 28 | ULONG Protect 29 | ); 30 | 31 | static NTSTATUS WINAPI hkNtWriteVirtualMemory ( 32 | HANDLE ProcessHandle, 33 | PVOID BaseAddress, 34 | PVOID Buffer, 35 | ULONG NumberOfBytesToWrite, 36 | PULONG NumberOfBytesWritten 37 | ); 38 | 39 | static NTSTATUS WINAPI hkNtProtectVirtualMemory ( 40 | HANDLE ProcessHandle, 41 | PVOID* BaseAddress, 42 | PSIZE_T RegionSize, 43 | ULONG NewProtect, 44 | PULONG OldProtect 45 | ); 46 | 47 | static class Unpacker 48 | { 49 | public: 50 | Memory* IsBeingMonitored(ULONG_PTR Address); 51 | BOOL Dump(Memory* StartAddress); 52 | VOID RemoveMonitor(Memory* Mem); 53 | VOID CleanMonitor(); 54 | 55 | public: 56 | WinAPIPointers Win32Pointers; 57 | std::list Watcher; 58 | std::vector StagesPath; 59 | } cUnpacker; 60 | 61 | 62 | LONG WINAPI VEHandler(EXCEPTION_POINTERS* pExceptionPointers); 63 | 64 | BOOL InitUnpackerHooks(HookManager& hkManager, Ichigo::Arguments& Arguments); 65 | 66 | VOID RemoveGuard(ULONG_PTR Address); 67 | VOID Shutdown(); 68 | 69 | static BOOL Ready; 70 | // Used for toggle on/off when the unpacker inject in itself 71 | static std::unordered_map IgnoreMap; 72 | 73 | static Ichigo::Arguments* IchigoOptions; 74 | } 75 | 76 | 77 | 78 | -------------------------------------------------------------------------------- /Shinigami/Ichigo/Utils.cpp: -------------------------------------------------------------------------------- 1 | #include "pch.h" 2 | #include "Utils.h" 3 | #include "Logger.h" 4 | 5 | BOOL Utils::SaveToFile(const wchar_t* filename, Memory* data, BOOL Paginate) 6 | { 7 | HANDLE hFile = CreateFileW(filename, GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL); 8 | BOOL success = TRUE; 9 | 10 | if (hFile == INVALID_HANDLE_VALUE) { 11 | return false; 12 | } 13 | 14 | DWORD BytesWritten; 15 | DWORD OldProt; 16 | 17 | VirtualProtect(data->Addr, data->Size, PAGE_READWRITE, &OldProt); 18 | 19 | success = WriteFile(hFile, data->Addr, data->Size, &BytesWritten, NULL) && (BytesWritten == data->Size); 20 | 21 | VirtualProtect(data->Addr, data->Size, OldProt, &OldProt); 22 | 23 | CloseHandle(hFile); 24 | 25 | return TRUE; 26 | } 27 | 28 | 29 | // Quick and dirty implementation 30 | std::wstring Utils::PathJoin(const std::wstring& BasePath, const std::wstring& FileName) 31 | { 32 | if (BasePath.back() == '\\') 33 | return BasePath + FileName; 34 | 35 | return BasePath + L'\\' + FileName; 36 | } 37 | 38 | 39 | 40 | std::wstring Utils::BuildFilenameFromProcessName(const wchar_t* suffix) 41 | { 42 | // 43 | // Get the name of the process executing 44 | // 45 | wchar_t exePath[MAX_PATH]; 46 | GetModuleFileName(nullptr, exePath, MAX_PATH); 47 | // 48 | // Get filename 49 | // 50 | wchar_t* exeName = PathFindFileNameW(exePath); 51 | 52 | // 53 | // Get the . pos 54 | // 55 | wchar_t* dot = wcsrchr(exeName, L'.'); 56 | 57 | // 58 | // Quick hack 59 | // 60 | if (dot != nullptr && _wcsicmp(dot, L".exe") == 0) { 61 | *dot = L'\0'; // Replace the dot with a null terminator 62 | } 63 | 64 | 65 | return std::wstring(exeName) + suffix; 66 | } 67 | 68 | MEM_ERROR Utils::IsReadWritable(ULONG_PTR* Address) 69 | { 70 | MEMORY_BASIC_INFORMATION mbi = { 0 }; 71 | 72 | if (!VirtualQuery(Address, &mbi, sizeof(mbi))) 73 | { 74 | return INVALID_MEMORY_AREA; 75 | } 76 | 77 | return (MEM_ERROR)(mbi.Protect == PAGE_EXECUTE_READWRITE || mbi.Protect == PAGE_READWRITE); 78 | } 79 | -------------------------------------------------------------------------------- /Shinigami/Shinigami/SimplePE.cpp: -------------------------------------------------------------------------------- 1 | #include "SimplePE.h" 2 | 3 | SimplePE::SimplePE(const std::wstring& Path) 4 | : Path(Path) 5 | { 6 | Valid = Load(); 7 | } 8 | 9 | BOOL SimplePE::Load() 10 | { 11 | HANDLE fileHandle = CreateFileW(Path.c_str(), GENERIC_READ, FILE_SHARE_READ, nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr); 12 | if (fileHandle == INVALID_HANDLE_VALUE) { 13 | std::printf("Unable to open PE\n"); 14 | return FALSE; 15 | } 16 | 17 | // Read the DOS header 18 | DWORD bytesRead = 0; 19 | if (!ReadFile(fileHandle, &DosHdr, sizeof(IMAGE_DOS_HEADER), &bytesRead, nullptr) || bytesRead != sizeof(IMAGE_DOS_HEADER)) { 20 | CloseHandle(fileHandle); 21 | std::printf("Unable to read IMAGE_DOS_HEADER\n"); 22 | return FALSE; 23 | } 24 | 25 | if (DosHdr.e_magic != IMAGE_DOS_SIGNATURE) { 26 | CloseHandle(fileHandle); 27 | std::puts("IMAGE_DOS_SIGNATURE mismatch"); 28 | return FALSE; 29 | } 30 | 31 | // Move to the NT headers 32 | SetFilePointer(fileHandle, DosHdr.e_lfanew, nullptr, FILE_BEGIN); 33 | 34 | // Read the NT headers 35 | if (!ReadFile(fileHandle, &NtHeader, sizeof(IMAGE_NT_HEADERS), &bytesRead, nullptr) || bytesRead != sizeof(IMAGE_NT_HEADERS)) { 36 | CloseHandle(fileHandle); 37 | std::puts("Unable to read NT signature"); 38 | return FALSE; 39 | } 40 | 41 | if (NtHeader.Signature != IMAGE_NT_SIGNATURE) { 42 | CloseHandle(fileHandle); 43 | std::puts("NT signature mismatch"); 44 | return FALSE; 45 | } 46 | 47 | // Check if it is a DLL 48 | DLL = (NtHeader.OptionalHeader.Magic == IMAGE_NT_OPTIONAL_HDR32_MAGIC || 49 | NtHeader.OptionalHeader.Magic == IMAGE_NT_OPTIONAL_HDR64_MAGIC) && 50 | (NtHeader.FileHeader.Characteristics & IMAGE_FILE_DLL) != 0; 51 | 52 | CloseHandle(fileHandle); 53 | 54 | return TRUE; 55 | 56 | } 57 | 58 | 59 | BOOL SimplePE::IsDLL() const 60 | { 61 | return DLL; 62 | } 63 | 64 | BOOL SimplePE::Is32Bit() const 65 | { 66 | return NtHeader.FileHeader.Machine == IMAGE_FILE_32BIT_MACHINE || NtHeader.FileHeader.Machine == IMAGE_FILE_MACHINE_I386; 67 | } 68 | -------------------------------------------------------------------------------- /Shinigami/Shinigami/Shinigami.vcxproj.filters: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | {4FC737F1-C7A5-4376-A066-2A32D752A2FF} 6 | cpp;c;cc;cxx;c++;cppm;ixx;def;odl;idl;hpj;bat;asm;asmx 7 | 8 | 9 | {93995380-89BD-4b04-88EB-625FBE52EBFB} 10 | h;hh;hpp;hxx;h++;hm;inl;inc;ipp;xsd 11 | 12 | 13 | {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} 14 | rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms 15 | 16 | 17 | 18 | 19 | Source Files 20 | 21 | 22 | Source Files 23 | 24 | 25 | Source Files 26 | 27 | 28 | Source Files 29 | 30 | 31 | Source Files 32 | 33 | 34 | Source Files 35 | 36 | 37 | 38 | 39 | Header Files 40 | 41 | 42 | Header Files 43 | 44 | 45 | Header Files 46 | 47 | 48 | Header Files 49 | 50 | 51 | Header Files 52 | 53 | 54 | Header Files 55 | 56 | 57 | -------------------------------------------------------------------------------- /Shinigami/Shinigami.sln: -------------------------------------------------------------------------------- 1 | 2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 17 4 | VisualStudioVersion = 17.4.33213.308 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Shinigami", "Shinigami\Shinigami.vcxproj", "{036F4F12-D4EB-4B57-ADAE-3B286A6A1900}" 7 | EndProject 8 | Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Ichigo", "Ichigo\Ichigo.vcxproj", "{3407133B-3CF8-46A9-8A9F-AB5286C91BC7}" 9 | EndProject 10 | Global 11 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 12 | Debug|x64 = Debug|x64 13 | Debug|x86 = Debug|x86 14 | Release|x64 = Release|x64 15 | Release|x86 = Release|x86 16 | EndGlobalSection 17 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 18 | {036F4F12-D4EB-4B57-ADAE-3B286A6A1900}.Debug|x64.ActiveCfg = Debug|x64 19 | {036F4F12-D4EB-4B57-ADAE-3B286A6A1900}.Debug|x64.Build.0 = Debug|x64 20 | {036F4F12-D4EB-4B57-ADAE-3B286A6A1900}.Debug|x86.ActiveCfg = Debug|Win32 21 | {036F4F12-D4EB-4B57-ADAE-3B286A6A1900}.Debug|x86.Build.0 = Debug|Win32 22 | {036F4F12-D4EB-4B57-ADAE-3B286A6A1900}.Release|x64.ActiveCfg = Release|x64 23 | {036F4F12-D4EB-4B57-ADAE-3B286A6A1900}.Release|x64.Build.0 = Release|x64 24 | {036F4F12-D4EB-4B57-ADAE-3B286A6A1900}.Release|x86.ActiveCfg = Release|Win32 25 | {036F4F12-D4EB-4B57-ADAE-3B286A6A1900}.Release|x86.Build.0 = Release|Win32 26 | {3407133B-3CF8-46A9-8A9F-AB5286C91BC7}.Debug|x64.ActiveCfg = Debug|x64 27 | {3407133B-3CF8-46A9-8A9F-AB5286C91BC7}.Debug|x64.Build.0 = Debug|x64 28 | {3407133B-3CF8-46A9-8A9F-AB5286C91BC7}.Debug|x86.ActiveCfg = Debug|Win32 29 | {3407133B-3CF8-46A9-8A9F-AB5286C91BC7}.Debug|x86.Build.0 = Debug|Win32 30 | {3407133B-3CF8-46A9-8A9F-AB5286C91BC7}.Release|x64.ActiveCfg = Release|x64 31 | {3407133B-3CF8-46A9-8A9F-AB5286C91BC7}.Release|x64.Build.0 = Release|x64 32 | {3407133B-3CF8-46A9-8A9F-AB5286C91BC7}.Release|x86.ActiveCfg = Release|Win32 33 | {3407133B-3CF8-46A9-8A9F-AB5286C91BC7}.Release|x86.Build.0 = Release|Win32 34 | EndGlobalSection 35 | GlobalSection(SolutionProperties) = preSolution 36 | HideSolutionNode = FALSE 37 | EndGlobalSection 38 | GlobalSection(ExtensibilityGlobals) = postSolution 39 | SolutionGuid = {7347F46F-722E-441D-9660-A8676F0CA773} 40 | EndGlobalSection 41 | EndGlobal 42 | -------------------------------------------------------------------------------- /Shinigami/Ichigo/ProcessUnhollow.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | // Our libs 9 | #include "PEDumper.h" 10 | #include "Mem.h" 11 | #include "HookManager.h" 12 | #include "defs.h" 13 | #include "Utils.h" 14 | #include "Logger.h" 15 | #include "Ichigo.h" 16 | // 17 | // Unhollow namespace containing the Information struct which holds the hooks and system pointers 18 | // 19 | namespace Unhollow 20 | { 21 | static struct Information 22 | { 23 | HMODULE NTDLL; 24 | BOOL DumptAtResume; 25 | PROCESS_INFORMATION pi; 26 | std::vector Watcher; 27 | WinAPIPointers Win32Pointers; 28 | } ProcessInformation; 29 | 30 | static NTSTATUS WINAPI hkNtAllocateVirtualMemory( 31 | HANDLE ProcessHandle, 32 | PVOID* BaseAddress, 33 | ULONG_PTR ZeroBits, 34 | PSIZE_T RegionSize, 35 | ULONG AllocationType, 36 | ULONG Protect 37 | ); 38 | 39 | static NTSTATUS WINAPI hkNtWriteVirtualMemory( 40 | HANDLE ProcessHandle, 41 | PVOID BaseAddress, 42 | PVOID Buffer, 43 | ULONG NumberOfBytesToWrite, 44 | PULONG NumberOfBytesWritten 45 | ); 46 | 47 | static NTSTATUS WINAPI hkNtCreateUserProcess 48 | ( 49 | PHANDLE ProcessHandle, 50 | PHANDLE ThreadHandle, 51 | ACCESS_MASK ProcessDesiredAccess, 52 | ACCESS_MASK ThreadDesiredAccess, 53 | POBJECT_ATTRIBUTES ProcessObjectAttributes, 54 | POBJECT_ATTRIBUTES ThreadObjectAttributes, 55 | ULONG ProcessFlags, 56 | ULONG ThreadFlags, 57 | PRTL_USER_PROCESS_PARAMETERS ProcessParameters, 58 | PPS_CREATE_INFO CreateInfo, 59 | PPS_ATTRIBUTE_LIST AttributeList 60 | ); 61 | 62 | static NTSTATUS WINAPI hkNtResumeThread( 63 | HANDLE ThreadHandle, 64 | PULONG SuspendCount 65 | ); 66 | 67 | Memory* HuntPE(); 68 | 69 | 70 | // Place our hooks 71 | BOOL InitUnhollowHooks(HookManager& hkManager, Ichigo::Arguments& Options); 72 | // Clean 73 | VOID Shutdown(); 74 | 75 | // Hold the current config state 76 | static Ichigo::Arguments* IchigoOptions; 77 | } 78 | 79 | -------------------------------------------------------------------------------- /Shinigami/Shinigami/Shinigami.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #include "Injector.h" 9 | #include "argparse.h" 10 | #include "ShinigamiArguments.h" 11 | #include "EncodingUtils.h" 12 | #include "SimplePE.h" 13 | 14 | #pragma comment(lib, "Shlwapi.lib") 15 | 16 | #define DLL_NAME L".\\Ichigo.dll" 17 | #define PROG_NAME "Shinigami" 18 | 19 | 20 | int PrintError() 21 | { 22 | DWORD ErrorCode = GetLastError(); 23 | LPWSTR ErrorMsgBuffer = nullptr; 24 | 25 | FormatMessageW(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, 26 | NULL, ErrorCode, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPWSTR)&ErrorMsgBuffer, 0, NULL); 27 | 28 | if (ErrorMsgBuffer) { 29 | std::wcerr << "Error: " << ErrorMsgBuffer << std::endl; 30 | LocalFree(ErrorMsgBuffer); 31 | } 32 | else { 33 | std::cerr << "Unknown error " << ErrorCode << std::endl; 34 | } 35 | 36 | return EXIT_FAILURE; 37 | } 38 | 39 | 40 | int main(int argc, char** argv) 41 | { 42 | ShinigamiArguments Arguments; 43 | 44 | try 45 | { 46 | Arguments.ParseArguments(argc, argv, PROG_NAME); 47 | } 48 | catch (const std::runtime_error& error) 49 | { 50 | std::cerr << "Exception\n" << std::endl; 51 | std::cerr << error.what() << std::endl; 52 | return EXIT_FAILURE; 53 | } 54 | 55 | const std::wstring& Target = Arguments.GetTarget(); 56 | SimplePE PE(Target); 57 | 58 | if (!PE.IsValid()) 59 | { 60 | std::cerr << "Is not a PE file\n"; 61 | return EXIT_FAILURE; 62 | } 63 | 64 | #ifdef _WIN64 65 | if (PE.Is32Bit()) 66 | { 67 | std::cerr << "Please use Shinigami 32-bit to execute this file!\n"; 68 | return EXIT_FAILURE; 69 | } 70 | #else 71 | if (!PE.Is32Bit()) 72 | { 73 | std::cerr << "Please use Shinigami 64-bit to execute this file!\n"; 74 | return EXIT_FAILURE; 75 | } 76 | #endif 77 | 78 | Injector injector(Arguments.TargetExecutableName); 79 | 80 | if (!injector.InjectSuspended(DLL_NAME, Arguments.GetIchigoArguments(), PE.IsDLL(), Arguments.ExportedFunction)) 81 | return PrintError(); 82 | 83 | return EXIT_SUCCESS; 84 | } 85 | -------------------------------------------------------------------------------- /Shinigami/Ichigo/dllmain.cpp: -------------------------------------------------------------------------------- 1 | // dllmain.cpp : Defines the entry point for the DLL application. 2 | #include "pch.h" 3 | 4 | #include "Ichigo.h" 5 | #include "ProcessUnhollow.h" 6 | #include "Unpacker.h" 7 | #include "Logger.h" 8 | 9 | #define DLL_NAME "Ichigo v1.2" 10 | #define MESSAGEBOX_ERROR_TITLE "Ichigo error" 11 | #define ERR_ICON MB_OK | MB_ICONERROR 12 | #define DLL_EXPORT __declspec(dllexport) 13 | 14 | 15 | 16 | BOOL APIENTRY DllMain( HMODULE hModule, 17 | DWORD ul_reason_for_call, 18 | LPVOID lpReserved 19 | ) 20 | { 21 | switch (ul_reason_for_call) 22 | { 23 | case DLL_PROCESS_ATTACH: 24 | if (!PipeLogger::InitPipe()) 25 | { 26 | MessageBoxA(NULL, "Unable to initialize log pipes! Exiting for safety...", MESSAGEBOX_ERROR_TITLE, ERR_ICON); 27 | ExitProcess(1); 28 | } 29 | PipeLogger::LogInfo(L"Starting " DLL_NAME ".."); 30 | break; 31 | case DLL_THREAD_ATTACH: 32 | case DLL_THREAD_DETACH: 33 | case DLL_PROCESS_DETACH: 34 | GenericUnpacker::Shutdown(); 35 | Unhollow::Shutdown(); 36 | PipeLogger::LogInfo(L"Exiting..."); 37 | PipeLogger::ClosePipe(); 38 | 39 | break; 40 | } 41 | return TRUE; 42 | } 43 | 44 | extern "C" 45 | { 46 | DLL_EXPORT void SetIchigoArguments(Ichigo::Arguments* args) 47 | { 48 | // Maybe we should copy it ? think about if this DLL somehow is injected in a different manner 49 | wmemcpy_s(Ichigo::Options.WorkDirectory, MAX_PATH, args->WorkDirectory, MAX_PATH); 50 | Ichigo::Options.Unhollow.StopAtWrite = args->Unhollow.StopAtWrite; 51 | Ichigo::Options.Quiet = args->Quiet; 52 | Ichigo::Options.OnlyPE = args->OnlyPE; 53 | Ichigo::Options.PID = args->PID; 54 | 55 | PipeLogger::BeQuiet(args->Quiet); 56 | PipeLogger::LogInfo(L"Loaded user aguments"); 57 | } 58 | 59 | DLL_EXPORT void StartIchigo() 60 | { 61 | 62 | if (!Unhollow::InitUnhollowHooks(Ichigo::hkManager, Ichigo::Options) || !GenericUnpacker::InitUnpackerHooks(Ichigo::hkManager, Ichigo::Options)) 63 | { 64 | MessageBoxA(NULL, "Unable to place the needed hooks! Exiting for safety...", MESSAGEBOX_ERROR_TITLE, ERR_ICON); 65 | ExitProcess(1); 66 | } 67 | 68 | PipeLogger::Log(L"Ichigo is ready!"); 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /Shinigami/Shinigami/ShinigamiArguments.cpp: -------------------------------------------------------------------------------- 1 | #include "ShinigamiArguments.h" 2 | 3 | 4 | ShinigamiArguments::ShinigamiArguments() 5 | { 6 | wchar_t cwd[MAX_PATH]; 7 | GetCurrentDirectoryW(MAX_PATH, cwd); 8 | WorkDirectory = cwd; 9 | } 10 | 11 | const std::wstring& ShinigamiArguments::GetTarget() const 12 | { 13 | // First verify if there is arguments here 14 | 15 | return TargetArguments[0]; 16 | } 17 | 18 | void ShinigamiArguments::ParseArguments(int argc, char* argv[], const char* ProgamName) 19 | { 20 | argparse::ArgumentParser parser(ProgamName); 21 | parser.add_argument("program_name") 22 | .help("Name of the program to execute"); 23 | parser.add_argument("--output", "-o") 24 | .help("Directory to dump artefacts"); 25 | parser.add_argument("--stop-at-write") 26 | .implicit_value(true) 27 | .default_value(false) 28 | .help("Unhollow: Stop the execution when the PE file is being to be written"); 29 | parser.add_argument("--verbose") 30 | .implicit_value(true) 31 | .default_value(false) 32 | .help("Display a verbose output"); 33 | parser.add_argument("--only-executables", "-p") 34 | .implicit_value(true) 35 | .default_value(false) 36 | .help("Only extract PE artefacts"); 37 | parser.add_argument("--exported", "-e") 38 | .help("Exported Function: Choose a exported function to execute if the target is a DLL (rundll will be used)"); 39 | 40 | try { 41 | parser.parse_args(argc, argv); 42 | } 43 | catch (const std::runtime_error& e) { 44 | throw std::runtime_error(std::string("Error parsing arguments: ") + e.what()); 45 | } 46 | 47 | TargetExecutableName = EncodingUtils::StringToWstring(parser.get("program_name")); 48 | TargetArguments = EncodingUtils::SplitWide(TargetExecutableName, L" "); 49 | 50 | if (parser.is_used("--output")) 51 | WorkDirectory = EncodingUtils::StringToWstring(parser.get("--output")); 52 | 53 | if (parser.is_used("--exported")) 54 | ExportedFunction = EncodingUtils::StringToWstring(parser.get("--exported")); 55 | 56 | 57 | wcsncpy_s(IchiArguments.WorkDirectory, MAX_PATH, WorkDirectory.c_str(), _TRUNCATE); 58 | 59 | IchiArguments.Unhollow.StopAtWrite = parser.get("--stop-at-write"); 60 | IchiArguments.Quiet = !parser.get("--verbose"); 61 | IchiArguments.OnlyPE = parser.get("--only-executables"); 62 | 63 | } -------------------------------------------------------------------------------- /Shinigami/Ichigo/Ichigo.vcxproj.filters: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | {4FC737F1-C7A5-4376-A066-2A32D752A2FF} 6 | cpp;c;cc;cxx;c++;cppm;ixx;def;odl;idl;hpj;bat;asm;asmx 7 | 8 | 9 | {93995380-89BD-4b04-88EB-625FBE52EBFB} 10 | h;hh;hpp;hxx;h++;hm;inl;inc;ipp;xsd 11 | 12 | 13 | {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} 14 | rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms 15 | 16 | 17 | 18 | 19 | Header Files 20 | 21 | 22 | Header Files 23 | 24 | 25 | Header Files 26 | 27 | 28 | Header Files 29 | 30 | 31 | Header Files 32 | 33 | 34 | Header Files 35 | 36 | 37 | Header Files 38 | 39 | 40 | Header Files 41 | 42 | 43 | Header Files 44 | 45 | 46 | Header Files 47 | 48 | 49 | Header Files 50 | 51 | 52 | 53 | 54 | Source Files 55 | 56 | 57 | Source Files 58 | 59 | 60 | Source Files 61 | 62 | 63 | Source Files 64 | 65 | 66 | Source Files 67 | 68 | 69 | Source Files 70 | 71 | 72 | Source Files 73 | 74 | 75 | Source Files 76 | 77 | 78 | -------------------------------------------------------------------------------- /.github/workflows/cd.yml: -------------------------------------------------------------------------------- 1 | name: Release 2 | 3 | env: 4 | X86_BUILD_PATH: "Shinigami/Release" 5 | X64_BUILD_PATH: "Shinigami/x64/Release" 6 | 7 | on: 8 | push: 9 | tags: 10 | - 'v*' # Push events to tagged releases (i.e. v1.0, v2.0, etc.) 11 | 12 | jobs: 13 | package_release: 14 | runs-on: windows-latest 15 | steps: 16 | - uses: actions/checkout@v2 17 | 18 | 19 | - name: Install Visual Studio Build Tools 20 | uses: microsoft/setup-msbuild@v1.0.2 21 | with: 22 | vs-version: '17.0' 23 | include-prerelease: true 24 | 25 | # Install vcpkg 26 | - name: Install vcpkg 27 | run: | 28 | git clone https://github.com/Microsoft/vcpkg.git 29 | cd vcpkg 30 | # Dirty hack 31 | git checkout a325228200d7f229f3337e612e0077f2a5307090 32 | .\bootstrap-vcpkg.bat 33 | 34 | # Install vcpkg dependencies 35 | - name: Install vcpkg dependencies 36 | run: | 37 | cd vcpkg 38 | .\vcpkg install zydis:x86-windows zydis:x64-windows 39 | .\vcpkg integrate install 40 | 41 | # Build the Ichigo and Shinigami projects 42 | - name: Build the Ichigo and Shinigami projects 43 | run: | 44 | cd Shinigami 45 | msbuild Shinigami.sln /t:Ichigo`;Shinigami /p:Configuration=Release /p:Platform=x64 46 | msbuild Shinigami.sln /t:Ichigo`;Shinigami /p:Configuration=Release /p:Platform=x86 47 | 48 | - name: Create Release and Zip Binaries 49 | run: | 50 | mkdir release 51 | Remove-Item -Path $env:X86_BUILD_PATH/*.pdb 52 | Remove-Item -Path $env:X64_BUILD_PATH/*.pdb 53 | 54 | Compress-Archive -LiteralPath $env:X86_BUILD_PATH -DestinationPath release/Shinigami-x86.zip 55 | Compress-Archive -LiteralPath $env:X64_BUILD_PATH -DestinationPath release/Shinigami-x64.zip 56 | # Create a new release 57 | 58 | - name: Create Release 59 | id: create_release 60 | uses: actions/create-release@v1 61 | env: 62 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 63 | with: 64 | tag_name: ${{ github.ref }} 65 | release_name: Release ${{ github.ref }} 66 | draft: false 67 | prerelease: false 68 | 69 | # Upload release artifacts 70 | - name: Upload Release Artifacts 71 | id: upload-release-asset-x86 72 | uses: actions/upload-release-asset@v1 73 | env: 74 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 75 | with: 76 | upload_url: ${{ steps.create_release.outputs.upload_url }} 77 | asset_path: release/Shinigami-x86.zip 78 | asset_name: Shinigami-x86.zip 79 | asset_content_type: application/zip 80 | 81 | - name: Upload Release Artifacts 82 | id: upload-release-asset-x64 83 | uses: actions/upload-release-asset@v1 84 | env: 85 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 86 | with: 87 | upload_url: ${{ steps.create_release.outputs.upload_url }} 88 | asset_path: release/Shinigami-x64.zip 89 | asset_name: Shinigami-x64.zip 90 | asset_content_type: application/zip 91 | -------------------------------------------------------------------------------- /Shinigami/Shinigami/Injector.cpp: -------------------------------------------------------------------------------- 1 | #include "Injector.h" 2 | 3 | // 4 | // Load the ichigo.dll library, set it's options and start the hooks 5 | // 6 | VOID __stdcall LoadDLL(ULONG_PTR Params) 7 | { 8 | ThreadData* ThData = reinterpret_cast(Params); 9 | HMODULE hModule = ThData->LoadLibraryW(ThData->DllName); 10 | 11 | if (!hModule) 12 | { 13 | ThData->ExitProcess(ThData->GetLastError()); 14 | } 15 | SetIchigoArguments SetArgsFunc = reinterpret_cast(ThData->GetProcAddress(hModule, ThData->SetArgumentsFuncName)); 16 | StartIchigo StartIchigoFunc = reinterpret_cast(ThData->GetProcAddress(hModule, ThData->StartIchigoFuncName)); 17 | if (!SetArgsFunc || !StartIchigoFunc) 18 | { 19 | ThData->ExitProcess(1); 20 | } 21 | 22 | SetArgsFunc(&ThData->Arguments); 23 | StartIchigoFunc(); 24 | } 25 | 26 | 27 | Injector::Injector(const std::wstring& ProcName) 28 | : ProcName(ProcName) 29 | {} 30 | 31 | // Create a suspended process 32 | // Inject the DLL using APC callbacks 33 | BOOL Injector::InjectSuspended(_In_ const std::wstring& DLLPath, _In_ const IchigoArguments& DLLArguments, BOOL IsDLL, const std::wstring& ExportedFunction) 34 | { 35 | STARTUPINFOW si; 36 | PROCESS_INFORMATION pi; 37 | DWORD ExitCode; 38 | ZeroMemory(&si, sizeof(si)); 39 | ZeroMemory(&si, sizeof(pi)); 40 | si.cb = sizeof(si); 41 | 42 | if (IsDLL) 43 | { 44 | ProcName = BuildRunDLLCommand(ProcName, ExportedFunction); 45 | Logger::LogInfo(L"Target is a DLL, injecting into the rundll32 process!"); 46 | } 47 | 48 | // Create process suspended 49 | bool status = CreateProcess( 50 | nullptr, 51 | (LPWSTR)ProcName.c_str(), 52 | NULL, 53 | NULL, 54 | NULL, 55 | CREATE_SUSPENDED, 56 | NULL, 57 | NULL, 58 | &si, 59 | &pi 60 | ); 61 | 62 | if (!status) return false; 63 | 64 | // Inject DLL using APC 65 | status = APCLoadDLL(pi, DLLPath, DLLArguments); 66 | 67 | if (!status) 68 | { 69 | TerminateProcess(pi.hProcess, EXIT_FAILURE); 70 | goto quit; 71 | } 72 | 73 | Logger::LogInfo(L"Injection finished, waiting connection..."); 74 | if (!PipeLogger::InitPipe()) 75 | { 76 | Logger::LogInfo(L"Unable to initialize log pipes! Error: %d\n", GetLastError()); 77 | TerminateProcess(pi.hProcess, 0); 78 | goto quit; 79 | } 80 | 81 | ResumeThread(pi.hThread); 82 | WaitForSingleObject(pi.hThread, INFINITE); 83 | GetExitCodeProcess(pi.hProcess, &ExitCode); 84 | 85 | Logger::LogInfo(L"Child process exited with code %d, closing...", ExitCode); 86 | quit: 87 | CloseHandle(pi.hThread); 88 | CloseHandle(pi.hProcess); 89 | PipeLogger::ClosePipe(); 90 | 91 | return status; 92 | } 93 | 94 | // Inject a APC callback to be called before the suspended process entrypoint that will load the target DLL by calling LoadLibrary 95 | BOOL Injector::APCLoadDLL(_In_ const PROCESS_INFORMATION& pi, _In_ const std::wstring& DLLName, _In_ const IchigoArguments& DLLArguments) const 96 | { 97 | // Setup thread data 98 | ThreadData th; 99 | SIZE_T BytesWritten; 100 | 101 | ZeroMemory(&th, sizeof(th)); 102 | // Maybe it's better to handle this function differently in the future 103 | th.LoadLibraryW = LoadLibraryW; 104 | th.GetProcAddress = GetProcAddress; 105 | th.ExitProcess = ExitProcess; 106 | th.GetLastError = GetLastError; 107 | 108 | // 109 | // Exported DLL functions to be called inside the injected code 110 | // 111 | memcpy_s(th.SetArgumentsFuncName, MAX_PATH, SET_ICHIGO_ARGS_FUNC_NAME, sizeof(SET_ICHIGO_ARGS_FUNC_NAME)); 112 | memcpy_s(th.StartIchigoFuncName, MAX_PATH, START_ICHIGO_FUNC_NAME, sizeof(START_ICHIGO_FUNC_NAME)); 113 | 114 | RtlCopyMemory(&th.Arguments, &DLLArguments, sizeof(th.Arguments)); 115 | wmemcpy_s(th.DllName, MAX_PATH, DLLName.c_str(), DLLName.size()); 116 | th.Arguments.PID = pi.dwProcessId; 117 | 118 | 119 | LPVOID pThreadData = VirtualAllocEx(pi.hProcess, NULL, sizeof(th), MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE); 120 | if (pThreadData == nullptr) return FALSE; 121 | 122 | if (!WriteProcessMemory(pi.hProcess, pThreadData, &th, sizeof(th), &BytesWritten)) 123 | { 124 | VirtualFreeEx(pi.hProcess, pThreadData, 0, MEM_RELEASE); 125 | return FALSE; 126 | } 127 | 128 | LPVOID pLoadDLLCode = VirtualAllocEx(pi.hProcess, NULL, INJECTED_SIZE, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE); 129 | if (!pLoadDLLCode) 130 | { 131 | VirtualFreeEx(pi.hProcess, pThreadData, 0, MEM_RELEASE); 132 | return FALSE; 133 | } 134 | 135 | if (!WriteProcessMemory(pi.hProcess, pLoadDLLCode, LoadDLL, INJECTED_SIZE, &BytesWritten)) 136 | { 137 | VirtualFreeEx(pi.hProcess, pThreadData, 0, MEM_RELEASE); 138 | VirtualFreeEx(pi.hProcess, pLoadDLLCode, 0, MEM_RELEASE); 139 | return FALSE; 140 | } 141 | 142 | if (!QueueUserAPC((PAPCFUNC)pLoadDLLCode, pi.hThread, (ULONG_PTR)pThreadData)) 143 | { 144 | VirtualFreeEx(pi.hProcess, pThreadData, 0, MEM_RELEASE); 145 | VirtualFreeEx(pi.hProcess, pLoadDLLCode, 0, MEM_RELEASE); 146 | return FALSE; 147 | } 148 | 149 | return TRUE; 150 | } 151 | 152 | std::wstring Injector::BuildRunDLLCommand(const std::wstring& DLLPath, const std::wstring& ExportedFunction) 153 | { 154 | // Hack to fix std::wstring behaviour on concating wide strings, when calling c_str() it dont come complete 155 | std::wstring RunDLL32 = L"rundll32.exe " + std::wstring(DLLPath.c_str()) + L" " + std::wstring(ExportedFunction.c_str()); 156 | return RunDLL32; 157 | } 158 | 159 | 160 | 161 | -------------------------------------------------------------------------------- /Shinigami/Ichigo/defs.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | 4 | typedef ULONG_PTR NTSTATUS; 5 | #define NT_SUCCESS(Status) (((NTSTATUS)(Status)) >= 0) 6 | #define NT_ERROR(Status) ((((ULONG)(Status)) >> 30) == 3) 7 | #define STATUS_SUCCESS 0 8 | 9 | typedef struct _UNICODE_STRING { 10 | USHORT Length; 11 | USHORT MaximumLength; 12 | PWSTR Buffer; 13 | } UNICODE_STRING, * PUNICODE_STRING; 14 | 15 | 16 | typedef struct _CURDIR { 17 | UNICODE_STRING DosPath; 18 | HANDLE Handle; 19 | } CURDIR, * PCURDIR; 20 | 21 | typedef struct _RTL_DRIVE_LETTER_CURDIR { 22 | USHORT Flags; 23 | USHORT Length; 24 | ULONG TimeStamp; 25 | UNICODE_STRING DosPath; 26 | } RTL_DRIVE_LETTER_CURDIR, * PRTL_DRIVE_LETTER_CURDIR; 27 | 28 | typedef struct _OBJECT_ATTRIBUTES { 29 | ULONG Length; 30 | HANDLE RootDirectory; 31 | PUNICODE_STRING ObjectName; 32 | ULONG Attributes; 33 | PVOID SecurityDescriptor; 34 | PVOID SecurityQualityOfService; 35 | } OBJECT_ATTRIBUTES, * POBJECT_ATTRIBUTES; 36 | 37 | typedef struct _RTL_USER_PROCESS_PARAMETERS { 38 | ULONG MaximumLength; 39 | ULONG Length; 40 | ULONG Flags; 41 | ULONG DebugFlags; 42 | HANDLE ConsoleHandle; 43 | ULONG ConsoleFlags; 44 | HANDLE StandardInput; 45 | HANDLE StandardOutput; 46 | HANDLE StandardError; 47 | CURDIR CurrentDirectory; 48 | UNICODE_STRING DllPath; 49 | UNICODE_STRING ImagePathName; 50 | UNICODE_STRING CommandLine; 51 | PVOID Environment; 52 | ULONG StartingPositionLeft; 53 | ULONG StartingPositionTop; 54 | ULONG Width; 55 | ULONG Height; 56 | ULONG CharWidth; 57 | ULONG CharHeight; 58 | ULONG ConsoleTextAttributes; 59 | ULONG WindowFlags; 60 | ULONG ShowWindowFlags; 61 | UNICODE_STRING WindowTitle; 62 | UNICODE_STRING DesktopInfo; 63 | UNICODE_STRING ShellInfo; 64 | UNICODE_STRING RuntimeData; 65 | RTL_DRIVE_LETTER_CURDIR CurrentDirectores[32]; 66 | ULONG EnvironmentSize; 67 | } RTL_USER_PROCESS_PARAMETERS, * PRTL_USER_PROCESS_PARAMETERS; 68 | 69 | 70 | typedef struct _PS_ATTRIBUTE { 71 | ULONG Attribute; 72 | SIZE_T Size; 73 | union { 74 | ULONG_PTR Value; 75 | PVOID ValuePtr; 76 | }; 77 | PSIZE_T ReturnLength; 78 | } PS_ATTRIBUTE, * PPS_ATTRIBUTE; 79 | 80 | 81 | typedef struct _PS_ATTRIBUTE_LIST { 82 | SIZE_T TotalLength; 83 | PS_ATTRIBUTE Attributes[ANYSIZE_ARRAY]; 84 | } PS_ATTRIBUTE_LIST, * PPS_ATTRIBUTE_LIST; 85 | 86 | 87 | typedef struct _PS_CREATE_INFO { 88 | SIZE_T Size; 89 | union { 90 | ULONG Flags; 91 | struct { 92 | ULONG FileOpenNameAvailable : 1; 93 | ULONG Reserved : 31; 94 | }; 95 | }; 96 | HANDLE ParentProcess; 97 | HANDLE DebugPort; 98 | HANDLE ExceptionPort; 99 | LARGE_INTEGER CreateTime; 100 | SIZE_T CommandLineLength; 101 | PWCH CommandLine; 102 | PVOID Environment; 103 | UNICODE_STRING CurrentDirectory; 104 | HANDLE CurrentDirectoryHandle; 105 | UNICODE_STRING DllPath; 106 | UNICODE_STRING ImageName; 107 | ULONG_PTR DlpBase; // Deprecated 108 | ULONG_PTR DlpSize; // Deprecated 109 | ULONG_PTR DlpSectionBase; // Deprecated 110 | ULONG_PTR DlpSectionSize; // Deprecated 111 | PS_ATTRIBUTE_LIST AttributeList; 112 | PS_ATTRIBUTE_LIST* AttributeListPtr; 113 | } PS_CREATE_INFO, * PPS_CREATE_INFO; 114 | 115 | 116 | typedef BOOL(WINAPI* pCreateProcessternalW) ( 117 | HANDLE hUserToken, 118 | LPCWSTR lpApplicationName, 119 | LPWSTR lpCommandLine, 120 | LPSECURITY_ATTRIBUTES lpProcessAttributes, 121 | LPSECURITY_ATTRIBUTES lpThreadAttributes, 122 | BOOL bheritHandles, 123 | DWORD dwCreationFlags, 124 | LPVOID lpEnvironment, 125 | LPCWSTR lpCurrentDirectory, 126 | LPSTARTUPINFOW lpStartupinfo, 127 | LPPROCESS_INFORMATION lpProcessformation, 128 | PHANDLE hNewToken 129 | ); 130 | 131 | typedef DWORD (WINAPI* pResumeThread) ( 132 | HANDLE hThread 133 | ); 134 | 135 | typedef NTSTATUS (WINAPI NtResumeThread)( 136 | HANDLE ThreadHandle, 137 | PULONG SuspendCount 138 | ); 139 | 140 | typedef NTSTATUS (WINAPI NtAllocateVirtualMemory) ( 141 | HANDLE ProcessHandle, 142 | PVOID* BaseAddress, 143 | ULONG_PTR ZeroBits, 144 | PSIZE_T RegionSize, 145 | ULONG AllocationType, 146 | ULONG Protect 147 | ); 148 | 149 | typedef NTSTATUS (WINAPI NtWriteVirtualMemory) ( 150 | HANDLE ProcessHandle, 151 | PVOID BaseAddress, 152 | PVOID Buffer, 153 | ULONG NumberOfBytesToWrite, 154 | PULONG NumberOfBytesWritten 155 | ); 156 | 157 | typedef NTSTATUS (WINAPI NtCreateUserProcess) 158 | ( 159 | PHANDLE ProcessHandle, 160 | PHANDLE ThreadHandle, 161 | ACCESS_MASK ProcessDesiredAccess, 162 | ACCESS_MASK ThreadDesiredAccess, 163 | POBJECT_ATTRIBUTES ProcessObjectAttributes, 164 | POBJECT_ATTRIBUTES ThreadObjectAttributes, 165 | ULONG ProcessFlags, 166 | ULONG ThreadFlags, 167 | PRTL_USER_PROCESS_PARAMETERS ProcessParameters, 168 | PPS_CREATE_INFO CreateInfo, 169 | PPS_ATTRIBUTE_LIST AttributeList 170 | ); 171 | 172 | typedef NTSTATUS (WINAPI NtProtectVirtualMemory) ( 173 | HANDLE ProcessHandle, 174 | PVOID* BaseAddress, 175 | PSIZE_T RegionSize, 176 | ULONG NewProtect, 177 | PULONG OldProtect 178 | ); 179 | 180 | 181 | struct WinAPIPointers { 182 | NtAllocateVirtualMemory* NtAllocateVirtualMemory; 183 | NtWriteVirtualMemory* NtWriteVirtualMemory; 184 | NtCreateUserProcess* NtCreateUserProcess; 185 | NtResumeThread* NtResumeThread; 186 | NtProtectVirtualMemory* NtProtectVirtualMemory; 187 | }; 188 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Shinigami 2 | [![Build](https://github.com/buzzer-re/Shinigami/actions/workflows/ci.yml/badge.svg)](https://github.com/buzzer-re/Shinigami/actions/workflows/ci.yml) 3 | [![Release](https://github.com/buzzer-re/Shinigami/actions/workflows/cd.yml/badge.svg)](https://github.com/buzzer-re/Shinigami/actions/workflows/cd.yml) 4 | 5 |
6 | image description 7 |
8 | 9 | 10 | Shinigami is an experimental tool designed to detect and unpack malware implants that are injected via process hollowing or generic packer routines. 11 | 12 | # How this works 13 | The tool operates by hooking NT functions related to Process Hollowing and marking newly executable memory pages with the page guard bit. This technique allows Shinigami to detect indirect flow changes, typically caused by shellcode or unpacked code, which are often indicative of malware. 14 | Shinigami creates the target executable in a suspended state and injects a DLL library called "Ichigo". This library automatically hooks every necessary function to detect and extract the implant. Once the artefact is fully extracted, the tool will kill the process. 15 | 16 | Shinigami effectiveness may vary depending on the specific malware it is targeting. However, it is a valuable addition to any malware analysis toolkit and may prove useful in detecting and analyzing malware that uses process hollowing or generic packer routines. 17 | 18 | ***Important: This is a dynamic unpacking tool and should not be run on your personal machine or in a static analysis lab*** 19 | 20 | 21 | 22 | # Current unpack methods 23 | 24 | ## Process Hollowing 25 | 26 | Shinigami's core method for extracting implants injected using process hollowing involves hooking two NT functions: NtResumeThread and NtWriteVirtualMemory. Here's how it works: 27 | - `NtResumeThread` Hook 28 | - Shinigami hunts through all the previously allocated memory via NtAllocateVirtualMemory to check if it contains the DOS header signature. If found, it extracts the remote PE file, patches it to have the sections aligned, and saves it on disk with the format ***filename_dumped.bin***. 29 | 30 | - `NtWriteVirtualMemory` Hook 31 | - Shinigami detects if the executable is trying to write a PE file in the remote process by hooking NtWriteVirtualMemory. If a PE file is found, it is extracted using the Buffer pointer and saved on disk as ***filename_before_written.bin***, this option is only used if the flag `--stop-at-write` is passed. 32 | 33 | ## Generic unpacker 34 | Shinigami's generic unpacker module marks newly allocated memory areas with the PAGE_GUARD bit, it also applies this bit if any existing memory area has its protections replaced by something executable. By using guard pages, it can track which memory area is going to be used to allocate some shellcode or PE images. 35 | 36 | For each shellcode detected, Shinigami saves the raw shellcode itself on disk. It also scans that memory region to find any PE files and saves them as well. Shinigami treats every different shellcode execution as a new stage, so by the end, you will have your working directory with files called `filename_shellcode_STAGENUM.bin or .exe`. 37 | 38 | 39 | 40 | ## Usage 41 | 42 | The tool has a couple of options: 43 | 44 | 45 | ```bash 46 | Usage: Shinigami [--help] [--version] [--output VAR] [--stop-at-write] [--verbose] [--only-executables] [--exported VAR] program_name 47 | 48 | Positional arguments: 49 | program_name Name of the program to execute 50 | 51 | Optional arguments: 52 | -h, --help shows help message and exits 53 | -v, --version prints version information and exits 54 | -o, --output Directory to dump artefacts 55 | --stop-at-write Unhollow: Stop the execution when the PE file is being to be written 56 | --verbose Display a verbose output 57 | -p, --only-executables Only extract PE artefacts 58 | -e, --exported Exported Function: Choose a exported function to execute if the target is a DLL (rundll will be used) 59 | ``` 60 | 61 | Some important options are: 62 | 63 | ***-o, --output***: Specifies the directory to dump the extracted artifacts. By default, extracted artifacts will be saved to a directory called output in the current working directory. You can specify a different directory by passing its path as an argument. 64 | 65 | ***--stop-at-write***: This argument is used during the unpacking of a hollowed process. When Shinigami detects that the PE file is being written to the hollowed process, it will stop the execution and save the extracted PE file. This option can be useful if you want to avoid executing the entire hollowed process and only need to extract the unpacked code. 66 | 67 | ***--verbose***: Displays a verbose output. This can be useful for debugging or understanding the inner workings of Shinigami. 68 | 69 | 70 | ### Example usage: 71 | ## Unhollow 72 | 73 | |![](assets/screenshots/test.png)| 74 | |:--:| 75 | |Testing against Dridex| 76 | 77 | After the extraction is done, the process is killed and you will have (I hope so) the extracted PE: 78 | 79 | |![](assets/screenshots/pe.png)| 80 | |:--:| 81 | |Dumped implant| 82 | 83 | 84 | The detected implant will be dumped following the format described at [detection methods](#current-detection-methods) section 85 | 86 | ## Unpacking 87 | 88 | |![](assets/screenshots/unpack_example.png)| 89 | |:--:| 90 | |Unpacking a random loader described [here](https://reversing.codes/posts/Manual-unpacking-in-details/)| 91 | 92 | In the example above, Shinigami automatically detected the behavior of a generic loader and extracted all the executed shellcodes and images within it, without requiring any specific switches to enable or disable the unpacking routine. This was possible because Shinigami shares some functions with the unhollow module, using shared hooks provided by the [Gancho](https://github.com/buzzer-re/gancho) library. 93 | 94 | 95 | ## Emotet 96 | 97 | |![](assets/screenshots/emotet.png)| 98 | |:--:| 99 | |Unpacking Emotet DLL| 100 | 101 | Shinigami also has DLL support and the ability to rebuild injected binaries using detached DOS headers. Notably, malware samples like Emotet use this technique to evade in-memory PE scanners. Shinigami detects such missing parts (DOS header) and employs heuristics to reconstruct them. 102 | 103 | ## Installing 104 | 105 | Grab your flavor at the [Release](https://github.com/buzzer-re/Shinigami/releases) page. 106 | 107 | ## Building and hacking 108 | 109 | It would be amazing if you help this project, so if you want here is the dependencies and steps 110 | 111 | 112 | - Install Visual Studio >= 2019 with the C++ workload. 113 | - Install [Zydis](https://github.com/zyantific/zydis), a fast and lightweight x86/x86-64 disassembler, I recommend use the [vcpkg](https://github.com/zyantific/zydis#building-zydis---using-vcpkg) method. 114 | - Zydis is used to make sure that the api hooking more precise and to ensure that no opcode is lost when copying. 115 | - Open the Shinigami.sln solution file in Visual Studio and build/code the project. 116 | 117 | Please open an issue or pull request for any changes you would like to make. 118 | 119 | ## Conclusion 120 | 121 | This cool mascot image was inspired on Bleach and generated by [Dall-E](https://openai.com/product/dall-e-2). 122 | 123 | -------------------------------------------------------------------------------- /Shinigami/Shinigami/Shinigami.vcxproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Debug 6 | Win32 7 | 8 | 9 | Release 10 | Win32 11 | 12 | 13 | Debug 14 | x64 15 | 16 | 17 | Release 18 | x64 19 | 20 | 21 | 22 | 16.0 23 | Win32Proj 24 | {036f4f12-d4eb-4b57-adae-3b286a6a1900} 25 | Shinigami 26 | 10.0 27 | 28 | 29 | 30 | Application 31 | true 32 | v143 33 | Unicode 34 | 35 | 36 | Application 37 | false 38 | v143 39 | true 40 | Unicode 41 | 42 | 43 | Application 44 | true 45 | v143 46 | Unicode 47 | 48 | 49 | Application 50 | false 51 | v143 52 | true 53 | Unicode 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | Level3 76 | true 77 | WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions) 78 | true 79 | stdcpp17 80 | 81 | 82 | Console 83 | true 84 | 85 | 86 | 87 | 88 | Level3 89 | true 90 | true 91 | true 92 | WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) 93 | true 94 | stdcpp17 95 | 96 | 97 | Console 98 | true 99 | true 100 | true 101 | 102 | 103 | 104 | 105 | Level3 106 | true 107 | _DEBUG;_CONSOLE;%(PreprocessorDefinitions) 108 | true 109 | stdcpp17 110 | 111 | 112 | Console 113 | true 114 | 115 | 116 | 117 | 118 | Level3 119 | true 120 | true 121 | true 122 | NDEBUG;_CONSOLE;%(PreprocessorDefinitions) 123 | true 124 | stdcpp17 125 | 126 | 127 | Console 128 | true 129 | true 130 | true 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | -------------------------------------------------------------------------------- /Shinigami/Ichigo/Ichigo.vcxproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Debug 6 | Win32 7 | 8 | 9 | Release 10 | Win32 11 | 12 | 13 | Debug 14 | x64 15 | 16 | 17 | Release 18 | x64 19 | 20 | 21 | 22 | 16.0 23 | Win32Proj 24 | {3407133b-3cf8-46a9-8a9f-ab5286c91bc7} 25 | Ichigo 26 | 10.0 27 | 28 | 29 | 30 | DynamicLibrary 31 | true 32 | v143 33 | Unicode 34 | 35 | 36 | DynamicLibrary 37 | false 38 | v143 39 | true 40 | Unicode 41 | 42 | 43 | DynamicLibrary 44 | true 45 | v143 46 | Unicode 47 | 48 | 49 | DynamicLibrary 50 | false 51 | v143 52 | true 53 | Unicode 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | Level3 76 | true 77 | WIN32;_DEBUG;ICHIGO_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) 78 | true 79 | Use 80 | pch.h 81 | 82 | 83 | Windows 84 | true 85 | false 86 | 87 | 88 | 89 | 90 | Level3 91 | true 92 | true 93 | true 94 | WIN32;NDEBUG;ICHIGO_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) 95 | true 96 | Use 97 | pch.h 98 | 99 | 100 | Windows 101 | true 102 | true 103 | true 104 | false 105 | 106 | 107 | 108 | 109 | Level3 110 | true 111 | _DEBUG;ICHIGO_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) 112 | true 113 | Use 114 | pch.h 115 | 116 | 117 | Windows 118 | true 119 | false 120 | 121 | 122 | 123 | 124 | Level3 125 | true 126 | true 127 | true 128 | NDEBUG;ICHIGO_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) 129 | true 130 | Use 131 | pch.h 132 | 133 | 134 | Windows 135 | true 136 | true 137 | true 138 | false 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | Create 162 | Create 163 | Create 164 | Create 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | -------------------------------------------------------------------------------- /Shinigami/Ichigo/HookManager.cpp: -------------------------------------------------------------------------------- 1 | #include "pch.h" 2 | #include "HookManager.h" 3 | #include 4 | #include "Logger.h" 5 | 6 | HookManager::HookManager() 7 | { 8 | #if defined(_WIN64) 9 | ZydisDecoderInit(&ZDecoder, ZYDIS_MACHINE_MODE_LONG_64, ZYDIS_ADDRESS_WIDTH_64); 10 | #else 11 | ZydisDecoderInit(&ZDecoder, ZYDIS_MACHINE_MODE_LEGACY_32, ZYDIS_ADDRESS_WIDTH_32); 12 | #endif 13 | } 14 | 15 | 16 | LPVOID 17 | HookManager::AddHook( 18 | _In_ BYTE* Src, 19 | _In_ BYTE* Dst, 20 | _In_ BOOL IgnoreProt 21 | ) 22 | { 23 | Hook* NewHook; 24 | auto it = HookChain.find(Src); 25 | 26 | if (it != HookChain.end()) 27 | { 28 | Hook* LastHook = it->second.back(); 29 | // 30 | // Add new hook in the hook chain, so that way all the hooks are called recursively 31 | // 32 | #if defined(_WIN64) 33 | NewHook = Hook64((BYTE*) LastHook->GatewayAddr, Dst, IgnoreProt); 34 | #else 35 | NewHook = Hook32((BYTE*)LastHook->GatewayAddr, Dst, IgnoreProt); 36 | #endif 37 | } 38 | else 39 | { 40 | #if defined(_WIN64) 41 | NewHook = Hook64(Src, Dst, IgnoreProt); 42 | #else 43 | NewHook = Hook32(Src, Dst, IgnoreProt); 44 | #endif 45 | } 46 | // 47 | // Sad, we failed ;-; 48 | // 49 | if (NewHook == nullptr) 50 | return nullptr; 51 | 52 | // 53 | // Insert new hook on the manager map 54 | // 55 | HookChain[Src].push_back(NewHook); 56 | 57 | return NewHook->GatewayAddr; 58 | } 59 | 60 | 61 | VOID HookManager::DisassambleAt(_In_ ULONG_PTR* Address, _In_ SIZE_T NumberOfInstructions) 62 | { 63 | ZydisFormatter formatter; 64 | ZydisFormatterInit(&formatter, ZYDIS_FORMATTER_STYLE_INTEL); 65 | CHAR Buffer[256]; 66 | 67 | 68 | for (SIZE_T i = 0; i < NumberOfInstructions; i++) 69 | { 70 | // Decode the instruction at the specified address 71 | ZydisDecodedInstruction instruction; 72 | ZydisDecoderDecodeBuffer(&ZDecoder, reinterpret_cast(Address), 16, &instruction); 73 | 74 | // Format the instruction and print it to the console 75 | 76 | ZydisFormatterFormatInstruction(&formatter, &instruction, Buffer, sizeof(Buffer), (ZyanU64)Address); 77 | std::printf("0x%x - %s\n", Address, Buffer); 78 | Address = (ULONG_PTR*)((BYTE*)Address + instruction.length); 79 | } 80 | } 81 | 82 | Hook* 83 | HookManager::Hook64(_In_ BYTE* Src, _In_ BYTE* Dst, _In_ BOOL IgnoreProt) 84 | { 85 | // 86 | // This is the base template trampoline code that will be used in future operations. 87 | // The "pop rax" instruction is necessary to restore the original value of the register and ensure that we don't mess up the function's logic. 88 | // We don't use the "push" instruction here because this is the beginning of the function. 89 | // If this hook is going to be used in the middle of a function in the future, 90 | // we will need to push rax to the stack first to preserve its value. 91 | // 92 | BYTE JumpToHookCode[] = { 93 | 0x48, 0xB8 , 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // mov rax,
94 | 0xFF, 0xE0, // jmp rax 95 | 0x58, // pop rax 96 | }; 97 | // 98 | // The stolen bytes will be saved in this buffer to execute later 99 | // We push rax to preserve its value, which will be recovered by the pop rax instruction in the trampoline code 100 | // 101 | BYTE JumpBackCode[] = { 102 | 0x50, // push rax 103 | 0x48, 0xB8 , 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // mov rax,
104 | 0xFF, 0xE0, // jmp rax 105 | }; 106 | // 107 | // Pointer to the Src, as this will be incremented later 108 | // 109 | BYTE* pSrc = Src; 110 | // 111 | // Holds how many bytes should be copied before place the trampoline 112 | // 113 | BYTE overlap = 0; 114 | 115 | // 116 | // Dissasemble and analyze the instructions make sure that everything is aligned and working properly 117 | // 118 | ZydisDecodedInstruction inst; 119 | 120 | // 121 | // Hook structure to store core information about this hook 122 | // 123 | Hook* HookStructure; 124 | 125 | // 126 | // Disassemble to pick the instructions length 127 | // 128 | while (ZYAN_SUCCESS(ZydisDecoderDecodeBuffer(&ZDecoder, pSrc, X64_TRAMPOLINE_SIZE, &inst)) && overlap < X64_TRAMPOLINE_SIZE) 129 | { 130 | overlap += inst.length; 131 | pSrc += inst.length; 132 | } 133 | 134 | // Allocate memory to store the overwritten bytes and the jump back trampoline 135 | BYTE* pOldCode = reinterpret_cast(VirtualAlloc(NULL, overlap + X64_TRAMPOLINE_SIZE + NOP_SLIDE, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE)); 136 | if (pOldCode == nullptr) { 137 | return nullptr; 138 | } 139 | 140 | DWORD dwOldProtect; 141 | 142 | // 143 | // Store the original code, nop everything and build the trampoline code to jump back 144 | // 145 | VirtualProtect(Src, overlap, PAGE_EXECUTE_READWRITE, &dwOldProtect); 146 | 147 | // 148 | // Add a NOP slide to give a good space to the HookChain structure 149 | // 150 | memset(pOldCode, NOP, NOP_SLIDE); 151 | // 152 | // Copy the exactly instructions that should be executed, extracted by Zydis 153 | // 154 | memcpy_s(pOldCode + NOP_SLIDE, overlap + X64_TRAMPOLINE_SIZE, Src, overlap); 155 | // 156 | // Nop Src to avoid execute invalid instructions 157 | // 158 | memset(Src, NOP, overlap); 159 | // 160 | // Add the jump back to the next instruction 161 | // 162 | *(ULONG_PTR*)(JumpBackCode + 3) = (ULONG_PTR)(Src + X64_TRAMPOLINE_SIZE - 1); 163 | 164 | memcpy_s(pOldCode + overlap + NOP_SLIDE, X64_TRAMPOLINE_SIZE, JumpBackCode, X64_TRAMPOLINE_SIZE); 165 | 166 | // 167 | // Build the trampoline code to jump to the hook function 168 | // 169 | *(ULONG_PTR*)(JumpToHookCode + 2) = (ULONG_PTR)Dst; 170 | 171 | memcpy_s(Src, X64_TRAMPOLINE_SIZE, JumpToHookCode, X64_TRAMPOLINE_SIZE); 172 | 173 | if (!IgnoreProt) 174 | VirtualProtect(Src, X64_TRAMPOLINE_SIZE, dwOldProtect, &dwOldProtect); 175 | 176 | HookStructure = new Hook; 177 | HookStructure->HookAddr = Dst; 178 | HookStructure->OriginalAddr = Src; 179 | HookStructure->GatewayAddr = pOldCode; 180 | HookStructure->NumInstLeftToExec = overlap; 181 | 182 | return HookStructure; 183 | } 184 | 185 | Hook* 186 | HookManager::Hook32( 187 | _In_ BYTE* Src, 188 | _In_ BYTE* Dst, 189 | _In_ BOOL IgnoreProt 190 | ) 191 | { 192 | ULONG_PTR dwOldCodeDelta; 193 | ULONG_PTR dwRelativeAddrDstDelta; 194 | 195 | DWORD dwOldProtection; 196 | DWORD overlap = 0; 197 | BYTE* pSrc = Src; 198 | Hook* HookStructure; 199 | 200 | ZydisDecodedInstruction inst; 201 | 202 | // 203 | // Disassemble to pick the instructions length 204 | // 205 | while (ZYAN_SUCCESS(ZydisDecoderDecodeBuffer(&ZDecoder, pSrc, X64_TRAMPOLINE_SIZE, &inst)) && overlap < X86_TRAMPOLINE_SIZE) 206 | { 207 | overlap += inst.length; 208 | pSrc += inst.length; 209 | } 210 | 211 | // 212 | // Change protections to for writing 213 | // 214 | if (!VirtualProtect(Src, X86_TRAMPOLINE_SIZE, PAGE_EXECUTE_READWRITE, &dwOldProtection)) 215 | { 216 | std::printf("Error on replacing protection!\n"); 217 | return nullptr; 218 | } 219 | 220 | // 221 | // Allocate a memory to store the code overwritten and the jump back 222 | // 223 | DWORD allocSize = overlap + NOP_SLIDE + X86_TRAMPOLINE_SIZE; 224 | BYTE* pOldCode = reinterpret_cast(VirtualAlloc(NULL, allocSize, MEM_RESERVE | MEM_COMMIT, PAGE_EXECUTE_READWRITE)); 225 | if (pOldCode == nullptr) 226 | { 227 | VirtualProtect(Src, X86_TRAMPOLINE_SIZE, dwOldProtection, &dwOldProtection); 228 | return nullptr; 229 | } 230 | 231 | // 232 | // Copy the old code before overwrite 233 | // 234 | memcpy_s(pOldCode, overlap, Src, TRAMPOLINE_SIZE); 235 | // 236 | // Set old code opcodes to NOP 237 | // 238 | memset(Src, NOP, overlap); 239 | // 240 | // Build the NOP slide 241 | // 242 | memset(pOldCode + overlap, NOP, NOP_SLIDE); 243 | // 244 | // Build code: jmp OldCodeDelta 245 | // 246 | dwOldCodeDelta = Src - pOldCode - TRAMPOLINE_SIZE - NOP_SLIDE; 247 | // 248 | // Write relative jump 249 | // 250 | *(BYTE*)(pOldCode + overlap + NOP_SLIDE) = JUMP; 251 | // 252 | // Write destination, relative address to Dst 253 | // 254 | *(DWORD_PTR*)(pOldCode + overlap + NOP_SLIDE + 1) = dwOldCodeDelta; 255 | // 256 | // Calculate relative address 257 | // 258 | dwRelativeAddrDstDelta = Dst - Src - X86_TRAMPOLINE_SIZE; 259 | // 260 | // Write jump instruction 261 | // 262 | *Src = JUMP; 263 | // 264 | // Write destination 265 | // 266 | *(DWORD_PTR*)(Src + 1) = dwRelativeAddrDstDelta; 267 | // 268 | // Recover old protections 269 | // 270 | if (!IgnoreProt && !VirtualProtect(Src, X86_TRAMPOLINE_SIZE, dwOldProtection, &dwOldProtection)) 271 | { 272 | std::printf("Error on replacing protection!\n"); 273 | VirtualFree(pOldCode, NULL, MEM_RELEASE); 274 | return nullptr; 275 | } 276 | 277 | HookStructure = new Hook; 278 | HookStructure->Gateway32Delta = dwRelativeAddrDstDelta; 279 | HookStructure->Trampoline32Delta = dwOldCodeDelta; 280 | HookStructure->HookAddr = Dst; 281 | HookStructure->OriginalAddr = Src; 282 | HookStructure->GatewayAddr = pOldCode; 283 | HookStructure->NumInstLeftToExec = overlap; 284 | 285 | 286 | return HookStructure; 287 | } 288 | -------------------------------------------------------------------------------- /Shinigami/Ichigo/ProcessUnhollow.cpp: -------------------------------------------------------------------------------- 1 | #include "pch.h" 2 | #include "ProcessUnhollow.h" 3 | 4 | // 5 | // Monitore Allocation and saves the target PID 6 | // 7 | NTSTATUS WINAPI Unhollow::hkNtAllocateVirtualMemory(HANDLE ProcessHandle, PVOID* BaseAddress, ULONG_PTR ZeroBits, PSIZE_T RegionSize, ULONG AllocationType, ULONG Protect) 8 | { 9 | NTSTATUS status = ProcessInformation.Win32Pointers.NtAllocateVirtualMemory(ProcessHandle, BaseAddress, ZeroBits, RegionSize, AllocationType, Protect); 10 | DWORD ProcessPID = GetProcessId(ProcessHandle); 11 | if (ProcessPID == Unhollow::ProcessInformation.pi.dwProcessId && NT_SUCCESS(status)) 12 | { 13 | if (BaseAddress == nullptr) 14 | { 15 | PipeLogger::LogInfo(L"NtAllocateVirtualMemory -- Error: returned allocation address is null Last error code: %d!", GetLastError()); 16 | return status; 17 | } 18 | // 19 | // Search if we already have this entry 20 | // 21 | auto& Watcher = Unhollow::ProcessInformation.Watcher; 22 | auto it = std::find_if(Watcher.begin(), Watcher.end(), [BaseAddress](Memory* mem) { return mem->Addr == (uint8_t*) BaseAddress; }); 23 | if (it == Watcher.end()) 24 | { 25 | PipeLogger::LogInfo(L"NtAllocateVirtualMemory -- Monitoring memory at 0x%llx --", BaseAddress); 26 | // 27 | // Create a memoryu entry that will be used later when hunting the PE in memory 28 | // 29 | Memory* mem = new Memory; 30 | mem->Addr = reinterpret_cast(*BaseAddress); 31 | mem->Size = (DWORD)*RegionSize; 32 | mem->safe = false; 33 | mem->ProcessID = GetProcessId(ProcessHandle); 34 | 35 | Watcher.push_back(mem); 36 | } 37 | } 38 | 39 | return status; 40 | } 41 | 42 | NTSTATUS WINAPI Unhollow::hkNtWriteVirtualMemory(HANDLE ProcessHandle, PVOID BaseAddress, PVOID Buffer, ULONG NumberOfBytesToWrite, PULONG NumberOfBytesWritten) 43 | { 44 | DWORD MonitoredPID = Unhollow::ProcessInformation.pi.dwProcessId;//Unhollow::ProcessInformation.pi.dwProcessId; 45 | 46 | if (IchigoOptions->Unhollow.StopAtWrite && GetProcessId(ProcessHandle) == MonitoredPID && 47 | NumberOfBytesToWrite >= sizeof(PIMAGE_DOS_HEADER) + sizeof(PIMAGE_NT_HEADERS)) 48 | { 49 | PIMAGE_DOS_HEADER pDOSHdr = (PIMAGE_DOS_HEADER)Buffer; 50 | 51 | if (pDOSHdr->e_magic == IMAGE_DOS_SIGNATURE) 52 | { 53 | PipeLogger::LogInfo(L"NtWriteVirtualMemory -- Detected an attempt to write a PE file in another process!"); 54 | Memory* hollow = PEDumper::DumpPE((ULONG_PTR*)Buffer); 55 | if (hollow) 56 | { 57 | PipeLogger::LogInfo(L"Extracted implant of %d bytes before it been written, saving!", hollow->Size); 58 | std::wstring FileName = Utils::BuildFilenameFromProcessName(L"_dumped_before_write.bin"); 59 | std::wstring SaveName = Utils::PathJoin(IchigoOptions->WorkDirectory, FileName); 60 | 61 | if (Utils::SaveToFile(SaveName.c_str(), hollow, FALSE)) 62 | { 63 | PipeLogger::Log(L"NtWriteVirtualMemory: -- Saved as %s! --", SaveName.c_str()); 64 | } 65 | else { 66 | PipeLogger::Log(L"NtWriteVirtualMemory: -- Error saving file: %d --", GetLastError()); 67 | } 68 | 69 | delete hollow; 70 | TerminateProcess(Unhollow::ProcessInformation.pi.hProcess, 0); 71 | ExitProcess(1); 72 | } 73 | } 74 | 75 | } 76 | 77 | NTSTATUS success = Unhollow::ProcessInformation.Win32Pointers.NtWriteVirtualMemory(ProcessHandle, BaseAddress, Buffer, NumberOfBytesToWrite, NumberOfBytesWritten); 78 | 79 | if (!NT_SUCCESS(success)) 80 | { 81 | PipeLogger::LogInfo(L"NtWriteVirtualMemory -- Error on writing process memory: %d --", GetLastError()); 82 | } 83 | 84 | return success; 85 | } 86 | 87 | 88 | // 89 | // Monitor every process creation until find a suspended 90 | // 91 | NTSTATUS WINAPI Unhollow::hkNtCreateUserProcess( 92 | PHANDLE ProcessHandle, 93 | PHANDLE ThreadHandle, 94 | ACCESS_MASK ProcessDesiredAccess, 95 | ACCESS_MASK ThreadDesiredAccess, 96 | POBJECT_ATTRIBUTES ProcessObjectAttributes, 97 | POBJECT_ATTRIBUTES ThreadObjectAttributes, 98 | ULONG ProcessFlags, 99 | ULONG ThreadFlags, 100 | PRTL_USER_PROCESS_PARAMETERS ProcessParameters, 101 | PPS_CREATE_INFO CreateInfo, 102 | PPS_ATTRIBUTE_LIST AttributeList 103 | ) { 104 | PipeLogger::LogInfo(L"NtCreateUserProcess -- Called for: %s --", ProcessParameters->ImagePathName.Buffer); 105 | // Call the original function and store its return value 106 | NTSTATUS status = Unhollow::ProcessInformation.Win32Pointers.NtCreateUserProcess( 107 | ProcessHandle, 108 | ThreadHandle, 109 | ProcessDesiredAccess, 110 | ThreadDesiredAccess, 111 | ProcessObjectAttributes, 112 | ThreadObjectAttributes, 113 | ProcessFlags, 114 | ThreadFlags, 115 | ProcessParameters, 116 | CreateInfo, 117 | AttributeList 118 | ); 119 | 120 | // Check if the process was successfully created and is suspended 121 | if (NT_SUCCESS(status) && (ProcessFlags & CREATE_SUSPENDED)) { 122 | // Copy the process information to the global ProcessInformation object 123 | Unhollow::ProcessInformation.DumptAtResume = TRUE; 124 | Unhollow::ProcessInformation.pi.dwProcessId = GetProcessId(*ProcessHandle); 125 | Unhollow::ProcessInformation.pi.dwThreadId = GetThreadId(*ThreadHandle); 126 | Unhollow::ProcessInformation.pi.hProcess = *ProcessHandle; 127 | 128 | // Log information about the newly created process 129 | PipeLogger::LogInfo(L"NtCreateUserProcess: -- Monitoring suspended process %d for memory writes... --", Unhollow::ProcessInformation.pi.dwProcessId); 130 | } 131 | 132 | else if (!NT_SUCCESS(status)) 133 | { 134 | PipeLogger::LogInfo(L"NtCreateUserProcess: -- Error creating %s -> %d --", ProcessParameters->ImagePathName.Buffer, GetLastError()); 135 | } 136 | 137 | // Return the status code from the original function 138 | return status; 139 | } 140 | 141 | NTSTATUS WINAPI Unhollow::hkNtResumeThread(HANDLE ThreadHandle, PULONG SuspendCount) 142 | { 143 | 144 | // TODO: Refactore this 145 | DWORD ThreadId = GetThreadId(ThreadHandle); 146 | 147 | if (Unhollow::ProcessInformation.DumptAtResume && Unhollow::ProcessInformation.pi.dwThreadId == ThreadId) { 148 | PipeLogger::LogInfo(L"NtResumeThread -- Called resume in the injected target, starting dump! --"); 149 | Memory* Hollow = Unhollow::HuntPE(); 150 | if (Hollow) 151 | { 152 | PipeLogger::LogInfo(L"NtResumeThread -- Dumped hollow of %d bytes --", Hollow->Size); 153 | std::wstring FileName = Utils::BuildFilenameFromProcessName(L"_dumped.bin"); 154 | std::wstring SaveName = Utils::PathJoin(IchigoOptions->WorkDirectory, FileName); 155 | 156 | if (Utils::SaveToFile(SaveName.c_str(), Hollow, FALSE)) 157 | PipeLogger::Log(L"NtResumeThread -- Saved PE as %s --", SaveName.c_str()); 158 | else 159 | PipeLogger::Log(L"NtResumeThread -- Unable to save PE file! --"); 160 | 161 | delete Hollow; 162 | } 163 | else 164 | { 165 | PipeLogger::Log(L"NtResumeThread -- Unable to dump, error code: %d. Exiting for safety --", GetLastError()); 166 | } 167 | // 168 | // Kill hollowed process 169 | // 170 | TerminateProcess(Unhollow::ProcessInformation.pi.hProcess, 0); 171 | ExitProcess(0); 172 | } 173 | 174 | return Unhollow::ProcessInformation.Win32Pointers.NtResumeThread(ThreadHandle, SuspendCount); 175 | } 176 | 177 | 178 | Memory* Unhollow::HuntPE() 179 | { 180 | Memory* PE = nullptr; 181 | // Walk the watch list and Hunt for the PE headers 182 | // TODO: Handle erased PE headers 183 | 184 | for (auto& MemEntry : Unhollow::ProcessInformation.Watcher) 185 | { 186 | if (MemEntry->ProcessID == Unhollow::ProcessInformation.pi.dwProcessId) 187 | { 188 | PE = PEDumper::FindRemotePE(Unhollow::ProcessInformation.pi.hProcess, MemEntry); 189 | 190 | if (PE != nullptr) 191 | break; 192 | } 193 | } 194 | 195 | return PE; 196 | } 197 | 198 | 199 | // 200 | // Hook every NT function related to the Process Hollowing technique 201 | // 202 | BOOL Unhollow::InitUnhollowHooks(HookManager& hkManager, Ichigo::Arguments& Options) 203 | { 204 | HMODULE NTDLL = GetModuleHandleA("NTDLL.DLL"); 205 | if (NTDLL == NULL) 206 | return FALSE; 207 | 208 | Unhollow::IchigoOptions = &Options; 209 | Unhollow::ProcessInformation.NTDLL = NTDLL; 210 | 211 | BYTE* NtResumeThreadPointer = reinterpret_cast(GetProcAddress(NTDLL, "NtResumeThread")); 212 | BYTE* NtAllocateVirtualMemoryPointer = reinterpret_cast(GetProcAddress(NTDLL, "NtAllocateVirtualMemory")); 213 | BYTE* NtWriteVirtualMemoryPointer = reinterpret_cast(GetProcAddress(NTDLL, "NtWriteVirtualMemory")); 214 | BYTE* NtCreateUserProcessPointer = reinterpret_cast(GetProcAddress(NTDLL, "NtCreateUserProcess")); 215 | 216 | Unhollow::ProcessInformation.Win32Pointers.NtAllocateVirtualMemory = (NtAllocateVirtualMemory*)hkManager.AddHook(NtAllocateVirtualMemoryPointer, (BYTE*)Unhollow::hkNtAllocateVirtualMemory, FALSE); 217 | Unhollow::ProcessInformation.Win32Pointers.NtWriteVirtualMemory = (NtWriteVirtualMemory*)hkManager.AddHook(NtWriteVirtualMemoryPointer, (BYTE*)Unhollow::hkNtWriteVirtualMemory, FALSE); 218 | Unhollow::ProcessInformation.Win32Pointers.NtCreateUserProcess = (NtCreateUserProcess*)hkManager.AddHook(NtCreateUserProcessPointer, (BYTE*)Unhollow::hkNtCreateUserProcess, FALSE); 219 | Unhollow::ProcessInformation.Win32Pointers.NtResumeThread = (NtResumeThread*)hkManager.AddHook(NtResumeThreadPointer, (BYTE*)Unhollow::hkNtResumeThread, FALSE); 220 | 221 | PipeLogger::LogInfo(L"Unhollow: -- Hooked Process Unhollow functions --"); 222 | 223 | return TRUE; 224 | } 225 | 226 | 227 | VOID Unhollow::Shutdown() 228 | { 229 | for (auto& addr : Unhollow::ProcessInformation.Watcher) 230 | { 231 | delete addr; 232 | } 233 | } 234 | 235 | -------------------------------------------------------------------------------- /Shinigami/Ichigo/PEDumper.cpp: -------------------------------------------------------------------------------- 1 | #include "pch.h" 2 | #include "PEDumper.h" 3 | #include "Logger.h" 4 | 5 | Memory* PEDumper::FindRemotePE(HANDLE hProcess, const Memory* mem) 6 | { 7 | // Check PE headers 8 | 9 | IMAGE_DOS_HEADER dosHdr; 10 | SIZE_T dwBytesRead; 11 | 12 | if (!ReadProcessMemory(hProcess, mem->Addr, &dosHdr, sizeof(dosHdr), &dwBytesRead)) return nullptr; 13 | 14 | if (dosHdr.e_magic != IMAGE_DOS_SIGNATURE) 15 | { 16 | // ScanPEHeaders((ULONG_PTR) dosHdr); 17 | return nullptr; 18 | } 19 | 20 | 21 | LPVOID pPE = VirtualAlloc(NULL, mem->Size, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE); 22 | if (pPE == nullptr) return nullptr; 23 | 24 | 25 | if (!ReadProcessMemory(hProcess, mem->Addr, pPE, mem->Size, &dwBytesRead)) return nullptr; 26 | 27 | 28 | Memory* dumped = new Memory; 29 | 30 | dumped->Addr = (uint8_t*) pPE; 31 | dumped->Size = mem->Size; 32 | dumped->prot = PAGE_READWRITE; 33 | dumped->safe = true; 34 | dumped->cAlloc = false; 35 | 36 | 37 | // 38 | // Make the Raw sections become the Virtual, since we are in memory 39 | // 40 | FixPESections(dumped); 41 | 42 | return dumped; 43 | } 44 | 45 | Memory* PEDumper::DumpPE(ULONG_PTR* Address) 46 | { 47 | 48 | PIMAGE_DOS_HEADER pDOSHdr = (PIMAGE_DOS_HEADER)Address; 49 | PIMAGE_NT_HEADERS pNTHdr = (PIMAGE_NT_HEADERS)((BYTE*)Address + pDOSHdr->e_lfanew); 50 | Memory* mem = nullptr; 51 | 52 | if (pDOSHdr->e_magic == IMAGE_DOS_SIGNATURE && pNTHdr->Signature == IMAGE_NT_SIGNATURE) 53 | { 54 | mem = new Memory; 55 | mem->Addr = (uint8_t*) Address; 56 | mem->Size = GetPESize(pNTHdr); 57 | mem->safe = false; 58 | } 59 | 60 | return mem; 61 | } 62 | 63 | PIMAGE_DOS_HEADER PEDumper::FindPE(Memory* Mem) 64 | { 65 | PIMAGE_DOS_HEADER pDosHeader; 66 | PIMAGE_NT_HEADERS pNtHeader; 67 | MEMORY_BASIC_INFORMATION mbi; 68 | 69 | for (uint8_t* Curr = reinterpret_cast(Mem->Addr); (ULONG_PTR)Curr < Mem->End - sizeof(IMAGE_DOS_HEADER); Curr++) 70 | { 71 | pDosHeader = reinterpret_cast(Curr); 72 | 73 | if (pDosHeader->e_magic == IMAGE_DOS_SIGNATURE) 74 | { 75 | pNtHeader = reinterpret_cast((ULONG_PTR)pDosHeader + pDosHeader->e_lfanew); 76 | 77 | if ((ULONG_PTR)pNtHeader <= Mem->End) 78 | { 79 | if (!VirtualQuery((LPCVOID)pNtHeader, &mbi, 0x1000)) 80 | continue; 81 | 82 | if (pNtHeader->Signature == IMAGE_NT_SIGNATURE) 83 | return pDosHeader; 84 | } 85 | } 86 | } 87 | // Search for detached headers 88 | return PEDumper::HeuristicSearch(Mem); 89 | } 90 | 91 | PIMAGE_DOS_HEADER PEDumper::HeuristicSearch(Memory* Mem) 92 | { 93 | PipeLogger::LogInfo(L"Starting heuristic scan for detached headers at 0x%p...", Mem->Addr); 94 | // Search for NT headers 95 | // If found, validated sections offset and if it has code 96 | PIMAGE_NT_HEADERS pNtHeader; 97 | MEMORY_BASIC_INFORMATION mbi; 98 | 99 | for (uint8_t* Curr = reinterpret_cast(Mem->Addr); (ULONG_PTR)Curr < Mem->End - sizeof(IMAGE_NT_HEADERS); Curr++) 100 | { 101 | pNtHeader = reinterpret_cast(Curr); 102 | if (IsValidNT(pNtHeader)) 103 | { 104 | // So far so good 105 | PipeLogger::LogInfo(L"Found possible NT header at 0x%p", Curr); 106 | 107 | if (!VirtualQuery((LPCVOID)&pNtHeader->OptionalHeader, &mbi, sizeof(IMAGE_OPTIONAL_HEADER))) 108 | continue; 109 | 110 | if ((ULONG_PTR) Mem->Addr + pNtHeader->OptionalHeader.AddressOfEntryPoint == Mem->IP) 111 | { 112 | // We are at this executable entrypoint, rebuild the DOS header 113 | if (Mem->Addr - Curr <= sizeof(IMAGE_DOS_HEADER)) 114 | { 115 | // We dont have space 116 | // TODO: Resize Mem struct 117 | } 118 | 119 | PIMAGE_DOS_HEADER DosHdr = RebuildDOSHeader(Mem, (ULONG_PTR) Curr); 120 | if (DosHdr != nullptr) 121 | { 122 | PipeLogger::LogInfo(L"DOS header rebuilded!"); 123 | return DosHdr; 124 | } 125 | } 126 | else 127 | { 128 | // Parse each section in this possible header, verify if is at a valided the section struct data (permissions, offset...) 129 | // If looks valid verify if the current EIP is between some of them 130 | // If it is, log that, else log that it found valid sections mapped but the code might be hidden somehow 131 | // Save the current NT address and proceed to the brute-force part of this code, if the brute-force fail 132 | // use this saved NT address and tell the user 133 | IMAGE_SECTION_HEADER* sectionHeader = IMAGE_FIRST_SECTION(pNtHeader); 134 | bool Invalid = true; 135 | bool IPInBetween = false; 136 | ULONG_PTR VirtualMemAddr; 137 | for (int i = 0; i < pNtHeader->FileHeader.NumberOfSections; i++, sectionHeader++) { 138 | // Invalid memory area 139 | if (Utils::IsReadWritable((ULONG_PTR*)sectionHeader) == INVALID_MEMORY_AREA) { 140 | Invalid = true; 141 | break; 142 | } 143 | 144 | VirtualMemAddr = sectionHeader->VirtualAddress + (ULONG_PTR)Mem->Addr; 145 | // Check if there is any overflow here 146 | if (sectionHeader->PointerToRawData + sectionHeader->SizeOfRawData + (ULONG_PTR) Mem->Addr >= Mem->End || 147 | sectionHeader->VirtualAddress + sectionHeader->Misc.VirtualSize + (ULONG_PTR) Mem->Addr >= Mem->End) 148 | { 149 | Invalid = true; 150 | break; 151 | } 152 | 153 | // Check if the Instruction pointer is between this image 154 | if (Mem->IP >= VirtualMemAddr && Mem->IP <= VirtualMemAddr + sectionHeader->Misc.VirtualSize) 155 | IPInBetween = true; 156 | 157 | } 158 | 159 | if (Invalid) 160 | continue; 161 | 162 | if (!Invalid && IPInBetween) 163 | { 164 | PipeLogger::LogInfo(L"Possible NT found at 0x%p! Trying to rebuild...", pNtHeader); 165 | PIMAGE_DOS_HEADER DosHdr = RebuildDOSHeader(Mem, (ULONG_PTR)Curr); 166 | if (DosHdr != nullptr) 167 | { 168 | PipeLogger::LogInfo(L"DOS header rebuilded!"); 169 | return DosHdr; 170 | } 171 | } 172 | } 173 | } 174 | } 175 | 176 | 177 | // We failed to search detached DOS headers 178 | // Search for common sections names such: .text, .data, .rdata 179 | // If found, walk back and try to rebuild the DOS headers and NT headers 180 | 181 | 182 | 183 | // We failed, return nullptr 184 | 185 | return nullptr; 186 | } 187 | 188 | 189 | // Verify NT headers fields to validate if is valid 190 | BOOL PEDumper::IsValidNT(PIMAGE_NT_HEADERS pNtHeader) 191 | { 192 | return pNtHeader->Signature == IMAGE_NT_SIGNATURE && 193 | ( 194 | pNtHeader->OptionalHeader.Subsystem == IMAGE_SUBSYSTEM_UNKNOWN || 195 | pNtHeader->OptionalHeader.Subsystem == IMAGE_SUBSYSTEM_NATIVE || 196 | pNtHeader->OptionalHeader.Subsystem == IMAGE_SUBSYSTEM_WINDOWS_GUI || 197 | pNtHeader->OptionalHeader.Subsystem == IMAGE_SUBSYSTEM_WINDOWS_CUI || 198 | pNtHeader->OptionalHeader.Subsystem == IMAGE_SUBSYSTEM_OS2_CUI || 199 | pNtHeader->OptionalHeader.Subsystem == IMAGE_SUBSYSTEM_POSIX_CUI || 200 | pNtHeader->OptionalHeader.Subsystem == IMAGE_SUBSYSTEM_WINDOWS_CE_GUI || 201 | pNtHeader->OptionalHeader.Subsystem == IMAGE_SUBSYSTEM_UNKNOWN 202 | ) 203 | && 204 | ( 205 | pNtHeader->OptionalHeader.Magic == IMAGE_NT_OPTIONAL_HDR32_MAGIC || 206 | pNtHeader->OptionalHeader.Magic == IMAGE_NT_OPTIONAL_HDR64_MAGIC 207 | ); 208 | } 209 | 210 | 211 | PIMAGE_DOS_HEADER PEDumper::RebuildDOSHeader(Memory* Mem, ULONG_PTR NtHeaderOffset) 212 | { 213 | // This function rely in the fact that we already have space allocated, if there is no space in the beginning of this file 214 | // the memory must be resized calling the mem.IncreaseSize(sizeof(IMAGE_DOS_HEADER), SHIFT_BEGIN) before call this function 215 | // Check if we have space 216 | DWORD OldProt; 217 | 218 | if (Mem->Size < sizeof(IMAGE_DOS_HEADER) || NtHeaderOffset >= Mem->End) return nullptr; 219 | 220 | PIMAGE_DOS_HEADER DosHdr = reinterpret_cast(Mem->Addr); 221 | // Rebuild basic fields only 222 | 223 | if (!VirtualProtect(Mem->Addr, sizeof(IMAGE_DOS_HEADER), PAGE_READWRITE, &OldProt)) 224 | return nullptr; 225 | 226 | DosHdr->e_magic = IMAGE_DOS_SIGNATURE; 227 | DosHdr->e_lfanew = NtHeaderOffset - (ULONG_PTR) DosHdr; 228 | 229 | VirtualProtect(Mem->Addr, sizeof(IMAGE_DOS_HEADER), OldProt, &OldProt); 230 | FixPESections(Mem); 231 | 232 | return DosHdr; 233 | } 234 | 235 | 236 | SIZE_T PEDumper::GetPESize(PIMAGE_NT_HEADERS pNTHeader) 237 | { 238 | // Get the first section header 239 | IMAGE_SECTION_HEADER* sectionHeader = IMAGE_FIRST_SECTION(pNTHeader); 240 | 241 | // Calculate the raw size of the image 242 | size_t rawSize = pNTHeader->OptionalHeader.SizeOfHeaders; 243 | for (int i = 0; i < pNTHeader->FileHeader.NumberOfSections; i++, sectionHeader++) { 244 | // Calculate the raw size of the section 245 | size_t rawSectionSize = sectionHeader->SizeOfRawData; 246 | if (rawSectionSize == 0) { 247 | // If the section has no raw data, use the size of one page instead 248 | rawSectionSize = pNTHeader->OptionalHeader.FileAlignment; 249 | } 250 | 251 | // Add the raw size of the section to the total 252 | rawSize += rawSectionSize; 253 | } 254 | 255 | return rawSize; 256 | } 257 | 258 | std::wstring AsciiToWide(const std::string& strAscii) 259 | { 260 | int nLen = static_cast(strAscii.length()); 261 | int nWideLen = MultiByteToWideChar(CP_ACP, 0, strAscii.c_str(), nLen, nullptr, 0); 262 | std::wstring strWide(nWideLen, L'\0'); 263 | MultiByteToWideChar(CP_ACP, 0, strAscii.c_str(), nLen, &strWide[0], nWideLen); 264 | return strWide; 265 | } 266 | 267 | #include "Logger.h" 268 | // 269 | // Fix in memory PE file to match the section information address in disk 270 | // 271 | VOID PEDumper::FixPESections(Memory* mem) 272 | { 273 | PIMAGE_DOS_HEADER pDosHdr = reinterpret_cast(mem->Addr); 274 | PIMAGE_NT_HEADERS pNtHeader = reinterpret_cast((BYTE*)mem->Addr + pDosHdr->e_lfanew); 275 | 276 | // Check if the PE headers are within a valid memory region 277 | if (Utils::IsReadWritable((ULONG_PTR*)pNtHeader) == INVALID_MEMORY_AREA) { 278 | return; 279 | } 280 | IMAGE_SECTION_HEADER* sectionHeaders = IMAGE_FIRST_SECTION(pNtHeader); 281 | 282 | // Modify the section headers 283 | for (WORD i = 0; i < pNtHeader->FileHeader.NumberOfSections; i++) 284 | { 285 | // Check if the section header is within a valid memory region 286 | if (Utils::IsReadWritable((ULONG_PTR*)sectionHeaders) == INVALID_MEMORY_AREA) { 287 | break; 288 | } 289 | 290 | // Change the section header's protection to read-write if necessary 291 | DWORD dwOldProtection; 292 | if (!VirtualProtect(sectionHeaders, sizeof(IMAGE_SECTION_HEADER), PAGE_READWRITE, &dwOldProtection)) { 293 | break; 294 | } 295 | 296 | // Modify the section header's fields 297 | sectionHeaders[i].PointerToRawData = sectionHeaders[i].VirtualAddress; 298 | sectionHeaders[i].SizeOfRawData = sectionHeaders[i].Misc.VirtualSize; 299 | 300 | // Restore the section header's original protection 301 | VirtualProtect(sectionHeaders, sizeof(IMAGE_SECTION_HEADER), dwOldProtection, &dwOldProtection); 302 | 303 | } 304 | } 305 | 306 | -------------------------------------------------------------------------------- /Shinigami/Ichigo/Unpacker.cpp: -------------------------------------------------------------------------------- 1 | #include "pch.h" 2 | #include "Unpacker.h" 3 | 4 | #ifdef _WIN64 5 | #define XIP Rip 6 | #else 7 | #define XIP Eip 8 | #endif 9 | 10 | NTSTATUS WINAPI GenericUnpacker::hkNtAllocateVirtualMemory(HANDLE ProcessHandle, PVOID* BaseAddress, ULONG_PTR ZeroBits, PSIZE_T RegionSize, ULONG AllocationType, ULONG Protect) 11 | { 12 | if (!GenericUnpacker::Ready) 13 | return GenericUnpacker::cUnpacker.Win32Pointers.NtAllocateVirtualMemory(ProcessHandle, BaseAddress, ZeroBits, RegionSize, AllocationType, Protect); 14 | 15 | SIZE_T AllocatedSize = *RegionSize; 16 | BOOL Track = FALSE; 17 | BOOL AddAfter = *BaseAddress == 0; 18 | 19 | if ((ProcessHandle == NULL || GetProcessId(ProcessHandle) == GenericUnpacker::IchigoOptions->PID) && (Protect == PAGE_EXECUTE_READWRITE || Protect == PAGE_EXECUTE_READ || Protect & PAGE_EXECUTE)) 20 | { 21 | Protect |= PAGE_GUARD; 22 | Track = TRUE; 23 | } 24 | 25 | NTSTATUS status = GenericUnpacker::cUnpacker.Win32Pointers.NtAllocateVirtualMemory(ProcessHandle, BaseAddress, ZeroBits, RegionSize, AllocationType, Protect); 26 | 27 | if (status == STATUS_SUCCESS) 28 | { 29 | GenericUnpacker::cUnpacker.Watcher.push_back({}); 30 | Memory& memory = GenericUnpacker::cUnpacker.Watcher.back(); 31 | memory.Addr = reinterpret_cast(*BaseAddress); 32 | memory.End = reinterpret_cast(memory.Addr + AllocatedSize); 33 | memory.Size = AllocatedSize; 34 | memory.prot = Protect; 35 | PipeLogger::LogInfo(L"Tracking newly allocated memory 0x%p with protections 0x%x", *BaseAddress, Protect); 36 | } 37 | 38 | return status; 39 | } 40 | 41 | // 42 | // Toggle on/off the PAGE_GUARD bit to avoid memory write errors, as we are more concerning about code execution than writing 43 | // This only exists in the case when for some reason the loader write in itself using OpenProcess + WriteProcessMemory 44 | // 45 | NTSTATUS WINAPI GenericUnpacker::hkNtWriteVirtualMemory(HANDLE ProcessHandle, PVOID BaseAddress, PVOID Buffer, ULONG NumberOfBytesToWrite, PULONG NumberOfBytesWritten) 46 | { 47 | if (!GenericUnpacker::Ready) 48 | return GenericUnpacker::cUnpacker.Win32Pointers.NtWriteVirtualMemory(ProcessHandle, BaseAddress, Buffer, NumberOfBytesToWrite, NumberOfBytesWritten); 49 | 50 | MEMORY_BASIC_INFORMATION mbi; 51 | VirtualQuery(BaseAddress, &mbi, NumberOfBytesToWrite); 52 | DWORD OldProtection = mbi.Protect; 53 | 54 | if ((ProcessHandle == NULL || GetProcessId(ProcessHandle) == GenericUnpacker::IchigoOptions->PID) && (mbi.Protect & PAGE_GUARD) && GenericUnpacker::cUnpacker.IsBeingMonitored((ULONG_PTR)BaseAddress)) 55 | { 56 | // Remove the PAGE_GUARD bit 57 | IgnoreMap[(ULONG_PTR)BaseAddress] = TRUE; 58 | VirtualProtect(BaseAddress, NumberOfBytesToWrite, mbi.Protect & ~PAGE_GUARD, &OldProtection); 59 | IgnoreMap[(ULONG_PTR)BaseAddress] = FALSE; 60 | } 61 | 62 | NTSTATUS status = GenericUnpacker::cUnpacker.Win32Pointers.NtWriteVirtualMemory(ProcessHandle, BaseAddress, Buffer, NumberOfBytesToWrite, NumberOfBytesWritten); 63 | 64 | VirtualProtect(BaseAddress, NumberOfBytesToWrite, OldProtection, &OldProtection); 65 | return status; 66 | } 67 | 68 | NTSTATUS WINAPI GenericUnpacker::hkNtProtectVirtualMemory(HANDLE ProcessHandle, PVOID* BaseAddress, PSIZE_T RegionSize, ULONG NewProtect, PULONG OldProtect) 69 | { 70 | // Verify that shit 71 | if (!GenericUnpacker::Ready) 72 | ignore: 73 | return GenericUnpacker::cUnpacker.Win32Pointers.NtProtectVirtualMemory(ProcessHandle, BaseAddress, RegionSize, NewProtect, OldProtect); 74 | 75 | if (IgnoreMap.size() > 0) 76 | { 77 | auto IgnoreIter = IgnoreMap.find((ULONG_PTR)*BaseAddress); 78 | if (IgnoreIter != IgnoreMap.end() && IgnoreIter->second) 79 | goto ignore; 80 | } 81 | 82 | // Detect if it will change to a executable memory 83 | BOOL Track = FALSE; 84 | if ((ProcessHandle == NULL || GetProcessId(ProcessHandle) == GenericUnpacker::IchigoOptions->PID) && (NewProtect == PAGE_EXECUTE_READWRITE || NewProtect == PAGE_EXECUTE_READ || (NewProtect & PAGE_EXECUTE))) 85 | { 86 | // Add the PAGE_GUARD bit as well 87 | NewProtect |= PAGE_GUARD; 88 | Track = TRUE; 89 | } 90 | 91 | NTSTATUS status = GenericUnpacker::cUnpacker.Win32Pointers.NtProtectVirtualMemory(ProcessHandle, BaseAddress, RegionSize, NewProtect, OldProtect); 92 | 93 | if (!NT_ERROR(status) && Track) 94 | { 95 | // Check if we already monitor this memory 96 | Memory* mem = GenericUnpacker::cUnpacker.IsBeingMonitored((ULONG_PTR)*BaseAddress); 97 | 98 | if (mem == nullptr) 99 | { 100 | // Monitor this address as well 101 | GenericUnpacker::cUnpacker.Watcher.push_back({}); 102 | Memory& memory = GenericUnpacker::cUnpacker.Watcher.back(); 103 | memory.Addr = reinterpret_cast(*BaseAddress); 104 | memory.End = reinterpret_cast(memory.Addr + *RegionSize); 105 | memory.Size = *RegionSize; 106 | memory.prot = NewProtect; 107 | PipeLogger::LogInfo(L"NtProtectVirtualMemory: Tracking memory at 0x%p with protections 0x%x", *BaseAddress, NewProtect); 108 | } 109 | } 110 | 111 | return status; 112 | } 113 | 114 | 115 | // Thanks a lot Hoang Bui -> https://medium.com/@fsx30/vectored-exception-handling-hooking-via-forced-exception-f888754549c6 116 | LONG WINAPI GenericUnpacker::VEHandler(EXCEPTION_POINTERS* pExceptionPointers) 117 | { 118 | if (!GenericUnpacker::Ready) 119 | return EXCEPTION_CONTINUE_SEARCH; 120 | 121 | DWORD dwOldProt; 122 | ULONG_PTR GuardedAddress; 123 | static ULONG_PTR LastValidExceptionAddress; 124 | MEMORY_BASIC_INFORMATION mbi; 125 | PEXCEPTION_RECORD ExceptionRecord = pExceptionPointers->ExceptionRecord; 126 | 127 | switch (ExceptionRecord->ExceptionCode) 128 | { 129 | case STATUS_GUARD_PAGE_VIOLATION: 130 | // 131 | // Verify if it's being monitored and executing 132 | // 133 | GuardedAddress = ExceptionRecord->ExceptionInformation[1]; 134 | if (GenericUnpacker::cUnpacker.IsBeingMonitored((ULONG_PTR)pExceptionPointers->ContextRecord->XIP)) 135 | { 136 | PipeLogger::LogInfo(L"STATUS_GUARD_PAGE_VIOLATION: Attempt to execute a monitored memory area at address 0x%p, starting dumping...", ExceptionRecord->ExceptionAddress); 137 | ULONG_PTR StartAddress = (ULONG_PTR)pExceptionPointers->ContextRecord->XIP; 138 | Memory* Mem = GenericUnpacker::cUnpacker.IsBeingMonitored(StartAddress); 139 | Mem->IP = (ULONG_PTR) pExceptionPointers->ContextRecord->XIP; 140 | 141 | if (GenericUnpacker::cUnpacker.Dump(Mem)) 142 | { 143 | PipeLogger::Log(L"Saved stage %d as %s ", GenericUnpacker::cUnpacker.StagesPath.size(), GenericUnpacker::cUnpacker.StagesPath.back().c_str()); 144 | GenericUnpacker::cUnpacker.RemoveMonitor(Mem); 145 | } 146 | 147 | } 148 | // An exception happened, but we are not monitoring this code and this code is operating inside our monitored memory 149 | // like an shellcode decryption process, we need to save this address to place the page_guard bit again 150 | else if (GenericUnpacker::cUnpacker.IsBeingMonitored(GuardedAddress)) 151 | { 152 | LastValidExceptionAddress = GuardedAddress; 153 | } 154 | 155 | pExceptionPointers->ContextRecord->EFlags |= TF; 156 | return EXCEPTION_CONTINUE_EXECUTION; 157 | 158 | case STATUS_SINGLE_STEP: 159 | // Add the PAGE_GUARD again 160 | if (GenericUnpacker::cUnpacker.IsBeingMonitored(LastValidExceptionAddress)) 161 | { 162 | VirtualQuery((LPCVOID) LastValidExceptionAddress, &mbi, PAGE_SIZE); 163 | mbi.Protect |= PAGE_GUARD; 164 | VirtualProtect((LPVOID) LastValidExceptionAddress, PAGE_SIZE, mbi.Protect, &dwOldProt); 165 | } 166 | return EXCEPTION_CONTINUE_EXECUTION; 167 | } 168 | 169 | return EXCEPTION_CONTINUE_SEARCH; 170 | } 171 | 172 | VOID GenericUnpacker::RemoveGuard(ULONG_PTR Address) 173 | { 174 | DWORD dwOldProt; 175 | MEMORY_BASIC_INFORMATION mbi; 176 | 177 | VirtualQuery((LPCVOID) Address, &mbi, 0x1000); 178 | 179 | if (mbi.Protect & PAGE_GUARD) 180 | VirtualProtect((LPVOID) Address, 0x1000, mbi.Protect & ~PAGE_GUARD , &dwOldProt); 181 | } 182 | 183 | 184 | // 185 | // Hook NtUserAllocateMemory and VirtualProtect to add the PAGE_GUARD bit to handle when the memory allocated is going 186 | // to be executed 187 | // 188 | BOOL GenericUnpacker::InitUnpackerHooks(HookManager& hkManager, Ichigo::Arguments& Arguments) 189 | { 190 | HMODULE NTDLL = GetModuleHandleA("NTDLL.DLL"); 191 | if (NTDLL == NULL) 192 | return FALSE; 193 | 194 | BYTE* NtAllocateVirtualMemoryPointer = reinterpret_cast(GetProcAddress(NTDLL, "NtAllocateVirtualMemory")); 195 | BYTE* NtWriteVirtualMemoryPointer = reinterpret_cast(GetProcAddress(NTDLL, "NtWriteVirtualMemory")); 196 | BYTE* NtProtectVirtualMemoryPointer = reinterpret_cast(GetProcAddress(NTDLL, "NtProtectVirtualMemory")); 197 | 198 | // 199 | // Here we might trigger the HookChain, so we need to be very carefully with the operations on this hook 200 | // Since it will be from an already hooked function 201 | // 202 | GenericUnpacker::cUnpacker.Win32Pointers.NtAllocateVirtualMemory = reinterpret_cast(hkManager.AddHook(NtAllocateVirtualMemoryPointer, (BYTE*)GenericUnpacker::hkNtAllocateVirtualMemory, FALSE)); 203 | GenericUnpacker::cUnpacker.Win32Pointers.NtWriteVirtualMemory = reinterpret_cast(hkManager.AddHook(NtWriteVirtualMemoryPointer, (BYTE*)GenericUnpacker::hkNtWriteVirtualMemory, FALSE)); 204 | GenericUnpacker::cUnpacker.Win32Pointers.NtProtectVirtualMemory = reinterpret_cast(hkManager.AddHook(NtProtectVirtualMemoryPointer, (BYTE*)GenericUnpacker::hkNtProtectVirtualMemory, TRUE)); 205 | 206 | // 207 | // Register the VEH handler 208 | // 209 | AddVectoredExceptionHandler(true, (PVECTORED_EXCEPTION_HANDLER)GenericUnpacker::VEHandler); 210 | 211 | PipeLogger::LogInfo(L"Unpacker: -- Hooked functions and added the VEH callback --"); 212 | GenericUnpacker::Ready = TRUE; 213 | GenericUnpacker::IchigoOptions = &Arguments; 214 | 215 | return TRUE; 216 | } 217 | 218 | // 219 | // Save the raw dump of the suspicious code 220 | // Also scan searching for MZ/PE headers 221 | // 222 | BOOL GenericUnpacker::Unpacker::Dump(Memory* Mem) 223 | { 224 | if (Mem == nullptr) return FALSE; 225 | std::wstring suffix = L"_shellcode." + std::to_wstring(StagesPath.size() + 1) + L".bin"; 226 | 227 | PIMAGE_DOS_HEADER dosHeader = reinterpret_cast(Mem->Addr); 228 | PIMAGE_NT_HEADERS NTHeaders; 229 | 230 | if (dosHeader->e_magic == IMAGE_DOS_SIGNATURE) 231 | { 232 | // Check NT 233 | NTHeaders = reinterpret_cast((ULONG_PTR) dosHeader + dosHeader->e_lfanew); 234 | if (NTHeaders->Signature == IMAGE_NT_SIGNATURE) 235 | { 236 | PEDumper::FixPESections(Mem); 237 | suffix = L"_stage_" + std::to_wstring(StagesPath.size() + 1); 238 | suffix += (NTHeaders->FileHeader.Characteristics & IMAGE_FILE_DLL) ? L".dll" : L".exe"; 239 | } 240 | } 241 | else 242 | { 243 | // Search a PE file within the region 244 | PIMAGE_DOS_HEADER pDosHeader = PEDumper::FindPE(Mem); 245 | if (pDosHeader != nullptr) 246 | { 247 | // Found it, save as part of this stage as well 248 | Memory PeMem; 249 | PIMAGE_NT_HEADERS pNtHeaders = reinterpret_cast((ULONG_PTR)pDosHeader + pDosHeader->e_lfanew); 250 | 251 | std::wstring EmbededPESuffix = L"_artefact_inside_stage_" + std::to_wstring(StagesPath.size() + 1) + L"_area"; 252 | EmbededPESuffix += (pNtHeaders->FileHeader.Characteristics & IMAGE_FILE_DLL) ? L".dll" : L".exe"; 253 | 254 | std::wstring FileName = Utils::BuildFilenameFromProcessName(EmbededPESuffix.c_str()); 255 | std::wstring SaveName = Utils::PathJoin(GenericUnpacker::IchigoOptions->WorkDirectory, FileName); 256 | 257 | 258 | PeMem.Addr = reinterpret_cast(pDosHeader); 259 | PeMem.Size = PEDumper::GetPESize(pNtHeaders); 260 | PeMem.End = reinterpret_cast(PeMem.Size + PeMem.Addr); 261 | 262 | if (Utils::SaveToFile(SaveName.c_str(), &PeMem, TRUE)) 263 | { 264 | PipeLogger::Log(L"Found a embedded PE file inside the newly executed memory are, saved as %s!", SaveName.c_str()); 265 | 266 | StagesPath.push_back(SaveName); 267 | return TRUE; 268 | } 269 | } 270 | } 271 | 272 | if (Ichigo::Options.OnlyPE) return FALSE; 273 | 274 | std::wstring FileName = Utils::BuildFilenameFromProcessName(suffix.c_str()); 275 | std::wstring SaveName = Utils::PathJoin(GenericUnpacker::IchigoOptions->WorkDirectory, FileName); 276 | 277 | if (Utils::SaveToFile(SaveName.c_str(), Mem, TRUE)) 278 | { 279 | StagesPath.push_back(SaveName); 280 | return TRUE; 281 | } 282 | 283 | return FALSE; 284 | } 285 | 286 | 287 | // 288 | // Verify if the exception happened in one of our monitered addresses 289 | // 290 | Memory* GenericUnpacker::Unpacker::IsBeingMonitored(ULONG_PTR Address) 291 | { 292 | for (auto& Mem: Watcher) 293 | { 294 | if (Address >= (ULONG_PTR)Mem.Addr && Address <= Mem.End) 295 | return &Mem; 296 | } 297 | 298 | return nullptr; 299 | } 300 | 301 | // 302 | // Remove memory area from the monitor list 303 | // 304 | VOID GenericUnpacker::Unpacker::RemoveMonitor(Memory* Mem) 305 | { 306 | Watcher.remove(*Mem); 307 | } 308 | 309 | // 310 | // Remove all memory entries page guards 311 | // 312 | VOID GenericUnpacker::Unpacker::CleanMonitor() 313 | { 314 | for (auto& Mem: Watcher) 315 | { 316 | GenericUnpacker::RemoveGuard((ULONG_PTR)Mem.Addr); 317 | } 318 | } 319 | 320 | // 321 | // Clean our data 322 | // 323 | VOID GenericUnpacker::Shutdown() 324 | { 325 | GenericUnpacker::cUnpacker.CleanMonitor(); 326 | } -------------------------------------------------------------------------------- /Shinigami/Shinigami/argparse.h: -------------------------------------------------------------------------------- 1 | /* 2 | __ _ _ __ __ _ _ __ __ _ _ __ ___ ___ 3 | / _` | '__/ _` | '_ \ / _` | '__/ __|/ _ \ Argument Parser for Modern C++ 4 | | (_| | | | (_| | |_) | (_| | | \__ \ __/ http://github.com/p-ranav/argparse 5 | \__,_|_| \__, | .__/ \__,_|_| |___/\___| 6 | |___/|_| 7 | 8 | Licensed under the MIT License . 9 | SPDX-License-Identifier: MIT 10 | Copyright (c) 2019-2022 Pranav Srinivas Kumar 11 | and other contributors. 12 | 13 | Permission is hereby granted, free of charge, to any person obtaining a copy 14 | of this software and associated documentation files (the "Software"), to deal 15 | in the Software without restriction, including without limitation the rights 16 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 17 | copies of the Software, and to permit persons to whom the Software is 18 | furnished to do so, subject to the following conditions: 19 | 20 | The above copyright notice and this permission notice shall be included in all 21 | copies or substantial portions of the Software. 22 | 23 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 24 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 25 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 26 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 27 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 28 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 29 | SOFTWARE. 30 | */ 31 | #pragma once 32 | #include 33 | #include 34 | #include 35 | #include 36 | #include 37 | #include 38 | #include 39 | #include 40 | #include 41 | #include 42 | #include 43 | #include 44 | #include 45 | #include 46 | #include 47 | #include 48 | #include 49 | #include 50 | #include 51 | #include 52 | #include 53 | #include 54 | #include 55 | #include 56 | 57 | namespace argparse { 58 | 59 | namespace details { // namespace for helper methods 60 | 61 | template 62 | struct HasContainerTraits : std::false_type {}; 63 | 64 | template <> struct HasContainerTraits : std::false_type {}; 65 | 66 | template <> struct HasContainerTraits : std::false_type {}; 67 | 68 | template 69 | struct HasContainerTraits< 70 | T, std::void_t().begin()), 71 | decltype(std::declval().end()), 72 | decltype(std::declval().size())>> : std::true_type {}; 73 | 74 | template 75 | static constexpr bool IsContainer = HasContainerTraits::value; 76 | 77 | template 78 | struct HasStreamableTraits : std::false_type {}; 79 | 80 | template 81 | struct HasStreamableTraits< 82 | T, 83 | std::void_t() << std::declval())>> 84 | : std::true_type {}; 85 | 86 | template 87 | static constexpr bool IsStreamable = HasStreamableTraits::value; 88 | 89 | constexpr std::size_t repr_max_container_size = 5; 90 | 91 | template std::string repr(T const& val) { 92 | if constexpr (std::is_same_v) { 93 | return val ? "true" : "false"; 94 | } 95 | else if constexpr (std::is_convertible_v) { 96 | return '"' + std::string{ std::string_view{val} } + '"'; 97 | } 98 | else if constexpr (IsContainer) { 99 | std::stringstream out; 100 | out << "{"; 101 | const auto size = val.size(); 102 | if (size > 1) { 103 | out << repr(*val.begin()); 104 | std::for_each( 105 | std::next(val.begin()), 106 | std::next( 107 | val.begin(), 108 | static_cast( 109 | std::min(size, repr_max_container_size) - 1)), 110 | [&out](const auto& v) { out << " " << repr(v); }); 111 | if (size <= repr_max_container_size) { 112 | out << " "; 113 | } 114 | else { 115 | out << "..."; 116 | } 117 | } 118 | if (size > 0) { 119 | out << repr(*std::prev(val.end())); 120 | } 121 | out << "}"; 122 | return out.str(); 123 | } 124 | else if constexpr (IsStreamable) { 125 | std::stringstream out; 126 | out << val; 127 | return out.str(); 128 | } 129 | else { 130 | return ""; 131 | } 132 | } 133 | 134 | namespace { 135 | 136 | template constexpr bool standard_signed_integer = false; 137 | template <> constexpr bool standard_signed_integer = true; 138 | template <> constexpr bool standard_signed_integer = true; 139 | template <> constexpr bool standard_signed_integer = true; 140 | template <> constexpr bool standard_signed_integer = true; 141 | template <> constexpr bool standard_signed_integer = true; 142 | 143 | template constexpr bool standard_unsigned_integer = false; 144 | template <> constexpr bool standard_unsigned_integer = true; 145 | template <> constexpr bool standard_unsigned_integer = true; 146 | template <> constexpr bool standard_unsigned_integer = true; 147 | template <> constexpr bool standard_unsigned_integer = true; 148 | template <> 149 | constexpr bool standard_unsigned_integer = true; 150 | 151 | } // namespace 152 | 153 | constexpr int radix_8 = 8; 154 | constexpr int radix_10 = 10; 155 | constexpr int radix_16 = 16; 156 | 157 | template 158 | constexpr bool standard_integer = 159 | standard_signed_integer || standard_unsigned_integer; 160 | 161 | template 162 | constexpr decltype(auto) 163 | apply_plus_one_impl(F&& f, Tuple&& t, Extra&& x, 164 | std::index_sequence /*unused*/) { 165 | return std::invoke(std::forward(f), std::get(std::forward(t))..., 166 | std::forward(x)); 167 | } 168 | 169 | template 170 | constexpr decltype(auto) apply_plus_one(F&& f, Tuple&& t, Extra&& x) { 171 | return details::apply_plus_one_impl( 172 | std::forward(f), std::forward(t), std::forward(x), 173 | std::make_index_sequence< 174 | std::tuple_size_v>>{}); 175 | } 176 | 177 | constexpr auto pointer_range(std::string_view s) noexcept { 178 | return std::tuple(s.data(), s.data() + s.size()); 179 | } 180 | 181 | template 182 | constexpr bool starts_with(std::basic_string_view prefix, 183 | std::basic_string_view s) noexcept { 184 | return s.substr(0, prefix.size()) == prefix; 185 | } 186 | 187 | enum class chars_format { 188 | scientific = 0x1, 189 | fixed = 0x2, 190 | hex = 0x4, 191 | general = fixed | scientific 192 | }; 193 | 194 | struct ConsumeHexPrefixResult { 195 | bool is_hexadecimal; 196 | std::string_view rest; 197 | }; 198 | 199 | using namespace std::literals; 200 | 201 | constexpr auto consume_hex_prefix(std::string_view s) 202 | -> ConsumeHexPrefixResult { 203 | if (starts_with("0x"sv, s) || starts_with("0X"sv, s)) { 204 | s.remove_prefix(2); 205 | return { true, s }; 206 | } 207 | return { false, s }; 208 | } 209 | 210 | template 211 | inline auto do_from_chars(std::string_view s) -> T { 212 | T x; 213 | auto [first, last] = pointer_range(s); 214 | auto [ptr, ec] = std::from_chars(first, last, x, Param); 215 | if (ec == std::errc()) { 216 | if (ptr == last) { 217 | return x; 218 | } 219 | throw std::invalid_argument{ "pattern does not match to the end" }; 220 | } 221 | if (ec == std::errc::invalid_argument) { 222 | throw std::invalid_argument{ "pattern not found" }; 223 | } 224 | if (ec == std::errc::result_out_of_range) { 225 | throw std::range_error{ "not representable" }; 226 | } 227 | return x; // unreachable 228 | } 229 | 230 | template struct parse_number { 231 | auto operator()(std::string_view s) -> T { 232 | return do_from_chars(s); 233 | } 234 | }; 235 | 236 | template struct parse_number { 237 | auto operator()(std::string_view s) -> T { 238 | if (auto [ok, rest] = consume_hex_prefix(s); ok) { 239 | return do_from_chars(rest); 240 | } 241 | throw std::invalid_argument{ "pattern not found" }; 242 | } 243 | }; 244 | 245 | template struct parse_number { 246 | auto operator()(std::string_view s) -> T { 247 | auto [ok, rest] = consume_hex_prefix(s); 248 | if (ok) { 249 | return do_from_chars(rest); 250 | } 251 | if (starts_with("0"sv, s)) { 252 | return do_from_chars(rest); 253 | } 254 | return do_from_chars(rest); 255 | } 256 | }; 257 | 258 | namespace { 259 | 260 | template inline const auto generic_strtod = nullptr; 261 | template <> inline const auto generic_strtod = strtof; 262 | template <> inline const auto generic_strtod = strtod; 263 | template <> inline const auto generic_strtod = strtold; 264 | 265 | } // namespace 266 | 267 | template inline auto do_strtod(std::string const& s) -> T { 268 | if (isspace(static_cast(s[0])) || s[0] == '+') { 269 | throw std::invalid_argument{ "pattern not found" }; 270 | } 271 | 272 | auto [first, last] = pointer_range(s); 273 | char* ptr; 274 | 275 | errno = 0; 276 | auto x = generic_strtod(first, &ptr); 277 | if (errno == 0) { 278 | if (ptr == last) { 279 | return x; 280 | } 281 | throw std::invalid_argument{ "pattern does not match to the end" }; 282 | } 283 | if (errno == ERANGE) { 284 | throw std::range_error{ "not representable" }; 285 | } 286 | return x; // unreachable 287 | } 288 | 289 | template struct parse_number { 290 | auto operator()(std::string const& s) -> T { 291 | if (auto r = consume_hex_prefix(s); r.is_hexadecimal) { 292 | throw std::invalid_argument{ 293 | "chars_format::general does not parse hexfloat" }; 294 | } 295 | 296 | return do_strtod(s); 297 | } 298 | }; 299 | 300 | template struct parse_number { 301 | auto operator()(std::string const& s) -> T { 302 | if (auto r = consume_hex_prefix(s); !r.is_hexadecimal) { 303 | throw std::invalid_argument{ "chars_format::hex parses hexfloat" }; 304 | } 305 | 306 | return do_strtod(s); 307 | } 308 | }; 309 | 310 | template struct parse_number { 311 | auto operator()(std::string const& s) -> T { 312 | if (auto r = consume_hex_prefix(s); r.is_hexadecimal) { 313 | throw std::invalid_argument{ 314 | "chars_format::scientific does not parse hexfloat" }; 315 | } 316 | if (s.find_first_of("eE") == std::string::npos) { 317 | throw std::invalid_argument{ 318 | "chars_format::scientific requires exponent part" }; 319 | } 320 | 321 | return do_strtod(s); 322 | } 323 | }; 324 | 325 | template struct parse_number { 326 | auto operator()(std::string const& s) -> T { 327 | if (auto r = consume_hex_prefix(s); r.is_hexadecimal) { 328 | throw std::invalid_argument{ 329 | "chars_format::fixed does not parse hexfloat" }; 330 | } 331 | if (s.find_first_of("eE") != std::string::npos) { 332 | throw std::invalid_argument{ 333 | "chars_format::fixed does not parse exponent part" }; 334 | } 335 | 336 | return do_strtod(s); 337 | } 338 | }; 339 | 340 | template 341 | std::string join(StrIt first, StrIt last, const std::string& separator) { 342 | if (first == last) { 343 | return ""; 344 | } 345 | std::stringstream value; 346 | value << *first; 347 | ++first; 348 | while (first != last) { 349 | value << separator << *first; 350 | ++first; 351 | } 352 | return value.str(); 353 | } 354 | 355 | } // namespace details 356 | 357 | enum class nargs_pattern { optional, any, at_least_one }; 358 | 359 | enum class default_arguments : unsigned int { 360 | none = 0, 361 | help = 1, 362 | version = 2, 363 | all = help | version, 364 | }; 365 | 366 | inline default_arguments operator&(const default_arguments& a, 367 | const default_arguments& b) { 368 | return static_cast( 369 | static_cast::type>(a) & 370 | static_cast::type>(b)); 371 | } 372 | 373 | class ArgumentParser; 374 | 375 | class Argument { 376 | friend class ArgumentParser; 377 | friend auto operator<<(std::ostream& stream, const ArgumentParser& parser) 378 | ->std::ostream&; 379 | 380 | template 381 | explicit Argument(std::string_view prefix_chars, 382 | std::array&& a, 383 | std::index_sequence /*unused*/) 384 | : m_accepts_optional_like_value(false), 385 | m_is_optional((is_optional(a[I], prefix_chars) || ...)), 386 | m_is_required(false), m_is_repeatable(false), m_is_used(false), 387 | m_prefix_chars(prefix_chars) { 388 | ((void)m_names.emplace_back(a[I]), ...); 389 | std::sort( 390 | m_names.begin(), m_names.end(), [](const auto& lhs, const auto& rhs) { 391 | return lhs.size() == rhs.size() ? lhs < rhs : lhs.size() < rhs.size(); 392 | }); 393 | } 394 | 395 | public: 396 | template 397 | explicit Argument(std::string_view prefix_chars, 398 | std::array&& a) 399 | : Argument(prefix_chars, std::move(a), std::make_index_sequence{}) {} 400 | 401 | Argument& help(std::string help_text) { 402 | m_help = std::move(help_text); 403 | return *this; 404 | } 405 | 406 | Argument& metavar(std::string metavar) { 407 | m_metavar = std::move(metavar); 408 | return *this; 409 | } 410 | 411 | template Argument& default_value(T&& value) { 412 | m_default_value_repr = details::repr(value); 413 | m_default_value = std::forward(value); 414 | return *this; 415 | } 416 | 417 | Argument& default_value(const char* value) { 418 | return default_value(std::string(value)); 419 | } 420 | 421 | Argument& required() { 422 | m_is_required = true; 423 | return *this; 424 | } 425 | 426 | Argument& implicit_value(std::any value) { 427 | m_implicit_value = std::move(value); 428 | m_num_args_range = NArgsRange{ 0, 0 }; 429 | return *this; 430 | } 431 | 432 | template 433 | auto action(F&& callable, Args &&... bound_args) 434 | -> std::enable_if_t, 435 | Argument&> { 436 | using action_type = std::conditional_t< 437 | std::is_void_v>, 438 | void_action, valued_action>; 439 | if constexpr (sizeof...(Args) == 0) { 440 | m_action.emplace(std::forward(callable)); 441 | } 442 | else { 443 | m_action.emplace( 444 | [f = std::forward(callable), 445 | tup = std::make_tuple(std::forward(bound_args)...)]( 446 | std::string const& opt) mutable { 447 | return details::apply_plus_one(f, tup, opt); 448 | }); 449 | } 450 | return *this; 451 | } 452 | 453 | auto& append() { 454 | m_is_repeatable = true; 455 | return *this; 456 | } 457 | 458 | template 459 | auto scan() -> std::enable_if_t, Argument&> { 460 | static_assert(!(std::is_const_v || std::is_volatile_v), 461 | "T should not be cv-qualified"); 462 | auto is_one_of = [](char c, auto... x) constexpr { 463 | return ((c == x) || ...); 464 | }; 465 | 466 | if constexpr (is_one_of(Shape, 'd') && details::standard_integer) { 467 | action(details::parse_number()); 468 | } 469 | else if constexpr (is_one_of(Shape, 'i') && 470 | details::standard_integer) { 471 | action(details::parse_number()); 472 | } 473 | else if constexpr (is_one_of(Shape, 'u') && 474 | details::standard_unsigned_integer) { 475 | action(details::parse_number()); 476 | } 477 | else if constexpr (is_one_of(Shape, 'o') && 478 | details::standard_unsigned_integer) { 479 | action(details::parse_number()); 480 | } 481 | else if constexpr (is_one_of(Shape, 'x', 'X') && 482 | details::standard_unsigned_integer) { 483 | action(details::parse_number()); 484 | } 485 | else if constexpr (is_one_of(Shape, 'a', 'A') && 486 | std::is_floating_point_v) { 487 | action(details::parse_number()); 488 | } 489 | else if constexpr (is_one_of(Shape, 'e', 'E') && 490 | std::is_floating_point_v) { 491 | action(details::parse_number()); 492 | } 493 | else if constexpr (is_one_of(Shape, 'f', 'F') && 494 | std::is_floating_point_v) { 495 | action(details::parse_number()); 496 | } 497 | else if constexpr (is_one_of(Shape, 'g', 'G') && 498 | std::is_floating_point_v) { 499 | action(details::parse_number()); 500 | } 501 | else { 502 | static_assert(alignof(T) == 0, "No scan specification for T"); 503 | } 504 | 505 | return *this; 506 | } 507 | 508 | Argument& nargs(std::size_t num_args) { 509 | m_num_args_range = NArgsRange{ num_args, num_args }; 510 | return *this; 511 | } 512 | 513 | Argument& nargs(std::size_t num_args_min, std::size_t num_args_max) { 514 | m_num_args_range = NArgsRange{ num_args_min, num_args_max }; 515 | return *this; 516 | } 517 | 518 | Argument& nargs(nargs_pattern pattern) { 519 | switch (pattern) { 520 | case nargs_pattern::optional: 521 | m_num_args_range = NArgsRange{ 0, 1 }; 522 | break; 523 | case nargs_pattern::any: 524 | m_num_args_range = NArgsRange{ 0, (std::numeric_limits::max)() }; 525 | break; 526 | case nargs_pattern::at_least_one: 527 | m_num_args_range = NArgsRange{ 1, (std::numeric_limits::max)() }; 528 | break; 529 | } 530 | return *this; 531 | } 532 | 533 | Argument& remaining() { 534 | m_accepts_optional_like_value = true; 535 | return nargs(nargs_pattern::any); 536 | } 537 | 538 | template 539 | Iterator consume(Iterator start, Iterator end, 540 | std::string_view used_name = {}) { 541 | if (!m_is_repeatable && m_is_used) { 542 | throw std::runtime_error("Duplicate argument"); 543 | } 544 | m_is_used = true; 545 | m_used_name = used_name; 546 | 547 | const auto num_args_max = m_num_args_range.get_max(); 548 | const auto num_args_min = m_num_args_range.get_min(); 549 | std::size_t dist = 0; 550 | if (num_args_max == 0) { 551 | m_values.emplace_back(m_implicit_value); 552 | std::visit([](const auto& f) { f({}); }, m_action); 553 | return start; 554 | } 555 | if ((dist = static_cast(std::distance(start, end))) >= 556 | num_args_min) { 557 | if (num_args_max < dist) { 558 | end = std::next(start, static_cast( 559 | num_args_max)); 560 | } 561 | if (!m_accepts_optional_like_value) { 562 | end = std::find_if( 563 | start, end, 564 | std::bind(is_optional, std::placeholders::_1, m_prefix_chars)); 565 | dist = static_cast(std::distance(start, end)); 566 | if (dist < num_args_min) { 567 | throw std::runtime_error("Too few arguments"); 568 | } 569 | } 570 | 571 | struct ActionApply { 572 | void operator()(valued_action& f) { 573 | std::transform(first, last, std::back_inserter(self.m_values), f); 574 | } 575 | 576 | void operator()(void_action& f) { 577 | std::for_each(first, last, f); 578 | if (!self.m_default_value.has_value()) { 579 | if (!self.m_accepts_optional_like_value) { 580 | self.m_values.resize( 581 | static_cast(std::distance(first, last))); 582 | } 583 | } 584 | } 585 | 586 | Iterator first, last; 587 | Argument& self; 588 | }; 589 | std::visit(ActionApply{ start, end, *this }, m_action); 590 | return end; 591 | } 592 | if (m_default_value.has_value()) { 593 | return start; 594 | } 595 | throw std::runtime_error("Too few arguments for '" + 596 | std::string(m_used_name) + "'."); 597 | } 598 | 599 | /* 600 | * @throws std::runtime_error if argument values are not valid 601 | */ 602 | void validate() const { 603 | if (m_is_optional) { 604 | // TODO: check if an implicit value was programmed for this argument 605 | if (!m_is_used && !m_default_value.has_value() && m_is_required) { 606 | throw_required_arg_not_used_error(); 607 | } 608 | if (m_is_used && m_is_required && m_values.empty()) { 609 | throw_required_arg_no_value_provided_error(); 610 | } 611 | } 612 | else { 613 | if (!m_num_args_range.contains(m_values.size()) && 614 | !m_default_value.has_value()) { 615 | throw_nargs_range_validation_error(); 616 | } 617 | } 618 | } 619 | 620 | std::string get_inline_usage() const { 621 | std::stringstream usage; 622 | // Find the longest variant to show in the usage string 623 | std::string longest_name = m_names.front(); 624 | for (const auto& s : m_names) { 625 | if (s.size() > longest_name.size()) { 626 | longest_name = s; 627 | } 628 | } 629 | if (!m_is_required) { 630 | usage << "["; 631 | } 632 | usage << longest_name; 633 | const std::string metavar = !m_metavar.empty() ? m_metavar : "VAR"; 634 | if (m_num_args_range.get_max() > 0) { 635 | usage << " " << metavar; 636 | if (m_num_args_range.get_max() > 1) { 637 | usage << "..."; 638 | } 639 | } 640 | if (!m_is_required) { 641 | usage << "]"; 642 | } 643 | return usage.str(); 644 | } 645 | 646 | std::size_t get_arguments_length() const { 647 | 648 | std::size_t names_size = std::accumulate( 649 | std::begin(m_names), std::end(m_names), std::size_t(0), 650 | [](const auto& sum, const auto& s) { return sum + s.size(); }); 651 | 652 | if (is_positional(m_names.front(), m_prefix_chars)) { 653 | // A set metavar means this replaces the names 654 | if (!m_metavar.empty()) { 655 | // Indent and metavar 656 | return 2 + m_metavar.size(); 657 | } 658 | 659 | // Indent and space-separated 660 | return 2 + names_size + (m_names.size() - 1); 661 | } 662 | // Is an option - include both names _and_ metavar 663 | // size = text + (", " between names) 664 | std::size_t size = names_size + 2 * (m_names.size() - 1); 665 | if (!m_metavar.empty() && m_num_args_range == NArgsRange{ 1, 1 }) { 666 | size += m_metavar.size() + 1; 667 | } 668 | return size + 2; // indent 669 | } 670 | 671 | friend std::ostream& operator<<(std::ostream& stream, 672 | const Argument& argument) { 673 | std::stringstream name_stream; 674 | name_stream << " "; // indent 675 | if (argument.is_positional(argument.m_names.front(), 676 | argument.m_prefix_chars)) { 677 | if (!argument.m_metavar.empty()) { 678 | name_stream << argument.m_metavar; 679 | } 680 | else { 681 | name_stream << details::join(argument.m_names.begin(), 682 | argument.m_names.end(), " "); 683 | } 684 | } 685 | else { 686 | name_stream << details::join(argument.m_names.begin(), 687 | argument.m_names.end(), ", "); 688 | // If we have a metavar, and one narg - print the metavar 689 | if (!argument.m_metavar.empty() && 690 | argument.m_num_args_range == NArgsRange{ 1, 1 }) { 691 | name_stream << " " << argument.m_metavar; 692 | } 693 | } 694 | stream << name_stream.str() << "\t" << argument.m_help; 695 | 696 | // print nargs spec 697 | if (!argument.m_help.empty()) { 698 | stream << " "; 699 | } 700 | stream << argument.m_num_args_range; 701 | 702 | if (argument.m_default_value.has_value() && 703 | argument.m_num_args_range != NArgsRange{ 0, 0 }) { 704 | stream << "[default: " << argument.m_default_value_repr << "]"; 705 | } 706 | else if (argument.m_is_required) { 707 | stream << "[required]"; 708 | } 709 | stream << "\n"; 710 | return stream; 711 | } 712 | 713 | template bool operator!=(const T& rhs) const { 714 | return !(*this == rhs); 715 | } 716 | 717 | /* 718 | * Compare to an argument value of known type 719 | * @throws std::logic_error in case of incompatible types 720 | */ 721 | template bool operator==(const T& rhs) const { 722 | if constexpr (!details::IsContainer) { 723 | return get() == rhs; 724 | } 725 | else { 726 | using ValueType = typename T::value_type; 727 | auto lhs = get(); 728 | return std::equal(std::begin(lhs), std::end(lhs), std::begin(rhs), 729 | std::end(rhs), 730 | [](const auto& a, const auto& b) { 731 | return std::any_cast(a) == b; 732 | }); 733 | } 734 | } 735 | 736 | private: 737 | class NArgsRange { 738 | std::size_t m_min; 739 | std::size_t m_max; 740 | 741 | public: 742 | NArgsRange(std::size_t minimum, std::size_t maximum) 743 | : m_min(minimum), m_max(maximum) { 744 | if (minimum > maximum) { 745 | throw std::logic_error("Range of number of arguments is invalid"); 746 | } 747 | } 748 | 749 | bool contains(std::size_t value) const { 750 | return value >= m_min && value <= m_max; 751 | } 752 | 753 | bool is_exact() const { return m_min == m_max; } 754 | 755 | bool is_right_bounded() const { 756 | return m_max < (std::numeric_limits::max)(); 757 | } 758 | 759 | std::size_t get_min() const { return m_min; } 760 | 761 | std::size_t get_max() const { return m_max; } 762 | 763 | // Print help message 764 | friend auto operator<<(std::ostream& stream, const NArgsRange& range) 765 | -> std::ostream& { 766 | if (range.m_min == range.m_max) { 767 | if (range.m_min != 0 && range.m_min != 1) { 768 | stream << "[nargs: " << range.m_min << "] "; 769 | } 770 | } 771 | else { 772 | if (range.m_max == (std::numeric_limits::max)()) { 773 | stream << "[nargs: " << range.m_min << " or more] "; 774 | } 775 | else { 776 | stream << "[nargs=" << range.m_min << ".." << range.m_max << "] "; 777 | } 778 | } 779 | return stream; 780 | } 781 | 782 | bool operator==(const NArgsRange& rhs) const { 783 | return rhs.m_min == m_min && rhs.m_max == m_max; 784 | } 785 | 786 | bool operator!=(const NArgsRange& rhs) const { return !(*this == rhs); } 787 | }; 788 | 789 | void throw_nargs_range_validation_error() const { 790 | std::stringstream stream; 791 | if (!m_used_name.empty()) { 792 | stream << m_used_name << ": "; 793 | } 794 | else { 795 | stream << m_names.front() << ": "; 796 | } 797 | if (m_num_args_range.is_exact()) { 798 | stream << m_num_args_range.get_min(); 799 | } 800 | else if (m_num_args_range.is_right_bounded()) { 801 | stream << m_num_args_range.get_min() << " to " 802 | << m_num_args_range.get_max(); 803 | } 804 | else { 805 | stream << m_num_args_range.get_min() << " or more"; 806 | } 807 | stream << " argument(s) expected. " << m_values.size() << " provided."; 808 | throw std::runtime_error(stream.str()); 809 | } 810 | 811 | void throw_required_arg_not_used_error() const { 812 | std::stringstream stream; 813 | stream << m_names.front() << ": required."; 814 | throw std::runtime_error(stream.str()); 815 | } 816 | 817 | void throw_required_arg_no_value_provided_error() const { 818 | std::stringstream stream; 819 | stream << m_used_name << ": no value provided."; 820 | throw std::runtime_error(stream.str()); 821 | } 822 | 823 | static constexpr int eof = std::char_traits::eof(); 824 | 825 | static auto lookahead(std::string_view s) -> int { 826 | if (s.empty()) { 827 | return eof; 828 | } 829 | return static_cast(static_cast(s[0])); 830 | } 831 | 832 | /* 833 | * decimal-literal: 834 | * '0' 835 | * nonzero-digit digit-sequence_opt 836 | * integer-part fractional-part 837 | * fractional-part 838 | * integer-part '.' exponent-part_opt 839 | * integer-part exponent-part 840 | * 841 | * integer-part: 842 | * digit-sequence 843 | * 844 | * fractional-part: 845 | * '.' post-decimal-point 846 | * 847 | * post-decimal-point: 848 | * digit-sequence exponent-part_opt 849 | * 850 | * exponent-part: 851 | * 'e' post-e 852 | * 'E' post-e 853 | * 854 | * post-e: 855 | * sign_opt digit-sequence 856 | * 857 | * sign: one of 858 | * '+' '-' 859 | */ 860 | static bool is_decimal_literal(std::string_view s) { 861 | auto is_digit = [](auto c) constexpr { 862 | switch (c) { 863 | case '0': 864 | case '1': 865 | case '2': 866 | case '3': 867 | case '4': 868 | case '5': 869 | case '6': 870 | case '7': 871 | case '8': 872 | case '9': 873 | return true; 874 | default: 875 | return false; 876 | } 877 | }; 878 | 879 | // precondition: we have consumed or will consume at least one digit 880 | auto consume_digits = [=](std::string_view sd) { 881 | // NOLINTNEXTLINE(readability-qualified-auto) 882 | auto it = std::find_if_not(std::begin(sd), std::end(sd), is_digit); 883 | return sd.substr(static_cast(it - std::begin(sd))); 884 | }; 885 | 886 | switch (lookahead(s)) { 887 | case '0': { 888 | s.remove_prefix(1); 889 | if (s.empty()) { 890 | return true; 891 | } 892 | goto integer_part; 893 | } 894 | case '1': 895 | case '2': 896 | case '3': 897 | case '4': 898 | case '5': 899 | case '6': 900 | case '7': 901 | case '8': 902 | case '9': { 903 | s = consume_digits(s); 904 | if (s.empty()) { 905 | return true; 906 | } 907 | goto integer_part_consumed; 908 | } 909 | case '.': { 910 | s.remove_prefix(1); 911 | goto post_decimal_point; 912 | } 913 | default: 914 | return false; 915 | } 916 | 917 | integer_part: 918 | s = consume_digits(s); 919 | integer_part_consumed: 920 | switch (lookahead(s)) { 921 | case '.': { 922 | s.remove_prefix(1); 923 | if (is_digit(lookahead(s))) { 924 | goto post_decimal_point; 925 | } 926 | else { 927 | goto exponent_part_opt; 928 | } 929 | } 930 | case 'e': 931 | case 'E': { 932 | s.remove_prefix(1); 933 | goto post_e; 934 | } 935 | default: 936 | return false; 937 | } 938 | 939 | post_decimal_point: 940 | if (is_digit(lookahead(s))) { 941 | s = consume_digits(s); 942 | goto exponent_part_opt; 943 | } 944 | return false; 945 | 946 | exponent_part_opt: 947 | switch (lookahead(s)) { 948 | case eof: 949 | return true; 950 | case 'e': 951 | case 'E': { 952 | s.remove_prefix(1); 953 | goto post_e; 954 | } 955 | default: 956 | return false; 957 | } 958 | 959 | post_e: 960 | switch (lookahead(s)) { 961 | case '-': 962 | case '+': 963 | s.remove_prefix(1); 964 | } 965 | if (is_digit(lookahead(s))) { 966 | s = consume_digits(s); 967 | return s.empty(); 968 | } 969 | return false; 970 | } 971 | 972 | static bool is_optional(std::string_view name, 973 | std::string_view prefix_chars) { 974 | return !is_positional(name, prefix_chars); 975 | } 976 | 977 | /* 978 | * positional: 979 | * _empty_ 980 | * '-' 981 | * '-' decimal-literal 982 | * !'-' anything 983 | */ 984 | static bool is_positional(std::string_view name, 985 | std::string_view prefix_chars) { 986 | auto first = lookahead(name); 987 | 988 | if (first == eof) { 989 | return true; 990 | } 991 | else if (prefix_chars.find(static_cast(first)) != 992 | std::string_view::npos) { 993 | name.remove_prefix(1); 994 | if (name.empty()) { 995 | return true; 996 | } 997 | return is_decimal_literal(name); 998 | } 999 | return true; 1000 | } 1001 | 1002 | /* 1003 | * Get argument value given a type 1004 | * @throws std::logic_error in case of incompatible types 1005 | */ 1006 | template T get() const { 1007 | if (!m_values.empty()) { 1008 | if constexpr (details::IsContainer) { 1009 | return any_cast_container(m_values); 1010 | } 1011 | else { 1012 | return std::any_cast(m_values.front()); 1013 | } 1014 | } 1015 | if (m_default_value.has_value()) { 1016 | return std::any_cast(m_default_value); 1017 | } 1018 | if constexpr (details::IsContainer) { 1019 | if (!m_accepts_optional_like_value) { 1020 | return any_cast_container(m_values); 1021 | } 1022 | } 1023 | 1024 | throw std::logic_error("No value provided for '" + m_names.back() + "'."); 1025 | } 1026 | 1027 | /* 1028 | * Get argument value given a type. 1029 | * @pre The object has no default value. 1030 | * @returns The stored value if any, std::nullopt otherwise. 1031 | */ 1032 | template auto present() const -> std::optional { 1033 | if (m_default_value.has_value()) { 1034 | throw std::logic_error("Argument with default value always presents"); 1035 | } 1036 | if (m_values.empty()) { 1037 | return std::nullopt; 1038 | } 1039 | if constexpr (details::IsContainer) { 1040 | return any_cast_container(m_values); 1041 | } 1042 | return std::any_cast(m_values.front()); 1043 | } 1044 | 1045 | template 1046 | static auto any_cast_container(const std::vector& operand) -> T { 1047 | using ValueType = typename T::value_type; 1048 | 1049 | T result; 1050 | std::transform( 1051 | std::begin(operand), std::end(operand), std::back_inserter(result), 1052 | [](const auto& value) { return std::any_cast(value); }); 1053 | return result; 1054 | } 1055 | 1056 | std::vector m_names; 1057 | std::string_view m_used_name; 1058 | std::string m_help; 1059 | std::string m_metavar; 1060 | std::any m_default_value; 1061 | std::string m_default_value_repr; 1062 | std::any m_implicit_value; 1063 | using valued_action = std::function; 1064 | using void_action = std::function; 1065 | std::variant m_action{ 1066 | std::in_place_type, 1067 | [](const std::string& value) { return value; } }; 1068 | std::vector m_values; 1069 | NArgsRange m_num_args_range{ 1, 1 }; 1070 | // Bit field of bool values. Set default value in ctor. 1071 | bool m_accepts_optional_like_value : 1; 1072 | bool m_is_optional : 1; 1073 | bool m_is_required : 1; 1074 | bool m_is_repeatable : 1; 1075 | bool m_is_used : 1; 1076 | std::string_view m_prefix_chars; // ArgumentParser has the prefix_chars 1077 | }; 1078 | 1079 | class ArgumentParser { 1080 | public: 1081 | explicit ArgumentParser(std::string program_name = {}, 1082 | std::string version = "1.0", 1083 | default_arguments add_args = default_arguments::all, 1084 | bool exit_on_default_arguments = true) 1085 | : m_program_name(std::move(program_name)), m_version(std::move(version)), 1086 | m_exit_on_default_arguments(exit_on_default_arguments), 1087 | m_parser_path(m_program_name) { 1088 | if ((add_args & default_arguments::help) == default_arguments::help) { 1089 | add_argument("-h", "--help") 1090 | .action([&](const auto& /*unused*/) { 1091 | std::cout << help().str(); 1092 | if (m_exit_on_default_arguments) { 1093 | std::exit(0); 1094 | } 1095 | }) 1096 | .default_value(false) 1097 | .help("shows help message and exits") 1098 | .implicit_value(true) 1099 | .nargs(0); 1100 | } 1101 | if ((add_args & default_arguments::version) == default_arguments::version) { 1102 | add_argument("-v", "--version") 1103 | .action([&](const auto& /*unused*/) { 1104 | std::cout << m_version << std::endl; 1105 | if (m_exit_on_default_arguments) { 1106 | std::exit(0); 1107 | } 1108 | }) 1109 | .default_value(false) 1110 | .help("prints version information and exits") 1111 | .implicit_value(true) 1112 | .nargs(0); 1113 | } 1114 | } 1115 | 1116 | ArgumentParser(ArgumentParser&&) noexcept = default; 1117 | ArgumentParser& operator=(ArgumentParser&&) = default; 1118 | 1119 | ArgumentParser(const ArgumentParser& other) 1120 | : m_program_name(other.m_program_name), m_version(other.m_version), 1121 | m_description(other.m_description), m_epilog(other.m_epilog), 1122 | m_prefix_chars(other.m_prefix_chars), 1123 | m_assign_chars(other.m_assign_chars), m_is_parsed(other.m_is_parsed), 1124 | m_positional_arguments(other.m_positional_arguments), 1125 | m_optional_arguments(other.m_optional_arguments), 1126 | m_parser_path(other.m_parser_path), m_subparsers(other.m_subparsers) { 1127 | for (auto it = std::begin(m_positional_arguments); 1128 | it != std::end(m_positional_arguments); ++it) { 1129 | index_argument(it); 1130 | } 1131 | for (auto it = std::begin(m_optional_arguments); 1132 | it != std::end(m_optional_arguments); ++it) { 1133 | index_argument(it); 1134 | } 1135 | for (auto it = std::begin(m_subparsers); it != std::end(m_subparsers); 1136 | ++it) { 1137 | m_subparser_map.insert_or_assign(it->get().m_program_name, it); 1138 | m_subparser_used.insert_or_assign(it->get().m_program_name, false); 1139 | } 1140 | } 1141 | 1142 | ~ArgumentParser() = default; 1143 | 1144 | ArgumentParser& operator=(const ArgumentParser& other) { 1145 | auto tmp = other; 1146 | std::swap(*this, tmp); 1147 | return *this; 1148 | } 1149 | 1150 | explicit operator bool() const { 1151 | auto arg_used = std::any_of(m_argument_map.cbegin(), 1152 | m_argument_map.cend(), 1153 | [](auto& it) { 1154 | return it.second->m_is_used; 1155 | }); 1156 | auto subparser_used = std::any_of(m_subparser_used.cbegin(), 1157 | m_subparser_used.cend(), 1158 | [](auto& it) { 1159 | return it.second; 1160 | }); 1161 | 1162 | return m_is_parsed && (arg_used || subparser_used); 1163 | } 1164 | 1165 | // Parameter packing 1166 | // Call add_argument with variadic number of string arguments 1167 | template Argument& add_argument(Targs... f_args) { 1168 | using array_of_sv = std::array; 1169 | auto argument = 1170 | m_optional_arguments.emplace(std::cend(m_optional_arguments), 1171 | m_prefix_chars, array_of_sv{ f_args... }); 1172 | 1173 | if (!argument->m_is_optional) { 1174 | m_positional_arguments.splice(std::cend(m_positional_arguments), 1175 | m_optional_arguments, argument); 1176 | } 1177 | 1178 | index_argument(argument); 1179 | return *argument; 1180 | } 1181 | 1182 | // Parameter packed add_parents method 1183 | // Accepts a variadic number of ArgumentParser objects 1184 | template 1185 | ArgumentParser& add_parents(const Targs &... f_args) { 1186 | for (const ArgumentParser& parent_parser : { std::ref(f_args)... }) { 1187 | for (const auto& argument : parent_parser.m_positional_arguments) { 1188 | auto it = m_positional_arguments.insert( 1189 | std::cend(m_positional_arguments), argument); 1190 | index_argument(it); 1191 | } 1192 | for (const auto& argument : parent_parser.m_optional_arguments) { 1193 | auto it = m_optional_arguments.insert(std::cend(m_optional_arguments), 1194 | argument); 1195 | index_argument(it); 1196 | } 1197 | } 1198 | return *this; 1199 | } 1200 | 1201 | ArgumentParser& add_description(std::string description) { 1202 | m_description = std::move(description); 1203 | return *this; 1204 | } 1205 | 1206 | ArgumentParser& add_epilog(std::string epilog) { 1207 | m_epilog = std::move(epilog); 1208 | return *this; 1209 | } 1210 | 1211 | /* Getter for arguments and subparsers. 1212 | * @throws std::logic_error in case of an invalid argument or subparser name 1213 | */ 1214 | template 1215 | T& at(std::string_view name) { 1216 | if constexpr (std::is_same_v) { 1217 | return (*this)[name]; 1218 | } 1219 | else { 1220 | auto subparser_it = m_subparser_map.find(name); 1221 | if (subparser_it != m_subparser_map.end()) { 1222 | return subparser_it->second->get(); 1223 | } 1224 | throw std::logic_error("No such subparser: " + std::string(name)); 1225 | } 1226 | } 1227 | 1228 | ArgumentParser& set_prefix_chars(std::string prefix_chars) { 1229 | m_prefix_chars = std::move(prefix_chars); 1230 | return *this; 1231 | } 1232 | 1233 | ArgumentParser& set_assign_chars(std::string assign_chars) { 1234 | m_assign_chars = std::move(assign_chars); 1235 | return *this; 1236 | } 1237 | 1238 | /* Call parse_args_internal - which does all the work 1239 | * Then, validate the parsed arguments 1240 | * This variant is used mainly for testing 1241 | * @throws std::runtime_error in case of any invalid argument 1242 | */ 1243 | void parse_args(const std::vector& arguments) { 1244 | parse_args_internal(arguments); 1245 | // Check if all arguments are parsed 1246 | for ([[maybe_unused]] const auto& [unused, argument] : m_argument_map) { 1247 | argument->validate(); 1248 | } 1249 | } 1250 | 1251 | /* Call parse_known_args_internal - which does all the work 1252 | * Then, validate the parsed arguments 1253 | * This variant is used mainly for testing 1254 | * @throws std::runtime_error in case of any invalid argument 1255 | */ 1256 | std::vector 1257 | parse_known_args(const std::vector& arguments) { 1258 | auto unknown_arguments = parse_known_args_internal(arguments); 1259 | // Check if all arguments are parsed 1260 | for ([[maybe_unused]] const auto& [unused, argument] : m_argument_map) { 1261 | argument->validate(); 1262 | } 1263 | return unknown_arguments; 1264 | } 1265 | 1266 | /* Main entry point for parsing command-line arguments using this 1267 | * ArgumentParser 1268 | * @throws std::runtime_error in case of any invalid argument 1269 | */ 1270 | // NOLINTNEXTLINE(cppcoreguidelines-avoid-c-arrays) 1271 | void parse_args(int argc, const char* const argv[]) { 1272 | parse_args({ argv, argv + argc }); 1273 | } 1274 | 1275 | /* Main entry point for parsing command-line arguments using this 1276 | * ArgumentParser 1277 | * @throws std::runtime_error in case of any invalid argument 1278 | */ 1279 | // NOLINTNEXTLINE(cppcoreguidelines-avoid-c-arrays) 1280 | auto parse_known_args(int argc, const char* const argv[]) { 1281 | return parse_known_args({ argv, argv + argc }); 1282 | } 1283 | 1284 | /* Getter for options with default values. 1285 | * @throws std::logic_error if parse_args() has not been previously called 1286 | * @throws std::logic_error if there is no such option 1287 | * @throws std::logic_error if the option has no value 1288 | * @throws std::bad_any_cast if the option is not of type T 1289 | */ 1290 | template T get(std::string_view arg_name) const { 1291 | if (!m_is_parsed) { 1292 | throw std::logic_error("Nothing parsed, no arguments are available."); 1293 | } 1294 | return (*this)[arg_name].get(); 1295 | } 1296 | 1297 | /* Getter for options without default values. 1298 | * @pre The option has no default value. 1299 | * @throws std::logic_error if there is no such option 1300 | * @throws std::bad_any_cast if the option is not of type T 1301 | */ 1302 | template 1303 | auto present(std::string_view arg_name) const -> std::optional { 1304 | return (*this)[arg_name].present(); 1305 | } 1306 | 1307 | /* Getter that returns true for user-supplied options. Returns false if not 1308 | * user-supplied, even with a default value. 1309 | */ 1310 | auto is_used(std::string_view arg_name) const { 1311 | return (*this)[arg_name].m_is_used; 1312 | } 1313 | 1314 | /* Getter that returns true if a subcommand is used. 1315 | */ 1316 | auto is_subcommand_used(std::string_view subcommand_name) const { 1317 | return m_subparser_used.at(subcommand_name); 1318 | } 1319 | 1320 | /* Getter that returns true if a subcommand is used. 1321 | */ 1322 | auto is_subcommand_used(const ArgumentParser& subparser) const { 1323 | return is_subcommand_used(subparser.m_program_name); 1324 | } 1325 | 1326 | /* Indexing operator. Return a reference to an Argument object 1327 | * Used in conjuction with Argument.operator== e.g., parser["foo"] == true 1328 | * @throws std::logic_error in case of an invalid argument name 1329 | */ 1330 | Argument& operator[](std::string_view arg_name) const { 1331 | auto it = m_argument_map.find(arg_name); 1332 | if (it != m_argument_map.end()) { 1333 | return *(it->second); 1334 | } 1335 | if (!is_valid_prefix_char(arg_name.front())) { 1336 | std::string name(arg_name); 1337 | const auto legal_prefix_char = get_any_valid_prefix_char(); 1338 | const auto prefix = std::string(1, legal_prefix_char); 1339 | 1340 | // "-" + arg_name 1341 | name = prefix + name; 1342 | it = m_argument_map.find(name); 1343 | if (it != m_argument_map.end()) { 1344 | return *(it->second); 1345 | } 1346 | // "--" + arg_name 1347 | name = prefix + name; 1348 | it = m_argument_map.find(name); 1349 | if (it != m_argument_map.end()) { 1350 | return *(it->second); 1351 | } 1352 | } 1353 | throw std::logic_error("No such argument: " + std::string(arg_name)); 1354 | } 1355 | 1356 | // Print help message 1357 | friend auto operator<<(std::ostream& stream, const ArgumentParser& parser) 1358 | -> std::ostream& { 1359 | stream.setf(std::ios_base::left); 1360 | 1361 | auto longest_arg_length = parser.get_length_of_longest_argument(); 1362 | 1363 | stream << parser.usage() << "\n\n"; 1364 | 1365 | if (!parser.m_description.empty()) { 1366 | stream << parser.m_description << "\n\n"; 1367 | } 1368 | 1369 | if (!parser.m_positional_arguments.empty()) { 1370 | stream << "Positional arguments:\n"; 1371 | } 1372 | 1373 | for (const auto& argument : parser.m_positional_arguments) { 1374 | stream.width(static_cast(longest_arg_length)); 1375 | stream << argument; 1376 | } 1377 | 1378 | if (!parser.m_optional_arguments.empty()) { 1379 | stream << (parser.m_positional_arguments.empty() ? "" : "\n") 1380 | << "Optional arguments:\n"; 1381 | } 1382 | 1383 | for (const auto& argument : parser.m_optional_arguments) { 1384 | stream.width(static_cast(longest_arg_length)); 1385 | stream << argument; 1386 | } 1387 | 1388 | if (!parser.m_subparser_map.empty()) { 1389 | stream << (parser.m_positional_arguments.empty() 1390 | ? (parser.m_optional_arguments.empty() ? "" : "\n") 1391 | : "\n") 1392 | << "Subcommands:\n"; 1393 | for (const auto& [command, subparser] : parser.m_subparser_map) { 1394 | stream << std::setw(2) << " "; 1395 | stream << std::setw(static_cast(longest_arg_length - 2)) 1396 | << command; 1397 | stream << " " << subparser->get().m_description << "\n"; 1398 | } 1399 | } 1400 | 1401 | if (!parser.m_epilog.empty()) { 1402 | stream << '\n'; 1403 | stream << parser.m_epilog << "\n\n"; 1404 | } 1405 | 1406 | return stream; 1407 | } 1408 | 1409 | // Format help message 1410 | auto help() const -> std::stringstream { 1411 | std::stringstream out; 1412 | out << *this; 1413 | return out; 1414 | } 1415 | 1416 | // Format usage part of help only 1417 | auto usage() const -> std::string { 1418 | std::stringstream stream; 1419 | 1420 | stream << "Usage: " << this->m_program_name; 1421 | 1422 | // Add any options inline here 1423 | for (const auto& argument : this->m_optional_arguments) { 1424 | stream << " " << argument.get_inline_usage(); 1425 | } 1426 | // Put positional arguments after the optionals 1427 | for (const auto& argument : this->m_positional_arguments) { 1428 | if (!argument.m_metavar.empty()) { 1429 | stream << " " << argument.m_metavar; 1430 | } 1431 | else { 1432 | stream << " " << argument.m_names.front(); 1433 | } 1434 | } 1435 | // Put subcommands after positional arguments 1436 | if (!m_subparser_map.empty()) { 1437 | stream << " {"; 1438 | std::size_t i{ 0 }; 1439 | for (const auto& [command, unused] : m_subparser_map) { 1440 | if (i == 0) { 1441 | stream << command; 1442 | } 1443 | else { 1444 | stream << "," << command; 1445 | } 1446 | ++i; 1447 | } 1448 | stream << "}"; 1449 | } 1450 | 1451 | return stream.str(); 1452 | } 1453 | 1454 | // Printing the one and only help message 1455 | // I've stuck with a simple message format, nothing fancy. 1456 | [[deprecated("Use cout << program; instead. See also help().")]] std::string 1457 | print_help() const { 1458 | auto out = help(); 1459 | std::cout << out.rdbuf(); 1460 | return out.str(); 1461 | } 1462 | 1463 | void add_subparser(ArgumentParser& parser) { 1464 | parser.m_parser_path = m_program_name + " " + parser.m_program_name; 1465 | auto it = m_subparsers.emplace(std::cend(m_subparsers), parser); 1466 | m_subparser_map.insert_or_assign(parser.m_program_name, it); 1467 | m_subparser_used.insert_or_assign(parser.m_program_name, false); 1468 | } 1469 | 1470 | private: 1471 | bool is_valid_prefix_char(char c) const { 1472 | return m_prefix_chars.find(c) != std::string::npos; 1473 | } 1474 | 1475 | char get_any_valid_prefix_char() const { return m_prefix_chars[0]; } 1476 | 1477 | /* 1478 | * Pre-process this argument list. Anything starting with "--", that 1479 | * contains an =, where the prefix before the = has an entry in the 1480 | * options table, should be split. 1481 | */ 1482 | std::vector 1483 | preprocess_arguments(const std::vector& raw_arguments) const { 1484 | std::vector arguments{}; 1485 | for (const auto& arg : raw_arguments) { 1486 | 1487 | const auto argument_starts_with_prefix_chars = 1488 | [this](const std::string& a) -> bool { 1489 | if (!a.empty()) { 1490 | 1491 | const auto legal_prefix = [this](char c) -> bool { 1492 | return m_prefix_chars.find(c) != std::string::npos; 1493 | }; 1494 | 1495 | // Windows-style 1496 | // if '/' is a legal prefix char 1497 | // then allow single '/' followed by argument name, followed by an 1498 | // assign char, e.g., ':' e.g., 'test.exe /A:Foo' 1499 | const auto windows_style = legal_prefix('/'); 1500 | 1501 | if (windows_style) { 1502 | if (legal_prefix(a[0])) { 1503 | return true; 1504 | } 1505 | } 1506 | else { 1507 | // Slash '/' is not a legal prefix char 1508 | // For all other characters, only support long arguments 1509 | // i.e., the argument must start with 2 prefix chars, e.g, 1510 | // '--foo' e,g, './test --foo=Bar -DARG=yes' 1511 | if (a.size() > 1) { 1512 | return (legal_prefix(a[0]) && legal_prefix(a[1])); 1513 | } 1514 | } 1515 | } 1516 | return false; 1517 | }; 1518 | 1519 | // Check that: 1520 | // - We don't have an argument named exactly this 1521 | // - The argument starts with a prefix char, e.g., "--" 1522 | // - The argument contains an assign char, e.g., "=" 1523 | auto assign_char_pos = arg.find_first_of(m_assign_chars); 1524 | 1525 | if (m_argument_map.find(arg) == m_argument_map.end() && 1526 | argument_starts_with_prefix_chars(arg) && 1527 | assign_char_pos != std::string::npos) { 1528 | // Get the name of the potential option, and check it exists 1529 | std::string opt_name = arg.substr(0, assign_char_pos); 1530 | if (m_argument_map.find(opt_name) != m_argument_map.end()) { 1531 | // This is the name of an option! Split it into two parts 1532 | arguments.push_back(std::move(opt_name)); 1533 | arguments.push_back(arg.substr(assign_char_pos + 1)); 1534 | continue; 1535 | } 1536 | } 1537 | // If we've fallen through to here, then it's a standard argument 1538 | arguments.push_back(arg); 1539 | } 1540 | return arguments; 1541 | } 1542 | 1543 | /* 1544 | * @throws std::runtime_error in case of any invalid argument 1545 | */ 1546 | void parse_args_internal(const std::vector& raw_arguments) { 1547 | auto arguments = preprocess_arguments(raw_arguments); 1548 | if (m_program_name.empty() && !arguments.empty()) { 1549 | m_program_name = arguments.front(); 1550 | } 1551 | auto end = std::end(arguments); 1552 | auto positional_argument_it = std::begin(m_positional_arguments); 1553 | for (auto it = std::next(std::begin(arguments)); it != end;) { 1554 | const auto& current_argument = *it; 1555 | if (Argument::is_positional(current_argument, m_prefix_chars)) { 1556 | if (positional_argument_it == std::end(m_positional_arguments)) { 1557 | 1558 | std::string_view maybe_command = current_argument; 1559 | 1560 | // Check sub-parsers 1561 | auto subparser_it = m_subparser_map.find(maybe_command); 1562 | if (subparser_it != m_subparser_map.end()) { 1563 | 1564 | // build list of remaining args 1565 | const auto unprocessed_arguments = 1566 | std::vector(it, end); 1567 | 1568 | // invoke subparser 1569 | m_is_parsed = true; 1570 | m_subparser_used[maybe_command] = true; 1571 | return subparser_it->second->get().parse_args( 1572 | unprocessed_arguments); 1573 | } 1574 | 1575 | throw std::runtime_error( 1576 | "Maximum number of positional arguments exceeded"); 1577 | } 1578 | auto argument = positional_argument_it++; 1579 | it = argument->consume(it, end); 1580 | continue; 1581 | } 1582 | 1583 | auto arg_map_it = m_argument_map.find(current_argument); 1584 | if (arg_map_it != m_argument_map.end()) { 1585 | auto argument = arg_map_it->second; 1586 | it = argument->consume(std::next(it), end, arg_map_it->first); 1587 | } 1588 | else if (const auto& compound_arg = current_argument; 1589 | compound_arg.size() > 1 && 1590 | is_valid_prefix_char(compound_arg[0]) && 1591 | !is_valid_prefix_char(compound_arg[1])) { 1592 | ++it; 1593 | for (std::size_t j = 1; j < compound_arg.size(); j++) { 1594 | auto hypothetical_arg = std::string{ '-', compound_arg[j] }; 1595 | auto arg_map_it2 = m_argument_map.find(hypothetical_arg); 1596 | if (arg_map_it2 != m_argument_map.end()) { 1597 | auto argument = arg_map_it2->second; 1598 | it = argument->consume(it, end, arg_map_it2->first); 1599 | } 1600 | else { 1601 | throw std::runtime_error("Unknown argument: " + current_argument); 1602 | } 1603 | } 1604 | } 1605 | else { 1606 | throw std::runtime_error("Unknown argument: " + current_argument); 1607 | } 1608 | } 1609 | m_is_parsed = true; 1610 | } 1611 | 1612 | /* 1613 | * Like parse_args_internal but collects unused args into a vector 1614 | */ 1615 | std::vector 1616 | parse_known_args_internal(const std::vector& raw_arguments) { 1617 | auto arguments = preprocess_arguments(raw_arguments); 1618 | 1619 | std::vector unknown_arguments{}; 1620 | 1621 | if (m_program_name.empty() && !arguments.empty()) { 1622 | m_program_name = arguments.front(); 1623 | } 1624 | auto end = std::end(arguments); 1625 | auto positional_argument_it = std::begin(m_positional_arguments); 1626 | for (auto it = std::next(std::begin(arguments)); it != end;) { 1627 | const auto& current_argument = *it; 1628 | if (Argument::is_positional(current_argument, m_prefix_chars)) { 1629 | if (positional_argument_it == std::end(m_positional_arguments)) { 1630 | 1631 | std::string_view maybe_command = current_argument; 1632 | 1633 | // Check sub-parsers 1634 | auto subparser_it = m_subparser_map.find(maybe_command); 1635 | if (subparser_it != m_subparser_map.end()) { 1636 | 1637 | // build list of remaining args 1638 | const auto unprocessed_arguments = 1639 | std::vector(it, end); 1640 | 1641 | // invoke subparser 1642 | m_is_parsed = true; 1643 | m_subparser_used[maybe_command] = true; 1644 | return subparser_it->second->get().parse_known_args_internal( 1645 | unprocessed_arguments); 1646 | } 1647 | 1648 | // save current argument as unknown and go to next argument 1649 | unknown_arguments.push_back(current_argument); 1650 | ++it; 1651 | } 1652 | else { 1653 | // current argument is the value of a positional argument 1654 | // consume it 1655 | auto argument = positional_argument_it++; 1656 | it = argument->consume(it, end); 1657 | } 1658 | continue; 1659 | } 1660 | 1661 | auto arg_map_it = m_argument_map.find(current_argument); 1662 | if (arg_map_it != m_argument_map.end()) { 1663 | auto argument = arg_map_it->second; 1664 | it = argument->consume(std::next(it), end, arg_map_it->first); 1665 | } 1666 | else if (const auto& compound_arg = current_argument; 1667 | compound_arg.size() > 1 && 1668 | is_valid_prefix_char(compound_arg[0]) && 1669 | !is_valid_prefix_char(compound_arg[1])) { 1670 | ++it; 1671 | for (std::size_t j = 1; j < compound_arg.size(); j++) { 1672 | auto hypothetical_arg = std::string{ '-', compound_arg[j] }; 1673 | auto arg_map_it2 = m_argument_map.find(hypothetical_arg); 1674 | if (arg_map_it2 != m_argument_map.end()) { 1675 | auto argument = arg_map_it2->second; 1676 | it = argument->consume(it, end, arg_map_it2->first); 1677 | } 1678 | else { 1679 | unknown_arguments.push_back(current_argument); 1680 | break; 1681 | } 1682 | } 1683 | } 1684 | else { 1685 | // current argument is an optional-like argument that is unknown 1686 | // save it and move to next argument 1687 | unknown_arguments.push_back(current_argument); 1688 | ++it; 1689 | } 1690 | } 1691 | m_is_parsed = true; 1692 | return unknown_arguments; 1693 | } 1694 | 1695 | // Used by print_help. 1696 | std::size_t get_length_of_longest_argument() const { 1697 | if (m_argument_map.empty()) { 1698 | return 0; 1699 | } 1700 | std::size_t max_size = 0; 1701 | for ([[maybe_unused]] const auto& [unused, argument] : m_argument_map) { 1702 | max_size = std::max(max_size, argument->get_arguments_length()); 1703 | } 1704 | for ([[maybe_unused]] const auto& [command, unused] : m_subparser_map) { 1705 | max_size = std::max(max_size, command.size()); 1706 | } 1707 | return max_size; 1708 | } 1709 | 1710 | using argument_it = std::list::iterator; 1711 | using argument_parser_it = 1712 | std::list>::iterator; 1713 | 1714 | void index_argument(argument_it it) { 1715 | for (const auto& name : std::as_const(it->m_names)) { 1716 | m_argument_map.insert_or_assign(name, it); 1717 | } 1718 | } 1719 | 1720 | std::string m_program_name; 1721 | std::string m_version; 1722 | std::string m_description; 1723 | std::string m_epilog; 1724 | bool m_exit_on_default_arguments = true; 1725 | std::string m_prefix_chars{ "-" }; 1726 | std::string m_assign_chars{ "=" }; 1727 | bool m_is_parsed = false; 1728 | std::list m_positional_arguments; 1729 | std::list m_optional_arguments; 1730 | std::map m_argument_map; 1731 | std::string m_parser_path; 1732 | std::list> m_subparsers; 1733 | std::map m_subparser_map; 1734 | std::map m_subparser_used; 1735 | }; 1736 | 1737 | } // namespace argparse --------------------------------------------------------------------------------