Пишем упаковщик по шагам. Шаг четвертый. Запускаем.

Предыдущий шаг: здесь.

Появилась новая версия библиотеки для работы с PE-файлами (0.1.4). Перекачайте и пересоберите ее.

Итак, из прошлых шагов мы имеем работающий упаковщик и базовый распаковщик, который пока что ничего не делает. В этом шаге мы добьемся запуска простых упакованных программ (которые не имеют ничего, кроме таблицы импорта и, возможно, релокаций). Первое, что нужно сделать в распаковщике помимо разархивирования данных - это поправить таблицу импорта оригинального файла. Обычно это делает загрузчик, но сейчас для сжатого файла роль загрузчика играем мы.

Добавим несколько полей в нашу структуру packed_file_info:

//Структура, хранящая информацию об упакованном файлеstruct packed_file_info
{
  BYTE number_of_sections;//Количество секций в оригинальном файле
  DWORD size_of_packed_data;//Размер упакованных данных
  DWORD size_of_unpacked_data;//Размер оригинальных данных
 
  DWORD total_virtual_size_of_sections;//Полный виртуальный размер всех секций оригинального файла
  DWORD original_import_directory_rva;//Относительный адрес оригинальной таблицы импорта
  DWORD original_import_directory_size;//Размер оригинальной таблицы импорта
  DWORD original_entry_point;//Оригинальная точка входа
 
  DWORD load_library_a;//Адрес процедуры LoadLibraryA из kernel32.dll
  DWORD get_proc_address;//Адрес процедуры GetProcAddress из kernel32.dll
  DWORD end_of_import_address_table;//Конец IAT};

Мы добавили 4 поля, которые нам пригодятся в распаковщике. Теперь необходимо их заполнить в коде упаковщика:

//...//Структура базовой информации о PE-файле
    packed_file_info basic_info ={0};//Получаем и сохраняем изначальное количество секций
    basic_info.number_of_sections= sections.size();
 
    //Запоминаем относительный адрес и размер//оригинальной таблицы импорта упаковываемого файла
    basic_info.original_import_directory_rva= image.get_directory_rva(IMAGE_DIRECTORY_ENTRY_IMPORT);
    basic_info.original_import_directory_size= image.get_directory_size(IMAGE_DIRECTORY_ENTRY_IMPORT);//Запоминаем его точку входа
    basic_info.original_entry_point= image.get_ep();//Запоминаем общий виртуальный размер всех секций//упаковываемого файла
    basic_info.total_virtual_size_of_sections= image.get_size_of_image();

Здесь все просто. Во втором уроке, если вы помните, я вручную считал общий виртуальный размер всех секций исходного файла и пояснял, что он эквивалентен значению, возвращаемому функцией get_size_of_image. Здесь мы этим воспользовались. С упаковщиком на этом все. Переходим к распаковщику (проект unpacker). Нам необходимо вкомпилировать в него алгоритм разархивирования LZO1Z. Я сделал просто и по-тупому - перенес в проект unpacker все файлы, необходимые для компиляции функции lzo1z_decompress (а именно, lzo1z_d1.c, lzo1x_d.ch, config1z.h, config1x.h, lzo_conf.h, lzo_ptr.h, lzo1_d.ch, miniacc.h). Кроме того, я прописал дополнительную include-директорию в проект: ../../lzo-2.06/include. Далее пришлось еще поковыряться с настройками проекта. Visual C++ при использовании функций memset, memcpy и подобных (а мы их использовать будем не раз) может по своему желанию встроить в получившийся exe-файл целую CRT, которая для нас совершенно лишняя. Пришлось отключить intrinsic (внутренние) функции (C/C++ - Optimization - Enable Intrinsic Functions - No) и полную оптимизацию (C/C++ - Optimization - Whole Program Optimization - No), на всякий случай добавить libcmt.lib в список игнорируемых библиотек (Linker - Input - Ignore Specific Default Libraries - libcmt.lib) и отключить генерацию кода на этапе линкования (Linker - Optimization - Link Time Code Generation - Default). А раз мы отключили все внутренние функции (среди них memset и memcpy), нам теперь нужна их собственная имплементация. Добавим два файла к проекту: memcpy.c и memset.c. В эти файлы я скопировал исходный код одноименных функций из CRT:

void* __cdecl memset(void*dst,
        int val,
        unsignedint count
        ){void*start = dst;
 
        while(count--){*(char*)dst =(char)val;
                dst =(char*)dst +1;}
 
        return(start);}
void* __cdecl memcpy(void* dst,
        constvoid* src,
        unsignedint count
        ){void* ret = dst;
 
        /*
         * copy from lower addresses to higher addresses
         */while(count--){*(char*)dst =*(char*)src;
                dst =(char*)dst +1;
                src =(char*)src +1;}
 
        return(ret);}

Нас поджидает еще одна проблема. У нас в коде теперь аж четыре модуля (четыре файла с исходным кодом, .c и .cpp), то после компиляции мы будем иметь четыре объектных (obj) файла. Далее линкер должен все это как-то слепить в единый exe-файл, и он это сделает. Но он расположит эти модули в exe-файле в одному ему известном порядке. Нам же необходимо, чтобы функция unpacker_main располагалась в самом начале кода распаковщика. Мы ведь ее в упаковщике патчим, помните? Эта проблема легко решается. Создадим текстовый файл с таким содержанием:

unpacker_main@0
lzo1z_decompress
memset
memcpy

Назовем его link_order.txt и расположим его в папке с исходниками проекта unpacker. Этот файл скажет линкеру, в каком порядке должны располагаться функции в результирующем файле. Укажем этот файл в настройках проекта: Linker - Optimization - Function Order - link_order.txt. Все, с настройками покончено, начинаем писать код распаковщика!

Во-первых, я увеличил количество данных, выделяемых на стеке до 256 байтов (sub esp, 256). Переменных локальных много, поэтому перестрахуемся, а то вдруг 128 не хватит.

Пропишем прототип функции распаковки в начало файла unpacker.cpp:

//Алгоритм распаковки#include "lzo_conf.h"/* decompression */
LZO_EXTERN(int)
lzo1z_decompress        (const lzo_bytep src, lzo_uint  src_len,
                                lzo_bytep dst, lzo_uintp dst_len,
                                lzo_voidp wrkmem /* NOT USED */);

Теперь мы сможем ее использовать в коде. Далее нам понадобятся функции VirtualAlloc (для выделения памяти), VirtualProtect (для изменения атрибутов страниц памяти) и VirtualFree (для освобождения выделенной памяти). Давайте импортируем их из kernel32.dll:

//kernel32.dll*reinterpret_cast<DWORD*>(&buf[0])='nrek';*reinterpret_cast<DWORD*>(&buf[4])='23le';*reinterpret_cast<DWORD*>(&buf[8])='lld.';*reinterpret_cast<DWORD*>(&buf[12])=0;
 
  //Загружаем библиотеку kernel32.dll
  HMODULE kernel32_dll;
  kernel32_dll = load_library_a(buf);
 
  //Тайпдеф прототипа функции VirtualAlloctypedef LPVOID (__stdcall* virtual_alloc_func)(LPVOID lpAddress, SIZE_T dwSize, DWORD flAllocationType, DWORD flProtect);//Тайпдеф прототипа функции VirtualProtecttypedef LPVOID (__stdcall* virtual_protect_func)(LPVOID lpAddress, SIZE_T dwSize, DWORD flNewProtect, PDWORD lpflOldProtect);//Тайпдеф прототипа функции VirtualFreetypedef LPVOID (__stdcall* virtual_free_func)(LPVOID lpAddress, SIZE_T dwSize, DWORD dwFreeType);
 
  //VirtualAlloc*reinterpret_cast<DWORD*>(&buf[0])='triV';*reinterpret_cast<DWORD*>(&buf[4])='Alau';*reinterpret_cast<DWORD*>(&buf[8])='coll';*reinterpret_cast<DWORD*>(&buf[12])=0;
 
  //Получаем адрес функции VirtualAlloc
  virtual_alloc_func virtual_alloc;
  virtual_alloc =reinterpret_cast<virtual_alloc_func>(get_proc_address(kernel32_dll, buf));
 
  //VirtualProtect*reinterpret_cast<DWORD*>(&buf[0])='triV';*reinterpret_cast<DWORD*>(&buf[4])='Plau';*reinterpret_cast<DWORD*>(&buf[8])='etor';*reinterpret_cast<DWORD*>(&buf[12])='tc';
 
  //Получаем адрес функции VirtualProtect
  virtual_protect_func virtual_protect;
  virtual_protect =reinterpret_cast<virtual_protect_func>(get_proc_address(kernel32_dll, buf));
 
  //VirtualFree*reinterpret_cast<DWORD*>(&buf[0])='triV';*reinterpret_cast<DWORD*>(&buf[4])='Flau';*reinterpret_cast<DWORD*>(&buf[8])='eer';
 
  //Получаем адрес функции VirtualFree
  virtual_free_func virtual_free;
  virtual_free =reinterpret_cast<virtual_free_func>(get_proc_address(kernel32_dll, buf));

Этот кусок кода аналогичен коду в шаге 3, где мы загружали user32.dll и получали в ней адрес функции MessageBoxA, так что пояснять не буду. Далее следует перенести в локальную область видимости необходимые переменные, которые для нас запас упаковщик:

//Относительный виртуальный адрес директории импорта
  DWORD original_import_directory_rva;//Виртуальный размер директории импорта
  DWORD original_import_directory_size;//Оригинальная точка входа
  DWORD original_entry_point;//Общий размер всех секций файла
  DWORD total_virtual_size_of_sections;//Количество секций в оригинальном файле
  BYTE number_of_sections;
 
  //Копируем эти значения из структуры packed_file_info,//которую для нас записал упаковщик
  original_import_directory_rva = info->original_import_directory_rva;
  original_import_directory_size = info->original_import_directory_size;
  original_entry_point = info->original_entry_point;
  total_virtual_size_of_sections = info->total_virtual_size_of_sections;
  number_of_sections = info->number_of_sections;

Мы это сделали потому, что скоро структура packed_file_info, находящаяся в самом начале первой секции упакованного файла, будет затерта реальными распакованными данными. Теперь выделим память и распакуем в нее упакованный блок данных:

//Указатель на память, в которую//мы запишем распакованные данные
  LPVOID unpacked_mem;//Выделяем память
  unpacked_mem = virtual_alloc(0,
    info->size_of_unpacked_data,
    MEM_COMMIT,
    PAGE_READWRITE);
 
  //Выходной размер распакованных данных//(эта переменная, в принципе, не нужна)
  lzo_uint out_len;
  out_len =0;
 
  //Производим распаковку алгоритмом LZO
  lzo1z_decompress(reinterpret_cast<constunsignedchar*>(reinterpret_cast<DWORD>(info)+sizeof(packed_file_info)),
    info->size_of_packed_data,
    reinterpret_cast<unsignedchar*>(unpacked_mem),
    &out_len,
    0);

Инициализировать алгоритм LZO перед распаковкой не нужно, для распаковки достаточно вызвать единственную функцию, что мы и сделали. Далее вычислим виртуальный адрес заголовка первой секции.

//Указатель на DOS-заголовок файлаconst IMAGE_DOS_HEADER* dos_header;//Указатель на файловый заголовок
  IMAGE_FILE_HEADER* file_header;//Виртуальный адрес начала заголовков секций
  DWORD offset_to_section_headers;//Просчитываем этот адрес
  dos_header =reinterpret_cast<const IMAGE_DOS_HEADER*>(original_image_base);
  file_header =reinterpret_cast<IMAGE_FILE_HEADER*>(original_image_base + dos_header->e_lfanew +sizeof(DWORD));//Вот по такой формуле
  offset_to_section_headers = original_image_base + dos_header->e_lfanew + file_header->SizeOfOptionalHeader
    +sizeof(IMAGE_FILE_HEADER)+sizeof(DWORD)/* Signature */;

Теперь мы имеем виртуальный адрес заголовков секций. Нам их необходимо перезаписать, чтобы в памяти они выглядели так, как выглядят в оригинальном файле. Перед тем, как мы будем это делать, необходимо обработать еще кое-какие мелочи:

//Обнулим всю память первой секции//эта область соответствует области памяти, которую//в оригинальном файле занимают все секцииmemset(reinterpret_cast<void*>(original_image_base + rva_of_first_section),
    0,
    total_virtual_size_of_sections - rva_of_first_section);
 
  //Изменим атрибуты блока памяти, в котором//расположены заголовки PE-файла и секций//Нам необходим доступ на запись
  DWORD old_protect;
  virtual_protect(reinterpret_cast<LPVOID>(offset_to_section_headers),
    number_of_sections *sizeof(IMAGE_SECTION_HEADER),
    PAGE_READWRITE, &old_protect);
 
  //Теперь изменим количество секций//в заголовке PE-файла на оригинальное
  file_header->NumberOfSections = number_of_sections;

