/*
Copyright (C) 2006 Evan Teran
                   eteran@alum.rit.edu

This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
*/

#include "MemoryRegions.h"
#include "SymbolManager.h"
#include "DebuggerCoreInterface.h"
#include "Debugger.h"
#include <sstream>
#include <iostream>
#include <fstream>
#include <string>
#include <asm/unistd.h>
#include <sys/mman.h>
#include <QApplication>

const unsigned int MemoryRegions::Region::READ	= PROT_READ;
const unsigned int MemoryRegions::Region::WRITE	= PROT_WRITE;
const unsigned int MemoryRegions::Region::EXEC	= PROT_EXEC;

struct BackupInfo {
	char 						*buffer;
	std::size_t					size;
	BaseTypes::address_t		tempAddress;
	uint32_t					perms;
	State						state;
	DebugEventHandlerInterface	*eventhandler;
};

//------------------------------------------------------------------------------
// Name: MemoryRegions(BaseTypes::pid_t pid)
// Desc: 
//------------------------------------------------------------------------------
MemoryRegions::MemoryRegions(BaseTypes::pid_t pid) : m_PID(pid) {
	sync();
}

//------------------------------------------------------------------------------
// Name: sync()
// Desc: 
// Note: platform specific code here
//------------------------------------------------------------------------------
void MemoryRegions::sync() {

	m_Regions.clear();

	if(m_PID != 0) {
		QString mapFile(QString().sprintf("/proc/%d/maps", m_PID));
		std::ifstream file(qPrintable(mapFile));
		if(file) {
			std::string line;

			while(std::getline(file, line)) {
				std::stringstream ss(line);
				std::string perms;

				Region region;

				if(ss >> std::hex >> region.start) {
					ss.ignore(1);
					if(ss >> std::hex >> region.end >> perms) {

						std::string temp[2];
						std::string name;
						if(ss >> region.base >> temp[0] >> temp[1]) {
							ss >> name;

							region.permissions = 0;
							region.permissions |= (perms[0] == 'r') ? Region::READ	: 0;
							region.permissions |= (perms[1] == 'w') ? Region::WRITE	: 0;
							region.permissions |= (perms[2] == 'x') ? Region::EXEC	: 0;
							region.name = name.c_str();

							m_Regions.push_back(region);

							// TODO: make this happen less often!
							if(region.name != "") {
								if(region.base == 0x00000000) {
									if(region.executable()) {
										if(Debugger::symbolManager != 0) {
											Debugger::symbolManager->loadSymbolFile(region.name, region.start);
										}
									}
								}
							}
						}
					}
				}
			}
		}
	}
}

//------------------------------------------------------------------------------
// Name: operator==(const Region &o)
// Desc: 
//------------------------------------------------------------------------------
bool MemoryRegions::Region::operator==(const Region &o) {
	return
		start == o.start && 
		end == o.end && 
		permissions == o.permissions && 
		name == o.name &&
		base == o.base;
}

//------------------------------------------------------------------------------
// Name: handleEvent(DebugEvent event)
// Desc: 
//------------------------------------------------------------------------------
void MemoryRegions::Region::handleEvent(DebugEvent event) {
	(void)event;
	
	// TODO: check that the event was caused by a hlt in our shellcode
	
	if(backupInfo != 0) {
	
		// restore the original code and register state
		Debugger::debuggerBase->writeBytes(backupInfo->tempAddress, backupInfo->buffer, backupInfo->size);
		Debugger::debuggerBase->setState(backupInfo->state);

		// update permissions mask
		permissions = backupInfo->perms;

		Debugger::debugEventHandler = backupInfo->eventhandler;
		
		if(backupInfo->buffer != 0) {
			delete [] backupInfo->buffer;
			backupInfo->buffer = 0;
		}
		
		delete backupInfo;
		backupInfo = 0;
	}
	lock = 0;
}

//------------------------------------------------------------------------------
// Name: ~Region()
// Desc: 
//------------------------------------------------------------------------------
MemoryRegions::Region::~Region() {
	if(backupInfo != 0) {
		if(backupInfo->buffer != 0) {
			delete [] backupInfo->buffer;
		}
		delete backupInfo;
	}
}


