Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

книги хакеры / Защита_от_взлома_сокеты,_эксплойты,_shell_код_Фостер_Дж_

.pdf
Скачиваний:
14
Добавлен:
19.04.2024
Размер:
3.68 Mб
Скачать

 

 

 

 

 

hang

e

 

 

 

 

 

 

 

 

 

 

 

 

hang

e

 

 

 

 

 

 

 

 

C

 

 

E

 

 

 

 

 

 

 

 

C

 

 

E

 

 

 

 

 

X

 

 

 

 

 

 

 

 

 

 

 

 

X

 

 

 

 

 

 

 

 

 

-

 

 

 

 

 

 

 

d

 

 

 

 

-

 

 

 

 

 

 

 

d

 

 

F

 

 

 

 

 

 

 

 

 

t

 

 

 

 

F

 

 

 

 

 

 

 

 

 

t

 

 

D

 

 

 

 

 

 

 

 

 

 

i

 

 

 

 

D

 

 

 

 

 

 

 

 

 

 

i

 

 

 

 

 

 

 

 

 

 

 

 

r

 

 

 

 

 

 

 

 

 

 

 

 

 

r

P

 

 

 

 

 

 

 

NOW!

o

 

 

P

 

 

 

 

 

 

 

NOW!

o

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

BUY

 

 

 

 

 

 

 

 

 

 

 

BUY

 

 

 

 

 

 

 

to

 

 

 

 

 

 

 

Пример: уязвимость, связанная с переполнением буфера 561

 

 

to

 

 

 

 

 

 

w Click

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

m

 

 

w Click

 

 

 

 

 

 

 

 

m

w

 

 

 

 

 

 

 

 

 

 

 

 

 

 

w

 

 

 

 

 

 

 

 

 

 

 

 

 

w

 

 

 

 

 

 

 

 

 

 

o

 

 

 

 

w

 

 

 

 

 

 

 

 

 

 

o

 

 

.

 

 

 

 

 

 

 

 

.c

 

 

 

 

.

 

 

 

 

 

 

 

 

.c

 

 

 

p

 

 

 

 

 

 

 

e

 

 

 

 

 

p

 

 

 

x cha

 

e

 

 

 

 

d

 

 

xchtypedef struct _t_ {

 

 

 

d

 

 

g

 

 

 

 

 

 

 

f-

 

an

WORD

t_s;

/* размер этого элемента */

 

 

 

 

f-

 

 

n

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

WORD

t_p;

/* родительский узел */

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

WORD

t_l;

/* левый потомок */

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

WORD

t_r;

/* правый потомок */

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

WORD

t_n;

/* следующий элемент в списке */

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

WORD

t_d;

/* не используется, зарезервировано */

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

/* для указателя на себя */

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

} TREE;

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

Сама структура дерева стандартна. В поле t_s хранится размер выделенного блока. Эта величина округляется до границы слова, так что в двух младших битах можно хранить флаги. Самый младший бит t_s равен 1, если блок используется, и 0 – если он свободен. Следующий бит проверяется только, если младший бит равен 1, и в этом случае он равен 1, если предыдущий блок свободен, и 0, если занят.

Âдействительности используются только поля t_s, t_p è t_l. Пользовательские данные хранятся по адресу, который находятся в поле t_l.

Логика алгоритма управления проста. Когда блок освобождается функцией free(), младший бит в поле t_s сбрасывается в 0, помечая тем самым, что блок свободен. Если число свободных узлов достигло некоторого порога, обычно 32, и освобождается еще один блок, то дерево передается функции realfree, которая реально освобождает память. Смысл такого решения в том, чтобы уменьшить число дорогостоящих операций освобождения и повысить за счет этого производительность. Функция realfree заново балансирует дерево, чтобы оптимизировать последующие вызовы malloc è free в будущем. Во время этой операции проверяется бит занятости в соседних блоках. Если оба блока свободны, они объединяются, и новый блок перемещается на нужное место в дереве в соответствии со своим размером. Как и в случае dlmalloc во время объединения производятся манипуляции над указателями.

Âпримере 11.5 приведена реализация функции realfree, эквивалентной chunk_free в dlmalloc. Именно здесь открывается возможность для эксплойта, так что имеет смысл хорошо разобраться в этом коде.

Пример 11.5. Функция realfree

1 static void

2 realfree(void *old)

