первый. Разминочный.
Бороться со своими мыслями, это уподобиться одному глупцу, который в целях аккуратности и гигиены решил больше не какать. День не какал, два не какал. Потом, конечно не выдержал, но всех продолжал уверять, что не какает.
Аноним
Алгоритм простейшего механизма аутентификации состоит в посимвольном сравнении введенного пользователем паролем с эталонным значением,
хранящимся либо в самой программе (как часто и бывает), либо вне ее, например, в конфигурационном файле или реестре (что встречается реже).
Достоинство такой защиты – крайне простая программная реализация. Ее ядро состоит фактически из одной строки, котораяую
на языке Си обычно выглядит можно записать так: – "if (strcmp(&введенный пароль, &эталонный пароль)) { /* Пароль неверен */} else {/* Пароль ОК */}"
Давайте дополним этот код процедурами запроса пароля и вывода результатов сравнения, а затем испытаем полученную программу на "прочность", т.е. стойкость к взлому.
// Простейшая система аутентификации
// посимвольное сравнение пароля
#include <stdio.h>
#include <string.h>
#define PASSWORD_SIZE 100
#define PASSWORD "myGOODpassword\n"
// этот перенос нужен затем, чтобы ^^^^
// не выкусывать перенос из строки,
// введенной пользователем
int main()
{
// Счетчик неудачных попыток аутентификации
int count=0;
// Буфер для пароля, введенного пользователем
char buff[PASSWORD_SIZE];
// Главный цикл аутентификации
for(;;)
{
// Запрашиваем и считываем пользовательский
// пароль
printf("Enter password:");
fgets(&buff[0],PASSWORD_SIZE,stdin);
// Сравниваем оригинальный и введенный пароль
if (strcmp(&buff[0],PASSWORD))
// Если пароли не совпадают – "ругаемся"
printf("Wrong password\n");
// Иначе (если пароли идентичны)
// выходим из цикла аутентификации
else break;
// Увеличиваем счетчик неудачных попыток
// аутентификации и, если все попытки
// исчерпаны – завершаем программу
if
(++count>3) return
–1;
}
// Раз мы здесь, то пользователь ввел правильный пароль
printf("Password OK\n");
}
Листинг 1 Пример простейшей системы аутентификации
В популярных кинофильмах крутые хакеры легко проникают в любые жутко защищенные системы, каким-то непостижимым образом угадывая искомый пароль с нескольких попыток. Почему бы неи
попробовать пойти их путем?
Не так уж редко пароли представляют собой осмысленные слова, наподобие "Ferrari", "QWERTY", имена любимых хомячков, названия географических пунктов и т.д. Угадывание пароля сродни гаданию на кофейной гуще – никаких гарантий на успех нет, и остается рассчитывать на одно лишь везение. А удача, как известно, птица гордая – палец ей в рот не клади. Нет ли более надежного способа взлома?
Давайте подумаем – раз эталонный пароль хранится в теле программы, то, если он не зашифрован каким-нибудь хитрым образом, его можно обнаружить тривиальным просмотром двоичного кода программы. Перебирая все, встретившиеся в ней текстовые строки, начиная с тех, что более всего смахивают на пароль, мы очень быстро подберем нужный ключ и "откроем" им программу!
Причем, область просмотра можно существенно сузить, – в подавляющем большинстве случаев компиляторы размешают все инициализированные переменные в сегменте данных (в PE-файлах он размещается в секции ".data"). Исключение составляют, пожалуй, ранние Багдадские (Borland-вые в смысле) компиляторы с их маниакальной любовью всовывать текстовые строки в сегмент кода – непосредственно по месту их вызова. Это упрощает сам компилятор, но порождает множество проблем. Современные операционные системы, в отличие от старушки MS-DOS, запрещают модификацию кодового сегмента, и все, размешенные в нем переменные, доступны лишь для чтения. К тому же, на процессорах с раздельной системой кэширования (на тех же Pentium-ах, например) они "засоряют" кодовый кэш, попадая туда при упреждающем чтении, но при первом же к ним обращении вновь загружаются из медленной оперативной памяти (кэша второго уровня) в кэш данных.
В результате – тормоза и падение производительности.
Что ж, пусть это будет секция данных! Остается только найти удобный инструмент для просмотра двоичного файла. Можно, конечно, нажать <F3> в своей любимой оболочке (FAR, DOS Navigator) и, придавив кирпичом <Page Down> любоваться бегущими циферками до тех пор, пока не надоест. Можно воспользоваться любым hex-редактором (QVIEW, HIEW…) – кому какой по вкусу, но в книге по соображениям наглядности я приведу результат работы утилиты DUMPBIN из штатной поставки Microsoft Visual Studio.
Попросим ее распечатать секцию данных (ключ "/SECTION:.data") в "сыром" виде (ключ "/RAWDATA:BYTES"), указав значок ">" для перенаправления вывода в файл (ответ программы занимает много места и на экране помещается один лишь "хвост").
> dumpbin /RAWDATA:BYTES /SECTION:.data simple.exe >filename
RAW DATA #3
00406000: 00 00 00 00 00 00 00 00 00 00 00 00 3B 11 40 00 ............;.@.
00406010: A4 40 40 00 00 00 00 00 00 00 00 00 E0 11 40 00 д@@.........р.@.
00406020: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
00406030: 45 6E 74 65 72 20 70 61 73 73 77 6F 72 64 3A 00 Enter password:.
00406040: 6D 79 47 4F 4F 44 70 61 73 73 77 6F 72 64 0A 00 myGOODpassword..
^^^^^^^^^^^^^^
00406050: 57 72 6F 6E 67 20 70 61 73 73 77 6F 72 64 0A 00 Wrong password..
00406060: 50 61 73 73 77 6F 72 64 20 4F 4B 0A 00 00 00 00 Password OK.....
00406070: 40 6E 40 00 00 00 00 00 40 6E 40 00 01 01 00 00 @n@.....@n@.....
Смотрите! Среди всего прочего тут есть одна строка до боли похожая на эталонный пароль (в тексте она выделена жирным шрифтом). Испытаем ее? Впрочем, какой смысл – судя по исходному тексту программы,
это действительно искомый пароль, открывающий защиту, словно Золотой Ключик. Только Слишком уж видное место выбрал компилятор для его хранения – пароль не мешало бы запрятать получше.
Один из способов сделать это – насильно поместить эталонный пароль в собственноручно выбранную нами секцию. Такая возможность не предусмотрена стандартном и потому каждый разработчик компилятора (строго говоря, не компилятора, а линкера, но это не суть важно) волен реализовывать ее по-своему (или не реализовывать вообще). В Microsoft Visual C++ для этой цели предусмотрена специальная прагма data_seg, указывающая в какую секцию помещать следующие за ней инициализированные переменные. Неинициализированные переменные по умолчанию располагаются в секции ".bbs" и управляются прагмой bss_seg соответственно.
Добавим в Листинг 1 следующие строки и посмотрим, что из этого у нас получится.
int count=0;
// С этого момента все инициализированные переменные будут
// размещаться в секции ".kpnc"
#pragma data_seg(".kpnc") // точку перед именем ставить
// не обязательно – просто так
// принято
char passwd[]=PASSWORD;
#pragma data_seg()
// Теперь все инициализированные переменные вновь будут
// размещаться в секции по умолчанию, т.е. ".data"
char buff[PASSWORD_SIZE]="";
...
if (strcmp(&buff[0],&passwd[0]))
> dumpbin /RAWDATA:BYTES /SECTION:.data simple2.exe >filename
RAW DATA #3
00406000: 00 00 00 00 00 00 00 00 00 00 00 00 9B 11 40 00 ............Ы.@.
00406010: 04 41 40 00 00 00 00 00 00 00 00 00 40 12 40 00 .A@.........@.@.
00406020: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
00406030: 45 6E 74 65 72 20 70 61 73 73 77 6F 72 64 3A 00 Enter password:.
00406040: 57 72 6F 6E 67 20 70 61 73 73 77 6F 72 64 0A 00 Wrong password..
00406050: 50 61 73 73 77 6F 72 64 20 4F 4B 0A 00 00 00 00 Password OK.....
00406060: 20 6E 40 00 00 00 00 00 20 6E 40 00 01 01 00 00 n@..... n@.....
00406070: 00 00 00 00 00 00 00 00 00 10 00 00 00 00 00 00 ................
Ага, теперь в секции данных пароля нет и хакеры "отдыхают"! Но не спешите с выводами.
Давайте сначала выведем на экран список всех секций, имеющихся в файле:
> dumpbin simple2.exe
Summary
2000 .data
1000 .kpnc
^^^^
1000 .rdata
4000 .text
Нестандартная секция ".kpnc" сразу же приковывает к себе внимание. А ну-ка глянем, что там в ней?
dumpbin /SECTION:.kpnc /RAWDATA simple2.exe
RAW DATA #4
00408000: 6D 79 47 4F 4F 44 70 61 73 73 77 6F 72 64 0A 00 myGOODpassword..
^^^^^^^^^^^^^^
Вот он, пароль! Спрятали, называется… Можно, конечно, извратится и засунуть секретные данные в секцию неинициализированных данных (".bss"), служебную RTL-секцию (".rdata") или даже секцию кода (".text") – не все там догадаются поискать, а работоспособность программы такое размещение не нарушит. Но не стоит забывать о возможности автоматизированного поиска текстовых строк в двоичном фале.
Пример реализации такого фильтра приведен в "Приложении" (см. "исходный текст filter.c"). В какой бы секции ни содержался эталонный пароль – фильтр без труда его найдет (единственная проблема – определить какая из множества текстовых строк представляет собой искомый ключ; возможно,
потребуется перебрать с десяток-другой потенциальных "кандидатов").
Правда, если пароль записан в уникоде, его поиск несколько осложняется, т.к. не все утилиты поддерживают эту кодировку, но надеяться, что это препятствие надолго задержит хакера – несколько наивно.