/*
JPC: An x86 PC Hardware Emulator for a pure Java Virtual Machine
Copyright (C) 2012-2013 Ian Preston
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License version 2 as published by
the Free Software Foundation.
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.
Details (including contact information) can be found at:
jpc.sourceforge.net
or the developer website
sourceforge.net/projects/jpc/
End of licence header
*/
package tools;
import java.io.*;
import java.util.*;
public class OracleFuzzer
{
public static final Random r = new Random(System.currentTimeMillis());
public static String altJar = "JPCApplication2.jar";
public static final int RM = 1;
public static final int PM = 2;
public static final int VM = 3;
public static final boolean compareStack = true;
public static int codeEIP = 0x2000;
private static String[] pcargs = new String[]
{"-max-block-size", "1", "-boot", "hda", "-hda", "linux.img", "-ram", "4", "-bios", "/resources/bios/fuzzerBIOS"};
public static byte[] real_mode_idt = new byte[0x120];
static
{
int eipBase = 0x100;
for (int i=0; i < 20; i++)
{
real_mode_idt[4*i] = (byte) (eipBase + i);
real_mode_idt[4*i+1] = (byte) ((eipBase + i) >> 8);
// leave cs 0
// put nop at each handler (the oracle sometimes executes this after an exception)
real_mode_idt[0x100+i] = (byte) 0x90;
}
}
public static byte[] protected_mode_idt = new byte[0x120];
static
{
int eipBase = 0x100;
for (int i=0; i < 20; i++)
{
long codeDescriptor = EmulatorControl.getInterruptGateDescriptor(7, eipBase + i);
protected_mode_idt[8*i] = (byte) codeDescriptor;
protected_mode_idt[8*i+1] = (byte) (codeDescriptor >> 8);
protected_mode_idt[8*i+2] = (byte) (codeDescriptor >> 16);
protected_mode_idt[8*i+3] = (byte) (codeDescriptor >> 24);
protected_mode_idt[8*i+4] = (byte) (codeDescriptor >> 32);
protected_mode_idt[8*i+5] = (byte) (codeDescriptor >> 40);
protected_mode_idt[8*i+6] = (byte) (codeDescriptor >> 48);
protected_mode_idt[8*i+7] = (byte) (codeDescriptor >> 56);
// leave cs 0
// put nop at each handler (the oracle sometimes executes this after an exception)
protected_mode_idt[0x100+i] = (byte) 0x90;
}
}
public static void main(String[] args) throws IOException
{
if (args.length == 0)
{
System.out.printf("Usage: java -jar Tools.jar -fuzz $type\n");
System.out.printf(" type = rm, pm, -tests $file\n");
System.out.printf(" N.B. This uses JPCApplication2.jar\n");
return;
}
if (args[0].equals("-tests"))
{
testFromFile(args[1], codeEIP);
return;
} else if (args[0].equals("rm"))
{
// test Real mode
fuzzRealMode(codeEIP);
} else if (args[0].equals("pm"))
{
// test Protected mode
fuzzProtectedMode(codeEIP);
} else
{
System.out.printf("Usage: java -jar Tools.jar -fuzz $type\n");
System.out.printf(" type = rm, pm, -tests $file\n");
}
// test Real mode with 4G segment limits after return from Protected mode
// test Virtual 8086 mode
}
public static int[] getCanonicalVM86ModeInput(int codeEIP, boolean random)
{
int[] real = getCanonicalRealModeInput(codeEIP, random);
real[EmulatorControl.EFLAGS_INDEX] |= EmulatorControl.VM86_FLAG;
real[EmulatorControl.CRO_INDEX] |= 1; // set PM
return real;
}
public static int[] getCanonicalRealModeInput(int codeEIP, boolean random)
{
int[] inputState = new int[EmulatorControl.names.length];
if (random)
{
for (int i=0; i < 8; i++)
inputState[i] = r.nextInt();
inputState[8] = codeEIP;
inputState[9] = (r.nextBoolean()? 0 : EmulatorControl.OF_FLAG) |
(r.nextBoolean()? 0 : EmulatorControl.SF_FLAG) |
(r.nextBoolean()? 0 : EmulatorControl.ZF_FLAG) |
(r.nextBoolean()? 0 : EmulatorControl.AF_FLAG) |
(r.nextBoolean()? 0 : EmulatorControl.PF_FLAG) |
(r.nextBoolean()? 0 : EmulatorControl.CF_FLAG);
for (int i=10; i < 16; i++)
inputState[i] = r.nextInt() & 0xffff;
inputState[30] = inputState[10] << 4; // es
inputState[31] = inputState[11] << 4; // cs
inputState[32] = inputState[12] << 4; // ds
inputState[33] = inputState[13] << 4;// ss
inputState[34] = inputState[14] << 4;// fs
inputState[35] = inputState[15] << 4;// gs
for (int i=0; i < 6; i++)
inputState[17 + i] = 0xffff;
for (int i=0; i < 3; i++)
inputState[25 + i] = r.nextInt();
inputState[11] = 0x0; inputState[31] = inputState[11] << 4; // cs
}
else
{
inputState[0] = 0x12345678;
inputState[1] = 0x9ABCDEF0;
inputState[2] = 0x192A3B4C;
inputState[3] = 0x5D6E7F80;
inputState[4] = 0x800; // esp
inputState[5] = 0x15263748;
inputState[6] = 0x9DAEBFC0;
inputState[7] = 0x15263748;
inputState[8] = codeEIP; // eip
inputState[9] = 0x846; // eflags
inputState[10] = 0x3000; inputState[30] = inputState[10] << 4; // es
inputState[11] = 0x0; inputState[31] = inputState[11] << 4; // cs
inputState[12] = 0x4000; inputState[32] = inputState[12] << 4; // ds
inputState[13] = 0x5000; inputState[33] = inputState[13] << 4;// ss
inputState[14] = 0x6000; inputState[34] = inputState[14] << 4;// fs
inputState[15] = 0x7000; inputState[35] = inputState[15] << 4;// gs
// RM segment limits (not used)
for (int i=0; i < 6; i++)
inputState[17 + i] = 0xffff;
inputState[25] = inputState[27] = inputState[29] = 0xffff;
}
inputState[36] = 0x60000010; // CR0
// FPU
long one = getDoubleBits(1.0);
long two = getDoubleBits(2.0);
long four = getDoubleBits(4.0);
long eight = getDoubleBits(8.0);
long sixteen = getDoubleBits(16.0);
long half = getDoubleBits(0.5);
long hundred = getDoubleBits(100.0);
long thousand = getDoubleBits(1000.0);
inputState[37] = (int) (one >> 32); // ST0H
inputState[38] = (int) one; // ST0L
inputState[39] = (int) (two >> 32); // ST1H
inputState[40] = (int) two; // ST1L
inputState[41] = (int) (four >> 32); // ST2H
inputState[42] = (int) four; // ST2L
inputState[43] = (int) (eight >> 32); // ST3H
inputState[44] = (int) eight; // ST3L
inputState[45] = (int) (sixteen >> 32); // ST4H
inputState[46] = (int) sixteen; // ST4L
inputState[47] = (int) (half >> 32); // ST5H
inputState[48] = (int) half; // ST5L
inputState[49] = (int) (hundred >> 32); // ST6H
inputState[50] = (int) hundred; // ST6L
inputState[51] = (int) (thousand >> 32); // ST7H
inputState[52] = (int) thousand; // ST7L
return inputState;
}
public static void fuzzRealMode(int codeEIP) throws IOException
{
int[] inputState = getCanonicalRealModeInput(codeEIP, true);
byte[][] preficesA = new byte[][]{new byte[0]};
byte[][] preficesB = new byte[][]{new byte[]{0x66}};
byte[][] preficesC = new byte[][]{new byte[]{0x67}};
byte[][] preficesD = new byte[][]{new byte[]{0x0F}};
new Thread(new FuzzThread(codeEIP, preficesA, inputState, RM, false, "tests/RMtest-A")).start();
new Thread(new FuzzThread(codeEIP, preficesB, inputState, RM, false, "tests/RMtest-B")).start();
new Thread(new FuzzThread(codeEIP, preficesC, inputState, RM, false, "tests/RMtest-C")).start();
new Thread(new FuzzThread(codeEIP, preficesD, inputState, RM, false, "tests/RMtest-D")).start();
}
public static int[] getCanonicalProtectedModeInput(int codeEIP, boolean isCS32Bit, boolean random)
{
int[] inputState = new int[EmulatorControl.names.length];
if (random)
{
for (int i=0; i < 8; i++)
inputState[i] = r.nextInt();
inputState[8] = codeEIP;
inputState[9] = (r.nextBoolean()? 0 : EmulatorControl.OF_FLAG) |
(r.nextBoolean()? 0 : EmulatorControl.SF_FLAG) |
(r.nextBoolean()? 0 : EmulatorControl.ZF_FLAG) |
(r.nextBoolean()? 0 : EmulatorControl.AF_FLAG) |
(r.nextBoolean()? 0 : EmulatorControl.PF_FLAG) |
(r.nextBoolean()? 0 : EmulatorControl.CF_FLAG);
for (int i=30; i < 36; i++)
inputState[i] = r.nextInt() & 0xffff;
for (int i=0; i < 6; i++)
inputState[17 + i] = r.nextInt();
inputState[23] = isCS32Bit ? 1 : 0;
for (int i=0; i < 3; i++)
inputState[25 + i] = r.nextInt();
inputState[11] = 0x0; inputState[31] = inputState[11] << 4; // cs
}
else
{
inputState[0] = 0x12345678;
inputState[1] = 0x9ABCDEF0;
inputState[2] = 0x192A3B4C;
inputState[3] = 0x5D6E7F80;
inputState[4] = 0x800; // esp
inputState[5] = 0x15263748;
inputState[6] = 0x9DAEBFC0;
inputState[7] = 0x15263748;
inputState[8] = codeEIP; // eip
inputState[9] = 0x846; // eflags
inputState[10] = 0x3000; inputState[30] = inputState[10] << 4; // es
inputState[11] = 0x0; inputState[31] = inputState[11] << 4; // cs
inputState[12] = 0x4000; inputState[32] = inputState[12] << 4; // ds
inputState[13] = 0x5000; inputState[33] = inputState[13] << 4;// ss
inputState[14] = 0x6000; inputState[34] = inputState[14] << 4;// fs
inputState[15] = 0x7000; inputState[35] = inputState[15] << 4;// gs
for (int i=0; i < 6; i++)
inputState[17 + i] = 0xffffff;
inputState[23] = isCS32Bit ? 1 : 0;
inputState[25] = inputState[27] = inputState[29] = 0xffff;
}
inputState[36] = 0x60000011; // CR0: PM, no paging
inputState[EmulatorControl.IDT_BASE_INDEX] = 0x1000;
// FPU
long one = getDoubleBits(1.0);
long two = getDoubleBits(2.0);
long four = getDoubleBits(4.0);
long eight = getDoubleBits(8.0);
long sixteen = getDoubleBits(16.0);
long half = getDoubleBits(0.5);
long hundred = getDoubleBits(100.0);
long thousand = getDoubleBits(1000.0);
inputState[37] = (int) (one >> 32); // ST0H
inputState[38] = (int) one; // ST0L
inputState[39] = (int) (two >> 32); // ST1H
inputState[40] = (int) two; // ST1L
inputState[41] = (int) (four >> 32); // ST2H
inputState[42] = (int) four; // ST2L
inputState[43] = (int) (eight >> 32); // ST3H
inputState[44] = (int) eight; // ST3L
inputState[45] = (int) (sixteen >> 32); // ST4H
inputState[46] = (int) sixteen; // ST4L
inputState[47] = (int) (half >> 32); // ST5H
inputState[48] = (int) half; // ST5L
inputState[49] = (int) (hundred >> 32); // ST6H
inputState[50] = (int) hundred; // ST6L
inputState[51] = (int) (thousand >> 32); // ST7H
inputState[52] = (int) thousand; // ST7L
return inputState;
}
public static void fuzzProtectedMode(int codeEIP) throws IOException
{
int[] inputState = getCanonicalProtectedModeInput(codeEIP, false, true);
byte[][] preficesA = new byte[][]{new byte[0]};
byte[][] preficesB = new byte[][]{new byte[]{0x66}};
byte[][] preficesC = new byte[][]{new byte[]{0x67}};
byte[][] preficesD = new byte[][]{new byte[]{0x0F}};
new Thread(new FuzzThread(codeEIP, preficesA, inputState, PM, false, "tests/PMtest-A")).start();
new Thread(new FuzzThread(codeEIP, preficesB, inputState, PM, false, "tests/PMtest-B")).start();
new Thread(new FuzzThread(codeEIP, preficesC, inputState, PM, false, "tests/PMtest-C")).start();
new Thread(new FuzzThread(codeEIP, preficesD, inputState, PM, false, "tests/PMtest-D")).start();
}
public static class FuzzThread implements Runnable
{
final byte[][] prefices;
final int codeEIP, mode;
final int[] inputState;
final String output;
final boolean is32Bit;
FuzzThread(int codeEIP, byte[][] prefices, int[] inputState, int mode, boolean is32Bit, String output)
{
this.prefices = prefices;
this.codeEIP = codeEIP;
this.inputState = inputState;
this.mode = mode;
this.output = output;
this.is32Bit = is32Bit;
}
public void run()
{
try {
testRange(codeEIP, prefices, inputState, mode, is32Bit, new BufferedWriter(new FileWriter(output+".cases")),
new BufferedWriter(new FileWriter(output+ ".log")));
} catch (IOException e)
{
e.printStackTrace();
}
}
}
public static void testFromFile(String file, int currentCSEIP) throws IOException
{
BufferedWriter cases = new BufferedWriter(new FileWriter(file + ".rerun.cases"));
BufferedWriter log = new BufferedWriter(new FileWriter(file + ".rerun.log"));
BufferedReader r = new BufferedReader(new FileReader(file));
String line = r.readLine();
for (;line != null; line = r.readLine())
{
String[] props = line.split(" ");
boolean is32Bit = false;
int mode = Integer.parseInt(props[0], 16);
int x86 = Integer.parseInt(props[1], 16);
int flagMask = (int) Long.parseLong(props[2], 16);
if (props.length > 3)
is32Bit = props[3].equals("32");
line = r.readLine();
String[] raw = line.trim().split(" ");
byte[] code = new byte[raw.length];
for (int i=0; i < code.length; i++)
code[i] = (byte) Integer.parseInt(raw[i], 16);
String[] rawState = r.readLine().trim().split(" ");
int[] inputState = new int[rawState.length];
for (int i=0; i < inputState.length; i++)
inputState[i] = (int) Long.parseLong(rawState[i], 16);
// read input memory values
line = r.readLine();
Map<Integer, byte[]> mem = new HashMap();
while (!line.contains("*****"))
{
int addr = Integer.parseInt(line.substring(0, 8), 16);
String[] hex = line.substring(9).trim().split(" ");
byte[] data = new byte[hex.length];
for (int i=0; i < hex.length; i++)
data[i] = (byte) Integer.parseInt(hex[i], 16);
mem.put(addr, data);
line = r.readLine();
}
testOpcode(currentCSEIP, code, x86, inputState, flagMask, mode, is32Bit, mem, cases, log);
}
}
public static void testRange(int codeEIP, byte[][] prefices, int[] inputState, int mode, boolean is32Bit, BufferedWriter cases, BufferedWriter log) throws IOException
{
EmulatorControl disam = new JPCControl(altJar, pcargs);
Map<Integer, byte[]> mem = new HashMap();
for (int b=0; b < prefices.length; b++)
{
int opcodeIndex = prefices[b].length;
byte[] code = new byte[opcodeIndex + 15];
for (int i=opcodeIndex; i < code.length; i++)
code[i] = (byte)(i-opcodeIndex);
System.arraycopy(prefices[b], 0, code, 0, opcodeIndex);
// 60158 cases
for (int i=0; i < 256; i++)
{
if (i == 0xF4) // don't test halt
continue;
if (i == 0x17) // don't test pop ss
continue;
// don't 'test' segment overrides
if ((i == 0x26) || (i == 0x2e) || (i == 0x36) || (i == 0x3e) || (i == 0x64) || (i == 0x65))
continue;
if (i == 0xf0) // don't test lock
continue;
if ((i == 0xf2) || (i == 0xf3)) // don't rep/repne
continue;
if ((i == 0x66) || (i == 0x67)) // don't test size overrides
continue;
if ((i == 0xe4) || (i == 0xe5) || (i == 0xec) || (i == 0xed)) // don't test in X,Ib
continue;
if ((i == 0xe6) || (i == 0xe7) || (i == 0xee) || (i == 0xef)) // don't test out Ib,X
continue;
code[opcodeIndex] = (byte) i;
if (disam.x86Length(code, is32Bit) == 1 + opcodeIndex)
{
testOpcode(codeEIP, code, 1, inputState, 0xffffffff, mode, is32Bit, mem, cases, log);
continue;
}
for (int j=0; j < 256; j++)
{
if ((j == 0xf4) && (i == 0x17)) // avoid a potential halt after a pop ss which forces a 2nd instruction
continue;
if ((j == 0xf4) && (i == 0xfb)) // avoid a potential halt after an sti which forces a 2nd instruction
continue;
code[opcodeIndex+1] = (byte) j;
testOpcode(codeEIP, code, 1, inputState, 0xffffffff, mode, is32Bit, mem, cases, log);
}
}
}
}
// returns resulting cseip
private static int testOpcode(int currentCSEIP, byte[] code, int x86, int[] inputState, int flagMask, int mode, boolean is32Bit, Map<Integer, byte[]> mem, BufferedWriter cases, BufferedWriter log) throws IOException
{
EmulatorControl disciple = new JPCControl(altJar, pcargs);
EmulatorControl oracle = new Bochs("linux.cfg");
// set cs base to 0
disciple.executeInstruction(); // jmp 0000:2000
oracle.executeInstruction(); // jmp 0000:2000
disciple.setState(inputState, currentCSEIP);
oracle.setState(inputState, currentCSEIP);
disciple.setPhysicalMemory(inputState[8], code);
oracle.setPhysicalMemory(inputState[8], code);
if (mode == RM)
{
disciple.setPhysicalMemory(0, real_mode_idt);
oracle.setPhysicalMemory(0, real_mode_idt);
}
else // PM and VM86 mode
{
disciple.setPhysicalMemory(inputState[EmulatorControl.IDT_BASE_INDEX], protected_mode_idt);
oracle.setPhysicalMemory(inputState[EmulatorControl.IDT_BASE_INDEX], protected_mode_idt);
}
// set memory inputs
for (Integer addr: mem.keySet())
{
disciple.setPhysicalMemory(addr, mem.get(addr));
oracle.setPhysicalMemory(addr, mem.get(addr));
}
try {
for (int i=0; i < x86; i++)
{
disciple.executeInstruction();
oracle.executeInstruction();
// finish repeated string ops
while (oracle.getState()[8] == inputState[8]) // eip must progress
oracle.executeInstruction();
}
} catch (Throwable e)
{
log.append("*****************ERROR****************\n");
log.append(e.getMessage());
log.newLine();
if (e.getCause() != null)
{
StringWriter sw = new StringWriter();
PrintWriter pw = new PrintWriter(sw);
e.getCause().printStackTrace(pw);
log.append(sw.toString());
}
log.write("Code:\n");
for (byte b: code)
log.write(String.format("%02x ", b));
log.newLine();
log.write("Input:\n");
Fuzzer.printState(inputState, log);
log.flush();
disciple.destroy();
oracle.destroy();
return inputState[31];
}
int[] us = disciple.getState();
int[] good = oracle.getState();
boolean sameStack = true;
if (compareStack)
{
int stackTop = good[EmulatorControl.SS_BASE_INDEX];
if ((good[EmulatorControl.SEG_PROP_INDEX] & 2) != 0)
stackTop += good[EmulatorControl.ESP_INDEX];
else
stackTop += good[EmulatorControl.ESP_INDEX] & 0xffff;
int stackPage = stackTop & ~0xfff;
byte[] oracleStack = new byte[4096];
oracle.getLinearPage(stackPage, oracleStack);
byte[] discipleStack = new byte[4096];
disciple.getLinearPage(stackPage, discipleStack);
sameStack = Fuzzer.comparePage(stackPage, discipleStack, oracleStack, log);
}
// need to compare memory if there are memory inputs
if (!sameState(us, good, flagMask) || !sameStack)
{
printCase(code, x86, mode == RM ? false : is32Bit, disciple, inputState, us, good, flagMask, log);
if (cases != null)
{
cases.append(String.format("%08x %08x %08x %s\n", mode, x86, flagMask, is32Bit ? "32" : "16"));
for (int i=0; i < code.length; i++)
cases.append(String.format("%02x ", code[i]));
cases.append("\n");
for (int i=0; i < inputState.length; i++)
cases.append(String.format("%08x ", inputState[i]));
cases.append("\n*****\n");
cases.flush();
}
}
disciple.destroy();
oracle.destroy();
return good[31]+good[8];
}
public static long getDoubleBits(double x)
{
return Double.doubleToLongBits(x);
}
private static boolean sameState(int[] disciple, int[] oracle, int flagMask)
{
for (int i=0; i < disciple.length; i++)
{
if (i == 9)
{
if ((disciple[i] & flagMask) != (oracle[i] & flagMask))
return false;
}
else if (i == 16) // ignore ticks
continue;
else if (disciple[i] != oracle[i])
return false;
}
return true;
}
private static Set<Integer> differentRegs(int[] disciple, int[] oracle, int flagMask)
{
Set<Integer> diff = new TreeSet();
for (int i=0; i < disciple.length; i++)
{
if (i == 9)
{
if ((disciple[i] & flagMask) != (oracle[i] & flagMask))
diff.add(i);
}
else if (i == 16) // ignore ticks
continue;
else if (disciple[i] != oracle[i])
diff.add(i);
}
return diff;
}
private static void printCase(byte[] code, int x86, boolean is32Bit, EmulatorControl disciple, int[] input, int[] discipleState, int[] oracle, int flagMask, BufferedWriter log) throws IOException
{
log.write("***************Test case error************************\n");
log.write("Code:\n");
for (byte b: code)
log.write(String.format("%02x ", b));
log.newLine();
log.write(disciple.disam(code, x86, is32Bit));
log.write("\nDifferences:\n");
Set<Integer> diff = differentRegs(discipleState, oracle, flagMask);
for (Integer index: diff)
log.write(String.format("Difference: %s %08x - %08x : ^ %08x\n", EmulatorControl.names[index], discipleState[index], oracle[index], discipleState[index] ^ oracle[index]));
log.write("Input:\n");
Fuzzer.printState(input, log);
log.write("Disciple:\n");
Fuzzer.printState(discipleState, log);
log.write("Oracle:\n");
Fuzzer.printState(oracle, log);
log.flush();
}
}