< Day Day Up > |
Now that you have reviewed buffer overflows, the following example will let you test what you have learned using a special crackme (test application).
The Analyst first posed this little crackme, and the solution is reprinted with permission from the publisher (+Tsehp). When you run the program, you will see a command-line program asking you to enter the serial number to unlock the program (Figure 5-5). However, you do not know the serial number. You have to guess it. If you guess correctly, you get a "congratulations" message. Figure 5-5. weird.exe buffer overflow crackmeFirst try entering a serial such as "IOWNU". Since it is incorrect, there will be no response. Hitting Return again closes the program. After trying a few guesses, you will quickly realize that there's a better way to find the correct serial. You can try writing a program to brute force it, but that's not very elegant. It is time to fire up our Windows reverse engineering tools. Please note that the only rule in this puzzle is that you are not allowed to perform any opcode patching on the target .exe . Using the reverse engineering techniques from the previous chapters, we will solve this crackme and find the correct serial. The tools you need are as follows :
First, open IDA and disassemble weird.exe . We go straight to the Strings window and find the "congratulations" string (Figure 5-6). Double-clicking this takes us to the target code (Figure 5-7). Figure 5-6. String disassembly of weird.exeFigure 5-7. Screenshot of target code in IDA ProThere is no hard and fast rule on how to approach cracking an application. RCE is more of an art than a science, and it often depends on luck and intuition just as much as skill and experience. In this case, we choose to start at the "congratulations" string section of code just because it looks like a promising starting point. Our target code is as follows: CODE:00401108 push ebp CODE:00401109 mov ebp, esp CODE:0040110B add esp, 0FFFFFFB4h ; char CODE:0040110E push offset aTheAnalystSWei ; _ _va_args CODE:00401113 call _printf ; print some text. CODE:00401118 pop ecx CODE:00401119 push offset asc_40C097 ; _ _va_args CODE:0040111E call _printf ; same CODE:00401123 pop ecx CODE:00401124 push offset aEnterYourSeria ; _ _va_args CODE:00401129 call _printf ; same again CODE:0040112E pop ecx CODE:0040112F lea eax, [ebp+s] ; buffer CODE:00401132 push eax ; s CODE:00401133 call _gets ; get entered serial CODE:00401138 pop ecx CODE:00401139 nop CODE:0040113A lea edx, [ebp+s] CODE:0040113D push edx ; s CODE:0040113E call _strlen ; get its length CODE:00401143 pop ecx CODE:00401144 mov edx, eax CODE:00401146 cmp edx, 19h ; is it less than 25? CODE:00401149 jl short loc_401182 ; yes CODE:0040114B cmp edx, 78h ; is it more than 120? CODE:0040114E jg short loc_401182 ; yes CODE:00401150 mov eax, 1 ; eax = 1 , initialize loop CODE:00401155 cmp edx, eax ; all chars done? CODE:00401157 jl short loc_40115E ; no, let's jump CODE:00401159 CODE:00401159 loc_401159: ; CODE XREF: _main+54j CODE:00401159 inc eax ; eax = eax + 1 CODE:0040115A cmp edx, eax ; all chars done? CODE:0040115C jge short loc_401159 ; no, let's loop CODE:0040115E CODE:0040115E loc_40115E: ; CODE XREF: _main+4Fj CODE:0040115E mov eax, 7A69h ; eax = 31337 CODE:00401163 test eax, eax CODE:00401165 jnz short loc_401182 ; jump quit CODE:00401167 cmp eax, 1388h CODE:0040116C jl short loc_40118 ; jump quit CODE:0040116E cmp eax, 3A98h CODE:00401173 jg short loc_401182 ; jump quit CODE:00401175 jmp short loc_401182 ; jump quit CODE:00401177 ; ------------------------------------------------------------------ CODE:00401177 push offset aWooCongrats ; _ _va_args ; good msg CODE:0040117C call _printf CODE:00401181 pop ecx CODE:00401182 CODE:00401182 loc_401182: ; CODE XREF: _main+41j CODE:00401182 ; _main+46j ... CODE:00401182 call _getch ; wait till a key is pressed CODE:00401187 xor eax, eax CODE:00401189 mov esp, ebp CODE:0040118B pop ebp CODE:0040118C retn There is a trick in the code. It turns out that there is no way to get to the "congratulations" message! A quick look shows us that there's no cross-reference to our congratulations, but rather some jumps that go directly to the end of the crackme. That's odd (but then, the crackme is called weird.exe ). It turns out that the only way to solve this puzzle is by forcing a buffer overflow in order to execute the "congratulations" code. In other words, we are going to craft the serial number itself in just such a way as to force a buffer overflow into the code that we want. We are going to have to exceed the buffer in exactly the correct way to insert the serial number manually on the stack and force it to execute. Thus, the serial number itself is the payload. The first step is to check the buffer and its size : CODE:0040112E pop ecx CODE:0040112F lea eax, [ebp+s] ; buffer CODE:00401132 push eax ; s CODE:00401133 call _gets ; get entered serial CODE:00401138 pop ecx CODE:00401139 nop CODE:0040113A lea edx, [ebp+s] CODE:0040113D push edx ; s This shows us that eax is pushed on the stack, just before a call to the gets() function. We can demonstrate what is happening using the following snippet of C code: -------------------------------------------------------------------------------- #include <stdio.h> #include <string.h> #include <conio.h> #include <iostream.h> int main( ) { unsigned char name[50]; gets(name); } -------------------------------------------------------------------------------- As we can see, there is a buffer called "name", which is 50 bytes long. We then use gets to input our data into name . We defined it as 50 characters long, but what would happen if we type in 100 characters ? That should yield a nice overflow. We now have to check how big our buffer is. According to IDA, it is 75 characters long. First, we look at our stack parameters: CODE:00401108 s = byte ptr -4Ch CODE:00401108 argc = dword ptr 8 CODE:00401108 argv = dword ptr 0Ch CODE:00401108 envp = dword ptr 10h CODE:00401108 arg_11 = dword ptr 19h Thus, we can be confident that the maximum size of the buffer is 75 characters. Let's test this theory, and enter something like 80 characters : -- The analyst's weird crackme -- --------------------------------- enter your serial please: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA As expected, our program crashes nicely . No wonder , since we entered a string of 80 characters, which is five characters more than the maximum size of the buffer. Having a look at the registers, we can see that EBP = 41414141h. This is interesting. 41h is the hexadecimal ASCII value of "A". Thus, we have just overwritten the base pointer (EBP). So far so good, but ideally , we want to overwrite EIP. Overwriting EIP allows us to execute any code we want. Next, we try entering 84 characters, to see what happens: -- The analyst's weird crackme -- --------------------------------- enter your serial please: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA Okay, we still get a nice crash, but now we get the following: instruction at the address 41414141h uses the memory address at 41414141h. memory cannot be read. Thus, we see that the program tries to execute code at 41414141h. What would happen if we replaced our return address with something besides 41414141h? Say, for example, something like the "congratulations" message address? CODE:00401177 push offset aWooCongrats ; _ _va_args ; good boy CODE:0040117C call _printf Thus, we know that if we put 401177 as our return address, we will have solved the crackme by printing the "congratulations" message on the screen. However, before we do that, let us test with a tagged serial such as: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA1234 We see that this string crashes the program at address 34333231 (Figure 5-8). Figure 5-8. Program crashThis demonstrates that we have to reverse the byte order when delivering our payload. Why? As you can see, the address at which we have crashed is the reverse order of the hex equivalent of our ASCII serial. Let us diverge from our example for a moment to explain this backward ordering. The reversed order is necessary on x86 processors because they are little-endian. The term "endian" originally comes from Jonathan Swift's Gulliver's Travels . In this satirical tale, the people of two different cities cracked their hard-boiled eggs open on different ends. One city cracked the big end of the egg, while the other city cracked the little end of the egg. This difference led to war between the two cities. In computer processors, "big-endian" and "little-endian" refer to the byte ordering of multibyte scalar values. The big-endian format stores the most significant byte in the lowest numeric byte address, while the little-endian format stores the least significant byte in the lowest numeric byte address. Thus, when manipulating byte values, you need to know the order in which the specific processor reads and writes data in memory. Returning to our example, the hex equivalent of 1-2-3-4 is 31-32-33-34. If we reverse 1-2-3-4 to get 4-3-2-1, that is the equivalent of reversing 31-32-33-34 to get 34-33-32-31, and we know 34333231 is the address of our crash. Thus, to successfully exploit the program, we have to also reverse the order of the memory address we want to inject. In other words, to execute 401177, we must place 771140 on the stack. We know that 771140 in ASCII is equivalent to w^Q@ ( the ^Q is Ctrl-Q). We now try to enter it into the program: -- The analyst's weird crackme -- --------------------------------- enter your serial please: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA w^Q@ Pressing the Return key gives us the desired "congratulations" message (Figure 5-9): wOO! congrats ;) Figure 5-9. Congratulations messageYou have now successfully exploited a buffer overflow to execute instructions that you injected onto the stack. In this case, you inserted a memory address that gives you access to a location in the program that you never should have been able to access. Now that you have solved it, you can examine the source code of the Analyst's crackme: #include <stdio.h> #include <string.h> #include <conio.h> #include <iostream.h> int main( ){ int i,len,temp; unsigned char name[75]; unsigned long check=0; printf("-- The analyst's weird crackme --\n"); printf("---------------------------------\n"); printf("enter your serial please:\n"); gets(name); asm{ nop}; len=strlen(name); //cout << len; if (len < 25) goto theend; if (len > 120 ) goto theend; for (i=1; i <= len ; i++) { temp += name[i] ; } if (temp = 31337) goto theend; if (temp < 5000) goto theend; if (temp > 15000) goto theend; goto theend; printf("wOO! congrats ;)\n"); theend: getch( ); return 0; |
< Day Day Up > |