remote storage (pwn_3 writeup) [AeroCTF]
m4dratEN:
Description: "Our programmers have done some kind of remote storage for various documents. They say there you can even sign your documents, as well as create files for them with additional information about the owner. Can you test?"
Reversing:
So, let's start by opening it in IDA. Here we can see that this binary is statically linked at least with libc and, unfortunately, there are no debug symbols in it, so you will have to spend some time understanding what is happening. Let's take a look at the main() function (it is reduced to be more readable):

We can see a simple function (check_creds), which checks user supplied data:

It compares user input with the 'admin' string, and if everything is correct, proceeds to the main menu:

Menu has the ability to perform one of 7 actions:
- Read from file
- Write to file
- View all existing files
- Sign file (calculate md5 of whole file, and then xor it with signer_name)
- Add additional information about the file
- Read additional information
- Just leave the program
Let's go in order. Download_file() function:

Gets filename as an input. If the file exists and its name does not contain restricted characters ('.', '\\', '/'), this function will print its content.
Next function upload_file():

This function performs the same filename validation mechanism as the previous one. If the check is passed, you can write up to 512 bytes to the file. If the write ends with non-zero bytes read, null terminates the resulting string.
Third option calls system function with "ls files" as a parameter:

Sign_file() function:
Asks the user for the file name, checks it for special characters, and tries to open it in read mode. If the file is successfully read, null terminates the resulting string and gives control to the next block:

At the moment we already can notice some interesting moments. First, the username of the file signer is requested. Later this data is xored (line: 54) with md5 hash from the file content (including '\n', '\t', ...), and then all this data passed into printf(), with no specific format, which means that we found the first vulnerability, namely the format string one. But there are a few things, which will increase hardness of writing reliable exploit:
- String buffer size is equal to the md5 hash block size (16 bytes), which is quite small.
- Function performs XOR with a file content hash.
Even though second point is quite simply to solve, first one remains a big problem. So let's digress from this for a while and look at the remaining functions.
Add_file_info() function:
The same basic file name and existence checks are performed as in the previous functions. After all checks, the user is prompted to enter additional information about the file (owner name, date of creation):

Immediately notice that in the buffer date reads 512 bytes, while its real size is guaranteed to be less than 512 (IDA could not accurately restore the size of the variables, but under the entire stack frame was allocated 576 bytes)...

Sooooo, here is BOF. That's good. This will allow us to directly rewrite the EIP. Really? Not really. The main remaining problem is the presence of a canary on the stack. Here we need the previous vulnerability of the format string. With it, we'll leak the canary, then overflow the buffer and overwrite ret_addr with anything. In this task there is a presence of one_gadget RCE, possibility of simple writing of a full-fledged ROP chain, a direct call to system. Also, in theory, you could overwrite the __malloc_hook / __free_hook, and also get rce. To write my exploit, I chose to call system.
Last function view_file_info():

Nothing special here. Just reads additional info of specific file.
Exploitation:
The plan for writing is as follows:
- with xored format string leak stack canary
- Overflow buffer in add_file_info() function
- Jump to system with appropriate arguments
Let's start the first part:
Since we know what our format string is xored with, it will not be difficult to write a program to perform reverse actions:


Perfect! Now having the ability to use a formatted string we will leak canary. Let's use gdb to find the required offset. You will first need to get the actual canary value before calling sign_file(). It will be in EAX:

Knowing it, we can find its relative position on the stack at the time of the call to printf():


1 - Pointer to a format string
2 - Canary

-> the format string should look something like this: "%95$p". Let's see if our guesses are correct. Here the Canary is set in function prologue:

Output of the exploit:

Perfect! Now there's the easy part. Overflow and call system(). Here is payload structure: <junk> + <canary> + <system> + <ret_after_system> + <"/bin/sh" ptr>
First, let's find the buffer size (use an easily recognizable pattern):


So we found the required number of characters before the canary was overwritten. => now the payload looks like this: "A" * 384 + canary + system + "ZZZZ" + "/bin/sh " ptr. It remains to get the address of system, as well as a pointer to "/bin/sh". The first (and second) we immediately can find with the help of IDA:


Payload: "A" * 384 + p32(canary) + p32(0x8052cf0) + "ZZZZ" + p32(0x80c7b8c).
Final exploitation:

Exploit source: exp
RU:
Описание: "У нас программисты сделали какое-то удалённое хранилище для различных документов. Говорят там даже можно подписывать свои документы, а также создавать к ним файлы с дополнительной информацией о владельце. Можете протестировать?"
Реверс:
Итак, начнем решение таска с открытия его в IDA. Сразу можно заметить, что бинарник статически слинкован как минимум с libc, а также, к сожалению, в нем отсутствуют дебаг символы, так что придется потратить немного времени на понимание происходящего и поиск нужных адресов. Взглянем на функцию main() (она уже приведена к читаемому формату):