Приступим к восстановлению заголовков секций:

//Виртуальный адрес структуры заголовка секции
  DWORD current_section_structure_pos;
  current_section_structure_pos = offset_to_section_headers;//Перечислим все секцииfor(int i =0; i != number_of_sections;++i){//Создаем структуру заголовка секции
    IMAGE_SECTION_HEADER section_header;//Обнуляем структуруmemset(&section_header, 0, sizeof(section_header));//Заполняем важные поля://Характеристики
    section_header.Characteristics=(reinterpret_cast<packed_section*>(unpacked_mem)+ i)->characteristics;//Смещение файловых данных
    section_header.PointerToRawData=(reinterpret_cast<packed_section*>(unpacked_mem)+ i)->pointer_to_raw_data;//Размер файловых данных
    section_header.SizeOfRawData=(reinterpret_cast<packed_section*>(unpacked_mem)+ i)->size_of_raw_data;//Относительный виртуальный адрес секции
    section_header.VirtualAddress=(reinterpret_cast<packed_section*>(unpacked_mem)+ i)->virtual_address;//Виртуальный размер секции
    section_header.Misc.VirtualSize=(reinterpret_cast<packed_section*>(unpacked_mem)+ i)->virtual_size;//Копируем оригинальное имя секцииmemcpy(section_header.Name, (reinterpret_cast<packed_section*>(unpacked_mem)+ i)->name, sizeof(section_header.Name));
 
    //Копируем заполненный заголовок//в память, где находятся заголовки секцийmemcpy(reinterpret_cast<void*>(current_section_structure_pos), &section_header, sizeof(section_header));
 
    //Перемещаем указатель на следующий заголовок секции
    current_section_structure_pos +=sizeof(section_header);}

