/*
 * HeuristicHarness.java - This file is part of the Jakstab project.
 * Copyright 2007-2015 Johannes Kinder <jk@jakstab.org>
 *
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.
 *
 * This code 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
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 *
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, see <http://www.gnu.org/licenses/>.
 */
package org.jakstab.loader;

import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;

import org.jakstab.Program;
import org.jakstab.asm.AbsoluteAddress;
import org.jakstab.loader.pe.AbstractCOFFModule;
import org.jakstab.cfa.RTLLabel;
import org.jakstab.rtl.expressions.ExpressionFactory;
import org.jakstab.rtl.expressions.RTLExpression;
import org.jakstab.rtl.expressions.RTLVariable;
import org.jakstab.rtl.statements.*;
import org.jakstab.util.Logger;

public class HeuristicHarness implements Harness {

	private static final Logger logger = Logger.getLogger(HeuristicHarness.class);
	
	private static int CALL_INSTR_DISTANCE = 1;
	
	private static byte[][] procedureHeads = new byte[][] {
		// mov edi, edi; push ebp; mov ebp, esp;
		{ -0x75, -0x01, 0x55, -0x75, -0x14},
		// push ebp; mov ebp, esp;
		{ 0x55, -0x75, -0x14 }
	};
	
	private AbsoluteAddress lastAddress;

	private RTLVariable esp = Program.getProgram().getArchitecture().stackPointer();
	
	private List<AbsoluteAddress> entryPoints;
	
	public HeuristicHarness() {
		entryPoints = new LinkedList<AbsoluteAddress>();
		
		Program program = Program.getProgram();
		
		if (program.getMainModule() instanceof AbstractCOFFModule) {
		byte[] data = ((AbstractCOFFModule)program.getMainModule()).getByteArray();
		for (int filePtr=0; filePtr<data.length; filePtr++) {
			patternLoop: for (int patternIdx = 0; patternIdx < procedureHeads.length; patternIdx++) {
				for (int i = 0; i < procedureHeads[patternIdx].length; i++) {
					if (data[filePtr + i] != procedureHeads[patternIdx][i])
						continue patternLoop;
				}
				// Pattern matched!
				AbsoluteAddress newEntryPoint = program.getMainModule().getVirtualAddress(filePtr);
				entryPoints.add(newEntryPoint);
				logger.verbose("Found possible procedure entry at " + newEntryPoint); 
				filePtr += procedureHeads[patternIdx].length;
				break;
			}
		}
		}
	}
	
	@Override
	public void install(Program program) {

		StatementSequence seq = new StatementSequence();
		seq.addLast(new RTLVariableAssignment(1, ExpressionFactory.createVariable("%DF", 1), ExpressionFactory.FALSE));

		AbsoluteAddress currentAddress = prologueAddress;
		AbsoluteAddress fallthroughAddress = new AbsoluteAddress(currentAddress.getValue() + CALL_INSTR_DISTANCE);
		
		// Call the entry point of the executable
		push32(seq, ExpressionFactory.createNumber(fallthroughAddress.getValue(), 32));
		seq.addLast(new RTLGoto(ExpressionFactory.createNumber(program.getStart().getAddress().getValue(), 32), RTLGoto.Type.CALL));
		putSequence(program, seq, currentAddress);
		program.setEntryAddress(currentAddress);
		
		// Now call all procedures that were heuristically detected

		for (Iterator<AbsoluteAddress> iter = entryPoints.iterator(); iter.hasNext();) {
			AbsoluteAddress entryPoint = iter.next();
			currentAddress = fallthroughAddress;
			fallthroughAddress = iter.hasNext() ? new AbsoluteAddress(currentAddress.getValue() + 1) : prologueAddress;
			seq = new StatementSequence();

			for (RTLVariable v : program.getArchitecture().getRegisters()) {
				if (!v.equals(esp))
					clearReg(seq, v);
			}
			push32(seq, ExpressionFactory.createNumber(fallthroughAddress.getValue(), 32));
			seq.addLast(new RTLGoto(ExpressionFactory.createNumber(entryPoint.getValue(), 32), RTLGoto.Type.CALL));
			putSequence(program, seq, currentAddress);
		}
		
		lastAddress = currentAddress;

		// epilogue with halt statement
		seq = new StatementSequence();
		//seq.addLast(new RTLSkip());
		seq.addLast(new RTLHalt());
		putSequence(program, seq, epilogueAddress);
	}
	
	private void push32(StatementSequence seq, RTLExpression value) {
		seq.addLast(new RTLVariableAssignment(esp.getBitWidth(), esp, 
				ExpressionFactory.createPlus(esp, ExpressionFactory.createNumber(-4, esp.getBitWidth()))
		));
		if (value != null) {
			seq.addLast(new RTLMemoryAssignment(ExpressionFactory.createMemoryLocation(esp, 32), value));
		}
	}
	
	private void clearReg(StatementSequence seq, RTLVariable v) {
		seq.addLast(new RTLVariableAssignment(v.getBitWidth(), v, ExpressionFactory.nondet(v.getBitWidth())));
	}
	
	private void putSequence(Program program, StatementSequence seq, AbsoluteAddress address) {
		int rtlId = 0;
		for (RTLStatement stmt : seq) {
			stmt.setLabel(address, rtlId++);
			stmt.setNextLabel(new RTLLabel(address, rtlId));
		}
		seq.getLast().setNextLabel(null);

		// add stub statements to program
		for (RTLStatement s : seq)
			program.putStatement(s);
	}

	@Override
	public boolean contains(AbsoluteAddress a) {
		return a.getValue() >= PROLOGUE_BASE && a.getValue() <= lastAddress.getValue();
	}

	@Override
	public AbsoluteAddress getFallthroughAddress(AbsoluteAddress a) {
		if (a.getValue() >= PROLOGUE_BASE && a.getValue() < lastAddress.getValue())
			return new AbsoluteAddress(a.getValue() + CALL_INSTR_DISTANCE);
		else if (a.equals(lastAddress))
			return epilogueAddress;
		else 
			return null;
	}

}
