CrackMe Challenge Part 5: Logical Code Segments Continued

CrackMe Part 5: Logical Code Segments Continued

The code in logical code segment 4 additionally changes the stack at address [esp+70]. The code is presented here:

004017E5 |. B8 3F000000 mov eax,3F
004017EA |. 8D4C24 70 lea ecx,dword ptr ss:[esp+70]
004017EE |. 8BFF mov edi,edi
004017F0 |> 8B55 0C /mov edx,dword ptr ss:[ebp+C]
004017F3 |. 8A1410 |mov dl,byte ptr ds:[eax+edx]
004017F6 |. 8811 |mov byte ptr ds:[ecx],dl
004017F8 |. 41 |inc ecx
004017F9 |. 48 |dec eax
004017FA |.^79 F4 jns short main.004017F0

The value at the address 0x70 contains the string of the inputted Name field followed by the ESETNOD32@ string. If we input six A's into the Name field, the value at address 0x70 is as follows:

AAAAAAD32@ESETNOD32@ESETNOD32@ESETNOD32@ESETNOD32@ESETNOD32@ESETNOD32@ESETNOD32@

In the above piece of code we can see that there aren't any function calls, so the code should be pretty simple to disassemble. First we're moving a constant value of 0x3F into the register eax, afterwards we're loading the address of the ESETNOD32@ string into ecx. The actual address is 0x0012EAB8. Then, we're loading the value from the address [ebp+C], which points to some data structure in the memory. Afterwards we're loading bytes from the data structure at the address [ebp+C] into register dl and overwriting our AAAAAAD32@ESET@ string with it. This goes on until eax register comes to zero, which means that the loop actually copies 0x40 values from the data structure.

We need to determine how the values at the data structure are being changed in relation to our input field. First let's try leaving the Name field alone and changing the Key 1 field. Let's input 10 A's into the Name field and enter 100 A's into the Key 1 field. The address stored on the stack at [ebp+C] points to the following data structure:

00B985D8 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00B985E8 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00B985F8 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00B98608 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00

Hmm, okey all zeros. We can't help ourselves with that. Maybe the program is filtering some characters and replacing them with zeros, thus replacing all our A's with all zeros. We can quickly test this with inputting the following characters into the Key 1 field:

AAAAAAAAAABBBBBBBBBBCCCCCCCCCCDDDDDDDDDDEEEEEEEEEEaaaaaaaaaabbbbbbbbbbccccccccccddddddddddeeeeeeeeee

The data structure then looks like this:

00B985D8 00 00 00 00 00 00 00 00 41 04 10 41 04 10 41 08
00B985E8 20 82 08 20 82 08 20 C3 0C 30 C3 0C 30 C3 10 41
00B985F8 04 10 41 04 10 46 9A 69 A6 9A 69 A6 9A 6D B6 DB
00B98608 6D B6 DB 6D B7 1C 71 C7 1C 71 C7 1C 75 D7 5D 75

Clearly we've figured out that we can influence the data structure with the Key 1 field, but we don't know exactly how yet. Let's try to input all a's (lower case) into the Key 1 field. If we enter 100 a's, the data structure looks like this:

00B985D8 69 A6 9A 69 A6 9A 69 A6 9A 69 A6 9A 69 A6 9A 69
00B985E8 A6 9A 69 A6 9A 69 A6 9A 69 A6 9A 69 A6 9A 69 A6
00B985F8 9A 69 A6 9A 69 A6 9A 69 A6 9A 69 A6 9A 69 A6 9A
00B98608 69 A6 9A 69 A6 9A 69 A6 9A 69 A6 9A 69 A6 9A 69

The bytes in data structure are still not very clear, we can't determine how the Key 1 affects them. Let's try to enter all b's and c's into it. The data structure then looks like the following:

