Введение

Привет всем, это моя первая запись, и сегодня мы увидим решения комнаты pwn101 на TryHackMe.



Все эти задачи относятся к бинарной категории, и их 10 от pwn101 до pwn110. Итак, начнем с первого.

PWN 101 — переполнение буфера

В первой задаче, если мы воспользуемся функцией checksec функции pwntools, как в примере ниже, мы увидим, что у бинарного файла есть множество защит.

Это кажется сложным, но, если мы внимательно посмотрим на код с ghidra, мы увидим, что нам нужно переполнить только тот код, который очень прост из-за gets, что опасно тем, что он не считает привязку массива. Ниже приведен очень простой скрипт для ее решения.

И вот результат, когда мы подключаемся к серверу через VPN от TryHackMe.

PWN 102 — изменить значение переменной

Во второй задаче бинарник говорит, что хочет «плохой еды», но что это значит с плохой едой? Как и в первой задаче, мы можем попытаться проверить наличие какой-либо защиты с помощью checksec.

Опять же, мы видим, что защит много, поэтому, прежде чем мы начнем усложнять себе жизнь, давайте посмотрим на код с Ghidra, который позволил нам увидеть демо кода на c.

С помощью этого кода мы видим, что программа проверяет, равны ли «local_c» и «local_10», которые мы будем называть var1 и var2, «0xc0d3» и «0xc0ff33». Что мы можем сделать, чтобы изменить их значения?

Мы можем переполнить буфер (это легко, потому что этот scanf не считает количество символов), но вместо того, чтобы перезаписать указатель инструкции, мы должны просто написать немного больше в буфере, где находятся все переменные, используемые в программе. .

Помните, что это правда, потому что переменные объявляются в стеке, а не в куче.

Вот краткий скрипт для решения задачи.

Ниже результат.

PWN 103 — Вернуться к Win

Название говорит само за себя, ведь мы должны сделать переполнение буфера, чтобы изменить указатель Instruction на то, что он будет указывать на функцию win. Прежде чем начать, мы должны иметь в виду идею переполнения буфера. Это означает, что мы хотим переписать буфер, чтобы достичь указателя Base, который находится перед указателем Instruction. А затем перезаписываем его нужной функцией.

После этой предпосылки нам просто нужно найти адрес функции win с помощью GDB или чего угодно.

Мы нашли тип функции win, которая в данном случае называется admins_only, где мы видим системную функцию, которая вызывает для нас bash. Теперь у нас есть все, что нужно для выполнения этой задачи, так что давайте напишем скрипт.

Мы должны ввести цифру 3 перед полезной нагрузкой, чтобы попасть в общий канал, где мы можем записать в буфер, а затем перед вызовом функции администратора мы должны добавить гаджет ret, который будет выравнивать стек.

PWN 104 — вернуться к шеллкоду

Это четвертая задача этой комнаты, и мы видим, что есть переполнение буфера, заданный адрес и все защиты отключены (кроме RELRO, о котором мы поговорим позже). Как следует из названия, мы должны выполнить переполнение буфера, чтобы выполнить шелл-код, который вызовет bash с системой из-за разрешения setuid, которое используется для выполнения программы от имени пользователя root. Прежде чем мы начнем, как всегда, давайте посмотрим, верны ли наши прогнозы с Ghidra.

Да, мы были правы, потому что есть операции чтения, которые принимают количество чисел, равное 200, когда буфер имеет размер 80. После этого мы видим, что указанный адрес является началом нашего буфера из стека, потому что у нас есть %p, возвращающий адрес из стека. Это очень полезно, потому что наш план состоит в том, чтобы записать в буфер исполняемый шелл-код, а затем изменить RIP, который в другой раз укажет на наш буфер, где теперь находится шелл-код, который будет выполняться.

Я предпочел использовать функцию shellcraft для создания шелл-кода, который вызывает bash, вместо того, чтобы искать его в Интернете, но если вы хотите, вы можете искать даже на shell-storm: https://shell-storm.org/shellcode /.

Кстати вот результат.

PWN 105 — целочисленное переполнение

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

С ghidra мы видим, что программа берет два числа и суммирует их вместе, и наша цель — сделать результат ‹ равным 0, но мы не можем вставлять отрицательные числа, иначе произойдет сбой.

Это кажется невозможным, но если вы введете число, превышающее его размер, оно будет преобразовано в отрицательное с помощью дополнения до 2. Это связано с тем, что тип данных int изменяется от -2147483647 до 2147483647, а отрицательные числа создаются путем ввода числа выше максимального. Зная это, если мы введем в качестве первого числа 2147483647, а в качестве второго числа 1 мы получим -2147483648, а если мы добавим 2147483647 к самому себе, мы получим -2.

Как мы и ожидали, мы получили свой удар.

PWN 106 — эксплойт форматирования строки

Мы подошли к первой проблеме с уязвимостью строки формата, но прежде чем мы начнем, что такое уязвимость строки формата?

