R0 CREW

Краткий обзор ANGR

Краткий обзор ANGR

Какие только не встретишь определения, но своим языком я бы сказал, что ANGR это инструмент дающий возможность применить символическое выполнение к бинарным файлам. Сама его суть в этом: любой бинарный исполняемый файл можно рассмотреть как уравнение, части которого это не только какие-либо введенные пользователем значения, но и состояния регистров, значения памяти и т.д. Так вот Angr - подобный решатель таких уравнений)))

Работу фреймворка обеспечивают

  1. Начальное задаваемое состояние
  2. Модуль VEX, который эмулирует регистры, память и тд, учитывая особенности процессоров
  3. Модуль SimuVex, реализующий символические переходы между состояниями VEX
    Покажу на примере возможности ANGR. Возьмем простой таск с forkbomb.ru (CTF платформа).

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

import angr
p = angr.Project('name_of_project')

Немного слов о загрузке

Формат загрузки angr.Project('name_of_project', load_options). Наиболее часто используется

load_options['auto_load_libs']	=	False

поскольку ANGR по умолчанию включает библиотеки в проект и соответственно тратит время на их разбор. Кстати, в проект можно включить не только исполняемые файлы, но и дампы PE и ELF.

Загрузив проект, давайте взглянем на его архитектуру и порядок байтов с помощью BoyScout:

boy = p.analyses.BoyScout()

print('arch	' + boy.arch)

print(boy.endianness)

Out:

arch   X86

endianess   Iend_LE

Можем также залезть под капот ANGR и взглянуть на то, как он выбирал нужный порядок байт и архитектуру

for arch, vote in boy.votes.items():
	print('   '.join(arch) + '  =  ' + str(vote))

Out:

AARCH64  Iend_LE  =  0
ARMHF    Iend_LE  =  0
ARMEL    Iend_BE  =  0
ARMEL    Iend_LE  =  0
AARCH64  Iend_BE  =  0
X86      Iend_LE  =  22
S390X    Iend_BE  =  0
ARMHF    Iend_BE  =  0
PPC64    Iend_LE  =  0
PPC32    Iend_LE  =  0
Soot     Iend_BE  =  0
MIPS32   Iend_LE  =  0
AMD64    Iend_LE  =  6
MIPS64   Iend_LE  =  0
MIPS32   Iend_BE  =  0
MIPS64   Iend_BE  =  0
PPC64    Iend_BE  =  0
PPC32    Iend_BE  =  0

Таким образом получили некоторую информацию о поддерживаемых архитектурах.

Посмотрим на библиотеки, загружающиеся с нашим файлом

p.loader.shared_objects.items() 

Собственно говоря здесь их не было. В противном случае их можно было бы отключить в опциях при перезагрузке проекта.

Вывод strings достаточно небольшой

!This program cannot be run in DOS mode.
.text
.data
Enter flag to check: 
Wrong length!
Wrong check 1!
Wrong check 2!
Wrong check 3!
Wrong check 4!
Wrong check 5!
Wrong check 6!
Wrong check 7!
Wrong check 8!
Wrong check 9!
Wrong check 10!
No zero!
Yes! Correct flag is %s
msvcrt.dll
printf
scanf
strlen
atoi
_controlfp
__set_app_type__
getmainargs
exit

Значит наш флаг, должен пройти 10 проверок, в результате мы получим Yes! Correct flag is... или соответствующее значение ошибки.

Снова вернемся к ANGR. Составим граф потока управления, сделать это можно с помощью CFGFast, CFGEmulated (в зависимости от использовании версии python может быть CFGAccurate). CFGFast дает преимущество по скорости (основан на анализе заголовка и границ функций), но если вы знаете, что ваш исследуемый образец поврежден, то лучше воспользоваться функционалом CFGEmulated.

cfg_emulated = p.analyses.CFGEmulated()

Поскольку программа достаточно малых размеров (по этой причине позволяем себе использовать CFGEmulated), можем полностью распечатать ее CFG

from angrutils import *

plot_cfg(cfg_emulated, "cfg_emulated", asminst=True, remove_imports=True, remove_path_terminator=True) 

Out:

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

functions = [function for function in cfg_emulated.kb.functions.values()]

Out:

[<Function sub_401000 (4198400)>,
 <Function sub_40101d (4198429)>,
 <Function _start (4199680)>,
 <Function sub_401578 (4199800)>,
 <Function sub_401580 (4199808)>,
 <Function sub_401588 (4199816)>,
 <Function sub_401590 (4199824)>,
 <Function sub_401598 (4199832)>,
 <Function sub_4015a0 (4199840)>,
 <Function sub_4015a8 (4199848)>,
 <Function sub_4015b0 (4199856)>,
 <Function printf (16777216)>,
 <Function scanf (16777220)>,
 <Function strlen (16777224)>,
 <Function atoi (16777228)>,
 <Function _controlfp (16777232)>,
 <Function _set_app_type (16777236)>,
 <Function _getmainargs (16777240)>,
 <Function exit (16777244)>]

Немного отвлеклись от решения. Чтобы добраться до правильного вывода необходимо указать Angr к какому адресу ему надо дойти.

Получить адрес мы можем из распечатанного CFG

Возьмем адрес, который нам необходимо пройти для того чтобы мы получили "Yes! Correct flag is". Я взял 0x4014CE.

Теперь зададим первоначальное состояние:

state = p.factory.entry_state()

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

Символическое выполнение позволяет выполнять SimulationManager. Задав изначальное состояние и указав конечное применим его

sm = p.factory.simgr(state)
sm.explore(find=0x4014ce)

На этом этапе Angr начинает символическое исполнение. Формат достаточно простой

sm.epxplore(find="Необходимые адреса", avoid="Ложные адреса")

По истечении определенного срока, ANGR выдаст подобный результат

Out:

<SimulationManager with 14 deadended, 1 found>

Где в результате мы имеем 1 решение для нашего “уравнения”

solution = sm.found[0]
solution.posix.dumps(0)

Out:

b'333gogog0gog0g0g0'

Поздравляю, мы решили таск. Причем замечу, мы не пользовались ни IDA, ни Ghidra…

Решение подобных заданий не требует участия реверс-инженера, если он имеет подобный скрипт за пазухой

import angr

p = angr.Project("project_name")

def find_the_bytes(state):
    return  b"WELLDONE" in state.posix.dumps(1)

state = p.factory.entry_state()
sm = p.factory.simgr(state)
sm.explore(find=find_the_bytes)
solution = sm.found[0]
print(solution.posix.dumps(0))

ANGR - новое оружие в арсенале реверс-инженера, позволяющее автоматизировать большой пласт работы, ведь состояние нашей конечной системы ограничено лишь нашей фантазией и временем ее решения/достижения.

4 Likes