00B985D8 6D B6 DB 6D B6 DB 6D B6 DB 6D B6 DB 6D B6 DB 6D
00B985E8 B6 DB 6D B6 DB 6D B6 DB 6D B6 DB 6D B6 DB 6D B6
00B985F8 DB 6D B6 DB 6D B6 DB 6D B6 DB 6D B6 DB 6D B6 DB
00B98608 6D B6 DB 6D B6 DB 6D B6 DB 6D B6 DB 6D B6 DB 6D
00B985D8 71 C7 1C 71 C7 1C 71 C7 1C 71 C7 1C 71 C7 1C 71
00B985E8 C7 1C 71 C7 1C 71 C7 1C 71 C7 1C 71 C7 1C 71 C7
00B985F8 1C 71 C7 1C 71 C7 1C 71 C7 1C 71 C7 1C 71 C7 1C
00B98608 71 C7 1C 71 C7 1C 71 C7 1C 71 C7 1C 71 C7 1C 71

Still nothing distinctive. It would be best to set a write breakpoint on the memory address 0x00B985D8 and input the same number of bytes as before, so the HeapAlloc will most probably be allocated at the same address. Then we can run the program again, and determine where those values are being changed. The breakpoint happens at address 0x00401300 in the following piece of code:

004012C0 $ 55 push ebp
004012C1 . 8BEC mov ebp,esp
004012C3 . 51 push ecx
004012C4 . 56 push esi
004012C5 . 57 push edi
004012C6 . 8BF8 mov edi,eax
004012C8 . 33F6 xor esi,esi
004012CA . 8BC1 mov eax,ecx
004012CC . 8945 FC mov dword ptr ss:[ebp-4],eax
004012CF . 85D2 test edx,edx
004012D1 . 7E 7D jle short main.00401350
004012D3 . 53 push ebx
004012D4 . EB 0A jmp short main.004012E0
004012D6 . 8DA424 00000000 lea esp,dword ptr ss:[esp]
004012DD . 8D49 00 lea ecx,dword ptr ds:[ecx]
004012E0 > 0FBE0437 movsx eax,byte ptr ds:[edi+esi]
004012E4 . 0FB680 C0145400 movzx eax,byte ptr ds:[eax+5414C0]
004012EB . 0FBE5C37 01 movsx ebx,byte ptr ds:[edi+esi+1]
004012F0 . 0FB69B C0145400 movzx ebx,byte ptr ds:[ebx+5414C0]
004012F7 . 02C0 add al,al
004012F9 . C0EB 04 shr bl,4
004012FC . 02C0 add al,al
004012FE . 0AC3 or al,bl
00401300 . 8801 mov byte ptr ds:[ecx],al
00401302 . 0FBE4437 02 movsx eax,byte ptr ds:[edi+esi+2]
00401307 . 0FBE5C37 01 movsx ebx,byte ptr ds:[edi+esi+1]
0040130C . 0FB69B C0145400 movzx ebx,byte ptr ds:[ebx+5414C0]
00401313 . 0FB680 C0145400 movzx eax,byte ptr ds:[eax+5414C0]
0040131A . C0E3 04 shl bl,4
0040131D . C0E8 02 shr al,2
00401320 . 0AC3 or al,bl
00401322 . 8841 01 mov byte ptr ds:[ecx+1],al
00401325 . 0FBE5C37 02 movsx ebx,byte ptr ds:[edi+esi+2]
0040132A . 0FB69B C0145400 movzx ebx,byte ptr ds:[ebx+5414C0]
00401331 . 0FBE4437 03 movsx eax,byte ptr ds:[edi+esi+3]
00401336 . C0E3 06 shl bl,6
00401339 . 0A98 C0145400 or bl,byte ptr ds:[eax+5414C0]
0040133F . 83C6 04 add esi,4
00401342 . 8859 02 mov byte ptr ds:[ecx+2],bl
00401345 . 83C1 03 add ecx,3
00401348 . 3BF2 cmp esi,edx
0040134A .^7C 94 jl short main.004012E0
0040134C . 8B45 FC mov eax,dword ptr ss:[ebp-4]
0040134F . 5B pop ebx
00401350 > B2 3D mov dl,3D
00401352 . 385437 FE cmp byte ptr ds:[edi+esi-2],dl
00401356 . 75 10 jnz short main.00401368
00401358 . 5F pop edi
00401359 . 66:C741 FF 0000 mov word ptr ds:[ecx-1],0
0040135F . C641 FE 00 mov byte ptr ds:[ecx-2],0
00401363 . 5E pop esi
00401364 . 8BE5 mov esp,ebp
00401366 . 5D pop ebp
00401367 . C3 retn
00401368 > 385437 FF cmp byte ptr ds:[edi+esi-1],dl
0040136C . 75 0C jnz short main.0040137A
0040136E . 5F pop edi
0040136F . 66:C741 FF 0000 mov word ptr ds:[ecx-1],0
00401375 . 5E pop esi
00401376 . 8BE5 mov esp,ebp
00401378 . 5D pop ebp
00401379 . C3 retn
0040137A > 5F pop edi
0040137B . C601 00 mov byte ptr ds:[ecx],0
0040137E . 5E pop esi
0040137F . 8BE5 mov esp,ebp
00401381 . 5D pop ebp
00401382 . C3 retn