3 {

4

TREE

*tp, *sp, *np;

5

size_t ts, size;

6

 

 

7

COUNT(nfree);

8

 

 

9

/* указатель на блок */

10tp = BLOCK(old);

11ts = size(tp);

 

 

 

 

hang

e

 

 

 

 

 

 

 

 

C

 

E

 

 

 

 

 

 

X

 

 

 

 

 

 

 

 

-

 

 

 

 

d

 

 

 

F

 

 

 

 

 

 

t

 

 

 

D

 

 

 

 

 

 

 

i

 

 

 

 

 

 

 

 

 

 

r

 

P

 

 

 

 

 

NOW!

o

 

 

 

 

 

 

 

 

 

 

 

 

 

 

BUY

 

 

 

 

 

 

 

to

 

 

562 Глава 11. Написание эксплойтов II

w Click

 

 

 

 

 

 

 

 

 

 

 

 

 

m

 

w

 

 

 

 

 

 

 

 

 

 

 

w

 

 

 

 

 

 

 

o

 

 

 

.

 

 

 

 

 

.c

 

 

 

 

p

 

 

 

 

g

 

 

if (!ISBIT0(ts))

 

 

 

 

 

 

 

e

 

 

 

 

df-xchan

12

 

 

 

 

 

 

 

 

13

return;

 

 

 

 

 

 

 

 

14

CLRBITS01(SIZE(tp));

 

 

 

 

 

 

 

 

15

 

 

 

 

 

hang

e

 

 

 

 

 

 

 

C

 

E

 

 

 

 

X

 

 

 

 

 

 

-

 

 

 

 

 

d

 

 

F

 

 

 

 

 

 

t

 

 

D

 

 

 

 

 

 

 

i

 

 

 

 

 

 

 

 

 

r

P

 

 

 

 

 

NOW!

o

 

 

 

 

 

 

 

 

 

 

 

 

BUY

 

 

 

 

 

 

to

 

 

 

 

 

w Click

 

 

 

 

 

m

 

 

 

 

 

 

w

 

 

 

 

 

 

 

 

 

 

w

 

 

 

 

 

 

 

o

 

 

.

 

 

 

 

 

.c

 

 

 

p

 

 

 

 

g

 

 

 

 

 

df

 

 

n

e

 

 

 

 

 

-x cha

 

 

 

 

16/* небольшой блок, поместить в нужное место связанного списка */

17if (SIZE(tp) < MINSIZE) {

18ASSERT(SIZE(tp) / WORDSIZE >= 1);

19ts = SIZE(tp) / WORDSIZE – 1;

20AFTER(tp) = List(ts);

21List[ts] = tp;

22return;

23}

24

25/* можно ли объединить со следующим блоком? */

26np = NEXT(tp);

27if (!ISBIT0(SIZE(np))) {

28if (np != Bottom)

29t_delete(np);

30SIZE(tp) += SIZE(np) + WORDSIZE;

31}

32

33/* можно ли объединить с предыдущим блоком? */

34if (ISBIT0(ts)) {

35np = LAST(tp);

36ASSERT(!ISBIT0(SIZE(np)));

37ASSERT(np != Bottom);

38t_delete(np);

39SIZE(np) += SIZE(tp) + WORDSIZE;

40tp = np;

41}

42}

Анализ

В строке 26 realfree смотрит, можно ли объединить текущий свободный блок со следующим. В строке 27 проверяется, что установлен флаг незанятости блока и что данный блок не является последним в списке. Если оба условия выполнены, то блок удаляется из связанного списка. Затем размеры обоих блоков складываются, и блок снова добавляется в дерево.

При написании эксплойта для этой реализации нужно иметь в виду, что мы не можем манипулировать заголовком нашего собственного блока, а лишь заголовком блока справа от него (см. строки 26-30). Если мы пишем данные за границей выделенного блока и создаем «подложный» заголовок, то можем заставить программу выполнить функцию t_delete, а значит сумеем произвольно манипулировать указателями. В примере 11.6 приведен текст функции, с помощью которой можно получить контроль над уязвимым приложением в случае затирания кучи. Она эквивалентна макросу UNLINK в реализации dlmalloc.

 

 

 

 

hang

e

 

 

 

 

 

 

 

 

C

 

E

 

 

 

 

 

X

 

 

 

 

 

 

 

-

 

 

 

 

 

d

 

 

 

F

 

 

 

 

 

 

t

 

 

 

D

 

 

 

 

 

 

 

i

 

 

 

 

 

 

 

 

 

 

r

 

P

 

 

 

 

 

NOW!

o

 

 

 

 

 

 

 

 

 

 

 

 

 

 

BUY

 

 

 

 

 

 

 

to

 

 

 

 

 

 

w Click

 

 

 

 

 

m

 

 

 

 

 

 

 

 

w

 

 

 

 

 

 

 

 

 

 

 

w

 

 

 

 

 

 

 

o

 

 

 

.

 

 

 

 

 

.c

 

11.6.

 

 

p

 

-xchПримерa

 

 

 

 

 

 

g

 

 

 

 

 

 

df

 

 

n

e

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

hang

e

 

 

 

 

 

 

 

 

C

 

