© 2006 Константин Панков & Hellsp@wn
Уже достаточно продолжительное время для защиты коммерческого программного обеспечения производители используют, так
называемые, упаковщики и протекторы PE файлов. С течением времени среди взломщиков этих продуктов стали пользоваться
популярностью анализаторы PE файлов, предоставляющие полную информацию о файле - становится известным даже название и версия
упаковщика, протектора, компилятора. Но цель этой статьи не только показать, как создаются подобные программы.
Цели данной статьи:
1) написать анализатор PE файлов, а вместе с тем и детектор упаковщиков\протекторов\компиляторов;
2) показать, как, возможно, работали антивирусные программы до появления упаковщиков и протекторов;
Читателю необходимо предварительно ознакомиться хотя бы с одной из следующих утилит: PEiD, PE Tools (PE Sniffer), PEPirate или DiE, ссылки, на которые приведены в конце статьи, почитать про PE формат, а также скачать исходники первой версии PEPirate, и разбирать написанный в них код параллельно с чтением статьи.
Подготовка.При написании программы мы будем для удобства пользоваться API функциями. На тот случай, если читатель не знаком с функциями CreateFile, CreateFileMapping, MapViewOfFile, SetFilePointer, ReadFile ему следует просмотреть их описание, приведенное ниже, в противном случае, перейти к следующей части статьи. Описание используемых функций:
Функция CreateFile открывает файл и возвращает его дескриптор, который может применяться для доступа к файлу.Функции UnMapViewOfFile и CloseHandle служат для закрытия объектов открытых при помощи CreateFile, CreateFileMapping и MapViewOfFile.
CreateFile(
FileName:PChar, // имя открываемого файла
dwAccess:DWORD, // тип доступа к файлу
dwShareMode:DWORD, // определяет способ совместного доступа к файлу
SecAttributes:TSecurityAttributes, // указатель на структуру TSecurityAttributes, если хотим по умолчанию, то nil
dwCreate:DWORD, // определяет действие, которое нужно выполнить если файл существует. Нам нужно открыть файл, значит
//OPEN_EXISTING
dwAttrsAndFlags:DWORD, // указывает аттрибуты и флаги для файла
hTemplateFile:HANDLE):DWORD; // дескриптор шаблона файла
Функция CreateFileMapping создает именованный или неименованный объект отображения файла
CreateFileMapping(
hFile:DWORD, // дескриптор файла, полученный с помощью CreateFile
SecAttr:TSecurityAttributes // 0, если по умолчанию
dwProtect:DWORD, // защита страницы файла для отображения
dwMaximumSizeHigh:DWORD,// три следующих параметра 0
dwMaximumSizeLow:DWORD,
MapName:PChar
):DWORD;
Функция MapViewOfFile отображает представление файла в адресное пространство вызывающего процесса и возвращает начальный адрес представления.
MapViewOfFile(
hMapObject:DWORD, // дескриптор полученный при помощи CreateFileMapping
dwAccess:DWORD, // тип доступа к представлению файла
dwOffsetHigh,dwOffsetLow,dwMap:DWORD // здесь ставим нули
):Pointer;
Функция SetFilePointer перемещает указатель открытого файла на указанную позицию.
SetFilePointer(
hFile:DWORD, // хендл открытого файла
lDistanceToMove:Integer, // число байт для перемещения
lpDistanceToMoveHigh:Pointer, // это поле для нас не важно
dwMoveMethod:DWORD // метод перемещения (от начала, от текущей позиции и т.д.)
):DWORD;
Функция ReadFile считывает фанные из файла.
ReadFile(
hFile:DWORD, // дескриптор считываемого файла
pBuf:pointer, // указатель на буфер куда будет помещена считанная информация
dwBytes:DWORD, // число байт, которое надо считать из файла
dwBytesRead:DWORD, // число реально считанных байт
pOverlap:POVERLAPPED // нас не интересует
):boolean;
В данной статье не будет описываться создание главного окна приложения, диалог выбора файлов, обработка исключений, потому что с этим не должно возникнуть никаких проблем. Работать с файлом мы будем при помощи функций API, хотя желающие могут использовать TFileStream. Итак, в дальнейшем изложении предполагается, что вы получили имя анализируемого файла и все API функции выполняются. Что же отобразим наш файл в память(так нам будет легче с ним работать). Делается это так:
hFile:=CreateFile(pchar(filename),GENERIC_READ or GENERIC_WRITE, FILE_SHARE_READ or FILE_SHARE_WRITE,nil,OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL,0);Следует отметить, что приведенная последовательность действий не единственная, например того же можно добиться, вызвав функцию LoadLibraryEx, т.е.
hFileMapping:=CreateFileMapping(hFile, nil, PAGE_READWRITE, 0, 0, 0);
p:=MapViewOfFile(hFileMapping,FILE_MAP_READ,0,0,0);
p:=LoadLibraryEx(pchar(filename),0,DONT_RESOLVE_DLL_REFERENCES);Подразумевается, что filename типа string. Итак, что нам дает это "p", а это ни что иное как указатель на структуру IMAGE_DOS_HEADER DOS-загаловок. Полностью описывать я её не буду, а расскажу лишь о двух полях e_magic и _lfanew(так называется в Delphi, а на самом же деле это e_lfanew), т.к. они будут нужны нам в дальнейшем. e_magic сигнатура исполняемого файла должна быть равна IMAGE_DOS_SIGNATURE, если нет, то это не PE файл. _lfanew - смещение на PE-заголовок, т.е. на структуру IMAGE_NT_HEADERS. Смотрим далее:
a:=p;// сохраняем p, чтобы позже закрыть отображениеи не забудьте, что Pтип = ^тип. Рассмотрим кратко структуру IMAGE_NT_HEADERS.
doshead:=PIMAGE_DOS_HEADER(p)^;//подразумеваем, что doshead.e_magic=IMAGE_DOS_SIGNATURE
p:=pointer(integer(p)+doshead._lfanew);//смещаемся на структуру IMAGE_NT_HEADERS
pehead:=PIMAGE_NT_HEADERS(p)^;//PEHead ни что иное как IMAGE_NT_HEADERS
IMAGE_NT_HEADERS = recordЕсли файл действительно PE, то Signature должна равняться IMAGE_NT_SIGNATURE. В IMAGE_FILE_HEADER нас интересует только поле NumberOfSection типа word, с помощью него можно узнать сколько секций в файле(это очень важно). А вот здесь все что нам нужно из IMAGE_OPTIONAL_HEADER32.
Signature:DWORD;
FileHeader:IMAGE_FILE_HEADER;
OptionalHeader:IMAGE_OPTIONAL_HEADER32;
end;
Нас интересуют поля:
| AddressOfEntryPoint | Это относительный адрес точки входа в программу. Именно с кода, находящегося по этому адресу начинается выполнение программы. |
| ImageBase | Отсюда начинается отображение исполняемого файла в память |
| Subsystem | Определяет является ли приложение GUI, Console, Native. |
| MajorLinkerVersion MinorLinkerVersion | Вместе определяют версия линковщика. |
Теперь мы рассмотрели необходимый минимум, если вам хочется узнать побольше,то ищите на сайте wasm.ru. Из всего изложенного мы получим:
EntryPoint:=IntToHex(PEHead.OptionalHeader.AddressOfEntryPoint);Вот мы и получили основные данные, которые показывают анализаторы. Остается найти только FileOffset, EPSection, FirstBytes и список секций. Сначала найдем FileOffset и секции. Если мы добавим к p размер структуры IMAGE_NT_HEADERS, то получим адрес структуры IMAGE_SECTION_HEADER. Адрес первой секции файла. Т.к. количество секций нам известно, то получить их список не составит труда:
LinkerInfo:=IntToStr(PEHead.OptionalHeader.MajorLinkerVersion)+'.'+IntToStr(PEHead.OptionalHeader.MinorLinkerVersion);
case pehead.OptionalHeader.Subsystem of
1: subsystemstr:='Native';
2: subsystemstr:='Win32 GUI';
3: subsystemstr:='Win32 Console';
else subsystemstr:='Unknown';
end;
NumOfSect:=IntToStr(PEHead.FileHeader.NumberOfSections);
ImgBase:=IntToHex(PEHead.OptionalHeader.ImageBase);
p:=pointer(integer(p)+sizeof(IMAGE_NT_HEADERS));Как видите все просто, только надо будет куда-то сохранять названия секций и все что вы захотите о них узнать. И еще, если FileOffset у вас будет равен 0, то FileOffset:=EntryPoint. С FirstBytes вообще элементарно:
for i:=1 to numbers do //numbers - количество секций, их мы получили ранее
begin
imgsection:=PIMAGE_SECTION_HEADER(p)^;//imgsection:IMAGE_SECTION_HEADER
lstrcpyn(@buf,@imgsection.name,8);//копируем в buf название секции
.....................................// далее следует код для определения FileOffset и EPSection
if (EntryPoint>=imgsection.VirtualAddress)and(EntryPoint<=imgsection.VirtualAddress+imgsection.Misc.VirtualSize) then
begin
EPSection:=buf;
FileOffset:=EntryPoint-imgsection.VirtualAddress+imgsection.PointerToRawData;//здесь EntryPoint уже типа dword
end;
p:=pointer(integer(p)+sizeof(IMAGE_SECTION_HEADER));//теперь в p адрес следующей секции
end;
1) SetFilePointer(hFile,fileoffset,nil,FILE_BEGIN); //работаем с файлом
ReadFile(hFile,firstbytesbuf,4,bytesread,nil);
firstbytest:=strtohex(firstbytesbuf);
2) for i:=0 to 3 do //работаем с памятью
firstbytesbuf[i]:=PChar(ImgBase+EntryPoint+i)^;
firstbytest:=strtohex(firstbytesbuf);
Примечание: данный способ не всегда корректен. В частности, неправильные значения получаются на файлах запакованных NsPack, WinUpack v0.399, для вычисления FileOffset в этих случаях, нужно учитывать FileAlignment. Рекомендация: прочтите статью про структуру PE
Функция StrToHex переводит символьную строку в Hex. Вот как выглядет эта функция:
function StrToHex(a:array of char):string;Вот и все с основными параметрами PE файла. Далее мы рассмотрим принципиальный метод детектирования упаковщиков\ протекторов\ компиляторов.
var i,j:byte;s:string;
19:34 25.08.2006 begin
j:=length(a)-1;
for i:=0 to j do
s:=s+inttohex(ord(a[i]),2);
StrToHex:=s;
end;
Детектирование упаковщиков, протекторов, компиляторов основано на том, что определенному компилятору(упаковщику,протектору) соответствует своя сигнатура - последовательность байт, т.о. считав её и сравнив с образцовой мы можем сказать какой компилятор использовался, все вышесказанное касается также протекторов и упаковщиков. Эталонные сигнатуры можно получить самим или использовать файл Signs.txt из PE Tools. Далее идет код детектирования:
InfoText:=SignInfo(hFile,FileOffset);//детектирование
UnMapViewOfFile(a); //все, закрываем файл
CloseHandle(hFileMapping);
CloseHandle(hFile);
function SignInfo(fs:cardinal;EntryPoint:cardinal):string;
var i:integer;
s,stemp,sign:string;
f:textfile;
a,temp:array[0..1000] of char;
Err:byte;
bytesread:dword;
begin
Err:=0;
if fileexists(extractfilepath(application.exename)+'Signs.txt')=true then //если файл существует, то
begin
assignfile(f,extractfilepath(application.exename)+'Signs.txt'); //открываем файл и работаем с ним
reset(f);
while not eof(f) do
begin
Application.ProcessMessages;// чтобы наша программа не зависла
readln(f,temp);
s:=copy(temp,pos('=',temp)+1,pos(']',temp)-1);//обрабатываем строку
SetFilePointer(fs, EntryPoint, nil, FILE_BEGIN);//перемещаемся по PE файлу
ReadFile(fs,a,length(s)-1,bytesread,nil);//и читаем из него для нашей сигнатуры
stemp:=StrToHex(a);//преобразуем в Hex
for i:=0 to length(s)-1 do //далее сверяем
begin
Application.ProcessMessages;
if s[i]<>':' then
if s[i]<>stemp[i] then
inc(Err);
if Err=1 then break;
end;
if Err=0 then
begin
sign:=copy(temp,pos('[',temp)+1,pos('=',temp)-2);
break;
end;
Err:=0;
s:='';
temp:='';
stemp:='';
end;
CloseFile(f);//закрываем Signs.txt
end;
if sign='' then
SignInfo:='Unknown!'
else
SignInfo:=sign;
end;
Примечание: данный пример на больших файлах и при большом количестве сигнатур, будет работать очень медленно. Правильнее было бы применить какой-нибуть алгоритм поиска (благо в интернете много реализаций на любом языке программирования), ну и конечно отказаться от работ со строками, тогда скорость сканирования заметно возрастёт. Также, закрывать файл совсем не обязательно, можно было бы дальше работать с ним через указатель. MOVE(P,A,SizeOf(A));
Все тоже очень просто. А причем здесь антивирусы спросите вы? Да, при всем. Допустим, что в природе не существует никаких упаковщиков и протекторов, и тогда перед нами предстанет сигнатура трояна или вируса, и поискав её в своей базе сигнатур мы точно сможем утверждать, что это зловредный код. Я скачал pinch - известный троян, и результаты не заставили себя ждать. Если он не запакован, то для него характерна следующая последовательность байт:
E8::00000050E8::01000050E8::0100004883F85A7516E8DEFFFFFF6888130000E8::0000006A00E8::000000EBE2т.е. если мы получим её, то очевидно будет, что перед нами pinch и если мы антивирус, то должны "кричать" об этом пользователю.
Вы можете усовершенствовать ваш анализатор: добавить дизассемблер, хексредактор, просмотр импорта и экспорта, и конечно же многое другое. Главное, что вам понадобиться - это ваше воображение и возможно в будущем ваша утилита станет популярной.
Примечание: а самое главное, внедряйте новые варианты детектирования. Т.е. находите уникальные особенности пакера/протектора. Детект от EntryPoint самое простое, что можно придумать. В сети много различного софта (Скрамблеры), чье предназначение скрыть информацию о том, чем реально запакован файл.
Исходный код анализатора и детектора PEPirate (черновая версия) - zip или rar
DiE - hellspawn.nm.ru
Туториалы Iczelion'a о PE, документация по PE формату, PEiD, PE Tools - wasm.ru
Форум посвященный программированию детекторов, здесь ответят на все возникшие у вас вопросы - hellspawn.ucoz.ru/forum
Оригинал статьи в txt-формате
 
Copyright© 2006 Константин Панков & Hellsp@wn Специально для Delphi Plus
Пожалуйста, оцените статью