Заголовки секций восстановили, теперь восстановим их данные:

//Указатель на сырые данные секции//Необходим для разлепления сжатых данных секций//и распихивания их по нужным местам
  DWORD current_raw_data_ptr;
  current_raw_data_ptr =0;//Восстановим указатель на заголовки секций
  current_section_structure_pos = offset_to_section_headers;//Снова перечисляем все секцииfor(int i =0; i != number_of_sections;++i){//Заголовок секции, который мы только что сами записалиconst IMAGE_SECTION_HEADER* section_header =reinterpret_cast<const IMAGE_SECTION_HEADER*>(current_section_structure_pos);
 
    //Копируем данные секции в то место памяти,//где они должны располагатьсяmemcpy(reinterpret_cast<void*>(original_image_base + section_header->VirtualAddress),
      reinterpret_cast<char*>(unpacked_mem)+ number_of_sections *sizeof(packed_section)+ current_raw_data_ptr,
      section_header->SizeOfRawData);
 
    //Перемещаем указатель на данные секции//в распакованном блоке данных
    current_raw_data_ptr += section_header->SizeOfRawData;
 
    //Переходим к следующему заголовку секции
    current_section_structure_pos +=sizeof(IMAGE_SECTION_HEADER);}
 
  //Освобождаем память с распакованными данными,//она нам больше не нужна
  virtual_free(unpacked_mem, 0, MEM_RELEASE);

