На главную | Содержание | Назад | Вперёд
Наши друзья

 

 

Установка заплат

ЕСЛИ ВЫ используете наш тип точек останова, то это говорит о том, что вы уже зна­комы с созданием заплат. Прочитав исходный байт команды и заменив его байтом ОхСС вы установили заплату в исходную программу! Безусловно, при установке за­плат можно заменять намного больше, чем одну команду. Заплаты могут использо­ваться для добавления операторов ветвления, новых блоков кода и даже для замеще­ния статических данных. С помощью заплат пираты часто взламывают механизмы за­щиты от копирования. И действительно, можно добиться впечатляющих результатов, заменив только одну команду перехода. Например, если в программе есть блок кода, который отвечает за проверку лицензионного файла, то пирату достаточно внедрить команду перехода, которая позволит обойти эту проверку лицензии9. Читатели, кото­рые заинтересованы во взломе программного обеспечения, могут получить тысячи до­кументов в Internet по этой теме. Для этого можно просто выполнить поиск в глобаль­ной сети по ключевой фразе "взлом программ" ("software cracking").
Конечно, неоспоримо важно уметь создавать заплаты. Это позволяет во многих случаях исправить ошибку в программе. Правда, с таким же успехом можно и внести ошибку в программу. Например, вы знаете, что конкретный файл используется про­граммным обеспечением атакуемого сервера. С помощью заплаты можно внедрить в этот файл потайной ход для доступа в систему. Хороший пример заплаты для про­граммного обеспечения (заплата для ядра Windows NT) приведен в главе 8, "Наборы средств для взлома".
Внесение ошибок
Существует множество форм внесения ошибок. По существу, идея заключается в предоставлении необычных или нестандартных входных данных в программу с по­следующим анализом происходящих в результате событий. Среди используемых методов можно назвать изменение программного кода и искажение данных в куче или стеке программы.
При внесении ошибок в программном обеспечении всегда будут происходить сбои. Вопрос в том, как именно они будут происходить? Появится ли при этом у ха­кера возможность получить доступ к системе? Раскроет ли программное обеспече­ние критически важную информацию? Приведет ли отказ в работе программы к се­рии отказов, которые повлияют на работу других частей системы? Если отказы не наносят ущерба системе, говорят об отказоустойчивой системе.
Внесение ошибок является одним из наиболее мощных методов тестирования программ, который по-прежнему практически не используется поставщиками коммерческих программ. Вот почему в современных коммерческих программах столько ошибок. Многие так называемые специалисты по компьютерной инженерии при­держиваются той точки зрения, что четкий процесс разработки программного обес­печения приводит к созданию абсолютно безопасных программ, в которых нет оши­бок, но это совсем не так. Реальная жизнь доказала, что в программном коде, создан­ном без продуманной стратегии тестирования, всегда будут опасные ошибки. Просто удивительно (и очень приятно для хакеров), что в большинстве фирм по созданию программ, на тестирование выделяются наименьшие суммы. Это значит, что в бли­жайшие годы мир будет принадлежать хакерам.
Внесение ошибок с помощью входных данных является отличным методом для выявления уязвимых мест. Причина проста: злоумышленник контролирует входные данные для программы, т. е. может проверить любую комбинацию входных данных. Естественно, что хакер обязательно найдет комбинацию, которая позволит ему взломать программу, не так ли?10
Фиксирование состояния процесса
Появление точки останова приводит к остановке программы в процессе выпол­нения. Останавливаются все действия во всех потоках. В этот момент можно вос­пользоваться специальными программами для чтения (или записи) в любой части памяти программы. Для обычной программы выделяется несколько важных облас­тей памяти. Рассмотрим дамп памяти для сервера имен версии BIND 9.02, работаю­щего под управлением Windows NT.
named.exe:


Found

memory

based

at

0x00010000,

size

4096

Found

memory

based

at

0x00020000,

size

4096

Found

memory

based

at

0x0012d000,

size

4096

Found

memory

based

at

0x0012e000,

size

8192

Found

memory

based

at

0x00140000,

size

184320

Found

memory

based

at

0x00240000,

size

24576

Found

memory

based

at

0x00250000,

size

4096

Found

memory

based

at

0x00321000,

size

581632

Found

memory

based

at

ОхООЗЬбООО,

size

4096

Found

memory

based

at

0x003b7000,

size

4096

Found

memory

based

at

0x003b8000,

size

4096

Found

memory

based

at

0x003b9000,

size

12288

Found

memory

based

at

ОхООЗЬсООО,

size

8192

Found

memory

based

at

ОхООЗЬеООО,

size

8192

Found

memory

based

at

ОхООЗсОООО,

size

8192

Found

memory

based

at

0x003c2000,

size

8192

Found

memory

based

at

0x003c4000,

size

4096

Found

memory

based

at

0x003c5000,

size

4096

Found

memory

based

at

ОхООЗсбООО,

size

12288

Found

memory

based

at

0x003c9000,

size

4096

