5.6 A Live Challenge

 <  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).

For this example, we use a Windows-based buffer overflow crackme named weird.exe . You may download the executable from our web site at http://www.securitywarrior.com.

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 crackme

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

  • Knowledge of x86 assembly language

  • A disassembler such as IDA or W32DASM

  • A hex-to-ASCII converter

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.exe
Figure 5-7. Screenshot of target code in IDA Pro

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


We see that this string crashes the program at address 34333231 (Figure 5-8).

Figure 5-8. Program crash

This 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 message

You 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  >  

Security Warrior
Security Warrior
ISBN: 0596005458
EAN: 2147483647
Year: 2004
Pages: 211

Similar book on Amazon

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net