4.3 Practical CE Reverse Engineering

 <  Day Day Up  >  

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 demonstrate several cracking methods . After this discussion, we offer a hands-on tutorial that allows you to walk through real-life examples of how reverse engineering can be used to get to the heart of a program.

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 buttons . The following are the required steps:

  1. Open Microsoft eMbedded Visual C++.

  2. Click File New.

  3. In the "Project Name :" field, type "test", as illustrated in Figure 4-2. Select WCE Application on the left.

Figure 4-2. WCE application creation window

By default, all compiled executables will be created in the C:\Program Files\Microsoft eMbedded Tools\Common\EVC\MyProjects\ directory.

  1. Click OK.

  2. Ensure "A typical `Hello World!' Application" is selected, and click Finish.

  3. Click OK.

We're running the programs on a PDA synchronized with our computer, but the beauty of Microsoft's eMbedded Visual Tools is you don't need a real device. The free MVT has an emulator for virtual testing .

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 next , we compile and build the executable:

  1. Ensure the device is connected via ActiveSync.

  2. Click Build test.exe .

  3. Click Yes/OK through the warnings.

  4. Locate the newly created executable in your C:\Program Files\Microsoft eMbedded Tools\Common\EVC\MyProjects\ directory, or whatever directory you selected during the wizard, and copy it to your device.

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 Windows CE device

4.3.2 CE Cracking Techniques

In this section, we briefly review some of the cracking techniques discussed in earlier chapters and apply them to embedded reverse engineering. Users who feel comfortable with the Windows CE OS can skip to Section 4.3.3. 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 tells you the wrong serial number was entered. While this is a nice gesture for the honest person who made a mistake, it is a telltale sign that the program is an easy crack.

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 verified . In other words, using a message box to warn about an invalid serial gives the cracker the necessary starting point to look for a weakness. Without it, a beginner cracker could spend hours slowly stepping through the program, testing and probing.

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 Names window, which lists all the functions and system calls used in the program. Figure 4-4 is taken from IDA Pro, with our test.exe program loaded into it. The highlighted function may be a good place to start when looking for a way to alter the displayed message.

Figure 4-4. Names window in IDA, listing the CE functions used
figs/sw_0404.gif 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 numbers and the denominator is blank, the calculation will fail. To avoid problems like this, a program will include checks to ensure that a value is indeed entered.

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. 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 accordingly . Again, this is a required function for program execution; however, it comes with a serious risk.

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 react with a positive serial check. To do this, a cracker would have to update the hex values as follows :

 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. 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 buffer overflow (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 hitting the NOP commands, the processor just slides into the exploit code and executes it.

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 size and bypasses the registration check. Typically, this method will also be used with a slight alteration on a compare or equivalence function, to ensure proper continued code execution.

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 essentially perform no operation. This is accomplished by simply moving a value from a register back into itself, as follows:

 (MOV R1, R1) 

This method of cracking is common because it is one of the easiest to implement. For example, if a cracker wanted to bypass a "sleep" function in a shareware program, she could easily search for and find something similar to the following code.

 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

4.3.3 Disassembling a CE Program

As discussed previously, a disassembler is a program that interprets machine code into a language that humans can understand. Recall that a disassembler attempts to convert hex/binary into its assembler equivalent. However, there are as many different assembler languages as there are types of processors. AMD, Intel, and RISC processors each have their own languages. In fact, processor upgrades often include changes to the assembler language, to provide greater functionality.

As a result of the many variations between languages, disassembling a program can be challenging. For example, Microsoft's MVT, discussed next, includes a disassembler to allow for CE debugging. However, this program will not debug code meant to run on a Motorola cell phone. This is why choosing the right debugger is an important process ”which brings us to IDA Pro.

Once you have obtained a copy of IDA Pro, execute it and select New from the pop-up screen. You will be prompted for a program to disassemble. For this exercise, we will use the test.exe file that we just created. However, we are going to alter the file and control the execution of the program to show a different message than the one it was originally programmed for. Loading the file

The first thing you need to do is load the test.exe file into IDA Pro. You need to have a local copy of the file on your computer. Step through the following instructions to get the test.exe file disassembled.

  1. Open IDA (click OK through splash screen).

  2. Click New at the Welcome screen and select test.exe from the hard drive; then, click Open.

  3. Check the "Load resources" box, change the "Processor type" drop-down menu selection to "ARM processors: ARM," and click OK, as illustrated in Figure 4-6.

  4. Click OK again if prompted to change the processor type.

At this point you may be asked for some *.dll files. We recommend that you find the requested files (either from MVT or from your device) and transfer them to a local folder on your PC. This allows IDA to fully disassemble the program. test.exe requires the AYGSHELL.DLL file, which can be downloaded from the Internet.

  1. Locate any requested *.dll files and wait for IDA to disassemble the program.

  2. If the Names window does not open, select it from the View Open Subviews Names menu.

Figure 4-6. IDA Pro startup configuration for test.exe

At this point, you should have the following chunk of code listed at the top of the disassembler window:

 .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 considered a subroutine . A subroutine is a mini-program that performs some action for the main program. In this case, it is loading a string. However, you will want to pay attention to the references that use this subroutine. These will be listed at the top of the routine under the CODE XREF, which stands for cross-reference. In our case, there are two addresses in this program that call this subroutine; they are sub_110E8+28 and sub_110E8+40. While these addresses may appear a bit cryptic, they are easy to understand. In short, the cross-reference sub_110E8+28 tells you that this LoadStringW subroutine was called by another subroutine that is located at address 110E8 in the program. The actual call to LoadStringW was made at the base 110E8 address plus 28 (hex) bits of memory into the routine.

Not all XREFs are always visible. If there are more than two, there will be a "..." after the second reference.

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 promising . Within a short chunk of code, you have several function calls that seem to be part of writing a message to a screen (i.e., BeginPaint, GetClientRect, LoadStringW, wcslen, DrawTextW). Now we will use the lessons we've learned to see what we can do.

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, assuming that the message is loaded into memory.

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

Figure 4-7. IDA Pro status bar showing memory address

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.

4.3.4 Microsoft's eMbedded Visual Tools

Currently, there are very few tools available for live debugging of Windows CE devices. The choice of free tools is even more limited. However, Microsoft, in its benevolent wisdom, has provided just such a tool. You will need this tool to reverse engineer most Windows CE applications, unless you are intimately familiar with ARM assembler. Even if you do know the ARM code, the debugger will allow you to access parts of a program that you cannot access via a disassembler.

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 variables while the program is executing. With this power, you can change a Zero flag from a 1 to a 0 in order to bypass a protection check, or even watch the program compare an entered serial number with the hardcoded number, one character at a time. Needless to say, a debugger gives you total control over the program. It not only lets you look at the heart of its operation, but allows you to redesign a program on the fly.

To illustrate this power, we will use our little example program again. We will change the message on the screen, but this time we will locate the hardcoded message in memory and redirect the LDR opcode to a different point in the memory. This has the effect of allowing us to write whatever message we want to the screen, providing it exists in memory.

4.3.5 Using the MVT

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 test.exe file into your debugger. Optionally , if you have a Windows Mobile device, you will want Microsoft ActiveSync loaded, with the device connected. In this case, be sure to have a copy of the test.exe file stored on the CE device, preferably under the root folder.

  1. Open Microsoft eMbedded Visual C++.

  2. Select File Open.

  3. .exe , .dll , .ocx ).

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.

