Reverse engineering

CrackMe Challenge Part 5: Logical Code Segments Continued

September 4, 2012 by Dejan Lukan

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 (continued on next line)

@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:

AAAAAAAAAABBBBBBBBBBCCCCCCCCCCDDDDDDDDDDEEEEEEEEEE (continued on next line)

aaaaaaaaaabbbbbbbbbbccccccccccddddddddddeeeeeeeeee

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:

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

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:

  1. eax = [0x5414C0 + key1[i]]
  2. ebx = [0x5414C0 + key1[i+1]]
  3. al = al * 2
  4. bl = bl shift right by 4
  5. al = al * 2
  6. al = al xor bl

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.

Posted: September 4, 2012
Dejan Lukan
View Profile

Dejan Lukan is a security researcher for InfoSec Institute and penetration tester from Slovenia. He is very interested in finding new bugs in real world software products with source code analysis, fuzzing and reverse engineering. He also has a great passion for developing his own simple scripts for security related problems and learning about new hacking techniques. He knows a great deal about programming languages, as he can write in couple of dozen of them. His passion is also Antivirus bypassing techniques, malware research and operating systems, mainly Linux, Windows and BSD. He also has his own blog available here: http://www.proteansec.com/.