E

 

 

 

 

 

X

 

 

 

 

 

 

 

-

 

 

 

 

 

d

 

 

 

F

 

 

 

 

 

 

t

 

 

 

D

 

 

 

 

 

 

 

i

 

 

 

 

 

 

 

 

 

 

r

 

P

 

 

 

 

 

NOW!

o

 

 

 

 

 

 

 

 

 

 

 

 

 

 

BUY

 

 

Пример: уязвимость, связанная с переполнением буфера 563

 

to

 

 

 

 

 

 

 

 

 

 

 

 

 

w Click

 

 

 

 

 

 

m

 

w

 

 

 

 

 

 

 

 

 

 

 

w

 

 

 

 

 

 

 

o

 

Функция t_delete

 

.

 

-x cha

 

.c

 

 

 

p

 

g

 

 

 

 

 

 

df

 

 

n

e

 

 

 

 

 

 

 

 

 

 

1 static

void

2 t_delete(TREE *op)

3 {

 

 

4

TREE

*tp, *sp, *gp;

5

 

 

6/* если это не узел дерева */

7if (ISNOTREE(op)) {

8tp = LINKBAK(op);

9if ((sp = LINKFOR(op)) != NULL)

10LINKBAK(sp) = tp;

11LINKFOR(tp) = sp;

12return;

13}

14}

Анализ

Функция t_delete манипулирует указателями для удаления блока из дерева. Сначала выполняются некоторые проверки, которые должен пройти сконструированный «подложный» блок. В строке 7 проверяется, что в поле t_l óçëà op находится -1. Поэтому, организуя переполнение, мы должны будем позаботиться о том, чтобы в этом поле для «подложного» блока была –1. Далее расшифруем макросы LINKFOR и LINKBAK:

#define LINKFOR(b) (((b)->t_n).w_p) #define LINKBAK(b) (((b)->t_p).w_n)

Ïîëå t_p «подложного» блока должно в результате переполнения содержать адрес, по которому будет находиться адрес возврата, – 4 * sizeof(WORD). А в поле t_n надо записать сам адрес возврата. Следовательно, после переполнения блок должен выглядеть, как показано на рис. 11.11.

Рис. 11.11. Подложный блок

 

 

 

 

hang

e

 

 

 

 

 

 

 

 

 

 

hang

e

 

 

 

 

 

 

 

 

C

 

E

 

 

 

 

 

 

 

C

 

E

 

 

 

 

 

X

 

 

 

 

 

 

 

 

 

X

 

 

 

 

 

 

 

-

 

 

 

 

 

d

 

 

 

-

 

 

 

 

 

d

 

 

F

 

 

 

 

 

 

 

i

 

 

 

F

 

 

 

 

 

 

 

i

 

 

 

 

 

 

 

 

 

t

 

 

 

 

 

 

 

 

 

 

t

 

P

D

 

 

 

 

 

 

 

 

o

 

P

D

 

 

 

 

 

 

 

 

o

 

 

 

 

NOW!

r

 

 

 

 

 

NOW!

r

 

 

 

 

 

BUY

 

 

 

 

 

 

 

 

BUY

 

 

 

 

 

 

to

 

 

564 Глава 11. Написание эксплойтов II

 

 

 

 

to

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

w

 

 

 

 

 

 

 

 

 

m

 

w

 

 

 

 

 

 

 

 

 

m

w Click

 

 

 

 

 

 

o

 

w Click

 

 

 

 

 

 

o

 

w

 

 

 

 

 

 

 

 

 

 

 

w

 

 

 

 

 

 

 

 

 

 

.

 

 

 

 

 

 

.c

 

 

 

.

 

 

 

 

 

 

.c

 

 

 

p

df

 

 

 

 

e

 

 

 

 

p

df

 

 

 

 

e

 

 

 

 

 

 

g

 

 

 

 

 

 

 

 

 

g

 

 

 

 

 

 

 

 

n

 

 

 

 

 

 

 

 

 

 

n

 

 

 

 

 

 

 

 

-xcha

 

 

 

 

 

Если «подложный блок» правильно заполнен и содержит правильные ад--x cha

 

 

 

 

 

реса возврата и того места в памяти, где этот адрес находится, то в результате манипулирования указателями в функции t_delete можно будет выполнить произвольный код. Уязвимой эту реализации делает хранение управляющей информации в том же блоке, что и данные. В некоторых операционных системах malloc реализована таким образом, что эта информация хранится отдельно. Тогда невозможно так создать подложный блок, чтобы вызвать желаемые манипуляции с указателями.

Эксплойты для ошибок при работе с целыми числами

