Posts Understanding the Movfuscator
Post
Cancel

Understanding the Movfuscator

MoVfuscator is the PoC for the Turing Completeness of Mov instruction.
Yes, you guessed it right. It uses only mov’s, except for a few places.
This makes reversing difficult, because the control flow is obfuscated.
I’ll be analyzing the challenge Mov of UTCTF’19 using IDA Free.

MoV binary

The Stack

Movfuscator uses its own stack. The stack consists of an array of addresses. The stack looks like this

mo0

Each element of the stack is at an offset of 0x200064 from it’s stack address. The stack begins at 0x83f70e8 and it grows from high to low address.

i1

The stack pointer is saved in the variable sesp. The variable NEW_STACK stores the address of guard.

1
2
3
4
5
6
7
mov esp, NEW_STACK              ; address of guard
mov esp, [esp-0x200068]         ; address of A[n-1]
mov esp, [esp-0x200068]         ; address of A[n-2]
;   ...
;   n times
;   ...
;   use esp

So, mov esp, [esp-0x200068], subtracts 4 from esp.
Now we can understand what start does.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
mov dword [esp-4*4], SIGSEGV
mov dword [esp-4*4+4], offset sa_dispatch
mov dword [esp-4*4+8], 0
call sigaction

mov dword [esp-3*4], SIGILL
mov dword [esp-3*4+4], offset sa_loop
mov dword [esp-3*4+8], 0
call sigaction

;
;   ...
;
.plt:08048210                 public dispatch
.plt:08048210 dispatch        proc near               ; DATA XREF: .data:sa_dispatch↓o
.plt:08048210                 mov     esp, NEW_STACK
.plt:08048216                 jmp     function
.plt:08048216 dispatch        endp

Movfuscator uses SIGSEGV to execute a function, and SIGILL to execute a JMP instruction which jumps to master_loop. Because we can’t mov to eip, which is invalid in x86.

Execution is controlled using the on variable. This is a boolean variable that determines whether a statement will be executed or not.

i2

The master_loop sets the value of on and then disables toggle_execution. This is the structure of if statement.

1
2
3
4
5
def logic_if(condition, dest, src)
    if (condition)
        dest = src
    else
        discard = src

It then adds sesp with 4 and stores the sum in stack_temp.

Push

i3

The array sel_data contains two members - discard and data_p. This is a MUX which selects data_p if on is set. So, if on is set, eax contains the address of NEW_STACK. And the value of esp-4 is stored in NEW_STACK, which is the stack pointer. And then the value of stack_temp is stored in the current stack pointer.

The above set of instructions are equivalent to

1
2
3
mov eax, [stack_temp]
sub esp, 4
mov [esp], eax

It can also be represented as

1
push dword [stack_temp]

The sequnce of instructions until 0x0804843C do the following

1
2
3
4
5
mov eax, [sesp]
add eax, 4
push eax
push dword [sesp]
push 0x880484fe

i4

It conditionally sets the value of target to branch_temp. The target variable is the destination an unconditional jump. In this code, the target is set to 0x88048744.

Let’s see how jump’s are implemented.

1
2
3
4
5
6
7
8
9
10
11
12
on = 1
...
target = jump_destination
; save registers R, F, D
on = 0
...
if (fetch_addr == target)
{
    ; restore registers R, F, D
    on = 1
}
...

i5

The above code saves the registers.

i6

It now checks if the fetch address equals the address contained in target. The equal-to comparison is computed for each byte and the result is the logical-and of the four comparisons. The result of the comparison is stored in the boolean variable b0.
Now if b0 is set, the registers are restored and the on variable is set.

i7

This is equivalent to the following if the on variable is set.

1
2
push 0
call _exit

You must be wondering how I deduced the call instruction. Here is it

Function Call

Function calls are implemented using the SIGSEGV signal. The array fault is defined like this

1
2
3
4
.data:085F7198 fault           dd offset no_fault      ; DATA XREF: _start+51F↑r
.data:085F719C                 dd 0

.data:085F71A0 no_fault        dd 0

So, fault when indexed with on returns 0 if on is set, otherwise a valid address. This return value is dereferenced which results in a SIGSEGV (Segmentation Fault) if its zero.

But since, the value of target is 0x88048744. The control jumps to main.

In main, the registers are restored and the on flag is set. After that it pushes fp, R1, R2, R3, F1, dword_804e04c, D1 into the stack

The function prologue

i8

It first assigns the frame pointer fp to the current stack pointer and allocates 37 dwords (148 bytes) from the stack. This is equivalent to the following x86

