(495) 925-0049, ITShop интернет-магазин 229-0436, Учебный Центр 925-0049
  Главная страница Карта сайта Контакты
Поиск
Вход
Регистрация
Рассылки сайта
 
 
 
 
 

C++ немного практики

Источник: kaimi

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

Писать будем на C++. Никаких извратов не будет, поэтому код должен быть понятен, тем более, я его досконально прокомментирую.
Начнем с инклюдов:

//библиотека ввода-вывода для вывода информации в консоль
#include <iostream>
//библиотека для работа с файлами
#include <fstream>
//вспомогательная библиотека для выравнивания, форматирования вывода и т.д.
#include <iomanip>
//конечно, нам потребуются структуры из Windows.h
//но ничто, в общем-то, не мешает их перенести прямо в код и скомпилировать это под линукс :)
#include <Windows.h>

Далее - несколько макросов, которые предоставил Крис Касперски в своей статье про формат PE. Мы будем чаще всего использовать ALIGN_UP - макрос для выравнивания числа на заданную границу.

#define Is2power(x) (!(x & (x - 1)))
#define ALIGN_DOWN(x, align) (x & ~(align - 1))
#define ALIGN_UP(x, align) ((x & (align - 1)) ? ALIGN_DOWN(x, align) + align : x)

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

