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
patch_analyzer.cpp
Go to the documentation of this file.
1#include "patch_analyzer.h"
2//---
3using namespace pesieve;
4
5template <typename DELTA_T>
6ULONGLONG pesieve::PatchAnalyzer::getJmpDestAddr(ULONGLONG currVA, int instrLen, DELTA_T lVal)
7{
8 int delta = instrLen + int(lVal);
9 ULONGLONG addr = currVA + delta;
10 return addr;
11}
12
13size_t pesieve::PatchAnalyzer::parseShortJmp(PatchList::Patch &patch, PBYTE patch_ptr, ULONGLONG patch_va)
14{
15 const size_t instr_size = 2;
16 if (!peconv::validate_ptr(this->patchedCode, this->codeSize, patch_ptr, instr_size)) {
17 return 0;
18 }
19 BYTE *lval = (BYTE*)((ULONGLONG)patch_ptr + 1);
20 ULONGLONG addr = getJmpDestAddr<BYTE>(patch_va, instr_size, (*lval));
21
22 patch.setHookTarget(addr);
23 return instr_size;
24}
25
26size_t pesieve::PatchAnalyzer::parseJmp(PatchList::Patch &patch, PBYTE patch_ptr, ULONGLONG patch_va)
27{
28 const size_t instr_size = 5;
29 if (!peconv::validate_ptr(this->patchedCode, this->codeSize, patch_ptr, instr_size)) {
30 return 0;
31 }
32 DWORD *lval = (DWORD*)((ULONGLONG) patch_ptr + 1);
33 ULONGLONG addr = getJmpDestAddr<DWORD>(patch_va, instr_size, (*lval));
34
35 patch.setHookTarget(addr);
36 return instr_size;
37}
38
39size_t pesieve::PatchAnalyzer::parseJmpViaAddr(PatchList::Patch &patch, PBYTE patch_ptr, ULONGLONG patch_va)
40{
41 const size_t instr_size = 6;
42 ULONGLONG addr = NULL;
43
44 DWORD *lval = (DWORD*)((ULONGLONG)patch_ptr + 2);
45 if (!isModule64bit) { //32bit
46 patch.setHookTarget(*lval, false);
47 }
48 else { //64bit
49 ULONGLONG addr = getJmpDestAddr<DWORD>(patch_va, instr_size, (*lval));
50 patch.setHookTarget(addr, false);
51 }
52 return instr_size;
53}
54
55size_t pesieve::PatchAnalyzer::parseMovJmp(PatchList::Patch &patch, PBYTE patch_ptr, bool is_long)
56{
57 size_t mov_instr_len = is_long ? 9 : 5;
58 if (!peconv::validate_ptr(this->patchedCode, this->codeSize, patch_ptr, mov_instr_len + 2)) {
59 return 0;
60 }
61
62 PBYTE jmp_ptr = patch_ptr + mov_instr_len; // next instruction
63 if (is64Modifier(*patch_ptr)) {
64 patch_ptr++;
65 jmp_ptr++;
66 mov_instr_len++; // add length of modifier
67 }
68
69 DWORD reg_id0 = patch_ptr[0] - 0xB8;
70
71 // before call/jmp there can be also the modifier...
72 if (is64Modifier(*jmp_ptr)) {
73 jmp_ptr++;
74 mov_instr_len++; // add length of modifier
75 }
76 if (!peconv::validate_ptr(this->patchedCode, this->codeSize, jmp_ptr, 2)) {
77 return 0;
78 }
79 DWORD reg_id1 = 0;
80 if (jmp_ptr[0] == 0xFF && jmp_ptr[1] >= 0xE0 && jmp_ptr[1] <= 0xEF ) { // jmp reg
81 //jmp reg
82 reg_id1 = jmp_ptr[1] - 0xE0;
83 } else if (jmp_ptr[0] == 0xFF && jmp_ptr[1] >= 0xD0 && jmp_ptr[1] <= 0xDF ) { // call reg
84 //jmp reg
85 reg_id1 = jmp_ptr[1] - 0xD0;
86 } else {
87#ifdef _DEBUG
88 std::cerr << "It is not MOV->JMP" << std::hex << (DWORD)jmp_ptr[0] << std::endl;
89#endif
90 return NULL;
91 }
92 //TODO: take into account also modifiers
93 if (reg_id1 != reg_id0) {
94#ifdef _DEBUG
95 std::cerr << "MOV->JMP : reg mismatch" << std::endl;
96#endif
97 return NULL;
98 }
99 size_t patch_size = mov_instr_len;
100 ULONGLONG addr = NULL;
101 if (!is_long) { //32bit
102 DWORD *lval = (DWORD*)((ULONGLONG) patch_ptr + 1);
103 addr = *lval;
104 } else { //64bit
105 ULONGLONG *lval = (ULONGLONG*)((ULONGLONG) patch_ptr + 1);
106 addr = *lval;
107 }
108 patch_size += 2; //add jump reg size
109 patch.setHookTarget(addr);
110#ifdef _DEBUG
111 std::cout << "----> Target: " << std::hex << addr << std::endl;
112#endif
113 return patch_size;
114}
115
117{
118 size_t instr_size = 5;
119 if (!peconv::validate_ptr(this->patchedCode, this->codeSize, patch_ptr, instr_size + 1)) {
120 return 0;
121 }
122 PBYTE ret_ptr = patch_ptr + instr_size; // next instruction
123 if (ret_ptr[0] != 0xC3) {
124 return NULL; // this is not push->ret
125 }
126 instr_size++;
127 DWORD *lval = (DWORD*)((ULONGLONG) patch_ptr + 1);
128 patch.setHookTarget(*lval);
129 return instr_size;
130}
131
133{
134 if (!isModule64bit) return false;
135 if (op >= 0x40 && op <= 0x4F) { // modifier
136 return true;
137 }
138 return false;
139}
140
142{
143 if (!isModule64bit) return false;
144 if (op >= 0x48 && op <= 0x4F) { // modifier
145 return true;
146 }
147 return false;
148}
149
150size_t pesieve::PatchAnalyzer::_analyzeHook(PatchList::Patch &patch, PBYTE patch_ptr, ULONGLONG patch_va)
151{
152 BYTE op = patch_ptr[0];
153 if (op == OP_JMP || op == OP_CALL_DWORD) {
154 return parseJmp(patch, patch_ptr, patch_va);
155 }
156 if (op == OP_SHORTJMP) {
157 return parseShortJmp(patch, patch_ptr, patch_va);
158 }
159 if (op == OP_PUSH_DWORD) {
160 return parsePushRet(patch, patch_ptr);
161 }
162 if (op == OP_JMP_VIA_ADDR_B1 && patch_ptr[1] == OP_JMP_VIA_ADDR_B2) {
163 return parseJmpViaAddr(patch, patch_ptr, patch_va);
164 }
165
166 bool is_long = false;
167 if (is64Modifier(op)) { // mov modifier
168 if (isLongModifier(op)) {
169 is_long = true;
170 }
171 op = patch_ptr[1];
172 }
173
174 if (op >= 0xB8 && op <= 0xBF) { // is mov
175 return parseMovJmp(patch, patch_ptr, is_long);
176 }
177 return 0;
178}
179
181{
182 if (this->relocs.find(patch.startRva) == this->relocs.end()) {
183 return 0;
184 }
185 // This patch is a relocated field
186 const size_t fieldSize = (this->moduleData.is64bit()) ? sizeof(ULONGLONG) : sizeof(DWORD);
187 if (!peconv::validate_ptr(this->patchedCode, this->codeSize, patch_ptr, fieldSize)) {
188 return 0;
189 }
190 ULONGLONG field = (this->moduleData.is64bit()) ? *((ULONGLONG*)patch_ptr) : *((DWORD*)patch_ptr);
192 return fieldSize;
193}
194
196{
197 const ULONGLONG patch_va = moduleData.rvaToVa(patch.startRva);
198 const size_t patch_offset = patch.startRva - sectionRVA;
199 BYTE* patch_ptr = this->patchedCode + patch_offset;
200 size_t size = patch.endRva - patch.startRva;
201
202 if (size > 1) {
203 bool isPadding = true;
204 for (size_t i = 1; i < size; ++i) {
205 if (patch_ptr[0] != patch_ptr[i]) {
206 isPadding = false;
207 }
208 }
209 if (isPadding) {
210 patch.type = PATCH_PADDING;
211 patch.paddingVal = patch_ptr[0];
212 }
213 }
214 if (patch_ptr[0] == 0xCC) {
215 if (size == 1 || patch.type == PATCH_PADDING) {
216 patch.type = PATCH_BREAKPOINT;
217 }
218 }
219 return size;
220}
221
223{
224 const ULONGLONG patch_va = moduleData.rvaToVa(patch.startRva);
225 const size_t patch_offset = patch.startRva - sectionRVA;
226 BYTE* patch_ptr = this->patchedCode + patch_offset;
227
228 size_t size = _analyzeRelocated(patch, patch_ptr);
229 if (size) {
230 return size;
231 }
232 const size_t kMinSize = 3;
233 if (!peconv::validate_ptr(this->patchedCode, this->codeSize, patch_ptr, kMinSize)) {
234 return 0;
235 }
236 size = _analyzeHook(patch, patch_ptr, patch_va);
237 if (size == 0 && patch_offset > 0) {
238 //it may happen that the address of an existing JMP/CALL was replaced
239 //try to parse a byte before the patch...
240 size = _analyzeHook(patch, patch_ptr -1, patch_va - 1);
241 if (size > 0) {
242 // subtract the added position:
243 size--;
244 }
245 }
246 return size;
247}
248
size_t _analyzeHook(PatchList::Patch &patch, PBYTE patch_ptr, ULONGLONG patch_va)
size_t parseShortJmp(PatchList::Patch &patch, PBYTE patch_ptr, ULONGLONG patch_va)
size_t _analyzeRelocated(PatchList::Patch &patch, BYTE *patch_ptr)
size_t analyzeOther(PatchList::Patch &patch)
size_t parseJmpViaAddr(PatchList::Patch &patch, PBYTE patch_ptr, ULONGLONG patch_va)
size_t parseJmp(PatchList::Patch &patch, PBYTE patch_ptr, ULONGLONG patch_va)
ULONGLONG getJmpDestAddr(ULONGLONG currVA, int instrLen, DELTA_T lVal)
size_t parseMovJmp(PatchList::Patch &patch, PBYTE patch_ptr, bool is_long)
size_t parsePushRet(PatchList::Patch &patch, PBYTE patch_ptr)
size_t analyzeHook(PatchList::Patch &patch)
void setHookTarget(ULONGLONG target_va, bool is_direct=true, t_patch_type hook_type=pesieve::HOOK_INLINE)
Definition patch_list.h:57
@ PATCH_BREAKPOINT
Definition patch_list.h:16
@ HOOK_ADDR_REPLACEMENT
Definition patch_list.h:14
@ PATCH_PADDING
Definition patch_list.h:15