Figure 4-8. Microsoft eMbedded Visual C++ connecting screen
Figure 4-9. Microsoft eMbedded Visual C++ CPU warning

Click OK on the next warning screen (Figure 4-10).

Figure 4-10. Microsoft eMbedded Visual C++ platform warning

The file will download and some file verification will occur.

Click OK on the debugging information warning screen (Figure 4-11).

Figure 4-11. Microsoft eMbedded Visual C++ debugging information alert

Patiently wait as the program launches.

You will be asked for several .dll files. For this example, they can be canceled . Note that you may be asked for system .dll s that you do not have; in this case, you can easily find them online for download.

Patiently wait for the program to synchronize.

4.3.6 Experiencing the MVC Environment

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 turn an equal condition into an unequal condition, which will allow you to control the flow of the code.

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 valuable as a means to sniff out a serial number or value to which you want access. We demonstrate this process in our example.

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 jumps to the next line after the BL from the main function.

4.3.7 Reverse Engineering test.exe

Now that you are familiar with the basic layout of the MVC, let's try it out. For this example, we use the test.exe program, which you have already altered via the hex editor. Our goal is to use this program as a foundation, but we are going to once again alter the displayed text using some of the methods previously discussed. Although this example is simple, it allows you to become familiar with the embedded debugging environment.

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 test.exe program resides. We will use the Modules window. Once we open this window, we quickly see that the test.exe program is between 0x2E010000 and 0x2E015FFF. (Note that the first two characters may vary. It is important to interpret the following examples if your address does not match them exactly.) You may have noted that you are already sitting in this memory block, but using the Modules window is a good way to validate that you are in the correct section. Next, hit Alt-G to open the Goto window. Enter the address 2E01135C, which is based on the 2E value combined with the 0001135C address value we have deduced from early exploration.

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.

Figure 4-12. Results of MVT reverse engineering

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 paused at the BPX, you can see that the register values are all filled. Note that some are red and some are black. The red ones symbolize changes, making it easy to spot values that have been updated. As an example, hit the F11 key. The F11 key executes the BL code at 1135C, which in turn causes the R0-R3, R12, Lr, PC, and Psr values to change.

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 happened . R0 now equals C, which is the hex equivalent to the value 12. If you recall, the original message was 12 characters long. R1 also changed, and now holds the memory address of the string. To see the string, hit Alt-6 to open the Memory window. Once the window is open, type in the value held by R1 and hit Enter. This should cause the value TEST to appear at the top of the Memory window.

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  >  

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

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