Ошибки при работе с целыми числами представляют собой опасный источ- ник уязвимостей в программах с открытым исходным текстом. Такие ошибки были найдены в OpenSSH, Snort, Apache и библиотеке Sun RPC XDR, а также во множестве в ядре. Их сложнее обнаружить, чем ошибки переполнения стека, и разработчики хуже понимают их последствия.

К тому же ни один из современных анализаторов исходных текстов не пытается обнаружить ошибки при вычислениях с целыми числами. По большей части анализаторы лишь выполняют поиск по регулярному выражению функций из библиотеки LIBC, относительно которых известно, что они потенциально могут быть причиной уязвимости. Хотя обычно поиск ошибок при работе с целыми числами имеет смысл начинать с функций выделения памяти, но, вообще говоря, они не связаны ни с какой конкретной стандартной функцией.

Переполнение целого числа

Переполнение целого числа (integer wrapping) возникает, когда в результате увеличения целое число достигает максимально допустимого значения и при переходе через него становится равным нулю, а затем принимает небольшие значения. Точно также, переход через нуль происходит в результате уменьшения небольшого целого числа, в результате оно начинает принимать большие значения. В следующих примерах фигурирует исключительно функция malloc, но проблема не связана именно с ней или вообще какой-то отдельной функцией из библиотеки LIBC. Мы будем рассматривать только переход через максимальное значение, а, стало быть, лишь операции сложения и умножения. Но не забывайте и о переходе через ноль в результате вычитания и деления. В примере 11.7 показано, как может возникать переполнение целого при сложении.

 

 

 

 

hang

e

 

 

 

 

 

 

 

 

C

 

E

 

 

 

 

 

X

 

 

 

 

 

 

 

-

 

 

 

 

 

d

 

 

 

F

 

 

 

 

 

 

t

 

 

 

D

 

 

 

 

 

 

 

i

 

 

 

 

 

 

 

 

 

 

r

 

P

 

 

 

 

 

NOW!

o

 

 

 

 

 

 

 

 

 

 

 

 

 

 

BUY

 

 

 

 

 

 

 

to

 

 

 

 

 

 

w Click

 

 

 

 

 

m

 

 

 

 

 

 

 

 

w

 

 

 

 

 

 

 

 

 

 

 

w

 

 

 

 

 

 

 

o

 

 

 

.

 

 

 

 

 

.c

 

11.7.

 

 

p

 

-xchПримерa

 

 

 

 

 

 

g

 

 

 

 

 

 

df

 

 

n

e

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

hang

e

 

 

 

 

 

 

 

 

C

 

E

 

 

 

 

 

X

 

 

 

 

 

 

 

-

 

 

 

 

 

d

 

 

 

F

 

 

 

 

 

 

t

 

 

 

D

 

 

 

 

 

 

 

i

 

 

 

 

 

 

 

 

 

 

r

 

P

 

 

 

 

 

NOW!

o

 

 

 

 

 

 

 

 

 

 

 

 

 

 

BUY

 

 

Эксплойты для ошибок при работе с целыми числами 565

 

to

 

 

 

 

 

 

 

 

 

 

 

 

 

w Click

 

 

 

 

 

 

m

 

w

 

 

 

 

 

 

 

 

 

 

 

w

 

 

 

 

 

 

 

o

 

Переполнение целого числа при сложении

 

.

 

-x cha

 

.c

 

 

 

p

 

g

 

 

 

 

 

 

df

 

 

n

e

 

 

 

 

 

 

 

 

 

 

1

#include <stdio.h>

2

#include <stdlib.h>

3

 

4 int main(void)

5

{

6unsigned int i, length1, length2;

7 char *buf;

8

9// максимальное 32-разрядное беззнаковое целое равно 4294967295

10length1 = 0xffffffff;

11length2 = 0x1;

12

13// выделить память для хранения length1 + 1 байтов

14buf = (char *) malloc(length1 + length2);

15

16// напечатать длину и содержимое буфера в 16-ричном виде

17printf("length1: %x\tlength2: %x\ttotal: %x\tbuf: %s\n",

length1, length2, length1 + length2, buf);

18

19// писать в буфер "A", пока не будет заполнено length1 байтов

20for (i=0; i<length1; i++) buf[i] = 0x41;

21

22// в последнюю позицию буфера записать 0

23buf[i] = 0x0;

24

25// напечатать длину и содержимое буфера в 16-ричном виде

26printf("length1: %x\tlength2: %x\ttotal: %x\tbuf: %s\n",

length1, length2, length1 + length2, buf);

27

28return 0;

29}

Анализ

В строках 10 и 11 инициализируются две переменные length1 è length2.

В строке 14 они складываются для получения полной длины буфера, но до выделения для него памяти. Переменная length1 равна 0xffffffff, то есть максимальному 32-разрядному беззнаковому целому. Когда к length1 прибавляется 1, хранящаяся в length2, вычисленный в строке 14 размер буфера оказывается равен 0, так как 0xffffffff + 1 = 0x100000000, но это число не представимо 32 битами, поэтому старший бит отбрасывается и остается 0x00000000, то есть 0. Это и называется переполнением целого.

