Useless emulator for fun (vmndh-2k12)
by @Jonathan Salwan - 2012-03-26Emulator NDH 2K12
This emulator was created for the CTF NDH 2012. Some challenges were on the NDH architecture. The NDH architecture is a new architecture which looks like a mix between ARM and x86.
GitHub (Stable v0.1) https://github.com/JonathanSalwan/VMNDH-2k12/
Authors
- Jonathan Salwan (Emulator & Architecture)
- Damien Cauquil (Compiler)
Installation
$ git clone git@github.com:JonathanSalwan/VMNDH-2k12.git
$ cd ./VMNDH-2k12
$ make
NDH Architecture
STACK [0000 - 7FFF] (default ASLR is disable. Possibility to set ASLR with -aslr)
Program [8000 - FFFE] (default NX & PIE are disable. Possibility to set NX & PIE with -nx -pie)
ASLR genered with:
__asm__("mov %gs:0x14, %eax");
__asm__("shr $0x08, %eax");
__asm__("mov %%eax, %0" : "=m"(aslr));
aslr = aslr % 0x3000 + 0x4ffe;
PIE genered with:
__asm__("mov %gs:0x14, %eax");
__asm__("shr $0x08, %eax");
__asm__("mov %%eax, %0" : "=m"(pie));
pie = pie % 0x3000 + 0x8000;
^ 0000>+-----------------+
| | | The max size of binary is 0x7ffe.
| | |
| | | Before the program is executed, argv and argc are
| | STACK ^^ | pushed on the stack.
| | || | If an argument is set with (-arg), argc = 1
| | | and argv points to the string.
| +-----------------+< SP & BP
6 | ARG | If you don't set an argument, argc and argv
4 | | are pushed with value 0x00.
K 8000>+-----------------+< PC
| | |
| | || | Exemple1: ./vmndh -file ./binary
| | CODE vv |
| | | [SP] 0x00 0x00 0x00 0x00 0x00 0x00
| | | <--argc-> <-argv-->
| | |
v ffff>+-----------------+ Exemple2: ./vmndh -file ./binary -arg "abcd"
[SP] 0x01 0x00 0xac 0x7f 0x00 0x00
<--argc-> <-argv-->
File Format
[MAGIC][size .text][.text content]
MAGIC: ".NDH"
SIZE: size of section TEXT
CODE: instructions
Instructions Encoding
[OPCODE] [OP_FLAGS | !] [OPERAND #1] [OPERAND #2]
[ADD] <opcode> <FLAG> <REG> <REG | DIR8 | DIR16> (size = 4 or 5 bytes)
- reg = add
- dir8 = addb
- dir16 = addl
[AND] <opcode> <FLAG> <REG> <REG | DIR8 | DIR16> (size = 4 or 5 bytes)
- reg = and
- dir8 = andb
- dir16 = andl
[CALL] <opcode> <FLAG> <REG | DIR16> (size = 3 or 4 bytes)
[CMP] <opcode> <FLAG> <REG> <REG | DIR8 | DIR16> (size = 4 or 5 bytes)
- reg = cmp
- dir8 = cmpb
- dir16 = cmpl
[DEC] <opcode> <REG> (size = 2)
[DIV] <opcode> <FLAG> <REG> <REG | DIR8 | DIR16> (size = 4 or 5 bytes)
- reg = div
- dir8 = divb
- dir16 = divl
[END] <opcode> (size = 1 byte)
[INC] <opcode> <REG> (size = 2 bytes)
[JMPL] <opcode> <DIR16> (size = 3 bytes)
[JMPS] <opcode> <DIR8> (size = 2 bytes)
[JNZ] <opcode> <DIR16> (size = 3 bytes)
[JZ] <opcode> <DIR16> (size = 3 bytes)
[JA] <opcode> <DIR16> (size = 3 bytes)
[JB] <opcode> <DIR16> (size = 3 bytes)
[MOV] <opcode> <FLAG> <REG | REG_INDIRECT> <REG | REG_INDIRECT | DIR8 | DIR16> (size = 4 or 5 bytes)
- reg = mov
- dir8 = movb
- dir16 = movl
- indir = mov [rX]
[MUL] <opcode> <FLAG> <REG> <REG | DIR8 | DIR16> (size = 4 or 5 bytes)
- reg = mul
- dir8 = mulb
- dir16 = mull
[NOP] <opcode> (size = 1 byte)
[NOT] <opcode> <REG> (size = 2 bytes)
[OR] <opcode> <FLAG> <REG> <REG | DIR8 | DIR16> (size = 4 or 5 bytes)
- reg = or
- dir8 = orb
- dir16 = orl
[POP] <opcode> <REG> (size = 2 bytes)
[PUSH] <opcode> <FLAG> <REG | DIR08 | DIR16> (size = 3 or 4 bytes)
- reg = push
- dir8 = pushb
- dir16 = pushl
[RET] <opcode> (size = 1 byte)
[SUB] <opcode> <FLAG> <REG> <REG | DIR8 | DIR16> (size = 4 or 5 bytes)
- reg = sub
- dir8 = subb
- dir16 = subl
[SYSCALL] <opcode> (size = 1 byte)
[TEST] <opcode> <REG> <REG> (size = 3 bytes)
[XCHG] <opcode> <REG> <REG> (size = 3 bytes
[XOR] <opcode> <FLAG> <REG> <REG | DIR8 | DIR16> (size = 4 or 5 bytes)
- reg = xor
- dir8 = xorb
- dir16 = xorl
Syscalls
r0 = syscall number
r1 = arg1
r2 = arg2
r3 = arg3
r4 = arg4
syscalls supported: open(), read(), write(), close(), exit(), setuid(), setgid(), dup2(),
send() recv(), socket(), listen(), bind(), accept(), chdir(), chmod(),
lseek(), getpid(), getuid(), pause()
[sys_open] r1 = uint16_t *
r2 = uint16_t
r3 = uint16_t
[sys_exit] r1 = uint16_t
[sys_read] r1 = uint16_t
r2 = uint16_t *
r3 = uint16_t
[sys_write] r1 = uint16_t
r2 = uint16_t *
r3 = uint16_t
[sys_close] r1 = uint16_t
[sys_exit] r1 = uint16_t
[sys_setuid] r1 = uint16_t
[sys_setgid] r1 = uint16_t
[sys_dup2] r1 = uint16_t
r2 = uint16_t
[sys_send] r1 = uint16_t
r2 = uint16_t *
r3 = uint16_t
r4 = uint16_t
[sys_recv] r1 = uint16_t
r2 = uint16_t *
r3 = uint16_t
r4 = uint16_t
[sys_socket] r1 = uint16_t
r2 = uint16_t
r3 = uint16_t
[sys_listen] r1 = uint16_t
r2 = uint16_t
[sys_bind] r1 = uint16_t (socket)
r2 = uint16_t (port)
[sys_accept] r1 = uint16_t (socket)
[SYS_CHDIR] r1 = uint16_t *
[SYS_CHMOD] r1 = uint16_t *
r2 = uint16_t
[SYS_LSEEK] r1 = uint16_t
r2 = uint16_t
r3 = uint16_t
[SYS_GETPID] n/a
[SYS_GETUID] n/a
[SYS_PAUSE] n/a
Flags
ZF
is set with following instructions:
- ADD
- SUB
- MUL
- DIC
- INC
- DEC
- OR
- XOR
- AND
- NOT
- TEST
- CMP
AF
& BF
are set with following instructions:
- CMP
AF
& BF
are used for JA
and JB
instructions.
Usage
Syntax: ./vmndh [OPTION] [FLAG]
OPTION:
-file Load binary
-arg Binary argument (optional)
FLAG:
-aslr Enable ASLR
-nx Enable NX bit
-pie Enable PIE
-debug Debug console
-core Generates a core dump when segfault
Compiler
The compiler is written in python and support labels, comments and includes. You can see the following source code with the famous 'Hello World'.
; NDH Hello world sample
.label main
movl r0, :helloworld
movl r1, #0
movl r2, #0
movl r5, #0
.label loop
mov r2, [r0]
test r2,r2
inc r5
inc r0
jnz :loop
movb r0, #4
movb r1, #1
movl r2, :helloworld
mov r3, r5
syscall
end
.label helloworld
.db "Hello World !",0x0A,0
Compile example
$ ./ndasm/ndasm.py -i ./src_asm_challenge/hello_world.asm -o hello_world.ndh
[*] Parsing source file ...
[+] Assembling ...
[*] Linking ...
[*] Creating outfile ...
[*] Done. 71 bytes written.
$ ./vmndh -file ./hello_world.ndh
Hello World !
$