├── README.md ├── doc └── EN_emotet_packer_analysis_and_config_extraction_v1.pdf ├── resources ├── cmd.gif ├── gui.gif ├── idc.gif └── usage.png ├── unpacked_sample_idc ├── emotet.unpacked.bin └── emotet.unpacked.idc └── unpacker ├── TitanEngine.dll ├── TitanEngine_x64.dll ├── TitanEngine_x86.dll └── src ├── configuration.py ├── dump_parser.py ├── main.py ├── titan_engine ├── __init__.py └── teSdk.py ├── unpacker.py └── yara ├── emotet_code.yar ├── emotet_rsa_key.yar └── hooks.yar /README.md: -------------------------------------------------------------------------------- 1 | # Emotet research 2 | In this repository you can find documentation about the packer of Emotet and its 3 | unpacker. This unpacker extracts the command and controls, and the public RSA 4 | key of Emotet (botnet identifier). 5 | 6 | ![alt text](resources/usage.png) 7 | 8 | ## General purpouse 9 | The purpose of this repository is, to show how the packet of emotet works, 10 | provide a sample of emotet payload with its idc (made by me) 11 | 12 | Also I wrote an unpacker for emotet (using TitanEngine) which extracts the final 13 | payload of emotet and the intermediate layers for extracting it. 14 | 15 | In addition, the unpackers extrats to a file the static configuration of emotet, 16 | the command and controls and its public RSA key (botnet identifier). 17 | 18 | ## Directories and files 19 | **./unpacker/src:** 20 |     - Source code of the packer. **main.py** file is the 21 | script for running the unpacker. 22 | **./unpacker/TitanEngine.dll** 23 |     - Titan Engine DLL, or you can download from 24 | **reversinglabs.com**. The DLL must be on **C:/windows/system32/** folder. 25 | **./doc:** 26 |     - Documentation of the emotet unpacker. 27 | **./unpacked_sample_id:** 28 |     - In this directory you can find a sample of 29 | final payload of the Emotet next to an idc documented by me. 30 | 31 | ## Requeriments 32 | Before to use the unpacker, I recommend to read the documentation I did about 33 | this packer. (./doc/EN_emotet_packer_analysis_and_config_extraction_v1.pdf) 34 | 35 | * pyrhon2.7 36 | * yara-python 37 | * Titan Engine DLL. Provided in this repo, or you can download from its web page 38 | 39 | 40 | ## Output 41 | If success a folder named "output" will be created. 42 | 43 | **./output/unpacked/{packed_file_name}.emotet.unpacked:** 44 |     - Emotet payload unpacked. (PE File) 45 | **./output/extracted_files/layer2/{packed_file_name}.layer2.bin:** 46 |     - Layer2 of emotet packer. (PE File) 47 | **./output/extracted_files/broken_payload/{packed_file_name}.payload.bin:** 48 |     - Emotet payload unpacked step 1, if you read the packer 49 | documentation you will realized that in the first step this file doesn't work, 50 | it must be fixed up. (PE File) 51 | **./output/static_configuration/{packed_file_name}.ips.txt:** 52 |     - List of "ip:port" of commands and controls. 53 | **./output/static_configuration/{packed_file_name}.rsa.txt:** 54 |     - RSA key used for communicating with the command and 55 | control. 56 | ## Usage 57 | After install all requeriments and the Titan Engine DLL... 58 | ``` 59 | cd unpacker/src/ 60 | ./main.py [options] {filename or folder} 61 | ``` 62 | ![CMD Demo](resources/cmd.gif) 63 | 64 | OR 65 | ``` 66 | double click :S 67 | ``` 68 | ![GUI Demo](resources/gui.gif) 69 | 70 | Then select the target file in the box. 71 | Press the "UnPack" buttom. 72 | Enjoy =) 73 | 74 | ## Final words 75 | These documentation and unpacker were done between November and December of 2018 76 | if after these dates it doesn't work maybe the unpacker doesn't work well or 77 | Emotet developers changed the packer. 78 | 79 | This unpacker isn't perfect, it's my first dynamic unpacker using Titan Engine. 80 | I did this research on my free time, because currently I'm not working on this 81 | kind of stuff and I don't have enough time. 82 | 83 | It'd be cool keep the repository updated for new versions of the packer or for 84 | some future error fixes. Therefore, feel free to send PR or open issues. I will 85 | try to keep it updated if I have time. 86 | 87 | Enjoy and keep fighting against the malware =) -------------------------------------------------------------------------------- /doc/EN_emotet_packer_analysis_and_config_extraction_v1.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/d00rt/emotet_research/7e3c412d8cc257a5deda430de7c3ef7b92e27f4e/doc/EN_emotet_packer_analysis_and_config_extraction_v1.pdf -------------------------------------------------------------------------------- /resources/cmd.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/d00rt/emotet_research/7e3c412d8cc257a5deda430de7c3ef7b92e27f4e/resources/cmd.gif -------------------------------------------------------------------------------- /resources/gui.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/d00rt/emotet_research/7e3c412d8cc257a5deda430de7c3ef7b92e27f4e/resources/gui.gif -------------------------------------------------------------------------------- /resources/idc.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/d00rt/emotet_research/7e3c412d8cc257a5deda430de7c3ef7b92e27f4e/resources/idc.gif -------------------------------------------------------------------------------- /resources/usage.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/d00rt/emotet_research/7e3c412d8cc257a5deda430de7c3ef7b92e27f4e/resources/usage.png -------------------------------------------------------------------------------- /unpacked_sample_idc/emotet.unpacked.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/d00rt/emotet_research/7e3c412d8cc257a5deda430de7c3ef7b92e27f4e/unpacked_sample_idc/emotet.unpacked.bin -------------------------------------------------------------------------------- /unpacker/TitanEngine.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/d00rt/emotet_research/7e3c412d8cc257a5deda430de7c3ef7b92e27f4e/unpacker/TitanEngine.dll -------------------------------------------------------------------------------- /unpacker/TitanEngine_x64.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/d00rt/emotet_research/7e3c412d8cc257a5deda430de7c3ef7b92e27f4e/unpacker/TitanEngine_x64.dll -------------------------------------------------------------------------------- /unpacker/TitanEngine_x86.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/d00rt/emotet_research/7e3c412d8cc257a5deda430de7c3ef7b92e27f4e/unpacker/TitanEngine_x86.dll -------------------------------------------------------------------------------- /unpacker/src/configuration.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | 4 | __author__ = "d00rt - @D00RT_RM" 5 | 6 | __version__ = "1.0.0" 7 | __maintainer__ = "d00rt - @D00RT_RM" 8 | __email__ = "d00rt.fake@gmail.com" 9 | __status__ = "Testing" 10 | 11 | 12 | DIR_CURRENT = os.path.dirname(__file__) 13 | 14 | DIR_YARA = os.path.join(DIR_CURRENT, "yara") 15 | DIR_DUMPS = os.path.join(DIR_CURRENT, "dumps") 16 | DIR_OUTPUT = os.path.join(DIR_CURRENT, "output") 17 | 18 | FOLDER_EXTRACTED_FILES = "extracted_files" 19 | DIR_EXTRACTED_FILES = os.path.join(DIR_OUTPUT, FOLDER_EXTRACTED_FILES) 20 | 21 | FOLDER_LAYER_2 = "layer2" 22 | FOLDER_PAYLOAD = "broken_payload" 23 | 24 | DIR_LAYER_2 = os.path.join(DIR_EXTRACTED_FILES, FOLDER_LAYER_2) 25 | DIR_PAYLOAD = os.path.join(DIR_EXTRACTED_FILES, FOLDER_PAYLOAD) 26 | 27 | FOLDER_STATIC_CONFIG = "static_configuration" 28 | DIR_STATIC_CONFIG = os.path.join(DIR_OUTPUT, FOLDER_STATIC_CONFIG) 29 | 30 | FOLDER_UNPACKED_FILES = "unpacked" 31 | DIR_UNPACKED_FILES = os.path.join(DIR_OUTPUT, FOLDER_UNPACKED_FILES) 32 | 33 | FILE_YARA_RSA = os.path.join(DIR_YARA, "emotet_rsa_key.yar") 34 | FILE_YARA_CODE = os.path.join(DIR_YARA, "emotet_code.yar") 35 | FILE_YARA_HOOKS = os.path.join(DIR_YARA, "hooks.yar") 36 | 37 | FILE_IPS = "ips.txt" 38 | FILE_RSA = "rsa.txt" 39 | FILE_LAYER_2 = "layer2" 40 | FILE_PAYLOAD = "broken_payload" 41 | 42 | FILE_EMOTET = "unpacked" -------------------------------------------------------------------------------- /unpacker/src/dump_parser.py: -------------------------------------------------------------------------------- 1 | from configuration import * 2 | import StringIO 3 | import struct 4 | import base64 5 | import yara 6 | import os 7 | 8 | """ 9 | class EmotetDumpFileParser: 10 | Simple class for parsing the memory dump where 11 | Emotet drops unpacked files. 12 | 13 | Also this class can reconstruct the emotet payload. 14 | 15 | class SimplePEFileParser: 16 | Simple and custom class for reading PE files. 17 | """ 18 | 19 | __author__ = "d00rt - @D00RT_RM" 20 | 21 | __version__ = "1.0.1" 22 | __maintainer__ = "d00rt - @D00RT_RM" 23 | __email__ = "d00rt.fake@gmail.com" 24 | __status__ = "Testing" 25 | 26 | 27 | # ==== 28 | BYTE = 0x01 29 | WORD = 0x02 30 | DWORD = 0x04 31 | 32 | _SIZE_TABLE = { 33 | BYTE: "B", 34 | WORD: "=H", 35 | DWORD: "=L", 36 | } 37 | 38 | # ==== DOS MZ Header 39 | PE_HEADER_OFFSET = 0x3C 40 | 41 | # ==== PE Header 42 | SIGNATURE = 0x00 43 | NUMBER_OF_SECTIONS = 0x06 44 | CODE_BASE = 0x2C 45 | IMAGE_BASE = 0x34 46 | FILE_ALIGNMENT = 0x3C 47 | SECTIONS_HEADER = 0xF8 48 | 49 | # ==== Section Header 50 | SECTION_HEADER_SIZE = 0x28 51 | NAME = 0x00 52 | VIRTUAL_SIZE = 0x08 53 | VIRTUAL_ADDRESS = 0x0C 54 | SIZE_OF_RAW_DATA = 0x10 55 | POINTER_TO_RAW_DATA = 0x14 56 | CHARACTERISTICS = 0x24 57 | 58 | 59 | class SimplePEFileParser(): 60 | 61 | def __init__(self, data=None, filename=None): 62 | self.DATA = '' 63 | self.ALIGNED = False 64 | 65 | if data == None and filename == None: 66 | raise Exception("No sources provided.") 67 | 68 | if data: 69 | self.DATA = data 70 | 71 | if filename: 72 | with open(filename, "rb") as f: 73 | self.DATA = f.read() 74 | 75 | if self.DATA == '': 76 | raise Exception("Provided data soruces for creating the reader are empty") 77 | 78 | if not self.simple_check_valid_pe_file(): 79 | raise Exception("Provided data isn't a valid PE file.") 80 | 81 | def patch_bytes(self, off, data): 82 | size = len(data) 83 | b_data = bytearray(self.DATA) 84 | b_data[off: off + size] = data 85 | self.DATA = str(b_data) 86 | 87 | def _fix_file_alignment_field(self, alignment=0x200): 88 | b_data = bytearray(self.DATA) 89 | pe_header = self.read_data_as_integer(PE_HEADER_OFFSET, DWORD) 90 | 91 | b_data[pe_header + alignment: pe_header + alignment + 4] = \ 92 | struct.pack("=L", alignment) 93 | self.DATA = str(b_data) 94 | 95 | def _fix_section_header_content_and_alignment_field(self, alignment): 96 | b_data = bytearray(self.DATA) 97 | pe_header = self.read_data_as_integer(PE_HEADER_OFFSET, DWORD) 98 | num_of_sections = self.read_data_as_integer(pe_header + NUMBER_OF_SECTIONS, WORD) 99 | section_header = pe_header + SECTIONS_HEADER 100 | 101 | aligned_section_header_content = self._get_aligned_section_header_content(alignment) 102 | self._fix_file_alignment_field(alignment) 103 | 104 | for i in range(num_of_sections): 105 | b_data[section_header + (i * SECTION_HEADER_SIZE) + POINTER_TO_RAW_DATA: \ 106 | section_header + (i * SECTION_HEADER_SIZE) + POINTER_TO_RAW_DATA + 4] = \ 107 | struct.pack("=L", aligned_section_header_content[i][0]) 108 | 109 | b_data[section_header + (i * SECTION_HEADER_SIZE) + SIZE_OF_RAW_DATA: \ 110 | section_header + (i * SECTION_HEADER_SIZE) + SIZE_OF_RAW_DATA + 4] = \ 111 | struct.pack("=L", aligned_section_header_content[i][1]) 112 | self.DATA = str(b_data) 113 | 114 | def _fill_data(self, raw_size): 115 | return '\x00' * raw_size 116 | 117 | def _align(self, value, aligment): 118 | r = value % aligment 119 | if r > 0: 120 | value += aligment - r 121 | 122 | return value 123 | 124 | def _get_aligned_section_header_content(self, aligment=0x200): 125 | aligned_section_headers = [] 126 | for s in self._get_section_header_content(): 127 | raw_addr, raw_size, virtual_address, virtual_size, name, characteristics = s 128 | raw_addr_aligned = self._align(raw_addr, aligment) 129 | raw_size_aligned = self._align(raw_size, aligment) 130 | if aligned_section_headers: 131 | r_a, r_s = aligned_section_headers[-1] 132 | raw_addr_aligned = r_a + r_s 133 | aligned_section_headers.append((raw_addr_aligned, raw_size_aligned)) 134 | 135 | return aligned_section_headers 136 | 137 | def _get_data_from_offset(self, offset, size): 138 | return self.DATA[offset: offset + size] 139 | 140 | def read_data_as_integer(self, offset, size): 141 | if not size in _SIZE_TABLE.keys(): 142 | return None 143 | 144 | return struct.unpack(_SIZE_TABLE[size], self._get_data_from_offset(offset, size))[0] 145 | 146 | def read_data_as_buffer(self, offset, size): 147 | if size < 0: 148 | return None 149 | return self._get_data_from_offset(offset, size) 150 | 151 | def _get_pe_header(self): 152 | return self.read_data_as_integer(PE_HEADER_OFFSET, DWORD) 153 | 154 | def _get_pe_signature(self): 155 | pe_header = self._get_pe_header() 156 | if not pe_header: 157 | return False 158 | return self.read_data_as_integer(pe_header + SIGNATURE, DWORD) 159 | 160 | def _get_pe_image_base(self): 161 | pe_header = self._get_pe_header() 162 | if not pe_header: 163 | return False 164 | return self.read_data_as_integer(pe_header + IMAGE_BASE, DWORD) 165 | 166 | def _get_pe_code_base(self): 167 | pe_header = self._get_pe_header() 168 | if not pe_header: 169 | return False 170 | return self.read_data_as_integer(pe_header + IMAGE_BASE, DWORD) 171 | 172 | def _get_number_of_sections(self): 173 | pe_header = self._get_pe_header() 174 | return self.read_data_as_integer(pe_header + NUMBER_OF_SECTIONS, WORD) 175 | 176 | def _get_content_of_sections(self): 177 | sections_content = [] 178 | for r_a, r_s, v_a, v_s, n, c in self._get_section_header_content(): 179 | section_data = self.DATA[r_a: r_a + r_s] 180 | sections_content.append(section_data) 181 | 182 | return sections_content 183 | 184 | def _get_file_alignment(self): 185 | pe_header = self._get_pe_header() 186 | return self.read_data_as_integer(pe_header + FILE_ALIGNMENT, DWORD) 187 | 188 | def _get_number_of_sections(self): 189 | pe_header = self._get_pe_header() 190 | return self.read_data_as_integer(pe_header + NUMBER_OF_SECTIONS, WORD) 191 | 192 | def _get_section_header(self): 193 | pe_header = self._get_pe_header() 194 | return pe_header + SECTIONS_HEADER 195 | 196 | def _check_pe_signature(self): 197 | pe_signature = self._get_pe_signature() 198 | if not pe_signature: 199 | return False 200 | 201 | return 0x00004550 == pe_signature 202 | 203 | def _get_section_header_content(self): 204 | sections_header_array = [] 205 | section_header = self._get_section_header() 206 | for i in range(self._get_number_of_sections()): 207 | name = self.read_data_as_buffer(section_header + (i * SECTION_HEADER_SIZE) + NAME, DWORD * 2).replace('\x00', '') 208 | pointer_to_raw_data = self.read_data_as_integer(section_header + (i * SECTION_HEADER_SIZE) + POINTER_TO_RAW_DATA, DWORD) 209 | size_of_raw_data = self.read_data_as_integer(section_header + (i * SECTION_HEADER_SIZE) + SIZE_OF_RAW_DATA, DWORD) 210 | virtual_address = self.read_data_as_integer(section_header + (i * SECTION_HEADER_SIZE) + VIRTUAL_ADDRESS, DWORD) 211 | virtual_size = self.read_data_as_integer(section_header + (i * SECTION_HEADER_SIZE) + VIRTUAL_SIZE, DWORD) 212 | characteristics = self.read_data_as_integer(section_header + (i * SECTION_HEADER_SIZE) + CHARACTERISTICS, DWORD) 213 | sections_header_array.append((pointer_to_raw_data, size_of_raw_data, virtual_address, virtual_size, name, characteristics)) 214 | return sections_header_array 215 | 216 | def _search_for_mz(self): 217 | while len(self.DATA): 218 | mz_index = self.DATA.index("MZ") 219 | self.DATA = self.DATA[mz_index:] 220 | 221 | if self._check_pe_signature(): 222 | return True 223 | return False 224 | 225 | def simple_check_valid_pe_file(self): 226 | if not self._search_for_mz(): 227 | return False 228 | 229 | return True 230 | 231 | def _get_data_based_on_section_header(self): 232 | last_section = self._get_section_header_content()[-1] 233 | r_a, r_s, e, m, o, t = last_section 234 | return self.DATA[:r_a + r_s] 235 | 236 | def align_file(self): 237 | sec_content = self._get_content_of_sections() 238 | 239 | header = self.DATA[: self._get_section_header_content()[0][0]] 240 | aligned_section_header_content = self._get_aligned_section_header_content() 241 | 242 | data = header 243 | data += self._fill_data(aligned_section_header_content[0][0] - len(data)) 244 | 245 | i = 1 246 | # Copy all sections except the last one 247 | for s in sec_content[:-1]: 248 | data += s 249 | data += self._fill_data(aligned_section_header_content[i][0] - len(data)) 250 | i += 1 251 | 252 | data += sec_content[i - 1] 253 | data += self._fill_data(aligned_section_header_content[i - 1][0] + aligned_section_header_content[i - 1][1] - len(data)) 254 | 255 | self.DATA = data 256 | self._fix_section_header_content_and_alignment_field(0x200) 257 | self.ALIGNED = True 258 | 259 | def from_va_to_offset(self, va): 260 | offset = 0 261 | va -= self._get_pe_image_base() 262 | for r_a, r_s, v_a, v_s, n, c in self._get_section_header_content(): 263 | if va < v_a + v_s: 264 | offset = va - v_a 265 | break 266 | return r_a + offset 267 | 268 | def write(self, filename): 269 | if not os.path.exists(os.path.dirname(filename)): 270 | os.makedirs(os.path.dirname(filename)) 271 | 272 | with open(filename, "wb") as f: 273 | f.write(self._get_data_based_on_section_header()) 274 | 275 | class EmotetDumpFileParser(): 276 | 277 | def __init__(self, data=None, dumpfilename=None): 278 | self.DUMP_DATA = '' 279 | self.PE_LAYER_2 = None 280 | self.PE_BROKEN_PAYLOAD = None 281 | self.PE_PAYLOAD = None 282 | self.RSA_CONFIG = [] 283 | self.IPS_CONFIG = [] 284 | 285 | if data == None and dumpfilename == None: 286 | raise Exception("No sources provided.") 287 | 288 | if data: 289 | self.DUMP_DATA = data 290 | 291 | if dumpfilename: 292 | with open(dumpfilename, "rb") as f: 293 | self.DUMP_DATA = f.read() 294 | 295 | if self.DUMP_DATA == '': 296 | raise Exception("Provided data soruces for creating the reader are empty") 297 | 298 | def _get_end_of_layer_2_in_the_dump(self): 299 | mz_index = self.DUMP_DATA.index("MZ") 300 | end_of_file = self.PE_LAYER_2._get_section_header_content()[-1][0] + self.PE_LAYER_2._get_section_header_content()[-1][1] 301 | return mz_index + end_of_file 302 | 303 | def parse(self): 304 | self.PE_LAYER_2 = SimplePEFileParser(data=self.DUMP_DATA) 305 | layer_2_end_offset = self._get_end_of_layer_2_in_the_dump() 306 | self.PE_BROKEN_PAYLOAD = SimplePEFileParser(data=self.DUMP_DATA[layer_2_end_offset:]) 307 | self.PE_PAYLOAD = SimplePEFileParser(data=self.DUMP_DATA[layer_2_end_offset:]) 308 | 309 | self.PE_LAYER_2.align_file() 310 | 311 | def write(self, fname_layer_2, fname_payload): 312 | if fname_layer_2: 313 | self.PE_LAYER_2.write(fname_layer_2) 314 | 315 | if fname_payload: 316 | self.PE_BROKEN_PAYLOAD.write(fname_payload) 317 | 318 | def _get_rsa_from_offset(self, offset): 319 | return self.PE_PAYLOAD.read_data_as_buffer(offset, 0x6A) 320 | 321 | def _get_ip_from_raw_ip(self, raw_ip): 322 | return '.'.join([str(struct.unpack("B", c)[0]) for c in raw_ip][::-1]) 323 | 324 | def _get_port_from_raw_port(self, raw_port): 325 | return str((struct.unpack("=L", raw_port)[0]) & 0xFFFF) 326 | 327 | def _get_ips_from_offset(self, offset): 328 | i = 0 329 | ip = self.PE_PAYLOAD.read_data_as_buffer(offset + (i * 8), 0x4) 330 | port = self.PE_PAYLOAD.read_data_as_buffer(offset + (i * 8) + 4, 0x4) 331 | 332 | while ip != "\x00\x00\x00\x00" and port != "\x00\x00\x00\x00": 333 | yield self._get_ip_from_raw_ip(ip) + ":" + self._get_port_from_raw_port(port) 334 | i += 1 335 | 336 | ip = self.PE_PAYLOAD.read_data_as_buffer(offset + (i * 8), 0x4) 337 | port = self.PE_PAYLOAD.read_data_as_buffer(offset + (i * 8) + 4, 0x4) 338 | 339 | 340 | def get_static_config(self, match_rsa, match_code): 341 | if match_code: 342 | 343 | for m in match_code: 344 | for string in m.strings: 345 | if string[1] == '$ip_pattern': 346 | ips_rva = struct.unpack("=L", string[2][9: 9 + 4])[0] 347 | ips_off = self.PE_PAYLOAD.from_va_to_offset(ips_rva) 348 | for ip_port in self._get_ips_from_offset(ips_off): 349 | self.IPS_CONFIG.append(ip_port) 350 | 351 | elif string[1] == '$key': 352 | rsa_rva = struct.unpack("=L", string[2][17: 17 + 4])[0] 353 | rsa_off = self.PE_PAYLOAD.from_va_to_offset(rsa_rva) 354 | self.RSA_CONFIG.append(self._get_rsa_from_offset(rsa_off)) 355 | 356 | elif string[1] == '$old_version_pattern': 357 | ips_rva = struct.unpack("=L", string[2][16: 16 + 4])[0] 358 | ips_off = self.PE_PAYLOAD.from_va_to_offset(ips_rva) 359 | for ip_port in self._get_ips_from_offset(ips_off): 360 | self.IPS_CONFIG.append(ip_port) 361 | 362 | rsa_rva = struct.unpack("=L", string[2][36: 36 + 4])[0] 363 | rsa_off = self.PE_PAYLOAD.from_va_to_offset(rsa_rva) 364 | rsa_rva = self.PE_PAYLOAD.read_data_as_integer(rsa_off, DWORD) 365 | rsa_off = self.PE_PAYLOAD.from_va_to_offset(rsa_rva) 366 | self.RSA_CONFIG.append(self._get_rsa_from_offset(rsa_off)) 367 | 368 | if not match_code and match_rsa: 369 | for m in match_rsa: 370 | for string in m.strings: 371 | self.RSA_CONFIG.append(string[2]) 372 | 373 | self.RSA_CONFIG = set(self.RSA_CONFIG) 374 | self.IPS_CONFIG = set(self.IPS_CONFIG) 375 | self.RSA_CONFIG = list(self.RSA_CONFIG) 376 | self.IPS_CONFIG = list(self.IPS_CONFIG) 377 | 378 | def print_hex(self, data, chunk_size=16): 379 | output = [] 380 | i = 0 381 | while i < len(data): 382 | chunk = " ".join("{0:02X}".format(ord(c)) for c in data[i * chunk_size: i * chunk_size + chunk_size]) 383 | if len(chunk): 384 | output.append(chunk) 385 | i += 1 386 | 387 | return output 388 | 389 | def _unhook_get_array_data(self, off, size): 390 | hook_array = [] 391 | raw_hooks = self.PE_LAYER_2.read_data_as_buffer(off, size * 0x0C) 392 | for i in range(size): 393 | src_data_va = struct.unpack("=L", raw_hooks[i * 0x0C: (i * 0x0C) + 0x04])[0] 394 | dst_off_in_payload = struct.unpack("=L", raw_hooks[i * 0xC + 4: (i * 0xC + 4) + 4])[0] 395 | _size = struct.unpack("=L", raw_hooks[i * 0xC + 8: (i * 0xC + 8) + 4])[0] 396 | hook_array.append((src_data_va, dst_off_in_payload, _size)) 397 | return hook_array 398 | 399 | def _unhook(self, match): 400 | for m in match: 401 | for string in m.strings: 402 | if string[1] == '$hooks1': 403 | hook_rva = struct.unpack("=L", string[2][10: 10 + 4])[0] 404 | hook_off = self.PE_LAYER_2.from_va_to_offset(hook_rva) 405 | 406 | if string[1] == '$hooks2': 407 | hook_rva = struct.unpack("=L", string[2][7: 7 + 4])[0] 408 | hook_off = self.PE_LAYER_2.from_va_to_offset(hook_rva) 409 | 410 | if string[1] == '$hooks3': 411 | hook_rva = struct.unpack("=L", string[2][8: 8 + 4])[0] 412 | hook_off = self.PE_LAYER_2.from_va_to_offset(hook_rva) 413 | 414 | elif string[1] == '$size': 415 | array_size = struct.unpack("=L", string[2][10: 10 + 4])[0] 416 | 417 | if array_size and hook_off: 418 | hook_array = self._unhook_get_array_data(hook_off, array_size) 419 | 420 | # Get .text section data for resizeing the destination offset 421 | # dst_offset are rva of .text section 422 | for r_a, r_s, v_a, v_s, n, c in self.PE_PAYLOAD._get_section_header_content(): 423 | if n == ".text": 424 | break 425 | 426 | for src_va, dst_off, size in hook_array: 427 | src_off = self.PE_LAYER_2.from_va_to_offset(src_va) 428 | hook_data = self.PE_LAYER_2.read_data_as_buffer(src_off, size) 429 | dst_off = dst_off - v_a + r_a 430 | self.PE_PAYLOAD.patch_bytes(dst_off, hook_data) 431 | return True 432 | else: 433 | return False 434 | 435 | def unhook(self): 436 | rules_hooks = yara.compile(FILE_YARA_HOOKS) 437 | 438 | match = rules_hooks.match(data=self.PE_LAYER_2.DATA) 439 | if match: 440 | return self._unhook(match) 441 | 442 | return [] 443 | 444 | def write_static_config(self, rsa_filename, ips_filename): 445 | if rsa_filename: 446 | if not os.path.exists(os.path.dirname(rsa_filename)): 447 | os.makedirs(os.path.dirname(rsa_filename)) 448 | 449 | with open(rsa_filename, "wb") as f: 450 | for rsa_key in self.RSA_CONFIG: 451 | f.write("== RSA KEY BASE64 ==\r\n") 452 | f.write(base64.b64encode(rsa_key)) 453 | f.write("\r\n") 454 | f.write("== RSA KEY BASE64 END ==\r\n") 455 | f.write("\r\n") 456 | f.write("== RSA KEY YARA EXPORT ==\r\n") 457 | for l in self.print_hex(rsa_key): 458 | f.write("{0}\r\n".format(l)) 459 | f.write("== RSA KEY YARA EXPORT END ==\r\n") 460 | 461 | if ips_filename: 462 | if not os.path.exists(os.path.dirname(ips_filename)): 463 | os.makedirs(os.path.dirname(ips_filename)) 464 | 465 | with open(ips_filename, "wb") as f: 466 | for ip in self.IPS_CONFIG: 467 | f.write("{0}\r\n".format(ip)) 468 | -------------------------------------------------------------------------------- /unpacker/src/main.py: -------------------------------------------------------------------------------- 1 | from optparse import OptionParser 2 | from unpacker import deEmotet 3 | from dump_parser import SimplePEFileParser 4 | import base64 as b64 5 | import hashlib 6 | import glob 7 | import sys 8 | import os 9 | 10 | 11 | __author__ = "d00rt - @D00RT_RM" 12 | 13 | __version__ = "1.0.0" 14 | __maintainer__ = "d00rt - @D00RT_RM" 15 | __email__ = "d00rt.fake@gmail.com" 16 | __status__ = "Testing" 17 | 18 | 19 | VALID_OUTPUTS = [ 20 | "plaintext", 21 | "json", 22 | ] 23 | 24 | LOG_ERRORS = False 25 | 26 | def log_error(message): 27 | if LOG_ERRORS == True: 28 | print message 29 | 30 | def md5(fname): 31 | hash_md5 = hashlib.md5() 32 | with open(fname, "rb") as f: 33 | for chunk in iter(lambda: f.read(4096), b""): 34 | hash_md5.update(chunk) 35 | return hash_md5.hexdigest() 36 | 37 | 38 | def print_plaintext(data): 39 | print "filename: {0}".format(data["filename"]) 40 | print "md5: {0}".format(data["md5"]) 41 | print "unpacked: {0}".format(data["unpacked"]) 42 | print "number_of_rsa_keys: {0}".format(data["number_of_rsa_keys"]) 43 | print "number_of_ips: {0}".format(data["number_of_ips"]) 44 | 45 | if data.get("rsa_keys", False): 46 | print "rsa_key: " 47 | for r in data["rsa_keys"]: 48 | print " {0}".format(b64.b64encode(r)) 49 | 50 | if data.get("ips", False): 51 | print "command and controls: " 52 | for r in data["ips"]: 53 | print " {0}".format(r) 54 | 55 | 56 | def print_json(data): 57 | print data 58 | 59 | 60 | def print_metadata(meta, filename, frmt, verbose=False): 61 | unpacked, rsa_keys, ips, m1, m2 = meta 62 | data = { 63 | "filename": filename, 64 | "md5": md5(filename), 65 | "unpacked": unpacked, 66 | "number_of_rsa_keys": len(rsa_keys), 67 | "number_of_ips": len(ips), 68 | "match_1": m1, 69 | "match_2": m2, 70 | } 71 | 72 | if verbose: 73 | verbose_data = { 74 | "rsa_keys": rsa_keys, 75 | "ips": ips, 76 | } 77 | data.update(verbose_data) 78 | 79 | if frmt == "plaintext": 80 | print_plaintext(data) 81 | 82 | if frmt == "json": 83 | print_json(data) 84 | 85 | 86 | def run_folder(folder, debug_log, verbose, frmt, write_results=False): 87 | for filename in glob.glob(os.path.join(folder, "*")): 88 | if not os.path.isdir(filename): 89 | run_file(filename, debug_log, verbose, frmt, gui=False, write_results=write_results) 90 | 91 | else: 92 | log_error("[!] Error. The file {0} is a folder.".format(filename)) 93 | 94 | 95 | def run_file(filename, debug_log, verbose, frmt, gui=False, write_results=False): 96 | if gui: 97 | # If gui == True -> the output will automatically saved 98 | EmotetUnpacker = deEmotet(gui=gui, debug=debug_log) 99 | unpacking_result = EmotetUnpacker.unpack_file(filename) 100 | return 101 | 102 | try: 103 | SimplePEFileParser(filename=filename) 104 | except Exception as e: 105 | log_error("[!] Error. {0} {1}".format(filename, e)) 106 | return 107 | 108 | EmotetUnpacker = deEmotet(gui=gui, debug=debug_log) 109 | unpacking_result = EmotetUnpacker.unpack_file(filename) 110 | 111 | if EmotetUnpacker.unpacked and unpacking_result: 112 | print_metadata(unpacking_result, filename, frmt, verbose=verbose) 113 | else: 114 | log_error("[!] Error. File: {0} For more info execute it again with --debug-log option.".format(filename)) 115 | 116 | if write_results and EmotetUnpacker.unpacked: 117 | EmotetUnpacker.write() 118 | 119 | 120 | def usage(): 121 | usage_message = [] 122 | usage_message.append("usage: %prog [options] arg ") 123 | usage_message.append(" _____ __ __ ___ _____ _____ _____ ") 124 | usage_message.append(" | ____| \\/ |/ _ \\_ _| ____|_ _| ") 125 | usage_message.append(" | _| | |\\/| | | | || | | _| | | ") 126 | usage_message.append(" | |___| | | | |_| || | | |___ | | ") 127 | usage_message.append(" |_____|_| |_|\\___/ |_| |_____| |_|_____ ____ ") 128 | usage_message.append(" | | | | \\ | | _ \\ / \\ / ___| |/ / ____| _ \\ ") 129 | usage_message.append(" | | | | \\| | |_) / _ \\| | | ' /| _| | |_) | ") 130 | usage_message.append(" | |_| | |\\ | __/ ___ \\ |___| . \\| |___| _ < ") 131 | usage_message.append(" \\___/|_| \\_|_| /_/_ \\_\\____|_|\\_\\_____|_| \\_\\ ") 132 | usage_message.append(" | |__ _ _ __| |/ _ \\ / _ \\ _ __| |_ ") 133 | usage_message.append(" | '_ \\| | | | / _` | | | | | | | '__| __| ") 134 | usage_message.append(" | |_) | |_| | | (_| | |_| | |_| | | | |_ ") 135 | usage_message.append(" |_.__/ \\__, | \\__,_|\\___/ \\___/|_| \\__| ") 136 | usage_message.append(" |___/ ") 137 | usage_message.append(" ") 138 | usage_message.append("https://github.com/d00rt/emotet_research - 2018 ") 139 | usage_message.append(" - @D00RT_RM - ") 140 | usage_message.append(" ") 141 | return '\r\n'.join(usage_message) 142 | 143 | 144 | def main(samples='', write=True, debug_log=False, verbose=False, frmt="plaintext"): 145 | if samples == '': 146 | run_file(filename=samples, debug_log=True, verbose=None, frmt=None, gui=True, write_results=True) 147 | return 148 | 149 | if not os.path.exists(samples): 150 | log_error("[!] Error. {0} does not exist.".format(samples)) 151 | return 152 | 153 | if os.path.isdir(samples): 154 | run_folder(samples, debug_log, verbose, frmt, write_results=write) 155 | else: 156 | run_file(samples, debug_log, verbose, frmt, write_results=write) 157 | 158 | 159 | if __name__ == "__main__": 160 | parser = OptionParser(usage=usage(), version="emotet unpacker 1.0.0 by d00rt") 161 | parser.add_option("-v", "--verbose", dest="verbose", 162 | help="adds RSA keys and Command and Control servers to the output records", action="store_true", default=False) 163 | parser.add_option("-e", "--print-errors", dest="errors", 164 | help="print to stdout when a file unpacking fails", action="store_true", default=False) 165 | parser.add_option("-d", "--debug-log", dest="debug_log", 166 | help="print to stdout the debug messages", action="store_true", default=False) 167 | parser.add_option("-w", "--write-output", dest="write_output", 168 | help="write the results in the output folder", action="store_true", default=False) 169 | parser.add_option("-f", "--output-format", dest="output_format", 170 | help="output format: ({0}) [default: %default]".format(', '.join(v for v in VALID_OUTPUTS)), type="string", default="plaintext") 171 | 172 | (options, args) = parser.parse_args() 173 | output_format = options.output_format.lower() if options.output_format.lower() in VALID_OUTPUTS else "plaintext" 174 | LOG_ERRORS = options.errors 175 | 176 | if len(args) == 1: 177 | main(args[0], write=options.write_output, debug_log=options.debug_log, verbose=options.verbose, frmt=output_format) 178 | 179 | elif len(args) == 0: 180 | main() 181 | else: 182 | parser.print_help() 183 | -------------------------------------------------------------------------------- /unpacker/src/titan_engine/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/d00rt/emotet_research/7e3c412d8cc257a5deda430de7c3ef7b92e27f4e/unpacker/src/titan_engine/__init__.py -------------------------------------------------------------------------------- /unpacker/src/titan_engine/teSdk.py: -------------------------------------------------------------------------------- 1 | from ctypes import * 2 | 3 | TE = windll.LoadLibrary("TitanEngine.dll") 4 | 5 | # check widechar, x64 6 | 7 | UE_ACCESS_READ = 0 8 | UE_ACCESS_WRITE = 1 9 | UE_ACCESS_ALL = 2 10 | 11 | UE_HIDE_BASIC = 1 12 | 13 | UE_PLUGIN_CALL_REASON_PREDEBUG = 1 14 | UE_PLUGIN_CALL_REASON_EXCEPTION = 2 15 | UE_PLUGIN_CALL_REASON_POSTDEBUG = 3 16 | 17 | TEE_HOOK_NRM_JUMP = 1 18 | TEE_HOOK_NRM_CALL = 3 19 | TEE_HOOK_IAT = 5 20 | 21 | UE_ENGINE_ALOW_MODULE_LOADING = 1 22 | UE_ENGINE_AUTOFIX_FORWARDERS = 2 23 | UE_ENGINE_PASS_ALL_EXCEPTIONS = 3 24 | UE_ENGINE_NO_CONSOLE_WINDOW = 4 25 | UE_ENGINE_BACKUP_FOR_CRITICAL_FUNCTIONS = 5 26 | UE_ENGINE_CALL_PLUGIN_CALLBACK = 6 27 | UE_ENGINE_RESET_CUSTOM_HANDLER = 7 28 | UE_ENGINE_CALL_PLUGIN_DEBUG_CALLBACK = 8 29 | 30 | UE_OPTION_REMOVEALL = 1 31 | UE_OPTION_DISABLEALL = 2 32 | UE_OPTION_REMOVEALLDISABLED = 3 33 | UE_OPTION_REMOVEALLENABLED = 4 34 | 35 | UE_STATIC_DECRYPTOR_XOR = 1 36 | UE_STATIC_DECRYPTOR_SUB = 2 37 | UE_STATIC_DECRYPTOR_ADD = 3 38 | 39 | UE_STATIC_DECRYPTOR_FOREWARD = 1 40 | UE_STATIC_DECRYPTOR_BACKWARD = 2 41 | 42 | UE_STATIC_KEY_SIZE_1 = 1 43 | UE_STATIC_KEY_SIZE_2 = 2 44 | UE_STATIC_KEY_SIZE_4 = 4 45 | UE_STATIC_KEY_SIZE_8 = 8 46 | 47 | UE_STATIC_APLIB = 1 48 | UE_STATIC_APLIB_DEPACK = 2 49 | UE_STATIC_LZMA = 3 50 | 51 | UE_STATIC_HASH_MD5 = 1 52 | UE_STATIC_HASH_SHA1 = 2 53 | UE_STATIC_HASH_CRC32 = 3 54 | 55 | UE_RESOURCE_LANGUAGE_ANY = -1 56 | 57 | UE_PE_OFFSET = 0 58 | UE_IMAGEBASE = 1 59 | UE_OEP = 2 60 | UE_SIZEOFIMAGE = 3 61 | UE_SIZEOFHEADERS = 4 62 | UE_SIZEOFOPTIONALHEADER = 5 63 | UE_SECTIONALIGNMENT = 6 64 | UE_IMPORTTABLEADDRESS = 7 65 | UE_IMPORTTABLESIZE = 8 66 | UE_RESOURCETABLEADDRESS = 9 67 | UE_RESOURCETABLESIZE = 10 68 | UE_EXPORTTABLEADDRESS = 11 69 | UE_EXPORTTABLESIZE = 12 70 | UE_TLSTABLEADDRESS = 13 71 | UE_TLSTABLESIZE = 14 72 | UE_RELOCATIONTABLEADDRESS = 15 73 | UE_RELOCATIONTABLESIZE = 16 74 | UE_TIMEDATESTAMP = 17 75 | UE_SECTIONNUMBER = 18 76 | UE_CHECKSUM = 19 77 | UE_SUBSYSTEM = 20 78 | UE_CHARACTERISTICS = 21 79 | UE_NUMBEROFRVAANDSIZES = 22 80 | UE_SECTIONNAME = 23 81 | UE_SECTIONVIRTUALOFFSET = 24 82 | UE_SECTIONVIRTUALSIZE = 25 83 | UE_SECTIONRAWOFFSET = 26 84 | UE_SECTIONRAWSIZE = 27 85 | UE_SECTIONFLAGS = 28 86 | 87 | UE_CH_BREAKPOINT = 1 88 | UE_CH_SINGLESTEP = 2 89 | UE_CH_ACCESSVIOLATION = 3 90 | UE_CH_ILLEGALINSTRUCTION = 4 91 | UE_CH_NONCONTINUABLEEXCEPTION = 5 92 | UE_CH_ARRAYBOUNDSEXCEPTION = 6 93 | UE_CH_FLOATDENORMALOPERAND = 7 94 | UE_CH_FLOATDEVIDEBYZERO = 8 95 | UE_CH_INTEGERDEVIDEBYZERO = 9 96 | UE_CH_INTEGEROVERFLOW = 10 97 | UE_CH_PRIVILEGEDINSTRUCTION = 11 98 | UE_CH_PAGEGUARD = 12 99 | UE_CH_EVERYTHINGELSE = 13 100 | UE_CH_CREATETHREAD = 14 101 | UE_CH_EXITTHREAD = 15 102 | UE_CH_CREATEPROCESS = 16 103 | UE_CH_EXITPROCESS = 17 104 | UE_CH_LOADDLL = 18 105 | UE_CH_UNLOADDLL = 19 106 | UE_CH_OUTPUTDEBUGSTRING = 20 107 | 108 | UE_OPTION_HANDLER_RETURN_HANDLECOUNT = 1 109 | UE_OPTION_HANDLER_RETURN_ACCESS = 2 110 | UE_OPTION_HANDLER_RETURN_FLAGS = 3 111 | UE_OPTION_HANDLER_RETURN_TYPENAME = 4 112 | 113 | UE_BREAKPOINT_INT3 = 1 114 | UE_BREAKPOINT_LONG_INT3 = 2 115 | UE_BREAKPOINT_UD2 = 3 116 | 117 | UE_BPXREMOVED = 0 118 | UE_BPXACTIVE = 1 119 | UE_BPXINACTIVE = 2 120 | 121 | UE_BREAKPOINT = 0 122 | UE_SINGLESHOOT = 1 123 | UE_HARDWARE = 2 124 | UE_MEMORY = 3 125 | UE_MEMORY_READ = 4 126 | UE_MEMORY_WRITE = 5 127 | UE_BREAKPOINT_TYPE_INT3 = 0x10000000 128 | UE_BREAKPOINT_TYPE_LONG_INT3 = 0x20000000 129 | UE_BREAKPOINT_TYPE_UD2 = 0x30000000 130 | 131 | UE_HARDWARE_EXECUTE = 4 132 | UE_HARDWARE_WRITE = 5 133 | UE_HARDWARE_READWRITE = 6 134 | 135 | UE_HARDWARE_SIZE_1 = 7 136 | UE_HARDWARE_SIZE_2 = 8 137 | UE_HARDWARE_SIZE_4 = 9 138 | 139 | UE_ON_LIB_LOAD = 1 140 | UE_ON_LIB_UNLOAD = 2 141 | UE_ON_LIB_ALL = 3 142 | 143 | UE_APISTART = 0 144 | UE_APIEND = 1 145 | 146 | UE_PLATFORM_x86 = 1 147 | UE_PLATFORM_x64 = 2 148 | UE_PLATFORM_ALL = 3 149 | 150 | UE_FUNCTION_STDCALL = 1 151 | UE_FUNCTION_CCALL = 2 152 | UE_FUNCTION_FASTCALL = 3 153 | UE_FUNCTION_STDCALL_RET = 4 154 | UE_FUNCTION_CCALL_RET = 5 155 | UE_FUNCTION_FASTCALL_RET = 6 156 | UE_FUNCTION_STDCALL_CALL = 7 157 | UE_FUNCTION_CCALL_CALL = 8 158 | UE_FUNCTION_FASTCALL_CALL = 9 159 | UE_PARAMETER_BYTE = 0 160 | UE_PARAMETER_WORD = 1 161 | UE_PARAMETER_DWORD = 2 162 | UE_PARAMETER_QWORD = 3 163 | UE_PARAMETER_PTR_BYTE = 4 164 | UE_PARAMETER_PTR_WORD = 5 165 | UE_PARAMETER_PTR_DWORD = 6 166 | UE_PARAMETER_PTR_QWORD = 7 167 | UE_PARAMETER_STRING = 8 168 | UE_PARAMETER_UNICODE = 9 169 | 170 | UE_CMP_NOCONDITION = 0 171 | UE_CMP_EQUAL = 1 172 | UE_CMP_NOTEQUAL = 2 173 | UE_CMP_GREATER = 3 174 | UE_CMP_GREATEROREQUAL = 4 175 | UE_CMP_LOWER = 5 176 | UE_CMP_LOWEROREQUAL = 6 177 | UE_CMP_REG_EQUAL = 7 178 | UE_CMP_REG_NOTEQUAL = 8 179 | UE_CMP_REG_GREATER = 9 180 | UE_CMP_REG_GREATEROREQUAL = 10 181 | UE_CMP_REG_LOWER = 11 182 | UE_CMP_REG_LOWEROREQUAL = 12 183 | UE_CMP_ALWAYSFALSE = 13 184 | 185 | UE_EAX = 1 186 | UE_EBX = 2 187 | UE_ECX = 3 188 | UE_EDX = 4 189 | UE_EDI = 5 190 | UE_ESI = 6 191 | UE_EBP = 7 192 | UE_ESP = 8 193 | UE_EIP = 9 194 | UE_EFLAGS = 10 195 | UE_DR0 = 11 196 | UE_DR1 = 12 197 | UE_DR2 = 13 198 | UE_DR3 = 14 199 | UE_DR6 = 15 200 | UE_DR7 = 16 201 | UE_RAX = 17 202 | UE_RBX = 18 203 | UE_RCX = 19 204 | UE_RDX = 20 205 | UE_RDI = 21 206 | UE_RSI = 22 207 | UE_RBP = 23 208 | UE_RSP = 24 209 | UE_RIP = 25 210 | UE_RFLAGS = 26 211 | UE_R8 = 27 212 | UE_R9 = 28 213 | UE_R10 = 29 214 | UE_R11 = 30 215 | UE_R12 = 31 216 | UE_R13 = 32 217 | UE_R14 = 33 218 | UE_R15 = 34 219 | UE_CIP = 35 220 | UE_CSP = 36 221 | 222 | class PE32Struct(Structure): 223 | _pack_ = 1 224 | _fields_ = [ ("PE32Offset", c_ulong), 225 | ("ImageBase", c_ulong), 226 | ("OriginalEntryPoint", c_ulong), 227 | ("NtSizeOfImage", c_ulong), 228 | ("NtSizeOfHeaders", c_ulong), 229 | ("SizeOfOptionalHeaders", c_ushort), 230 | ("FileAlignment", c_ulong), 231 | ("SectionAligment", c_ulong), 232 | ("ImportTableAddress", c_ulong), 233 | ("ImportTableSize", c_ulong), 234 | ("ResourceTableAddress", c_ulong), 235 | ("ResourceTableSize", c_ulong), 236 | ("ExportTableAddress", c_ulong), 237 | ("ExportTableSize", c_ulong), 238 | ("TLSTableAddress", c_ulong), 239 | ("TLSTableSize", c_ulong), 240 | ("RelocationTableAddress", c_ulong), 241 | ("RelocationTableSize", c_ulong), 242 | ("TimeDateStamp", c_ulong), 243 | ("SectionNumber", c_ushort), 244 | ("CheckSum", c_ulong), 245 | ("SubSystem", c_ushort), 246 | ("Characteristics", c_ushort), 247 | ("NumberOfRvaAndSizes", c_ulong) ] 248 | 249 | class PE64Struct(Structure): 250 | _pack_ = 1 251 | _fields_ = [ ("PE64Offset", c_ulong), 252 | ("ImageBase", c_ulonglong), 253 | ("OriginalEntryPoint", c_ulong), 254 | ("NtSizeOfImage", c_ulong), 255 | ("NtSizeOfHeaders", c_ulong), 256 | ("SizeOfOptionalHeaders", c_ushort), 257 | ("FileAlignment", c_ulong), 258 | ("SectionAligment", c_ulong), 259 | ("ImportTableAddress", c_ulong), 260 | ("ImportTableSize", c_ulong), 261 | ("ResourceTableAddress", c_ulong), 262 | ("ResourceTableSize", c_ulong), 263 | ("ExportTableAddress", c_ulong), 264 | ("ExportTableSize", c_ulong), 265 | ("TLSTableAddress", c_ulong), 266 | ("TLSTableSize", c_ulong), 267 | ("RelocationTableAddress", c_ulong), 268 | ("RelocationTableSize", c_ulong), 269 | ("TimeDateStamp", c_ulong), 270 | ("SectionNumber", c_ushort), 271 | ("CheckSum", c_ulong), 272 | ("SubSystem", c_ushort), 273 | ("Characteristics", c_ushort), 274 | ("NumberOfRvaAndSizes", c_ulong) ] 275 | 276 | class ImportEnumData(Structure): 277 | _pack_ = 1 278 | _fields_ = [ ("NewDll", c_bool), 279 | ("NumberOfImports", c_int), 280 | ("ImageBase", c_ulong), 281 | ("BaseImportThunk", c_ulong), 282 | ("ImportThunk", c_ulong), 283 | ("APIName", c_char_p), 284 | ("DLLName", c_char_p) ] 285 | 286 | class THREAD_ITEM_DATA(Structure): 287 | _pack_ = 1 288 | _fields_ = [ ("hThread", c_void_p), 289 | ("dwThreadId", c_ulong), 290 | ("ThreadStartAddress", c_void_p), 291 | ("ThreadLocalBase", c_void_p) ] 292 | 293 | MAX_PATH = 260 294 | 295 | class LIBRARY_ITEM_DATA(Structure): 296 | _pack_ = 1 297 | _fields_ = [ ("hFile", c_void_p), 298 | ("BaseOfDll", c_void_p), 299 | ("hFileMapping", c_void_p), 300 | ("hFileMappingView", c_void_p), 301 | ("szLibraryPath", c_char * MAX_PATH), 302 | ("szLibraryName", c_char * MAX_PATH) ] 303 | 304 | class LIBRARY_ITEM_DATAW(Structure): 305 | _pack_ = 1 306 | _fields_ = [ ("hFile", c_void_p), 307 | ("BaseOfDll", c_void_p), 308 | ("hFileMapping", c_void_p), 309 | ("hFileMappingView", c_void_p), 310 | ("szLibraryPath", c_wchar * MAX_PATH), 311 | ("szLibraryName", c_wchar * MAX_PATH) ] 312 | 313 | class PROCESS_ITEM_DATA(Structure): 314 | _pack_ = 1 315 | _fields_ = [ ("hProcess", c_void_p), 316 | ("dwProcessId", c_ulong), 317 | ("hThread", c_void_p), 318 | ("dwThreadId", c_ulong), 319 | ("hFile", c_void_p), 320 | ("BaseOfImage", c_void_p), 321 | ("ThreadStartAddress", c_void_p), 322 | ("ThreadLocalBase", c_void_p) ] 323 | 324 | class HandlerArray(Structure): 325 | _pack_ = 1 326 | _fields_ = [ ("ProcessId", c_ulong), 327 | ("hHandle", c_void_p) ] 328 | 329 | class PluginInformation(Structure): 330 | _pack_ = 1 331 | _fields_ = [ ("PluginName", c_char * 64), 332 | ("PluginMajorVersion", c_ulong), 333 | ("PluginMinorVersion", c_ulong), 334 | ("PluginBaseAddress", c_void_p), 335 | ("TitanDebuggingCallBack", c_void_p), 336 | ("TitanRegisterPlugin", c_void_p), 337 | ("TitanReleasePlugin", c_void_p), 338 | ("TitanResetPlugin", c_void_p), 339 | ("PluginDisabled", c_bool) ] 340 | 341 | TEE_MAXIMUM_HOOK_SIZE = 14 342 | TEE_MAXIMUM_HOOK_RELOCS = 7 343 | 344 | TEE_MAXIMUM_HOOK_INSERT_SIZE = 5 345 | TEE_MAXIMUM_HOOK_INSERT_SIZE64 = 14 346 | 347 | class HOOK_ENTRY(Structure): 348 | _pack_ = 1 349 | _fields_ = [ ("IATHook", c_bool), 350 | ("HookType", c_ubyte), 351 | ("HookSize", c_ulong), 352 | ("HookAddress", c_void_p), 353 | ("RedirectionAddress", c_void_p), 354 | ("HookBytes", c_ubyte * TEE_MAXIMUM_HOOK_SIZE), 355 | ("OriginalBytes", c_ubyte * TEE_MAXIMUM_HOOK_SIZE), 356 | ("IATHookModuleBase", c_void_p), 357 | ("IATHookNameHash", c_ulong), 358 | ("HookIsEnabled", c_bool), 359 | ("HookIsRemote", c_bool), 360 | ("PatchedEntry", c_void_p), 361 | ("RelocationInfo", c_ulong * TEE_MAXIMUM_HOOK_RELOCS), 362 | ("RelocationCount", c_int) ] 363 | 364 | UE_DEPTH_SURFACE = 0 365 | UE_DEPTH_DEEP = 1 366 | 367 | UE_UNPACKER_CONDITION_SEARCH_FROM_EP = 1 368 | 369 | UE_UNPACKER_CONDITION_LOADLIBRARY = 1 370 | UE_UNPACKER_CONDITION_GETPROCADDRESS = 2 371 | UE_UNPACKER_CONDITION_ENTRYPOINTBREAK = 3 372 | UE_UNPACKER_CONDITION_RELOCSNAPSHOT1 = 4 373 | UE_UNPACKER_CONDITION_RELOCSNAPSHOT2 = 5 374 | 375 | UE_FIELD_OK = 0 376 | UE_FIELD_BROKEN_NON_FIXABLE = 1 377 | UE_FIELD_BROKEN_NON_CRITICAL = 2 378 | UE_FIELD_BROKEN_FIXABLE_FOR_STATIC_USE = 3 379 | UE_FIELD_BROKEN_BUT_CAN_BE_EMULATED = 4 380 | UE_FILED_FIXABLE_NON_CRITICAL = 5 381 | UE_FILED_FIXABLE_CRITICAL = 6 382 | UE_FIELD_NOT_PRESET = 7 383 | UE_FIELD_NOT_PRESET_WARNING = 8 384 | 385 | UE_RESULT_FILE_OK = 10 386 | UE_RESULT_FILE_INVALID_BUT_FIXABLE = 11 387 | UE_RESULT_FILE_INVALID_AND_NON_FIXABLE = 12 388 | UE_RESULT_FILE_INVALID_FORMAT = 13 389 | 390 | class FILE_STATUS_INFO(Structure): 391 | _pack_ = 1 392 | _fields_ = [ ("OveralEvaluation", c_ubyte), 393 | ("EvaluationTerminatedByException", c_bool), 394 | ("FileIs64Bit", c_bool), 395 | ("FileIsDLL", c_bool), 396 | ("FileIsConsole", c_bool), 397 | ("MissingDependencies", c_bool), 398 | ("MissingDeclaredAPIs", c_bool), 399 | ("SignatureMZ", c_ubyte), 400 | ("SignaturePE", c_ubyte), 401 | ("EntryPoint", c_ubyte), 402 | ("ImageBase", c_ubyte), 403 | ("SizeOfImage", c_ubyte), 404 | ("FileAlignment", c_ubyte), 405 | ("SectionAlignment", c_ubyte), 406 | ("ExportTable", c_ubyte), 407 | ("RelocationTable", c_ubyte), 408 | ("ImportTable", c_ubyte), 409 | ("ImportTableSection", c_ubyte), 410 | ("ImportTableData", c_ubyte), 411 | ("IATTable", c_ubyte), 412 | ("TLSTable", c_ubyte), 413 | ("LoadConfigTable", c_ubyte), 414 | ("BoundImportTable", c_ubyte), 415 | ("COMHeaderTable", c_ubyte), 416 | ("ResourceTable", c_ubyte), 417 | ("ResourceData", c_ubyte), 418 | ("SectionTable", c_ubyte) ] 419 | 420 | class FILE_FIX_INFO(Structure): 421 | _pack_ = 1 422 | _fields_ = [ ("OveralEvaluation", c_ubyte), 423 | ("FixingTerminatedByException", c_bool), 424 | ("FileFixPerformed", c_bool), 425 | ("StrippedRelocation", c_bool), 426 | ("DontFixRelocations", c_bool), 427 | ("OriginalRelocationTableAddress", c_ulong), 428 | ("OriginalRelocationTableSize", c_ulong), 429 | ("StrippedExports", c_bool), 430 | ("DontFixExports", c_bool), 431 | ("OriginalExportTableAddress", c_ulong), 432 | ("OriginalExportTableSize", c_ulong), 433 | ("StrippedResources", c_bool), 434 | ("DontFixResources", c_bool), 435 | ("OriginalResourceTableAddress", c_ulong), 436 | ("OriginalResourceTableSize", c_ulong), 437 | ("StrippedTLS", c_bool), 438 | ("DontFixTLS", c_bool), 439 | ("OriginalTLSTableAddress", c_ulong), 440 | ("OriginalTLSTableSize", c_ulong), 441 | ("StrippedLoadConfig", c_bool), 442 | ("DontFixLoadConfig", c_bool), 443 | ("OriginalLoadConfigTableAddress", c_ulong), 444 | ("OriginalLoadConfigTableSize", c_ulong), 445 | ("StrippedBoundImports", c_bool), 446 | ("DontFixBoundImports", c_bool), 447 | ("OriginalBoundImportTableAddress", c_ulong), 448 | ("OriginalBoundImportTableSize", c_ulong), 449 | ("StrippedIAT", c_bool), 450 | ("DontFixIAT", c_bool), 451 | ("OriginalImportAddressTableAddress", c_ulong), 452 | ("OriginalImportAddressTableSize", c_ulong), 453 | ("StrippedCOM", c_bool), 454 | ("DontFixCOM", c_bool), 455 | ("OriginalCOMTableAddress", c_ulong), 456 | ("OriginalCOMTableSize", c_ulong) ] 457 | 458 | class PROCESS_INFORMATION(Structure): 459 | _pack_ = 1 460 | _fields_ = [ ("hProcess", c_void_p), 461 | ("hThread", c_void_p), 462 | ("dwProcessId", c_ulong), 463 | ("dwThreadId", c_ulong) ] 464 | 465 | EXCEPTION_MAXIMUM_PARAMETERS = 15 466 | 467 | class EXCEPTION_RECORD(Structure): 468 | _pack_ = 1 469 | pass 470 | 471 | EXCEPTION_RECORD._fields_ = [ ("ExceptionCode", c_ulong), 472 | ("ExceptionFlags", c_ulong), 473 | ("ExceptionRecord", POINTER(EXCEPTION_RECORD)), 474 | ("ExceptionAddress", c_void_p), 475 | ("NumberParameters", c_ulong), 476 | ("ExceptionInformation", c_ulong * EXCEPTION_MAXIMUM_PARAMETERS) ] 477 | 478 | class EXCEPTION_DEBUG_INFO(Structure): 479 | _pack_ = 1 480 | _fields_ = [ ("ExceptionRecord", EXCEPTION_RECORD), 481 | ("dwFirstChance", c_ulong) ] 482 | 483 | class CREATE_THREAD_DEBUG_INFO(Structure): 484 | _pack_ = 1 485 | _fields_ = [ ("hThread", c_void_p), 486 | ("lpThreadLocalBase", c_void_p), 487 | ("lpStartAddress", c_void_p) ] 488 | 489 | class CREATE_PROCESS_DEBUG_INFO(Structure): 490 | _pack_ = 1 491 | _fields_ = [ ("hFile", c_void_p), 492 | ("hProcess", c_void_p), 493 | ("hThread", c_void_p), 494 | ("dwDebugInfoFileOffset", c_ulong), 495 | ("nDebugInfoSize", c_ulong), 496 | ("lpThreadLocalBase", c_void_p), 497 | ("lpStartAddress", c_void_p), 498 | ("lpImageName", c_void_p), 499 | ("fUnicode", c_ushort) ] 500 | 501 | class EXIT_THREAD_DEBUG_INFO(Structure): 502 | _pack_ = 1 503 | _fields_ = [ ("dwExitCode", c_ulong) ] 504 | 505 | class EXIT_PROCESS_DEBUG_INFO(Structure): 506 | _pack_ = 1 507 | _fields_ = [ ("dwExitCode", c_ulong) ] 508 | 509 | class LOAD_DLL_DEBUG_INFO(Structure): 510 | _pack_ = 1 511 | _fields_ = [ ("hFile", c_void_p), 512 | ("lpBaseOfDll", c_void_p), 513 | ("dwDebugInfoFileOffset", c_ulong), 514 | ("nDebugInfoSize", c_ulong), 515 | ("lpImageName", c_void_p), 516 | ("fUnicode", c_ushort) ] 517 | 518 | class UNLOAD_DLL_DEBUG_INFO(Structure): 519 | _pack_ = 1 520 | _fields_ = [ ("lpBaseOfDll", c_void_p) ] 521 | 522 | class OUTPUT_DEBUG_STRING_INFO(Structure): 523 | _pack_ = 1 524 | _fields_ = [ ("lpDebugStringData", c_char_p), 525 | ("fUnicode", c_ushort), 526 | ("nDebugStringLength", c_ushort) ] 527 | 528 | class RIP_INFO(Structure): 529 | _pack_ = 1 530 | _fields_ = [ ("dwError", c_ulong), 531 | ("dwType", c_ulong) ] 532 | 533 | class _U(Union): 534 | _pack_ = 1 535 | _fields_ = [ ("Exception", EXCEPTION_DEBUG_INFO), 536 | ("CreateThread", CREATE_THREAD_DEBUG_INFO), 537 | ("CreateProcessInfo", CREATE_PROCESS_DEBUG_INFO), 538 | ("ExitThread", EXIT_THREAD_DEBUG_INFO), 539 | ("ExitProcess", EXIT_PROCESS_DEBUG_INFO), 540 | ("LoadDll", LOAD_DLL_DEBUG_INFO), 541 | ("UnloadDll", UNLOAD_DLL_DEBUG_INFO), 542 | ("DebugString", OUTPUT_DEBUG_STRING_INFO), 543 | ("RipInfo", RIP_INFO) ] 544 | 545 | class DEBUG_EVENT(Structure): 546 | _pack_ = 1 547 | _anonymous_ = ("u",) 548 | _fields_ = [ ("dwDebugEventCode", c_ulong), 549 | ("dwProcessId", c_ulong), 550 | ("dwThreadId", c_ulong), 551 | ("u", _U) ] 552 | 553 | class STARTUPINFOW(Structure): 554 | _pack_ = 1 555 | _fields_ = [ ("cb", c_ulong), 556 | ("lpReserved", c_wchar_p), 557 | ("lpDesktop", c_wchar_p), 558 | ("lpTitle", c_wchar_p), 559 | ("dwX", c_ulong), 560 | ("dwY", c_ulong), 561 | ("dwXSize", c_ulong), 562 | ("dwYSize", c_ulong), 563 | ("dwXCountChars", c_ulong), 564 | ("dwYCountChars", c_ulong), 565 | ("dwFillAttribute", c_ulong), 566 | ("dwFlags", c_ulong), 567 | ("wShowWindow", c_ushort), 568 | ("cbReserved2", c_ushort), 569 | ("lpReserved2", POINTER(c_ubyte)), 570 | ("hStdInput", c_void_p), 571 | ("hStdOutput", c_void_p), 572 | ("hStdError", c_void_p) ] 573 | 574 | fImportEnum = WINFUNCTYPE(None, POINTER(ImportEnumData)) 575 | fImportFix = WINFUNCTYPE(None, c_void_p) 576 | fResourceEnum = WINFUNCTYPE(None, c_wchar_p, c_ulong, c_wchar_p, c_ulong, c_ulong, c_ulong, c_ulong) 577 | fThreadEnum = WINFUNCTYPE(None, POINTER(THREAD_ITEM_DATA)) 578 | fThreadExit = WINFUNCTYPE(None, POINTER(EXIT_THREAD_DEBUG_INFO)) 579 | fBreakPoint = WINFUNCTYPE(None) 580 | fCustomHandler = WINFUNCTYPE(None, c_void_p) 581 | fLibraryBreakPoint = WINFUNCTYPE(None, POINTER(LOAD_DLL_DEBUG_INFO)) 582 | fLibraryEnum = WINFUNCTYPE(None, POINTER(LIBRARY_ITEM_DATA)) 583 | fLibraryEnumW = WINFUNCTYPE(None, POINTER(LIBRARY_ITEM_DATAW)) 584 | fHookEnum = WINFUNCTYPE(c_bool, POINTER(HOOK_ENTRY), c_void_p, POINTER(LIBRARY_ITEM_DATA), c_ulong) 585 | fProcessWithLibraryEnum = WINFUNCTYPE(None, c_ulong, c_void_p) 586 | fStaticDecrypt = WINFUNCTYPE(c_bool, c_void_p, c_ulong) 587 | fInitializeDbg = WINFUNCTYPE(None, c_char_p, c_ubyte, c_ubyte) 588 | 589 | TE.GetPE32DataFromMappedFile.restype = c_ulonglong 590 | TE.GetPE32Data.restype = c_ulonglong 591 | TE.GetPE32DataW.restype = c_ulonglong 592 | TE.ConvertVAtoFileOffset.restype = c_ulonglong 593 | TE.ConvertVAtoFileOffsetEx.restype = c_ulonglong 594 | TE.ConvertFileOffsetToVA.restype = c_ulonglong 595 | TE.ConvertFileOffsetToVAEx.restype = c_ulonglong 596 | TE.ResourcerLoadFileForResourceUse.restype = c_ulonglong 597 | TE.ResourcerLoadFileForResourceUseW.restype = c_ulonglong 598 | TE.ThreaderCreateRemoteThread.restype = c_ulonglong 599 | TE.ThreaderCreateRemoteThreadEx.restype = c_ulonglong 600 | TE.ThreaderGetOpenHandleForThread.restype = c_ulonglong 601 | TE.GetContextDataEx.restype = c_ulonglong 602 | TE.GetContextData.restype = c_ulonglong 603 | TE.FindEx.restype = c_ulonglong 604 | TE.Find.restype = c_ulonglong 605 | TE.GetDebuggedDLLBaseAddress.restype = c_ulonglong 606 | TE.GetDebuggedFileBaseAddress.restype = c_ulonglong 607 | TE.GetFunctionParameter.restype = c_ulonglong 608 | TE.GetJumpDestinationEx.restype = c_ulonglong 609 | TE.GetJumpDestination.restype = c_ulonglong 610 | TE.ImporterGetCurrentDelta.restype = c_ulonglong 611 | TE.ImporterFindAPIWriteLocation.restype = c_ulonglong 612 | TE.ImporterFindOrdinalAPIWriteLocation.restype = c_ulonglong 613 | TE.ImporterFindAPIByWriteLocation.restype = c_ulonglong 614 | TE.ImporterFindDLLByWriteLocation.restype = c_ulonglong 615 | TE.ImporterGetAPIOrdinalNumber.restype = c_ulonglong 616 | TE.ImporterGetRemoteAPIAddress.restype = c_ulonglong 617 | TE.ImporterGetRemoteAPIAddressEx.restype = c_ulonglong 618 | TE.ImporterGetLocalAPIAddress.restype = c_ulonglong 619 | TE.ImporterGetAPIOrdinalNumberFromDebugee.restype = c_ulonglong 620 | TE.ImporterGetRemoteDLLBase.restype = c_ulonglong 621 | TE.ImporterGetForwardedAPIOrdinalNumber.restype = c_ulonglong 622 | TE.ImporterGetNearestAPIAddress.restype = c_ulonglong 623 | TE.TracerLevel1.restype = c_ulonglong 624 | TE.HashTracerLevel1.restype = c_ulonglong 625 | TE.TracerFixKnownRedirection.restype = c_ulonglong 626 | TE.HandlerGetHandleDetails.restype = c_ulonglong 627 | TE.HandlerGetOpenMutexHandle.restype = c_ulonglong 628 | TE.HandlerGetOpenMutexHandleW.restype = c_ulonglong 629 | 630 | TE.GetSharedOverlay.restype = c_char_p 631 | TE.StaticDisassembleEx.restype = c_char_p 632 | TE.StaticDisassemble.restype = c_char_p 633 | TE.DisassembleEx.restype = c_char_p 634 | TE.Disassemble.restype = c_char_p 635 | TE.ImporterGetLastAddedDLLName.restype = c_char_p 636 | TE.ImporterGetDLLName.restype = c_char_p 637 | TE.ImporterGetAPIName.restype = c_char_p 638 | TE.ImporterGetAPINameEx.restype = c_char_p 639 | TE.ImporterGetDLLNameFromDebugee.restype = c_char_p 640 | TE.ImporterGetAPINameFromDebugee.restype = c_char_p 641 | TE.ImporterGetForwardedAPIName.restype = c_char_p 642 | TE.ImporterGetForwardedDLLName.restype = c_char_p 643 | TE.ImporterGetNearestAPIName.restype = c_char_p 644 | TE.TranslateNativeName.restype = c_char_p 645 | TE.HandlerGetHandleName.restype = c_char_p 646 | 647 | TE.GetSharedOverlayW.restype = c_wchar_p 648 | TE.TranslateNativeNameW.restype = c_wchar_p 649 | TE.HandlerGetHandleNameW.restype = c_wchar_p 650 | 651 | TE.GetPEBLocation.restype = c_void_p 652 | 653 | TE.ThreaderGetThreadInfo.restype = POINTER(THREAD_ITEM_DATA) 654 | TE.ThreaderGetThreadData.restype = POINTER(THREAD_ITEM_DATA) 655 | 656 | TE.InitDebug.restype = POINTER(PROCESS_INFORMATION) 657 | TE.InitDebugW.restype = POINTER(PROCESS_INFORMATION) 658 | TE.InitDebugEx.restype = POINTER(PROCESS_INFORMATION) 659 | TE.InitDebugExW.restype = POINTER(PROCESS_INFORMATION) 660 | TE.InitDLLDebug.restype = POINTER(PROCESS_INFORMATION) 661 | TE.InitDLLDebugW.restype = POINTER(PROCESS_INFORMATION) 662 | 663 | TE.GetDebugData.restype = POINTER(DEBUG_EVENT) 664 | TE.GetTerminationData.restype = POINTER(DEBUG_EVENT) 665 | 666 | TE.GetProcessInformation.restype = POINTER(PROCESS_INFORMATION) 667 | TE.GetStartupInformation.restype = POINTER(STARTUPINFOW) 668 | 669 | TE.LibrarianGetLibraryInfo.restype = POINTER(LIBRARY_ITEM_DATA) 670 | TE.LibrarianGetLibraryInfoEx.restype = POINTER(LIBRARY_ITEM_DATA) 671 | 672 | TE.LibrarianGetLibraryInfoW.restype = POINTER(LIBRARY_ITEM_DATAW) 673 | TE.LibrarianGetLibraryInfoExW.restype = POINTER(LIBRARY_ITEM_DATAW) 674 | 675 | TE.HooksGetHookEntryDetails.restype = POINTER(HOOK_ENTRY) 676 | 677 | TE.ExtensionManagerGetPluginInfo.restype = POINTER(PluginInformation) -------------------------------------------------------------------------------- /unpacker/src/unpacker.py: -------------------------------------------------------------------------------- 1 | from dump_parser import EmotetDumpFileParser, SimplePEFileParser 2 | from configuration import * 3 | from titan_engine.teSdk import * 4 | import time 5 | import base64 6 | import shutil 7 | import yara 8 | import glob 9 | import os 10 | 11 | """ 12 | Emotet unpacking module: 13 | Dynamic unpacker using TitanEngine framework. 14 | """ 15 | 16 | __author__ = "d00rt - @D00RT_RM" 17 | 18 | __version__ = "1.0.0" 19 | __maintainer__ = "d00rt - @D00RT_RM" 20 | __email__ = "d00rt.fake@gmail.com" 21 | __status__ = "Testing" 22 | 23 | 24 | DIST_TO_RETN = 0x100 25 | 26 | class deEmotet(): 27 | 28 | def __init__(self, gui=True, debug=False): 29 | self.CbOnInitialize = fInitializeDbg(self.OnInitialize) 30 | 31 | self.CbOnDbgInit = fBreakPoint(self.OnDbgInit) 32 | self.CbOnCallEAX = fBreakPoint(self.OnCallEAX) 33 | self.CbOnCallEDX = fBreakPoint(self.OnCallEDX) 34 | self.CbOnCallECX = fBreakPoint(self.OnCallECX) 35 | self.CbOnCallEBX = fBreakPoint(self.OnCallEBX) 36 | self.CbOnCallESP = fBreakPoint(self.OnCallESP) 37 | self.CbOnCallEBP = fBreakPoint(self.OnCallEBP) 38 | self.CbOnCallESI = fBreakPoint(self.OnCallESI) 39 | self.CbOnCallEDI = fBreakPoint(self.OnCallEDI) 40 | 41 | self.CbOnWipp = fBreakPoint(self.OnWipp) 42 | 43 | self.wild = c_ubyte(0xcc) 44 | self.layer2_base_addr = 0 45 | self.counter = 0 46 | self.debug_log = debug 47 | 48 | self.gui = gui 49 | self.unpacked = False 50 | self.UNPACKING_RESULT = None 51 | 52 | if self.gui: 53 | TE.EngineCreateUnpackerWindow("[ d00rt!deEmotet ]", "d00rt!deEmotet 2018/12/12", "d00rt!deEmotet 2018-11-30", 54 | "d00rt - @D00RT_RM", self.CbOnInitialize) 55 | 56 | self.log("Emotet unpacker v1.0.0 2018-12-10") 57 | self.log("d00rt (@D00RT_RM) / https://github.com/d00rt") 58 | 59 | def log(self, message): 60 | if not self.debug_log: 61 | return 62 | 63 | if self.gui: 64 | TE.EngineAddUnpackerWindowLogMessage(message) 65 | else: 66 | print message 67 | 68 | def write(self): 69 | if self.emotet_dump_parser.PE_LAYER_2: 70 | filename_layer_2 = os.path.join(DIR_LAYER_2, '.'.join([os.path.basename(self.Input), FILE_LAYER_2])) 71 | self.emotet_dump_parser.write(filename_layer_2, None) 72 | self.log("[+] Layer 2 extracted to: {0}".format(filename_layer_2)) 73 | else: 74 | self.log("[!] Could not extract the Layer 2") 75 | 76 | if self.emotet_dump_parser.PE_PAYLOAD: 77 | filename_payload = os.path.join(DIR_PAYLOAD, '.'.join([os.path.basename(self.Input), FILE_PAYLOAD])) 78 | self.emotet_dump_parser.write(None, filename_payload) 79 | self.log("[+] Broken Payload extracted to: {0}".format(filename_payload)) 80 | else: 81 | self.log("[!] Could not extract the Broken Payload") 82 | 83 | if self.emotet_dump_parser.RSA_CONFIG: 84 | filename_rsa = os.path.join(DIR_STATIC_CONFIG,'.'.join([os.path.basename(self.Input), FILE_RSA])) 85 | self.emotet_dump_parser.write_static_config(filename_rsa, None) 86 | self.log("[+] For RSA results check: {0}".format(os.path.join(FOLDER_STATIC_CONFIG, FILE_RSA))) 87 | else: 88 | self.log("[!] RSA key not found. Can not write its results.") 89 | 90 | if self.emotet_dump_parser.IPS_CONFIG: 91 | filename_ips = os.path.join(DIR_STATIC_CONFIG,'.'.join([os.path.basename(self.Input), FILE_IPS])) 92 | self.emotet_dump_parser.write_static_config(None, filename_ips) 93 | self.log("[+] For C&C results check: {0}".format(os.path.join(FOLDER_STATIC_CONFIG, FILE_IPS))) 94 | else: 95 | self.log("[!] C&C ips not found. Can not write its results.") 96 | 97 | if self.unpacked: 98 | filename_unpacked = os.path.join(DIR_UNPACKED_FILES, '.'.join([os.path.basename(self.Input), FILE_EMOTET])) 99 | self.emotet_dump_parser.PE_PAYLOAD.write(filename_unpacked) 100 | self.log("[+] Emotet payload unpacked: {0}".format(filename_unpacked)) 101 | else: 102 | self.log("[!] Could not unpack the file.") 103 | 104 | def _parse_dump(self, dump, match_rsa, match_code): 105 | if not match_rsa: 106 | self.log("[!] New RSA key detected (New botnet). Update {0} file =)".format(FILE_YARA_RSA)) 107 | if not match_code: 108 | self.log("[!] Payload yara rule doesn't match, could be:") 109 | self.log(" 1) Provided file is a new emotet-payload file") 110 | self.log(" so, emotet gang knows about this packer...") 111 | self.log(" 2) Provided file could be an old emotet version.") 112 | self.log(" 3) This packer is not good enough for your intell-team") 113 | 114 | self.emotet_dump_parser = EmotetDumpFileParser(dumpfilename=dump) 115 | self.emotet_dump_parser.parse() 116 | self.emotet_dump_parser.get_static_config(match_rsa, match_code) 117 | 118 | if self.emotet_dump_parser.RSA_CONFIG: 119 | self.log("[+] {0} RSA keys found.".format(len(self.emotet_dump_parser.RSA_CONFIG))) 120 | else: 121 | self.log("[+] RSA keys not found.") 122 | 123 | if self.emotet_dump_parser.IPS_CONFIG: 124 | self.log("[+] {0} C&C found.".format(len(self.emotet_dump_parser.IPS_CONFIG))) 125 | else: 126 | self.log("[+] C&C not found.") 127 | 128 | if self.emotet_dump_parser.unhook(): 129 | self.unpacked = True 130 | self.log("[+] Emotet payload correctly unpacked.") 131 | 132 | else: 133 | self.log("[!] Emotet payload can not be unpacked.") 134 | 135 | self.UNPACKING_RESULT = (self.unpacked, self.emotet_dump_parser.RSA_CONFIG, self.emotet_dump_parser.IPS_CONFIG, match_rsa, match_code) 136 | 137 | if self.gui: 138 | self.write() 139 | 140 | def OnWipp(self): 141 | matches = False 142 | 143 | if not TE.RemoveAllBreakPoints(UE_OPTION_REMOVEALL): 144 | self.log("[!] Can not delete the BPs") 145 | 146 | TE.DumpRegions(self.Info.contents.hProcess, DIR_DUMPS, False) 147 | rules_rsa = yara.compile(FILE_YARA_RSA) 148 | rules_code = yara.compile(FILE_YARA_CODE) 149 | 150 | for file in glob.glob(os.path.join(DIR_DUMPS, '*')): 151 | m_code = rules_code.match(filepath=file) 152 | m_rsa = rules_rsa.match(filepath=file) 153 | 154 | if m_code or m_rsa: 155 | matches = True 156 | self.log("[+] Yara match: ") 157 | for m in m_code + m_rsa: 158 | self.log(" - {0}".format(m.rule)) 159 | 160 | self._parse_dump(file, m_rsa, m_code) 161 | else: 162 | os.remove(file) 163 | 164 | if not matches: 165 | self.log("[!] Not Yara matches!") 166 | 167 | TE.StopDebug() 168 | self.log("[+] Stop Debug") 169 | 170 | def set_decrypt_fn_bp(self, call_addr): 171 | # BE CAREFUL 172 | # If this point is never reached, the binary will be executed 173 | # So if it is a malware but doesn't match with Emotet it will run 174 | # Execute the packer in a secure environment. 175 | # 176 | # I don't know how to deal with this using TitanEngine, any 177 | # suggestion is welcome 178 | if not TE.RemoveAllBreakPoints(UE_OPTION_REMOVEALL): 179 | self.log("[!] Can not delete the BPs") 180 | 181 | self.layer2_base_addr = call_addr & 0xFFFF0000 182 | pat = (c_ubyte * 15)(0x8B, 0x45, 0xCC, 0x83, 0xC0, 0x01, 0x3D, 0xFF, 0x00, 0x00, 0x00, 0x89, 0x45, 0xCC, 0xCC) 183 | 184 | loc = TE.Find(self.layer2_base_addr, self.SizeOfImg, byref(pat), len(pat), byref(self.wild)) 185 | 186 | aux_loc = loc 187 | 188 | while aux_loc: 189 | loc = aux_loc 190 | aux_loc = TE.Find(loc + 1, self.SizeOfImg, byref(pat), len(pat), byref(self.wild)) 191 | 192 | if not loc: 193 | self.log("[!] Could not locate decrypt function pattern.") 194 | TE.StopDebug() 195 | self.log("[+] Stop Debug") 196 | return 197 | 198 | # I'm sure that the pat is in a less address from where the first match was 199 | top = loc 200 | 201 | pat = (c_ubyte * 3)(0x55, 0x89, 0xE5) 202 | loc = TE.Find(loc - DIST_TO_RETN, DIST_TO_RETN, byref(pat), len(pat), byref(self.wild)) 203 | 204 | aux_loc = loc 205 | while loc and loc < top: 206 | aux_loc = loc 207 | dist = aux_loc - loc 208 | loc = TE.Find(loc + 1 , DIST_TO_RETN - dist - 1, byref(pat), len(pat), byref(self.wild)) 209 | 210 | loc = aux_loc 211 | self.log("[+] Function location: {0:X}".format(loc)) 212 | # TODO 213 | # Think on a better way to do it 214 | pat = (c_ubyte * 1)(0xC3) 215 | loc = TE.Find(loc, DIST_TO_RETN, byref(pat), len(pat), byref(self.wild)) 216 | 217 | if not TE.SetBPX(loc, UE_BREAKPOINT, self.CbOnWipp): 218 | #self.Dr = c_ulong(0) 219 | #TE.GetUnusedHardwareBreakPointRegister(byref(self.Dr)) 220 | #if not TE.SetHardwareBreakPoint(loc, self.Dr, UE_HARDWARE_EXECUTE, UE_HARDWARE_SIZE_1, self.CbOnWipp): 221 | self.log("[!] Could not set-up 'pre per' breakpoint.") 222 | TE.StopDebug() 223 | self.log("[+] Stop Debug") 224 | return 225 | 226 | # self.log("[+] Breakpoint 'pre per' set-up {0:X}".format(loc)) 227 | 228 | def _check_valid_call(self, addr): 229 | if addr & 0xFF000000 > 0x70000000 or addr & 0xFFFF0000 == self.Base & 0xFFFF0000: 230 | return False 231 | return True 232 | 233 | def OnCallEAX(self): 234 | self.counter -= 1 235 | layer2_oep = TE.GetContextData(UE_EAX) 236 | if not self._check_valid_call(layer2_oep): 237 | return 238 | 239 | self.log("[+] EAX " + hex(layer2_oep)) 240 | self.set_decrypt_fn_bp(layer2_oep) 241 | 242 | def OnCallECX(self): 243 | self.counter -= 1 244 | layer2_oep = TE.GetContextData(UE_ECX) 245 | if not self._check_valid_call(layer2_oep): 246 | return 247 | 248 | self.log("[+] ECX " + hex(layer2_oep)) 249 | self.set_decrypt_fn_bp(layer2_oep) 250 | 251 | def OnCallEDX(self): 252 | self.counter -= 1 253 | layer2_oep = TE.GetContextData(UE_EDX) 254 | if not self._check_valid_call(layer2_oep): 255 | return 256 | 257 | self.log("[+] EDX " + hex(layer2_oep)) 258 | self.set_decrypt_fn_bp(layer2_oep) 259 | 260 | def OnCallEBX(self): 261 | self.counter -= 1 262 | layer2_oep = TE.GetContextData(UE_EBX) 263 | if not self._check_valid_call(layer2_oep): 264 | return 265 | 266 | self.log("[+] EBX " + hex(layer2_oep)) 267 | self.set_decrypt_fn_bp(layer2_oep) 268 | 269 | def OnCallESP(self): 270 | self.counter -= 1 271 | layer2_oep = TE.GetContextData(UE_ESP) 272 | if not self._check_valid_call(layer2_oep): 273 | return 274 | 275 | self.log("[+] ESP " + hex(layer2_oep)) 276 | self.set_decrypt_fn_bp(layer2_oep) 277 | 278 | def OnCallEBP(self): 279 | self.counter -= 1 280 | layer2_oep = TE.GetContextData(UE_EBP) 281 | if not self._check_valid_call(layer2_oep): 282 | return 283 | 284 | self.log("[+] EBP " + hex(layer2_oep)) 285 | self.set_decrypt_fn_bp(layer2_oep) 286 | 287 | def OnCallESI(self): 288 | self.counter -= 1 289 | layer2_oep = TE.GetContextData(UE_ESI) 290 | if not self._check_valid_call(layer2_oep): 291 | return 292 | 293 | self.log("[+] ESI " + hex(layer2_oep)) 294 | self.set_decrypt_fn_bp(layer2_oep) 295 | 296 | def OnCallEDI(self): 297 | self.counter -= 1 298 | layer2_oep = TE.GetContextData(UE_EDI) 299 | if not self._check_valid_call(layer2_oep): 300 | return 301 | 302 | self.log("[+] EDI " + hex(layer2_oep)) 303 | self.set_decrypt_fn_bp(layer2_oep) 304 | 305 | def set_call_bp(self, reg): 306 | REG_BP = { 307 | 0: self.CbOnCallEAX, 308 | 1: self.CbOnCallECX, 309 | 2: self.CbOnCallEDX, 310 | 3: self.CbOnCallEBX, 311 | 4: self.CbOnCallESP, 312 | 5: self.CbOnCallEBP, 313 | 6: self.CbOnCallESI, 314 | 7: self.CbOnCallEDI, 315 | } 316 | 317 | bp_num = 0 318 | 319 | pat = (c_ubyte * 2)(0xFF, 0xD0 + reg) 320 | loc = TE.Find(self.Base, self.SizeOfImg, byref(pat), len(pat), byref(self.wild)) 321 | if loc == 0: 322 | return bp_num 323 | 324 | while loc > self.lo and loc < self.hi: 325 | aux_loc = loc 326 | dist = aux_loc - loc 327 | 328 | if not TE.SetBPX(loc, UE_SINGLESHOOT, REG_BP[reg]): 329 | return bp_num 330 | 331 | bp_num += 1 332 | 333 | self.counter += 1 334 | 335 | loc = TE.Find(loc, self.SizeOfImg - dist, byref(pat), len(pat), byref(self.wild)) 336 | return bp_num 337 | 338 | def OnDbgInit(self): 339 | total_bp = 0 340 | self.counter = 0 341 | 342 | self.Base = TE.GetDebuggedFileBaseAddress() 343 | self.lo += self.Base 344 | self.hi += self.Base 345 | 346 | total_bp += self.set_call_bp(0) #EAX 347 | total_bp += self.set_call_bp(1) #ECX 348 | total_bp += self.set_call_bp(2) #EDX 349 | total_bp += self.set_call_bp(3) #EBX 350 | total_bp += self.set_call_bp(4) #ESP 351 | total_bp += self.set_call_bp(5) #EBP 352 | total_bp += self.set_call_bp(6) #ESI 353 | total_bp += self.set_call_bp(7) #EDI 354 | 355 | if total_bp == 0: 356 | TE.StopDebug() 357 | self.log("[+] Stop Debug") 358 | return 359 | 360 | def OnInitialize(self, pOriginal, pRealign=False, pCopyOvl=False): 361 | if not pOriginal: return 362 | 363 | self.Input = pOriginal 364 | TE.SetBPXOptions(UE_BREAKPOINT_INT3) 365 | 366 | self.Validity = FILE_STATUS_INFO() 367 | 368 | if not TE.IsPE32FileValidEx(pOriginal, UE_DEPTH_DEEP, byref(self.Validity)) \ 369 | or self.Validity.OveralEvaluation != UE_RESULT_FILE_OK: 370 | self.log("[!] The file seems to be invalid.") 371 | return 372 | 373 | self.IsDLL = TE.IsFileDLL(pOriginal, 0) 374 | 375 | self.Info = TE.InitDebugEx(pOriginal, 0, 0, self.CbOnDbgInit) 376 | 377 | if self.Info: 378 | self.log("[+] Debugger initialized.") 379 | 380 | self.SizeOfImg = TE.GetPE32Data(pOriginal, 0, UE_SIZEOFIMAGE) 381 | self.EP = TE.GetPE32Data(pOriginal, 0, UE_OEP) 382 | 383 | self.SnapRange = TE.GetPE32Data(pOriginal, 0, UE_SECTIONVIRTUALOFFSET) 384 | self.SnapSize = self.EP - self.SnapRange 385 | 386 | self.lo = None 387 | pe_parser = SimplePEFileParser(filename=pOriginal) 388 | 389 | for r_a, r_s, v_a, v_s, n, c in pe_parser._get_section_header_content(): 390 | if self.EP >= v_a and self.EP < v_a + v_s: 391 | self.lo = v_a 392 | self.hi = v_a + v_s 393 | break 394 | 395 | if self.lo == None: 396 | self.log("[!] Could not get code section!") 397 | return 398 | 399 | TE.DebugLoop() 400 | else: 401 | self.log("[!] Could not initialize debugging!") 402 | 403 | if os.path.exists(DIR_DUMPS): 404 | shutil.rmtree(DIR_DUMPS) 405 | 406 | TE.StopDebug() 407 | self.log("[+] Exit Code: {0:X}.".format(TE.GetExitCode())) 408 | 409 | 410 | def unpack_file(self, filename): 411 | self.log("[+] Filename: {0}".format(filename)) 412 | self.OnInitialize(filename) 413 | self.log("") 414 | return self.UNPACKING_RESULT 415 | 416 | -------------------------------------------------------------------------------- /unpacker/src/yara/emotet_code.yar: -------------------------------------------------------------------------------- 1 | rule EmotetPayloadCode{ 2 | meta: 3 | author = "d00rt - @D00RT_RM" 4 | 5 | version = "1.0.0" 6 | maintainer = "d00rt - @D00RT_RM" 7 | email = "d00rt.fake@gmail.com" 8 | status = "Testing" 9 | 10 | strings: 11 | $ip_pattern = { 12 | 40 ?? ?? ?? ?? ?? 83 3C C5 ?? ?? ?? ?? 00 75 ?? ?? E8 13 | } 14 | 15 | $key = { 16 | 89 ?? ?? ?? ?? ?? B? 01 00 00 00 C7 05 ?? ?? ?? 17 | ?? ?? ?? ?? ?? C7 05 ?? ?? ?? ?? 6A 00 00 00 C3 18 | } 19 | 20 | $old_version_pattern = { 21 | C7 05 ?? ?? ?? ?? 00 00 00 00 C7 05 ?? ?? ?? ?? 22 | ?? ?? ?? ?? C7 05 ?? ?? ?? ?? ?? ?? ?? ?? E8 ?? 23 | ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? 24 | ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? 25 | } 26 | 27 | condition: 28 | ($ip_pattern and $key) or $old_version_pattern 29 | } 30 | 31 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /unpacker/src/yara/emotet_rsa_key.yar: -------------------------------------------------------------------------------- 1 | rule EmotetRSAKey1 2 | { 3 | meta: 4 | author = "d00rt - @D00RT_RM" 5 | 6 | version = "1.0.0" 7 | maintainer = "d00rt - @D00RT_RM" 8 | email = "d00rt.fake@gmail.com" 9 | status = "Testing" 10 | 11 | strings: 12 | $rsa_key = { 13 | 30 68 02 61 00 A5 AB 62 8D A5 4B E7 97 A5 0F 24 14 | B3 BA 95 D4 35 6D 60 17 F6 29 FC E9 EA ED 97 C1 15 | 31 B3 B8 47 E2 20 8A DF 04 33 92 2D 44 31 DF 1F 16 | B0 84 DD 3D F3 8C 59 08 98 C3 BF 7B D1 46 04 15 17 | FF EF 50 9C 6E 08 88 E7 1C 10 30 0E B1 61 15 42 18 | 68 AE 4A 0E 49 6D 4B 4A 42 00 36 6B 27 9D D0 A2 19 | 85 BD FE BF 5B 02 03 01 20 | } 21 | condition: 22 | $rsa_key 23 | } 24 | 25 | rule EmotetRSAKey2 26 | { 27 | meta: 28 | author = "d00rt - @D00RT_RM" 29 | 30 | version = "1.0.0" 31 | maintainer = "d00rt - @D00RT_RM" 32 | email = "d00rt.fake@gmail.com" 33 | status = "Testing" 34 | 35 | strings: 36 | $rsa_key = { 37 | 30 68 02 61 00 A2 CE B7 FD 7F C9 91 FC 55 58 F3 38 | C8 D0 11 5D C7 12 F6 38 70 9B 50 28 05 B5 6F 3D 39 | C1 5B CE 17 50 77 C0 59 1D 3F 55 C4 94 25 03 20 40 | 1D 93 D8 05 A0 AA 2A AD 7A E3 F2 50 02 5E 36 47 41 | C0 D8 E1 FD D1 CE 6F A9 93 55 8C 5A 61 7B 55 DB 42 | 09 97 DF 68 43 AD 0A A5 B0 F6 8B B7 71 BE FE 42 43 | B9 EB 41 C6 5B 02 03 01 44 | } 45 | condition: 46 | $rsa_key 47 | } 48 | 49 | rule EmotetRSAKey3 50 | { 51 | meta: 52 | author = "d00rt - @D00RT_RM" 53 | 54 | version = "1.0.0" 55 | maintainer = "d00rt - @D00RT_RM" 56 | email = "d00rt.fake@gmail.com" 57 | status = "Testing" 58 | 59 | strings: 60 | $rsa_key = { 61 | 30 68 02 61 00 BC E6 5F DB 9F E4 BE 71 0F F8 94 62 | FB A4 43 73 D6 C2 C3 6D 67 33 AA 25 02 51 35 DA 63 | 0D FA C4 D0 C7 C7 1D F3 A8 F5 58 3F CA F1 97 DC 64 | BA F5 B4 4D 49 AF 9B 75 35 DE 52 D0 ED 9B 32 1A 65 | 5D 5D A6 90 5E 3D 24 A5 F3 F3 EF 75 A2 1B 6C 2F 66 | F1 FE C2 7C 9A 9F FB 43 52 22 C1 1B A8 8E 11 D3 67 | E3 22 5A A7 6F 02 03 01 68 | } 69 | condition: 70 | $rsa_key 71 | } 72 | 73 | rule EmotetRSAKey4 74 | { 75 | meta: 76 | author = "d00rt - @D00RT_RM" 77 | 78 | version = "1.0.0" 79 | maintainer = "d00rt - @D00RT_RM" 80 | email = "d00rt.fake@gmail.com" 81 | status = "Testing" 82 | 83 | strings: 84 | $rsa_key = { 85 | 30 68 02 61 00 B3 80 57 F7 D2 29 E9 78 3E 22 60 86 | 18 A7 72 79 09 D0 AB FA 12 1A EB FB 6F 0F FD 3F 87 | 4D 1F D5 4D F4 87 55 A0 AB 2C C8 53 33 53 30 B5 88 | DD 63 E9 43 53 19 1C 30 49 D7 CA A1 F0 90 A3 C9 89 | 58 B1 28 84 3F E2 C3 0B F4 65 F2 B6 DE 59 A9 3A 90 | B1 79 56 2E CB 49 49 B0 2C 3A EE 16 2F 42 A6 60 91 | 9D 2A 21 58 FB 02 03 01 00 01 92 | } 93 | condition: 94 | $rsa_key 95 | } 96 | 97 | rule EmotetRSAKeyOldVersion1 98 | { 99 | meta: 100 | author = "d00rt - @D00RT_RM" 101 | 102 | version = "1.0.0" 103 | maintainer = "d00rt - @D00RT_RM" 104 | email = "d00rt.fake@gmail.com" 105 | status = "Testing" 106 | 107 | strings: 108 | $rsa_key = { 109 | 30 68 02 61 00 AE 26 2B 2D 54 40 57 4E 52 4C C4 110 | 50 2C CE 70 F0 69 9B 6E A5 0D D4 10 80 C1 D0 BC 111 | E2 B7 38 84 C1 47 0C DE 80 07 CE A1 12 FB AD 98 112 | FC B9 7E 2C 5D C8 EF B1 57 82 CD 00 2E E6 0E 4B 113 | 8E A1 98 C3 8C 4F 08 95 FE 96 5C 47 AE 72 A8 CD 114 | FE 89 1C 5A 1A 96 29 83 CE 36 D5 D5 E0 55 D3 14 115 | 58 70 42 81 EB 02 03 01 00 01 116 | } 117 | condition: 118 | $rsa_key 119 | } -------------------------------------------------------------------------------- /unpacker/src/yara/hooks.yar: -------------------------------------------------------------------------------- 1 | rule Layer2Hooks { 2 | 3 | meta: 4 | author = "d00rt - @D00RT_RM" 5 | 6 | version = "1.0.0" 7 | maintainer = "d00rt - @D00RT_RM" 8 | email = "d00rt.fake@gmail.com" 9 | status = "Testing" 10 | 11 | strings: 12 | $size = { 13 | 01 ?? 8B ?? ?? 83 ?? 01 81 ?? ?? ?? ?? ?? 89 14 | } 15 | 16 | $hooks1 = { 17 | 8B 45 ?? 8B 4D ?? 31 D2 8D 35 ?? ?? ?? ?? BF FB FF FF FF 8D 1D ?? ?? ?? ?? 83 C3 FB 18 | } 19 | 20 | $hooks2 = { 21 | E8 ?? ?? ?? ?? 8D ?? ?? ?? ?? ?? 8B 4D ?? 6B D1 0C 01 D0 83 C0 08 22 | } 23 | 24 | $hooks3 = { 25 | 8B 45 ?? 8B 4D ?? 8D 15 ?? ?? ?? ?? ?? FB FF FF FF 8D 3D ?? ?? ?? ?? 83 C7 FB 26 | } 27 | 28 | condition: 29 | $size and ($hooks1 or $hooks2 or $hooks3) 30 | } 31 | --------------------------------------------------------------------------------