Таким образом, размер буфера buf равен 0. В строке 20 производится попытка заполнить буфер символами 0x41 (буква 'A'). При этом length2 не учитывается, так как в последний байт мы собираемся записать ноль.

В строке 23 в последний байт буфера записывается 0.

 

 

 

 

hang

e

 

 

 

 

 

 

 

 

 

 

hang

e

 

 

 

 

 

 

 

 

C

 

E

 

 

 

 

 

 

 

C

 

E

 

 

 

 

 

X

 

 

 

 

 

 

 

 

 

X

 

 

 

 

 

 

 

-

 

 

 

 

 

d

 

 

 

-

 

 

 

 

 

d

 

 

F

 

 

 

 

 

 

 

i

 

 

 

F

 

 

 

 

 

 

 

i

 

 

 

 

 

 

 

 

 

t

 

 

 

 

 

 

 

 

 

 

t

 

P

D

 

 

 

 

 

 

 

 

o

 

P

D

 

 

 

 

 

 

 

 

o

 

 

 

 

NOW!

r

 

 

 

 

 

NOW!

r

 

 

 

 

 

BUY

 

 

 

 

 

 

 

 

BUY

 

 

 

 

 

 

to

 

 

566 Глава 11. Написание эксплойтов II

 

 

 

 

to

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

w

 

 

 

 

 

 

 

 

 

m

 

w

 

 

 

 

 

 

 

 

 

m

w Click

 

 

 

 

 

 

o

 

w Click

 

 

 

 

 

 

o

 

w

 

 

 

 

 

 

 

 

 

 

 

w

 

 

 

 

 

 

 

 

 

 

.

 

 

 

 

 

 

.c

 

 

 

.

 

 

 

 

 

 

.c

 

 

 

p

df

 

 

 

 

e

 

 

 

 

p

df

 

 

 

 

e

 

 

 

 

 

 

g

 

 

 

 

 

 

 

 

 

g

 

 

 

 

 

 

 

 

n

 

 

 

 

 

 

 

 

 

 

n

 

 

 

 

 

 

 

 

-xcha

 

 

 

 

 

Если эту программу откомпилировать и выполнить, то произойдет ава--x cha

 

 

 

 

 

рийный выход. Причина в том, что мы пытаемся записать 4294967295 букв ‘A’

âбуфер нулевой длины. Если переменной length1 присвоить значение 0xfffffffe, а length2 – значение 2, поведение будет аналогичным. Если же length1 = 0x5, à length2 = 0x1, то мы получим «нормальное поведение».

Пример 11.7 может показаться надуманным и непрактичным, так как он не предполагает никакого взаимодействия с пользователем, и программа немедленно «грохается» в «уязвимом» случае. Но в нем продемонстрированы проблемы, которые могут возникнуть и в реальных программах из-за переполнения целых чисел. Например, обращение к malloc в строке 1 на практике чаще выглядит так: buf = (char *) malloc(length1 + 1). Единица в этом случае нужна для резервирования места под нулевой байт, поскольку все строки должны завершаться нулем, иначе возможно переполнение стека или затирание кучи. Конечно же, в реальном приложении переменной length1 не будет присвоено литеральное значение 0xffffffff. Обычно оно вычисляется на основе «данных, полученных от пользователя». Логическая ошибка возникла потому, что программист предположил, что пользователь введет «нормальное» значение, а не такое большое, как 4294967295. Но не забывайте, что внешние данные могут поступать из самых разных источников: переменные окружения, аргументы

âкомандной строке, конфигурационный параметр, число отправленных приложению пакетов, поле в заголовке сетевого протокола и ттому подобных. Поэтому если значение length должно задаваться пользователем и никак иначе, то в программе следует проверять, что оно укладывается в определенный программистом разумный диапазон. Ошибка переполнения при умножении, показанная в примере 11.8, очень похожа на рассмотренную выше.

Пример 11.8. Переполнение целого числа при умножении

1 #include <stdio.h>

2 #include <stdlib.h>

3

4 int main(void)

