Home Rust VM
Post
Cancel

Rust VM

This post shows how to reverse a niteCTF-22 Rust VM challenge.

There are two files vm (Rust Vitual Machine) and encrpyt (Program File containing instructions). Running the binary, it asks for a input, prints ‘\n’ and exits.

Analyzing Binary in IDA

Loading up the binary in IDA, there is a main function which reads the content of the encrypt file into an array mem, and calls run_loop. 2.png

The run_loop calls process_opcode, and program runs till pc doesn’t become 0.

1
2
3
4
r-> register array (total 8 registers)
mem -> memory
cflag -> jump flag
pc -> program counter

3.png

The process_opcode function interprets instructions according to the op_code and returns new pc after executing the instruction. 4.png

I like to remove all the panic and overflow conditions from the decompiled functions, it makes my eyes bleed. So here is what the process_opcode function looks like.

Reversing process_opcode the various OP codes can be summarised as:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
0x10 - 0x17 // 2 byte instruction
a b
load into register ----> 
reg[a & 0xf] = b 
eg: 0x11 0xa3 instruction -> reg[1] = 0xa3  

0x18 - 0x1f // 3 byte instruction
a b c
reg[a & 0xf] = mem[(b<<8)+c] 

0x20 - 0x27 // 3 byte instruction
a b c
load to memory
mem[c+(b<<8)] = register[a & 0xf]


0x80 // 3 byte instruction
a b c 
print data from mem 
prints from mem[(b<<8)+c] till it encounters a null byte

0x81  // 3 byte instruction
   get input and store in mem[(b<<8)+c]

0x90  // 1 byte instruction
    nop(no operation)

'0' // 2 byte instruction
   '0' 0x(c)(d)
    register[c] = register[d]

'1' // 2 byte instruction 
   '1' 0x(c)(d)
    register[c] = ~(register[c] & register[d])

'2' // 2 byte instruction
    '2' 0x(c)(d)
    if register[c] ==register[d]
        cflag = 1

'@' // 2 byte instruction
    '@' 0x(c)(d)
    reg[c] = reg[c] >> (mem[0xcd]& 0xF)

'A' // 2 byte instruction
    'A' 0x(c)(d)
    reg[c] = reg[c] << (mem[0xcd]& 0xF)

'P' // 3 byte instruction
    'P' b c 
    if(cflag == 1)
        cflag = 0
        pc +=3 
    else:
        pc = ((b<<8)+c)

Invalid op_code  
        print UNRECOGNIZED OPCODE: {opcode}!  and exit

Analysing the encrypt file

Printing every instruction in a new line for better visiblity. All the instructions are here

1
2
3
4
5
6
7
8
9
10
17 00     // reg[7] = 0
16 01     // reg[6] = 1
81 b0 00  // reads input from std:io and stroes it into mem[((0xb0)<<8) + 0x00]
10 59     // reg[0] = 'Y' (0x59)
20 c0 00  // mem[((0xc0)<<8) + 0x00] = reg[0]
10 6f     // reg[0] = 'o' (0x6f)       
20 c0 01  // mem[((0xc0)<<8) + 0x01] = reg[0] 
10 75     // so from mem[(0xc0)<<8)] onwards it stores 'You got it right!' 
20 c0 02  // which gets printed when we enter the correct flag.(80 C0 00 last instruction in the file)
...

Further instructions loads a character from our input into in reg[0], some number in reg[1] , operates on it and checks whether it is equal or not to some hardcoded value.

1
2
3
4
5
6
7
8
9
10
...
18 b0 00        reg[0] = mem[(0xb0 << 8)+0x00]
11 a3           reg[1] = 0xa3
30 20           reg[2] = reg[0]   
30 31           reg[3] = reg[1]
31 01           reg[0] = ~(reg[0] & reg[1]) 
30 10           reg[1] = reg[0]
31 20           reg[2] = ~(reg[2] & reg[0])
31 31           reg[3] = ~(reg[2] & reg[0])
...
1
2
3
4
...
32 12          if (reg[1]==reg[2]) cflag=1
50 00 00       if (cflag!=1) pc = 0 and the program exits 
...

Basically there is a character-by-character comparison and the program terminates as soon as it encounters a wrong character, I used this vulnerability to solve this challenge.

I opened hex editor, edited the 50 00 00 instruction to invalid instruction 88 00 00, now one by one went on correcting the instruction back and forth, side by side brute forcing characters with a simple python script, so whenever I got a correct character program returns UNRECOGNIZED OPCODE and I know that previous entered character is correct.

1
2
3
4
5
6
7
8
9
10
11
12
from pwn import *
import string

flag = "nite{"
for i in string.printable:
    p = process("./vm")
    p.sendline(flag+i)
    x = p.recv()
    if len(x)>3:
        print(flag+i)
        break

1.png

And the flag is nite{n4nd_1s_un1v3rs4l_g4te}.

This post is licensed under CC BY 4.0 by the author.