Фундаментальные основы хакерства

       

первый. Разминочный.


Бороться со своими мыслями, это уподобиться одному глупцу, который в целях аккуратности и гигиены решил больше не какать. День не какал, два не какал. Потом, конечно не выдержал, но всех продолжал уверять, что не какает.

Аноним

Алгоритм простейшего механизма аутентификации состоит в посимвольном сравнении введенного пользователем паролем с эталонным значением,

хранящимся либо в самой программе (как часто и бывает), либо вне ее, например, в конфигурационном файле или реестре (что встречается реже).

Достоинство такой защиты – крайне простая программная реализация. Ее ядро состоит фактически из одной строки, котораяую

на языке Си обычно выглядит можно записать так: – "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"). В какой бы секции ни содержался эталонный пароль – фильтр без труда его найдет (единственная проблема – определить какая из множества текстовых строк представляет собой искомый ключ; возможно,
потребуется перебрать с десяток-другой потенциальных "кандидатов").
Правда, если пароль записан в уникоде, его поиск несколько осложняется, т.к. не все утилиты поддерживают эту кодировку, но надеяться, что это препятствие надолго задержит хакера – несколько наивно.

Содержание раздела