|< Day Day Up >|
4.3 Practical CE Reverse Engineering
For this section, you will need to use the tools described in previous chapters, including hex editors and disassemblers. We start by creating a simple "Hello World!" application, and we then use this program to
4.3.1 Hello, World!
When learning a programming language, the first thing most people do is to create the famous "Hello, World" application. This program is simple, but it helps to get a new programmer familiar with the syntax structure, compiling steps, and general layout of the tool used to create the program. In fact, Microsoft's eMbedded Visual C++ goes so far as to provide its users with a wizard that creates a basic "Hello World" application with the click of a few
Figure 4-2. WCE application creation window
After a few seconds, a new "test" class appears on the left side of the screen, under which are all the classes and functions automatically created by the wizard. We aren't making any changes to the code, so
Once the steps are complete, find test.exe on your device and execute it. If everything went according to plan, you'll see a screen similar to Figure 4-3. After a short break to discuss some of the popular methods crackers use to subvert protection, we will take a closer look at test.exe and make some changes to it using our reversing tools.
Figure 4-3. test.exe screen on the
4.3.2 CE Cracking Techniques
In this section, we
22.214.171.124 Predictable system calls
In about 80% of all software, there is a common flaw that leads to the eventual cracking of the software: predictable code. For example, if you go through the registration process, you will almost always find a message that
The problem arises simply because there are a limited number of alert boxes that appear in a program. A cracker has only to open the program in IDA Pro and search the strings for any calls made to MessageBoxW ”the name of the function responsible for sending a message to the computer screen.
Once the cracker finds this call, she can use the reference list included with IDA Pro to backtrack through the program until she finds the point where the serial number is
Other common calls are Load String (for loading serial number values into a variable), Registry checks (for checking to see if the program is registered or not), and System Time checks (for checking for trial period deadlines). To find these, a cracker only has to use the
Figure 4-4. Names window in IDA, listing the CE functions used
126.96.36.199 strlen and wcslen
When working with strings such as usernames, serials, or other text entries, it is important to monitor the length. The length of the string is important for two reasons. One, a program that expects a string may generate an error if it receives a variable with no value. For example, if a program is trying to divide two
The second main use of string length checks is when setting aside memory for a variable. For example, our "Hello, World!" application must set aside enough memory for a 12-character variable. The program checks to see how much space is required using wcslen, as the following code illustrates:
ADD R0, SP, #0x54; Points R0 to memory address of 'Hello World!' string. BL wcslen; Tests the length of the string and places that value in R0.
While testing string length is undeniably important, it is also an easy function to find and abuse. Because these types of functions are required when verifying serial numbers, a cracker has only to look in the Names window of the application to start the reversing process. In fact, crackers sometimes target this check and reset the required serial number length to zero, thus bypassing a program's security.
188.8.131.52 strcmp and CMP
Another popular method of finding serial number checks is through the use of the comparison ( CMP) instruction. This type of function is used to compare two values to see if they are equal, and it can flip the Zero flag to true or false
Using strcmp or CMP as the sole method of validation in a registration process is not recommended. This particular function is one of the most abused and exploited functions in assembler. In fact, the use of this one little command can sometimes neuter a program that uses complex serial verification routines with encryption, name checks, and more.
For example, some programs do not actually store their serial numbers in the program file. Instead, an algorithm is used to create a valid serial number on the fly, based on owner names, hardware settings, the date/time, and more. In other words, thousands of lines of code are dedicated to creating a valid registration key. This key is used in the validation process to check any serial number that is entered to unlock a program. However, at the very end of the verification routine, most programs simply perform a simple comparison between the entered serial number and the one generated by the complex algorithm. The results of this check are placed into one of the registries, which are used to determine how the program flows. Typically, the next line includes some conditional branch call that either accepts the entered serial number or rejects it. Let's take a look at the following example, in which strcmp is used to verify a registration value:
Assume R1 = address of correct serial ADD R0, SP, #0x12 : This updates RO with a value pulled from the stack, which corresponds to the serial : number entered by the user. BL strcmp : This compares the values held in addresses that R0 and R1 point to and sets the : Zero flag accordingly: 1 for no match and 0 for match. MOVS R2, R0 : Writes the value of R0 into R2 (the entered serial number). MOV R0, #0 : Assigns R0 = 0 CMP R2, R0 : The CMP will check R0 against the value held by R2 (the results of the strcmp); : if these values match, then the serials do not match.
Following this function, there would be a branch link to another section of code that would update the serial status and probably alert the user to a success or failure of the registration attempt. This would be done using the status flags, updated when the CMP opcode was executed. The following is an example:
BNE loc_0011345 BEQ loc_0011578
Therefore, if a cracker wanted to patch this program, he would only need to ensure that the CMP opcode always worked to his advantage. To do this, he would update the following opcode:
CMP R2, R1 CMP R2, R2
Since R2 will always equal R2, the CMP updates the status flags with an Equal status. This is used in the BNE/BEQ branches, which
CMP R2, R1 Hex: 01 0 52 E1 CMP R2, R2 Hex: 02 0 52 E1
In other words, thanks to strcmp and the change of one hex character, the protection of this program is nullified.
184.108.40.206 NOP sliding
When attacking a program, there are some situations that require a cracker to overwrite existing code with something known as a nonoperation (NOP). A nonoperation simply tells the processor to move on to the next command. When a series of NOP commands are used in sequence, the processor virtually slides through the code until it hits a command it can perform. This technique is popular in both the hacking and cracking community, but for different reasons.
A hacker typically uses NOP slides to facilitate the execution of inserted code through a buffer overflow. A
(discussed in Chapter 5) is a method of overflowing a variable's intended memory allocation with data. This allows a hacker to write her own code right into the memory, which can be used to create a backdoor, elevate permissions, and more. However, a hacker does not always know where her code ends up in the target computer's memory, so she typically pads her exploit code with NOP commands. This allows a hacker to guess where in the memory to point the execution code. Upon
A cracker, on the other hand, does not use NOP slides to execute code. Instead, he uses NOP commands to overwrite code he does not want executed. For example, many programs include a jump or branch in the assembler code that instructs the processor to validate a serial number. If a cracker can locate this jump in the program, he can overwrite it with a NOP command. This ensures that the program remains the same byte
Traditionally, the NOP command is as simple as typing 0x90 over the hex that needs to be nullified. However, this works only on an x86 processor, not on ARM. If you attempt to use 0x90s on ARM, you end up inserting UMULLSS, which is the command to perform an unsigned multiply long if the LS condition flags are set, followed by an update of the status flags depending on the result of the calculation. Obviously, this is about as far from a NOP as you can get.
Ironically, the ARM processor has no true NOP command. Instead, a cracker would need to use a series of commands that
(MOV R1, R1)
This method of cracking is common because it is one of the
Assembler HEX MOV R0, #0x15 15 00 A0 E3 BL Sleep FF 39 00 EB MOV R4, R0 00 40 A0 E1
Using a hex editor, a cracker would only have to make the following changes to the code to cause the "sleep" function to be ignored:
Assembler HEX MOV R0, #0x15 15 00 A0 E3 MOV R1,R1 MOV R4, R0 00 40 A0 E1
Note the missing Sleep command. When you overwrite this command, the revised program will not display, for example, a nag screen that temporarily restricts access. Instead, the user will be taken straight into the program.
To our knowledge, at the time of this writing there are no hex editors that work directly on Windows Mobile platforms. However, you can edit the application on the desktop (Figure 4-5) using methods described in previous chapters.
Figure 4-5. UltraEdit-32 hex output of test.exe
Locate any requested *.dll files and wait for IDA to disassemble the program.
If the Names window does not open, select it from the View Open Subviews Names menu.
At this point, you should have the following
.text:00011564 ; S U B R O U T I N E .text:00011564 .text:00011564 .text:00011564 LoadStringW ; CODE XREF: sub_110E8+28#p .text:00011564 ; sub_110E8+40#p ... .text:00011564 LDR R12, =_ _imp_LoadStringW .text:00011568 LDR PC, [R12] .text:00011568 ; End of function LoadStringW
If you look at this code, you can see that LoadStringW is
While it is possible to scroll up to this memory location, IDA makes it easy by allowing us to click on the reference. Here's the secret: right-click on the "..." and select the "Jump to cross reference" option. Select the third option on the list, which should be 1135C. Without this shortcut, you would have to go to each XREF and check to see where in the display process the code is.
Once at address 1135C, you can see that it looks very
As we learned, wcslen is a common point of weakness. We are going to use this knowledge to change the size of our message. Let's take a closer look at this part of the code,
.text:0001135C BL LoadStringW ;load string .text:00011360 ADD R0, SP, #0x54 ;change value of ;R0 to point to string location .text:00011364 BL wcslen ;get length of ;string and put value in R0 .text:00011368 MOV R3, #0x25 ;R3 = 0x25 .text:0001136C MOV R2, R0 ;moves our string ;length into R2 .text:00011370 STR R3, [SP] ;pushes R3 value ;on memory stack .text:00011374 ADD R3, SP, #4 ;R3 = memory stack ;address + 4 .text:00011378 ADD R1, SP, #0x54 ;R1 = memory stack ;address + 0x54 .text:0001137C MOV R0, R5 ;moves R5 to R0 .text:00011380 BL DrawTextW ;writes text to ;screen using R0, R1, R2 to define ;location of string in memory, ;length of string, and type of draw.
Now that we have broken down this part of the code (which you will be able to do with practice), how can we change the length of the string that is drawn to the screen? Since we know that this value was moved into R2, we can assume that R2 is used by the DrawTextW routine to define the length. In other words, if we can control the value in R2, we can control the message on the screen.
To do this, we only need to change the assembler at address 1136C. Since R2 gets its value from R0, we can simply replace the R0 variable with a hardcoded value of our own. Now that we know this, let us edit the program using our hex editor.
Once you get the hex editor open, you will quickly see that the address in IDA does not match the address in the hex editor. However, IDA does provide the address in another part of the screen, as illustrated in Figure 4-7. The status bar located at the bottom left corner of the IDA window gives the actual memory location you need to edit.
Using the opcodes discussed previously in this chapter, you recreate the hex code you want to use in place of the existing code. The following is the original hex code and the code you will want to replace it with.
Here is the original:
MOV R2, R0 00 20 00 E1
And here it is, updated:
MOV R2, 1 01 20 00 E3
Note the change from E1 to E3; it differentiates between a MOV of a register value and a MOV of a hardcoded value.
What did this change accomplish? If you download the newest test.exe file to your PDA, you will see that it now has a message of just "R". In other words, we caused the program to only load the first character of the message it had stored in memory. Now, imagine what we could do if we increased the size of the message to something greater than the message in memory. Using this type of trick, a cracker could perform all kinds of manipulation. However, these types of tricks often take more than just a disassembler, which is where MVT comes in handy.
Currently, there are very few tools available for live debugging of Windows CE devices. The choice of
tools is even more limited. However, Microsoft, in its
In short, MVT allows you to run a program, one line or opcode at a time. In addition, it allows you to observe the memory stack, register values, and values of variables in the program while it is executing. And if that isn't enough, the debugger allows you to actually change the values of the registers and
The first step in debugging a program is to load it into the MVT. This step typically involves the use of the Microsoft eMbedded Visual C++ (MVC) program that is included with the MVT package. Once C++ is open, perform the following steps to load the
file into your debugger.
Open Microsoft eMbedded Visual C++.
Select File Open.
Select the local copy of test.exe .
After brief delay, select Project Settings from the top menu.
Click the Debug tab.
In the "Download directory:" text box, type "\" (or point the directory to the folder you have selected on the CE device).
Click OK, and then hit F11.
You will see a Connecting screen (Figure 4-8) followed by a warning screen (Figure 4-9). Select Yes on the CPU Mismatch Warning dialog window.
Click OK on the next warning screen (Figure 4-10).
The file will download and some file verification will occur.
Click OK on the debugging information warning screen (Figure 4-11).
Patiently wait as the program launches.
You will be asked for several
files. For this example, they can be
Patiently wait for the program to synchronize.
Once the program is loaded in debug mode, you will notice it is similar to IDA Pro. This is because the program must be disassembled before it can be executed in debug mode. As with any debugger, take a moment to become familiar with the tools and options available to you.
The Registers screen is one of the most useful, after the main Disassembly window. It is also important to note that you can change the conditional flags by double-clicking on their labels. This can easily
The Call Stack windows provide a means of keeping track of the function in which you currently reside, as well as where the function will return if it is a BL. The Memory window allows you to look right into the RAM and the values it is holding. This is extremely
When debugging a complicated program, you may also need to jump to determine where in memory a linked file exists. Doing so allows you to locate the code and set a breakpoint. Using the Modules window, you can easily find the memory range and jump to that point of code. In addition, pressing Alt-F9 allows you to set breakpoints (BPXs). Use breakpoints when you want to step into the address of a BL. MVC does not step into a BL; instead, it executes the code and
Now that you are familiar with the basic layout of the MVC, let's try it out. For this example, we use the
program, which you have already
The first thing we want to do is to jump to the point in the program where the message is displayed. Since we already found this using IDA Pro, we can easily jump to this part of the program. First, we need to know where in memory our
program resides. We will use the Modules window. Once we open this window, we quickly see that the
program is between 0x2E010000 and 0x2E015FFF. (Note that the first two
Once you find that address, place a breakpoint next to it so the program will stop running at this point: either right-click on the memory address or hit Alt-F9. Make sure to enter the address with a 0x appended to the front. Without this hex declaration, the breakpoint will not set. If you are successful, you will see a red dot next to the address.
Now, hit the F5 key to execute the program. If all went well, the program stops at the address at which you placed the BPX. At this point in the execution, part of the program has executed. In fact, your Windows CE device may have the blank HACK window loaded on its screen (as shown in Figure 4-12). However, we are not yet at the place in the code where the actual message is written to the screen.
If you compare the disassembly screen in the MVT with that of the code in the IDA Pro hack we worked on previously, you can see we are at the key part of the code in which the message is written to the screen. However, unlike IDA Pro, the MVT does not provide the function names (e.g., 1135C is the LoadStringW function). This is one reason it is useful to have both programs open in tandem.
Once the program is
Since we know that the 1135C address pointed to a function that loaded the string, we can assume that the registers have been updated with this string's information. This is in fact what has
If you are wondering why our long 12-character string did not appear, you have to remember that memory is written to in reverse order: the value of the string ends at the address 2E015818. In other words, if you scroll up a few lines, you should see your message. So you now know that R2 points to the address in the program's memory where the string is stored, and R0 holds the length of the string.
If we step through the program, we can see that the string is eventually added to the stack and is stored back into memory at 2E06FA60. During this process, the value in R0 is placed in R12, and R5's value is placed in R0. There are some other value updates, but eventually, at 2E011380, the string is written to the screen.
During this process, note that address 11378 contained an add opcode that updated the value of R1 by adding Sp with 0x54. This is used to point to the place in temporary memory where the string is stored. So if we changed the 0x54 value to a value of our choosing, the output screen should reflect the change. To illustrate, let us look through the Memory window to see if we can find a different message. After scrolling down a bit, you should come to memory address 2E06FA10, which points to the beginning of the word HACK. Now that we have found an alternative message, how can we get this message to display?
This process is a matter of basic math. If our stack pointer is 6FA0C, to which 0x54 is added to point to the original message, we need to determine what value needs to be added to the stack pointer to point to our new address. In other words, 6FA60 - 0x54 = Sp, which means the original address is 6FA60. Using this equation, if the desired address is 6FDAC, then to figure out the difference we simply need to subtract the Sp from 6FDAC (i.e., 6FDAC - 6FA0C = 3A0).
At this point, we have determined the purpose of this hack. We have located a string in the memory that we wish to display and figured out the distance from the Sp to that memory address. We know that the opcode and assembler at address 11378 needs to be changed as follows.
Here's the original:
ADD R1, SP, #0x54 54 10 8D E2
And here it is, updated:
ADD R1, SP, #0x3A0 3A 1E 8D E2
We also can use the lessons we previously learned to reduce the size of the string buffer to four characters. This would simply require us to change the instructions and assembler at 1136C as follows.
Here's the original:
MOV R2, R0 00 20 00 E1
And the updated:
MOV R2, 1 01 20 00 E3
Once you have completed this exercise, save the new binary file and run it on MVT (or, optionally, upload it to your Windows CE device). If you got everything right, you should be rewarded with a screen similar to Figure 4-12.
|< Day Day Up >|