[ << ALL_FEED ]

Как починить CFG. Часть вторая

Ещё в группе «Реверс»

Как починить CFG. Часть вторая 🛠

Ранее мы рассказывали про то, как восстановить Control Flow Graph (CFG) в случае его обфускации. Однако часто при анализе даже необфусцированного ВПО можно встретить случаи, когда CFG некоторых функций генерируется с ошибками. Одним из таких примеров является написанное на Delphi ВПО, где из-за особенностей обработки исключений часто можно встретить картину, как на скриншоте 1.

🤔 Если присмотреться внимательнее (скриншот 2), то можно заметить, что в блоке (1) на стек сохраняется адрес одного из последующих блоков (3), после чего выполняется некоторая логика (зачастую — освобождение ресурсов или объектов) и происходит прыжок на сохраненный ранее адрес (2).

Из-за того что в блоке 2 присутствует дополнительная ссылка, IDA не может однозначно определить адрес, куда будет осуществлен прыжок в jmp eax. Чтобы исправить эту проблему, напишем небольшой хук, который будет проверять и автоматически патчить подобные места в коде.

Создадим класс хука, который будет ожидать событие ev_ana_insn. Для начала необходимо убедиться, что это действительно интересующая нас последовательность, после чего пройтись «вверх» и попытаться найти сохраненный на стек адрес. Затем пропатчить jmp eax на jmp short address.

class DelphiJmpEaxFixer(idaapi.IDP_Hooks):
    def lookup_push_insn(self, start: int, limit: int = 30) -> int | None:
        ...
 
    def ev_ana_insn(self, insn: idaapi.insn_t) -> bool:
        #
        b = bytes(idaapi.get_bytes(insn.ea - 1, 3))
        if idaapi.is_tail(idaapi.get_flags(insn.ea)):
            return True
        # pop eax | 58
        # jmp eax | ff e0
        # ensure all pop & jmp seq
        if b[0] != 0x58 or b[1] != 0xFF or b[2] != 0xE0:
            return False
 
        print(f"Got jmp short eax at {insn.ea:x}")
        pushed_address = self.lookup_push_insn(insn.ea)
        if pushed_address is None:
            return False
        delta = pushed_address - insn.ea
        if delta < 0 or delta > 128:
            return False
        print(f"{delta=}")
 
        asm_call = f"jmp short {delta}"
        assembled = idaapi.AssembleLine(insn.ea, 0, 0, True, asm_call)
        if assembled is None:
            return False
        return idaapi.patch_bytes(insn.ea, assembled)
Code language: Intel x86 Assembly (x86asm)


Напишем функцию поиска сохраняемого на стек адреса lookup_push_insn. В ней найдем предположительную верхнюю границу блока 2 и проверим, что блок имеет единственную ссылку. Дополнительно ограничим диапазон поиска, в целях оптимизации.

def lookup_push_insn(self, start: int, limit: int = 30) -> int | None:
    ptr: int = start
    insn = idaapi.insn_t()
    jmp_ref = idaapi.BADADDR
    for _ in range(limit, 0, -1):
        prev_addr = idaapi.decode_prev_insn(insn, ptr)
        if prev_addr == idaapi.BADADDR:
            break
        ptr = prev_addr
        _refs = [xref for xref in idautils.CodeRefsTo(ptr, False)]
        # If we found refs it's likely an upper basic block address
        # it must be a single jmp ref
        if _refs:
            if len(_refs) != 1:
                return None
            jmp_ref = next(iter(_refs))
            break
 
    if jmp_ref == idaapi.BADADDR:
        return None
 
    ref_insn_sz = idaapi.decode_insn(insn, jmp_ref)
    if ref_insn_sz == 0 or insn.itype != idaapi.NN_jmp:
        return None
 
    addr = idaapi.decode_prev_insn(insn, ptr)
    if addr == idaapi.BADADDR or insn.itype != idaapi.NN_push:
        return None
 
    return insn.Op1.value
Code language: Python (python)


Добавим инициализацию хука при запуске скрипта и загрузим его в IDA. Запустим повторный анализ бинарного файла. В результате получим исправленный граф (скриншот 3).

Оставшиеся одиночные блоки — это вызовы обработчиков исключений; в данном случае они не влияют на ход работы программы. Стоит помнить, что пока хук активен, он будет автоматически вызываться даже при разметке нового кода, который не был размечен ранее.

hook_instance = DelphiJmpEaxFixer()
hook_instance.hook()
Code language: plaintext (plaintext)


Happy reversing! 💫


#tip #reverse #idapython
@ptescalator

Ещё от global_author

  • Этот ваш Siemens…

    Недавно наши коллеги из Центра промышленной экспертизы Позитива обнаружили на MalwareBazaar один любопытный Windows-образец. Вот его…

  • Анти-антивирус

    Недавно нам на глаза попалась APK-шка с интригующим и внушающим доверие названием «Антивирус ФСБ.apk». Установив его,…

  • .exe .docm .xlsm

    .exe .docm .xlsm Вредоносные файлы с этими расширениями чаще всего встречаются в сетевом трафике компаний. Давайте…

  • Хаос, шок, приватные ключи от не менее приватного GitLab 💻

    Последние пару месяцев злоумышленник распространял трояны с нескольких npm-учеток: alex05255, mdrafiqulislamrabby, b.w1001, abdev8773 и mollspotwood54400. Список…

  • ::%16777216 — так что же ты такое?

    ::%16777216 — так что же ты такое? Известно, что в ходе атак злоумышленники могут использовать туннелирование.…

Ещё от global_author

  • Этот ваш Siemens…

    Недавно наши коллеги из Центра промышленной экспертизы Позитива обнаружили на MalwareBazaar один любопытный Windows-образец. Вот его…

  • Анти-антивирус

    Недавно нам на глаза попалась APK-шка с интригующим и внушающим доверие названием «Антивирус ФСБ.apk». Установив его,…

  • .exe .docm .xlsm

    .exe .docm .xlsm Вредоносные файлы с этими расширениями чаще всего встречаются в сетевом трафике компаний. Давайте…

  • Хаос, шок, приватные ключи от не менее приватного GitLab 💻

    Последние пару месяцев злоумышленник распространял трояны с нескольких npm-учеток: alex05255, mdrafiqulislamrabby, b.w1001, abdev8773 и mollspotwood54400. Список…

  • ::%16777216 — так что же ты такое?

    ::%16777216 — так что же ты такое? Известно, что в ходе атак злоумышленники могут использовать туннелирование.…

Ещё в группе «Реверс»