//------------------------------------------------------------------------------
// Name: setPermissions(bool read, bool write, bool execute)
// Desc: attempts to change the permissions of a given region by injecting
//       shellcode into the debugged process, and executing a mprotect syscall
//       we probably need to ensure that the debug event server thread is not
//       waiting for an event at this moment because we need to catch the "hlt"
//       instruction we injected in order to restore the original code
//       we also probably need this code to work perfectly in order to implement
//       memory access breakpoints
// Note: this code is experimental and clearly x86 Linux only!
//------------------------------------------------------------------------------
void MemoryRegions::Region::setPermissions(bool read, bool write, bool execute, BaseTypes::address_t tempAddress) {

	uint32_t perms = 0;
	const uint32_t len = size();
	const uint32_t addr = start;
	
	if(read)	perms |= READ;
	if(write)	perms |= WRITE;
	if(execute)	perms |= EXEC;
	
	#define LEN_OFFSET	1
	#define ADDR_OFFSET	6
	#define PERM_OFFSET	11
	
	char shellcode[] = {
		"\xb9\x00\x10\x00\x00"	// mov    $0x00001000,%ecx
		"\xbb\x00\x40\x00\x00"	// mov    $0x00004000,%ebx
		"\xba\x07\x00\x00\x00"	// mov    $0x00000007,%edx
		"\xb8\x7d\x00\x00\x00"	// mov    $0x7d,%eax
		"\xcd\x80"				// int    $0x80
		"\xf4"					// hlt
	};
			
	// put the data in the right part of the shellcode
	std::memcpy(&shellcode[LEN_OFFSET], &len, sizeof(len));
	std::memcpy(&shellcode[ADDR_OFFSET], &addr, sizeof(addr));
	std::memcpy(&shellcode[PERM_OFFSET], &perms, sizeof(perms));
	
	
	backupInfo = new BackupInfo;
	backupInfo->buffer		= new char[sizeof(shellcode)];
	backupInfo->size		= sizeof(shellcode);
	backupInfo->tempAddress	= tempAddress;
	backupInfo->perms		= perms;

	
	// get the current register state of the process
	Debugger::debuggerBase->getState(backupInfo->state);
	
	// backup the bytes currently at our temp address
	Debugger::debuggerBase->readBytes(tempAddress, backupInfo->buffer, backupInfo->size);
	
	// write out our shellcode
	Debugger::debuggerBase->writeBytes(tempAddress, shellcode, sizeof(shellcode));
	
	// set eip in the state to out temp address
	const uint32_t orig_eip = backupInfo->state.eip;
	backupInfo->state.eip = tempAddress;
	Debugger::debuggerBase->setState(backupInfo->state);
	backupInfo->state.eip = orig_eip;
	
	lock = 1;
	
	backupInfo->eventhandler = Debugger::debugEventHandler;
	Debugger::debugEventHandler = this;
	
	// run and wait for the "crash" caused by the hlt instruction
	// should be a SIGSEG on Linux
	Debugger::debuggerBase->resume(0);
	
	// this is a crappy solution but it works good enough for now
	while(lock) {
		QApplication::processEvents();
	}
}

//------------------------------------------------------------------------------
// Name: setPermissions(bool read, bool write, bool execute)
// Desc: 
//------------------------------------------------------------------------------
void MemoryRegions::Region::setPermissions(bool read, bool write, bool execute) {
	const BaseTypes::address_t tempAddress = 0x08048000;
	setPermissions(read, write, execute, tempAddress);
}

//------------------------------------------------------------------------------
// Name: findBaseAddress(const QString &name, bool *ok) const
// Desc: 
//------------------------------------------------------------------------------
BaseTypes::address_t MemoryRegions::findBaseAddress(const QString &name, bool *ok) const {
	
	if(ok != 0) {
		*ok = false;
	}
	
	QVector<Region> r = regions();
	for(QVector<Region>::const_iterator i = r.begin(); i != r.end(); ++i) {
		if(i->base == 0x00000000) {
			if(i->name.endsWith(name)) {
				if(ok != 0) {
					*ok = true;
				}
				return i->start;
			}
		}
	}
	return 0x00000000;
}

//------------------------------------------------------------------------------
// Name: findRegion(BaseTypes::address_t address) const
// Desc: 
//------------------------------------------------------------------------------
bool MemoryRegions::findRegion(BaseTypes::address_t address) const {
	QVector<Region> r = regions();
	for(QVector<Region>::const_iterator i = r.begin(); i != r.end(); ++i) {
		if(address >= i->start && address < i->end) {
			return true;
		}
	}
	return false;
}

//------------------------------------------------------------------------------
// Name: findRegion(BaseTypes::address_t address, MemoryRegions::Region &region) const
// Desc: 
//------------------------------------------------------------------------------
bool MemoryRegions::findRegion(BaseTypes::address_t address, MemoryRegions::Region &region) const {

	QVector<Region> r = regions();
	for(QVector<Region>::const_iterator i = r.begin(); i != r.end(); ++i) {
		if(address >= i->start && address < i->end) {
			region = *i;
			return true;
		}
	}
	return false;
}
