// // Copyright (C) 2017 Tavis Ormandy // // This program is free software; you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation; either version 2 of the License, or // (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // #ifndef _GNU_SOURCE # define _GNU_SOURCE #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "log.h" #include "winnt_types.h" #include "pe_linker.h" #include "ntoskernel.h" #include "util.h" #include "hook.h" #include "rsignal.h" #include "engineboot.h" #include "scanreply.h" #include "streambuffer.h" #include "openscan.h" const char header[] = "function log(msg) { parseFloat('__log: ' + msg); }\n" "function dump(obj) { for (i in obj) { log(i); log('\\t' + obj[i]); }; }\n"; const char footer[] = { #include "script.h" // Generated by Makefile 0, // String terminator }; DWORD (* __rsignal)(PHANDLE KernelHandle, DWORD Code, PVOID Params, DWORD Size); double JavaScriptLog(const char *nptr, char **endptr) { if (strncmp(nptr, "__log: ", 7) == 0) { LogMessage("%s", nptr + 7); return 0; } return strtod(nptr, endptr); } static DWORD EngineScanCallback(PSCANSTRUCT Scan) { return 0; } static DWORD ReadStream(PVOID this, QWORD Offset, PVOID Buffer, DWORD Size, PDWORD SizeRead) { memcpy(Buffer, this + Offset, *SizeRead = MIN(strlen(this+Offset), Size)); return TRUE; } static DWORD GetStreamSize(PVOID this, PQWORD FileSize) { *FileSize = strlen(this); return TRUE; } int main(int argc, char **argv, char **envp) { PVOID StrtodPtr; PIMAGE_DOS_HEADER DosHeader; PIMAGE_NT_HEADERS PeHeader; HANDLE KernelHandle; SCAN_REPLY ScanReply; BOOTENGINE_PARAMS BootParams; SCANSTREAM_PARAMS ScanParams; STREAMBUFFER_DESCRIPTOR ScanDescriptor; ENGINE_INFO EngineInfo; ENGINE_CONFIG EngineConfig; struct pe_image image = { .entry = NULL, .name = "engine/mpengine.dll", }; // Load the mpengine module. if (pe_load_library(image.name, &image.image, &image.size) == false) { return 1; } // Handle relocations, imports, etc. link_pe_images(&image, 1); // Fetch the headers to get base offsets. DosHeader = (PIMAGE_DOS_HEADER) image.image; PeHeader = (PIMAGE_NT_HEADERS)(image.image + DosHeader->e_lfanew); // Load any additional exports. if (!process_extra_exports(image.image, PeHeader->OptionalHeader.BaseOfCode, "engine/mpengine.map")) { LogMessage("A map file is required to intercept interpreter output. See documentation."); } // Calculate the commands needed to get export and map symbols visible in gdb. if (IsGdbPresent()) { LogMessage("GDB: add-symbol-file %s %#x+%#x", image.name, image.image, PeHeader->OptionalHeader.BaseOfCode); LogMessage("GDB: shell bash genmapsym.sh %#x+%#x symbols_%d.o < %s", image.image, PeHeader->OptionalHeader.BaseOfCode, getpid(), "engine/mpengine.map"); LogMessage("GDB: add-symbol-file symbols_%d.o 0", getpid()); __debugbreak(); } if (get_export("__rsignal", &__rsignal) == -1) { LogMessage("Cannot resolve mpengine!__rsignal, cannot continue"); return 1; } if (get_export("_strtod", &StrtodPtr) != -1) { insert_function_redirect(StrtodPtr, JavaScriptLog, HOOK_REPLACE_FUNCTION); } else { LogMessage("Unable to hook output (missing or incomplete map?), but input might still work. See documentation."); } // Call DllMain() image.entry((PVOID) 'MPEN', DLL_PROCESS_ATTACH, NULL); ZeroMemory(&BootParams, sizeof BootParams); ZeroMemory(&EngineInfo, sizeof EngineInfo); ZeroMemory(&EngineConfig, sizeof EngineConfig); BootParams.ClientVersion = BOOTENGINE_PARAMS_VERSION; BootParams.Attributes = BOOT_ATTR_NORMAL; BootParams.SignatureLocation = L"engine"; BootParams.ProductName = L"Legitimate Antivirus"; EngineConfig.QuarantineLocation = L"quarantine"; EngineConfig.Inclusions = L"*.*"; EngineConfig.EngineFlags = 1 << 1; BootParams.EngineInfo = &EngineInfo; BootParams.EngineConfig = &EngineConfig; KernelHandle = NULL; LogMessage("Please wait, initializing engine..."); if (__rsignal(&KernelHandle, RSIG_BOOTENGINE, &BootParams, sizeof BootParams) != 0) { LogMessage("__rsignal(RSIG_BOOTENGINE) returned failure, missing definitions?"); LogMessage("Make sure the VDM files and mpengine.dll are in the engine directory"); return 1; } ZeroMemory(&ScanParams, sizeof ScanParams); ZeroMemory(&ScanDescriptor, sizeof ScanDescriptor); ZeroMemory(&ScanReply, sizeof ScanReply); ScanParams.Descriptor = &ScanDescriptor; ScanParams.ScanReply = &ScanReply; ScanReply.EngineScanCallback = EngineScanCallback; ScanReply.field_C = 0x7fffffff; ScanDescriptor.Read = ReadStream; ScanDescriptor.GetSize = GetStreamSize; LogMessage("Ready, type javascript (history available, use arrow keys)"); LogMessage("Try log(msg) or dump(obj), mp.getAttribute() can query scan state booleans."); while (true) { CHAR *InputBuf = readline("> "); if (InputBuf) { CHAR *EscapeBuf = calloc(strlen(InputBuf) + 1, 3); CHAR *p = EscapeBuf; if (!EscapeBuf) break; // This is probably not correct. for (size_t i = 0; InputBuf[i]; i++) { if (InputBuf[i] == '%') { *p++ = '%'; *p++ = '2'; *p++ = '5'; } else if (InputBuf[i] == '"') { *p++ = '%'; *p++ = '2'; *p++ = '2'; } else if (InputBuf[i] == '\\') { *p++ = '%'; *p++ = '5'; *p++ = 'c'; } else { *p++ = InputBuf[i]; } } if (asprintf((PVOID) &ScanDescriptor.UserPtr, "%s\ntry{log(eval(unescape(\"%s\")))} catch(e) { log(e); }\n%s", header, EscapeBuf, footer) == -1) { err(EXIT_FAILURE, "memory allocation failure"); } free(EscapeBuf); } else { break; } if (__rsignal(&KernelHandle, RSIG_SCAN_STREAMBUFFER, &ScanParams, sizeof ScanParams) != 0) { LogMessage("__rsignal(RSIG_SCAN_STREAMBUFFER) returned failure, file unreadable?"); return 1; } add_history(InputBuf); free(ScanDescriptor.UserPtr); free(InputBuf); } return 0; }