├── Atomic Stealer ├── amos_config_extract_idapython-3-2025.py ├── amos_config_extract_idapython-4-2025.py └── idapython_amos_stealer_string_decrypt.py ├── Emotet ├── emotet_dll_resolver.py └── emotet_string_decrypt_ida_debugger.py ├── LockBit └── lockbit_string_decrypt.py ├── MeduzaStealer └── idapython_meduza_stealer_string_decrypt.py ├── RaccoonStealer └── raccoon_stealer_string_decrypt_IDAPython.py ├── Resident └── crc32_calc_CobaltStrike_Resident_campaign.py ├── StealC └── stealc_idapython.py └── Vidar ├── IDAPython_Vidar_TEST.py ├── Vidar_Stealer_3.7_RC4_string_decryption.py └── vidar_string_decrypt_IDAPython.py /Atomic Stealer/amos_config_extract_idapython-3-2025.py: -------------------------------------------------------------------------------- 1 | # Author: RussianPanda 2 | # Samples: 9fbf5b97697355937f470d68620fe7c917f48831731bdd43e9a48f0f02f73030 3 | # b0a567a7b1704d8794c58be47afa3b375677b5b4c2afd9e43128ee0afa0ade42 4 | 5 | import ida_bytes 6 | import ida_segment 7 | import ida_funcs 8 | import ida_ua 9 | import idaapi 10 | import idc 11 | import idautils 12 | import os 13 | import binascii 14 | import base64 15 | import re 16 | 17 | def extract_base_value_from_fn(func_addr): 18 | func = ida_funcs.get_func(func_addr) 19 | if not func: 20 | return None 21 | 22 | base_value = None 23 | 24 | for ea in idautils.Heads(func.start_ea, func.end_ea): 25 | if idc.print_insn_mnem(ea) == "mov": 26 | if idc.get_operand_type(ea, 1) == 5: 27 | value = idc.get_operand_value(ea, 1) 28 | 29 | if 0x300 <= value <= 0x500: 30 | next_ea = ea 31 | for _ in range(10): 32 | next_ea = idc.next_head(next_ea) 33 | if next_ea >= func.end_ea: 34 | break 35 | 36 | if idc.print_insn_mnem(next_ea) == "sub": 37 | return value 38 | 39 | return base_value 40 | 41 | def find_calls(pattern_addr, max_before=40, max_after=20): 42 | results = { 43 | "before": [], 44 | "after": [] 45 | } 46 | 47 | current = pattern_addr 48 | for _ in range(max_before): 49 | prev = idc.prev_head(current) 50 | if prev == idaapi.BADADDR: 51 | break 52 | 53 | if ida_bytes.get_byte(prev) == 0xE8: 54 | offset = ida_bytes.get_dword(prev + 1) 55 | target = prev + 5 + offset 56 | results["before"].append((prev, target)) 57 | 58 | current = prev 59 | 60 | lea_ea = pattern_addr + 5 61 | if lea_ea != idaapi.BADADDR: 62 | lea_size = idc.get_item_size(lea_ea) 63 | if lea_size > 0: 64 | current = lea_ea + lea_size 65 | 66 | for _ in range(max_after): 67 | if current == idaapi.BADADDR: 68 | break 69 | 70 | if ida_bytes.get_byte(current) == 0xE8: 71 | offset = ida_bytes.get_dword(current + 1) 72 | target = current + 5 + offset 73 | results["after"].append((current, target)) 74 | 75 | current = idc.next_head(current) 76 | 77 | return results 78 | 79 | def analyze_fn(func_addr, base_values): 80 | func = ida_funcs.get_func(func_addr) 81 | if not func: 82 | return None 83 | 84 | immediates = [] 85 | 86 | ea = func.start_ea 87 | while ea < func.end_ea: 88 | insn_len = ida_ua.decode_insn(ida_ua.insn_t(), ea) 89 | if insn_len == 0: 90 | ea += 1 91 | continue 92 | 93 | disasm = idc.generate_disasm_line(ea, 0) 94 | 95 | if "mov" in disasm.lower() and ", " in disasm: 96 | parts = disasm.split(", ") 97 | if len(parts) == 2: 98 | val_part = parts[1].strip() 99 | try: 100 | if "0x" in val_part: 101 | value = int(val_part.split("0x")[1].split()[0], 16) 102 | elif val_part.endswith("h"): 103 | value = int(val_part[:-1], 16) 104 | elif val_part.isdigit(): 105 | value = int(val_part) 106 | else: 107 | ea += insn_len 108 | continue 109 | 110 | immediates.append(value) 111 | except ValueError: 112 | pass 113 | 114 | ea += insn_len 115 | 116 | for base in base_values: 117 | for val in immediates: 118 | if val < base: 119 | result = base - val 120 | if 32 <= result <= 126: 121 | return chr(result) 122 | 123 | for val1 in immediates: 124 | for val2 in immediates: 125 | if val1 == val2: 126 | continue 127 | 128 | result = val1 - val2 129 | if 32 <= result <= 126: 130 | return chr(result) 131 | 132 | return None 133 | 134 | def find_string_chars(pattern_addr, global_base_values): 135 | """Find all possible characters for a pattern""" 136 | calls = find_calls(pattern_addr) 137 | 138 | first_char = None 139 | last_char = None 140 | 141 | pattern_base_values = [] 142 | 143 | for _, target in calls["before"]: 144 | base_value = extract_base_value_from_fn(target) 145 | if base_value: 146 | pattern_base_values.append(base_value) 147 | 148 | for _, target in calls["after"]: 149 | base_value = extract_base_value_from_fn(target) 150 | if base_value: 151 | pattern_base_values.append(base_value) 152 | 153 | base_values_to_use = pattern_base_values if pattern_base_values else global_base_values 154 | 155 | for call_addr, target in calls["before"]: 156 | char = analyze_fn(target, base_values_to_use) 157 | if char: 158 | first_char = char 159 | break 160 | 161 | for call_addr, target in calls["after"]: 162 | char = analyze_fn(target, base_values_to_use) 163 | if char: 164 | last_char = char 165 | break 166 | 167 | result = "" 168 | if first_char: 169 | result += first_char 170 | if last_char: 171 | result += last_char 172 | 173 | return result 174 | 175 | def deduplicate_string(duplicated_string): 176 | if not duplicated_string: 177 | return "" 178 | 179 | fixed_string = "" 180 | i = 0 181 | while i < len(duplicated_string): 182 | fixed_string += duplicated_string[i] 183 | 184 | if i+1 < len(duplicated_string) and duplicated_string[i] == duplicated_string[i+1]: 185 | i += 2 186 | else: 187 | i += 1 188 | 189 | return fixed_string 190 | 191 | def add_base64_padding(data): 192 | return data + '=' * (-len(data) % 4) 193 | 194 | def decode_base64_with_custom_alphabet(encoded_data, custom_alphabet): 195 | standard_b64_alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/" 196 | 197 | translation_table = str.maketrans(custom_alphabet, standard_b64_alphabet) 198 | 199 | standard_b64_data = encoded_data.translate(translation_table) 200 | 201 | padded_data = add_base64_padding(standard_b64_data) 202 | 203 | return base64.b64decode(padded_data) 204 | 205 | def extract_and_print_config_details(decoded_text): 206 | print("\n" + "=" * 50) 207 | print("EXTRACTED CONFIGURATION") 208 | print("=" * 50) 209 | 210 | user_value = None 211 | build_id_value = None 212 | url = None 213 | 214 | applescript_pattern = r'user: ([A-Za-z0-9+/=\-_]+)\\".*?BuildID: ([A-Za-z0-9+/=\-_]+)\\"' 215 | applescript_match = re.search(applescript_pattern, decoded_text) 216 | 217 | if applescript_match: 218 | user_value = applescript_match.group(1) 219 | build_id_value = applescript_match.group(2) 220 | else: 221 | user_match = re.search(r'user:\s*([A-Za-z0-9+/=\-_]+)[\\"]', decoded_text) 222 | if user_match: 223 | user_value = user_match.group(1) 224 | 225 | build_match = re.search(r'BuildID:\s*([A-Za-z0-9+/=\-_]+)[\\"]', decoded_text) 226 | if build_match: 227 | build_id_value = build_match.group(1) 228 | 229 | url_match = re.search(r'http[s]?://[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+/[a-z]+', decoded_text) 230 | if url_match: 231 | url = url_match.group(0) 232 | 233 | if user_value: 234 | print(f"User: {user_value}") 235 | if build_id_value: 236 | print(f"BuildID: {build_id_value}") 237 | if url: 238 | print(f"C2 URL: {url}") 239 | 240 | print("=" * 50) 241 | 242 | 243 | summary = "" 244 | if user_value: 245 | summary += f"User: {user_value}\n" 246 | if build_id_value: 247 | summary += f"BuildID: {build_id_value}\n" 248 | if url: 249 | summary += f"C2 URL: {url}\n" 250 | 251 | if summary: 252 | idaapi.info(f"Extracted Configuration\n\n{summary}") 253 | 254 | return summary 255 | 256 | def generate_string(): 257 | print("AMOS Stealer Configuration Extractor") 258 | print("=" * 50) 259 | 260 | seg = ida_segment.get_segm_by_name("__text") 261 | if not seg: 262 | seg = ida_segment.get_first_seg() 263 | 264 | start_ea = seg.start_ea 265 | end_ea = seg.end_ea 266 | 267 | pattern_addrs = [] 268 | current_ea = start_ea 269 | 270 | print("Scanning for patterns...") 271 | while current_ea < end_ea: 272 | if (ida_bytes.get_byte(current_ea) == 0xE9 and 273 | ida_bytes.get_dword(current_ea + 1) == 0): 274 | 275 | next_ea = current_ea + 5 276 | if next_ea < end_ea and ida_bytes.get_byte(next_ea) == 0x48 and ida_bytes.get_byte(next_ea + 1) == 0x8D: 277 | pattern_addrs.append(current_ea) 278 | 279 | current_ea = idc.next_head(current_ea) 280 | 281 | print(f"Found {len(pattern_addrs)} patterns") 282 | 283 | print("Collecting global base values...") 284 | global_base_values = [] 285 | 286 | for addr in pattern_addrs[:min(50, len(pattern_addrs))]: 287 | calls = find_calls(addr) 288 | 289 | for _, target in calls["before"] + calls["after"]: 290 | base_value = extract_base_value_from_fn(target) 291 | if base_value and base_value not in global_base_values: 292 | global_base_values.append(base_value) 293 | print(f"Found base value: {hex(base_value)}") 294 | 295 | print(f"Found {len(global_base_values)} global base values: {[hex(x) for x in global_base_values]}") 296 | 297 | pattern_chars = {} 298 | 299 | for i, addr in enumerate(pattern_addrs): 300 | if i % 5000 == 0: 301 | print(f"Processed {i}/{len(pattern_addrs)} patterns") 302 | 303 | chars = find_string_chars(addr, global_base_values) 304 | 305 | if chars: 306 | pattern_chars[addr] = chars 307 | 308 | print(f"Found characters for {len(pattern_chars)} patterns") 309 | 310 | strings = {} 311 | sorted_addrs = sorted(pattern_chars.keys()) 312 | 313 | if sorted_addrs: 314 | current_string = "" 315 | current_start = None 316 | 317 | for i, addr in enumerate(sorted_addrs): 318 | if current_start is None: 319 | current_start = addr 320 | current_string = pattern_chars[addr] 321 | continue 322 | 323 | if i > 0 and addr - sorted_addrs[i-1] < 200: 324 | current_string += pattern_chars[addr] 325 | else: 326 | if len(current_string) > 2: 327 | deduped_string = deduplicate_string(current_string) 328 | 329 | if len(deduped_string) > 2000 or len(deduped_string) == 128: 330 | strings[current_start] = deduped_string 331 | 332 | current_start = addr 333 | current_string = pattern_chars[addr] 334 | 335 | if current_start and len(current_string) > 2: 336 | deduped_string = deduplicate_string(current_string) 337 | 338 | if len(deduped_string) > 2000 or len(deduped_string) == 128: 339 | strings[current_start] = deduped_string 340 | 341 | print(f"Found {len(strings)} strings matching criteria") 342 | 343 | output_dir = r"C:\russianpanda" 344 | if not os.path.exists(output_dir): 345 | os.makedirs(output_dir) 346 | 347 | output_file = os.path.join(output_dir, "filtered_strings.txt") 348 | 349 | alphabet_string = None 350 | encoded_data_string = None 351 | 352 | for addr, string in sorted(strings.items()): 353 | if len(string) == 128: 354 | alphabet_string = string 355 | alphabet_addr = addr 356 | elif len(string) > 2000: 357 | encoded_data_string = string 358 | encoded_addr = addr 359 | 360 | with open(output_file, "w") as f: 361 | 362 | for addr, string in sorted(strings.items()): 363 | f.write(f"String at 0x{addr:X}: \"{string}\"\n") 364 | f.write(f"Length: {len(string)}\n") 365 | 366 | hex_string = ' '.join(f"{ord(c):02X}" for c in string) 367 | f.write(f"Hex: {hex_string}\n\n") 368 | 369 | 370 | decoded_text = None 371 | 372 | if alphabet_string and encoded_data_string: 373 | try: 374 | print("\nAttempting to decode using custom Base64...") 375 | print(f"Custom alphabet found at 0x{alphabet_addr:X} (length: {len(alphabet_string)})") 376 | print(f"Encoded data found at 0x{encoded_addr:X} (length: {len(encoded_data_string)})") 377 | 378 | alphabet_binary = binascii.unhexlify(alphabet_string) 379 | custom_alphabet = alphabet_binary.decode(errors="ignore") 380 | 381 | encoded_binary = binascii.unhexlify(encoded_data_string) 382 | encoded_data = encoded_binary.decode(errors="ignore") 383 | 384 | decoded_data = decode_base64_with_custom_alphabet(encoded_data, custom_alphabet) 385 | decoded_text = decoded_data.decode(errors="replace") 386 | 387 | decoded_file = os.path.join(output_dir, "decoded_config.txt") 388 | with open(decoded_file, "w") as f: 389 | f.write(decoded_text) 390 | 391 | print(f"\nDecoded data saved to {decoded_file}") 392 | 393 | extract_and_print_config_details(decoded_text) 394 | 395 | except Exception as e: 396 | print(f"Error during decoding process: {e}") 397 | else: 398 | if not alphabet_string: 399 | print("\nCould not find a 128-byte string for the custom alphabet.") 400 | if not encoded_data_string: 401 | print("\nCould not find a blob for the encoded data.") 402 | 403 | return strings 404 | 405 | try: 406 | generate_string() 407 | except Exception as e: 408 | print(f"Error: {str(e)}") 409 | import traceback 410 | traceback.print_exc() 411 | -------------------------------------------------------------------------------- /Atomic Stealer/amos_config_extract_idapython-4-2025.py: -------------------------------------------------------------------------------- 1 | # Author: RussianPanda 2 | # Sample: e37d314adb19bb463dd3257d5609df37d1fbe3eb21567b89b7d3352ee23451d7 3 | 4 | import ida_bytes 5 | import ida_segment 6 | import ida_funcs 7 | import ida_ua 8 | import idaapi 9 | import idc 10 | import idautils 11 | import re 12 | import binascii 13 | import base64 14 | import traceback 15 | 16 | def extract_chr_from_fn(func_addr): 17 | func = ida_funcs.get_func(func_addr) 18 | if not func: 19 | return None 20 | 21 | ea = func.start_ea 22 | while ea < func.end_ea: 23 | if idc.print_insn_mnem(ea) == "mov" and idc.print_operand(ea, 0) == "eax": 24 | if idc.get_operand_type(ea, 1) == 5: 25 | base_value = idc.get_operand_value(ea, 1) 26 | 27 | next_ea = idc.next_head(ea) 28 | if next_ea < func.end_ea and idc.print_insn_mnem(next_ea) == "sub": 29 | if idc.print_operand(next_ea, 0) == "eax": 30 | if idc.get_operand_type(next_ea, 1) == 4: 31 | var_name = idc.print_operand(next_ea, 1) 32 | 33 | for search_ea in range(func.start_ea, next_ea): 34 | if idc.print_insn_mnem(search_ea) == "mov": 35 | if idc.print_operand(search_ea, 0) == var_name: 36 | sub_value = idc.get_operand_value(search_ea, 1) 37 | result = base_value - sub_value 38 | 39 | if 32 <= result <= 126: 40 | return chr(result) 41 | 42 | ea = idc.next_head(ea) 43 | 44 | return None 45 | 46 | def is_hex_str(s, min_ratio=0.9): 47 | hex_chars = sum(1 for c in s if c in '0123456789abcdefABCDEF') 48 | return hex_chars / len(s) >= min_ratio if len(s) > 0 else False 49 | 50 | def add_base64_padding(data): 51 | return data + '=' * (-len(data) % 4) 52 | 53 | def decode_base64_with_custom_alphabet(encoded_data, custom_alphabet, chunk_size=10000): 54 | standard_b64_alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/" 55 | 56 | try: 57 | alphabet_bin = binascii.unhexlify(custom_alphabet) 58 | custom_alphabet_str = alphabet_bin.decode('utf-8', errors='replace') 59 | 60 | sample_encoded = encoded_data[:min(1000, len(encoded_data))] 61 | encoded_bin_sample = binascii.unhexlify(sample_encoded) 62 | encoded_sample_str = encoded_bin_sample.decode('utf-8', errors='replace') 63 | 64 | encoded_chunks = [] 65 | for i in range(0, len(encoded_data), chunk_size*2): # *2 because each hex char is 0.5 bytes 66 | chunk = encoded_data[i:i+chunk_size*2] 67 | encoded_bin = binascii.unhexlify(chunk) 68 | encoded_str = encoded_bin.decode('utf-8', errors='replace') 69 | encoded_chunks.append(encoded_str) 70 | 71 | encoded_data_str = ''.join(encoded_chunks) 72 | 73 | except Exception as e: 74 | print(f"Error converting hex: {str(e)}") 75 | return None 76 | 77 | if len(custom_alphabet_str) > 64: 78 | custom_alphabet_str = custom_alphabet_str[:64] 79 | print(f"Trimmed alphabet to 64 characters") 80 | elif len(custom_alphabet_str) < 64: 81 | padding_needed = 64 - len(custom_alphabet_str) 82 | custom_alphabet_str += "A" * padding_needed 83 | print(f"Padded alphabet with {padding_needed} 'A's") 84 | 85 | translation_table = str.maketrans(custom_alphabet_str, standard_b64_alphabet) 86 | 87 | decoded_chunks = [] 88 | total_length = len(encoded_data_str) 89 | 90 | for i in range(0, total_length, chunk_size): 91 | chunk = encoded_data_str[i:i+chunk_size] 92 | 93 | standard_b64_chunk = chunk.translate(translation_table) 94 | 95 | for approach in range(3): 96 | try: 97 | if approach == 0: 98 | padded_data = add_base64_padding(standard_b64_chunk) 99 | decoded_chunk = base64.b64decode(padded_data) 100 | elif approach == 1: 101 | decoded_chunk = base64.b64decode(standard_b64_chunk) 102 | else: 103 | decoded_chunk = base64.b64decode(standard_b64_chunk + "==") 104 | 105 | decoded_chunks.append(decoded_chunk) 106 | break 107 | except Exception as e: 108 | if approach == 2: 109 | print(f"Failed to decode chunk at position {i}: {str(e)}") 110 | 111 | if decoded_chunks: 112 | return b''.join(decoded_chunks) 113 | 114 | return None 115 | 116 | def extract_config_from_text(decoded_text): 117 | config = {} 118 | 119 | user_patterns = [ 120 | r'user:\s*([A-Za-z0-9+/=\-_]+)[\\"]', 121 | r'[uU]ser[^:]*:\s*["\']?([A-Za-z0-9+/=\-_]+)["\']?' 122 | ] 123 | 124 | for pattern in user_patterns: 125 | match = re.search(pattern, decoded_text) 126 | if match: 127 | config['user'] = match.group(1) 128 | break 129 | 130 | build_patterns = [ 131 | r'BuildID:\s*([A-Za-z0-9+/=\-_]+)[\\"]', 132 | r'[bB]uild[^:]*:\s*["\']?([A-Za-z0-9+/=\-_]+)["\']?' 133 | ] 134 | 135 | for pattern in build_patterns: 136 | match = re.search(pattern, decoded_text) 137 | if match: 138 | config['build_id'] = match.group(1) 139 | break 140 | 141 | url_patterns = [ 142 | r'https?://[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+/[a-zA-Z0-9_/\-\.]+', 143 | r'https?://[a-zA-Z0-9][a-zA-Z0-9\-\.]+\.[a-zA-Z]{2,}/[a-zA-Z0-9_/\-\.]+', 144 | r'https?://[^/\s"\']+' 145 | ] 146 | 147 | for pattern in url_patterns: 148 | match = re.search(pattern, decoded_text) 149 | if match: 150 | config['url'] = match.group(0) 151 | break 152 | 153 | return config 154 | 155 | def find_close_chr(funcs, start_idx, direction=1, max_distance=100): 156 | chars = [] 157 | current_idx = start_idx 158 | prev_func = funcs[start_idx] 159 | 160 | while 0 <= current_idx < len(funcs): 161 | current_idx += direction 162 | if current_idx < 0 or current_idx >= len(funcs): 163 | break 164 | 165 | current_func = funcs[current_idx] 166 | distance = abs(current_func - prev_func) 167 | 168 | if distance > max_distance: 169 | break 170 | 171 | char = extract_chr_from_fn(current_func) 172 | if char: 173 | chars.append((current_func, char)) 174 | prev_func = current_func 175 | else: 176 | break 177 | 178 | return chars 179 | 180 | def extract_str(funcs, start_idx, max_distance=100): 181 | start_func = funcs[start_idx] 182 | start_char = extract_chr_from_fn(start_func) 183 | if not start_char: 184 | return None, None 185 | 186 | forward_chars = find_close_chr(funcs, start_idx, 1, max_distance) 187 | backward_chars = find_close_chr(funcs, start_idx, -1, max_distance) 188 | 189 | backward_chars.reverse() 190 | all_chars = backward_chars + [(start_func, start_char)] + forward_chars 191 | 192 | string = ''.join(char for _, char in all_chars) 193 | start_addr = all_chars[0][0] if all_chars else start_func 194 | 195 | return start_addr, string 196 | 197 | def ad_extact(): 198 | print("=" * 50) 199 | 200 | funcs = [] 201 | for ea in idautils.Functions(): 202 | funcs.append(ea) 203 | 204 | print(f"Found {len(funcs)} total functions") 205 | 206 | funcs.sort() 207 | 208 | sample_rate = 100 209 | samples = [] 210 | for i in range(0, len(funcs), sample_rate): 211 | samples.append(i) 212 | 213 | print(f"Testing {len(samples)} sample functions...") 214 | 215 | strings = {} 216 | for i, idx in enumerate(samples): 217 | if i % 10 == 0: 218 | print(f"Testing sample {i}/{len(samples)}...") 219 | 220 | if idx >= len(funcs): 221 | continue 222 | 223 | start_addr, string = extract_str(funcs, idx) 224 | if string and len(string) > 10: 225 | strings[start_addr] = string 226 | print(f"Found string at 0x{start_addr:X}, length={len(string)}") 227 | 228 | hex_strings = {} 229 | hex_alphabet_candidates = [] 230 | hex_encoded_data_candidates = [] 231 | 232 | for addr, string in strings.items(): 233 | if is_hex_str(string): 234 | hex_strings[addr] = string 235 | 236 | if 120 <= len(string) <= 140: 237 | hex_alphabet_candidates.append((addr, string)) 238 | print(f"Found potential hex-encoded alphabet at 0x{addr:X}, length={len(string)}") 239 | 240 | elif len(string) > 500: 241 | hex_encoded_data_candidates.append((addr, string)) 242 | print(f"Found potential hex-encoded data at 0x{addr:X}, length={len(string)}") 243 | 244 | print(f"Found {len(hex_alphabet_candidates)} potential hex-encoded alphabets") 245 | print(f"Found {len(hex_encoded_data_candidates)} potential hex-encoded data blocks") 246 | 247 | if hex_alphabet_candidates and hex_encoded_data_candidates: 248 | for alpha_idx, (alpha_addr, alphabet) in enumerate(hex_alphabet_candidates): 249 | for data_idx, (data_addr, encoded_data) in enumerate(hex_encoded_data_candidates): 250 | 251 | sample_size = min(10000, len(encoded_data)) 252 | sample_data = encoded_data[:sample_size] 253 | 254 | decoded_data = decode_base64_with_custom_alphabet(sample_data, alphabet) 255 | 256 | if decoded_data: 257 | print(" Initial decoding successful, decoding full data...") 258 | 259 | full_decoded_data = decode_base64_with_custom_alphabet(encoded_data, alphabet) 260 | 261 | if full_decoded_data: 262 | None 263 | 264 | try: 265 | decoded_text = full_decoded_data.decode('utf-8', errors='replace') 266 | 267 | print(f" First 200 chars of decoded text: {decoded_text[:200]}...") 268 | 269 | config = extract_config_from_text(decoded_text) 270 | 271 | if config: 272 | print("\n Extracted Configuration:") 273 | for key, value in config.items(): 274 | print(f" {key.capitalize()}: {value}") 275 | 276 | config_str = "\n".join([f"{key.capitalize()}: {value}" for key, value in config.items()]) 277 | idaapi.info(f"AMOS Stealer Configuration\n\n{config_str}") 278 | 279 | return True 280 | else: 281 | print(" No configuration found in decoded text") 282 | except Exception as e: 283 | print(f" Error interpreting as text: {str(e)}") 284 | else: 285 | print(" Full decoding failed") 286 | else: 287 | print(" Initial decoding failed") 288 | 289 | print("\nAll decoding attempts failed") 290 | else: 291 | print("Not enough candidates for decoding") 292 | if not hex_alphabet_candidates: 293 | print("Missing hex-encoded alphabet candidates") 294 | if not hex_encoded_data_candidates: 295 | print("Missing hex-encoded data candidates") 296 | 297 | return False 298 | 299 | def scan_for_strings_by_size(): 300 | print("=" * 50) 301 | 302 | target_sizes = { 303 | (120, 140): "alphabet", 304 | (500, 60000): "encoded" 305 | } 306 | 307 | segments = [] 308 | for seg in idautils.Segments(): 309 | segments.append((idc.get_segm_start(seg), idc.get_segm_end(seg))) 310 | 311 | print(f"Found {len(segments)} segments") 312 | 313 | strings = {} 314 | hex_alphabet_candidates = [] 315 | hex_encoded_data_candidates = [] 316 | 317 | for seg_start, seg_end in segments: 318 | print(f"Scanning segment 0x{seg_start:X} - 0x{seg_end:X}") 319 | 320 | funcs_in_segment = [] 321 | for ea in idautils.Functions(seg_start, seg_end): 322 | funcs_in_segment.append(ea) 323 | 324 | funcs_in_segment.sort() 325 | 326 | i = 0 327 | while i < len(funcs_in_segment): 328 | current_addr = funcs_in_segment[i] 329 | char = extract_chr_from_fn(current_addr) 330 | 331 | if char: 332 | string = char 333 | last_addr = current_addr 334 | j = i + 1 335 | 336 | while j < len(funcs_in_segment): 337 | next_addr = funcs_in_segment[j] 338 | next_char = extract_chr_from_fn(next_addr) 339 | 340 | if next_addr - last_addr < 100 and next_char: 341 | string += next_char 342 | last_addr = next_addr 343 | j += 1 344 | 345 | if len(string) % 5000 == 0 and len(string) > 0: 346 | print(f" Built string of length {len(string)}...") 347 | 348 | for (min_size, max_size), type_name in target_sizes.items(): 349 | if min_size <= len(string) <= max_size: 350 | break 351 | else: 352 | break 353 | 354 | for (min_size, max_size), type_name in target_sizes.items(): 355 | if min_size <= len(string) <= max_size: 356 | strings[current_addr] = string 357 | print(f"Found {type_name} string at 0x{current_addr:X}, length={len(string)}") 358 | 359 | if is_hex_str(string): 360 | if type_name == "alphabet": 361 | hex_alphabet_candidates.append((current_addr, string)) 362 | elif type_name == "encoded": 363 | hex_encoded_data_candidates.append((current_addr, string)) 364 | 365 | break 366 | 367 | i = j 368 | else: 369 | i += 1 370 | 371 | print(f"Found {len(hex_alphabet_candidates)} potential hex-encoded alphabets") 372 | print(f"Found {len(hex_encoded_data_candidates)} potential hex-encoded data blocks") 373 | 374 | if hex_alphabet_candidates and hex_encoded_data_candidates: 375 | for alpha_idx, (alpha_addr, alphabet) in enumerate(hex_alphabet_candidates): 376 | 377 | for data_idx, (data_addr, encoded_data) in enumerate(hex_encoded_data_candidates): 378 | 379 | sample_size = min(10000, len(encoded_data)) 380 | sample_data = encoded_data[:sample_size] 381 | 382 | decoded_data = decode_base64_with_custom_alphabet(sample_data, alphabet) 383 | 384 | if decoded_data: 385 | 386 | full_decoded_data = decode_base64_with_custom_alphabet(encoded_data, alphabet) 387 | 388 | if full_decoded_data: 389 | None 390 | 391 | try: 392 | decoded_text = full_decoded_data.decode('utf-8', errors='replace') 393 | 394 | config = extract_config_from_text(decoded_text) 395 | 396 | if config: 397 | print("\n Extracted Configuration:") 398 | for key, value in config.items(): 399 | print(f" {key.capitalize()}: {value}") 400 | 401 | config_str = "\n".join([f"{key.capitalize()}: {value}" for key, value in config.items()]) 402 | idaapi.info(f"AMOS Stealer Configuration\n\n{config_str}") 403 | 404 | return True 405 | else: 406 | print(" No configuration found in decoded text") 407 | except Exception as e: 408 | print(f" Error interpreting as text: {str(e)}") 409 | else: 410 | print(" Full decoding failed") 411 | else: 412 | print(" Initial decoding failed") 413 | 414 | print("\nAll size-based decoding attempts failed") 415 | else: 416 | print("Not enough size-based candidates for decoding") 417 | 418 | return False 419 | 420 | def main(): 421 | print("AMOS Stealer Configuration Extractor") 422 | print("=" * 50) 423 | 424 | if scan_for_strings_by_size(): 425 | return True 426 | 427 | if ad_extact(): 428 | return True 429 | 430 | print("\nFailed to extract configuration") 431 | return False 432 | 433 | try: 434 | success = main() 435 | if success: 436 | None 437 | else: 438 | print("\nFailed to extract configuration") 439 | except Exception as e: 440 | print(f"Error: {str(e)}") 441 | traceback.print_exc() -------------------------------------------------------------------------------- /Atomic Stealer/idapython_amos_stealer_string_decrypt.py: -------------------------------------------------------------------------------- 1 | # Author: RussianPanda 2 | # Sample: 57db36e87549de5cfdada568e0d86bff 3 | 4 | import idautils 5 | import idc 6 | import idaapi 7 | import ida_funcs 8 | import yara 9 | import ctypes 10 | 11 | def hex_string_to_byte_array(hex_string): 12 | return bytearray.fromhex(hex_string) 13 | 14 | def decrypt_data(data, byte_data, length, decimal_value): 15 | if not (byte_data[9] & 1): 16 | for i in range(length): 17 | if data[i] == data[i + 1] + 1: 18 | byte_data[i + 10] ^= (byte_data[i + 1] + 1) 19 | elif data[i] == data[i + 1] + 2: 20 | byte_data[i + 10] ^= (byte_data[i + 1] + 2) 21 | elif data[i] == data[i + 1]: 22 | byte_data[i + 10] ^= ctypes.c_uint8(i + decimal_value).value 23 | elif data[i] == data[i + 1] + 4: 24 | byte_data[i + 10] ^= (byte_data[i + 1] + 3) 25 | elif data[i] == data[i + 1] + 5: 26 | byte_data[i + 10] ^= (byte_data[i + 1] + 4) 27 | data[i] += 1 28 | 29 | def hex_to_readable_string(hex_string): 30 | bytes_object = bytes.fromhex(hex_string) 31 | return ''.join(chr(byte) for byte in bytes_object if 32 <= byte <= 127) 32 | 33 | def main(hex_data, decimal_value): 34 | data_length = len(hex_data) // 2 35 | byte_data = hex_string_to_byte_array(hex_data) 36 | data = [0] * data_length 37 | try: 38 | decrypt_data(data, byte_data, data_length - 10, decimal_value) 39 | decrypted_hex_data = ''.join(f"{byte:02X}" for byte in byte_data) 40 | readable_string = hex_to_readable_string(decrypted_hex_data) 41 | except IndexError as e: 42 | #print(f"Error occurred during decryption: {e}") 43 | readable_string = "Decryption Error" 44 | 45 | return readable_string 46 | 47 | # Define YARA rule 48 | str_scan_rule_one = 'rule str_scan { strings: $b = {48 8B 85 ?? ?? FF FF 48 63 8D ?? ?? FF FF 0F BE 04 08 8B 8D ?? ?? FF FF} condition: $b }' 49 | str_scan_rules_one = yara.compile(sources={'str_scan_rule': str_scan_rule_one}) 50 | 51 | str_scan_rule_two = 'rule str_scan_two { strings: $a = {48 8B 45 ?? 48 63 4D ?? 0F BE 04 08 8B 4D ?? 83 C1 ??31 C8 88 C2} condition: $a }' 52 | str_scan_rules_two = yara.compile(sources={'str_scan_rule': str_scan_rule_two}) 53 | 54 | def get_segment_data(segment): 55 | start = segment.start_ea 56 | end = segment.end_ea 57 | return idc.get_bytes(start, end - start) 58 | 59 | def hex_to_decimal(hex_string): 60 | try: 61 | return int(hex_string, 16) 62 | except ValueError: 63 | return None 64 | 65 | def is_pattern(mnemonic, op1, op2): 66 | if mnemonic == "movups" and op1 == "xmm0" and "cs:" in op2: 67 | return "xmmword" in op2 or "__" in op2 68 | elif mnemonic == "mov" and "cs:" in op2: 69 | return ("dword" in op2 or "qword_" in op2) and op1 != "xmm0" 70 | return False 71 | 72 | # Read memory content from an operand and assign the raw hex value to 'value' 73 | def read_mem_from_operand(operand): 74 | if ':' in operand: 75 | address = idc.get_name_ea_simple(operand.split(":")[1]) 76 | else: 77 | address = idc.get_name_ea_simple(operand) 78 | 79 | if address != idaapi.BADADDR: 80 | # Determine the number of bytes to read based on the operand type 81 | if "dword" in operand: 82 | bytes_to_read = 4 # dword = 4 bytes 83 | elif "qword" in operand: 84 | bytes_to_read = 8 # qword = 8 bytes 85 | else: 86 | bytes_to_read = 16 # Default to 16 bytes for other types 87 | 88 | data = idaapi.get_bytes(address, bytes_to_read) 89 | if data: 90 | hex_value = ''.join(format(byte, '02x') for byte in data) 91 | return hex_value 92 | return None 93 | 94 | 95 | def add_ida_comment(address, comment): 96 | idc.set_cmt(address, comment, 0) 97 | 98 | def hex_string_to_decimal(hex_str): 99 | if hex_str.endswith('h'): 100 | return int(hex_str[:-1], 16) 101 | return None 102 | 103 | 104 | def hex_to_signed_decimal(hex_string): 105 | # Assumes the hexadecimal string represents a signed 8-bit integer 106 | number = int(hex_string, 16) 107 | if number >= 2**7: # 2**7 is 128, which is the boundary for a signed 8-bit integer 108 | number -= 2**8 # Adjust for negative values 109 | return number 110 | 111 | def hex_to_signed_8bit(hex_string): 112 | last_8_bits_hex = hex_string[-2:] 113 | is_negative_8bit = int(last_8_bits_hex[0], 16) >= 8 114 | if is_negative_8bit: 115 | return int(last_8_bits_hex, 16) - (1 << 8) 116 | else: 117 | return int(last_8_bits_hex, 16) 118 | 119 | def find_second_lea_for_zero_len_str(function_ea, decryption_decimal_value): 120 | lea_count = 0 121 | last_compare_value = None 122 | found_second_lea = False 123 | for insn_ea in idautils.FuncItems(function_ea): 124 | insn = idaapi.insn_t() 125 | if idaapi.decode_insn(insn, insn_ea) > 0: 126 | if insn.get_canon_mnem() == "lea": 127 | lea_count += 1 128 | if lea_count == 2: 129 | found_second_lea = True 130 | second_operand = idc.print_operand(insn.ea, 1) 131 | address = idc.get_operand_value(insn.ea, 1) 132 | 133 | if found_second_lea and insn.get_canon_mnem() == "cmp": 134 | op2 = idc.print_operand(insn.ea, 1) 135 | compare_value = hex_string_to_decimal(op2) 136 | if compare_value is not None: 137 | last_compare_value = compare_value 138 | 139 | decrypted_lea_data = "" 140 | if last_compare_value is not None and found_second_lea: 141 | total_length = last_compare_value + 13 142 | lea_data = idaapi.get_bytes(address, total_length) 143 | if lea_data: 144 | lea_data_hex = ''.join(format(byte, '02x') for byte in lea_data) 145 | #print(f"Data at address 0x{address:x} (length: {total_length} bytes): {lea_data_hex}") 146 | 147 | # Decrypt the LEA data 148 | byte_data = hex_string_to_byte_array(lea_data_hex) 149 | data = [0] * len(byte_data) 150 | try: 151 | decrypt_data(data, byte_data, len(byte_data) - 10, decryption_decimal_value) 152 | decrypted_hex_data = ''.join(f"{byte:02X}" for byte in byte_data) 153 | decrypted_lea_data = hex_to_readable_string(decrypted_hex_data) 154 | except IndexError as e: 155 | print(f"Error occurred during decryption of data at LEA: {e}") 156 | 157 | else: 158 | print(f"Failed to read data from address 0x{address:x}.") 159 | 160 | return decrypted_lea_data 161 | 162 | # Iterate over all segments 163 | for seg_ea in idautils.Segments(): 164 | segment = idaapi.getseg(seg_ea) 165 | segment_data = get_segment_data(segment) 166 | 167 | if segment_data: 168 | # Get matches for both rules 169 | matches_one = str_scan_rules_one.match(data=segment_data) 170 | matches_two = str_scan_rules_two.match(data=segment_data) 171 | 172 | # Combine matches from both rules 173 | combined_matches = matches_one + matches_two 174 | 175 | for match in combined_matches: 176 | for string in match.strings: 177 | offset, _, _ = string 178 | match_address = segment.start_ea + offset 179 | function = ida_funcs.get_func(match_address) 180 | if function: 181 | function_name = idc.get_func_name(match_address) 182 | #print(f"YARA pattern matched in function: {function_name} at address: 0x{match_address:x}") 183 | 184 | decimal_values = [] 185 | 186 | current_address = match_address 187 | for _ in range(5): 188 | insn = idaapi.insn_t() 189 | idaapi.decode_insn(insn, current_address) 190 | if insn.get_canon_mnem() == "add": 191 | operand_value = insn.ops[1].value 192 | if insn.ops[1].type == idaapi.o_imm: 193 | hex_value = hex(operand_value).rstrip("L").lstrip("0x") or "0" 194 | decimal_value = hex_to_signed_8bit(hex_value) 195 | decimal_values.append(decimal_value) # Store each decimal value 196 | #print(f" 0x{current_address:x}: 'add' instruction (Hex: {hex_value} -> Decimal: {decimal_value})") 197 | 198 | current_address = idaapi.next_head(current_address, idaapi.getseg(current_address).end_ea) 199 | 200 | # Scanning previous instructions until 'call' is found 201 | call_address = None 202 | current_address = match_address 203 | while current_address >= function.start_ea: 204 | insn = idaapi.insn_t() 205 | if idaapi.decode_insn(insn, current_address) > 0: 206 | if insn.get_canon_mnem() == "call": 207 | call_address = current_address 208 | called_function = idaapi.get_name(idc.get_operand_value(current_address, 0)) 209 | called_func_ea = idc.get_name_ea_simple(called_function) 210 | 211 | #print(f" Previous 'call' instruction at: 0x{call_address:x} calling function: {called_function} at address: 0x{called_func_ea:x}") 212 | 213 | # Enter the called function and analyze the first 57 instructions 214 | if called_func_ea != idaapi.BADADDR: 215 | current_address = called_func_ea 216 | values = [] 217 | temp_values = [] 218 | last_cmp_instruction = None 219 | last_cmp_operand = None 220 | compare_value = None 221 | additional_bytes = "" 222 | for _ in range(57): 223 | insn = idaapi.insn_t() 224 | 225 | if idaapi.decode_insn(insn, current_address) > 0: 226 | #print(f" 0x{current_address:x}: {idc.generate_disasm_line(current_address, 0)}") 227 | 228 | # Check if the instruction is a 'cmp' 229 | if insn.get_canon_mnem() == "cmp": 230 | last_cmp_instruction = current_address 231 | op2 = idc.print_operand(insn.ea, 1) 232 | compare_value = hex_string_to_decimal(op2) 233 | 234 | # Print the decimal value only if it's not None 235 | if compare_value is not None: 236 | #print(f"COMPARE VALUE: {compare_value}") 237 | last_cmp_operand = compare_value 238 | 239 | 240 | mnemonic = insn.get_canon_mnem() 241 | op1 = idc.print_operand(insn.ea, 0) 242 | op2 = idc.print_operand(insn.ea, 1) 243 | 244 | if is_pattern(mnemonic, op1, op2) and '+0Ch' not in op2: 245 | xmmword_addr = idc.get_operand_value(insn.ea, 1) # Store the xmmword address 246 | value = read_mem_from_operand(op2) 247 | if value: 248 | temp_values.append(value) 249 | #print(f" 0x{current_address:x}: {idc.generate_disasm_line(current_address, 0)} -> Value: {value}") 250 | 251 | # Process 'add' instructions within the next 5 instructions 252 | if insn.get_canon_mnem() == "add": 253 | operand_value = insn.ops[1].value 254 | if insn.ops[1].type == idaapi.o_imm: 255 | hex_value = hex(operand_value).rstrip("L").lstrip("0x") or "0" 256 | decimal_value = hex_to_decimal(hex_value) 257 | 258 | current_address = idaapi.next_head(current_address, idaapi.getseg(current_address).end_ea) 259 | 260 | encrypted_string = ''.join(reversed(temp_values)) 261 | #print(f'Encrypted string for address 0x{match_address:x}: {encrypted_string}') 262 | values.append(encrypted_string) 263 | 264 | encrypted_string_length = len(encrypted_string) // 2 265 | if encrypted_string_length == 0: 266 | if decimal_values: 267 | decryption_decimal_value = decimal_values[-1] 268 | else: 269 | decryption_decimal_value = None 270 | second_lea_address = find_second_lea_for_zero_len_str(called_func_ea, decryption_decimal_value) 271 | decrypted_lea_data = find_second_lea_for_zero_len_str(called_func_ea, decryption_decimal_value) 272 | print(f"Decrypted string at LEA: {decrypted_lea_data} at 0x{call_address:x}") 273 | 274 | 275 | if decimal_values: 276 | decryption_decimal_value = decimal_values[-1] 277 | main_output = main(encrypted_string, decryption_decimal_value) 278 | else: 279 | print("No Constant Value Found for Decryption.") 280 | 281 | if compare_value is not None and (encrypted_string_length <= compare_value) and xmmword_addr: 282 | additional_bytes_address = xmmword_addr + encrypted_string_length 283 | for _ in range(11): # Grab next 11 bytes to make a total of 12 with the current byte 284 | next_byte = idc.get_bytes(additional_bytes_address, 1) 285 | if next_byte is None: 286 | break 287 | encrypted_string += next_byte.hex() 288 | additional_bytes_address += 1 289 | 290 | if encrypted_string.endswith('000000'): 291 | encrypted_string = encrypted_string[:-6] 292 | elif encrypted_string.endswith('0000'): 293 | encrypted_string = encrypted_string[:-4] 294 | elif encrypted_string.endswith('00'): 295 | encrypted_string = encrypted_string[:-2] 296 | 297 | #print(f"Extended encrypted string for address 0x{match_address:x}: {encrypted_string}") 298 | 299 | if decimal_values: 300 | decryption_decimal_value = decimal_values[-1] 301 | main_output = main(encrypted_string, decryption_decimal_value) 302 | print(f"Decrypted string at address 0x{match_address:x}: {main_output}") 303 | add_ida_comment(call_address, f"{main_output}") 304 | else: 305 | print("No Constant Value Found for Decryption.") 306 | 307 | break 308 | current_address = idaapi.prev_head(current_address, function.start_ea) 309 | -------------------------------------------------------------------------------- /Emotet/emotet_dll_resolver.py: -------------------------------------------------------------------------------- 1 | # reference: https://kienmanowar.wordpress.com/2022/12/19/z2abimonthly-malware-challege-emotet-back-from-the-dead/ 2 | # reference: https://github.com/OALabs/hashdb/blob/main/algorithms/emotet.py 3 | # Emotet string decryption and API/DLL resolver based on the calculated hash. Note: in order to successfully run emotet_dll_resolver.py, you need to set the correct function type definition in IDA ("Y"), for example: __int64 __fastcall sub_18000F174(int a1, int a2), otherwise it will return "NoneType" error 4 | 5 | import idaapi 6 | import idautils 7 | 8 | ea = 0x18000F174 9 | 10 | final_hash_values = [] 11 | get_operand_final_values = [] 12 | args_value = [] 13 | 14 | common_dll = ['kernel32.dll','user32.dll','ntdll.dll','shlwapi.dll','iphlpapi.dll','urlmon.dll','ws2_32.dll','crypt32.dll','shell32.dll','advapi32.dll','gdiplus.dll','gdi32.dll','ole32.dll','psapi.dll','cabinet.dll','imagehlp.dll','netapi32.dll','wtsapi32.dll','mpr.dll','wininet.dll','userenv.dll','bcrypt.dll', 'comctl32.dll', 'comdlg32.dll', 'msvcrt.dll', 'oleaut32.dll', 'srsvc.dll', 'winhttp.dll', 'advpack.dll', 'combase.dll', 'ntoskrnl.exe'] 15 | 16 | # Get operand hash values and addresses 17 | def get_hash_val(ea): 18 | for xref in idautils.CodeRefsTo(ea, 0): 19 | args = idaapi.get_arg_addrs(xref)[1] 20 | args_value.append(args) 21 | get_operand = get_operand_value(args, 1) & 0xffffffff 22 | get_operand_final = hex(get_operand) 23 | get_operand_final_values.append(get_operand_final) 24 | return args_value, get_operand_final_values 25 | 26 | 27 | args_value, hash_val = get_hash_val(0x18000F174) 28 | 29 | 30 | # Get hash values from the list of common DLLs 31 | def hash_calc(dll_name): 32 | hash_value = 0 33 | for c in dll: 34 | hash_value = (((hash_value << 16) & 0xffffffff) + ((hash_value << 6) & 0xffffffff) + ord(c) - hash_value) & 0xffffffff 35 | final_hash_value = hash_value ^ 0x106308C0 36 | return final_hash_value 37 | 38 | for dll in common_dll: 39 | hash_val = hash_calc(dll) 40 | final_hash_values.append(hex(hash_val)) 41 | 42 | common_dll_hash_val = final_hash_values 43 | 44 | # Add the enum with the correct name and flag value 45 | eid = ida_enum.add_enum(0, "DLL_Enum", ida_bytes.hex_flag()) 46 | 47 | # Iterate through the values in hash_val and common_dll, and add them as enum members 48 | for operand, dll in zip(common_dll_hash_val, common_dll): 49 | # Make sure to pass the operand value as an integer, not a string 50 | ida_enum.add_enum_member(eid, '%s_hash' % dll, int(operand,16), idaapi.BADADDR) 51 | 52 | # Retrieving enums 53 | all_enums = ida_enum.get_enum_qty() 54 | for i in range(0, all_enums): 55 | enum_id = ida_enum.getn_enum(i) 56 | mask = ida_enum.get_first_bmask(enum_id) 57 | enum_constant = ida_enum.get_first_enum_member(enum_id, mask) 58 | 59 | # Assign enums to the operands 60 | for i in args_value: 61 | idx_operand = 1 62 | idc.op_enum(i, idx_operand, enum_id, 0) 63 | -------------------------------------------------------------------------------- /Emotet/emotet_string_decrypt_ida_debugger.py: -------------------------------------------------------------------------------- 1 | import idc, idautils, idaapi 2 | 3 | # reference https://kienmanowar.wordpress.com/2022/12/19/z2abimonthly-malware-challege-emotet-back-from-the-dead/ 4 | 5 | decrypt_fn_addr = 0x180025C58 6 | 7 | for addr in idautils.CodeRefsTo(decrypt_fn_addr, 1): 8 | # go into the the string building and decryption wrap function 9 | decrypt_str_addr = idaapi.get_func(addr).start_ea 10 | #get a function name 11 | decrypt_fn_name = idc.get_func_name(decrypt_str_addr) 12 | 13 | # building out the Appcall proto 14 | decrypt_fn_proto = "wchar_t * __fastcall {:s}();".format(decrypt_fn_name) 15 | decrypt_fn_appcall = idaapi.Appcall.proto(decrypt_str_addr, decrypt_fn_proto) 16 | 17 | # calling the decryption function 18 | decrypt_string = decrypt_fn_appcall() 19 | cleaned_str = decrypt_string.replace(b'\x00', b'') 20 | #convert from bytes to string 21 | cleaned_str = cleaned_str.decode('latin-1') 22 | 23 | # setting the comments 24 | idc.set_cmt(addr, cleaned_str, 0) 25 | idc.set_func_cmt(decrypt_str_addr, cleaned_str, 1) 26 | 27 | -------------------------------------------------------------------------------- /LockBit/lockbit_string_decrypt.py: -------------------------------------------------------------------------------- 1 | import idautils 2 | import idaapi 3 | import idc 4 | 5 | # String decrypt function 6 | ea = 0x10001168 7 | operands = [] 8 | 9 | def xor_decrypt(data, key): 10 | return bytes([~(b ^ key[i % len(key)]) & 0xff for i, b in enumerate(data)]) 11 | xor_key = 'FE9F0311' 12 | key = bytes.fromhex(xor_key) 13 | 14 | # Iterate through code references to start address 15 | for xref in idautils.CodeRefsTo(ea, 0): 16 | # Move to previous instruction 17 | print("decrypt function at: " + hex(xref)) 18 | prev_ea = idc.prev_head(xref) 19 | 20 | # Keep iterating through previous instructions until "lea" is found or beginning of function is reached 21 | while prev_ea >= idaapi.get_func(xref).start_ea: 22 | # Check if instruction is "lea" 23 | if idaapi.print_insn_mnem(prev_ea) == "lea": 24 | 25 | print("Found 'lea' instruction at address: " + hex(prev_ea)) 26 | break 27 | 28 | # Check if mnem is "jnz" 29 | if idaapi.print_insn_mnem(prev_ea) == "jnz": 30 | print("Found 'jnz' instruction at address: " + hex(prev_ea)) 31 | break 32 | # Check if mnem is "jz" 33 | if idaapi.print_insn_mnem(prev_ea) == "jz": 34 | print("Found 'jz' instruction at address: " + hex(prev_ea)) 35 | break 36 | 37 | # Move to previous instruction 38 | prev_ea = idc.prev_head(prev_ea) 39 | 40 | # Iterate through "mov" after "lea" 41 | while prev_ea >= idaapi.get_func(xref).start_ea: 42 | # Check if instruction is "mov" 43 | if idaapi.print_insn_mnem(prev_ea) == "mov" and idaapi.get_byte(prev_ea+2) != 0xF0: 44 | # Get second operand and add it to list of operands 45 | second_operand = idc.print_operand(prev_ea, 1).replace("h", "") 46 | operands.append(int(second_operand, 16).to_bytes(4, byteorder="little")) 47 | print("Second operand of 'mov' instruction at address " + hex(prev_ea) + " is " + second_operand) 48 | 49 | elif idaapi.print_insn_mnem(prev_ea) == "push": 50 | # Concatenate operands and stop iterating if "push" is found 51 | operands_bytes = b"".join(operands) 52 | enc_string = operands_bytes.hex() 53 | data = bytes.fromhex(enc_string) 54 | decrypted_operand = xor_decrypt(data, key).replace(b'\x00', b'') 55 | print("Encoded string: " + enc_string) 56 | 57 | decrypted_str = decrypted_operand.decode("utf-8", errors="ignore") 58 | 59 | # Check if the decrypted string contains printable ASCII chars 60 | printable_chars = [c for c in decrypted_str if ord(c) >= 32 and ord(c) <= 126] 61 | if len(printable_chars) != len(decrypted_str): 62 | print("Decoded string contains non-printable ASCII characters, ignoring") 63 | else: 64 | print("Decoded string: " + decrypted_str) 65 | idc.set_cmt(xref, decrypted_str, 0) # set decrypted string as comment for xref 66 | break 67 | # Move to next instruction 68 | prev_ea = idc.next_head(prev_ea) 69 | # Clear operands list for next iteration 70 | operands.clear() 71 | # Continue iterating if "push" is not found 72 | continue 73 | -------------------------------------------------------------------------------- /MeduzaStealer/idapython_meduza_stealer_string_decrypt.py: -------------------------------------------------------------------------------- 1 | # Author: RussianPanda 2 | # Reference: https://github.com/X-Junior/Malware-IDAPython-Scripts/tree/main/PivateLoader 3 | # Tested on sample https://www.unpac.me/results/7cac1177-08f5-4faa-a59e-3c7107964f0f?hash=29cf1ba279615a9f4c31d6441dd7c93f5b8a7d95f735c0daa3cc4dbb799f66d4#/ 4 | 5 | import idautils, idc, idaapi, ida_search 6 | import re 7 | 8 | pattern1 = '66 0F EF' 9 | pattern2 = 'C5 FD EF' 10 | 11 | # Start search from end of the file 12 | start = idc.get_segm_end(idc.get_first_seg()) 13 | 14 | addr_to_data = {} 15 | 16 | def search_and_process_pattern(pattern, start): 17 | while True: 18 | addr = ida_search.find_binary(start, 0, pattern, 16, ida_search.SEARCH_UP | ida_search.SEARCH_NEXT) 19 | 20 | if addr == idc.BADADDR: 21 | break 22 | 23 | ptr_addr = addr 24 | found_mov = False 25 | data = '' 26 | 27 | for _ in range(400): 28 | ptr_addr = idc.prev_head(ptr_addr) 29 | 30 | if idc.print_insn_mnem(ptr_addr) == 'call' or idc.print_insn_mnem(ptr_addr) == 'jmp' or idc.print_insn_mnem(ptr_addr) == 'jz': 31 | break 32 | 33 | if idc.print_insn_mnem(ptr_addr) == 'movaps' and re.match(r'xmm[0-9]+', idc.print_operand(ptr_addr, 1)): 34 | break 35 | 36 | if idc.print_insn_mnem(ptr_addr) == 'mov': 37 | # Ignore the instruction if the destination is ecx 38 | if idc.print_operand(ptr_addr, 0) == 'ecx' or idc.print_operand(ptr_addr, 0) == 'edx': 39 | continue 40 | 41 | op1_type = idc.get_operand_type(ptr_addr, 0) 42 | op2_type = idc.get_operand_type(ptr_addr, 1) 43 | 44 | operand_value = idc.get_operand_value(ptr_addr, 1) 45 | 46 | if (op1_type == idc.o_displ or op1_type == idc.o_reg) and op2_type == idc.o_imm and len(hex(operand_value)[2:]) >= 4: 47 | hex_data = hex(idc.get_operand_value(ptr_addr, 1))[2:] 48 | hex_data = hex_data.rjust(8, '0') 49 | 50 | if hex_data.endswith('ffffffff'): 51 | hex_data = hex_data[:-8] 52 | if hex_data.startswith('ffffffff'): 53 | hex_data = hex_data[8:] 54 | 55 | # Alternative method for unpacking hex data 56 | bytes_data = bytes.fromhex(hex_data) 57 | int_data = int.from_bytes(bytes_data, 'little') 58 | hex_data = hex(int_data)[2:].rjust(8, '0') 59 | 60 | data = hex_data + data 61 | found_mov = True 62 | 63 | if found_mov: # Append the data only if the desired mov instruction was found 64 | if addr in addr_to_data: 65 | addr_to_data[addr] = data + addr_to_data[addr] 66 | else: 67 | addr_to_data[addr] = data 68 | 69 | # Continue search from the previous address 70 | start = addr - 1 71 | 72 | # Search and process pattern1 73 | search_and_process_pattern(pattern1, start) 74 | 75 | # Reset the start variable to search for pattern2 76 | start = idc.get_segm_end(idc.get_first_seg()) 77 | 78 | # Search and process pattern2 79 | search_and_process_pattern(pattern2, start) 80 | 81 | # XOR the string and key and print the decrypted strings 82 | for addr, data in addr_to_data.items(): 83 | if len(data) >= 10: 84 | string = data[:len(data)//2] 85 | key = data[len(data)//2:] 86 | 87 | # XOR the string and key 88 | xored_bytes = bytes([a ^ b for a, b in zip(bytes.fromhex(string), bytes.fromhex(key))]) 89 | 90 | decrypted_string = xored_bytes.decode('utf-8', errors='ignore') 91 | 92 | print(f"{hex(addr)}: {decrypted_string}") 93 | 94 | # Set IDA comment at the appropriate address 95 | idaapi.set_cmt(addr, decrypted_string, 0) 96 | -------------------------------------------------------------------------------- /RaccoonStealer/raccoon_stealer_string_decrypt_IDAPython.py: -------------------------------------------------------------------------------- 1 | # Tested on the newest build 2.1.0-4 2 | import idc 3 | import idaapi 4 | import idautils 5 | 6 | ea = 0x004158D8 7 | 8 | def xor_decrypt(data, key, size): 9 | out = [] 10 | for i in range(size): 11 | out.append(data[i % len(data)] ^ key[i % len(key)]) 12 | return bytes(out) 13 | 14 | for xref in idautils.CodeRefsTo(ea, 0): 15 | args = idaapi.get_arg_addrs(xref)[0] 16 | args_size = idaapi.get_arg_addrs(xref)[2] 17 | key_args = idaapi.get_arg_addrs(xref)[1] 18 | get_operand_key = idc.get_operand_value(key_args, 1) 19 | #print(f"String decrypt function: {hex(xref)}") 20 | get_operand = idc.get_operand_value(args, 0) 21 | op = idc.print_operand(args_size, 0) 22 | 23 | if op == "esi": 24 | #print(f"ESI found at: {hex(args)}") 25 | 26 | # Start searching backwards from the current instruction 27 | current_ea = args_size 28 | found = False 29 | data_addr = idaapi.get_arg_addrs(xref)[0] 30 | get_operand = idc.get_operand_value(data_addr, 1) 31 | data = idc.get_bytes(get_operand, 20) 32 | data = data.replace(b"\x00", b"") 33 | #print(f"DATA: {data}") 34 | key_args = idaapi.get_arg_addrs(xref)[1] 35 | get_operand_key = idc.get_operand_value(key_args, 1) 36 | 37 | while not found and current_ea > 0: 38 | # Get the previous instruction's address 39 | prev_ea = idc.prev_head(current_ea) 40 | # Get the previous instruction's mnemonics 41 | prev_instr = idc.print_insn_mnem(prev_ea) 42 | 43 | # Check if the previous instruction matches the target operand 44 | if idc.print_operand(prev_ea, 0) == "esi": 45 | # Look for combinations of ["inc", "mov", "xor"] 46 | if prev_instr == "inc": 47 | prev2_ea = idc.prev_head(prev_ea) 48 | prev2_instr = idc.print_insn_mnem(prev2_ea) 49 | if prev2_instr == "mov": 50 | prev3_ea = idc.prev_head(prev2_ea) 51 | prev3_instr = idc.print_insn_mnem(prev3_ea) 52 | if prev3_instr == "xor": 53 | # Found the combination, print the addresses 54 | #print(f"Addresses: {prev_ea:x}, {prev2_ea:x}, {prev3_ea:x}") 55 | size_esi = idc.get_operand_value(prev2_ea, 0) 56 | #print(f"Size of the esi: {1}") 57 | #print(hex(get_operand_key)) 58 | key = idc.get_bytes(get_operand_key, 1) 59 | decrypt_me = xor_decrypt(data, key, 1) 60 | decr_str = ''.join(map(chr, decrypt_me)) 61 | print(f"Decrypted string: {decr_str}") 62 | idc.set_cmt(xref, f"{decr_str}", 1) 63 | found = True 64 | # Look for combinations of ["pop", "push", "call"] 65 | elif prev_instr == "pop": 66 | prev2_ea = idc.prev_head(prev_ea) 67 | prev2_instr = idc.print_insn_mnem(prev2_ea) 68 | if prev2_instr == "push": 69 | prev3_ea = idc.prev_head(prev2_ea) 70 | prev3_instr = idc.print_insn_mnem(prev3_ea) 71 | if prev3_instr == "call": 72 | # Found the combination, prinnt the addresses 73 | #print(f"Addresses: {prev3_ea:x}, {prev2_ea:x}, {prev_ea:x}") 74 | size_esi = idc.get_operand_value(prev2_ea, 0) 75 | #print(f"Size of the esi: {size_esi}") 76 | #print(hex(get_operand_key)) 77 | key = idc.get_bytes(get_operand_key, size_esi) 78 | #print(f"KEY: {key}") 79 | decrypt_me = xor_decrypt(data, key, size_esi) 80 | decr_str = ''.join(map(chr, decrypt_me)) 81 | print(f"Decrypted string: {decr_str}") 82 | idc.set_cmt(xref, f"{decr_str}", 1) 83 | found = True 84 | current_ea = prev_ea 85 | 86 | if op == "edi": 87 | #print(f"EDI found at: {hex(args)}") 88 | 89 | # Start searching backwards from the current instruction 90 | current_ea = args_size 91 | found = False 92 | data_addr = idaapi.get_arg_addrs(xref)[0] 93 | get_operand = idc.get_operand_value(data_addr, 1) 94 | data = idc.get_bytes(get_operand, 20) 95 | data = data.replace(b"\x00", b"") 96 | #print(f"DATA: {data}") 97 | key_args = idaapi.get_arg_addrs(xref)[1] 98 | get_operand_key = idc.get_operand_value(key_args, 1) 99 | 100 | while not found and current_ea > 0: 101 | # Get the previous instruction's address 102 | prev_ea = idc.prev_head(current_ea) 103 | # Get the previous instruction's mnemonics 104 | prev_instr = idc.print_insn_mnem(prev_ea) 105 | # Check if the previous instruction matches the target operand 106 | if idc.print_operand(prev_ea, 0) == "edi": 107 | # Look for combinations of ["inc", "mov", "xor"] 108 | if prev_instr == "inc": 109 | prev2_ea = idc.prev_head(prev_ea) 110 | prev2_instr = idc.print_insn_mnem(prev2_ea) 111 | if prev2_instr == "mov": 112 | prev3_ea = idc.prev_head(prev2_ea) 113 | prev3_instr = idc.print_insn_mnem(prev3_ea) 114 | if prev3_instr == "xor": 115 | # Found the combination, print the addresses 116 | #print(f"Addresses: {prev3_ea:x}, {prev2_ea:x}, {prev_ea:x}") 117 | size_esi = idc.get_operand_value(prev2_ea, 0) 118 | #print(f"Size of the edi: {1}") 119 | key = idc.get_bytes(get_operand_key, 1) 120 | print(hex(key_args)) 121 | if key == b'\xff': 122 | key = b'i' 123 | decrypt_me = xor_decrypt(data, key, 1) 124 | decr_str = ''.join(map(chr, decrypt_me)) 125 | #print(f"KEY: {key}") 126 | print(f"Decrypted string: {decr_str}") 127 | idc.set_cmt(xref, f"{decr_str}", 1) 128 | else: 129 | decrypt_me = xor_decrypt(data, key, 1) 130 | decr_str = ''.join(map(chr, decrypt_me)) 131 | #print(f"KEY: {key}") 132 | print(f"Decrypted string: {decr_str}") 133 | idc.set_cmt(xref, f"{decr_str}", 1) 134 | found = True 135 | # Look for combinations of ["pop", "push"] 136 | elif prev_instr == "pop": 137 | prev2_ea = idc.prev_head(prev_ea) 138 | prev2_instr = idc.print_insn_mnem(prev2_ea) 139 | if prev2_instr == "push": 140 | prev3_ea = idc.prev_head(prev2_ea) 141 | prev3_instr = idc.print_insn_mnem(prev3_ea) 142 | #print(f"Addresses: {prev3_ea:x}, {prev2_ea:x}") 143 | size_edi = idc.get_operand_value(prev2_ea, 0) 144 | #print(f"Size of the edi: {size_edi}") 145 | key = idc.get_bytes(get_operand_key, size_edi) 146 | #print(f"KEY: {key}") 147 | decrypt_me = xor_decrypt(data, key, size_edi) 148 | decr_str = ''.join(map(chr, decrypt_me)) 149 | print(f"Decrypted string: {decr_str}") 150 | idc.set_cmt(xref, f"{decr_str}", 1) 151 | found = True 152 | current_ea = prev_ea 153 | 154 | if op == "ebx": 155 | #print(f"EBX found at: {hex(args)}") 156 | 157 | # Start searching backwards from the current instruction 158 | current_ea = args_size 159 | found = False 160 | data_addr = idaapi.get_arg_addrs(xref)[0] 161 | get_operand = idc.get_operand_value(data_addr, 1) 162 | data = idc.get_bytes(get_operand, 20) 163 | data = data.replace(b"\x00", b"") 164 | #print(f"DATA: {data}") 165 | key_args = idaapi.get_arg_addrs(xref)[1] 166 | get_operand_key = idc.get_operand_value(key_args, 1) 167 | 168 | while not found and current_ea > 0: 169 | # Get the previous instruction's address 170 | prev_ea = idc.prev_head(current_ea) 171 | # Get the previous instruction's mnemonics 172 | prev_instr = idc.print_insn_mnem(prev_ea) 173 | # Check if the previous instruction matches the target operand 174 | if idc.print_operand(prev_ea, 0) == "ebx": 175 | # Look for combinations of ["push", "call", "push", "pop", "push"] 176 | if prev_instr == "push": 177 | prev2_ea = idc.prev_head(prev_ea) 178 | prev2_instr = idc.print_insn_mnem(prev2_ea) 179 | if prev2_instr == "call": 180 | prev3_ea = idc.prev_head(prev2_ea) 181 | prev3_instr = idc.print_insn_mnem(prev3_ea) 182 | if prev3_instr == "push": 183 | prev4_ea = idc.prev_head(prev3_ea) 184 | prev4_instr = idc.print_insn_mnem(prev4_ea) 185 | if prev4_instr == "pop": 186 | prev5_ea = idc.prev_head(prev4_ea) 187 | prev5_instr = idc.print_insn_mnem(prev5_ea) 188 | if prev4_instr == "push": 189 | # Found the combination, print the addresses 190 | #print(f"Addresses: {prev_ea:x}, {prev2_ea:x}, {prev3_ea:x}, {prev4_ea:x}, {prev5_ea:x}") 191 | size_ebx = idc.get_operand_value(prev2_ea, 0) 192 | #print(f"Size of the ebx: {size_ebx}") 193 | key = idc.get_bytes(get_operand_key, size_ebx) 194 | #print(f"KEY: {key}") 195 | decrypt_me = xor_decrypt(data, key, size_ebx) 196 | decr_str = ''.join(map(chr, decrypt_me)) 197 | print(f"Decrypted string: {decr_str}") 198 | idc.set_cmt(xref, f"{decr_str}", 1) 199 | found = True 200 | elif prev_instr == "pop": 201 | prev2_ea = idc.prev_head(prev_ea) 202 | prev2_instr = idc.print_insn_mnem(prev2_ea) 203 | if prev2_instr == "push": 204 | prev3_ea = idc.prev_head(prev2_ea) 205 | prev3_instr = idc.print_insn_mnem(prev3_ea) 206 | #print(f"Addresses: {prev3_ea:x}, {prev2_ea:x}") 207 | size_ebx = idc.get_operand_value(prev2_ea, 0) 208 | #print(f"Size of the ebx: {size_ebx}") 209 | key = idc.get_bytes(get_operand_key, size_ebx) 210 | #print(f"KEY: {key}") 211 | decrypt_me = xor_decrypt(data, key, size_ebx) 212 | #print(f"DATA: {data}") 213 | decr_str = ''.join(map(chr, decrypt_me)) 214 | print(f"Decrypted string: {decr_str}") 215 | idc.set_cmt(xref, f"{decr_str}", 1) 216 | found = True 217 | current_ea = prev_ea 218 | if op != "esi" and op != "edi" and op != "ebx": 219 | get_size = idc.get_operand_value(args_size, 0) 220 | data_addr = idaapi.get_arg_addrs(xref)[0] 221 | get_operand = idc.get_operand_value(data_addr, 1) 222 | data = idc.get_bytes(get_operand, 20) 223 | data = data.replace(b"\x00", b"") 224 | #print(f"DATA: {data}") 225 | key_args = idaapi.get_arg_addrs(xref)[1] 226 | get_operand_key = idc.get_operand_value(key_args, 1) 227 | key = idc.get_bytes(get_operand_key, get_size) 228 | #print(f"KEY: {key}") 229 | decrypt_me = xor_decrypt(data, key, get_size) 230 | decr_str = ''.join(map(chr, decrypt_me)) 231 | print(f"Decrypted string: {decr_str}") 232 | idc.set_cmt(xref, f"{decr_str}", 1) 233 | -------------------------------------------------------------------------------- /Resident/crc32_calc_CobaltStrike_Resident_campaign.py: -------------------------------------------------------------------------------- 1 | # reference: https://unit42.paloaltonetworks.com/using-idapython-to-make-your-life-easier-part-2/ 2 | 3 | import idautils 4 | import idaapi 5 | import pefile 6 | from crccheck.crc import Crc32Jamcrc 7 | import os 8 | 9 | ea = 0x61A4D440 10 | dll_name = ['kernel32.dll', 'advapi32.dll', 'wininet.dll', 'ws2_32.dll'] 11 | 12 | win_path = os.environ['WINDIR'] # getting Windows path 13 | system32_path = os.path.join(win_path, "system32") # getting the C:/Windows/System32 path 14 | export_name = [] 15 | for dll in dll_name: 16 | dll_path = os.path.join(system32_path, dll) 17 | pe = pefile.PE(dll_path) 18 | 19 | for export in pe.DIRECTORY_ENTRY_EXPORT.symbols: 20 | export_name.append(export.name) 21 | 22 | 23 | # resolve hashes and renaming the DWORDs 24 | for xref in idautils.CodeRefsTo(ea, 1): 25 | crc32_hash_addr = idaapi.get_arg_addrs(xref)[1] 26 | crc_32_hash_val = get_operand_value(crc32_hash_addr, 1) 27 | dword_val_addr = idaapi.get_arg_addrs(xref)[3] 28 | 29 | for m in export_name: 30 | try: 31 | crc_hash = Crc32Jamcrc.calc(m) 32 | crc = crc_32_hash_val 33 | except: 34 | pass 35 | if crc == crc_hash: 36 | m = str(m, 'utf-8') 37 | get_dword_val = get_operand_value(dword_val_addr, 1) 38 | idc.set_name(get_dword_val, "api_"+m, SN_CHECK) 39 | 40 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /StealC/stealc_idapython.py: -------------------------------------------------------------------------------- 1 | # Author: RussianPanda 2 | # Sample: 911981d657b02f2079375eecbd81f3d83e5fa2b8de73afad21783004cbcc512d 3 | 4 | import idaapi 5 | import idautils 6 | import idc 7 | import ida_bytes 8 | import ida_ua 9 | import ida_search 10 | import ida_name 11 | import base64 12 | import re 13 | import string 14 | 15 | def find_opcode(binary_data): 16 | opcode = bytes.fromhex("73 74 72 69 6E 67 20 74 6F 6F 20 6C 6F 6E 67") 17 | 18 | positions = [] 19 | pos = binary_data.find(opcode) 20 | 21 | while pos != -1: 22 | positions.append(pos) 23 | pos = binary_data.find(opcode, pos + 1) 24 | 25 | if positions: 26 | build_id = None 27 | rc4_key = None 28 | 29 | for pos in positions: 30 | next_bytes = binary_data[pos + len(opcode):pos + len(opcode) + 120] 31 | 32 | current_str = "" 33 | for i, b in enumerate(next_bytes): 34 | if 32 <= b <= 126: 35 | current_str += chr(b) 36 | elif current_str: 37 | build_id = current_str 38 | break 39 | 40 | string_count = 0 41 | current_str = "" 42 | for b in next_bytes: 43 | if 32 <= b <= 126: 44 | current_str += chr(b) 45 | else: 46 | if current_str: 47 | string_count += 1 48 | if string_count == 3: 49 | rc4_key = current_str 50 | break 51 | current_str = "" 52 | 53 | if build_id and rc4_key: 54 | break 55 | 56 | return { 57 | "build_id": build_id, 58 | "rc4_key": rc4_key 59 | } 60 | else: 61 | return None 62 | 63 | def get_binary_data(): 64 | result = bytearray() 65 | 66 | for seg_ea in idautils.Segments(): 67 | seg_start = seg_ea 68 | seg_end = idc.get_segm_end(seg_ea) 69 | seg_size = seg_end - seg_start 70 | 71 | seg_data = idaapi.get_bytes(seg_start, seg_size) 72 | result.extend(seg_data) 73 | 74 | return bytes(result) 75 | 76 | def detect_rc4_key(): 77 | print("Detecting RC4 key automatically...") 78 | binary_data = get_binary_data() 79 | detected_info = find_opcode(binary_data) 80 | 81 | if detected_info and detected_info["rc4_key"]: 82 | print(f"Found RC4 key: {detected_info['rc4_key']}") 83 | if detected_info["build_id"]: 84 | print(f"Build ID: {detected_info['build_id']}") 85 | return detected_info["rc4_key"] 86 | else: 87 | raise Exception("Failed to detect RC4 key") 88 | 89 | def rc4_decrypt(encrypted_data, key): 90 | S = list(range(256)) 91 | j = 0 92 | 93 | for i in range(256): 94 | j = (j + S[i] + key[i % len(key)]) % 256 95 | S[i], S[j] = S[j], S[i] 96 | 97 | i = j = 0 98 | result = bytearray() 99 | 100 | for byte in encrypted_data: 101 | i = (i + 1) % 256 102 | j = (j + S[i]) % 256 103 | S[i], S[j] = S[j], S[i] 104 | k = S[(S[i] + S[j]) % 256] 105 | result.append(byte ^ k) 106 | 107 | return result 108 | 109 | def sanitize_name(name): 110 | valid_chars = string.ascii_letters + string.digits + "_" 111 | result = "" 112 | for c in name: 113 | if c in valid_chars: 114 | result += c 115 | else: 116 | result += "_" 117 | 118 | if result and not result[0].isalpha() and result[0] != '_': 119 | result = "_" + result 120 | 121 | if not result: 122 | result = "renamed_str" 123 | 124 | return result 125 | 126 | def decrypt_string(base64_encoded, key_str): 127 | try: 128 | key_bytes = key_str.encode('utf-8') 129 | 130 | encrypted_data = base64.b64decode(base64_encoded) 131 | 132 | decrypted_data = rc4_decrypt(encrypted_data, key_bytes) 133 | 134 | try: 135 | is_printable = all(32 <= b <= 126 or b in (9, 10, 13) for b in decrypted_data) 136 | if is_printable: 137 | return decrypted_data.decode('utf-8') 138 | else: 139 | return decrypted_data.hex() 140 | except UnicodeDecodeError: 141 | return decrypted_data.hex() 142 | except Exception as e: 143 | return f"Error: {str(e)}" 144 | 145 | def is_base64(s): 146 | pattern = r'^[A-Za-z0-9+/]+={0,2}$' 147 | 148 | if re.match(pattern, s) and len(s) % 4 == 0: 149 | try: 150 | base64.b64decode(s) 151 | return True 152 | except: 153 | pass 154 | return False 155 | 156 | def find_lea_rdx_instructions(): 157 | result = [] 158 | 159 | seg = idaapi.get_segm_by_name(".text") 160 | if not seg: 161 | return result 162 | 163 | current_addr = seg.start_ea 164 | 165 | while current_addr < seg.end_ea: 166 | if idc.print_insn_mnem(current_addr) == "lea" and idc.print_operand(current_addr, 0) == "rdx": 167 | 168 | op_str = idc.print_operand(current_addr, 1) 169 | 170 | str_addr = idc.get_operand_value(current_addr, 1) 171 | 172 | str_content = ida_bytes.get_strlit_contents(str_addr, -1, 0) 173 | 174 | if str_content: 175 | try: 176 | str_content = str_content.decode('utf-8', errors='replace') 177 | 178 | if is_base64(str_content): 179 | result.append((current_addr, str_addr, str_content)) 180 | except: 181 | pass 182 | 183 | current_addr = idc.next_head(current_addr) 184 | 185 | return result 186 | 187 | def find_qwords(lea_insn_addr, decrypted_str, max_instructions=20): 188 | qwords = [] 189 | 190 | current_addr = lea_insn_addr 191 | end_addr = current_addr + max_instructions * 16 192 | 193 | while current_addr < end_addr: 194 | current_addr = idc.next_head(current_addr) 195 | 196 | if current_addr == idaapi.BADADDR: 197 | break 198 | 199 | if idc.print_insn_mnem(current_addr) == "lea" and idc.print_operand(current_addr, 0) == "rcx": 200 | op_str = idc.print_operand(current_addr, 1) 201 | 202 | if op_str.startswith("qword_"): 203 | qword_addr = idc.get_operand_value(current_addr, 1) 204 | qwords.append((current_addr, qword_addr)) 205 | 206 | if idc.print_insn_mnem(current_addr) in ["ret", "jmp"]: 207 | break 208 | 209 | return qwords 210 | 211 | def add_comments_and_rename(): 212 | key = detect_rc4_key() 213 | 214 | matches = find_lea_rdx_instructions() 215 | 216 | str_count = 0 217 | str_var_count = 0 218 | qword_count = 0 219 | 220 | for insn_addr, str_addr, encrypted in matches: 221 | try: 222 | decrypted = decrypt_string(encrypted, key) 223 | 224 | comment = f"Decrypted: \"{decrypted}\"" 225 | if idc.set_cmt(insn_addr, comment, 0): 226 | str_count += 1 227 | print(f"Added comment at 0x{insn_addr:X}: {comment}") 228 | 229 | data_comment = f"Decrypted: \"{decrypted}\"" 230 | idc.set_cmt(str_addr, data_comment, 0) 231 | 232 | new_name = sanitize_name(f"str_{decrypted[:20]}") 233 | if ida_name.set_name(str_addr, new_name, ida_name.SN_CHECK): 234 | str_var_count += 1 235 | print(f"Renamed string at 0x{str_addr:X} to {new_name}") 236 | 237 | associated_qwords = find_qwords(insn_addr, decrypted) 238 | for qword_insn_addr, qword_addr in associated_qwords: 239 | qword_new_name = sanitize_name(f"qw_{decrypted[:20]}") 240 | if ida_name.set_name(qword_addr, qword_new_name, ida_name.SN_CHECK): 241 | qword_count += 1 242 | print(f"Renamed qword at 0x{qword_addr:X} to {qword_new_name}") 243 | 244 | except Exception as e: 245 | print(f"Error processing string at 0x{str_addr:X}: {e}") 246 | 247 | print(f"Added {str_count} decryption comments") 248 | print(f"Renamed {str_var_count} string variables") 249 | print(f"Renamed {qword_count} associated qword variables") 250 | 251 | def main(): 252 | add_comments_and_rename() 253 | 254 | if __name__ == "__main__": 255 | main() 256 | -------------------------------------------------------------------------------- /Vidar/IDAPython_Vidar_TEST.py: -------------------------------------------------------------------------------- 1 | # Vidar string decryptor TEST script 2 | # NOTE TO SELF: fix the ea's and the way the encrypted strings are passed 3 | # MD5: 3d2668ce280362a6f56418068aca2292 4 | 5 | import idautils 6 | 7 | ea_first = 0x401081 8 | 9 | ea = 0x404053 10 | 11 | readable_list = [] 12 | 13 | def xor_decrypt(data, key): 14 | out = [] 15 | 16 | for i in range(len(data)): 17 | out.append(data[i] ^ key[i%len(key)]) 18 | return out 19 | 20 | cur_addr_first = idc.prev_head(ea_first) # get first string at 0040107C 21 | get_string_ref_first = get_operand_value(cur_addr_first, 0) 22 | strings_first = get_string_ref_first 23 | get_string_bytes_first_str = idc.get_bytes(strings_first, 6) 24 | xor_key_first_str = idc.next_head(cur_addr_first) 25 | xor_key_first_str_location = get_operand_value(xor_key_first_str,0) 26 | read_xor_bytes_first_str = idc.get_bytes(xor_key_first_str_location, 6) 27 | decrypt_first_str = xor_decrypt(get_string_bytes_first_str, read_xor_bytes_first_str) 28 | decrypted_str_first = ''.join(map(chr, decrypt_first_str)) 29 | readable_list.append(decrypted_str_first) 30 | 31 | print(decrypted_str_first) 32 | 33 | for ref in idautils.XrefsTo(ea): 34 | cur_addr = idc.next_head(ref.frm) 35 | #print(hex(cur_addr)) 36 | 37 | xor_key = idc.next_head(cur_addr) # xor keys address 38 | 39 | if print_insn_mnem(cur_addr) == 'pop': 40 | continue 41 | 42 | get_string_ref = get_operand_value(cur_addr, 0) 43 | 44 | 45 | strings_two = get_string_ref 46 | 47 | xor_key_bytes_location = get_operand_value(xor_key, 0) 48 | 49 | size_addr = idc.next_head(xor_key) 50 | 51 | #print(hex(size_addr)) 52 | 53 | 54 | size = 0 55 | if print_insn_mnem(size_addr) == "mov": 56 | if idc.print_operand(size_addr, 1) == 'ebx': 57 | ebx_size = 32 58 | #size_ebx = get_operand_value(ebx_size, 0) 59 | size = ebx_size 60 | 61 | elif idc.print_operand(size_addr, 1) == 'eax': 62 | eax_size = 32 63 | #size_eax = get_operand_value(eax_size, 0) 64 | size = eax_size 65 | 66 | 67 | elif idc.print_operand(size_addr, 1) == 'esi': 68 | esi_size = 32 69 | size = esi_size 70 | 71 | else: 72 | if print_insn_mnem(size_addr) == "push": 73 | push_size = get_operand_value(size_addr, 0) 74 | size = push_size 75 | 76 | 77 | get_string_ref = get_operand_value(cur_addr, 0) 78 | 79 | get_string_bytes = idc.get_bytes(strings_two, size) 80 | strings_two = get_string_ref 81 | #print(get_string_bytes) 82 | read_xor_bytes = idc.get_bytes(xor_key_bytes_location, size) # xor key 83 | #print(read_xor_bytes) 84 | 85 | try: 86 | decrypt_me = xor_decrypt(get_string_bytes, read_xor_bytes) 87 | except: 88 | pass 89 | max_size = 1000 90 | if len(decrypt_me) > max_size: 91 | continue 92 | 93 | 94 | decrypted_str = ''.join(map(chr, decrypt_me)) 95 | readable_list.append(readable_str) 96 | print(decrypted_str) 97 | 98 | idc.set_cmt(cur_addr,decrypted_str,0) #setting comments 99 | 100 | 101 | # decrypting the strings within the pattern [call, mov, push] 102 | for ref in idautils.XrefsTo(ea): 103 | cur_addr = idc.next_head(ref.frm) 104 | #print(hex(cur_addr)) 105 | 106 | xor_key = idc.next_head(cur_addr) # xor keys address 107 | 108 | if print_insn_mnem(cur_addr) == 'pop': 109 | continue 110 | 111 | if print_insn_mnem(cur_addr) == "mov": 112 | next_one = idc.next_head(cur_addr) 113 | if print_insn_mnem(next_one) != "pop": 114 | string_addr = next_one 115 | get_str_byte_location = get_operand_value(string_addr,0) 116 | get_str_bytes = idc.get_bytes(get_str_byte_location, 35) # string bytes 117 | 118 | xor_key = next_head(string_addr) # xor keys address 119 | xor_key_bytes_location_2 = get_operand_value(xor_key, 0) 120 | 121 | get_xor_bytes = idc.get_bytes(xor_key_bytes_location_2, 35) 122 | 123 | decrypt_me = xor_decrypt(get_str_bytes, get_xor_bytes) 124 | decrypted_str = ''.join(map(chr, decrypt_me)) 125 | print(decrypted_str) 126 | idc.set_cmt(cur_addr,decrypted_str,0) #setting comments 127 | 128 | #decrypting strings in pattern [call, push, mov, push] 129 | 130 | for ref in idautils.XrefsTo(ea): 131 | cur_addr = idc.next_head(ref.frm) 132 | 133 | 134 | if print_insn_mnem(cur_addr) == 'pop': 135 | continue 136 | 137 | if print_insn_mnem(cur_addr) == "push": 138 | next_one = idc.next_head(cur_addr) 139 | if print_insn_mnem(next_one) == "mov" and print_insn_mnem(next_one) != "pop": 140 | mov_addr = next_one 141 | enc_strings_addr = idc.prev_head(mov_addr) 142 | 143 | get_str_byte_location = get_operand_value(enc_strings_addr,0) 144 | get_str_bytes = idc.get_bytes(get_str_byte_location, 35) # string bytes 145 | 146 | mov_next_frm_enc_string_addr = idc.next_head(enc_strings_addr) 147 | xor_key = next_head(mov_next_frm_enc_string_addr) # xor keys address 148 | xor_key_bytes_location_2 = get_operand_value(xor_key, 0) 149 | get_xor_bytes = idc.get_bytes(xor_key_bytes_location_2, 35) 150 | 151 | decrypt_me = xor_decrypt(get_str_bytes, get_xor_bytes) 152 | decrypted_str = ''.join(map(chr, decrypt_me)) 153 | print(decrypted_str) 154 | idc.set_cmt(cur_addr,decrypted_str,0) 155 | 156 | 157 | -------------------------------------------------------------------------------- /Vidar/Vidar_Stealer_3.7_RC4_string_decryption.py: -------------------------------------------------------------------------------- 1 | import idautils 2 | import idc 3 | import idaapi 4 | from Crypto.Cipher import ARC4 5 | import base64 6 | 7 | # String decrypt function 8 | ea = 0x00401420 9 | 10 | # Find all references to the decryption function 11 | xrefs = idautils.CodeRefsTo(ea, 0) 12 | for xref in xrefs: 13 | 14 | key = b"09976218420008604394" 15 | cipher = ARC4.new(key) 16 | 17 | args = idaapi.get_arg_addrs(xref)[0] 18 | str_dec_fn = hex(xref) 19 | prev_ea = idc.prev_head(args) 20 | get_operand = idc.get_operand_value(args, 0) 21 | bytes_str = idc.get_bytes(get_operand, 30) 22 | null_index = bytes_str.find(b'\x00') 23 | if null_index != -1: 24 | bytes_str = bytes_str[:null_index] 25 | bytes_str = bytes_str.decode() 26 | dec = base64.b64decode(bytes_str) 27 | decr = cipher.decrypt(dec) 28 | 29 | # Add a comment with the decrypted string to the corresponding instruction 30 | idc.set_cmt(prev_ea, f"{decr}", 0) 31 | decrypted_strings.append(decr) 32 | 33 | -------------------------------------------------------------------------------- /Vidar/vidar_string_decrypt_IDAPython.py: -------------------------------------------------------------------------------- 1 | # Tested on sample MD5:bc6e5949540eb57199bff7f3c46cc62a 2 | 3 | import idautils 4 | import idc 5 | import idaapi 6 | 7 | def xor_decrypt(data, key): 8 | out = [] 9 | 10 | for i in range(len(data)): 11 | out.append(data[i] ^ key[i % len(key)]) 12 | return out 13 | 14 | # String decrypt function 15 | ea = 0x00401085 16 | 17 | # Find all references to the decryption function 18 | for xref in idautils.CodeRefsTo(ea, 0): 19 | args = idaapi.get_arg_addrs(xref)[0] 20 | print(f"String decrypt function: {hex(xref)}") 21 | prev_ea = idc.prev_head(args) 22 | get_operand = idc.get_operand_value(args, 0) 23 | key = idc.get_bytes(get_operand, 200) 24 | 25 | # Check if there is a null byte within the last 40 bytes of the key 26 | if b"\x00" in key[-40:]: 27 | key = key[:len(key)-40+key[-40:].index(b"\x00")] 28 | 29 | string_addr = idc.get_operand_value(prev_ea, 0) 30 | data = idaapi.get_strlit_contents(string_addr, -1, idc.get_inf_attr(idc.INF_MAX_EA)) 31 | try: 32 | decrypt_me = xor_decrypt(data, key) 33 | except: 34 | prev_ea_frm_dword = idc.prev_head(prev_ea) 35 | #print(hex(prev_ea_frm_dword)) 36 | string_addr_frm_dword = idc.get_operand_value(prev_ea_frm_dword, 0) 37 | data = idaapi.get_strlit_contents(string_addr_frm_dword, -1, idc.get_inf_attr(idc.INF_MAX_EA)) 38 | decrypt_me = xor_decrypt(data, key) 39 | 40 | decr_str = ''.join(map(chr, decrypt_me)) 41 | decrypted_str = "" 42 | for c in decr_str: 43 | if ord(c) >= 32 and ord(c) <= 126: 44 | decrypted_str += c 45 | print(decrypted_str) 46 | 47 | # Add a comment for each decrypted string 48 | idc.set_cmt(xref, f"{decrypted_str}", 1) 49 | --------------------------------------------------------------------------------