Ways to bypass protection and hacking programs


In order to be able to break programs that are guarded by tricky defense mechanisms, it is necessary to know different ways of finding these very mechanisms in an experimental application. In today's article it will be shown how to do this, and in the end we will examine an example of working with a graphical application.

CProgramming hacking method 1. Search for entered password in memory

The password stored in the body of the program in clear text is rather an out of the ordinary exception than the rule. What are the services of a hacker, if the password is already visible to the naked eye? Therefore, the developers of protection in every possible way try to hide it from prying eyes (we will talk later about how exactly they do it).
However, given the size of modern packages, a programmer can easily put a password in some stray file, while providing it with “crackers” - lines that look like a password but are not a password. Try to figure out where the linden is, and where it is not, especially since there may be several hundreds or even thousands of lines suitable for this role in a project!
Let's approach the solution of the problem from the reverse - we will not look for the original password, which is unknown to us, but the line that we fed to the program as a password. And having found it, we will install a bryak on it, and then everything will be exactly the same as before. Bryak pops up on the appeal compared to, we exit the comparing procedure, adjust the JMP and ...
Take another look at the source code of the example we are breaking passCompare1.cpp:
for (;;) 
{ 
    printf ("Enter password:"); 
    fgets (& buff [0], PASSWORD_SIZE, stdin); 
    if (strcmp (& buff [0], PASSWORD)) 
        printf ("Wrong password \ n"); 
    else break; 
    if (++ count> 2) return -1; 
}
Please note buffthat the password entered by the user is read, compared with the original, then (if the comparison fails), it is requested again, but (!) Is buffnot cleared! It follows that, if after issuing a Wrong password call a debugger and walking through the context search, you can find that cherished buff, and the rest is a matter of technology!
So let's get started (we still don't know what we are getting involved in, but, alas, everything is more complicated in life than in theory). This time, run passCompare1.exeseparately from the debugger. Then we connect to the process from the debugger (Attach to process in WinDbg). I want to draw your attention: in the process selection window all running processes are displayed and for each of them its capacity is displayed in the Platform column. Enter any password that came to your mind (for example, KPNC Kaspersky++), skip the angry Wrong cry past the ears and press Break in the debugger (Alt + Del).

Process list window for selection

Let's try to find the entered password in memory:
1 0: 001> s -a 0x0 L? 0x7FFFFFFF "KPNC Kaspersky"  

PExplanations

