Crackme Chiwaka1 Study

imageThis article attempts to describe in detail the study of one entertaining crackme. It will be interesting first of all for beginners in reverse engineering (which I am, and which the article is mainly intended for), but perhaps more serious specialists will find the technique used in it interesting. Understanding this will require basic knowledge of assembler and WinAPI.

Foreword


The university assembler course was awakened by the spirit of a researcher dozing for a couple of years, which originated in high school. Several unpretentious crackme were quickly sacrificed to this spirit, but soon a copy of more interesting was found on the hard drive. Unfortunately, over the past years, the source of this crackme could not be found, so the archive was uploaded to Google Drive.

Tools used:
  • Debugger (I used OllyDbg )
  • A utility for getting window descriptors for running programs (for example, Zero Dump or supplied with Visual Studio Spy ++)
  • Some files included in the fasm archive

First look at the enemy


The appearance of the main window of the experimental is very simple: two edit'a and a few buttons. Moreover, the first input field is intended to enable writing to the second, which, judging by the Check button located next to it, will be the main obstacle to crack registration.
The About window contains one interesting inscription: “Patching is allowed, but where ??” preceding the most interesting. The whole essence of this inscription will be revealed in the research process, but for now we will not spoil it.
By the way, if someone has already seen this crackme and found a way to patch it (well, or if it seemed to some people to be very simple and he made it one left in the process of reading the article), then please tell everyone about the success, preferably also comments.
Attempts to provoke at least some reaction of the program are unsuccessful: no reaction to pressing the Enable and Check buttons follows. However, it is striking that in the active input field you can write only in Latin and put a minus sign. We notice this fact and begin work.

To battle!


To win this crackme, we need to deal with two input fields.

First edit and Enable button


Having loaded the program in OllyDbg, we immediately notice an interesting GetProcAddress near the entry point:

00401086 |. 68 56504000 PUSH OFFSET 00405056; / Procname = "" 10, "& 7", 14, "* - ', 4", 0F, ", - $", 02
0040108B |. FF35 9C504000 PUSH [DWORD DS: 40509C]; | hModule = NULL
00401091 |. E8 C0020000 CALL      ; \ KERNEL32.GetProcAddress

To see what is being transmitted to it, put the crack directly on the GetProcAddress call. Staying here, we see that the SetWindowLongA function "drew" in Procname , and the user32.dll descriptor in hModule:

00401086 |. 68 56504000 PUSH OFFSET 00405056; / Procname = "SetWindowLongA"
0040108B |. FF35 9C504000 PUSH [DWORD DS: 40509C]; | hModule = 767F0000 ('USER32')
00401091 |. E8 C0020000 CALL      ; \ KERNEL32.GetProcAddress

Calling this function through GetProcAddress hints that they wanted to hide it from us and something important happens here. To understand what exactly happens there, let's look at the parameters passed to this function. This can be done very quickly by putting the breaker on call eax, located a few commands below.
Why call eax?
By convention, the call to stdcall used in WinAPI functions requires that the integer value (that is, the address of the desired function) that GetProcAddress returns must be in the eax register.

After stopping at call eax, you can take a closer look at what went on the stack. Olly kindly offers us a decryption of the transmitted values:

0018FC04 / 00020876; | hWnd = 00020876, class = Edit
0018FC08 | FFFFFFFC; | Index = GWL_WNDPROC
0018FC0C | 0040302B; \ NewValue = Chiwaka1.40302B

A quick look at the description of the function and it becomes clear that here the window procedure of one of the edits is replaced. The instinct suggests that this is an active edit, but to be sure, you need to use the Zero Dump utility (the analogue of Spy ++ that is at hand). Dragging the scope of the Finder Tool to the active edit, we find that its descriptor matches the one passed to SetWindowLongA.
When you restart the crack, this value will change, so the check must be carried out in one sitting.

Now it’s clear where the legs grow from the input filtering we discovered earlier. Let's see what else the new window procedure does. Let's go to the address (Go To -> Expression or Ctrl + G in OllyDbg), which was passed by the NewValue parameter (40302b):