5 {

6unsigned int i, length1, length2;

7 char *buf;

8

9// 0xffffffff/5 â 16-ричном или 1073741824 в десятичном виде

10length1 = 0x33333333;

11length2 = 0x5;

12

13// выделить память для хранения length1*length2 + 1 байтов

14buf = (char *) malloc(length1*length2 + 1);

15 16 // напечатать длину и содержимое буфера в 16-ричном виде

 

 

 

 

 

hang

e

 

 

 

 

 

 

 

 

C

 

 

E

 

 

 

 

 

X

 

 

 

 

 

 

 

 

 

-

 

 

 

 

 

 

 

d

 

 

F

 

 

 

 

 

 

 

 

 

t

 

 

D

 

 

 

 

 

 

 

 

 

 

i

 

 

 

 

 

 

 

 

 

 

 

 

r

P

 

 

 

 

 

 

 

NOW!

o

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

BUY

 

 

 

 

 

 

 

to

 

 

 

 

 

 

w Click

 

 

 

 

 

 

m

 

 

 

 

 

 

 

 

w

 

 

 

 

 

 

 

 

 

 

 

 

 

w

 

 

 

 

 

 

 

 

 

 

o

 

 

.

 

 

 

 

 

 

 

 

.c

 

 

 

p

 

 

 

 

 

 

 

e

 

 

 

 

d

 

 

xch17

 

 

 

 

 

 

 

f-

 

an

 

 

 

 

 

 

 

 

 

hang

e

 

 

 

 

 

 

 

 

C

 

E

 

 

 

 

 

X

 

 

 

 

 

 

 

-

 

 

 

 

 

d

 

 

 

F

 

 

 

 

 

 

t

 

 

 

D

 

 

 

 

 

 

 

i

 

 

 

 

 

 

 

 

 

 

r

 

P

 

 

 

 

 

NOW!

o

 

 

 

 

 

 

 

 

 

 

 

 

 

 

BUY

 

 

Эксплойты для ошибок при работе с целыми числами 567

 

to

 

 

 

 

 

 

 

 

 

 

 

 

 

w Click

 

 

 

 

 

 

m

 

w

 

 

 

 

 

 

 

 

 

 

 

w

 

 

 

 

 

 

 

o

 

 

 

.

 

 

 

 

 

.c

 

printf("length1: %x\tlength2: %x\ttotal: %x\tbuf: %s\n",

 

 

p

 

 

 

 

g

 

 

 

 

 

df-x chan

e

 

 

 

 

 

 

 

length1, length2, length1*length2 + 1, buf);

18

19// заполнить буфер символами "A"

20for (i=0; i<(length1*length2); i++) buf[i] = 0x41;

22// в последнюю позицию буфера записать 0

23buf[i] = 0x0;

24

25// напечатать длину и содержимое буфера в 16-ричном виде

26printf("length1: %x\tlength2: %x\ttotal: %x\tbuf: %s\n",

length1, length2, length1*length2 + 1, buf);

27

28return 0;

29}

Анализ

Две переменные (length1 è length2) перемножаются для вычисления размера буфера, и к результату прибавляется 1 (для нулевого байта). Максимально возможное 32-разрядное беззнаковое число равно 0xffffffff. В данном случае следует рассматривать значение length2 (5) как «зашитое» в код приложения. Чтобы размер буфера обратился в 0, значение length1 должно быть равно 0x33333333, так как 0x33333333 * 5 = 0xffffffff. После прибавления 1 мы в результате переполнения получаем 0, то есть выделяется память для буфера нулевой длины. Когда в строке 20 мы пытается писать в этот буфер, программа аварийно завершается. Эта ошибка переполнения при умножении очень похожа на ту, что была обнаружена в пакете OpenSSH.

Обход проверки размера

В программах часто встречаются проверки, призванные гарантировать, что тот или иной код будет выполнен лишь при условии, что некое целое число больше или меньше другого числа. Иногда такие проверки вставляют, чтобы защититься от ошибок, связанных с переполнением целого, рассмотренных выше. Чаще всего подобная проверка выполняется, если переменная интерпретируется как максимальное число ответов или размер буфера, чтобы злонамеренный пользователь не попытался превысить ожидаемый порог. Такая тактика действительно может защитить от переполнения. Но к несча- стью для благоразумного программиста даже простой знак «больше» или «меньше» может иметь печальные последствия для безопасности, поэтому требуется дополнительный код.

В примере 11.9 показано, как с помощью проверки переменной определяется дальнейший путь выполнения программы и, что более важно, как переполнение целого позволяет эту проверку обойти.

 

 

 

 

hang

e

 

 

 

 

 

 

 

 

C

 

E

 

 

 

 

 

X

 

 

 

 

 

 

 

-

 

 

 

 

 

d

 

 

F

 

 

 

 

 

 

 

t

 

 

D

 

 

 

 

 

 

 

 

i

 

 

 

 

 

 

 

 

 

 

r

P

 

 

 

 

 

NOW!

o

 

 

 

 

 

 

 

 

 

 

 

 

BUY

 

 

 

 

 

 

to

 

 

 

568 Глава 11. Написание эксплойтов II

w Click

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

m

w

 

 

 

 

 

 

 

 

 

 

 

w

 

 

 

 

 

 

 

 

