Такое поведение объясняется тем, что функция, встретив спецификатор “%s”, ожидает увидеть указатель на строку, а не саму строку. Поэтому, происходит попытка обращения по адресу 0x5038384B (“K98PN” в символьном представлении), который находится вне пределов досягаемости программы, что и вызывает исключение.

Спецификатор “%s” пригоден для отображения содержимого указателей, которые так же встречаются в программах. Это можно продемонстрировать с помощью следующего примера [318] (на диске, прилагаемом к книге, он содержится в файле “/SRC/buff.printf.%s.c”):

· #include «stdio.h»· #include «string.h»· #include «malloc.h»·· void main()· {· FILE *f;· char *pass;· char *_pass;· pass= (char *)malloc(100);· _pass=(char *)malloc(100);· if (!(f=fopen("buff.psw","r"))) return;· fgets(_pass,100,f);· _pass[strlen(_pass)-1]=0;· printf("Passw:");fgets(pass,100,stdin);· pass[strlen(pass)-1]=0;· //…· printf(pass);·}

На этот раз буфера размещены не в стеке, а в куче, области памяти выделенной функцией malloc, и в стеке считанного пароля уже не содержится. Однако вместо самого буфера в стеке находится указатель на него! Используя спецификатор “%s”, можно вывести на экран строку, расположенную по этому адресу. Например, это можно сделать так:

· buff.printf.%s.exe· Passw:%s· K98PN*

Кроме того, с помощью спецификатора “%s” можно получить даже код (и данные) самой программы! Другими словами, существует возможность прочитать содержимое любой ячейки памяти, доступной программе. Это происходит в том случе, когда строка, введенная пользователем, помещается в стек (а это происходит очень часто). Пример, приведенный ниже, как раз и иллюстрирует такую возможность (на диске, прилагаемом к книге, он находится в файле “/SRC/buff.pritnf.dump.c”):

· #include «stdio.h»· #include «string.h»·· void main()· {· char buff[16];· printf("printf dump demo\n");· printf("Login:");· fgets( amp;buff[0],12,stdin);· buff[strlen(buff)-1]=0;· printf(buff);·}·

Строка “%x%sXXXX” выдаст на экран строку, расположенную по адресу “XXXX”. Спецификатор “%x” необходим, чтобы пропустить четыре байта, в которых расположена подстрока “%x%s”. На сам же адрес “XXXX” наложены некоторые ограничения. Так, например, с клавиатуры невозможно ввести символ с кодом нуля.

Следующий пример выдает на экран содержимое памяти, начиная с адреса 0x401001 в виде строки (то есть, до тех пор, пока не встретится нуль, обозначающий завершение строки). Примечательно, что для ввода символов с кодами 0x1, 0x10 и 0x40 оказывается вполне достаточно клавиши Ctrl.

· buff.printf.dump.exe

· printf dump demo

· Login:%x%s^A^P@

· 73257825ЛьГь>h0`@O>@

Четыре первые байта ответа программы выданы спецификатором “%x", а последние представляют собой введенный указатель. А сама строка расположена с пятого по тринадцатый байт. Если ее записать в файл и дизассемблировать, например, с помощью qview, то получится следующее (последний байт очевидно равен нулю, поскольку именно он послужил концом строки):

· 00000020: 8BEC mov ebp,esp

· 00000022: 83EC10 sub esp,00000010

· 00000025: 6830604000 push 00406030

А вот как выглядит результат дизассемблирования файла demo.printf.dump.exe с помощью IDA:

· text:00401000 sub_0_401000 proc near; CODE XR

· text:00401000

· text:00401000 var_11 = byte ptr -11h

· text:00401000 var_10 = byte ptr -10h

· text:00401000

· text:00401000 55 push ebp

· text:00401001 8B EC mov ebp, esp

· text:00401003 83 EC 10 sub esp, 10h

· text:00401006 68 30 60 40 00 push offset aPrintfDumpDemo;

· text:0040100B E8 DB 01 00 00 call sub_0_4011EB

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

Спецификатор “%c” читает двойное слово из стека и усекает его до байта. Поэтому, в большинстве случаев он оказывается непригоден. Так, если в примере buff.printf.demo попытаться заменить спецификатор “%x” на спецификатор “%c” результат работы будет выгядеть так:

· buff.printf.exe

· printf bug demo

· Login:kpnc

· Passw:%c%c

· Invalid password: KN

Перейти на страницу:

Похожие книги