0040302B 55 PUSH EBP
0040302C 8BEC MOV EBP, ESP
0040302E 817D 0C 0201,000 CMP [DWORD SS: EBP + 0C], 102
00403 035 75 61 JNE SHORT 00403098
00403037 8B45 10 MOV EAX, [DWORD SS: EBP + 10]

The constant 102 that the 40302E is being compared to is WM_CHAR . I looked at the list of all the constants denoting the type of message in the file %fasm_directory%\INCLUDE\EQUATES\USER32.INC(link to the archive with fasm in the list of used tools), since the Internet was not at hand, but fasm was in the gentleman's student set.
Let’s go through the code as if the next JNE jump is not being performed. The code after the JNE will be executed if the WM_CHAR message arrives, which is sent to the window after pressing (or rather, after the WM_KEYDOWN message has been processed) the key corresponding to the ASCII code. In this post, we are interested in wParam, which contains the character code of the pressed key. Next, this value is written to EAX and a series of checks begins that do not allow the entry of anything other than the Latin and minus.

0040303A 3C 41 CMP AL, 41
0040303C 72 04 JB SHORT 00403042
0040303E 3C 5A CMP AL, 5A
00403 040 76 10 JBE SHORT 00403052
00403042 3C 61 CMP AL, 61
00403 044 72 04 JB SHORT 0040304A
00403046 3C 7A CMP AL, 7A
00403 048 76 08 JBE SHORT 00403052
0040304A 3C 08 CMP AL, 8
0040304C 74 04 JE SHORT 00403052
0040304E 3C 2D CMP AL, 2D
00403 050 75 61 JNE SHORT 004030B3
00403052 3C 08 CMP AL, 8
00403 054 75 12 JNE SHORT 00403068
00403056 833D D5504000 0 CMP [DWORD DS: 4050D5], 0
0040305D 74 09 JE SHORT 00403068
0040305F 832D D5504000 0 SUB [DWORD DS: 4050D5], 1

The reaction to backspace (code 8 in ASCII) is interesting: on 403056, a certain value in memory is checked for equality to zero and the value is decremented to one if it is not zero. This behavior means that the length of the entered string is counted. And yes, this really happens a bit further (last command):

00403069 8B0D D5504000 MOV ECX, [DWORD DS: 4050D5]
0040306F 8881 70504000 MOV [BYTE DS: ECX + 405070], AL
00403075 8305 D5504000 0 ADD [DWORD DS: 4050D5], 1

It is also clearly visible that the entered character is written into memory at the end of the already entered sequence. In calculating the place for writing a character, the same address (4050D5) appears, the value at which is the length of the entered string.

Conclusion: to find the place where the data entered is verified, the search for the appropriate GetDlgItemText (since it will not be there) will not help, but the breakdown in the memory to which the recording will take place will help.

So, let's set the breaker to read a few bytes, starting with the address 405070. It also makes sense to play it safe and set a breakpoint to read the number of characters entered (it was previously established that this is 4050D5). The second is best done just before clicking Check, so as not to be distracted by stops while entering a line. Now run the program, enter something in the first edit and click on Check.
Hidden text
The bytes located in the place where the crack is triggered (and also a lot more where in this crack), are in such a sequence that it can be interpreted in different ways. If you try to shift higher in commands, then the place of operation of the crack will be reanalyzed and will look different. To fix this, just go to -> Expression (Ctrl + G) to go to the address of the crack.

We see that the stop occurred on recording the number of characters entered in EAX, and the next command compares the 11th character (405070 + A) of the entered string with a minus (2D - minus code in ASCII).

00402007 A1 D5504000 MOV EAX, [DWORD DS: 4050D5] <- memory breakpoint when reading 4050D5
0040200C 803D 7A504000 2 CMP [BYTE DS: 40507A], 2D
00402013 75 1E JNE SHORT 00402033
00402015 33C9 XOR ECX, ECX
00402017 33DB XOR EBX, EBX
00402019 80B9 70504000 0 CMP [BYTE DS: ECX + 405070], 0               
00402020 74 11 JE SHORT 00402033
00402022 8A99 70504000 MOV BL, [BYTE DS: ECX + 405070]              
00402028 03C3 ADD EAX, EBX
0040202A 83C1 03 ADD ECX, 3
0040202D EB EA JMP SHORT 00402019
0040202F 33C0 XOR EAX, EAX
00402031 5E POP ESI
00420 032 58 POP EAX
00402033 C605 7A504000 0 MOV [BYTE DS: 40507A], 0
0040203A C3 RETN