И, почти все готово. Чтобы упакованный файл запустился, остается лишь пофиксить его таблицу импорта, снова выступив в роли PE-загрузчика. Для начала пофиксим виртуальный адрес и размер таблицы импорта в PE-заголовке:

//Вычислим относительный виртуальный адрес//начала таблицы директорий
  DWORD offset_to_directories;
  offset_to_directories = original_image_base + dos_header->e_lfanew
    +sizeof(IMAGE_NT_HEADERS32)-sizeof(IMAGE_DATA_DIRECTORY)* IMAGE_NUMBEROF_DIRECTORY_ENTRIES;
 
  //Указатель на директорию импорта
  IMAGE_DATA_DIRECTORY* import_dir =reinterpret_cast<IMAGE_DATA_DIRECTORY*>(offset_to_directories +sizeof(IMAGE_DATA_DIRECTORY)* IMAGE_DIRECTORY_ENTRY_IMPORT);//Записываем значения размера и виртуального адреса в соответствующие поля
  import_dir->Size = original_import_directory_size;
  import_dir->VirtualAddress = original_import_directory_rva;

Заполняем таблицу импорта:

//Если у файла имеются импортыif(original_import_directory_rva){//Виртуальный адрес первого дескриптора
    IMAGE_IMPORT_DESCRIPTOR* descr;
    descr =reinterpret_cast<IMAGE_IMPORT_DESCRIPTOR*>(original_import_directory_rva + original_image_base);
 
    //Перечисляем все дескрипторы//Последний - нулевойwhile(descr->Name){//Загружаем необходимую DLL
      HMODULE dll;
      dll = load_library_a(reinterpret_cast<char*>(descr->Name + original_image_base));//Указатели на таблицу адресов и lookup-таблицу
      DWORD* lookup, *address;//Учтем, что lookup-таблицы может и не быть,//как я говорил в предыдущем шаге
      lookup =reinterpret_cast<DWORD*>(original_image_base +(descr->OriginalFirstThunk ? descr->OriginalFirstThunk : descr->FirstThunk));
      address =reinterpret_cast<DWORD*>(descr->FirstThunk + original_image_base);
 
      //Перечисляем все импорты в дескриптореwhile(true){//До первого нулевого элемента в лукап-таблице
        DWORD lookup_value =*lookup;if(!lookup_value)break;
 
        //Проверим, импортируется ли функция по ординалуif(IMAGE_SNAP_BY_ORDINAL32(lookup_value))*address =static_cast<DWORD>(get_proc_address(dll, reinterpret_cast<constchar*>(lookup_value & ~IMAGE_ORDINAL_FLAG32)));else*address =static_cast<DWORD>(get_proc_address(dll, reinterpret_cast<constchar*>(lookup_value + original_image_base +sizeof(WORD))));
 
        //Переходим к следующему элементу++lookup;++address;}
 
      //Переходим к следующему дескриптору++descr;}}