1
2
mov ebp, esp    ;   ebp is **fp**
sub esp, 148

i9

Computes fp-19*4 and stores the value of R3 into the address. So, this is basically

1
2
mov R3, 0
mov [fp-19*4], R3

Great ! So, we have a dword at fp-0x4c initialized to 0.
Then we have an array of bytes at fp-0x47 initialized as follows

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
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
mov R0, 0x1a
mov byte [fp-18*4], R0
mov R0, 0x19
mov byte [fp-0x47], R0
mov R0, 11
mov byte [fp-0x46], R0
mov R0, 0x31
mov byte [fp-0x45], R0
mov R0, 6
mov byte [fp-17*4], R0
mov R0, 4
mov byte [fp-0x43], R0
mov R0, 0x18
mov byte [fp-0x42], R0
mov R0, 0x10
mov byte [fp-0x41], R0
mov R0, 10
mov byte [fp-16*4], R0
mov R0, 0x33
mov byte [fp-0x3f], R0
mov R0, 0x19
mov byte [fp-0x3e], R0
mov R0, 10
mov byte [fp-0x3d], R0
mov R0, 0x33
mov byte [fp-15*4], R0
mov R0, 0
mov byte [fp-0x3b], R0
mov R0, 10
mov byte [fp-0x3a], R0
mov R0, 0x3c
mov byte [fp-0x39], R0
mov R0, 0x19
mov byte [fp-14*4], R0
mov R0, 13
mov byte [fp-0x37], R0
mov R0, 6
mov byte [fp-0x36], R0
mov R0, 0x19
mov byte [fp-0x35], R0
mov R0, 0x3c
mov byte [fp-13*4], R0
mov R0, 14
mov byte [fp-0x33], R0
mov R0, 0x10
mov byte [fp-0x32], R0
mov R0, 0x3c
mov byte [fp-0x31], R0
mov R0, 0x10
mov byte [fp-12*4], R0
mov R0, 12
mov byte [fp-0x2f], R0
mov R0, 0x32
mov byte [fp-0x2e], R0
mov R0, 10
mov byte [fp-0x2d], R0
mov R0, 0x14
mov byte [fp-11*4], R0
mov R0, 13
mov byte [fp-0x2b], R0
mov R0, 6
mov byte [fp-0x2a], R0
mov R0, 0x19
mov byte [fp-0x29], R0
mov R0, 0x3c
mov byte [fp-10*4], R0
mov R0, 0x19
mov byte [fp-0x27], R0
mov R0, 6
mov byte [fp-0x26], R0
mov R0, 0x33
mov byte [fp-0x25], R0
mov R0, 4
mov byte [fp-9*4], R0
mov R0, 10
mov byte [fp-0x23], R0
mov R0, 0x33
mov byte [fp-0x22], R0
mov R0, 0x19
mov byte [fp-0x21], R0
mov R0, 14
mov byte [fp-8*4], R0
mov R0, 6
mov byte [fp-0x1f], R0
mov R0, 0x31
mov byte [fp-0x1e], R0
mov R0, 0x31
mov byte [fp-0x1d], R0
mov R0, 0x1e
mov byte [fp-7*4], R0
mov R0, 0x3c
mov byte [fp-0x1b], R0
mov R0, 0x17
mov byte [fp-0x1a], R0
mov R0, 10
mov byte [fp-0x19], R0
mov R0, 0x31
mov byte [fp-6*4], R0
mov R0, 6
mov byte [fp-0x17], R0
mov R0, 0x19
mov byte [fp-0x16], R0
mov R0, 10
mov byte [fp-0x15], R0
mov R0, 9
mov byte [fp-5*4], R0
mov R0, 0x3c
mov byte [fp-0x13], R0
mov R0, 0x19
mov byte [fp-0x12], R0
mov R0, 12
mov byte [fp-0x11], R0
mov R0, 0x3c
mov byte [fp-4*4], R0
mov R0, 0x19
mov byte [fp-0xf], R0
mov R0, 13
mov byte [fp-0xe], R0
mov R0, 10
mov byte [fp-0xd], R0
mov R0, 0x3c
mov byte [fp-3*4], R0
mov R0, 0
mov byte [fp-0xb], R0
mov R0, 13
mov byte [fp-0xa], R0
mov R0, 6
mov byte [fp-0x9], R0
mov R0, 0x31
mov byte [fp-2*4], R0
mov R0, 0x31
mov byte [fp-7], R0
mov R0, 10
mov byte [fp-6], R0
mov R0, 0x33
mov byte [fp-5], R0
mov R0, 4
mov byte [fp-4], R0
mov R0, 10
mov byte [fp-3], R0
mov R0, 2
mov byte [fp-2], R0