The following instruction changes the data block accordingly:

00401300 . 8801 mov byte ptr ds:[ecx],al

The ECX register points exactly to our data block at address 0x00B985D8 when the loop is first called. The register al contains the value that is being written into our data block. The following lines are used to compute the value in register al:

The edi register points to the Key 1 input value, whereas esi is 0 at the beginning but being incremented by 4 each iteration; we can see that at address 0x0040133F. We're reading bytes from Key 1 input argument and using that to select the value at address 0x005414C0. The memory residing at that address holds the following values:

005414C0 40 40 40 40 40 40 40 40 40 40 40 40 40 40 40 40
005414D0 40 40 40 40 40 40 40 40 40 40 40 40 40 40 40 40
005414E0 40 40 40 40 40 40 40 40 40 40 40 3E 40 40 40 3F
005414F0 34 35 36 37 38 39 3A 3B 3C 3D 40 40 40 40 40 40
00541500 40 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E
00541510 0F 10 11 12 13 14 15 16 17 18 19 40 40 40 40 40
00541520 40 1A 1B 1C 1D 1E 1F 20 21 22 23 24 25 26 27 28
00541530 29 2A 2B 2C 2D 2E 2F 30 31 32 33 40 40 40 40 40
00541540 40 40 40 40 40 40 40 40 40 40 40 40 40 40 40 40
00541550 40 40 40 40 40 40 40 40 40 40 40 40 40 40 40 40

We can quickly confirm that those values are always the same. This can be confirmed by inputing random gibberish into the Name and Key 1 input fields and observing this data structure, which doesn't change.

The algorithm used to compute the value in the register al that is later written into our data structure is the following:

Since we need to input printable characters into the input fields, the values in key[i] and key[i+1] can only range from 0x20 − 0x7F, which reads the values from the following range of addresses: 0x005414E0 − 0x0054153F.

Now we can control the values at the address 0x70, because our data structure gets copied onto the stack at 0x70 offset from esp.

Logical Code Segment 5

The next code segment just compares the values on the stack addresses [esp+70] and [esp+C0] to determine if they are the same. The code is as follows:

004017FC |. B8 40000000 mov eax,40
00401801 |. 33C9 xor ecx,ecx
00401803 |> 8B940C C000000>/mov edx,dword ptr ss:[esp+ecx+C0]
0040180A |. 3B540C 70 |cmp edx,dword ptr ss:[esp+ecx+70]
0040180E |. 0F85 53010000 |jnz main.00401967
00401814 |. 83E8 04 |sub eax,4
00401817 |. 83C1 04 |add ecx,4
0040181A |. 83F8 04 |cmp eax,4
0040181D |.^73 E4 jnb short main.00401803

We can see that 0x40 bytes is being compared at addresses [esp+0x70] and [esp+0xC0]. If the values are not the same our failure message box is being displayed and we must enter other input values. But if the stack memory regions are equal, the execution of the program can continue.

Conclusion

In this series we could see that a piece of stack memory was additionally changed and then compared to another piece of stack memory. If the memories contain the same values, the program execution will continue without an error, otherwise the failure message box will be shown.

Comments