The first parameter after the command s, the flag -a, defines the search target as a set of ASCII characters. The second parameter is the offset from where to start searching. In fact, starting a search with a zero offset is a stupid idea. Judging by the memory card, the service code is located here and the password cannot be found. However, it does not harm anything, and so much faster than to figure out what address the program is loaded from and where exactly to start the search.
The third parameter is the upper limit of the search, that is, the "where". Here we have the maximum 32-bit signed number, so we cover the whole possible range of the 32-bit process.
The last parameter is the desired string itself. Please note that we are not looking for the entire line, but only part of it ( KPNC Kaspersky++against KPNC Kaspersky). This allows you to get rid of false positives arising from references to internal buffers.
Result (your values ​​are likely to be different, and they will change each time you restart the application):
00f9f810 4b 50 4e 43 20 4b 61 73-70 65 72 73 6b 79 2b 2b KPNC Kaspersky ++ 
0147fd80 4b 50 4e 43 20 4b 61 73-70 65 72 73 6b 79 2b 2b KPNC Kaspersky ++  
Two whole entries! Why two? Suppose that when reading keyboard input, the characters first get into the system buffer, which gives a false positive. Nevertheless, do not put the same, both at once, both breakpoints. In this case, there are enough four debug registers of the processor, but what if we found a dozen entries? Yes, and in two breakpoints no wonder get lost from the habit! How to filter out noise?
We start to think.
A memory card comes to the rescue - knowing the owner of the region that owns the buffer, you can say a lot about this buffer. Having quickly stuffed an already familiar command !dh passCompare1, we will get something like the following (information about sections .dataand was selected only .rdata):
SECTION HEADER # 2 
.rdata name 
A7E virtual size 
2000 address virtual 
C00 size of raw data 
1200 file pointer to raw data 
0 file pointer to relocation table 
0 file pointer to line numbers 
0 number of relocations 
0 number of line numbers 
40000040 flags 
Initialized Data 
( the align specified 'the no) 
the Read Only 
 
the SECTION the HEADER # 3 
.data name 
388 virtual size 
3000 virtual address 
200 of the raw data size bed 
1E00 file pointer to the raw data 
0 file pointer to relocation table 
0 file pointer to line numbers 
0 number of relocations 
0 number of line numbers 
C0000040 flags
Initialized Data 
(no align specified) 
Read Write
At the same time, we define the base address of the application module: lmf m passCompare1(in my particular case it is equal 0xDE0000, and your value will most likely be different). Know where the section is loaded in memory .rdata0xDE0000 + 0x2000 == 0xDE2000and where the loaded section .data0xDE0000 + 0x3000 == 0xDE3000This is much higher than the location of the buffers with the entered password. Therefore, the addresses found do not indicate in the area .dataand .rdata.
We think further. The address 0x147fd80goes far beyond the limits of the application being broken, and in general it is not clear what it belongs to. Having scratched the back of the head, we will recall such “goodies” of Windows as a heap. With the help of the command, !heaplet's see where it starts:
Index Address Name Debugging options enabled 
1: 01470000
From this we conclude that the address is 0x147fd80clearly in the heap.
We understand further. As the stack grows from bottom to top (that is, from higher addresses to lower ones), the address 0xf9f810is on the stack. Confidence fosters the fact that most programmers allocate buffers in local variables, and local variables, in turn, are placed by the compiler on the stack.
Well, let's try to breakout at the first address?
0: 001> ba r4 00f9f810 
0: 001> g
On the second password request, enter again KPNC Kaspersky++We press Enter and wait for the momentary activation of the debugger. The breakpoint occurred on the second of these lines:
77c349f3 8806 mov byte ptr [esi], al 
77c349f5 46 inc esi 
77c349f6 8975d8 mov dword ptr [ebp-28h], esi
Look what is in the register esi:
dc esi 
00f9f810 434e504b 73614b20 73726570 2b2b796b KPNC Kaspersky ++
However, this was to be expected. Let's try to exit the current function by Shift + F11. And we again get on the same line. Again, look at the contents of this register:
00f9f811 20434e50 7073614b 6b737265 0a2b2b79 PNC Kaspersky ++.
Yeah, one character is bitten off. Therefore, we are in a comparing procedure. Let's get out of it by pressing F5, because when you press Shift + F11, we will move on to the next iteration of enumerating characters.
00de10e0 b80821de00: mov eax, offset passCompare1 `: string '(00de2108)! 
00de10e5 8a11: mov the dl, byte ptr [ecx] 
00de10e7 3a10 cmp the dl, byte ptr [eax] ds: 002B: 00de2108 = 6d 
00de10e9 751A jne passCompare1 main + 0xc5 (! 00de1105)
And here we are in the body already familiar to us (develop visual memory!) Procedures for comparing the original and user-entered passwords. Just in case, for greater conviction, we derive the meaning of the EAX and ECX pointers to find out what is being compared with:
0: 000> dc eax 
00de2108 4f47796d 6170444f 6f777373 000a6472 myGOODpassword .. 
0: 000> dc ecx 
00f9f810 434e504b 73614b20 73726570 2b2b796b KPNC Kaspersky ++
Just what we are looking for!
Well, the rest we have already passed. We write down the address of the conditional jump (the key sequence for the search), using the information from the previous article, we find the address of the instruction on the media corresponding to the projected memory, correct the executable file, and everything is ok.

BConclusions

So, we got acquainted with one more or less universal way of hacking protection based on password comparison (later we will see that it is also suitable for protection based on registration numbers). Its main advantage is simplicity. And there are a lot of disadvantages ... there are many disadvantages:
  • if the programmer clears the buffer after the comparison, the search for the entered password will not do anything, unless there are system buffers that are not so easy to overwrite, but it is not so easy to track the password transfers from the system buffers to the local ones;
  • there are many service buffers, and it is very difficult to determine which one is “real”. A programmer can also allocate a buffer in a data segment (static buffer), and in a stack (local buffer), and in a heap, and even allocate memory with low-level calls like VirtualAlloc or ... but how much imagination will play out. As a result, it is sometimes necessary to sift all the occurrences found by blunt brute force.

CHow to hack programs 2. Blade on the password entry function

InBreaking with GUI applications

It is time to diversify our object hacking. Now we’ll try to build a GUI application. In quality of training we will sort passCompare3This is the same as passCompare1.exewith the GUI based on the MFC Dialog Based App (see downloadable materials for the article).


Also note that the text is different in this example. If earlier we worked with the base type char, then a wrapper is used here - a class CStringthat, most likely, we will meet more often when hacking professional applications. In addition to the two buttons that go to the blank by default, add an Edit Control element to the form. Bind it to a variable m_passwordand create an event to handle clicking on the OK button. This will be the key application procedure that checks the entered password for equality to the reference one:
const CString PASSWORD = _T ("myGOODpassword"); 
... 
void CpassCompare3Dlg :: OnBnClickedOk () 
{ 
    CString str = NULL; 
 
    m_password.GetWindowText (str); 
    if (PASSWORD.Compare (str)) 
    { 
        MessageBox (_T ("Wrong password")); 
        m_password.SetSel (0, -1, 0); 
        return; 
    } 
    else 
    { 
        MessageBox (_T ("Password OK")); 
    } 
    CDialogEx :: OnOK (); 
}
It seems no surprises foreseen.
With all the desire, the method of direct password search in the memory cannot be called elegant, nor is it practical. And actually, why search for the password itself, stumbling over randomly scattered buffers, when you can put a bounce directly on the function that reads it? Hmm, you can and so ... yes, so guess what function the developer decided to read the password is unlikely to be much easier.
In fact, the same action can be performed with just a few functions and going through them will not take much time. In particular, the contents of the editing window are usually extracted using either a function GetWindowTextW(most often) or a function GetDlgItemTextW(and this is much less common). All versions of Windows NT and younger prefer to work with unicode, so at the end of the functions of working with text W(wide), and not A(ASCII).
If we are talking about windows, run our GUI "crack mace" and set a breakpoint on the function GetWindowTextWbp User32!GetWindowTextW). Although this function is system, the breakpoint will not be global and will not affect all applications in the system, but will function only in the context of this application.
KPNC Kaspersky++Enter some password ( as usual), press the Enter key, and the debugger immediately pops up:
! USER32 GetWindowTextW: 
7510a8e0 6a10 the push 10h 
7510a8e2 68e0041875 the push offset USER32 __ HrLoadAllImportsForDll + 0x1e9 (751804e0)! 
7510a8e7 e88c510200 call USER32 _SEH_prolog4 (7512fa78)! 
7510a8ec 8b750c: mov esi, dword ptr [ebp + 0Ch] 
7510a8ef 85f6 test esi, esi
It may happen that the “left” activation of the debugger occurs first. You can skip it by repeatedly pressing F5 until the debugger reappears on the same function. And it is better not to miss the right moment, as soon as we get in GetWindowTextW, get out of it by pressing Shift + F11 and see where we are. If there are calls to redraw elements around, then this means that this is a procedure to redraw the form and we are not here.
Redrawing elements:
6b08a247 0f84804f0200 je COMCTL32! Button_DrawThemed + 0x2519b (6b0af1cd) 
6b08a24d 57 push edi 
6b08a24e 50 push eax 
6b08a24f ff33 push dword ptr [ebx] 
6b08a251 ff15c0f51d6b call dword ptr [COMCTL32! _Imp__GetWindowTextW (6b1df5c0)] 
6b08a257 8bcb mov ecx, ebx 
6b08a259 e80b010000 call COMCTL32 ! Button_GetTextFlags (6b08a369)
If, however, after exiting, USER32!GetWindowTextWwe enter passCompare3!CWnd::GetWindowTextW, we make another exit. Following the logic, we end up with a handler for pressing the OK button on the form or Enter on the keyboard, right on the first line of the listing below:
E8c6750100 call passCompare3 00ef2809! The CWnd :: GetWindowTextW (00f09dd4) 
00ef280e 8b45ec: mov eax, dword ptr [ebp-14h] 
00ef2811 85c0 test eax, eax 
... 
00ef284b 1bc0 sbb eax, eax 
00ef284d 83c801 or eax, 1 
00ef2850 8bce mov ecx, esi
Hit F10 to take a step forward in the program trace. Now we can find out the value in the eax register:
0: 000> dc eax 
014a6cb8 0050004b 0043004e 004b0020 00730061 KPNC. Kas 
014a6cc8 00650070 00730072 0079006b 002b002b persky +. +.
Well, the entered password, there is a contact. But why is there a period after each character? I think you have already guessed that it means the two-byte nature of the character before it. Taking a sip of beer, kvass or lemonade (optional), we recall that, although the class CStringcan work with types char(single-byte representation of characters) and wchar_t(multibyte representation up to four bytes, that is, unicode in UTF-8, -16 or -32), it depends on the compiler settings. Namely, which character is included: MBCS- char, UNICODE- wchar_t. The second set of characters is most often used, since wide characters are included by default.
Apparently, the reference password lurked somewhere nearby. We will do a couple of steps inside the procedure. We get to the line 00ef2850 8bce mov ecx, esithat is at the end of the listing above.
Check the contents of the ecx register:
0: 000> dc ecx 
01437518 0079006d 004f0047 0044004f 00610070 myGOODpa 
01437528 00730073 006f0077 00640072 abab0000 ssword ... ..
And really! Intuition did not let us down, the reference password is right there.


Andchange the data type

And what if the programmer used not the class CString, but in the old manner - an array of wide characters to save the entered password wchar_tLet's see the sample passCompare35It differs from the previous one only in changing the data type of the read string and using the overloaded method to read it:
wchar_t str [MAX_PASSWORD_SIZE]; 
... 
m_password.GetWindowText (str, MAX_PASSWORD_SIZE);
As you can see, this method appeared string parameter dimension, it is the value of a string buffer, add it to your ad in the beginning of the program: const int MAX_PASSWORD_SIZE = 0x666;.
Let's set a debugger on the executable file. Put the bryak on the function GetWindowTextW, as in the previous example. Now, if we trace the execution of the program after the debugger ascends, we will not find the reference password in the same place.
What to do? How to find him now? We will go the other way, but in the same direction. When we are in passCompare3!CWnd::GetWindowTextWafter the call User32!GetWindowText, we will already have the buffer read from the control containing the string. If we put bryak on this buffer, we will get to the place where the passwords are compared.
There we will catch the reference password. But how to find out the buffer address? There are at least two ways. The first is to use the command kp, it will display a stack of calls for all functions with their parameters. At the top there will be the last function called, in which we are now, with parameters:
0: 000> kp 
# ChildEBP RetAddr 
! 00 004fe4b8 00e127c0 passCompare35 CWnd :: GetWindowTextW (wchar_t * lpszString = 0x004fe4d0 "KPNC Kaspersky ++", int nMaxCount = 0n1638) + 0x24 [f: \ dd \ vctools \ vc7libs \ ship \ atlmfc \ src \ mfc \ winocc.cpp @ 255] 
...


The second way is to use the information from the Locals window (useful thing): View → Locals. If the programmer left the password in local variables of a function, we would simply see it in the Locals window. Convenient, of course. So, you found out the address of the buffer with the password (in your case, it will be different), it remains with a flick of the wrist:
ba r4 0x004fe4d0
Continue execution. The debugger immediately flashes again in the function passCompare35!CpassCompare35Dlg::OnBnClickedOk, right on the last line:
! passCompare35 CpassCompare35Dlg :: OnBnClickedOk: 
... 
00e127bb e805c50100 passCompare35 call the CWnd :: GetWindowTextW (00e2ecc5)! 
00e127c0 a1c4fefd00: mov eax, dword ptr [passCompare35 the PASSWORD (00fdfec4)!] 
00e127c5 8d8d30f3ffff lea ecx, [ebp-0CD0h] 
00e127cb 0f1f440000 nop dword ptr [eax + eax] 
00e127d0 668b10: mov dx, word ptr [eax] 
00e127d3 663b11 cmp dx, word ptr [ecx] 
00e127d6 751e jne passCompare35! CpassCompare35Dlg :: OnBnClickedOk + 0x66 (00e127f6) [br = 1] 
...
The highlighted line and the code to it are extremely similar to our protective mechanism. Check the values ​​stored in the registers:
0: 000> du eax 
01547af0 "myGOODpassword" 
0: 000> du ecx 
012fe838 "KPNC Kaspersky ++" <code>
User-entered string and reference password, as on a silver platter! Just changing the data type can transform the hacking process.
Wonderful! So, without any false positives, elegantly, quickly and beautifully, we bypassed the protection of the program.
This method is universal, and later we will use it more than once. All salt - to define key function of protection and to put on it bryak. In Windows, all inclinations (references to a key file, registry, etc.) are reduced to calling API functions, the list of which, although long, is finite and known in advance.

Commentaires

You are welcome to share your ideas with us in comments!