o

 

 

.

 

-xcha

 

 

.c

 

 

 

p

 

 

 

Пример 11.9. Обход проверки беззнакового целого числа с помощью

 

 

 

 

 

 

g

 

 

 

 

 

df

 

 

n

e

 

 

 

 

 

 

 

 

 

 

 

 

 

 

hang

e

 

 

 

 

 

 

 

C

 

E

 

 

 

 

X

 

 

 

 

 

 

-

 

 

 

 

 

d

 

 

F

 

 

 

 

 

 

t

 

 

D

 

 

 

 

 

 

 

i

 

 

 

 

 

 

 

 

 

r

P

 

 

 

 

 

NOW!

o

 

 

 

 

 

 

 

 

 

 

 

 

BUY

 

 

 

 

 

 

to

 

 

 

 

 

w Click

 

 

 

 

 

m

 

 

 

 

 

 

w

 

 

 

 

 

 

 

 

 

 

w

 

 

 

 

 

 

 

o

 

 

.

 

 

 

 

 

.c

 

 

 

p

 

 

 

 

g

 

 

 

 

 

df

 

 

n

e

 

 

 

 

 

-x cha

 

 

 

 

переполнения

1 #include <stdio.h>

2

3 int main(void)

4 {

5 unsigned int num;

6

7num = 0xffffffff;

8

num++;

9

 

10if (num > 512)

11{

12printf("Слишком большое число, выходим\n");

13return -1;

14} else {

15printf("Тест на длину пройден.\n");

16}

17

18return 0;

19}

Анализ

Можете считать, что значение переменной num в строке 7 поступило из внешнего источника. В строке 8 производится некая манипуляция с этой переменной, являющаяся частью алгоритма, а в строке 10 выполняется собственно проверка, то есть указанное число (плюс 1) сравнивается с 512. В данном случае число равно 4294967925. Ясно, что оно больше 512, но стоило добавить к нему 1, как оно обратилось в нуль, и проверка дала отрицательный результат.

Чтобы обойти проверку размера, вовсе необязательно устраивать переполнение целого, да и число не обязано быть беззнаковым. В реальных программах часто обходятся проверки, в которых участвуют целые числа со знаком. Один такой случай продемонстрирован в примере 11.10.

Пример 11.10. Обход проверки, в которой участвует число со знаком, без переполнения

1#include <stdio.h>

2#include <stdlib.h>

3 #include <string.h>

4

5 #define BUFSIZE 1024

6

7 int main(int argc, char *argv[])

8{

 

 

 

 

hang

e

 

 

 

 

 

 

 

C

 

E

 

 

 

 

 

X

 

 

 

 

 

 

 

-

 

 

 

 

d

 

 

F

 

 

 

 

 

 

t

 

 

D

 

 

 

 

 

 

 

i

 

 

 

 

 

 

 

 

 

r

P

 

 

 

 

 

NOW!

o

 

 

 

 

 

 

 

 

 

 

 

 

BUY

 

 

 

 

 

 

to

 

 

 

 

 

w Click

 

 

 

 

 

m

 

 

 

 

 

 

w

 

 

 

 

 

 

 

 

 

 

w

 

 

 

 

 

 

 

o

 

 

.

 

 

 

 

 

.c

 

 

 

p

 

 

 

 

g

 

 

 

 

 

 

 

 

 

e

 

 

 

 

df-xchan9

 

 

 

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29 }

 

 

 

 

 

hang

e

 

 

 

 

 

 

 

 

C

 

E

 

 

 

 

 

X

 

 

 

 

 

 

 

-

 

 

 

 

 

d

 

 

 

F

 

 

 

 

 

 

t

 

 

 

D

 

 

 

 

 

 

 

i

 

 

 

 

 

 

 

 

 

 

r

 

P

 

 

 

 

 

NOW!

o

 

 

 

 

 

 

 

 

 

 

 

 

 

 

BUY

 

 

Эксплойты для ошибок при работе с целыми числами 569

 

to

 

 

 

 

 

 

 

 

 

 

 

 

 

w Click

 

 

 

 

 

 

m

 

w

 

 

 

 

 

 

 

 

 

 

 

w

 

 

 

 

 

 

 

o

 

 

 

.

 

 

 

 

 

.c

 

 

 

 

p

 

 

 

 

g

 

 

char inputbuf[BUFSIZE] = {0}, outputbuf[BUFSIZE] = {0};

 

 

 

df-x chan

e

 

 

 

 

 

 

 

int num, limit = BUFSIZE;

if (argc != 3) return -1;

strncpy(inputbuf, argv[2], BUFSIZE-1); num = atoi(argv[1]);

printf("num: %x\tinputbuf: %s\n", num, inputbuf);

if (num > limit)

