Previous step is here.
It's time to improve our packer. It can pack and run simple binaries already, which have only import table. Binaries with exports, resources, TLS, DLL with relocations are still beyond its powers. We have to work on this. Let's implement processing of second important thing after imports - of resource directory.
Firstly, let's add a couple of fields to packed_file_info structure:
1 2 3 4 |
//... DWORD original_resource_directory_rva; //Original resource directory relative address DWORD original_resource_directory_size; //Original resource directory size //... |
These fields will store information about original resource directory. In the unpacker we will store them in PE file header after unpacking sections data. But this is not all yet. We pack the data of all sections by assembling them into one large block, including resources. Icon and version information are lost from file, if they existed. It may not run at all, if it had some specific manifests in resources, which set up rights or libraries required to launch the file. We need to place original main application icon, version information and manifest next to packed data in new resource directory, and write a pointer to it to PE header.
Let's start with the unpacker (unpacker project), moreover, the changes will be minimal. Let's add lines, similar to import directory code:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
//... //Resource directory relative virtual address DWORD original_resource_directory_rva; //Resource directory virtual size DWORD original_resource_directory_size; //... original_resource_directory_rva = info->original_resource_directory_rva; original_resource_directory_size = info->original_resource_directory_size; //... //Pointer to resource directory IMAGE_DATA_DIRECTORY* resource_dir; resource_dir = reinterpret_cast<IMAGE_DATA_DIRECTORY*>(offset_to_directories + sizeof(IMAGE_DATA_DIRECTORY) * IMAGE_DIRECTORY_ENTRY_RESOURCE); //Write size and virtual address values to corresponding fields resource_dir->Size = original_resource_directory_size; resource_dir->VirtualAddress = original_resource_directory_rva; |
That's all with the unpacker. Now we turn to the packer (simple_pe_packer project). Again, we add lines for resource directory similar to import directory ones:
1 2 3 4 5 6 |
//... //Store original address and size //of packed file original resource directory basic_info.original_resource_directory_rva = image.get_directory_rva(IMAGE_DIRECTORY_ENTRY_RESOURCE); basic_info.original_resource_directory_size = image.get_directory_size(IMAGE_DIRECTORY_ENTRY_RESOURCE); //... |
Basically, this should be enough already for file with resources (forms, for example) to launch. Problems will occur, if the file has xp manifest or something like that. So we now need to rebuild resource directory in the packer, as I described above. We add new #include <pe_resource_manager.h> to the beginning of main.cpp. It is necessary to access helper resource management classes. We will add code to certain place in packer, where original file sections are removed (right before this action):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 |
//New empty root resources directory pe_base::resource_directory new_root_dir; if(image.has_resources()) { std::cout << "Repacking resources..." << std::endl; //Get original file resources (root directory) pe_base::resource_directory root_dir = image.get_resources(); //Wrap original and new resource directory //using helper classes pe_resource_viewer res(root_dir); pe_resource_manager new_res(new_root_dir); try { //List all named icon groups //and icon groups with ID pe_resource_viewer::resource_id_list icon_id_list(res.list_resource_ids(pe_resource_viewer::resource_icon_group)); pe_resource_viewer::resource_name_list icon_name_list(res.list_resource_names(pe_resource_viewer::resource_icon_group)); //Named resources are always placed first, so let's check if they exist if(!icon_name_list.empty()) { //Get first icon for first language (by index 0) //If we would have to list languages for specific icon, we could call list_resource_languages //If we would have to get an icon for specific language, we could call get_icon_by_name (overload with language parameter) //Add an icon group to a new resource directory new_res.add_icon( res.get_icon_by_name(icon_name_list[0]), icon_name_list[0], res.list_resource_languages(pe_resource_viewer::resource_icon_group, icon_name_list[0]).at(0)); } else if(!icon_id_list.empty()) //If there aren't any named icon groups, but groups with ID exist { //Get first icon for first language (by index 0) //If we would have to list languages for specified icon, we could call list_resource_languages //If we would have to get icon for certain language, we could call get_icon_by_id_lang //Add an icon group to a new resource directory new_res.add_icon( res.get_icon_by_id(icon_id_list[0]), icon_id_list[0], res.list_resource_languages(pe_resource_viewer::resource_icon_group, icon_id_list[0]).at(0)); } } catch(const pe_exception&) { //If there is any issue with resources, for example, missing icons, //do nothing } try { //List manifests with ID pe_resource_viewer::resource_id_list manifest_id_list(res.list_resource_ids(pe_resource_viewer::resource_manifest)); if(!manifest_id_list.empty()) //If manifest exists { //Get first manifest for first language (by index 0) //Add manifest to a new resource group new_res.add_resource( res.get_resource_data_by_id(pe_resource_viewer::resource_manifest, manifest_id_list[0]).get_data(), pe_resource_viewer::resource_manifest, manifest_id_list[0], res.list_resource_languages(pe_resource_viewer::resource_manifest, manifest_id_list[0]).at(0) ); } } catch(const pe_exception&) { //If there is any resource error, //do nothing } try { //Get list of version information structures with ID pe_resource_viewer::resource_id_list version_info_id_list(res.list_resource_ids(pe_resource_viewer::resource_version)); if(!version_info_id_list.empty()) //If version information exists { //Get first version information structure for first language (by index 0) //Add version information to a new resource directory new_res.add_resource( res.get_resource_data_by_id(pe_resource_viewer::resource_version, version_info_id_list[0]).get_data(), pe_resource_viewer::resource_version, version_info_id_list[0], res.list_resource_languages(pe_resource_viewer::resource_version, version_info_id_list[0]).at(0) ); } } catch(const pe_exception&) { //If there is any resource error, //do nothing } } |
So, what happens in this piece of code? In short, we get original file resources and search them for:
1. Icons
2. Manifest
3. Version information
These three kinds of resources should be placed unpacked to a new resource directory. We get the first icon (actually, the icon group) from original file, it is used by Windows as main file icon. File always has one manifest, and it describes, which rights are required for file to launch, and which DLL files are used. Other information may be stored there. Al last, version information - this is actually information about file, which also can be viewed in Windows Explorer.
I did not describe before how the resource directory is organized, so for general education purposes I will give this information now. Let's take a look at some exe file resources in CFF Explorer:
As you can see, the resources are organized as a tree structure. Root directory recordings begin with resource type ones. They nest directories with recordings containing resource name or ID, and they, in turn, nest language recordings directories. Last ones contain data directory, which points to resource data.
So, the code above lists all the icons with name or ID. We look for first icon because all PE file resources are sorted, and named resources come first, followed by resources with ID. It will be the first icon of application, and we add it to a new resource directory, keeping its name/ID and language. We process manifest and version information in a similar way - all files have no more than one such recording, and they are always unnamed, i.e. having ID, so we get and save first manifest recording and first version information recording.
We created a new resource directory, now we should save it to our file. First, let's add a couple of lines before import rebuilding code:
1 2 3 4 5 6 7 8 |
//If we have resources to build, //disable automatic section stripping //after adding imports to it if(!new_root_dir.get_entry_list().empty()) settings.enable_auto_strip_last_section(false); //Rebuild imports image.rebuild_imports(imports, added_section, settings); |
The fact is that all rebuilders in my PE library will automatically strip all null bytes from the end of the section, into which rebuilt imports/exports/resources etc. are placed, if that section is last in the file. This is an absolutely adequate behavior, which allows to reduce the file size within the limits of file alignment, as the loader will restore null bytes anyway, but in memory, in number of [aligned virtual section size] minus [physical section size]. Thus, if any structure within section ends with null element (for example, final import table descriptor), it will not be physically saved to file, if this import table is placed in last PE file section. By the way, some interesting bugs in editors like CFF Explorer are associated with this fact. It relies on physical file data only, not on virtual. Therefore it can display import table incorrectly with stripped null bytes in its end.
Now we need null bytes related to import table not to be stripped, as we are going to place resources right after imports. If we strip these null bytes, then resource directory will be placed over import directory, as a result we will get total crap.
Let's go further - rebuild resources:
1 2 3 |
//Rebuild resources if they exist if(!new_root_dir.get_entry_list().empty()) image.rebuild_resources(new_root_dir, added_section, added_section.get_raw_data().size()); |
Everything is clear here - if there are any resources, we save them to a new PE file. We place them at the end of added section, right after imports. Because this section is the last at the moment of resource rebuilding, it will expand automatically.
We have only to remove one line, which deletes the resource directory:
1 |
image.remove_directory(IMAGE_DIRECTORY_ENTRY_RESOURCE); |
Ready! We can test our packer on any exe file with imports, resources and even relocations (although we don't process them yet). Let's take any project in MSVC++ with MFC and view its original resources with CFF explorer:
After packing:
So, one icon group with several icons (application icons), a manifest (configuration files) and version information remain. Of course, the packed file successfully runs!
Full solution for this step: Own PE Packer step 5