It is clearly visible that we do not need a jump after comparison, since further (from 402019) a certain cycle is launched, which we hardly want to miss. Let's check this assumption and falsify the result of comparing the 11th character with a minus by modifying the ZF flag (well, or just restart the check with the appropriate string for this comparison).
In this cycle, every third entered character is checked to the first zero and the codes of these characters are added to the EAX register, which, as we remember, is the length of the entered string. At 4020300, instead of a minus, zero is written and the verification is completed.
Let's follow the RETN.

004010F8 /. 3D 0A030000 CMP EAX, 30A
004010FD |. 0F85 C6000000 JNE 004011C9
00401103 |. FF75 08 PUSH [DWORD SS: EBP + 8]
00401106 |. E8 380F0000 CALL 00402043
0040110B |. C605 A4504000 MOV [BYTE DS: 4050A4], 1
00401112 |. 68 12504000 PUSH OFFSET 00405012; / Text = "Well done! Keep going ;-)"
00401117 |. 6A 65 PUSH 65; | ControlID = 101.
00401119 |. FF75 08 PUSH [DWORD SS: EBP + 8]; | hDialog => [ARG.EBP + 8]
0040111C |. E8 23020000 CALL       ; \ USER32.SetDlgItemTextA

The first command we see comparing the value of EAX with 30A and jumping somewhere in case of inequality. Next, the coveted “Well done! Keep going ;-) ” . Now you need to see how to get there. Next to the cherished SetDlgItemTextA, a call is noticeable, which we definitely need to go to. To get there, you must pass a test at 30A. The easiest way to do this is by modifying the ZF flag before the JNE jump.
Having executed Call, we see the following:

00420 043 55 PUSH EBP
00402044 8BEC MOV EBP, ESP
00420 046 53 PUSH EBX
00402047 68 70504000 PUSH OFFSET 00405070; start of entered line
0040204C FF35 9C504000 PUSH [DWORD DS: 40509C]
00402052 E8 FFF2FFFF CALL       ; Jump to kernel32.GetProcAddress
00402057 6A 66 PUSH 66
00402059 FF75 08 PUSH [DWORD SS: EBP + 8]
0040205C FFD0 CALL EAX
0040205E 50 PUSH EAX
0040205F 68 7B504000 PUSH OFFSET 0040507B; 12th character of the entered string
00402064 FF35 9C504000 PUSH [DWORD DS: 40509C]
0040206A E8 E7F2FFFF CALL       ; Jump to kernel32.GetProcAddress
0040206F 5B POP EBX
00402 070 53 PUSH EBX
00402071 FFD0 CALL EAX
00402073 68 70504000 PUSH OFFSET 00405070; start of entered line                     
00402078 FF35 9C504000 PUSH [DWORD DS: 40509C]
0040207E E8 D3F2FFFF CALL       ; Jump to kernel32.GetProcAddress
00402083 68 E8030000 PUSH 3E8
00402088 FF75 08 PUSH [DWORD SS: EBP + 8]
0040208B FFD0 CALL EAX
0040208D 50 PUSH EAX
0040208E 68 7B504000 PUSH OFFSET 0040507B; 12th character of the entered string
00402093 FF35 9C504000 PUSH [DWORD DS: 40509C]
00402099 E8 B8F2FFFF CALL       ; Jump to kernel32.GetProcAddress
0040209E 5B POP EBX
0040209F 6A 00 PUSH 0
004020A1 53 PUSH EBX
004020A2 FFD0 CALL EAX