Видим простую проверку, check_creds():

Она сравнивает пользовательский ввод со строкой 'admin', и если все верно, пропускает в основное меню:

В нем есть возможность выполнить одно из 7 действий:
- Прочитать файл
- Записать файл
- Просмотреть список уже созданных файлов
- Подписать файл
- Добавить файлу информацию (дата создания, имя автора, ...)
- Посмотреть дополнительную информацию о файле
- Выйти
Пойдем по порядку. Функция download_file():

Получает на вход имя файла. Если файл существует, а также в его имени не присутствуют запрещенные символы ('.', '\\', '/'), происходит вывод его содержимого.
Следующая функция upload_file():

Использует такой же механизм проверки валидности имени файла, как и предыдущая. Если проверка пройдена позволяет записать до 512 байт в файл. Если запись завершается с не нулевым количеством прочтенных байт null-терминирует получившуюся строку.
Третья опция вызывает system с аргументом "ls files":

Функция sign_file():
Запрашивает у пользователя имя файла, проверяет его на наличие спец символов и пытается его открыть в режиме для чтения. Если чтение файла завершается успешно, то null-терминирует получившуюся строку и передает управление следующему блоку:

В нем уже можно заметить интересные моменты. Для начала запрашивается имя пользователя подписывающего файл. Позже эти данные ксорятся (строка:54) с md5-хешем от содержимого файла (включая '\n', '\t', ...), а затем все эти данные попадают в printf(), при чем без конкретного формата -> мы нашли первую уязвимость, а именно уязвимость форматной строки. Но есть несколько моментов осложнящих написание эксплоита:
- размер буфера для строки равен размеру блока md5-хеша (16 байт), что довольно мало.
- Производится операция xor с хешем от содержимого файла.
Если второй пункт довольно просто решается, то вот с первым будут проблемы. Поэтому на время отвлечемся от этого и взглянем на оставшиеся функции.
Функция add_file_info():
Производятся такие же базовые проверки имени и существования файла как и в предыдущих функциях. После всех проверок пользователю предлагается ввести дополнительную информацию о файле (имя владельца, дата создания):

Сразу замечаем, что в буфер date читается 512 байт, в то время, как его реальный размер гарантированно меньше 512 (IDA не смогла точно восстановить размеры переменных, но под весь стековый фрейм было выделено 576 байт)...

Получаем BOF. Уже неплохо. Это позволит нам напрямую влиять на EIP. Точно? Не совсем. Главная оставшаяся проблема, это наличие канарейки на стеке. Тут то нам и пригодится прошлая уязвимость форматной строки. С помощью нее мы сольем канарейку, затем переполним буфер и перезапишем ret_addr чем угодно. В данном таске есть наличие one_gadget RCE, возможности простого написания полноценной ROP цепочки, прямого вызова system. Также, в теории, можно было бы переписать __malloc_hook / __free_hook и тоже получить rce. Для написания своего эксплоита я выбрал вариант с вызовом system.
Оставшаяся функция view_file_info():

Просто читает дополнительную информацию конкретного файла.
Эксплуатация:
План для написания следующий:
- С помощью проксоренной форматной строки слить stack canary
- Переполнить буфер в add_file_info()
- Совершить прыжок на system()
Приступим к первой части:
Т.к. мы знаем с чем ксорится наша форматная строка не составит труда написать программу для совершения обратных действий:


Отлично. Теперь имея возможность использовать форматную строку сольем канарейку. Воспользуемся gdb для поиска необходимого смещения. Сначала потребуется получить актуальное значение канарейки перед вызовом sign_file().
Оно будет находиться в EAX:

Зная его мы сможем найти его относительное расположение на стеке в момент вызова printf():


1 - Указатель на форматную строку.
2 - Канарейка

-> форматная строка должна выглядеть как-то так: "%95$p". Проверим верны ли наши догадки. Вот канарейка выставляется в проголе функции:

А вот, что выдает эксплоит:

Отлично. Теперь остается самая простая часть. Переполнение и вызов system().
План payload'a: <junk> + <canary> + <system> + <ret_after_system> + <"/bin/sh" ptr>
Для начала найдем размер буфера (воспользуемся легко узнаваемым паттерном):


Так мы нашли необходимое количество символов до момента перезаписи канарейки. => теперь payload выглядит так: "A" * 384 + canary + system + "ZZZZ" + "/bin/sh" ptr. Осталось получить адрес system, а также указатель на "/bin/sh". Первое (да и второе) находим моментально с помощью IDA:


Payload: "A" * 384 + p32(canary) + p32(0x8052cf0) + "ZZZZ" + p32(0x80c7b8c).
Финальная эксплуатация:

Код эксплоита: exp