кароче говоря там из гаджетов только безусловные jop-ы и всего один единственный ret;
я подумал, что этот таск - это жопа.
а жопа состоит из двух полужопий.
и между ними нужно вставить ret;
а пиндосы по другому проходят
они сначала ret; кладут
я подумал, что этот таск - это жопа.
а жопа состоит из двух полужопий.
и между ними нужно вставить ret;
а пиндосы по другому проходят
они сначала ret; кладут
Предварительные ласки
Сам бинарник весит 4.4кб. В нем нету не plt ни got. не залинкована не одна либа. Нету не единой функции, нету даже названия у main().
мда, не густо. стек с защитой от выполнения. ну и aslr на сервере на уровне системы. посмотрим что внутри....
Прилюдия
а что вы знаете про минимализм?
start
vmmap
i fu
жесть. не исполняемый стек, и 4096 байт инструкций. и всё.
сперва появилась идея использовать vdso. такие функции, как time и gettimeofday имеют в себе огромное множество полезных вещей. Но, но на удалёной системе aslr.
задумавшись про ядро я вспомни про dmesg:
мда. исходя из увиденного, можно предположить, что у нас есть буффер. что программа крашится в любом случае. и что если буффер переполнить - то крашится она чуть иначе.
а ещё, что буффер не большой, так как излишки паттерна выплюнуло обратно в терминал.
Раздевайся
Пролог (преамбула) - кладём в стек указатель стекфрейма - $RSP. далее буквально капля кода. есть syscall, это уже хорошо. в конце, вместо эпилога, идёт безусловный переход по адресу, который находится в ячейке памяти на 8 байт меньше, чем ячейка памяти, которую занимает $RSP - указатель стекового фрейма.
зато над основным телом программы есть мёртвая зона с кодом.
тут-то мы и будем искать гаджеты.
но сперва посмотрим на краш под дебагером. и на память в процессе краша.
Экшен!
скармливаем паттерн, доходим до джампа. на этом джампе смело можно ставить брэйк, он нам понадобится. сверху ничего интересного не происходит.
как мы видем ведёт он в никуда.
C-подобный:
pwndbg> p 12*8*2
$2 = 192
pwndbg> p 12*8*2 - 16
$3 = 176
Последняя ячейка принадлежит текущему указателю стека - $RSP.
Джамп указывает на предыдущую ячейку памяти, как бы намекая нам на жоп-чейн. Забегая вперёд скажу, что JOP-чейн - это альтернатива привычным ret; чейнам. Используется в ситуациях когда нормальных гаджетов нету, либо когда нужно обойти файрвол или какой-либо модуль защиты на удалённом сервере. Так как про 0x41414141 все знают, то существуют комплексные решения для защиты серверов, которые тут же улавливают подобные вещи и сообщают куда надо. Но это не наш случай. Пока что не наш :3
А в нашем случае нужно делать stack pivoting.
Это методика, которая позволяет создать фейковый стекфрейм (вернее он настоящий, но не предусмотрен самой программой) и создать его в таком месте, где у нас есть возможность управлять памятью. То бишь - расширить себе стек.
что ещё мы знаем про ассемблер?It seems that the easiest way to set the stack pointer to a specific value is by using the xchg gadget. It exchanges the EAX and ESP register values. The plan is therefore:
mov esp, edx; ret; == push edx; pop esp; ret
пока всё. у нас есть аж 3 мненоники которые позволят нам это осуществить. (вообще их значительно больше, но я знаю пока только эти)
Жоп-Чейн
Я специально оттягивал это на по-позже.
0x\ue\n\n0
эээээээээ..... всё что с сисколлом - сразу нет.
остаётся 4 гаджета. первый - нет, т.к. новый указатель будет в младших байтах от старого (выше).
второй тоже нет, т.к. он не влияет на указатель.
и 3 и 4 тоже не влияют. и шо делать?
ноу-ретурну ret; приделать.
и так, логика. у нас в стекфрейме 2 ячейки, в одну попадает каретка ($rip), во вторую мы можем записать данные.
мы берем и записываем в первую ячейку адрес гаджета, а во вторую ячейку - адрес начала нашего нового стекфрейма. каретка попадает на pop rsp, берет значение из текущего стекфрейма (тоесть из следуещей ячейки в данном случае, так как она у нас по совместительству ячейка текущего стекфрейма), и дублирует его в регистр $rsp. после этого стекфрейм сдвигается, а то, что находится в пределах нового стекфрейма - попадает в стек соответственно.
это какая-то просто 3/14 + 0x3da
реально, человеческому мозгу сложно думать снизу вверх и слева на право. ещё и в 16-ричной системе думанья. Но, это эксплойтинг, чёрная магия. А это значит, что вопрос только желания и усилий.
Я решил себе эту задачу с помощью подключения в дело ассоциативного мышления, так как мой мозг не работает в 16-ричной, и ему сложно было счтитать. по этому я расширил регистры своего мозга
C-подобный:
( (p64(0x1) + p64(0x2) + p64(0x3) + p64(0x4) + p64(0x5) + p64(0x6) + p64(0x7)) + p64(0x8) + p64(0x9) + p64(0xa) + p64(0xb) + p64(0xc) + p64(0xd) + p64(0xe) + p64(0xf) + p64(0x20) + p64(0x21) + p64(0x22) + p64(0x23) + p64(0x24) + p64(0x25) + p64(0x26) + ) ((( p64(0x00401000)+p64(0x7fffffffe4a8) )))
сперва я сделал вот так. пошёл на уступки всё-так процессору, проявил ссолидарность, и начал говорить с ним на его языке.
он ответил мне взаимностью, и пояснил, что в стеке лежат не данные, а указатели на данные и данные.
то бишь, jmp [rdi] == переход не на инструкцию, которая лежит в $rdi, а на инструкцию, которая лежит по адресу, который лежит в $rdi.
гениальная хрень. как в шахматах или в шашках. всё ради одного региста - rdx
а потом - джамп через rcx. куда?? нам осталось сделать 2 вещи:
1 - положить в $RAX 0x3b
2 - вызваать syscall
ага ага. только мы с rcx прыгаем в жоп. а с жопа - в rcx. привет, infloop.
сутки где-то я думал что-же делать.
оказалось просто. вставить ret;
а за ret; положить в память то, что нам нужно. я так до конца и не понял опираясь на что процессор выбирает ячейку из которой ему вздумается взять следующий адрес для rsp (после ret фрейм так же сдвигается), но поскольку мы с ним договорились - он обьяснил мне на пальцах.
ну и для красаты чисто
Эпилог
В начале бинарник выплёвывает нам лик. это адрес последней ячейки из 192.
не сильно сложная математика, если учитывать особенность данных в памяти и указателей на данные в памяти.
Это был Кот. Если шо - на связи.