mirror of
https://github.com/hasherezade/pe-sieve.git
synced 2026-10-12 02:15:22 +00:00
539 lines
17 KiB
C++
539 lines
17 KiB
C++
#include "scanner.h"
|
|
|
|
#include <sstream>
|
|
#include <fstream>
|
|
#include <string>
|
|
#include <locale>
|
|
#include <codecvt>
|
|
|
|
#include <tlhelp32.h>
|
|
|
|
#include "../utils/format_util.h"
|
|
#include "../utils/path_converter.h"
|
|
#include "../utils/workingset_enum.h"
|
|
#include "../utils/modules_enum.h"
|
|
#include "../utils/process_privilege.h"
|
|
#include "../utils/process_util.h"
|
|
|
|
#include "headers_scanner.h"
|
|
#include "code_scanner.h"
|
|
#include "iat_scanner.h"
|
|
#include "workingset_scanner.h"
|
|
#include "mapping_scanner.h"
|
|
#include "hook_targets_resolver.h"
|
|
#include "thread_scanner.h"
|
|
|
|
using namespace pesieve;
|
|
using namespace pesieve::util;
|
|
|
|
namespace pesieve {
|
|
|
|
bool validate_param_str(PARAM_STRING& strparam)
|
|
{
|
|
if (!strparam.buffer || strparam.length == 0) {
|
|
return false;
|
|
}
|
|
if (IsBadReadPtr(strparam.buffer, strparam.length)) {
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
namespace util {
|
|
|
|
void print_scantime(std::stringstream& stream, size_t timeInMs)
|
|
{
|
|
float seconds = ((float)timeInMs / 1000);
|
|
float minutes = ((float)timeInMs / 60000);
|
|
stream << std::dec << timeInMs << " ms.";
|
|
if (seconds > 0.5) {
|
|
stream << " = " << seconds << " sec.";
|
|
}
|
|
if (minutes > 0.5) {
|
|
stream << " = " << minutes << " min.";
|
|
}
|
|
}
|
|
|
|
}; // namespace util
|
|
|
|
void print_scan_time(const char* scanned_element, size_t total_time)
|
|
{
|
|
std::stringstream ss;
|
|
ss << "[*] "<< scanned_element << " scanned in ";
|
|
util::print_scantime(ss, total_time);
|
|
std::cout << ss.str() << std::endl;
|
|
}
|
|
|
|
bool is_running(HANDLE processHandle)
|
|
{
|
|
DWORD exitCode = 0;
|
|
if (GetExitCodeProcess(processHandle, &exitCode)) {
|
|
if (exitCode != STILL_ACTIVE) {
|
|
#ifdef _DEBUG
|
|
std::cerr << "Process terminated, exit = " << std::dec << exitCode << "\n";
|
|
#endif
|
|
return false; //process terminated, discontinue the scan
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
};
|
|
|
|
pesieve::ProcessScanner::ProcessScanner(HANDLE procHndl, bool is_reflection, pesieve::t_params _args)
|
|
: processHandle(procHndl), isDEP(false), isReflection(is_reflection),
|
|
args(_args)
|
|
{
|
|
symbols.InitSymbols(this->processHandle, _args.download_symbols);
|
|
if (validate_param_str(args.modules_ignored)) {
|
|
pesieve::util::string_to_list(args.modules_ignored.buffer, PARAM_LIST_SEPARATOR, ignoredModules);
|
|
}
|
|
}
|
|
|
|
t_scan_status pesieve::ProcessScanner::scanForHollows(HANDLE processHandle, ModuleData& modData, RemoteModuleData &remoteModData, ProcessScanReport& process_report)
|
|
{
|
|
BOOL isWow64 = FALSE;
|
|
#ifdef _WIN64
|
|
is_process_wow64(processHandle, &isWow64);
|
|
#endif
|
|
HeadersScanner scanner(processHandle, modData, remoteModData);
|
|
HeadersScanReport *scan_report = scanner.scanRemote();
|
|
if (!scan_report) {
|
|
return SCAN_ERROR;
|
|
}
|
|
|
|
if (scan_report->archMismatch && isWow64) {
|
|
#ifdef _DEBUG
|
|
std::cout << "Arch mismatch, reloading..." << std::endl;
|
|
#endif
|
|
if (modData.reloadWow64()) {
|
|
delete scan_report; // delete previous report
|
|
scan_report = scanner.scanRemote();
|
|
}
|
|
}
|
|
scan_report->moduleFile = modData.szModName;
|
|
scan_report->isInPEB = modData.isModuleInPEBList();
|
|
|
|
t_scan_status is_replaced = ModuleScanReport::get_scan_status(scan_report);
|
|
if (is_replaced && !scan_report->isHdrReplaced()) {
|
|
is_replaced = SCAN_NOT_SUSPICIOUS;
|
|
}
|
|
process_report.appendReport(scan_report);
|
|
return is_replaced;
|
|
}
|
|
|
|
t_scan_status pesieve::ProcessScanner::scanForIATHooks(HANDLE processHandle, ModuleData& modData, RemoteModuleData &remoteModData, ProcessScanReport& process_report, t_iat_scan_mode filter)
|
|
{
|
|
const peconv::ExportsMapper *expMap = process_report.exportsMap;
|
|
if (!expMap) {
|
|
return SCAN_ERROR;
|
|
}
|
|
|
|
if (process_report.isModuleReplaced(modData.moduleHandle)) {
|
|
#ifdef _DEBUG
|
|
std::cout << "Cannot scan replaced module for IAT hooks!\n";
|
|
#endif
|
|
return SCAN_ERROR;
|
|
}
|
|
IATScanner scanner(processHandle, modData, remoteModData, *expMap, process_report.modulesInfo, filter);
|
|
IATScanReport *scan_report = scanner.scanRemote();
|
|
if (!scan_report) {
|
|
return SCAN_ERROR;
|
|
}
|
|
t_scan_status scan_res = ModuleScanReport::get_scan_status(scan_report);
|
|
scan_report->moduleFile = modData.szModName;
|
|
process_report.appendReport(scan_report);
|
|
return scan_res;
|
|
}
|
|
|
|
t_scan_status pesieve::ProcessScanner::scanForHooks(HANDLE processHandle, ModuleData& modData, RemoteModuleData &remoteModData, ProcessScanReport& process_report, bool scan_data, bool scan_inaccessible)
|
|
{
|
|
CodeScanner scanner(processHandle, modData, remoteModData);
|
|
scanner.setScanData(scan_data);
|
|
scanner.setScanInaccessible(scan_inaccessible);
|
|
CodeScanReport *scan_report = scanner.scanRemote();
|
|
if (!scan_report) {
|
|
return SCAN_ERROR;
|
|
}
|
|
t_scan_status is_hooked = ModuleScanReport::get_scan_status(scan_report);
|
|
|
|
scan_report->moduleFile = modData.szModName;
|
|
process_report.appendReport(scan_report);
|
|
return is_hooked;
|
|
}
|
|
|
|
bool pesieve::ProcessScanner::resolveHooksTargets(ProcessScanReport& process_report)
|
|
{
|
|
HookTargetResolver hookResolver(process_report);
|
|
std::set<ModuleScanReport*> &code_reports = process_report.reportsByType[ProcessScanReport::REPORT_CODE_SCAN];
|
|
size_t resolved_count = hookResolver.resolveAllHooks(code_reports);
|
|
return (resolved_count > 0);
|
|
}
|
|
|
|
inline bool set_non_suspicious(const std::set<ModuleScanReport*> &scan_reports, bool dnet_modules_only)
|
|
{
|
|
bool is_set = false;
|
|
std::set<ModuleScanReport*>::iterator itr;
|
|
for (itr = scan_reports.begin(); itr != scan_reports.end(); ++itr) {
|
|
ModuleScanReport* report = *itr;
|
|
if (!report) {
|
|
//this should never happen
|
|
continue;
|
|
}
|
|
if (dnet_modules_only && !report->isDotNetModule) {
|
|
continue;
|
|
}
|
|
if (report->status == SCAN_SUSPICIOUS) {
|
|
report->status = SCAN_NOT_SUSPICIOUS;
|
|
is_set = true;
|
|
}
|
|
}
|
|
return is_set;
|
|
}
|
|
|
|
bool pesieve::ProcessScanner::filterDotNetReport(ProcessScanReport& process_report)
|
|
{
|
|
if (!process_report.isManaged // Not a .NET process
|
|
|| this->args.dotnet_policy == pesieve::PE_DNET_NONE) // .NET policy not set
|
|
{
|
|
return false; // no filtering needed
|
|
}
|
|
bool is_set = false;
|
|
if (this->args.dotnet_policy == pesieve::PE_DNET_SKIP_MAPPING
|
|
|| this->args.dotnet_policy == pesieve::PE_DNET_SKIP_ALL)
|
|
{
|
|
// set hook modules as not suspicious
|
|
const std::set<ModuleScanReport*> &reports = process_report.reportsByType[ProcessScanReport::REPORT_MAPPING_SCAN];
|
|
is_set = set_non_suspicious(reports, true);
|
|
}
|
|
if (this->args.dotnet_policy == pesieve::PE_DNET_SKIP_HOOKS
|
|
|| this->args.dotnet_policy == pesieve::PE_DNET_SKIP_ALL)
|
|
{
|
|
// set hook modules as not suspicious
|
|
const std::set<ModuleScanReport*> &reports = process_report.reportsByType[ProcessScanReport::REPORT_CODE_SCAN];
|
|
is_set = set_non_suspicious(reports, false);
|
|
}
|
|
if (this->args.dotnet_policy == pesieve::PE_DNET_SKIP_SHC
|
|
|| this->args.dotnet_policy == pesieve::PE_DNET_SKIP_ALL)
|
|
{
|
|
// set shellcodes detected by mempage scan as not suspicious
|
|
const std::set<ModuleScanReport*> &reports = process_report.reportsByType[ProcessScanReport::REPORT_MEMPAGE_SCAN];
|
|
const bool is_set1 = set_non_suspicious(reports, false);
|
|
|
|
// set shellcodes detected by thread scan as not-suspicious
|
|
const std::set<ModuleScanReport*>& reports2 = process_report.reportsByType[ProcessScanReport::REPORT_THREADS_SCAN];
|
|
const bool is_set2 = set_non_suspicious(reports2, false);
|
|
is_set = is_set1 || is_set2;
|
|
}
|
|
return is_set;
|
|
}
|
|
|
|
ProcessScanReport* pesieve::ProcessScanner::scanRemote()
|
|
{
|
|
this->isDEP = is_DEP_enabled(this->processHandle);
|
|
|
|
const bool is_64bit = pesieve::util::is_process_64bit(this->processHandle);
|
|
|
|
ProcessScanReport *pReport = new ProcessScanReport(this->args.pid, is_64bit, this->isReflection, &this->args);
|
|
|
|
char image_buf[MAX_PATH] = { 0 };
|
|
GetProcessImageFileNameA(this->processHandle, image_buf, MAX_PATH);
|
|
pReport->mainImagePath = device_path_to_win32_path(image_buf);
|
|
|
|
std::stringstream errorsStr;
|
|
|
|
// scan modules
|
|
size_t modulesScanned = 0;
|
|
try {
|
|
modulesScanned = scanModules(*pReport);
|
|
} catch (std::exception &e) {
|
|
modulesScanned = 0;
|
|
errorsStr << e.what();
|
|
}
|
|
|
|
// scan working set
|
|
size_t regionsScanned = 0;
|
|
try {
|
|
regionsScanned = scanWorkingSet(*pReport);
|
|
} catch (std::exception &e) {
|
|
regionsScanned = 0;
|
|
errorsStr << e.what();
|
|
}
|
|
|
|
// scan IATs
|
|
size_t iatsScanned = 0;
|
|
if (args.iat) {
|
|
try {
|
|
iatsScanned = scanModulesIATs(*pReport);
|
|
}
|
|
catch (std::exception& e) {
|
|
iatsScanned = 0;
|
|
errorsStr << e.what();
|
|
}
|
|
}
|
|
|
|
// scan threads
|
|
size_t threadsScanned = 0;
|
|
if (args.threads) {
|
|
try {
|
|
threadsScanned = scanThreads(*pReport);
|
|
}
|
|
catch (std::exception& e) {
|
|
threadsScanned = 0;
|
|
errorsStr << e.what();
|
|
}
|
|
}
|
|
|
|
// throw error only if none of the scans was successful
|
|
if (!modulesScanned && !iatsScanned && !regionsScanned && !threadsScanned) {
|
|
throw std::runtime_error(errorsStr.str());
|
|
}
|
|
//post-process hooks
|
|
resolveHooksTargets(*pReport);
|
|
|
|
//post-process detection reports according to the .NET policy
|
|
filterDotNetReport(*pReport);
|
|
return pReport;
|
|
}
|
|
|
|
size_t pesieve::ProcessScanner::scanWorkingSet(ProcessScanReport &pReport) //throws exceptions
|
|
{
|
|
if (!util::count_workingset_entries(this->processHandle)) {
|
|
throw std::runtime_error("Could not query the working set. ");
|
|
return 0;
|
|
}
|
|
process_details proc_details(this->isReflection, this->isDEP);
|
|
|
|
DWORD start_tick = GetTickCount();
|
|
std::set<mem_region_info> region_bases;
|
|
size_t pages_count = util::enum_workingset(processHandle, region_bases);
|
|
if (!args.quiet) {
|
|
std::cout << "Scanning workingset: " << std::dec << pages_count << " memory regions." << std::endl;
|
|
}
|
|
size_t counter = 0;
|
|
//now scan all the nodes:
|
|
|
|
for (auto set_itr = region_bases.begin(); set_itr != region_bases.end() && is_running(this->processHandle); ++set_itr, ++counter) {
|
|
const mem_region_info region = *set_itr;
|
|
|
|
WorkingSetScanner scanner(this->processHandle, proc_details, region, this->args, pReport);
|
|
WorkingSetScanReport *my_report = scanner.scanRemote();
|
|
if (!my_report) {
|
|
continue;
|
|
}
|
|
my_report->is_listed_module = pReport.hasModule((ULONGLONG) my_report->module);
|
|
// this is a code section inside a PE file that was already detected
|
|
if (!my_report->has_pe
|
|
&& (pReport.hasModuleContaining((ULONGLONG)my_report->module, my_report->moduleSize))
|
|
)
|
|
{
|
|
my_report->status = SCAN_NOT_SUSPICIOUS;
|
|
}
|
|
|
|
pReport.appendReport(my_report);
|
|
}
|
|
if (!args.quiet) {
|
|
const DWORD total_time = GetTickCount() - start_tick;
|
|
print_scan_time("Workingset", total_time);
|
|
}
|
|
return counter;
|
|
}
|
|
|
|
ModuleScanReport* pesieve::ProcessScanner::scanForMappingMismatch(ModuleData& modData, ProcessScanReport& process_report)
|
|
{
|
|
MappingScanner scanner(processHandle, modData);
|
|
MappingScanReport *scan_report = scanner.scanRemote();
|
|
|
|
process_report.appendReport(scan_report);
|
|
return scan_report;
|
|
}
|
|
|
|
size_t pesieve::ProcessScanner::scanModules(ProcessScanReport &pReport) //throws exceptions
|
|
{
|
|
std::vector<HMODULE> hMods;
|
|
if (!enum_modules(this->processHandle, hMods, LIST_MODULES_ALL)) {
|
|
return 0;
|
|
}
|
|
const size_t modules_count = hMods.size();
|
|
if (args.imprec_mode != PE_IMPREC_NONE || args.iat != pesieve::PE_IATS_NONE || args.threads) {
|
|
pReport.exportsMap = new peconv::ExportsMapper();
|
|
}
|
|
|
|
size_t counter = 0;
|
|
for (counter = 0; counter < modules_count && is_running(this->processHandle); counter++) {
|
|
if (processHandle == nullptr) break;
|
|
const HMODULE module_base = hMods[counter];
|
|
//load module from file:
|
|
ModuleData modData(processHandle, module_base, true, args.use_cache);
|
|
ModuleScanReport *mappingScanReport = this->scanForMappingMismatch(modData, pReport);
|
|
|
|
//load the original file to make the comparisons:
|
|
if (!modData.loadOriginal()) {
|
|
if (!args.quiet) {
|
|
std::cout << "[!][" << args.pid << "] Suspicious: could not read the module file!" << std::endl;
|
|
}
|
|
//make a report that finding original module was not possible
|
|
pReport.appendReport(new UnreachableModuleReport(module_base, 0, modData.szModName));
|
|
continue;
|
|
}
|
|
if (modData.isDotNet()) {
|
|
// the process contains at least one .NET module. Treat it as managed process:
|
|
pReport.isManaged = true;
|
|
}
|
|
// Don't scan modules that are in the ignore list
|
|
const std::string plainName = peconv::get_file_name(modData.szModName);
|
|
if (is_in_list(plainName, this->ignoredModules)) {
|
|
// ...but add such modules to the exports lookup:
|
|
if (pReport.exportsMap) {
|
|
pReport.exportsMap->add_to_lookup(modData.szModName, (HMODULE)modData.original_module, (ULONGLONG)modData.moduleHandle);
|
|
}
|
|
if (!args.quiet) {
|
|
std::cout << "[*] Skipping ignored: " << std::hex << (ULONGLONG)modData.moduleHandle << " : " << modData.szModName << std::endl;
|
|
}
|
|
pReport.appendReport(new SkippedModuleReport(modData.moduleHandle, modData.original_size, modData.szModName));
|
|
continue;
|
|
}
|
|
|
|
if (!args.quiet) {
|
|
std::cout << "[*] Scanning: " << modData.szModName;
|
|
if (modData.isDotNet()) {
|
|
std::cout << " (.NET) ";
|
|
}
|
|
std::cout << std::endl;
|
|
}
|
|
//load data about the remote module
|
|
RemoteModuleData remoteModData(processHandle, this->isReflection, module_base);
|
|
if (!remoteModData.isInitialized()) {
|
|
if (!is_running(processHandle)) break;
|
|
pReport.appendReport(new MalformedHeaderReport(module_base, 0, modData.szModName));
|
|
continue;
|
|
}
|
|
t_scan_status is_hollowed = scanForHollows(processHandle, modData, remoteModData, pReport);
|
|
if (is_hollowed == SCAN_ERROR) {
|
|
continue;
|
|
}
|
|
if (is_hollowed == SCAN_NOT_SUSPICIOUS) {
|
|
//if the content does not differ, ignore the different name of the mapped file
|
|
mappingScanReport->status = SCAN_NOT_SUSPICIOUS;
|
|
}
|
|
|
|
// the module is not hollowed, so we can add it to the exports lookup:
|
|
if (pReport.exportsMap) {
|
|
pReport.exportsMap->add_to_lookup(modData.szModName, (HMODULE) modData.original_module, (ULONGLONG) modData.moduleHandle);
|
|
}
|
|
|
|
if (!args.no_hooks //if hooks not disabled
|
|
&& (is_hollowed == SCAN_NOT_SUSPICIOUS) // and process is not hollowed
|
|
)
|
|
{
|
|
const bool scan_data = ((this->args.data >= pesieve::PE_DATA_SCAN_ALWAYS) && (this->args.data != pesieve::PE_DATA_SCAN_INACCESSIBLE_ONLY))
|
|
|| (!this->isDEP && (this->args.data == pesieve::PE_DATA_SCAN_NO_DEP));
|
|
|
|
const bool scan_inaccessible = (this->isReflection && (this->args.data >= PE_DATA_SCAN_INACCESSIBLE));
|
|
scanForHooks(processHandle, modData, remoteModData, pReport, scan_data, scan_inaccessible);
|
|
}
|
|
}
|
|
return counter;
|
|
}
|
|
|
|
size_t pesieve::ProcessScanner::scanModulesIATs(ProcessScanReport &pReport) //throws exceptions
|
|
{
|
|
if (!pReport.exportsMap) {
|
|
return 0; // this feature cannot work without Exports Map
|
|
}
|
|
std::vector<HMODULE> hMods;
|
|
if (!enum_modules(this->processHandle, hMods, LIST_MODULES_ALL)) {
|
|
return 0;
|
|
}
|
|
const size_t modules_count = hMods.size();
|
|
if (!args.quiet) {
|
|
std::cout << "Scanning for IAT hooks: " << std::dec << modules_count << " modules." << std::endl;
|
|
}
|
|
|
|
DWORD start_tick = GetTickCount();
|
|
size_t counter = 0;
|
|
for (counter = 0; counter < modules_count && is_running(this->processHandle); counter++) {
|
|
if (!processHandle) break; // this should never happen
|
|
|
|
const HMODULE module_base = hMods[counter];
|
|
//load module from file:
|
|
ModuleData modData(processHandle, module_base, true, args.use_cache);
|
|
|
|
// Don't scan modules that are in the ignore list
|
|
std::string plainName = peconv::get_file_name(modData.szModName);
|
|
if (is_in_list(plainName, this->ignoredModules)) {
|
|
continue;
|
|
}
|
|
|
|
//load data about the remote module
|
|
RemoteModuleData remoteModData(processHandle, this->isReflection, module_base);
|
|
if (remoteModData.isInitialized() == false) {
|
|
//make a report that initializing remote module was not possible
|
|
pReport.appendReport(new MalformedHeaderReport(module_base, 0, modData.szModName));
|
|
continue;
|
|
}
|
|
|
|
// do the IAT scan:
|
|
scanForIATHooks(processHandle, modData, remoteModData, pReport, this->args.iat);
|
|
}
|
|
if (!args.quiet) {
|
|
const DWORD total_time = GetTickCount() - start_tick;
|
|
print_scan_time("IATs", total_time);
|
|
}
|
|
return counter;
|
|
}
|
|
|
|
size_t pesieve::ProcessScanner::scanThreads(ProcessScanReport& pReport) //throws exceptions
|
|
{
|
|
if (!this->symbols.IsInitialized()) {
|
|
if (!args.quiet) {
|
|
std::cerr << "[-] Failed to initialize symbols!\n";
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
const DWORD pid = pReport.pid; //original PID, not a reflection!
|
|
|
|
const bool is_64bit = pesieve::util::is_process_64bit(this->processHandle);
|
|
#ifndef _WIN64
|
|
if (is_64bit) return 0;
|
|
#endif
|
|
|
|
if (!args.quiet) {
|
|
std::cout << "Scanning threads." << std::endl;
|
|
}
|
|
DWORD start_tick = GetTickCount();
|
|
|
|
std::map<DWORD, thread_info> threads_info;
|
|
if (!pesieve::util::fetch_threads_info(pid, threads_info)) { //extended info, but doesn't work on old Windows...
|
|
|
|
if (!pesieve::util::fetch_threads_by_snapshot(pid, threads_info)) { // works on old Windows, but gives less data..
|
|
|
|
if (!args.quiet) {
|
|
std::cerr << "[-] Failed enumerating threads." << std::endl;
|
|
}
|
|
return 0;
|
|
}
|
|
}
|
|
if (!pesieve::util::query_threads_details(threads_info)) {
|
|
if (!args.quiet) {
|
|
std::cout << "[-] Failed quering thread details." << std::endl;
|
|
}
|
|
}
|
|
size_t scanned_count = 0;
|
|
for (auto itr = threads_info.begin(); itr != threads_info.end() && is_running(this->processHandle); ++itr) {
|
|
const thread_info &info = itr->second;
|
|
|
|
ThreadScanner scanner(this->processHandle, this->isReflection, pReport.isManaged, info, pReport.modulesInfo, pReport.exportsMap, &symbols);
|
|
ThreadScanReport* report = scanner.scanRemote();
|
|
if (report) {
|
|
pReport.appendReport(report);
|
|
scanned_count++;
|
|
}
|
|
}
|
|
if (!args.quiet) {
|
|
const DWORD total_time = GetTickCount() - start_tick;
|
|
print_scan_time("Threads", total_time);
|
|
}
|
|
return scanned_count;
|
|
}
|