int main(int argc, const char* argv[])
{
//если аргумент не передали - выведем пример использования и выйдем
	if(argc != 2)
	{
		std::cout << "Usage: sectons.exe pe_file" << std::endl;
		return 0;
	}

Теперь пришла пора открыть файл, имя которого нам передали через консоль.

//откроем файл формата PE в бинарном режиме
	std::ifstream pefile;
	pefile.open(argv[1], std::ios::in / std::ios::binary);
	if(!pefile.is_open())
	{
//если вдруг его открыть не удалось, то выведем ошибку и выйдем
		std::cout << "Can't open file" << std::endl;
		return 0;
	}
 
//определим размер файла, он нам пригодится дальше
	pefile.seekg(0, std::ios::end);
//для этого переведем файловый указатель чтения в самый конец файла, получим его позицию
	std::streamoff filesize = pefile.tellg();
//это и будет размер файла в байтах
//затем вернем файловый указатель в начало файла
	pefile.seekg(0);

Как я писал в предыдущей статье, в самом начале файла должна лежать структура IMAGE_DOS_HEADER. Считаем ее и немного проверим.

	IMAGE_DOS_HEADER dos_header;
	pefile.read(reinterpret_cast<char*>(&dos_header), sizeof(IMAGE_DOS_HEADER));
	if(pefile.bad() // pefile.eof())
	{
//если вдруг считать не удалось...
		std::cout << "Unable to read IMAGE_DOS_HEADER" << std::endl;
		return 0;
	}
 
//Первые два байта структуры должны быть MZ, но, так как в x86 у нас обратный порядок следования байтов,
//мы сравниваем эти байты со значением 'ZM'
	if(dos_header.e_magic != 'ZM')
	{
		std::cout << "IMAGE_DOS_HEADER signature is incorrect" << std::endl;
		return 0;
	}
 
//Начало заголовка самого PE-файла (IMAGE_NT_HEADERS) должно быть
//выровнено на величину двойного слова (DWORD)
//убедимся, что это так
	if((dos_header.e_lfanew % sizeof(DWORD)) != 0)
	{
//а иначе наш PE-файл некорректен
		std::cout << "PE header is not DWORD-aligned" << std::endl;
		return 0;
	}

Теперь необходимо считать структуру IMAGE_NT_HEADERS. Я программу писал исключительно под PE32, хотя сделать ее для PE64 или вообще универсальной труда никакого не составляет. Читать будем, соответственно, структуру IMAGE_NT_HEADERS32 (это 32-разрядная версия IMAGE_NT_HEADERS, они все определены в глубине Windows.h). Сейчас я пропускаю множество необходимых проверок полей заголовка PE-файла (например, не проверяю выравнивания), потому что они сейчас не являются критичными.

//Переходим на структуру IMAGE_NT_HEADERS и готовимся считать ее
	pefile.seekg(dos_header.e_lfanew);
	if(pefile.bad() // pefile.fail())
	{
		std::cout << "Cannot reach IMAGE_NT_HEADERS" << std::endl;
		return 0;
	}
 
//Читаем
//читать будем только часть структуры IMAGE_NT_HEADERS
//без дата директорий
//они нам и не понадобятся сейчас
	IMAGE_NT_HEADERS32 nt_headers;
	pefile.read(reinterpret_cast<char*>(&nt_headers), sizeof(IMAGE_NT_HEADERS32) - sizeof(IMAGE_DATA_DIRECTORY) * 16);
	if(pefile.bad() // pefile.eof())
	{
		std::cout << "Error reading IMAGE_NT_HEADERS32" << std::endl;
		return 0;
	}
//Проверяем, что наш файл - PE
//сигнатура у него должна быть "PE\0\0"
//помним про обратный порядок байтов и проверяем...
	if(nt_headers.Signature != 'EP')
	{
		std::cout << "Incorrect PE signature" << std::endl;
		return 0;
	}
 
//Проверяем, что это PE32
	if(nt_headers.OptionalHeader.Magic != 0x10B)
	{
		std::cout << "This PE is not PE32" << std::endl;
		return 0;
	}

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

//позиция в файле таблицы секций - это размер всех заголовков полностью
//(включая дос-стаб, если он есть и все дата директории, если они есть)
	DWORD first_section = dos_header.e_lfanew + nt_headers.FileHeader.SizeOfOptionalHeader + sizeof(IMAGE_FILE_HEADER) + sizeof(DWORD) /* Signature */;
 
//переходим на первую секцию в таблице секций
	pefile.seekg(first_section);
	if(pefile.bad() // pefile.fail())
	{
		std::cout << "Cannot reach section headers" << std::endl;
		return 0;
	}

Немного подготовим консоль для удобного вывода информации. Выставим выравнивание текста по левому краю и вывод чисел в 16-ричной системе счисления. std::showbase добавит перед 16-разрядными числами "0x" автоматически.

	std::cout << std::hex << std::showbase << std::left;

Теперь начнем читать таблицу секций. Количество секций лежит в IMAGE_NT_HEADERS.FileHeader.NumberOfSections.

	for(int i = 0; i < nt_headers.FileHeader.NumberOfSections; i++)
	{
//готовим заголовок секции
		IMAGE_SECTION_HEADER header;
//и читаем его
		pefile.read(reinterpret_cast<char*>(&header), sizeof(IMAGE_SECTION_HEADER));
		if(pefile.bad() // pefile.eof())
		{
			std::cout << "Error reading section header" << std::endl;
			return 0;
		}

Дальше я добавил всевозможные проверки корректности таблицы секций. Разберем их.

//во-первых, "сырой" размер данных и виртуальный размер секции
//не могут быть одновременно нулевыми
		if(!header.SizeOfRawData && !header.Misc.VirtualSize)
		{
			std::cout << "Virtual and Physical sizes of section can't be 0 at the same time" << std::endl;
			return 0;
		}
 
//если размер инициализированных данных ("сырых") не равен нулю...
		if(header.SizeOfRawData != 0)
		{
//Проверим, что инициализированные данные секции также не вылетают за пределы нашего PE-файла
			if(ALIGN_DOWN(header.PointerToRawData, nt_headers.OptionalHeader.FileAlignment) + header.SizeOfRawData > filesize)
			{
				std::cout << "Incorrect section address or size" << std::endl;
				return 0;
			}
 
//в этой переменной мы сохраним выровненный виртуальный размер секции
			DWORD virtual_size_aligned;
 
//если виртуальный размер секции был выставлен в ноль,
			if(header.Misc.VirtualSize == 0)
//то ее выровненный виртуальный размер равен ее реальному размеру инициализированных данных,
//выровненному на границу SectionAlignment
				virtual_size_aligned = ALIGN_UP(header.SizeOfRawData, nt_headers.OptionalHeader.SectionAlignment);
			else
//а иначе он равен ее виртуальному размеру,
//выровненному на границу SectionAlignment
				virtual_size_aligned = ALIGN_UP(header.Misc.VirtualSize, nt_headers.OptionalHeader.SectionAlignment);

Если вам сейчас трудно вспомнить, что это всё такое - виртуальный размер, реальный адрес, выравнивание, то советую вернуться к первой статье и всё повторить.

//Проверим, что виртуальное пространство секции не вылетает за пределы виртуального пространства всего PE-файла
			if(header.VirtualAddress + virtual_size_aligned > ALIGN_UP(nt_headers.OptionalHeader.SizeOfImage, nt_headers.OptionalHeader.SectionAlignment))
			{
				std::cout << "Incorrect section address or size" << std::endl;
				return 0;
			}
		}

Пришло время вывести информацию о секции - раз уж она прошла все проверки :)

//имя секции может иметь размер до 8 символов
		char name[9] = {0};
		memcpy(name, header.Name, 8);
//выводим имя секции
		std::cout << std::setw(20) << "Section: " << name << std::endl << "=======================" << std::endl;
//ее размеры, адреса
		std::cout << std::setw(20) << "Virtual size:" << header.Misc.VirtualSize << std::endl;
		std::cout << std::setw(20) << "Raw size:"  << header.SizeOfRawData << std::endl;
		std::cout << std::setw(20) << "Virtual address:" << header.VirtualAddress << std::endl;
		std::cout << std::setw(20) << "Raw address:" << header.PointerToRawData << std::endl;
 
//и самые важные характеристики
		std::cout << std::setw(20) << "Characteristics: ";
		if(header.Characteristics & IMAGE_SCN_MEM_READ)
			std::cout << "R ";
		if(header.Characteristics & IMAGE_SCN_MEM_WRITE)
			std::cout << "W ";
		if(header.Characteristics & IMAGE_SCN_MEM_EXECUTE)
			std::cout << "X ";
		if(header.Characteristics & IMAGE_SCN_MEM_DISCARDABLE)
			std::cout << "discardable ";
		if(header.Characteristics & IMAGE_SCN_MEM_SHARED)
			std::cout << "shared";
 
		std::cout << std::endl << std::endl;
	}
 
	return 0;
}

Вот и все, наша программа готова, и ей можно через консоль скормить любой исполняемый файл (PE32), чтобы получить информацию о его секциях. На первом скриншоте как раз показан вывод этой программы при анализе самой себя, собранной в Visual Studio 2010 в отладочной версии.

Полная версия кода (без комментариев): скачать (txt).



 Распечатать »
 Правила публикации »
  Написать редактору 
 Рекомендовать » Дата публикации: 09.08.2011 
 

Магазин программного обеспечения   WWW.ITSHOP.RU
Toad Data Modeler Per Seat License/Maint
Microsoft Office 365 для Дома 32-bit/x64. 5 ПК/Mac + 5 Планшетов + 5 Телефонов. Подписка на 1 год.
EMS SQL Management Studio for InterBase/Firebird (Business) + 1 Year Maintenance
Quest Software. Toad for DBA Suite for Oracle
ZBrush 4R6 Win Commercial Single License ESD
 
Другие предложения...
 
Курсы обучения   WWW.ITSHOP.RU
 
Другие предложения...
 
Магазин сертификационных экзаменов   WWW.ITSHOP.RU
 
Другие предложения...
 
3D Принтеры | 3D Печать   WWW.ITSHOP.RU
 
Другие предложения...
 
Новости по теме
 
Рассылки Subscribe.ru
Информационные технологии: CASE, RAD, ERP, OLAP
Программирование на Microsoft Access
CASE-технологии
OS Linux для начинающих. Новости + статьи + обзоры + ссылки
Реестр Windows. Секреты работы на компьютере
СУБД Oracle "с нуля"
Работа в Windows и новости компании Microsoft
 
Статьи по теме
 
Новинки каталога Download
 
Исходники
 
Документация
 
 



    
rambler's top100 Rambler's Top100