Found

memory

based

at

ОхООЗсаООО,

size

4096

Found

memory

based

at

ОхООЗсЬООО,

size

4096

Found

memory

based

at

ОхООЗссООО,

size

8192

Found

memory

based

at

ОхООЗеЮОО ,

size

12288

Found

memory

based

at

0x003e5000,

size

4096

Found

memory

based

at

0x003fl000,

size

24576

Found

memory

based

at

0x003f8000,

size

4096

Found

memory

based

at

0x0042a000,

size

8192

Found

memory

based

at

0x0042c000,

size

8192

Found

memory

based

at

0x0042e000,

size

8192

memory memory memory memory memory memory memory memory memory memory memory memory memory memory memory memory memory memory memory memory memory memory memory memory Kio:r.ory memory memory memory memory memory memo г у memory memory memory memory memory memory memory memory memory memory memory memory memory memory memory memo г у based at based at based at based at based at based at based at based at based at based at based at based at based at based at based at based at based at based at based at based at based at based at based at based at based at based at based at based at based at based at based at based at based at based at based at based at based at based at based at based at based at based at based at based at based at based at based at 0x00430000 0x00441000 OxOO4d80O0 0x004fl000 0x004f7000 0x00500000 0x00700000 0x00790000 0x0089c000 0x0D89d000 0x0D99c000 0x0099d000 ОхООаЭеООО 0x00a9f000 0x00aa0000 0x00c7eQ00 0x00c7f000 0x00cae000 OxOOcafООО OxOffedODO OxOffefOOO OxlOOlfOOO 0x10020000 0x10023000 0x10024000 0x71a83000 0x71a95000 0x71aa5000 0x71ac2000 0x77c58000 0x77c5a000 0x77cac000 0x77d2f000 0x77d9d000 0x77e36000 0x77e37000 0x77e39000 0x77ed6000 0x77ed7000 0x77fc5000 0x7ffd9000 0x7ffdaOOO 0x7ffdb000 0x7ffdcOOO 0x7ffdd000 0x7ffde000 0x7ffdf000 size s i z о size size size size size size size size size size size size size size size size size size size size size size size size size size size size size size size size size size size size size size size size size size size size size 4096
491520
45056
20480
16384
65536
4096
4096
40Э6
12288
4096
12288
4096
4096
503808
4096
135168
4096
4096
8192
4096
4096
12268
4096
4096
8192
4096
4096
4096
8192
20480
4096
4096
8192
4096
8192
8192
4 096
8192
20480
4096
4096
4096
4096
4096
4096
4096
Можно прочесть все данные в этих областях памяти и сохранить их. Эти данные можно рассматривать как "моментальный снимок" исполняющегося процесса. Можно продолжить исполнение программы и приостановить его в любой другой момент с помощью следующей точки останова. При этом в любой момент ячейки памяти можно заполнить данными, которые были сохранены при первом останове. Это позволяет "перезапустить" программу с момента выполненного "снимка", т.е. можно бесконечно "прокручивать" программу назад к нужной точке.
Это мощный метод, который применяется при автоматическом тестировании про­грамм. Он позволяет сделать моментальный снимок памяти программы и перезапус­тить ее. После восстановления данных в памяти можно внедрить вредоносные данные или симулировать различные типы атакующих данных. Также вполне возможно орга­низовать этот процесс в виде цикла и проверять один и тот же программный код с по­мощью различных входных данных. Автоматизированный метод проверки программ очень эффективен и позволяет проверить миллионы комбинаций входных данных.
Следующий листинг иллюстрирует выполнение "моментального снимка" памяти проверяемого процесса. Запрос выполняется относительно всех возможных областей памяти. Данные каждой выделенной области памяти копируются в перечень структур.
struct mb
{
MEMORY_iASIC_INFORMATION mbi;
char *p;
};
std:    :list<struct mb *> gMemList; void takesnapO
{
DWORD start = 0; SI6E_T lpRead;
while(start < OxFFFFFFFF)
{
MEMOR¥_iASIC_INFORMATION mbi;
int sz =
VirtualQueryEx( hProcess,
(void *)start, &mbi ,
sizeof(MEMORY_iASIC_INFORMATION));
if( (mbi.State == MEM_COMMIT)
&&
((mbii..PProtect != PAGE_READONLY)
&&
((imbL-Prrotect !!= PAGE_EXECUTE_READ)
&&
((mbL..Erotect != PAGE_GUARD)
&&
(mbi.Protect != PAGE_NOACCESS)
)
TRACE("Обнаружена область памяти то адресу %d,   размер %d\n",
mbi.iaseAddress,
mbi.RegionSize); struct mb *b = new mb;
memcpy(    (void *)&(b->mbi),
(void *)&mbi , sizeof (MEMORY_iASI CONFORMATION)) ) ;
char *p =  (char *»maulloc«mbi.RegionSize) ;
b->p = p;
if(!ReadProcessMemory( hProcess,
(void *)start, p, mbi.RegionSize, &lpRead))
{
TRACE("Ошибка в ReadProcessMemory %d\nRead %d",
GetLastError (), lpRead);
if(mbi.RegionSize != lpRead)
{
TRACE('Read short bytes %d != %d\n',
mbi.RegionSize, lpRead);
{

Восстановление исходного кода и структуры программы
gMemList.push_front(b);
}
if(start + mbi.RegionSize < start) break; start += mbi.RegionSize;
}
}
Восстановление исходного кода и структуры программы 129
gMemList.push_front(b);
}
if (start + mbi.RegionSize < start) break; start  += mbi.RegionSize;
}
}
В этом примере для проверки всех областей памяти, начиная с адреса 0 и закан­чивая OxFFFFFFFF, используется функция VirtualQueryEx. Если обнаруживает­ся блок выделенной памяти, то предоставляются сведения о размере выделенной об­ласти памяти и в следующем запросе указывается адрес, который следует сразу по­сле данной области. Это позволяет устранить повторные запросы к одной и той же области памяти. Если область памяти зарезервирована, значит, она используется. При этом осуществляется проверка, что для области памяти не установлены права доступа "только для чтения", поэтому создается список только тех областей памяти, данные в которых могут изменяться. Нет причины сохранять области памяти, кото­рые нельзя изменить, хотя при желании можно сохранить и эти области памяти на тот случай, если есть предположение, что права доступа к памяти могут изменяться
во время исполнения приложения. ;
Если нужно восстановить состояние программы, следует прибегнуть к восста­новлению всех сохраненных значений областей памяти.
void setsnap () { ;i[:'^!ffc,v;.:'.i
std: :iis^^truct3mbHc>: s-.3te№ato,rcff^egMemLiseabegen(T;n"-
while(ff != gMembist.endO)
{
strucrfmbWrSt=ProcessMemorу(hProcess,
{
DWORD  lpBytes; ...,lri..-, .^..^„^^
TRACE("3airacb в память с адреса %d,   размер %d\n", ess,
u->mbi.BaseAddress ,
u->mbi'RegionSize);rccSS3rter,or^ %rt\n=-. if(!WriteProcessMemory(hProcess,
u->mbi.BaseAddress/
ifdpBytes ! = u->mbi .ReglbftSdze)
u->mbi.RegionSize,
TP.A.CF.    Warning,  vr&lpBytes))i y^r';
{
TRACE^m^^  в WriteProcessMemory %d\n", GetLastError());
}
ff+if(lpBytes ! = u->mbi.RegionSize)
{
TRACE("Warning,   write  failed %d   != %d\n",
Как видим, программной^?* Reлgi OBrS::CZTагновления значений ячеек памяти намно­го проще. Здесь уже не надо отправлять запросы по адресам памяти, потому что ори-гинальнь^е= +значения просто восстанавливаются.

Дизассемблирование машинного кода
Чрезвычайно важно, чтобы отладчик умел дизассемблировать команды. При подходе к точке останова или пошагового события каждый поток исследуемого про­цесса по-прежнему указывает на определенную команду. Используя функции структуры CONTEXT, можно определить адрес памяти, где хранится команда, но это не позволяет узнать, какая именно команда была использована.
Для этого данные в памяти должны быть дезассемблированы. К счастью, нам не нужно создавать собственный дизассемблер с нуля. Дизассемблер от Microsoft по­ставляется совместно с операционными системами этой компании. Этот дизассемб­лер используется, например, утилитой Dr. Watson при отказе в работе программы. Воспользуемся этим уже существующим средством для добавления функций дизас­семблера в наш отладчик.
HANDLE hThread =
fOpenThread(
THREAD_ ALL_ACCESS ,
FALSE,
theThread->m_thread_id
) ;
if(hThread == NULL)
{
_error_oat("[!] Ошибка при открытии! обработчика t потока t.!\n");
return FALSE;
}
DEBUGPACKET   dp ;
dp.context - ttthe'Tfhre;ad->mi=ctx; dp.hProcess = ttheThread->m_hhProcess;
dp.hThread = hThread;

DWORD ulOffset  - dp.context.Eip;
//  Дизассемблирование команды, if   (  disasm   (   &dp ,
&ulOffset d
((TPUCHAR) mJLnstruction,
FALSE       ) )
{
ret = TRUE;
}
else
{
_error_oat(™error: di.sassaembl.iingj in^truction\\i^")) ; ret = FALSE;
}
CloseHandle(hThread);
В этом программном коде используется определенная пользователем структура по­тока. Благодаря полученному контексту мы теперь знаем, какая команда была выпол­нена. Вызов функции disasm описан в исходном коде Dr. Watson и легко может быть добавлен в ваш проект. Мы рекомендуем использовать исходный код Dr. Watson для добавления возможностей дизассемблера. Однако существуют и другие дизассембле­ры с открытым кодом, которые предоставляет подобные возможности.

 

На главную | Содержание | Назад | Вперёд
 
Яндекс.Метрика