{

printf("Слишком большое, выходим.\n"); return -1;

} else {

memcpy(outputbuf, inputbuf, num); printf("outputbuf: %s\n", outputbuf);

}

return 0;

Анализ

По умолчанию, все целые знаковые, если явно не указан модификатор unsigned. Но не забывайте о «тихом» приведении типов. Чтобы обойти проверку в строке 19, достаточно в качестве первого аргумента программы в командной строке задать отрицательное число. Попробуйте, например, выполнить такие команды:

$ gcc -p example example.c

$./example -200 ‘perl -e 'print "A"x2000'‘

Âэтом случае в outputbuf не будут записаны буквы ‘A’, поскольку отрицательное значение -200 пройдет проверку в строке 19, после чего произойдет затирание кучи, так как memcpy попытается писать в память за пределами буфера.

Другие ошибки, связанные с целыми числами

Ошибки возникают также из-за сравнения 16-разрядных и 32-разрядных чи- сел, осознанной или нет. Впрочем, подобные ошибки в промышленных программах встречаются редко, так как, скорее всего, будут обнаружены отделом контроля качества или конечным пользователем. При работе с символами Unicode, а также с функциями для манипуляций символами типа wchar_t

 

 

 

 

hang

e

 

 

 

 

 

 

 

 

 

hang

e

 

 

 

 

 

 

 

 

C

 

E

 

 

 

 

 

 

C

 

E

 

 

 

 

 

X

 

 

 

 

 

 

 

 

X

 

 

 

 

 

 

 

-

 

 

 

 

 

d

 

 

-

 

 

 

 

 

d

 

 

F

 

 

 

 

 

 

 

i

 

 

F

 

 

 

 

 

 

 

i

 

 

 

 

 

 

 

 

 

t

 

 

 

 

 

 

 

 

 

t

 

P

D

 

 

 

 

 

 

 

 

o

P

D

 

 

 

 

 

 

 

 

o

 

 

 

 

NOW!

r

 

 

 

 

NOW!

r

 

 

 

 

 

BUY

 

 

 

 

 

 

 

BUY

 

 

 

 

 

 

to

 

 

 

570 Глава 11. Написание эксплойтов II

 

 

 

 

to

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

w

 

 

 

 

 

 

 

 

 

m

w

 

 

 

 

 

 

 

 

 

m

w Click

 

 

 

 

 

 

o

w Click

 

 

 

 

 

 

o

 

w

 

 

 

 

 

 

 

 

 

 

w

 

 

 

 

 

 

 

 

 

 

.

 

 

 

 

 

 

.c

 

 

.

 

 

 

 

 

 

.c

 

 

 

p

df

 

 

 

 

e

 

 

 

p

df

 

 

 

 

e

 

 

 

 

 

 

g

 

 

 

 

 

 

 

 

g

 

 

 

 

 

 

 

 

n

 

 

 

 

 

 

 

 

 

n

 

 

 

 

 

 

 

 

-xcha

 

 

в программах для Windows, на вычисление длин буферов и размеры перемен--x cha

 

 

 

 

 

ных целочисленных типов также надо обращать внимание.

Хотя рассмотренные выше ошибки из-за переполнения целых касались только беззнаковых 32-разрядных чисел, но все то же самое относится и к целым со знаком, и к коротким целым (типа short), и к 64-разрядной арифметике, и к прочим числовым величинам.

Как правило, чтобы ошибка при работе с целыми привела к переполнению стека или затиранию кучи и, следовательно, к возможности написания эксплойта, злонамеренный пользователь должен иметь прямой или косвенный контроль над какой-то переменной, определяющей размер. Маловероятно, что он получит прямой контроль, скажем, возможность указать размер в командной строке, хотя всякое бывает. Скорее, программа вычислит неверную длину исходя из других данных, введенных или отправленных пользователем.

Пример: уязвимость OpenSSH из за переполнения целого в процедуре оклика/отзыва CVE 2002 0639

Âпопулярном приложении OpenSSH была обнаружена уязвимость в последовательности аутентификации. Ей можно воспользоваться только, если серверное приложение SSH поддерживает механизмы аутентификации skey и bsdauth.

Âбольшинстве дистрибутивов операционных систем эти две опции при компиляции сервера не задаются. Но в OpenBSD обе по умолчанию включены.

Детали уязвимости

Это классический пример уязвимости из-за переполнения целого числа. Ошибка допущена в следующем фрагменте:

1nresp = packet_get_int();

2if (nresp > 0) {

3response = xmalloc(nresp * sizeof(char*));

4for (i = 0; i < nresp; i++) {

5response[i] = packet_get_string(NULL);

6}

7}

У противника есть возможность воздействовать на значение nresp (строка 1), изменив код SSH-клиента. В результате изменится объем памяти, выделен-