- The Problem
- The Tools
- Three Illustrations
- Building a Cracke
- Summary
Three Illustrations
The following examples illustrate the process that Airscanner goes through when examining a program for vulnerabilities:
- The first example demonstrates how a password can be pulled out of a device’s memory in plain text.
- The second example shows how a debugger can be used to bypass the password verification routine.
- The final example explains how a password program can secure the information correctly, but ironically be used by an attacker to crack the password.
Picking the Password Out of iPassword
iPassword is a program sold as a digital locker for Windows Mobile devices. Here’s how the Creative Iye website describes its product: "iPassword is an easy to learn, simple to use password protector. It uses a strong custom encryption routine to prevent the unwanted eyes from prying. iPassword was designed to keep a variety of data, such as passwords, credit card info, bank account info, email accounts, IP addresses, PINs, TANs, and other confidential data safely and securely in a well-organized form."
Once the program has been installed and its master password is set, the user is prompted for a password each time the program is executed. Assuming that the correct password is provided, the screen shown in Figure 1 lists all the items that are protected by "128-bit encryption."
Figure 1 iPassword’s Master Password window.
Every program written in C++ uses a common set of functions that are stored in a core operating system file (coredll.dll for Windows Mobile). As a result, it’s fairly easy to guess what functions are probably being called to perform some action. In the case of iPassword’s password entry, we know that the info entered into the text box has to be passed to the program. Since IDA provides us with a list of all the functions called in the program, it’s fairly simple to scan that list, looking for good candidates. One of these is GetDlgItemTextW, which is used to retrieve text from a dialog box, such as the one used by iPassword (see Figure 2).
Figure 2 Function listing in IDA.
To test this theory, we double-click the function name, which prompts IDA to take us to the place where that function exists in the program. The function is actually in a file called coredll.dll, and this piece of code just directs the processor to the memory address of that function. Regardless, we can place a breakpoint here, which tells IDA to stop the code execution if it detects the processor attempting to execute code at the breakpoint’s address. In IDA, this is done by pressing the F2 button.
Next, we need to configure IDA to connect to the executable on the device (Debugger/Process Options), after which we can hit the F9 button. After a few seconds, the iPassword Master Password window (the one shown earlier, in Figure 1) appears on our PDA screen.
To see if GetDlgItemTextW is used, we enter a random value and click OK. As we guessed, iPassword retrieves the data from the box, using GetDlgItemTextW. This action causes the processor to access the code at the breakpoint’s address, which makes IDA pause the program.
Now we have to figure out at what point the function was called in the program. Since iPassword uses GetDlgItemTextW numerous times as it retrieves credit card information, PINs, etc. from the user, we have to work backward from the breakpoint. Fortunately, this is fairly easy.
When a subroutine is called, the processor has to be able to know where to continue after the subroutine is finished. This is accomplished by placing the return address onto the stack and storing it in memory. However, we’ve stopped the program just before it jumps into the subroutine, which means that the return address is stored in the processor’s registers (temporary storage locations in the processor). In fact, it’s stored in the link register (LR). If we right-click this register and select the "Jump in a new window" option (see Figure 3), we’ll be taken to the address of the program where this subroutine will be returning. Once here, we move the cursor down to address 0x00019C84 (the address directly after the GetDlgItemTextW function call) and press F4 , which causes IDA to execute until it hits the address under the mouse.
Figure 3 Using Link Register to locate the function’s return address.
Typically, immediately after a password is pulled in from a user, it will be verified against the stored password. The methods vary, but most programs either verify the password several lines down in the code, or push the verification out to its own subroutine. In this case, the verification occurs between address 0x19C98 and address 0x19CC0.
While watching the code execute, we realized that the opcode at 0x00019C84 loaded the memory address of the unencrypted password that’s stored in memory. As a result, we let this line execute, right-clicked register R4, and selected "Jump in a new window" to view this value. As Figure 4 illustrates, it’s really quite easy to pull the valid password right out of the device’s memory.
Figure 4 The valid password stored in memory.
The code also holds a few other security risks. Let’s look at the code directly following the GetDlgItemTextW function:
00019C80 BL GetDlgItemTextW ;Get entered password and store in memory 00019C84 LDR R4, =unk_441BC ;Set R4 equal to address of valid password 00019C88 ADD R0, SP, #0x114+var_E4 ;Set R0 equal to address of encrypted pass 00019C8C MOV R1, R4 ;Set R1 equal to address of valid password 00019C90 BL sub_1113C ;Decrypt encrypted password ?? 00019C94 SUB R2, R4, R5 ;Subtract value of R5 from R4 00019C98 loc_19C98 00019C98 LDRH R0, [R2,R5] ;Load character of real password 00019C9C MOV R6, #1 ;Set R6 to 1 (counter) 00019CA0 MOV R1, R0,LSL#16 ;Perform a shift 00019CA4 LDRH R0, [R5],#2 ;Load character of entered password 00019CA8 MOV R3, R1,LSR#16 ;Perform a shift 00019CAC MOV R1, R0,LSL#16 ;Perform a shift 00019CB0 MOV R0, R1,LSR#16 ;Perform a shift 00019CB4 CMP R3, R0 ;Compare the two characters 00019CB8 BNE loc_19CCC ;Branch out if not equal 00019CBC CMP R3, #0 ;Check for end of password 00019CC0 BNE loc_19C98 ;Branch to 19C98 if more characters 00019CC4 MOV R1, R9 ;Move R9 to R1 00019CC8 B loc_19CD8 ;Jump to open program
You might not be an ARM assembly language master, but you should get the idea from reading this code. The real password is compared to the entered password character-by-character. If the characters match, good—if not, the processor is told to jump to another part of the program.
There are a few other important security issues to note:
- The subroutine at address 0x00019C90 that decrypts the stored real encrypted password is very simple to emulate. If the device’s registry can be read by someone, the stored password could easily be decrypted by hand.
- If the password is deleted from the registry, iPassword simply opens and decrypts the contents of the "secure" file, without any password.
- You can use IDA to jump over the verification routine, and the "encrypted" file opens right up.
And now, the most disconcerting problem: The software supposedly uses 128-bit encryption to protect your data—but we’re not convinced. At this point, we know that there’s no link between the password and the encrypted data. Otherwise, any attempt at jumping over the password verification routine would result in corrupted output. So if the password isn’t used to encrypt the data, what is? Well, the function that’s used to decrypt this password:
(00019C90 BL sub_1113C)
is the same function used to decrypt the data in the database. In fact, here’s that code:
00011198 LDRH R0, [R3,R4] 0001119C SUB R6, R6, #1 000111A0 CMP R6, #0 000111A4 MOV R1, R0,LSL#16 000111A8 RSB R2, R5, R1,LSR#16 <- 128-bit encryption??? 000111AC MOV R0, R2,LSL#16 000111B0 MOV R1, R0,LSR#16 000111B4 STRH R1, [R4],#2 000111B8 BHI loc_11198
In the middle of this function is a command that performs a reverse subtraction on two values. This isn’t 128-bit encryption. In reality, the data in the database is protected with a weak cipher that leaves the user very exposed.
Unfortunately, many programs store sensitive information in memory. All of these programs expose unsuspecting users to the risk of their data being compromised.
Picking CodeWallet’s Pocket
Thankfully, not all programs expose their secret keys in the way iPassword does. However, that doesn’t mean that these programs are any better at protecting the data. This next example demonstrates how a logic error in the programming, combined with inadequate data protection, can expose a "secure" file.
DeveloperOne describes its product this way: "CodeWallet Pro is the #1 secure personal and business information organizer for your Windows Mobile Pocket PC or Smartphone. Manage passwords, banking information, credit card details, PIN codes, travel plans, insurance policies, registration codes, gift lists, you name it, CodeWallet Pro manages it."
As with iPassword, CodeWallet promises to protect your data, but via a little reverse-engineering and debugging, it was fairly easy to nullify this protection. Fortunately, CodeWallet fixed this problem, and the most current version (6.62) is no longer vulnerable to this issue. That said, this type of problem is not isolated to CodeWallet.
When the user first opens her CodeWallet program, a password entry screen appears, similar to the one in Figure 5.
Figure 5 CodeWallet’s main window.
From here, the user can either type a numeric key using the pad provided by the program, or use the keyboard to enter a stronger alphanumeric key. As with iPassword, a text box is used to collect the password. However, instead of using the GetDlgItemTextW function as our entry point, we’re going to probe this program from another angle.
When a password is entered into CodeWallet, the program encrypts the entry and bumps it up against another hash stored in the data file. If the hashes match, the file is decrypted and opened for the user to view. If the hashes don’t match, the program sends a notice to the user via a message box (see Figure 6).
Figure 6 CodeWallet’s invalid password message.
Since we’re dealing with a program written in C++, we can deduce that this message box was created via the MessageBoxW function. So, to figure out where in the program this function is called, we open IDA, list the functions, click the item named MessageBoxW, and set a breakpoint using the F2 function (see Figure 7).
Figure 7 The MessageBoxW breakpoint.
Once the breakpoint is set, we choose Debugging > Process options in IDA and press F9. After a few seconds, the PPC displays the key entry window. Next, we type an invalid password and click OK. At this point, IDA monitors the code execution, detects the MessageBoxW breakpoint, and stops the program. Using the same method as we used with iPassword, we can figure out the source of the MessageBoxW function call by right-clicking the LR register field and jumping to that location in memory (in this example, 0x00045ED0).
Things get a bit tricky here. We need to back up through the program until we find out where the password is validated. Fortunately, this isn’t too difficult, because IDA provides the information we need.
When we jumped back into the address specified by the LR register, we landed in the middle of a subroutine. Chances are that this subroutine has nothing to do with the actual validation process, but instead is the piece of the program where the processor is sent if the wrong password is entered. We know this in part because of experience, but if you look at the code preceding the LR address, you won’t find any compare functions. We can further verify our guess by locating the MessageBoxW function call, which can be found buried in the BL sub_4D80C subroutine directly above the LR address.
So where did the call originate for the current code? If we go up roughly 11 lines from the current address in IDA, we see the following:
.text:00045EA4 loc_45EA4 ; CODE XREF: sub_45854+4D0
This line says that the current subroutine was called from the address 0x45854+4D0, which equals 0x00045D24. To get there quickly, we can simply click on the CODE XREF address.
At this point, we see a few lines of code indicative of something worth investigating:
.text:00045D1C BL sub_37B44 .text:00045D20 CMP R0, #0 .text:00045D24 BEQ loc_45EA4 -> to the message box
Password verification often is located in a subroutine. When this routine is returned, it sets the R0 register to either 1 (pass) or 0 (fail). So if R0 is equal to #0, the "branch if equal" instruction redirects the processor to the message box alert subroutine.
We need to know how this value gets set to 0. To find out, we need to investigate the subroutine located at 0x37b44, which we access by double-clicking the BL sub_37B44 entry in IDA. Once there, we apply some simple logic to help us narrow down the part of the subroutine that we need to examine. In other words, we know that when this subroutine is finished, it will have to assign R0 a value—so we start at the bottom of the subroutine and work our way up.
At the top of the subroutine is an opcode with the following:
.text:00037B44 STMFD SP!, {R4-R6,LR}
This opcode basically tells the processor to store the values in registers R4–R6 and the LR value onto the stack. This action is necessary because the processor most likely will need to overwrite the values in R4–R6 with its new information generated by the following subroutine. Anything in those registers will be lost, so the data is placed onto the stack for safekeeping. Incidentally, when the subroutine is complete, all of that information will have to be taken off the stack and placed back into the registers. Knowing that this trick is almost always accomplished via the opposite command (LDMFD), we only have to scroll down through the code until we get to this point:
.text:00037CDC LDMFD SP!, {R4-R6,PC}
Now let’s look at the 10 lines preceding this point, and see if we can figure out how 0 ends up in R0 if the wrong password is entered:
.text:00037CB4 MOV R2, #0x10 .text:00037CB8 ADD R1, SP, #0x188+var_6C .text:00037CBC ADD R0, R6, #0x26C .text:00037CC0 BL memcmp .text:00037CC4 CMP R0, #0 .text:00037CC8 LDR R0, [SP,#0x188+var_14] .text:00037CCC BNE loc_37CE4 .text:00037CD0 BL sub_521AC .text:00037CD4 MOV R0, #1 .text:00037CD8 ADD SP, SP, #0x178 .text:00037CDC LDMFD SP!, {R4-R6,PC}
Address 0x00037CD8 is responsible for resetting the SP address to where it was prior to the subroutine, so we can ignore that. However, we can see that in address 0x00037CD0 the number #1 is moved (with MOV) into the R0 register. Obviously, this opcode isn’t executed if we enter the wrong password. Why not?
The only way it wouldn’t be executed is if the processor never hit that line, which means that a previous opcode must have redirected the processor away. Look at address 0x00037CCC. It contains a BNE (Branch if Not Equal) opcode. This is where the processor is diverted away. Following that branch to its code, we see that it leads to address 0x37CE4, where we find this:
.text:00037CE4 loc_37CE4 ; CODE XREF: sub_37B44+188 .text:00037CE4 BL sub_521AC .text:00037CE8 MOV R0, #0 .text:00037CEC ADD SP, SP, #0x178 .text:00037CF0 LDMFD SP!, {R4-R6,PC}
Here we have an alternate ending that sets R0 to the value #0, and then returns to the parent routine, where another CMP opcode is waiting to determine whether the password was valid. Now that we know this, we need to figure out how the BNE condition is set.
When dealing with ARM opcodes, the conditions are almost always set via some sort of CMP (compare) function. If the comparison is true, then the EQ (equal) flags are set in the processor; if not, the NE (not equal) flags are set. If we look up a couple of lines at address 0x00037CC4, we can see a CMP R0, #0 opcode, and directly before that is function call memcmp. What are the chances that our entered password is compared to the existing one via this function? Pretty good.
To test this theory, we need to set a breakpoint on the memcmp call and allow IDA to do its job. We first press F9 to allow the current break condition to run its course. Once the screen is back to the entry windows, enter a new password and click OK. If any breakpoints are hit, just let IDA continue by pressing F9. Once you click OK, IDA breaks at the memcmp function, as expected.
Now let’s take a look at what’s being compared. To do this, we need to understand a little bit about how the memcmp function works. Thankfully, we can gain some of this information from Microsoft’s website. We learn that memcmp takes three variables: value 1, value 2, and the length to be compared. We can confirm this pattern by testing a few passwords to see what happens. To view the actual values, right-click R0 and R1 and select the "Jump in a new window" option. If you enter the correct password, both R0 and R1 should contain matching data; if not, the contents of the memory will differ, as Figure 8illustrates.
Figure 8 CodeWallet memcmp details.
When the function returns, it sets R0, R2, and R3 as 00000000 if the two values match, or it provides information regarding what doesn’t compare if the memory values don’t match. Knowing this, we can determine how the CMP R0, #0 is used to control the program flow. But what happens if we fudge the results? Will CodeWallet figure it out? And more importantly, will the data be left exposed?
To find out, we need to set a breakpoint at address 0x00037CC. Assuming that you enter the wrong password, the registers window should show you something similar to Figure 9.
Figure 9 Registers after a failed compare.
To test our theory, we only need to change the results to what the program would expect if we entered the correct password. To do this, simply click R0 and press 0 on your keyboard to update the register value. Repeat this action for R2 and R3. Finally, press F9 to let the program continue. Note that you will have to repeat this process again, because the program checks the password entry twice during execution.
A few seconds after the second memcmp manipulation is complete, CodeWallet 6.14 opens and presents full access to the data (see Figure 10). What does this mean? It appears that the encrypted file is protected by a static key embedded in the software, which means that your password has nothing to do with the security of that data.
Figure 10 CodeWallet’s "secure" data.
As mentioned earlier, CodeWallet was informed of this problem several times, and finally released an update that fixes this attack vector. Unfortunately, we’ve found numerous other programs that suffer from very similar bugs.