Вот и все, мы, как PE-загрузчик, заполнили PE-файлу таблицу импорта. Осталась пара мелочей:

//Вернем атрибуты памяти заголовков, как было изначально
  virtual_protect(reinterpret_cast<LPVOID>(offset_to_section_headers), number_of_sections *sizeof(IMAGE_SECTION_HEADER), old_protect, &old_protect);
 
  //Эпилог вручную
  _asm
  {//Переходим на оригинальную точку входа
    mov eax, original_entry_point;
    add eax, original_image_base;
    leave;//Вот так
    jmp eax;}

Теперь вы поняли, зачем нам нужны были собственные пролог и эпилог функции на ассемблере. Вместо инструкции ret, которая раньше располагалась в самом конце кода распаковщика, мы поставили инструкцию jmp eax, осуществляющую переход на код оригинального файла.

Итак, распаковщик теперь сможет запустить простейший PE-файл, имеющий только таблицу импорта. Любой файл с ресурсами, TLS, экспортами работать не будет, и этим мы займемся в следующих шагах. Но мы уже можем запаковать сами себя и запустить запакованный вариант!

Как видно, мы запаковали сами себя, получив бинарник packed_simple_pe_packer.exe, и он работает!

Полный солюшен со всеми проектами для данного шага: Own PE Packer Step 4

Также рекомендую почитать

 Обсудить на форуме


Источник: http://feedproxy.google.com/~r/kaimi/dev/~3/hJHKtytjSyU/

Читать комменты и комментировать

Добавить комментарий / отзыв



Защитный код
Обновить

Пишем упаковщик по шагам. Шаг четвертый. Запускаем. | | 2012-09-17 20:31:18 | | Блоги и всяко-разно | | Предыдущий шаг: здесь.Появилась новая версия библиотеки для работы с PE-файлами (0.1.4). Перекачайте и пересоберите ее.Итак, из прошлых шагов мы имеем работающий упаковщик и базовый распаковщик, | РэдЛайн, создание сайта, заказать сайт, разработка сайтов, реклама в Интернете, продвижение, маркетинговые исследования, дизайн студия, веб дизайн, раскрутка сайта, создать сайт компании, сделать сайт, создание сайтов, изготовление сайта, обслуживание сайтов, изготовление сайтов, заказать интернет сайт, создать сайт, изготовить сайт, разработка сайта, web студия, создание веб сайта, поддержка сайта, сайт на заказ, сопровождение сайта, дизайн сайта, сайт под ключ, заказ сайта, реклама сайта, хостинг, регистрация доменов, хабаровск, краснодар, москва, комсомольск |
 
Поделиться с друзьями: