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

Ещё в группе «Реверс»
- Почему IDA не сворачивает константы и как это исправить
Почему IDA не сворачивает константы и как это исправить 👨💻 В последнее время в ВПО все…
- Распознаем STL-код легко
Распознаем STL-код легко 😐 Нередко в процессе реверс-инжиниринга мы сталкиваемся с STL-кодом, анализ которого на первый…
- Опять CFG
Опять CFG 👋 Частой задачей при извлечении конфигураций ВПО на потоке является получение границ функций и…
- Деобфусцируем имена функций .NET вручную
Деобфусцируем имена функций .NET вручную 🙌 ВПО на .NET обожает пакеры, обфускацию (имен, CFG и прочего)…
- Идея для правила корреляции в SIEM
Идея для правила корреляции в SIEM 💡 Хотя отслеживание всей цепочки атаки, описанной в постах выше,…
Ранее мы рассказывали про то, как восстановить 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
Ещё в группе «Реверс»
- Почему IDA не сворачивает константы и как это исправить
Почему IDA не сворачивает константы и как это исправить 👨💻 В последнее время в ВПО все…
- Распознаем STL-код легко
Распознаем STL-код легко 😐 Нередко в процессе реверс-инжиниринга мы сталкиваемся с STL-кодом, анализ которого на первый…
- Опять CFG
Опять CFG 👋 Частой задачей при извлечении конфигураций ВПО на потоке является получение границ функций и…
- Деобфусцируем имена функций .NET вручную
Деобфусцируем имена функций .NET вручную 🙌 ВПО на .NET обожает пакеры, обфускацию (имен, CFG и прочего)…
- Идея для правила корреляции в SIEM
Идея для правила корреляции в SIEM 💡 Хотя отслеживание всей цепочки атаки, описанной в постах выше,…