Four GetProcAddress into which parts of the string entered by us are passed! The first time the address is the beginning of the line, the second is the address of 12 characters, and then again the beginning and again the 12th character. As we remember, in place of 11 characters (which should be a minus), zero is written. Now it’s clear that this is necessary in order to pass a null-terminated string to GetProcAddress.
Now we can pose a very definite task: find two functions from user32.dll (the handle passed to GetProcAddress testifies to this), each of which has two parameters (the number of push'eys before call eax), the first of which is ten characters long.
We narrow the search circle by looking at the parameters that are passed and comparing them with what should happen after pressing the Enable button. We have already seen SetDlgItemTextA with an encouraging inscription, and there is a chance that it will appear in the first input field, since we still need the second. And the second field should be made active, because we still click on the Enable button!
To quickly look at all the functions with a length of 10 characters from user32.dll, I used a search in the built-in headlight editor for the file %fasm_directory%\INCLUDE\API\USER32.INC. For search applied regexp '\s\i{10}\,'. You could further choose from the found functions those that have two parameters, but this was not necessary, since GetDlgItem is ideally suited for. This idea is confirmed by the “magic constant” 66h, which is the ID of the second, as yet inactive input field (this can be seen using zDump or a similar utility). This value is the first parameter at GetDlgItem.
It remains only to activate the second input field. For two parameters, EnableWindow can do this perfectly .
Checking the found GetDlgItem-EnableWindow string and ... success!



With the first input field sorted out, go to the "main dish".

Second edit and decisive hit on the Check button


Since the replacement of the window procedure was performed only for one edit, the text from the second should (in any case there is reason to hope for it) be extracted in a more trivial way. We will use the Search for -> All intermodular calls command and see what can be used to get text from the second input field.

All intermodular calls
0040103C CALL        7684CB0C USER32.DialogBoxParamA TemplateName = 1, hParent = NULL, DialogProc = Chiwaka1.401061, InitParam = 0
004011B2 CALL        7684CB0C USER32.DialogBoxParamA TemplateName = 2, hParent = NULL, DialogProc = Chiwaka1.401206, InitParam = 0
004011C4 CALL              7682B99C USER32.EndDialog Result = 0
00401214 CALL              7682B99C USER32.EndDialog Result = 1
0040123D CALL              7682B99C USER32.EndDialog Result = 1
0040104C CALL          765779C8 kernel32.ExitProcess
00401072 CALL             7682F1BA USER32.GetDlgItem ItemID = 101.
00401 002 CALL     76571245 kernel32.GetModuleHandleA ModuleName = NULL
00401091 CALL       76571222 kernel32.GetProcAddress Procname = "" 10, "& 7", 14, "* - ', 4", 0F, ", - $", 02
00401168 CALL       76571222 kernel32.GetProcAddress Procname = "" 04, "& 7", 07, "/ $", LF, "7 &.", 17, "&; 7", 02
00401268 CALL       76571222 kernel32.GetProcAddress
004010C4 CALL           7681612E USER32.SendMessageA Msg = WM_COMMAND
0040111C CALL        7681C4D6 USER32.SetDlgItemTextA ControlID = 101., Text = "Well done! Keep going ;-)"
004011DC CALL        7681C4D6 USER32.SetDlgItemTextA ControlID = 101., Text = "Careful now !!"

The most suitable candidate for a function that extracts text from the second input field seems to be one from GetProcAddress, which probably gets the address GetDlgItemTextA . To verify this, run the program, unlock the second field with the found line, and then put the breaks on all three GetProcAddress. Now you can click on Check and make sure that the forecasts are correct.

0040115D |> \ 68 46504000 PUSH OFFSET 00405046; / Procname = "GetDlgItemTextA"
00401162 |. FF35 9C504000 PUSH [DWORD DS: 40509C]; | hModule = 767F0000 ('USER32')
00401168 |. E8 E9010000 CALL      ; \ KERNEL32.GetProcAddress    
0040116D |. 6A 40 PUSH 40
0040116F |. 68 70504000 PUSH OFFSET 00405070
00401174 |. 6A 66 PUSH 66
00401176 |. FF75 08 PUSH [DWORD SS: EBP + 8]
00401179 |. FFD0 CALL EAX

Next, the number of characters entered is checked (GetDlgItemText returns it to EAX), the value is written to memory, and four functions are called. Most likely, it is in these four functions that some actions with the string occur, so we will examine them one by one.

Having glanced at the first call, we observe some frauds with characters in a line, performed in a loop until the first zero is detected (402126):

CALL 00402101
00402101 8D05 70504000 LEA EAX, [405070]; entered string
00402 107 EB 1D JMP SHORT 00402126
00402109 8038 40 CMP [BYTE DS: EAX], 40; 40h = @ (last character in ASCII before the Latin alphabet in uppercase)
0040210C 76 0A JBE SHORT 00402118
0040210E 8038 5B CMP [BYTE DS: EAX], 5B; 5Bh = [(first character after Latin in uppercase)
00402111 73 05 JAE SHORT 00402118
00402113 8000 20 ADD [BYTE DS: EAX], 20; 20h - the difference between the letter in different registers
00402116 EB 0D JMP SHORT 00402125
00402118 8038 60 CMP [BYTE DS: EAX], 60; 60h = '(last character before lowercase Latin letters)
0040211B 76 08 JBE SHORT 00402125
0040211D 8038 7B ​​CMP [BYTE DS: EAX], 7B; 7Bh = {(the first character after the Latin alphabet is lowercase)
00402 120 73 03 JAE SHORT 00402125
00402122 8028 20 SUB [BYTE DS: EAX], 20; 20h - the difference between the letter in different registers                 
00402 125 40 INC EAX
00402126 8038 00 CMP [BYTE DS: EAX], 0
00402129 75 DE JNE SHORT 00402109
0040212B C3 RETN

Having looked at the constants that are found here, and then at the ASCII table, it is easy to notice that this call inverts the case of each of the entered characters. Everything turned out to be very simple.

CALL 00402133
00402133 53 PUSH EBX
00402 134 33DB XOR EBX, EBX
00402 136 33D2 XOR EDX, EDX
00402138 8D05 70504000 LEA EAX, [405070]; entered string
0040213E 50 PUSH EAX
0040213F 8B0D A5504000 MOV ECX, [DWORD DS: 4050A5]
00421 045 51 PUSH ECX
00402146 49 DEC ECX
00402147 8A1401 MOV DL, [BYTE DS: EAX + ECX]
0040214A 8893 A9504000 MOV [BYTE DS: EBX + 4050A9], DL
0402150 43 INC EBX
00402151 83F9 00 CMP ECX, 0
00402154 ^ 75 F0 JNE SHORT 00402146
00402156 59 POP ECX
00402157 58 POP EAX
00402 158 49 DEC ECX
00402159 33DB XOR EBX, EBX
0040215B 33D2 XOR EDX, EDX
0040215D 33C0 XOR EAX, EAX
0040215F EB 1A JMP SHORT 0040217B
00402161 8A99 70504000 MOV BL, [BYTE DS: ECX + 405070]; entered string
00402167 8A90 70504000 MOV DL, [BYTE DS: EAX + 405070]; entered string
0040216D 8891 70504000 MOV [BYTE DS: ECX + 405070], DL
00402173 8898 70504000 MOV [BYTE DS: EAX + 405070], BL
00402 179 49 DEC ECX
0040217A 40 INC EAX
0040217B 83F8 05 CMP EAX, 5
0040217E ^ 72 E1 JB SHORT 00402161
00402180 C605 7A504000 4 MOV [BYTE DS: 40507A], 41
00402187 5B POP EBX
00421 088 C3 RETN

The second call is a little larger, and several actions are performed in it. The first step is to copy the entered line backwards to another location in memory using the following cycle:

00402146 49 DEC ECX
00402147 8A1401 MOV DL, [BYTE DS: EAX + ECX]
0040214A 8893 A9504000 MOV [BYTE DS: EBX + 4050A9], DL
0402150 43 INC EBX
00402151 83F9 00 CMP ECX, 0
00402154 ^ 75 F0 JNE SHORT 00402146

Then the same thing happens with the original string, but only 10 characters are inverted. This may indicate that in the future only they will be used, but it is too early to guess.
And finally, writing to the 11th position of the original string of the character 'A' is performed:

00402180 C605 7A504000 4 MOV [BYTE DS: 40507A], 41

Remembering the trick already used in this crack, we can assume that there will be something similar, however, the WinAPI function will not be introduced in a ready-made form.

CALL 00401285 brings us to another call:

CALL 004012C4
004012C4 / $ 8D05 BD504000 LEA EAX, [4050BD]
004012CA |. 33D2 XOR EDX, EDX
004012CC |. 8A15 F0204000 MOV DL, [BYTE DS: 4020F0]
004012D2 |. 8810 MOV [BYTE DS: EAX], DL
004012D4 |. 8A15 31214000 MOV DL, [BYTE DS: 402131]
004012DA |. 8850 01 MOV [BYTE DS: EAX + 1], DL
004012DD |. 8A15 AE204000 MOV DL, [BYTE DS: 4020AE]
004012E3 |. 8850 02 MOV [BYTE DS: EAX + 2], DL
004012E6 |. 8A15 CF204000 MOV DL, [BYTE DS: 4020CF]
004012EC |. 8850 03 MOV [BYTE DS: EAX + 3], DL
004012EF |. 8A15 41204000 MOV DL, [BYTE DS: 402041]
004012F5 |. 8850 04 MOV [BYTE DS: EAX + 4], DL
004012F8 |. 8A15 40204000 MOV DL, [BYTE DS: 402040]
004012FE |. 8850 05 MOV [BYTE DS: EAX + 5], DL
00401301 |. 8A15 31214000 MOV DL, [BYTE DS: 402131]
00401307 |. 8850 06 MOV [BYTE DS: EAX + 6], DL
0040130A |. 8A15 05204000 MOV DL, [BYTE DS: 402005]
00401310 |. 8850 07 MOV [BYTE DS: EAX + 7], DL
00401313 |. 8A15 83124000 MOV DL, [BYTE DS: 401283]
00401319 |. 8850 08 MOV [BYTE DS: EAX + 8], DL
0040131C |. 8A15 5B124000 MOV DL, [BYTE DS: 40125B]
00401322 |. 8850 09 MOV [BYTE DS: EAX + 9], DL
00401325 \. C3 RETN

Here, manipulations take place with memory that is not associated with either the original string or its copy, so without going into the intricacies of what is happening, let's see what happens after the return. And after returning, a copy of the entered string is processed using xor'a. Most likely, one of these two lines should end up being a WinAPI function, and some should be a message about successful registration.

The last call will clarify everything.

CALL 0040125D
0040125D / $ 68 70504000 PUSH OFFSET 00405070; / Procname = "the entered line back and forth with the inverted register and the character 'A' at the end"
00401262 |. FF35 9C504000 PUSH [DWORD DS: 40509C]; | hModule = 767F0000 ('USER32')
00401268 |. E8 E9000000 CALL      ; \ KERNEL32.GetProcAddress
0040126D |. 6A 30 PUSH 30
0040126F |. 68 BB124000 PUSH 004012BB; ASCII "API-API"
00401274 |. 68 A9504000 PUSH OFFSET 004050A9; ASCII "entered string backwards with inverted register, to which xor is applied"
00401279 |. 6A 00 PUSH 0
0040127B |. FFD0 CALL EAX
0040127D \. C3 RETN

It remains just a little bit: again to solve the problem of finding a suitable WinAPI function. Here is what we know about her:
  • name length - 11 characters, letter A at the end
  • located in user32.dll
  • four parameters are passed, of which two parameters are strings

To search for a suitable function, I used a file search %fasm_directory%\INCLUDE\PCOUNT\USER32.INCby the following regular expression '^\i{10}\%\s\=\s\s4'. The ANSI versions of functions are not duplicated in this file; therefore, large A cannot be specified at the end. There weren’t so many suitable functions, but in terms of meaning only one fits perfectly: MessageBoxA .
Remembering what operations are performed with it before being transferred to GetProcAddress, we will transform accordingly: MessageBox - (case inversion) - mESSAGEbOX - (character order inversion) - XObEGASSEm .
We enter the coveted line in the second input field and get the winning MessageBox with a window title reminiscent of adventures!



Instead of a conclusion


Cracky investigated demonstrates an interesting way to firmly “bind” the protection to the program itself. The mechanism used makes it quite difficult to patch. It is difficult for me to imagine the application of this method in commercial defense (however, I will be glad if someone talks about examples), but it is very good for a couple of days puzzle, especially for beginners.

Also popular now: