Skip to content

Commit

Permalink
Merge branch 'thread_devel1'
Browse files Browse the repository at this point in the history
  • Loading branch information
hasherezade committed Sep 8, 2024
2 parents cab8f52 + 5ac79e7 commit df89282
Show file tree
Hide file tree
Showing 11 changed files with 305 additions and 26 deletions.
2 changes: 2 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,7 @@ set (utils_srcs
utils/process_reflection.cpp
utils/console_color.cpp
utils/strings_util.cpp
utils/syscall_extractor.cpp
)

set (utils_hdrs
Expand All @@ -182,6 +183,7 @@ set (utils_hdrs
utils/custom_mutex.h
utils/custom_buffer.h
utils/process_symbols.h
utils/syscall_extractor.h
)

set (params_info_hdrs
Expand Down
2 changes: 2 additions & 0 deletions pe_sieve.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,13 @@
#include "color_scheme.h"

#include "utils/artefacts_util.h"
#include "utils/syscall_extractor.h"

using namespace pesieve;
using namespace pesieve::util;

pesieve::PatternMatcher g_Matcher;
pesieve::SyscallTable g_SyscallTable;

namespace pesieve {
void check_access_denied(DWORD processID)
Expand Down
4 changes: 2 additions & 2 deletions pe_sieve_ver_short.h
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,6 @@
#define PESIEVE_MAJOR_VERSION 0
#define PESIEVE_MINOR_VERSION 3
#define PESIEVE_MICRO_VERSION 9
#define PESIEVE_PATCH_VERSION 7
#define PESIEVE_PATCH_VERSION 8

#define PESIEVE_VERSION_STR "0.3.9.7"
#define PESIEVE_VERSION_STR "0.3.9.8"
97 changes: 80 additions & 17 deletions scanners/thread_scanner.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@
#include "../utils/ntddk.h"
#include "../stats/stats.h"
#include "../utils/process_symbols.h"
#include "../utils/syscall_extractor.h"

extern pesieve::SyscallTable g_SyscallTable;

#define ENTROPY_TRESHOLD 3.0
//#define NO_ENTROPY_CHECK
Expand Down Expand Up @@ -50,6 +53,19 @@ namespace pesieve {
}
};

bool get_page_details(HANDLE processHandle, LPVOID start_va, MEMORY_BASIC_INFORMATION& page_info)
{
size_t page_info_size = sizeof(MEMORY_BASIC_INFORMATION);
const SIZE_T out = VirtualQueryEx(processHandle, (LPCVOID)start_va, &page_info, page_info_size);
const bool is_read = (out == page_info_size) ? true : false;
const DWORD error = is_read ? ERROR_SUCCESS : GetLastError();
if (error != ERROR_SUCCESS) {
//nothing to read
return false;
}
return true;
}

DWORD WINAPI enum_stack_thread(LPVOID lpParam)
{
t_stack_enum_params* args = static_cast<t_stack_enum_params*>(lpParam);
Expand Down Expand Up @@ -153,6 +169,46 @@ std::string ThreadScanReport::translate_thread_state(DWORD thread_state)

//---

bool pesieve::ThreadScanner::checkReturnAddrIntegrity(IN const std::vector<ULONGLONG>& callStack)
{
if (this->info.last_syscall == INVALID_SYSCALL || !symbols || !callStack.size() || !info.is_extended) {
return true; // skip the check
}
const std::string syscallFuncName = g_SyscallTable.getSyscallName(this->info.last_syscall);

const ULONGLONG lastCalled = *callStack.begin();
const std::string lastFuncCalled = symbols->funcNameFromAddr(lastCalled);

if (SyscallTable::isSameSyscallFunc(syscallFuncName, lastFuncCalled)) {
return true;
}
if (this->info.ext.wait_reason == Suspended && callStack.size() == 1 && lastFuncCalled == "RtlUserThreadStart" && this->info.last_syscall == 0) {
return true; //normal for suspended threads
}
if (this->info.ext.wait_reason == UserRequest && syscallFuncName == "NtWaitForSingleObject") {
if (lastFuncCalled.rfind("NtQuery", 0) == 0 || lastFuncCalled.rfind("ZwQuery", 0) == 0) {
return true;
}
}
if (syscallFuncName == "NtCallbackReturn") {
const ScannedModule* mod = modulesInfo.findModuleContaining(lastCalled);
if (mod && mod->getModName() == "win32u.dll") return true;
}
#ifdef _SHOW_THREAD_INFO
std::cout << "\n#### TID=" << std::dec <<info.tid << " " << syscallFuncName << " VS " << lastFuncCalled << " DIFFERENT"<< std::endl;
printThreadInfo(info);
std::cout << "STACK:\n";
for (auto itr = callStack.rbegin(); itr != callStack.rend(); ++itr) {
ULONGLONG next_return = *itr;
symbols->dumpSymbolInfo(next_return);
std::cout << "\t";
printResolvedAddr(next_return);
}
std::cout << std::endl;
#endif //_SHOW_THREAD_INFO
return false;
}

size_t pesieve::ThreadScanner::analyzeCallStack(IN const std::vector<ULONGLONG> call_stack, IN OUT ctx_details& cDetails)
{
size_t processedCntr = 0;
Expand Down Expand Up @@ -241,7 +297,11 @@ size_t pesieve::ThreadScanner::fillCallStackInfo(IN HANDLE hProcess, IN HANDLE h
#ifdef _SHOW_THREAD_INFO
std::cout << "\n=== TID " << std::dec << GetThreadId(hThread) << " ===\n";
#endif //_SHOW_THREAD_INFO
return analyzeCallStack(args.callStack, cDetails);
const size_t analyzedCount = analyzeCallStack(args.callStack, cDetails);
if (!cDetails.is_managed) {
cDetails.is_ret_as_syscall = checkReturnAddrIntegrity(args.callStack);
}
return analyzedCount;
}

template <typename PTR_T>
Expand Down Expand Up @@ -345,6 +405,9 @@ void pesieve::ThreadScanner::printThreadInfo(const pesieve::util::thread_info& t
if (threadi.is_extended) {
std::cout << std::hex << "\tSysStart: ";
printResolvedAddr(threadi.ext.sys_start_addr);
if (threadi.last_syscall != INVALID_SYSCALL) {
std::cout << "\tLast Syscall: " << std::hex << threadi.last_syscall << " Func: " << g_SyscallTable.getSyscallName(threadi.last_syscall) << std::endl;
}
std::cout << "\tState: [" << ThreadScanReport::translate_thread_state(threadi.ext.state) << "]";
if (threadi.ext.state == Waiting) {
std::cout << " Reason: [" << ThreadScanReport::translate_wait_reason(threadi.ext.wait_reason) << "] Time: " << threadi.ext.wait_time;
Expand All @@ -354,19 +417,6 @@ void pesieve::ThreadScanner::printThreadInfo(const pesieve::util::thread_info& t
std::cout << "\n";
}

bool get_page_details(HANDLE processHandle, LPVOID start_va, MEMORY_BASIC_INFORMATION &page_info)
{
size_t page_info_size = sizeof(MEMORY_BASIC_INFORMATION);
const SIZE_T out = VirtualQueryEx(processHandle, (LPCVOID)start_va, &page_info, page_info_size);
const bool is_read = (out == page_info_size) ? true : false;
const DWORD error = is_read ? ERROR_SUCCESS : GetLastError();
if (error != ERROR_SUCCESS) {
//nothing to read
return false;
}
return true;
}

bool pesieve::ThreadScanner::fillAreaStats(ThreadScanReport* my_report)
{
if (!my_report) return false;
Expand Down Expand Up @@ -471,8 +521,9 @@ bool pesieve::ThreadScanner::scanRemoteThreadCtx(HANDLE hThread, ThreadScanRepor
}
}

if (this->info.is_extended && info.ext.state == Waiting
&& !cDetails.is_ret_in_frame)
const bool hasEmptyGUI = has_empty_gui_info(tid);

if (this->info.is_extended && info.ext.state == Waiting && !cDetails.is_ret_in_frame)
{
const ULONGLONG ret_addr = cDetails.ret_on_stack;
is_shc = isAddrInShellcode(ret_addr);
Expand All @@ -494,11 +545,23 @@ bool pesieve::ThreadScanner::scanRemoteThreadCtx(HANDLE hThread, ThreadScanRepor
}
}

const bool hasEmptyGUI = has_empty_gui_info(tid);
// other indicators of stack being corrupt:

bool isStackCorrupt = false;

if (this->info.is_extended && !cDetails.is_managed && !cDetails.is_ret_as_syscall)
{
isStackCorrupt = true;
}

if (hasEmptyGUI &&
cDetails.stackFramesCount == 1
&& this->info.is_extended && info.ext.state == Waiting && info.ext.wait_reason == UserRequest)
{
isStackCorrupt = true;
}

if (isStackCorrupt) {
my_report->thread_state = info.ext.state;
my_report->thread_wait_reason = info.ext.wait_reason;
my_report->thread_wait_time = info.ext.wait_time;
Expand Down
5 changes: 4 additions & 1 deletion scanners/thread_scanner.h
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@

namespace pesieve {


//! A report from the thread scan, generated by ThreadScanner
class ThreadScanReport : public ModuleScanReport
{
Expand Down Expand Up @@ -100,13 +101,14 @@ namespace pesieve {
ULONGLONG rbp;
ULONGLONG last_ret; // the last return address on the stack
ULONGLONG ret_on_stack; // the last return address stored on the stack
bool is_ret_as_syscall;
bool is_ret_in_frame;
bool is_managed; // does it contain .NET modules
size_t stackFramesCount;
std::set<ULONGLONG> shcCandidates;

_ctx_details(bool _is64b = false, ULONGLONG _rip = 0, ULONGLONG _rsp = 0, ULONGLONG _rbp = 0, ULONGLONG _ret_addr = 0)
: is64b(_is64b), rip(_rip), rsp(_rsp), rbp(_rbp), last_ret(_ret_addr), ret_on_stack(0), is_ret_in_frame(false),
: is64b(_is64b), rip(_rip), rsp(_rsp), rbp(_rbp), last_ret(_ret_addr), ret_on_stack(0), is_ret_as_syscall(false), is_ret_in_frame(false),
stackFramesCount(0),
is_managed(false)
{
Expand Down Expand Up @@ -143,6 +145,7 @@ namespace pesieve {
bool fetchThreadCtxDetails(IN HANDLE hProcess, IN HANDLE hThread, OUT ctx_details& c);
size_t fillCallStackInfo(IN HANDLE hProcess, IN HANDLE hThread, IN LPVOID ctx, IN OUT ctx_details& cDetails);
size_t analyzeCallStack(IN const std::vector<ULONGLONG> stack_frame, IN OUT ctx_details& cDetails);
bool checkReturnAddrIntegrity(IN const std::vector<ULONGLONG>& callStack);
bool fillAreaStats(ThreadScanReport* my_report);
bool reportSuspiciousAddr(ThreadScanReport* my_report, ULONGLONG susp_addr);

Expand Down
4 changes: 2 additions & 2 deletions stats/entropy_stats.h
Original file line number Diff line number Diff line change
Expand Up @@ -32,11 +32,9 @@ namespace pesieve {
entropy = stats::calcShannonEntropy(histogram, area_size);
}

std::map<BYTE, size_t> histogram;
double entropy;

protected:

const virtual void fieldsToJSON(std::stringstream& outs, size_t level)
{
OUT_PADDED(outs, level, "\"area_start\" : ");
Expand All @@ -49,6 +47,8 @@ namespace pesieve {
outs << std::dec << entropy;
}

std::map<BYTE, size_t> histogram;

friend class AreaStatsCalculator;

}; // AreaStats
Expand Down
16 changes: 16 additions & 0 deletions utils/process_symbols.h
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,22 @@ class ProcessSymbolsManager

//---

std::string funcNameFromAddr(const ULONG_PTR addr)
{
if (!isInit) return "";

CHAR buffer[sizeof(SYMBOL_INFO) + MAX_SYM_NAME] = { 0 };
PSYMBOL_INFO pSymbol = (PSYMBOL_INFO)buffer;
pSymbol->SizeOfStruct = sizeof(SYMBOL_INFO);
pSymbol->MaxNameLen = MAX_SYM_NAME;

DWORD64 Displacement = 0;
if (!SymFromAddr(hProcess, addr, &Displacement, pSymbol)) {
return "";
}
return pSymbol->Name;
}

bool dumpSymbolInfo(const ULONG_PTR addr)
{
if (!isInit) return false;
Expand Down
101 changes: 101 additions & 0 deletions utils/syscall_extractor.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
#include "syscall_extractor.h"

#include <windows.h>
#include <peconv.h> // include libPeConv header
#include <iostream>
#include "process_util.h"

namespace pesieve {
namespace util {

bool isSyscallFunc(const std::string& funcName)
{
std::string prefix("Nt");
if (funcName.size() < (prefix.size() + 1)) {
return false;
}
if (funcName.compare(0, prefix.size(), prefix) != 0) {
return false;
}
char afterPrefix = funcName.at(prefix.size());
if (afterPrefix >= 'A' && afterPrefix <= 'Z') {
// the name of the function after the Nt prefix will start in uppercase,
// syscalls are in functions like: NtUserSetWindowLongPtr, but not: NtdllDefWindowProc_A
return true;
}
return false;
}

size_t extract_syscalls(BYTE* pe_buf, size_t pe_size, std::map<DWORD, std::string>& syscallToName, size_t startID = 0)
{
std::vector<std::string> names_list;
if (!peconv::get_exported_names(pe_buf, names_list)) {
return 0;
}

std::map<DWORD, std::string> sys_functions;
for (auto itr = names_list.begin(); itr != names_list.end(); ++itr) {
std::string funcName = *itr;
if (isSyscallFunc(funcName)) {
ULONG_PTR va = (ULONG_PTR)peconv::get_exported_func(pe_buf, funcName.c_str());
if (!va) continue;

DWORD rva = DWORD(va - (ULONG_PTR)pe_buf);
sys_functions[rva] = funcName;
}
}
size_t id = startID;
for (auto itr = sys_functions.begin(); itr != sys_functions.end(); ++itr) {
std::string funcName = itr->second;
syscallToName[id++] = funcName;
}
return id;
}

size_t extract_from_dll(IN const std::string& path, size_t startSyscallID, OUT std::map<DWORD, std::string>& syscallToName)
{
size_t bufsize = 0;
BYTE* buffer = peconv::load_pe_module(path.c_str(), bufsize, false, false);

if (!buffer) {
std::cerr << "Failed to load the PE: " << path << "\n";
return 0;
}

size_t extracted_count = extract_syscalls(buffer, bufsize, syscallToName, startSyscallID);
peconv::free_pe_buffer(buffer);

if (!extracted_count) {
std::cerr << "No syscalls extracted from: " << path << "\n";
}
return extracted_count;
}

}; //namespace util

}; //namespace pesieve

size_t pesieve::util::extract_syscall_table(OUT std::map<DWORD, std::string>& syscallToName)
{
PVOID old_val = NULL;
pesieve::util::wow64_disable_fs_redirection(&old_val);

std::stringstream outs;
size_t extracted_count = 0;

char ntdll_path[MAX_PATH] = { 0 };
ExpandEnvironmentStringsA("%SystemRoot%\\system32\\ntdll.dll", ntdll_path, MAX_PATH);
extracted_count += extract_from_dll(ntdll_path, 0, syscallToName);

char win32u_path[MAX_PATH] = { 0 };
ExpandEnvironmentStringsA("%SystemRoot%\\system32\\win32u.dll", win32u_path, MAX_PATH);
extracted_count += extract_from_dll(win32u_path, 0x1000, syscallToName);

pesieve::util::wow64_revert_fs_redirection(&old_val);

if (!extracted_count) {
std::cerr << "Failed to extract syscalls.\n";
return 0;
}
return syscallToName.size();
}
Loading

0 comments on commit df89282

Please sign in to comment.