Они представляют собой что-то вроде переполнения, зависящего от использования некоторых функций, таких как знаменитая «printf» и все функции его семейства, такие как «fprintf», «spritf», «snprintf» и многие другие. Это потому, что все эти функции нуждаются в спецификации типа данных, такой как «%d», и если мы забудем их использовать, злоумышленник вместо того, чтобы писать реальный ввод, может написать их и заставить printf получить данные из стека.

Как вы можете видеть в printf, который получает наш буфер, тип данных отсутствует, поэтому, когда он будет печатать этот буфер, если printf увидит тип данных, он примет его как указатель на стек.

После этой предпосылки какова наша цель? В этом случае нам просто нужно найти флаг в стеке, введя некоторые типы данных, такие как «%p», которые получают адрес из стека.

В этом примере мы написали немного «A», а затем немного %p, и, как вы можете видеть, мы нашли «A», просочившись через уязвимость строки формата. Это означает, что мы можем сделать то же самое, но на этот раз мы хотим найти флаг. Чтобы сделать это проще, мы можем мгновенно попробовать это удаленно.

Код подключается удаленно i раз от 6 до 11, числа, где я нашел флаг, затем он берет адреса, данные из программы, удаляет символ «x», переворачивает и суммирует их вместе.

PWN 107 – обход средств защиты

Это будет первая задача, в которой мы воспользуемся уязвимостью строки формата, чтобы вызвать мощное переполнение буфера.

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

Как всегда, прежде чем мы начнем, давайте проверим защиту и код с помощью ghidra.

Это кажется очень сложным, потому что у нас есть все средства защиты, но, как мы и предполагали, у нас есть printf без типа данных. Это означает, что мы можем использовать его для утечки канарейки и функции программы. Так что мы можем попытаться найти их, просочившись в некоторые адреса.

После некоторой попытки мы понимаем, что мы не можем продолжать писать только «%p», иначе мы будем заблокированы канарейкой, поэтому мы должны использовать другой тип скрипта, чтобы увидеть адреса ниже. Для этого мы можем сделать это так:

“ % ‹Позиция в стеке›$‹используемый тип данных›.

С помощью этого метода мы находим канарейку и функцию входа

Не забудьте найти канарейку, посмотрите, есть ли в адресе два 0 в конце, и чтобы найти любую функцию, сопоставьте базу с той, что задана из библиотеки или даже из GDB, потому что последние 3 цифры не меняются.

Со всей этой информацией мы можем написать наш скрипт.

В приведенном выше коде мы утекли канарейку и функцию входа. Мы использовали канарейку в качестве входных данных перед базовым указателем, а запись — для нахождения базового адреса функций. С базовым адресом мы можем найти любую функцию в программе, просто добавив к нему смещение функции, которую мы хотим. Функции можно найти с помощью readelf следующим образом:

Или еще лучше (во время выполнения), объявив переменную, которая принимает двоичный файл, как в этом случае «elf = ELF(«./pwn107.pwn107»)», и для поиска функций:
get_streak_offset = elf.symbols['get_streak ']

Если мы найдем базовый адрес эльфа или, как правило, библиотеки и сохраним его в такой переменной: elf.address = ‹base_address_found›, каждый раз, когда мы находим функцию, как мы делали выше, она будет автоматически вычислять реальный адрес его. В конце вот результат этой задачи.

PWN 108 – перезапись выполнена

В этой задаче, как следует из названия, нам нужно перезаписать GOT или таблицу глобальных смещений, но что это такое?

Прежде чем мы начнем говорить о GOT, мы должны упомянуть PLT или таблицу точного связывания, потому что именно там происходит переход функции при ее вызове. Например, если мы вызовем printf, он не перейдет непосредственно в printf, вместо этого он перейдет в printf@plt, который указывает на printf@got (даже лучше, [email protected], указанный from вызывается функцией plt ) или реальной функции, которую мы хотим вызвать.

Таким образом, PLT представляет собой таблицу, состоящую из адресов, указывающих на .got.plt или реальную функцию. В то время как GOT - это фактическая таблица смещений, заполненная компоновщиком для внешних символов.

После этих небольших скобок о GOT и PLT, как я думаю, вы поняли, наша цель состоит в том, чтобы перезаписать запись функции в программе, например, puts, адресом функции выигрыша.

Помните, что все это возможно, только потому, что у нас в качестве защиты Частичный Релро, а не Полный, иначе он не будет читать наши изменения.

Запустив программу, мы можем увидеть, что в качестве первого входа у нас есть обычная переменная, а во втором у нас есть уязвимость строки формата. Зная это, мы можем сначала найти, где наш ввод помещается в стек.

Теперь, когда у нас есть ввод на %6$p, мы можем записать в качестве первого ввода адрес размещения, а затем во втором мы можем использовать «%n», который записывает в заданный адрес количество символов, которые мы пишем. Таким образом, мы можем записать на шестой позиции адрес, преобразованный в int или строку, который будут занимать путы, отправленные на первый вход. Вот код, который объясняет это.

После нескольких секунд выполнения мы получаем это.

PWN 109 – вернуться в PLT

