[Исследование] Расшифровка строк с помощью Dumpulator

D2

Администратор
Регистрация
19 Фев 2025
Сообщения
4,380
Реакции
0
Оригинал: https://kienmanowar.wordpress.com/2023/05/22/case-study-decrypt-strings-using-dumpulator/

1. Ссылки​

2. Анализ кода​

Я получил подозрительную DLL, которую необходимо проанализировать. Эта DLL упакована(packed). После распаковки и закидывания DLL в IDA, IDA успешно проанализировала ее, найдя более 7000 функций (включая вызовы функций API и библиотек). При беглом просмотре на вкладке Strings я обнаружил множество строк следующего формата:
2023-05-22_10-02-03.png



Исходя из предоставленной информации, я уверен, что эти строки определенно были зашифрованы. Просмотрев фрагмент кода с использованием произвольной строки, я обнаружил соответствующий ассемблерный код и псевдокод (имена функций и переменных изменены соответствующим образом):

2023-05-22_10-10-29.png



На приведенном выше изображении легко в этом убедиться:

  • В регистре EAX будет храниться адрес зашифрованной строки.
  • В регистре EDX будет храниться адрес строки после расшифровки.
  • Функция mw_decrypt_str_wrap выполняет задачу расшифровки строки.
Если у кого-то из вас возникла такая же идея проанализировать функцию mw_decrypt_str_wrap, чтобы переписать код IDApython для расшифровки, поздравляю вас, вы разделяете ту же мысль, что и я! Функция mw_decrypt_str_wrap будет вызывать функцию mw_decrypt_str.

2023-05-22_10-29-49.png



Побродив по различным функциям и подумав над тем, как кодить, я начал чувствовать себя все более обескураженным. Более того, изучая перекрестные ссылки на функцию mw_decrypt_str_wrap, я заметил, что она вызывается более 4000 раз для расшифровки строк... ВАТАФАК.

3. Используем dumpulator​

Как видно из приведенного изображения, в функции дешифрования слишком много вызовов функций. Кроме того, переписывание этой функции расшифровки потребует много времени и отладки кода для проверки. Я считаю, что нужно найти способ эмулировать эту функцию для выполнения шага расшифровки и получения расшифрованной строки. Мне пришло в голову несколько решений, и я также спросил своего брата, который предложил использовать x или y решения. После некоторых проб и ошибок я решил попробовать использовать dumpulator. Чтобы использовать dumpulator, сначала нужно создать файл минидампа этой DLL (дамп при остановке в DllEntryPoint). Получив файл дампа, я протестировал следующий фрагмент кода:
Код: Скопировать в буфер обмена
Код:
from dumpulator import Dumpulator
 
dec_str_fn = 0x02FE08C0
enc_str_offset = 0x02FD9988
 
dp = Dumpulator("mal_dll.dmp", quiet=True)
tmp_addr = dp.allocate(256)
dp.call(dec_str_fn, [], regs={'eax':enc_str_offset , 'edx': tmp_addr})
dec_str = dp.read_str(dp.read_long(tmp_addr))
print(f"Encrypted string: '{dp.read_str(enc_str_offset)}'")
print(f"Decrypted string: '{dec_str}'")

Результат выполнения приведенного выше кода:

2023-05-22_10-52-30.png



Черт возьми... это именно то, что я хотел.

Далее я перепишу код в соответствии со своим замыслом следующим образом:

  • Используем regex для поиска шаблонов(patterns) и извлечения всех закодированных строковых адресов.

  • Отфильтруем адреса, соответствующие шаблону, но не являющиеся функциями дешифрования или неопределенными адресами, и добавим их в BLACK_LIST.
Вот не очень удачный фрагмент кода, который удовлетворяет моим требованиям:

Код: Скопировать в буфер обмена
Код:
import re
import struct
import pefile
from dumpulator import Dumpulator
 
dump_image_base = 0x2F80000
dec_str_fn = 0x02FE08C0
 
BLACK_LIST = [0x3027520, 0x30380b6, 0x30380d0, 0x3039a08, 0x3039169, 0x303a6b6, 0x303aa0e, 0x303ab5c, 0x303bbf3, 0x3066075, 0x306661b, 0x3083e50,
              0x3084373, 0x30856d1, 0x30858aa, 0x308c7ac, 0x308d02d, 0x30acbfd, 0x30cd12e, 0x30cd187, 0x30cd670, 0x30cd6d4, 0x30cfe2f, 0x30d4cc4,
              0x3106da0]
 
FILE_PATH = 'dumped_dll.dll'
dp = Dumpulator("mal_dll.dmp", quiet=True)
 
file_data = open(FILE_PATH, 'rb').read()
pe = pefile.PE(data=file_data)
 
egg = rb'\x8D\x55.\xB8(....)\xE8....\x8b.'
tmp_addr = dp.allocate(256)
 
def decrypt_str(xref_addr, enc_str_offset):   
    print(f"Processing xref address at: {hex(xref_addr)}")
    print(f"Encryped string offset: {hex(enc_str_offset)}")
    dp.call(dec_str_fn, [], regs={'eax': enc_str_offset, 'edx': tmp_addr})
    dec_str = dp.read_str(dp.read_long(tmp_addr))
    print(f"{hex(xref_addr)}: {dec_str}\n")
    return dec_str
    
for m in re.finditer(egg, file_data):
    enc_str_offset = struct.unpack('<I', m.group(1))[0]
    inst_offset = m.start()
    enc_str_offset_in_dmp = enc_str_offset - 0x400000 + dump_image_base
    call_fn_addr = inst_offset + 8 - 0x400 + dump_image_base + 0x1000
    if call_fn_addr not in BLACK_LIST:
        str_ret =  decrypt_str(call_fn_addr, enc_str_offset_in_dmp)
 
print(f"H0lY SH1T... IT's D0NE!!!")

Результат выполнения приведенного выше скрипта:

2023-05-22_11-10-53.png



Никаких ошибок!!! В качестве заключительного шага я добавил в этот скрипт фрагмент кода, который выведет Python-файл. Этот файл будет содержать команды idc.set_cmt для установки комментария для расшифрованных строк, приведенных выше по адресу вызова функции decrypt.

Итоговый результат выглядит следующим образом:

1700814149653.png


Не получилось на xss гифку загрузить(получился скриншот), можете перейти по ссылке в начале статьи и посмотреть
2023-05-22_11-16-07.png



2023-05-22_11-16-51.png



Конец.
 
Сверху Снизу