Vm Challenge1
This VM should keep my program safe… right?
Initial Overview
VM exe coded in C, takes an arg which is a 32 bit integer which is the password, prints the flag if correct, prints nothing if not.
Analysis
Initial Analysis
First thing we have to do is find the VM function, bytecode, and target (what prints the flag). For the target, we see:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
00401000 void __fastcall sub_401000(int32_t arg1, int32_t arg2)
00401000 {
00401000 int32_t Buffer = arg1;
00401000
0040100f if (arg2 == 1)
0040100f {
00401018 int32_t var_114_1 = 0;
0040101d int32_t eax_1;
0040101d int32_t edx;
0040101d eax_1 = RtlCrc64(&Buffer, 4, 0);
00401031 void var_108;
00401031 wsprintfA(&var_108, "FLAG{0x%x%x}", eax_1, edx);
00401037 TEB* fsbase;
00401037 struct _TEB* Self = fsbase->NtTib.Self;
0040104a int32_t nNumberOfCharsToWrite = lstrlenA(&var_108);
00401061 WriteConsoleA(Self->ProcessEnvironmentBlock->ProcessParameters->StandardOutput,
00401061 &var_108, nNumberOfCharsToWrite, nullptr, nullptr);
0040100f }
00401000 }
So arg2 has to be one, I would go farther to find out what needs to be set, but when we disassemble the bytecode it will be pretty obvious.
The VM function is pretty easy to find, just look for a function that has a bunch of branches based of a single byte (0x004012b5).
Finally, the argument passed into the VM function contains the bytecode:
1
2
3
4
5
6
7
8
9
00401145 __builtin_memcpy(i, 0x403000, 0x82);
00401156 struct _PEB* ProcessEnvironmentBlock =
00401156 fsbase->NtTib.Self->ProcessEnvironmentBlock;
0040115c **(eax_5 + 0xc) = *(eax_5 + 8);
0040115e int32_t* ecx_7 = *(eax_5 + 0xc);
00401163 ecx_7[1] = *ecx_7;
0040116b *(*(eax_5 + 0xc) + 0x1c) = ProcessEnvironmentBlock;
0040116e *(eax_5 + 4) = i;
00401171 vm(eax_5);
So what is in “i”?
1
3e020000003f01370104400101013d003e16c31f483f003901003f003b03034101033b03034301363ec9a505ae3f033700033ef09bacb43f033b00033e70d4eaf23f013edf310e813f033b01030000000000000000000000000000000000000000000000000000000000000000000000003b03034100014301363e010000003f03360000
Looks like good old bytecode!
VM analysis
The first byte comparison we see is:
1
2
3
4
5
004012dd if (bytecode == 0x36)
004012dd {
004014b2 ebx[0x14] = 0;
004014b2 break;
004012dd }
This terminates the loop, so we can assume that 0x36 is the opcode for halt/exit/ret (has no functions so it might as well be).
The second comparison we see is:
1
2
3
4
5
004012e9 if (bytecode >= 0x37 && bytecode <= 0x3c)
004012e9 {
004012ed sub_401236(ebx);
004012f2 esi = *(ebx + 4);
004012e9 }
In that we see an argument parser (for bytes following) and then:
1
2
3
4
5
0040126b if (eax_2 == 0x37)
0040126b {
004012a5 result = ecx + edx_1;
004012a8 *(esi_1 + (edi_1 << 2) + 0xc) = result;
0040126b }
Similar comparisons take place, with similar operations, so tl;dr:
1
2
3
4
5
6
0x37 = add
0x38 = sub
0x39 = mul (used like sub/add)
0x3a = div (used like sub/add)
0x3b = xor
0x3c = mov
Going back to the VM, we see:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
00401348 if (eax_7 == 0x3d)
00401348 {
0040146a char temp1_1 = esi[1];
00401471 uint32_t bytecode_4 = bytecode_2;
00401474 bytecode_2 = esi[1];
00401474
00401477 if (temp1_1 >= 0x14)
00401477 bytecode_2 = bytecode_4;
00401477
0040147a bytecode_3 = bytecode_2;
0040147a
0040147d if (temp1_1 >= 0x14)
00401499 esi = &esi[1];
0040147d else
0040147d {
0040147f int32_t* eax_33 = *(ebx + 0xc);
00401482 *eax_33 -= 4;
00401485 bytecode_2 = *(ebx + 0xc);
00401494 **bytecode_2 =
00401494 *(bytecode_2 + (bytecode_3 << 2) + 0xc);
00401499 esi = &(*(ebx + 4))[1];
0040147d }
00401348 }
This was… confusing. Using dynamic analysis it was found that it pushed the password onto the “stack”, which we are about to explore.
The next comparison is:
1
2
3
4
5
6
7
00401348 else if (eax_7 == 0x3e)
00401351 {
00401449 int32_t* eax_29 = *(ebx + 0xc);
0040144e *eax_29 -= 4;
0040145c ***(ebx + 0xc) = *(*(ebx + 4) + 1);
00401462 esi = *(ebx + 4) + 4;
00401351 }
This is just a push instruction, it pushes a 32 bit integer onto the the stack, this is evident by the subtracting of eax_29, similar to the substitution of rsp during a push.
The following comparison is:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
00401351 else if (eax_7 == 0x3f)
0040135a {
0040141a arg1 = esi[1];
00401420 bytecode_2 = arg1;
00401420
00401426 if (arg1 >= 0x14)
00401426 bytecode_2 = *arg1[1];
00401426
00401429 bytecode_1 = bytecode_2;
00401429
0040142c if (arg1 >= 0x14)
00401499 esi = &esi[1];
0040142c else
0040142c {
0040142e bytecode_2 = *(ebx + 0xc);
0040143d *(bytecode_2 + (bytecode_1 << 2) + 0xc) =
0040143d **bytecode_2;
00401441 int32_t* eax_28 = *(ebx + 0xc);
00401445 *eax_28 += 4;
00401499 esi = &(*(ebx + 4))[1];
0040142c }
0040135a }
The usage of eax+=4 looks like increasing rsp after a pop, we can confirm that this works similar to pop via testing, we can also see in the bytecode that the following argument is always 4 or less (indicating a register ID).
The following bytecode was:
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
0040135a else if (eax_7 == 0x40)
00401363 {
004013c1 char var_7;
004013c1
004013c8 if (sub_40120d(ebx, &var_8, &var_7))
004013c8 {
004013ce bytecode_2 = *(ebx + 0xc);
004013d1 char ecx_4 = esi[3];
004013d9 int32_t esi_2 =
004013d9 *(bytecode_2 + (var_7 << 2) + 0xc);
004013dd char eax_21 = ecx_4;
004013e0 int32_t i_1 = 4;
004013e0
004013e1 if (ecx_4 > 4)
004013e1 eax_21 = 4;
004013e1
004013e9 if (eax_21)
004013e9 {
004013eb uint32_t edi_1 = var_8;
004013f2 void* eax_23 = &bytecode_2[(edi_1 + 3) << 2];
004013fc int32_t i;
004013fc
004013fc do
004013fc {
004013f5 *eax_23 = 0;
004013f8 eax_23 += 1;
004013f9 i = i_1;
004013f9 i_1 -= 1;
004013fc } while (i != 1);
0040140d __builtin_memcpy(
0040140d *(ebx + 0xc) + ((edi_1 + 3) << 2), esi_2,
0040140d eax_21);
0040140f edi = *(ebx + 4);
004013e9 }
004013c8 }
004013c8
00401412 esi = &edi[3];
00401363 }
This was unreadable, through testing I found that this was used as an anti-debug check, it moved whether or not the process was being debugged into the register id “01”.
The following code was:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
00401363 else if (eax_7 == 0x41)
00401368 {
00401377 char var_6;
0040137e char var_5;
0040137e
0040137e if (sub_40120d(ebx, &var_5, &var_6))
0040137e {
00401380 void* ecx_2 = *(ebx + 0xc);
00401387 bytecode_2 = *(ecx_2 + (var_5 << 2) + 0xc);
0040138f int32_t eax_16 = *(ecx_2 + (var_6 << 2) + 0xc);
00401393 *(ecx_2 + 8) = 0;
00401393
00401399 if (bytecode_2 < eax_16)
0040139e *(*(ebx + 0xc) + 8) = 0xff;
00401399 else if (bytecode_2 > eax_16)
004013a9 *(*(ebx + 0xc) + 8) = 1;
0040137e }
0040137e
004013b0 esi = *(ebx + 4) + 2;
00401368 }
This looked like a comparison function, as you can see this section:
1
2
3
4
5
6
00401393 *(ecx_2 + 8) = 0;
00401393
00401399 if (bytecode_2 < eax_16)
0040139e *(*(ebx + 0xc) + 8) = 0xff;
00401399 else if (bytecode_2 > eax_16)
004013a9 *(*(ebx + 0xc) + 8) = 1;
It sets a flag to zero if equal, -1 if bytecode_2 is less, and 1 if more.
Finally, we make it to the end:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
00401300 {
00401302 uint32_t eax = bytecode;
00401305 arg1 = 0;
00401305
0040130a if (eax == 0x42)
0040132d arg1 = 1;
0040130a else if (eax == 0x43)
00401328 arg1 = !*(*(ebx + 0xc) + 8);
0040130f else if (eax == 0x44)
0040131c arg1 = *(*(ebx + 0xc) + 8);
0040131c
00401331 if (arg1)
0040133b esi = &esi[esi[1]];
0040133b
00401499 esi = &esi[1];
00401300 }
This uses the flag set by cmp in order to jump ahead a few bytes. 0x42 looks like a jmp (always set), 0x43 looks like jz, and 0x44 looks like jnz.
Disassembling
So far we have:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
0x36 = hlt
0x37 = add
0x38 = sub
0x39 = mul (used like sub/add)
0x3a = div (used like sub/add)
0x3b = xor
0x3c = mov
0x3d = push password
0x3e = push
0x3f = pop
0x40 = mov ecx, isbeingdebugged
0x41 = cmp
0x42 = jmp
0x43 = jz
0x44 = jnz
Now we can disassemble:
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
; 00 = eax
; 01 = ecx
; 02 = edx
; 03 = esi
; 04 = edi
push 2 ; 3e02000000
pop ecx ; 3f01
add ecx, edi ; 370104
mov ecx, isBeingDebugged ; 40010101
push password ; 3d00
push 0x481fc316 ; 3e16c31f48
pop eax ; 3f00
mul ecx, eax ; 390100
pop eax ; 3f00
xor esi, esi ; 3b0303
cmp ecx, esi ; 410103
xor esi, esi ; 3b0303
jz +1 ; 4301
hlt ; 36
push 0xae05a5c9 ; 3ec9a505ae
pop esi ; 3f03
add eax, esi ; 370003
push 0xb4ac9bf0 ; 3ef09bacb4
pop esi ; 3f03
xor eax, esi ; 3b0003
push 0xf2ead470 ; 3e70d4eaf2
pop ecx ; 3f01
push 0x810e31df ; 3edf310e81
pop esi ; 3f03
xor ecx, esi ; 3b0103
nop ; 000000000000000000000000000000000000000000000000000000000000000000000000
xor esi, esi ; 3b0303
cmp eax, ecx ; 410001
jz +1 ; 4301
hlt ; 36
push 0x1 ; 3e01000000 ; win?
pop esi ; 3f03
hlt ; 36
Assembly Analysis
The first section:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
push 2 ; 3e02000000
pop ecx ; 3f01
add ecx, edi ; 370104
mov ecx, isBeingDebugged ; 40010101
push password ; 3d00
push 0x481fc316 ; 3e16c31f48
pop eax ; 3f00
mul ecx, eax ; 390100
pop eax ; 3f00
xor esi, esi ; 3b0303
cmp ecx, esi ; 410103
xor esi, esi ; 3b0303
jz +1 ; 4301
hlt ; 36
Just moves the password integer into eax and terminates if debugged.
The second section:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
push 0xae05a5c9 ; 3ec9a505ae
pop esi ; 3f03
add eax, esi ; 370003
push 0xb4ac9bf0 ; 3ef09bacb4
pop esi ; 3f03
xor eax, esi ; 3b0003
push 0xf2ead470 ; 3e70d4eaf2
pop ecx ; 3f01
push 0x810e31df ; 3edf310e81
pop esi ; 3f03
xor ecx, esi ; 3b0103
nop ; 000000000000000000000000000000000000000000000000000000000000000000000000
xor esi, esi ; 3b0303
cmp eax, ecx ; 410001
jz +1 ; 4301
hlt ; 36
We can solve with starting from the end. First, eax and ecx must be the same. Since ecx is not dependent on the input, we can analyze it first. Let’s simulate it in python and see the end result:
1
2
3
4
5
6
7
8
9
10
# push 0xf2ead470 ; 3e70d4eaf2
# pop ecx ; 3f01
ecx = 0xf2ead470
# push 0x810e31df ; 3edf310e81
# pop esi ; 3f03
esi = 0x810e31df
# xor ecx, esi ; 3b0103
ecx = ecx ^ esi
print(ecx)
We get 1944380847
, now that we know that value, we can find out input. The equation can be represented as 1944380847 = (eax + 0xae05a5c9) ^ 0xb4ac9bf0
let’s simplify by xoring each side: 3343416927 = (eax + 0xae05a5c9)
next lets subtract each side: 423811222 = eax.
With that, we have our input.
Flag
FLAG{0x4a516025f931857b}