PE-sieve
Scans all running processes. Recognizes and dumps a variety of potentially malicious implants (replaced/implanted PEs, shellcodes, hooks, in-memory patches).
All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Pages
thread_scanner.h
Go to the documentation of this file.
1#pragma once
2
3#include <windows.h>
4
5#include "module_scanner.h"
8#include "../stats/stats.h"
10#include <vector>
11
12namespace pesieve {
13
24
25 inline std::string indicator_to_str(const ThSusIndicator& indicator)
26 {
27 switch (indicator) {
28 case THI_NONE: return "NONE";
29 case THI_SUS_START: return "SUS_START";
30 case THI_SUS_IP: return "SUS_IP";
31 case THI_SUS_RET: return "SUS_RET";
32 case THI_SUS_CALLSTACK_SHC: return "SUS_CALLSTACK_SHC";
33 case THI_SUS_CALLS_INTEGRITY: return "SUS_CALLS_INTEGRITY";
34 case THI_SUS_CALLSTACK_CORRUPT: return "SUS_CALLSTACK_CORRUPT";
35 }
36 return "";
37 }
38
40 typedef struct _ctx_details {
41 bool is64b;
42 ULONGLONG rip;
43 ULONGLONG rsp;
44 ULONGLONG rbp;
45 ULONGLONG last_ret; // the last return address on the stack
46 ULONGLONG ret_on_stack; // the last return address stored on the stack
49 bool is_managed; // does it contain .NET modules
50 std::vector<ULONGLONG> callStack;
51
52 _ctx_details(bool _is64b = false, ULONGLONG _rip = 0, ULONGLONG _rsp = 0, ULONGLONG _rbp = 0, ULONGLONG _ret_addr = 0)
53 : is64b(_is64b), rip(_rip), rsp(_rsp), rbp(_rbp), last_ret(_ret_addr), ret_on_stack(0), is_ret_as_syscall(true), is_ret_in_frame(true),
54 is_managed(false)
55 {
56 }
57
58 void init(bool _is64b = false, ULONGLONG _rip = 0, ULONGLONG _rsp = 0, ULONGLONG _rbp = 0, ULONGLONG _ret_addr = 0)
59 {
60 this->is64b = _is64b;
61 this->rip = _rip;
62 this->rsp = _rsp;
63 this->rbp = _rbp;
64 this->last_ret = _ret_addr;
65 }
66
68
71 {
72 public:
73 static const DWORD THREAD_STATE_UNKNOWN = (-1);
74 static const DWORD THREAD_STATE_WAITING = 5;
75
76 static std::string translate_thread_state(DWORD thread_state);
77 static std::string translate_wait_reason(DWORD thread_wait_reason);
78
79 //---
80
81 ThreadScanReport(DWORD _tid)
82 : ModuleScanReport(0, 0),
83 tid(_tid),
87 {
88 }
89
90 const virtual void callstackToJSON(std::stringstream& outs, size_t level, const pesieve::t_json_level& jdetails)
91 {
92 bool printCallstack = (jdetails >= JSON_DETAILS) ? true : false;
93 if (this->indicators.find(THI_SUS_CALLSTACK_CORRUPT) != this->indicators.end()) {
94 printCallstack = true;
95 }
96 if (this->indicators.find(THI_SUS_CALLSTACK_SHC) != this->indicators.end()) {
97 printCallstack = true;
98 }
99 OUT_PADDED(outs, level, "\"stack_ptr\" : ");
100 outs << "\"" << std::hex << stack_ptr << "\"";
101 if (cDetails.callStack.size()) {
102 outs << ",\n";
103 OUT_PADDED(outs, level, "\"frames_count\" : ");
104 outs << std::dec << cDetails.callStack.size();
105 if (printCallstack) {
106 outs << ",\n";
107 OUT_PADDED(outs, level, "\"frames\" : [");
108 for (auto itr = cDetails.callStack.rbegin(); itr != cDetails.callStack.rend(); ++itr) {
109 if (itr != cDetails.callStack.rbegin()) {
110 outs << ", ";
111 }
112 const ULONGLONG addr = *itr;
113 outs << "\"" << std::hex << addr;
114 auto sItr = this->addrToSymbol.find(addr);
115 if (sItr != this->addrToSymbol.end()) {
116 outs << ";" << sItr->second;
117 }
118 outs << "\"";
119
120 }
121 outs << "]";
122 }
123 }
124 }
125
126 const bool moduleInfoToJSON(std::stringstream& outs, size_t level, const pesieve::t_json_level& jdetails)
127 {
128 if (!this->module) {
129 return false;
130 }
131 outs << ",\n";
132 OUT_PADDED(outs, level, "\"module\" : ");
133 outs << "\"" << std::hex << (ULONGLONG)module << "\"";
134 if (moduleSize) {
135 outs << ",\n";
136 OUT_PADDED(outs, level, "\"module_size\" : ");
137 outs << "\"" << std::hex << (ULONGLONG)moduleSize << "\"";
138 }
139 outs << ",\n";
140 OUT_PADDED(outs, level, "\"protection\" : ");
141 outs << "\"" << std::hex << protection << "\"";
142 if (stats.isFilled()) {
143 outs << ",\n";
144 stats.toJSON(outs, level);
145 }
146 return true;
147 }
148
149 const bool threadInfoToJSON(std::stringstream& outs, size_t level, const pesieve::t_json_level& jdetails)
150 {
151 OUT_PADDED(outs, level, "\"state\" : ");
153 outs << "\"" << "UNKNOWN" << "\"";
154 }
155 else {
156 outs << "\"" << translate_thread_state(thread_state) << "\"";
157 }
159 outs << ",\n";
160 OUT_PADDED(outs, level, "\"wait_reason\" : ");
161 outs << "\"" << translate_wait_reason(thread_wait_reason) << "\"";
162 }
163 if (stack_ptr) {
164 outs << ",\n";
165 OUT_PADDED(outs, level, "\"callstack\" : {\n");
166 callstackToJSON(outs, level + 1, jdetails);
167 outs << "\n";
168 OUT_PADDED(outs, level, "}");
169 }
170 bool showLastCall = (jdetails >= JSON_DETAILS) ? true : false;
171 if ((this->indicators.find(THI_SUS_CALLS_INTEGRITY) != this->indicators.end()) ||
172 (this->indicators.find(THI_SUS_CALLSTACK_CORRUPT) != this->indicators.end()) )
173 {
174 showLastCall = true;
175 }
176 if (showLastCall) {
177 if (!this->lastSyscall.empty()) {
178 outs << ",\n";
179 OUT_PADDED(outs, level, "\"last_sysc\" : ");
180 outs << "\"" << this->lastSyscall << "\"";
181 }
182 if (!this->lastFunction.empty() && (this->lastFunction != this->lastSyscall)) {
183 outs << ",\n";
184 OUT_PADDED(outs, level, "\"last_func\" : ");
185 outs << "\"" << this->lastFunction << "\"";
186 }
187 }
188 outs << "\n";
189 return true;
190 }
191
192 const bool indicatorsToJSON(std::stringstream& outs, size_t level, const pesieve::t_json_level& jdetails)
193 {
194 OUT_PADDED(outs, level, "\"indicators\" : [");
195 for (auto itr = indicators.begin(); itr != indicators.end(); ++itr) {
196 if (itr != indicators.begin()) {
197 outs << ", ";
198 }
199 outs << "\"" << indicator_to_str(*itr) << "\"";
200 }
201 outs << "]";
202 return true;
203 }
204
205 const virtual void fieldsToJSON(std::stringstream &outs, size_t level, const pesieve::t_json_level &jdetails)
206 {
207 ElementScanReport::_toJSON(outs, level);
208 outs << ",\n";
209 OUT_PADDED(outs, level, "\"thread_id\" : ");
210 outs << std::dec << tid;
211 outs << ",\n";
212 OUT_PADDED(outs, level, "\"thread_info\" : {\n");
213 threadInfoToJSON(outs, level + 1, jdetails);
214 OUT_PADDED(outs, level, "}");
215 outs << ",\n";
216 indicatorsToJSON(outs, level, jdetails);
217
218 if (susp_addr) {
219 outs << ",\n";
220 if (this->module && this->moduleSize) {
221 OUT_PADDED(outs, level, "\"susp_addr\" : ");
222 }
223 else {
224 OUT_PADDED(outs, level, "\"susp_return_addr\" : ");
225 }
226 outs << "\"" << std::hex << susp_addr << "\"";
227 }
228 moduleInfoToJSON(outs, level, jdetails);
229 }
230
231 const virtual bool toJSON(std::stringstream& outs, size_t level, const pesieve::t_json_level &jdetails)
232 {
233 OUT_PADDED(outs, level, "\"thread_scan\" : {\n");
234 fieldsToJSON(outs, level + 1, jdetails);
235 outs << "\n";
236 OUT_PADDED(outs, level, "}");
237 return true;
238 }
239
240 DWORD tid;
241 ULONGLONG susp_addr;
243 ULONGLONG stack_ptr;
247
248 std::string lastSyscall;
249 std::string lastFunction;
250
252 std::map<ULONGLONG, std::string> addrToSymbol;
253 std::set<ULONGLONG> shcCandidates;
254 std::set<ThSusIndicator> indicators;
255
258 };
259
263 public:
264 ThreadScanner(HANDLE hProc, bool _isReflection, bool _isManaged, const util::thread_info& _info, ModulesInfo& _modulesInfo, peconv::ExportsMapper* _exportsMap, ProcessSymbolsManager* _symbols)
265 : ProcessFeatureScanner(hProc), isReflection(_isReflection), isManaged(_isManaged),
266 info(_info), modulesInfo(_modulesInfo), exportsMap(_exportsMap), symbols(_symbols)
267 {
268 }
269
270 virtual ThreadScanReport* scanRemote();
271
272 protected:
273 void initReport(ThreadScanReport& my_report);
275 static std::string choosePreferredFunctionName(const std::string& dbgSymbol, const std::string& manualSymbol);
276
277 bool scanRemoteThreadCtx(HANDLE hThread, ThreadScanReport& my_report);
278 bool fetchThreadCtxDetails(IN HANDLE hProcess, IN HANDLE hThread, OUT ThreadScanReport& my_report);
279
280 bool isAddrInNamedModule(ULONGLONG addr);
281 void printThreadInfo(const util::thread_info& threadi);
282 std::string resolveLowLevelFuncName(IN const ULONGLONG addr, OUT OPTIONAL size_t* disp = nullptr);
283 std::string resolveAddrToString(IN ULONGLONG addr);
284 bool printResolvedAddr(const ULONGLONG addr);
285 size_t fillCallStackInfo(IN HANDLE hProcess, IN HANDLE hThread, IN LPVOID ctx, IN OUT ThreadScanReport& my_report);
286 size_t analyzeCallStackInfo(IN OUT ThreadScanReport& my_report);
287 size_t _analyzeCallStack(IN OUT ctx_details& cDetails, OUT IN std::set<ULONGLONG>& shcCandidates);
288
289 bool checkReturnAddrIntegrity(IN const std::vector<ULONGLONG>& callStack, IN OUT ThreadScanReport& my_report);
290
291 bool fillAreaStats(ThreadScanReport* my_report);
292 bool reportSuspiciousAddr(ThreadScanReport* my_report, ULONGLONG susp_addr);
293 bool filterDotNet(ThreadScanReport& my_report);
294
299 peconv::ExportsMapper* exportsMap;
301 };
302
303}; //namespace pesieve
virtual const bool _toJSON(std::stringstream &outs, size_t level=JSON_LEVEL, const pesieve::t_json_level &jdetails=JSON_BASIC)
ModuleScanReport(HMODULE _module, size_t _moduleSize, t_scan_status _status=SCAN_NOT_SUSPICIOUS)
A container of all the process modules that were scanned.
ProcessFeatureScanner(HANDLE _processHandle)
A report from the thread scan, generated by ThreadScanner.
virtual const void callstackToJSON(std::stringstream &outs, size_t level, const pesieve::t_json_level &jdetails)
std::set< ThSusIndicator > indicators
std::map< ULONGLONG, std::string > addrToSymbol
virtual const void fieldsToJSON(std::stringstream &outs, size_t level, const pesieve::t_json_level &jdetails)
const bool moduleInfoToJSON(std::stringstream &outs, size_t level, const pesieve::t_json_level &jdetails)
static std::string translate_wait_reason(DWORD thread_wait_reason)
const bool threadInfoToJSON(std::stringstream &outs, size_t level, const pesieve::t_json_level &jdetails)
static const DWORD THREAD_STATE_UNKNOWN
const bool indicatorsToJSON(std::stringstream &outs, size_t level, const pesieve::t_json_level &jdetails)
static const DWORD THREAD_STATE_WAITING
std::set< ULONGLONG > shcCandidates
virtual const bool toJSON(std::stringstream &outs, size_t level, const pesieve::t_json_level &jdetails)
static std::string translate_thread_state(DWORD thread_state)
bool checkReturnAddrIntegrity(IN const std::vector< ULONGLONG > &callStack, IN OUT ThreadScanReport &my_report)
virtual ThreadScanReport * scanRemote()
size_t analyzeCallStackInfo(IN OUT ThreadScanReport &my_report)
bool reportSuspiciousAddr(ThreadScanReport *my_report, ULONGLONG susp_addr)
std::string resolveAddrToString(IN ULONGLONG addr)
std::string resolveLowLevelFuncName(IN const ULONGLONG addr, OUT OPTIONAL size_t *disp=nullptr)
size_t _analyzeCallStack(IN OUT ctx_details &cDetails, OUT IN std::set< ULONGLONG > &shcCandidates)
void printThreadInfo(const util::thread_info &threadi)
bool fillAreaStats(ThreadScanReport *my_report)
void reportResolvedCallstack(ThreadScanReport &my_report)
ThreadScanner(HANDLE hProc, bool _isReflection, bool _isManaged, const util::thread_info &_info, ModulesInfo &_modulesInfo, peconv::ExportsMapper *_exportsMap, ProcessSymbolsManager *_symbols)
bool printResolvedAddr(const ULONGLONG addr)
bool filterDotNet(ThreadScanReport &my_report)
void initReport(ThreadScanReport &my_report)
peconv::ExportsMapper * exportsMap
const util::thread_info & info
bool isAddrInNamedModule(ULONGLONG addr)
static std::string choosePreferredFunctionName(const std::string &dbgSymbol, const std::string &manualSymbol)
ProcessSymbolsManager * symbols
bool scanRemoteThreadCtx(HANDLE hThread, ThreadScanReport &my_report)
bool fetchThreadCtxDetails(IN HANDLE hProcess, IN HANDLE hThread, OUT ThreadScanReport &my_report)
size_t fillCallStackInfo(IN HANDLE hProcess, IN HANDLE hThread, IN LPVOID ctx, IN OUT ThreadScanReport &my_report)
#define OUT_PADDED(stream, field_size, str)
Definition format_util.h:12
struct pesieve::util::_thread_info thread_info
struct pesieve::_ctx_details ctx_details
A custom structure keeping a fragment of a thread context.
std::string indicator_to_str(const ThSusIndicator &indicator)
enum pesieve::ThSusIndicator _ThSusIndicator
@ THI_SUS_CALLSTACK_SHC
@ THI_SUS_CALLSTACK_CORRUPT
@ THI_SUS_CALLS_INTEGRITY
@ JSON_DETAILS
include the basic list patches in the main JSON report
void init(bool _is64b=false, ULONGLONG _rip=0, ULONGLONG _rsp=0, ULONGLONG _rbp=0, ULONGLONG _ret_addr=0)
std::vector< ULONGLONG > callStack
_ctx_details(bool _is64b=false, ULONGLONG _rip=0, ULONGLONG _rsp=0, ULONGLONG _rbp=0, ULONGLONG _ret_addr=0)