/*************************************************************************/
/* Copyright (c) 2015 dx, http://kaimi.ru                                */
/*                                                                       */
/* Permission is hereby granted, free of charge, to any person           */
/* obtaining a copy of this software and associated documentation        */
/* files (the "Software"), to deal in the Software without               */
/* restriction, including without limitation the rights to use,          */
/* copy, modify, merge, publish, distribute, sublicense, and/or          */
/* sell copies of the Software, and to permit persons to whom the        */
/* Software is furnished to do so, subject to the following conditions:  */
/* The above copyright notice and this permission notice shall be        */
/* included in all copies or substantial portions of the Software.       */
/*                                                                       */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,       */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF    */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY  */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,  */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE     */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.                */
/*************************************************************************/
#include <string.h>
#include "pe_bound_import.h"
#include "utils.h"

namespace pe_bliss
{
using namespace pe_win;

//BOUND IMPORT
//Default constructor
bound_import_ref::bound_import_ref()
	:timestamp_(0)
{}

//Constructor from data
bound_import_ref::bound_import_ref(const std::string& module_name, uint32_t timestamp)
	:module_name_(module_name), timestamp_(timestamp)
{}

//Returns imported module name
const std::string& bound_import_ref::get_module_name() const
{
	return module_name_;
}

//Returns bound import date and time stamp
uint32_t bound_import_ref::get_timestamp() const
{
	return timestamp_;
}

//Sets module name
void bound_import_ref::set_module_name(const std::string& module_name)
{
	module_name_ = module_name;
}

//Sets timestamp
void bound_import_ref::set_timestamp(uint32_t timestamp)
{
	timestamp_ = timestamp;
}

//Default constructor
bound_import::bound_import()
	:timestamp_(0)
{}

//Constructor from data
bound_import::bound_import(const std::string& module_name, uint32_t timestamp)
	:module_name_(module_name), timestamp_(timestamp)
{}

//Returns imported module name
const std::string& bound_import::get_module_name() const
{
	return module_name_;
}

//Returns bound import date and time stamp
uint32_t bound_import::get_timestamp() const
{
	return timestamp_;
}

//Returns bound references cound
size_t bound_import::get_module_ref_count() const
{
	return refs_.size();
}

//Returns module references
const bound_import::ref_list& bound_import::get_module_ref_list() const
{
	return refs_;
}

//Adds module reference
void bound_import::add_module_ref(const bound_import_ref& ref)
{
	refs_.push_back(ref);
}

//Clears module references list
void bound_import::clear_module_refs()
{
	refs_.clear();
}

//Returns module references
bound_import::ref_list& bound_import::get_module_ref_list()
{
	return refs_;
}

//Sets module name
void bound_import::set_module_name(const std::string& module_name)
{
	module_name_ = module_name;
}

//Sets timestamp
void bound_import::set_timestamp(uint32_t timestamp)
{
	timestamp_ = timestamp;
}

const bound_import_module_list get_bound_import_module_list(const pe_base& pe)
{
	//Returned bound import modules list
	bound_import_module_list ret;

	//If image has no bound imports
	if(!pe.has_bound_import())
		return ret;

	uint32_t bound_import_data_len =
		pe.section_data_length_from_rva(pe.get_directory_rva(image_directory_entry_bound_import), pe.get_directory_rva(image_directory_entry_bound_import), section_data_raw, true);

	if(bound_import_data_len < pe.get_directory_size(image_directory_entry_bound_import))
		throw pe_exception("Incorrect bound import directory", pe_exception::incorrect_bound_import_directory);
	
	const char* bound_import_data = pe.section_data_from_rva(pe.get_directory_rva(image_directory_entry_bound_import), section_data_raw, true);

	//Check read in "read_pe" function raw bound import data size
	if(bound_import_data_len < sizeof(image_bound_import_descriptor))
		throw pe_exception("Incorrect bound import directory", pe_exception::incorrect_bound_import_directory);

	//current bound_import_data_ in-string position
	unsigned long current_pos = 0;
	//first bound import descriptor
	//so, we're working with raw data here, no section helpers available
	const image_bound_import_descriptor* descriptor = reinterpret_cast<const image_bound_import_descriptor*>(&bound_import_data[current_pos]);

	//Enumerate until zero
	while(descriptor->OffsetModuleName)
	{
		//Check module name offset
		if(descriptor->OffsetModuleName >= bound_import_data_len)
			throw pe_exception("Incorrect bound import directory", pe_exception::incorrect_bound_import_directory);

		//Check module name for null-termination
		if(!pe_utils::is_null_terminated(&bound_import_data[descriptor->OffsetModuleName], bound_import_data_len - descriptor->OffsetModuleName))
			throw pe_exception("Incorrect bound import directory", pe_exception::incorrect_bound_import_directory);

		//Create bound import descriptor structure
		bound_import elem(&bound_import_data[descriptor->OffsetModuleName], descriptor->TimeDateStamp);

		//Check DWORDs
		if(descriptor->NumberOfModuleForwarderRefs >= pe_utils::max_dword / sizeof(image_bound_forwarder_ref)
			|| !pe_utils::is_sum_safe(current_pos, 2 /* this descriptor and the next one */ * sizeof(image_bound_import_descriptor) + descriptor->NumberOfModuleForwarderRefs * sizeof(image_bound_forwarder_ref)))
			throw pe_exception("Incorrect bound import directory", pe_exception::incorrect_bound_import_directory);

		//Move after current descriptor
		current_pos += sizeof(image_bound_import_descriptor);

		//Enumerate referenced bound import descriptors
		for(unsigned long i = 0; i != descriptor->NumberOfModuleForwarderRefs; ++i)
		{
			//They're just after parent descriptor
			//Check size of structure
			if(current_pos + sizeof(image_bound_forwarder_ref) > bound_import_data_len)
				throw pe_exception("Incorrect bound import directory", pe_exception::incorrect_bound_import_directory);

			//Get IMAGE_BOUND_FORWARDER_REF pointer
			const image_bound_forwarder_ref* ref_descriptor = reinterpret_cast<const image_bound_forwarder_ref*>(&bound_import_data[current_pos]);

			//Check referenced module name
			if(ref_descriptor->OffsetModuleName >= bound_import_data_len)
				throw pe_exception("Incorrect bound import directory", pe_exception::incorrect_bound_import_directory);

			//And its null-termination
			if(!pe_utils::is_null_terminated(&bound_import_data[ref_descriptor->OffsetModuleName], bound_import_data_len - ref_descriptor->OffsetModuleName))
				throw pe_exception("Incorrect bound import directory", pe_exception::incorrect_bound_import_directory);

			//Add referenced module to current bound import structure
			elem.add_module_ref(bound_import_ref(&bound_import_data[ref_descriptor->OffsetModuleName], ref_descriptor->TimeDateStamp));

			//Move after referenced bound import descriptor
			current_pos += sizeof(image_bound_forwarder_ref);
		}

		//Check structure size
		if(current_pos + sizeof(image_bound_import_descriptor) > bound_import_data_len)
			throw pe_exception("Incorrect bound import directory", pe_exception::incorrect_bound_import_directory);

		//Move to next bound import descriptor
		descriptor = reinterpret_cast<const image_bound_import_descriptor*>(&bound_import_data[current_pos]);

		//Save created descriptor structure and references
		ret.push_back(elem);
	}

	//Return result
	return ret;
}

//imports - bound imported modules list
//imports_section - section where export directory will be placed (must be attached to PE image)
//offset_from_section_start - offset from imports_section raw data start
//save_to_pe_headers - if true, new bound import directory information will be saved to PE image headers
//auto_strip_last_section - if true and bound imports are placed in the last section, it will be automatically stripped
const image_directory rebuild_bound_imports(pe_base& pe, const bound_import_module_list& imports, section& imports_section, uint32_t offset_from_section_start, bool save_to_pe_header, bool auto_strip_last_section)
{
	//Check that exports_section is attached to this PE image
	if(!pe.section_attached(imports_section))
		throw pe_exception("Bound import section must be attached to PE file", pe_exception::section_is_not_attached);

	uint32_t directory_pos = pe_utils::align_up(offset_from_section_start, sizeof(uint32_t));
	uint32_t needed_size = sizeof(image_bound_import_descriptor) /* Ending null descriptor */;
	uint32_t needed_size_for_strings = 0;

	//Calculate needed size for bound import data
	for(bound_import_module_list::const_iterator it = imports.begin(); it != imports.end(); ++it)
	{
		const bound_import& import = *it;
		needed_size += sizeof(image_bound_import_descriptor);
		needed_size_for_strings += static_cast<uint32_t>((*it).get_module_name().length()) + 1 /* nullbyte */;

		const bound_import::ref_list& refs = import.get_module_ref_list();
		for(bound_import::ref_list::const_iterator ref_it = refs.begin(); ref_it != refs.end(); ++ref_it)
		{
			needed_size_for_strings += static_cast<uint32_t>((*ref_it).get_module_name().length()) + 1 /* nullbyte */;
			needed_size += sizeof(image_bound_forwarder_ref);
		}
	}
	
	needed_size += needed_size_for_strings;
	
	//Check if imports_section is last one. If it's not, check if there's enough place for bound import data
	if(&imports_section != &*(pe.get_image_sections().end() - 1) && 
		(imports_section.empty() || pe_utils::align_up(imports_section.get_size_of_raw_data(), pe.get_file_alignment()) < needed_size + directory_pos))
		throw pe_exception("Insufficient space for bound import directory", pe_exception::insufficient_space);

	std::string& raw_data = imports_section.get_raw_data();

	//This will be done only if imports_section is the last section of image or for section with unaligned raw length of data
	if(raw_data.length() < needed_size + directory_pos)
		raw_data.resize(needed_size + directory_pos); //Expand section raw data
	
	uint32_t current_pos_for_structures = directory_pos;
	uint32_t current_pos_for_strings = current_pos_for_structures + needed_size - needed_size_for_strings;

	for(bound_import_module_list::const_iterator it = imports.begin(); it != imports.end(); ++it)
	{
		const bound_import& import = *it;
		image_bound_import_descriptor descriptor;
		descriptor.NumberOfModuleForwarderRefs = static_cast<uint16_t>(import.get_module_ref_list().size());
		descriptor.OffsetModuleName = static_cast<uint16_t>(current_pos_for_strings - directory_pos);
		descriptor.TimeDateStamp = import.get_timestamp();

		memcpy(&raw_data[current_pos_for_structures], &descriptor, sizeof(descriptor));
		current_pos_for_structures += sizeof(descriptor);
		
		size_t length = import.get_module_name().length() + 1 /* nullbyte */;
		memcpy(&raw_data[current_pos_for_strings], import.get_module_name().c_str(), length);
		current_pos_for_strings += static_cast<uint32_t>(length);

		const bound_import::ref_list& refs = import.get_module_ref_list();
		for(bound_import::ref_list::const_iterator ref_it = refs.begin(); ref_it != refs.end(); ++ref_it)
		{
			const bound_import_ref& ref = *ref_it;
			image_bound_forwarder_ref ref_descriptor = {0};
			ref_descriptor.OffsetModuleName = static_cast<uint16_t>(current_pos_for_strings - directory_pos);
			ref_descriptor.TimeDateStamp = ref.get_timestamp();

			memcpy(&raw_data[current_pos_for_structures], &ref_descriptor, sizeof(ref_descriptor));
			current_pos_for_structures += sizeof(ref_descriptor);

			length = ref.get_module_name().length() + 1 /* nullbyte */;
			memcpy(&raw_data[current_pos_for_strings], ref.get_module_name().c_str(), length);
			current_pos_for_strings += static_cast<uint32_t>(length);
		}
	}

	//Adjust section raw and virtual sizes
	pe.recalculate_section_sizes(imports_section, auto_strip_last_section);
	
	image_directory ret(pe.rva_from_section_offset(imports_section, directory_pos), needed_size);

	//If auto-rewrite of PE headers is required
	if(save_to_pe_header)
	{
		pe.set_directory_rva(image_directory_entry_bound_import, ret.get_rva());
		pe.set_directory_size(image_directory_entry_bound_import, ret.get_size());
	}

	return ret;
}
}