At 0x804ba9c, the int variable at fp-0x4c is set to 0.

If target is 0x8804bb37, it executes the following

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
if (target == 0x8804bb37)
{
    ;   restore the registers
    R{0,1,2,3} = jmp_r{0,1,2,3}
    F{0,1} = jmp_f{0,1}
    D{0,1} = jmp_d{0, 1}
    dword_804e044 = dword_85f717c
    dword_804e04c = dword_85f7184

    ; set execution flag
    on = 1
}

mov R3, [fp-19*4]
if (on)
{
    mov R3, [R3]
    mov R2, [fp-37*4]
    add R2, R3
    mov R1, [fp-18*4]
    add R3, R1
    mov R0, byte [R3]
    mov R3, R0
    xor R3, 0x53
    sub R3, 3
    xor R3, 0x33
    mov R0, R3
    mov [R2], R0
}

Since, the target contains 0x88048744 which is not 0x8804bb37, none of the instructions in the if enclosed by on is executed.

At 0x0804C2D4, we have another branch check

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
if (target == 0x8804C2D4)
{
    RESTORE_REGS()
    on = 1
}

    mov R3, [fp-19*4]

if (on)
{
    add R3, 1
    mov [fp-19*4], R3
    mov R3, [fp-19*4]
    setc
    sbb R3, 0x47
    mov branch_temp, 0x8804bb37
}

i10

alu_false contains 1 at index 0, and 0 at the remaining indices. So, this sets the complement of the Carry flag.
ZeroFlag is evaluated as a NOR logic, i.e., ZF = !(alu_s[0] | alu_s[1] | alu_s[2] | alu_s[3])

i11

alu_b7 is an array of 256 dwords, the first 128 are zero, and the rest are 1. Indexing into this array determines the Sign bit (bit 7) of the index.

i12

Okay, so alu_cmp_of represents a truth table. Of what ? Well, there are only two out of the eight minterms set. So, we get the following SOP

1
x'ys + xy's'

Where x, y, s are the sign bits of alu_x, alu_y, alu_z.
Cool ! This is the overflow flag

i13

It xor’s SignFlag and OverflowFlag and sets target to branch_temp which is 0x8804bb37. By x0ring the sign and overflow flags we get the LessThan flag.

So, if R3 is less than 0x47, the target is set to 0x8804bb37.
Then we have the following

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
mov byte [fp-0x4d], 0
if (target == 0x8804CA3B)
{
    on = 1
}
if (on)
{
    mov esp, fp
    mov D1, [esp]
    mov dword_804e04c, [esp+4]
    sub esp, 4*2
    mov eax, [esp]
    sub esp, 4
    mov F1, eax
    mov eax, [esp]
    sub esp, 4
    mov R3, eax
    mov eax, [esp]
    sub esp, 4
    mov R2, eax
    mov eax, [esp]
    sub esp, 4
    mov R1, eax
    mov eax, [esp]
    sub esp, 4
    mov fp, eax
    mov eax, [esp]
    sub esp, 4
    mov branch_temp, eax
    mov target, branch_temp
    on = 0
}

A SIGILL is executed which causes the control to jump to master loop. And the execution of the instructions are skipped until the address the control reaches at 0x804bb37

So, this is basically a while loop.

The control first compares R3 with 0x47 and branches to 0x804bb37 while R3 is less than 0x47. When the condition becomes false, it executes from 0x804ca3b

Solution

So, the logic is

1
2
3
4
5
6
7
8
9
10
int main()
{
    int i = 0;
    char arr[] = { 26, 25, 11, 49, 6, 4, 24, 16, 10, 51, 25, 10, 51, 0, 10, 60, 25, 13, 6, 25, 60, 14, 16, 60, 16, 12, 50, 10, 20, 13, 6, 25, 60, 25, 6, 51, 4, 10, 51, 25, 14, 6, 49, 49, 30, 60, 23, 10, 49, 6, 25, 10, 9, 60, 25, 12, 60, 25, 13, 10, 60, 0, 13, 6, 49, 49, 10, 51, 4, 10, 2 };

    for (i = 0; i < 0x47; ++i)
    {
        arr[i] = (arr[i]^0x53)-3 ^ 0x33;
    }
}

Executing the above code, yields the flag - utflag{sentence_that_is_somewhat_tangentially_related_to_the_challenge}

References

MoV is Turing Complete

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