aa430587cc
This brings the Java API up to par with Python feature-wise and substantially simplifies the hook implementation, enabling proper bounds-checked hooks. The rewrite strives for compatibility with the previous API, but there are some breaking changes. It is possible to push closer to full backwards compatibility if required, at the cost of reintroducing some of the suboptimal designs. Here are the main points of breakage: - ReadHook and WriteHook are gone, replaced simply by MemHook. Hooking valid memory accesses now requires a type parameter. This enables fetch and read-after hooks with a unified API and a single callback object. - mem_read now takes an int, not a long. We are unable to allocate more than 2GB in a single request anyway (Java limitation). - Instruction hooks now require specifying the instruction explicitly, instead of guessing based on the hook type. This is necessary to distinguish sysenter/syscall and ARM64 mrs/msr/sys/sysl, without excessively bloating the library with redundant hook types. Bounds must also be specified, to support bounds-checked instruction hooks. - Reading object-type registers (any register larger than 64 bits, or registers with special formats) requires a second argument to reg_read. This allows us to provide a fast reg_read that returns a long for the common cases, while still supporting a more general reg_read for other registers. - mem_map_ptr is rewritten to take a *direct* java.nio.Buffer, which enables many more use cases than a simple byte array, and improves performance (a byte array cannot really be used as a mapped buffer without GC-pinning it, which hurts the GC performance). - Context handling API is redesigned to be safer and more object-oriented. A lot of bugs are fixed with this implementation: - Unicorn instances can be properly garbage-collected, instead of hanging around forever in the Unicorn.unicorns table. - Hooks no longer fire outside of their bounds (#1164), and in fact, hook bounds are properly respected (previously, all hooks were just registered globally to all addresses). - Hooks are substantially faster, as they are now dispatched directly via a single method call rather than being indirected through invokeCallbacks. - Loading vector registers works now, rather than crashing the VM (#1539). Several features are now enabled in the Java implementation: - All of the current ctl_* calls are implemented. - mmio_map is implemented. - New virtual TLB mode is implemented. - reading/writing Context registers is implemented. - New hook types are added: TcgOpcodeHook, EdgeGeneratedHook, InvalidInstructionHook, TlbFillHook, and the instruction hooks Arm64SysHook, CpuidHook. - All known special registers are supported.
185 lines
7.0 KiB
Java
185 lines
7.0 KiB
Java
package tests;
|
|
|
|
import static org.junit.Assert.assertEquals;
|
|
|
|
import java.nio.ByteBuffer;
|
|
import java.nio.ByteOrder;
|
|
|
|
import org.junit.Test;
|
|
|
|
import unicorn.MemRegion;
|
|
import unicorn.TlbFillHook;
|
|
import unicorn.Unicorn;
|
|
import unicorn.X86_Float80;
|
|
|
|
public class FunctionalityTests {
|
|
@Test
|
|
public void testMemRegions() {
|
|
Unicorn uc = new Unicorn(Unicorn.UC_ARCH_ARM64, Unicorn.UC_MODE_ARM);
|
|
long ADDR1 = 0x10000;
|
|
long ADDR2 = 0xdeadbeeffeed1000L;
|
|
uc.mem_map(ADDR1, 2 * 1024 * 1024, Unicorn.UC_PROT_ALL);
|
|
uc.mem_map(ADDR2, 4096, Unicorn.UC_PROT_READ);
|
|
MemRegion[] arr = uc.mem_regions();
|
|
assertEquals("two memory regions", 2, arr.length);
|
|
assertEquals("begin", ADDR1, arr[0].begin);
|
|
assertEquals("end", ADDR1 + 2 * 1024 * 1024 - 1, arr[0].end);
|
|
assertEquals("perms", Unicorn.UC_PROT_ALL, arr[0].perms);
|
|
assertEquals("begin", ADDR2, arr[1].begin);
|
|
assertEquals("end", ADDR2 + 4096 - 1, arr[1].end);
|
|
assertEquals("perms", Unicorn.UC_PROT_READ, arr[1].perms);
|
|
uc.close();
|
|
}
|
|
|
|
@Test
|
|
public void testContext() {
|
|
Unicorn uc = new Unicorn(Unicorn.UC_ARCH_ARM64, Unicorn.UC_MODE_ARM);
|
|
uc.reg_write(Unicorn.UC_ARM64_REG_X0, 0xdeadbeef);
|
|
Unicorn.Context ctx = uc.context_save();
|
|
uc.reg_write(Unicorn.UC_ARM64_REG_X0, 0xfeedface);
|
|
assertEquals("X0 changed", 0xfeedface,
|
|
uc.reg_read(Unicorn.UC_ARM64_REG_X0));
|
|
uc.context_restore(ctx);
|
|
assertEquals("X0 restored", 0xdeadbeef,
|
|
uc.reg_read(Unicorn.UC_ARM64_REG_X0));
|
|
uc.reg_write(Unicorn.UC_ARM64_REG_X0, 0xfee1dead);
|
|
uc.context_update(ctx);
|
|
assertEquals("X0 changed", 0xfee1dead,
|
|
uc.reg_read(Unicorn.UC_ARM64_REG_X0));
|
|
uc.reg_write(Unicorn.UC_ARM64_REG_X0, 0xdeadbeef);
|
|
assertEquals("X0 changed", 0xdeadbeef,
|
|
uc.reg_read(Unicorn.UC_ARM64_REG_X0));
|
|
uc.context_restore(ctx);
|
|
assertEquals("X0 restored", 0xfee1dead,
|
|
uc.reg_read(Unicorn.UC_ARM64_REG_X0));
|
|
uc.close();
|
|
}
|
|
|
|
@Test
|
|
public void testMmio() {
|
|
// mov ecx, [0xaaaaaaa8]; inc ecx; dec edx; mov [0xaaaaaaa8], ecx; inc ecx; dec edx
|
|
final byte[] X86_CODE32_MEM_READ_WRITE =
|
|
{ -117, 13, -88, -86, -86, -86, 65, 74, -119, 13, -88, -86, -86,
|
|
-86, 65, 74 };
|
|
|
|
long ADDRESS = 0x100000;
|
|
|
|
Unicorn u = new Unicorn(Unicorn.UC_ARCH_X86, Unicorn.UC_MODE_32);
|
|
// map 2MB memory for this emulation
|
|
u.mem_map(ADDRESS, 2 * 1024 * 1024, Unicorn.UC_PROT_ALL);
|
|
|
|
// write machine code to be emulated to memory
|
|
u.mem_write(ADDRESS, X86_CODE32_MEM_READ_WRITE);
|
|
|
|
// initialize machine registers
|
|
u.reg_write(Unicorn.UC_X86_REG_ECX, 0x12345678);
|
|
u.reg_write(Unicorn.UC_X86_REG_EDX, 0x22334455);
|
|
|
|
u.mmio_map(0xaaaaa000L, 0x1000, (uc, offset, size, user_data) -> {
|
|
assertEquals("read offset", 0xaa8, offset);
|
|
assertEquals("read size", 4, size);
|
|
assertEquals("read user_data", "read_data", user_data);
|
|
return 0x44556677;
|
|
}, "read_data", (uc, offset, size, value, user_data) -> {
|
|
assertEquals("write offset", 0xaa8, offset);
|
|
assertEquals("write size", 4, size);
|
|
assertEquals("write value", 0x44556678, value);
|
|
assertEquals("write user_data", "write_data", user_data);
|
|
}, "write_data");
|
|
|
|
u.emu_start(ADDRESS, ADDRESS + X86_CODE32_MEM_READ_WRITE.length, 0, 0);
|
|
|
|
assertEquals("ecx", 0x44556679, u.reg_read(Unicorn.UC_X86_REG_ECX));
|
|
assertEquals("edx", 0x22334453, u.reg_read(Unicorn.UC_X86_REG_EDX));
|
|
|
|
u.close();
|
|
}
|
|
|
|
@Test
|
|
public void testMemMapPtr() {
|
|
ByteBuffer buffer =
|
|
ByteBuffer.allocateDirect(0x1000).order(ByteOrder.LITTLE_ENDIAN);
|
|
final byte[] X86_CODE32_MEM_WRITE =
|
|
{ -119, 13, -86, -86, -86, -86, 65, 74 };
|
|
|
|
long ADDRESS = 0x100000;
|
|
|
|
Unicorn u = new Unicorn(Unicorn.UC_ARCH_X86, Unicorn.UC_MODE_32);
|
|
u.mem_map(ADDRESS, 2 * 1024 * 1024, Unicorn.UC_PROT_ALL);
|
|
u.mem_map_ptr(0xaaaaa000L, buffer, Unicorn.UC_PROT_ALL);
|
|
u.mem_write(ADDRESS, X86_CODE32_MEM_WRITE);
|
|
u.reg_write(Unicorn.UC_X86_REG_ECX, 0x12345678);
|
|
u.emu_start(ADDRESS, ADDRESS + X86_CODE32_MEM_WRITE.length, 0, 0);
|
|
|
|
assertEquals("buffer contents", 0x12345678, buffer.getInt(0xaaa));
|
|
|
|
u.close();
|
|
}
|
|
|
|
@Test
|
|
public void testTlbHook() {
|
|
// mov ecx, [0xaaaaaaa8]
|
|
final byte[] X86_CODE32_MEM_READ = { -117, 13, -88, -86, -86, -86 };
|
|
|
|
long ADDRESS = 0x100000;
|
|
|
|
Unicorn u = new Unicorn(Unicorn.UC_ARCH_X86, Unicorn.UC_MODE_32);
|
|
u.mem_map(ADDRESS, 2 * 1024 * 1024, Unicorn.UC_PROT_ALL);
|
|
u.mem_map(0xbbbbb000L, 0x1000, Unicorn.UC_PROT_READ);
|
|
u.hook_add((TlbFillHook) (uc, address, type, user_data) -> {
|
|
assertEquals("fill hook address", 0xaaaaa000L, address);
|
|
assertEquals("fill hook type", Unicorn.UC_MEM_READ, type);
|
|
assertEquals("fill hook user", "fill_hook", user_data);
|
|
return 0xbbbbb000L | Unicorn.UC_PROT_READ;
|
|
}, 0xaaaaa000L, 0xaaaab000L, "fill_hook");
|
|
u.mem_write(ADDRESS, X86_CODE32_MEM_READ);
|
|
u.mem_write(0xbbbbbaa8L, new byte[] { 1, 2, 3, 4 });
|
|
u.reg_write(Unicorn.UC_X86_REG_ECX, 0x12345678);
|
|
u.ctl_tlb_mode(Unicorn.UC_TLB_VIRTUAL);
|
|
u.emu_start(ADDRESS, ADDRESS + X86_CODE32_MEM_READ.length, 0, 0);
|
|
assertEquals("ecx", u.reg_read(Unicorn.UC_X86_REG_ECX), 0x04030201);
|
|
|
|
u.close();
|
|
}
|
|
|
|
@Test
|
|
public void testX86ReadFloat80() {
|
|
// fldl2e; fsin
|
|
final byte[] X86_CODE = { -39, -22, -39, -2 };
|
|
|
|
long ADDRESS = 0x100000;
|
|
|
|
Unicorn u = new Unicorn(Unicorn.UC_ARCH_X86, Unicorn.UC_MODE_32);
|
|
u.mem_map(ADDRESS, 2 * 1024 * 1024, Unicorn.UC_PROT_ALL);
|
|
u.mem_write(ADDRESS, X86_CODE);
|
|
u.emu_start(ADDRESS, ADDRESS + X86_CODE.length, 0, 0);
|
|
X86_Float80 reg1 =
|
|
(X86_Float80) u.reg_read(Unicorn.UC_X86_REG_ST0, null);
|
|
X86_Float80 reg2 =
|
|
(X86_Float80) u.reg_read(Unicorn.UC_X86_REG_FP7, null);
|
|
assertEquals(null, ADDRESS, ADDRESS, ADDRESS);
|
|
assertEquals(Math.sin(Math.log(Math.E) / Math.log(2)), reg1.toDouble(),
|
|
1e-12);
|
|
assertEquals(reg1.toDouble(), reg2.toDouble(), 1e-12);
|
|
u.close();
|
|
}
|
|
|
|
@Test
|
|
public void testX86WriteFloat80() {
|
|
// fsin
|
|
final byte[] X86_CODE = { -39, -2 };
|
|
|
|
long ADDRESS = 0x100000;
|
|
|
|
Unicorn u = new Unicorn(Unicorn.UC_ARCH_X86, Unicorn.UC_MODE_32);
|
|
u.mem_map(ADDRESS, 2 * 1024 * 1024, Unicorn.UC_PROT_ALL);
|
|
u.mem_write(ADDRESS, X86_CODE);
|
|
X86_Float80 reg = X86_Float80.fromDouble(-1.1);
|
|
u.reg_write(Unicorn.UC_X86_REG_ST0, reg);
|
|
u.emu_start(ADDRESS, ADDRESS + X86_CODE.length, 0, 0);
|
|
reg = (X86_Float80) u.reg_read(Unicorn.UC_X86_REG_ST0, null);
|
|
assertEquals(Math.sin(-1.1), reg.toDouble(), 1e-12);
|
|
u.close();
|
|
}
|
|
}
|