PE-sieve
Scans all running processes. Recognizes and dumps a variety of potentially malicious implants (replaced/implanted PEs, shellcodes, hooks, in-memory patches).
Loading...
Searching...
No Matches
pesieve.py
Go to the documentation of this file.
1#!/usr/bin/env python3
2# Requires Python 3.9+
3
4import ctypes
5import os
6
7PESIEVE_MIN_VER = 0x040102 # minimal version of the PE-sieve DLL to work with this wrapper
8PESIEVE_MAX_VER = 0x040102 # maximal version of the PE-sieve DLL to work with this wrapper
9
10ERROR_SCAN_FAILURE = -1
11MAX_PATH = 260
12
13def version_to_str(version_val):
14 major = (version_val >> 24) & 0xFF
15 minor = (version_val >> 16) & 0xFF
16 patch = (version_val >> 8) & 0xFF
17 build = version_val & 0xFF
18 return f"{major}.{minor}.{patch}.{build}"
19
20
21
22class t_output_filter(ctypes.c_int):
23 OUT_FULL = 0
24 OUT_NO_DUMPS = 1
25 OUT_NO_DIR = 2
26 OUT_FILTERS_COUNT = 3
27
28class t_shellc_mode(ctypes.c_int):
29 SHELLC_NONE = 0
30 SHELLC_PATTERNS = 1
31 SHELLC_STATS = 2
32 SHELLC_PATTERNS_OR_STATS = 3
33 SHELLC_PATTERNS_AND_STATS = 4
34 SHELLC_COUNT = 5
35
36class t_obfusc_mode(ctypes.c_int):
37 OBFUSC_NONE = 0
38 OBFUSC_STRONG_ENC = 1
39 OBFUSC_WEAK_ENC = 2
40 OBFUSC_ANY = 3
41 OBFUSC_COUNT = 4
42
43class t_imprec_mode(ctypes.c_int):
44 PE_IMPREC_NONE = 0
45 PE_IMPREC_AUTO = 1
46 PE_IMPREC_UNERASE = 2
47 PE_IMPREC_REBUILD0 = 3
48 PE_IMPREC_REBUILD1 = 4
49 PE_IMPREC_REBUILD2 = 5
50 PE_IMPREC_MODES_COUNT = 6
51
52class t_dump_mode(ctypes.c_int):
53 PE_DUMP_AUTO = 0
54 PE_DUMP_VIRTUAL = 1
55 PE_DUMP_UNMAP = 2
56 PE_DUMP_REALIGN = 3
57 PE_DUMP_MODES_COUNT = 4
58
59class t_iat_scan_mode(ctypes.c_int):
60 PE_IATS_NONE = 0
61 PE_IATS_CLEAN_SYS_FILTERED = 1
62 PE_IATS_ALL_SYS_FILTERED = 2
63 PE_IATS_UNFILTERED = 3
64 PE_IATS_MODES_COUNT = 4
65
66class t_dotnet_policy(ctypes.c_int):
67 PE_DNET_NONE = 0
68 PE_DNET_SKIP_MAPPING = 1
69 PE_DNET_SKIP_SHC = 2
70 PE_DNET_SKIP_HOOKS = 3
71 PE_DNET_SKIP_ALL = 4
72 PE_DNET_COUNT = 5
73
74class t_data_scan_mode(ctypes.c_int):
75 PE_DATA_NO_SCAN = 0
76 PE_DATA_SCAN_DOTNET = 1
77 PE_DATA_SCAN_NO_DEP = 2
78 PE_DATA_SCAN_ALWAYS = 3
79 PE_DATA_SCAN_INACCESSIBLE = 4
80 PE_DATA_SCAN_INACCESSIBLE_ONLY = 5
81 PE_DATA_COUNT = 6
82
83class t_json_level(ctypes.c_int):
84 JSON_BASIC = 0
85 JSON_DETAILS = 1
86 JSON_DETAILS2 = 2
87 JSON_LVL_COUNT = 3
88
89class t_results_filter(ctypes.c_int):
90 SHOW_NONE = 0
91 SHOW_ERRORS = 1
92 SHOW_NOT_SUSPICIOUS = 2
93 SHOW_SUSPICIOUS = 4
94 SHOW_SUSPICIOUS_AND_ERRORS = 5
95 SHOW_SUCCESSFUL_ONLY = 6
96 SHOW_ALL = 7
97
98class t_report_type(ctypes.c_int):
99 REPORT_NONE = 0
100 REPORT_SCANNED = 1
101 REPORT_DUMPED = 2
102 REPORT_ALL = 3
103
104class PARAM_STRING(ctypes.Structure):
105 _fields_ = [
106 ('length', ctypes.c_ulong),
107 ('buffer', ctypes.c_char_p)
108 ]
109
110class t_params(ctypes.Structure):
111 _fields_ = [
112 ('pid', ctypes.c_ulong),
113 ('dotnet_policy', t_dotnet_policy),
114 ('imprec_mode', t_imprec_mode),
115 ('quiet', ctypes.c_bool),
116 ('out_filter', t_output_filter),
117 ('no_hooks', ctypes.c_bool),
118 ('shellcode', t_shellc_mode),
119 ('obfuscated', t_obfusc_mode),
120 ('threads', ctypes.c_bool),
121 ('iat', t_iat_scan_mode),
122 ('data', t_data_scan_mode),
123 ('minidump', ctypes.c_bool),
124 ('rebase', ctypes.c_bool),
125 ('dump_mode', t_dump_mode),
126 ('json_output', ctypes.c_bool),
127 ('make_reflection', ctypes.c_bool),
128 ('use_cache', ctypes.c_bool),
129 ('json_lvl', t_json_level),
130 ('results_filter', t_results_filter),
131 ('output_dir', ctypes.c_char * (MAX_PATH + 1)),
132 ('modules_ignored', PARAM_STRING),
133 ('pattern_file', PARAM_STRING)
134 ]
135
136class t_report(ctypes.Structure):
137 _fields_ = [
138 ('pid', ctypes.c_ulong),
139 ('is_managed', ctypes.c_bool),
140 ('is_64bit', ctypes.c_bool),
141 ('is_reflection', ctypes.c_bool),
142 ('scanned', ctypes.c_ulong),
143 ('suspicious', ctypes.c_ulong),
144 ('replaced', ctypes.c_ulong),
145 ('hdr_mod', ctypes.c_ulong),
146 ('unreachable_file', ctypes.c_ulong),
147 ('patched', ctypes.c_ulong),
148 ('iat_hooked', ctypes.c_ulong),
149 ('implanted', ctypes.c_ulong),
150 ('implanted_pe', ctypes.c_ulong),
151 ('implanted_shc', ctypes.c_ulong),
152 ('other', ctypes.c_ulong),
153 ('skipped', ctypes.c_ulong),
154 ('errors', ctypes.c_ulong)
155 ]
156
157lib = None
158PESieve_version = None
159
160def init():
161 global lib
162 global PESieve_version
163 ptr_size = ctypes.sizeof(ctypes.c_voidp)
164 if ptr_size == 4:
165 pesieve_dll = "pe-sieve32.dll"
166 else:
167 pesieve_dll = "pe-sieve64.dll"
168
169 try:
170 pesieve_path = os.path.abspath(os.getcwd()) + os.path.sep + pesieve_dll
171 lib = ctypes.cdll.LoadLibrary(pesieve_path)
172 except FileNotFoundError:
173 if 'PESIEVE_DIR' in os.environ:
174 pesieve_path = os.environ.get('PESIEVE_DIR') + os.path.sep + pesieve_dll
175 lib = ctypes.cdll.LoadLibrary(pesieve_path)
176 else:
177 raise
178
179 PESieve_version = ctypes.c_uint32.in_dll(lib, "PESieve_version").value
180 if (PESieve_version < PESIEVE_MIN_VER or PESieve_version > PESIEVE_MAX_VER):
181 dll_version_str = version_to_str(PESieve_version)
182 exception_msg = f"Version mismatch: the PE-sieve.dll version ({dll_version_str}) doesn't match the bindings version"
183 raise Exception(exception_msg)
184
185 lib.PESieve_scan.argtypes = [ctypes.POINTER(t_params)]
186 lib.PESieve_scan.restype = t_report
187
188 lib.PESieve_scan_ex.argtypes = [
189 ctypes.POINTER(t_params),
190 t_report_type,
191 ctypes.c_char_p,
192 ctypes.c_size_t,
193 ctypes.POINTER(ctypes.c_size_t),
194 ]
195 lib.PESieve_scan_ex.restype = t_report
196
198 if not lib:
199 init()
200 lib.PESieve_help()
201
202def PESieve_scan(params: t_params) -> t_report:
203 if not lib:
204 init()
205 if not isinstance(params, t_params):
206 raise TypeError("params must be t_params")
207
208 return lib.PESieve_scan(ctypes.byref(params))
209
210def PESieve_scan_ex(params: t_params, rtype: t_report_type, buf_size: int) -> tuple[t_report, str, int]:
211 if not lib:
212 init()
213 if not isinstance(params, t_params):
214 raise TypeError("params must be t_params")
215 if buf_size < 0:
216 raise ValueError("buf_size must be >= 0")
217
218 out_size = ctypes.c_size_t(0)
219
220 if buf_size > 0:
221 json_buf = ctypes.create_string_buffer(buf_size)
222 report = lib.PESieve_scan_ex(
223 ctypes.byref(params),
224 rtype,
225 ctypes.cast(json_buf, ctypes.c_char_p),
226 ctypes.c_size_t(buf_size),
227 ctypes.byref(out_size)
228 )
229 json_str = json_buf.value.decode("utf-8", errors="replace")
230 else:
231 report = lib.PESieve_scan_ex(
232 ctypes.byref(params),
233 rtype,
234 None,
235 ctypes.c_size_t(0),
236 ctypes.byref(out_size)
237 )
238 json_str = ""
239
240 return (report, json_str, out_size.value)
241
242def PESieve_scan_ex_auto(params: t_params, rtype: t_report_type):
243 report, json_str, needed = PESieve_scan_ex(params, rtype, 0)
244 if needed == 0:
245 return report, ""
246 report, json_str, needed2 = PESieve_scan_ex(params, rtype, needed)
247 return report, json_str
PESieve_help()
Definition pesieve.py:197
version_to_str(version_val)
Definition pesieve.py:13
tuple[t_report, str, int] PESieve_scan_ex(t_params params, t_report_type rtype, int buf_size)
Definition pesieve.py:210
t_report PESieve_scan(t_params params)
Definition pesieve.py:202
PESieve_scan_ex_auto(t_params params, t_report_type rtype)
Definition pesieve.py:242