Вернитесь к PLT или вернитесь к libc, кстати это будет одна из самых интересных задач, потому что у нас нет ни printf без типа данных, ни функции win, а у нас есть только get и из него мы должны вызывать системная функция с параметром «/bin/sh».

Итак, как всегда, давайте посмотрим на защиту.

В качестве основной защиты у нас есть стек «NX» или «Не исполняемый», и, как гласит аббревиатура, мы не можем выполнить этот стек. Это кажется сложным, но у нас есть союзник или библиотека программы. Библиотека может быть настолько мощной, если мы объединим ее с PLT и GOT программы, потому что таким образом мы можем вызвать, например, функцию puts из PLT, которая имеет в качестве параметра функцию, подобную setvbuf, или даже функцию puts. сам, из GOT.PLT.

С помощью этого метода мы можем заставить программу распечатать каждую функцию, которая у нее есть, что позволит нам обнаружить имеющуюся у нее libc, с помощью которой мы сможем вычислить разницу между утечкой и смещением этой функции, найденной в библиотеке, чтобы найти базовый адрес libc. Имея это, мы можем вычислить каждую функцию времени выполнения программы, просто добавив к ней смещение функции из библиотеки libc.

Вот что я сделал.

Идея этого кода такая же, как я объяснял ранее, потому что мы создаем первую полезную нагрузку, в которой мы вызываем путы, которые в качестве параметра имеют setvbuf. Этим вызовом мы получаем утечку, из которой можем вычислить базовый адрес libc, после чего возвращаемся к основному. Этот трюк позволяет нам перезапустить программу, но на этот раз мы знаем все функции программы, включая «систему», поэтому нам нужно только найти «/bin/sh». Мы можем сделать это с помощью «libc.search(b’/bin/sh’)», но мы должны пропустить первую часть фразы «SHELL = /bin/sh», используя следующую функцию. Теперь у нас есть все, что нам нужно, чтобы сделать нашу вторую полезную нагрузку, созданную путем заполнения, для достижения RIP, ret для выравнивания стека, pop_rdi для передачи аргумента /bin/sh в систему, а затем вызов этого последнего.

И после нескольких попыток вот результат.

PWN 110 – игра с ROP

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

Мы даже можем проверить это с помощью команды «ldd».

На изображении выше видно даже, что там и защита "NX" включена, и канарейка, но с последней проблем не будет, так как если мы откроем программу на gdb или ghidra, то увидим, что проверки на нее нет .

Для этой задачи мы можем определить два разных способа ее решения:

Первый — это обычный метод, в котором мы должны вызвать функцию, подобную системе, с параметром «/bin//sh». Единственная проблема на этот раз в том, что у нас нет никаких библиотек, то есть мы не можем найти /bin/sh из программы, и даже системы нет. Как ни странно, но мы можем обойти эти проблемы с помощью выдаваемых программой гаджетов 39322, которых много для нормы. Поэтому мы можем делать то, что хотим. Итак, сначала мы должны поместить строку «/bin//sh» в часть памяти как .data, которая будет иметь функцию временного хранилища, а затем мы должны сделать системный вызов execve, который в качестве параметров имеет «/bin/ /sh», из раздела .data, за которым следуют два байта NULL.

В этом коде я поместил в rdx строку «/bin//sh», а в rdi — адрес раздела .data, затем я переместил строку, хранящуюся в rdx, в rdi, указывающую на раздел данных. После этого я заполнил оставшиеся регистры нулевыми байтами и, в конце концов, сделал системный вызов с числом 59, то есть значением системного вызова execve. И это результат.

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

Но какие параметры он принимает? Он принимает три аргумента:

  1. Начальный адрес памяти для изменения;
  2. Длина памяти, начиная с данного адреса;
  3. Тип защиты, который нам нужен, например, PROT_READ, PROT_WRITE и PROT_EXEC.

Итак, прежде всего нам нужно найти начальный адрес стека, который нужно изменить. Мы можем сделать это, используя функцию, полученную из программы под названием «__libc_stack_end», как следует из названия, она возвращает конечный адрес стека. При этом мы можем просто сделать двоичную маску, чтобы получить адрес без первых трех цифр, то есть, конечно, адрес до нашего ввода и стека. Вторым параметром будут цифры, которые мы удалили из адреса, так как мы удалили 3 бита, это будет 0x1000. В итоге последним параметром будет 0x777, что делает часть памяти доступной для чтения, записи и выполнения.

Вот как я его построил.

Я сделал первый ввод, где я вызвал puts с параметром __libc_stack_end_ в качестве параметра для его утечки, затем я вернулся к основному, чтобы построить реальную полезную нагрузку. Этот сделан с площадкой для доступа к RIP, ret для выравнивания стека, щелчками трех регистров для передачи аргументов для mprotect, вызовом этого последнего, вызовом RSP, чтобы вернуться с того места, где мы были перед rops и выполнить шеллкод.

Это конец этой комнаты, спасибо за чтение, я надеюсь, что это было полезно и интересно.

0xCY@