Chapter 5. Overflow Attacks

Attacking applications is a core technique for vulnerability researchers. Test engineers can spare a company from needless expense and public embarrassment by finding early exploitation points in the company’s software. This chapter reviews a variety of application attack techniques, including buffer overflows and heap overflows. It also builds on the reverse engineering knowledge gained from the previous chapters.

Buffer Overflows

To exploit an overflow, you need a thorough knowledge of assembly language, C++, and the operating system you wish to attack. This chapter describes buffer overflows, traces their evolution, and even walks you through a live sample.

A buffer overflow attack deliberately enters more data than a program was written to handle. The extra data overflows the region of memory set aside to accept it, thus overwriting another region of memory that was meant to hold some of the program’s instructions. In the ideal version of this attack, the overflow values introduced become new instructions that give the attacker control of the target processor.

Buffer overflow attacks are not a new phenomenon. For example, the original Morris worm in 1988 used a buffer overflow. In fact, the issue of buffer overflow risks to computer systems has been recognized since the 1960s.

A Sample Overflow

Buffer overflows result from an inherent weakness in the C++ programming language. The problem (which is inherited from C and likewise found in other languages, such as Fortran) is that C++ does not automatically perform bounds-checking when passing data. To understand this concept, consider the following sample code that illustrates how a C/C++ function returns data to the main program:

// lunch.cpp : Overflowing the stomach buffer

#include <stdafx.h>
#include <stdio.h>
#include <string.h>

void bigmac(char *p);

int main(int argc, char *argv[])
{
    bigmac("Could you supersize that please?"); // size > 9 overflows
    return 0;
}

void bigmac(char *p)
{
     char stomach[10]; //limit the size to 10
     strcpy(stomach, p);
     printf(stomach);
}

To test this program, you compile it using a C++ compiler. Although the program compiles without errors, when we execute it we get a program crash similar to Figure 5-1.

Buffer overflow crash

Figure 5-1. Buffer overflow crash

What happened? When this program executes, it calls the function bigmac and passes it the long string “Could you supersize that please?” Unfortunately, strcpy( ) never checks the string’s length. This is dangerous, because in this case passing a string longer than nine characters generates a buffer overflow.

Like several other C++ functions, strcpy( ) is inherently weak, in that it will write the extra characters past the variable end. This usually results in a program crash. In this particular case, the crash was an error in reading past the end of the statically allocated string. In a worst-case scenario, such an overflow might allow you to execute arbitrary code on the target system, as discussed later in this chapter.

Understanding Buffers

Buffer overflows are a leading type of security vulnerability. In order to understand how a hacker can use a buffer overflow to infiltrate or crash a computer, you need to understand exactly what a buffer is.

Tip

This section provides a basic introduction to buffers; experienced users should skip ahead to Section 5.3.

A computer program consists of code that accesses variables stored in various locations in memory. As a program is executed, each variable is assigned a specific amount of memory, determined by the type of information the variable is expected to hold. For example, a Short Integer only needs a little bit of memory, whereas a Long Integer needs more space in the computer’s memory (RAM). There are many different possible types of variables, each with its own predefined memory length. The space set aside in the memory is used to store information that the program needs for its execution. The program stores the value of a variable in this memory space, then pulls the value back out of memory when it’s needed. This virtual space is called a buffer.

A good analogy for a buffer is a categorized CD collection. You have probably seen the tall CD towers that hold about 300 CDs. Your computer’s memory is similar to a CD holder. The difference is that a computer can have millions of slots that are used to store information, compared to the relatively limited space on a CD rack. Our example CD collection consists of three main categories: Oldies, Classical, and Pop Rock (Figure 5-2). Logically, we would separate the 300 slots into 3 parts, with 100 slots for each genre of music. The bottom 100 of the CD holder is set aside for Oldies, the middle 100 is for Classical, and the top 100 contains Pop. Each slot is labeled with a number; you know where each type of music begins and ends based on the slot number.

A segmented CD rack is similar to a buffer

Figure 5-2. A segmented CD rack is similar to a buffer

A computer’s memory is very similar. When a program is loaded into memory, it automatically allocates chunks of memory for all the variables it has been programmed to use. However, instead of one slot per variable, each variable uses several slots. This situation is analagous to a CD set: if you wanted to store your four-CD Bach collection, you would use four consecutive slots. This piece of memory is called a buffer. Simply put, a buffer is just a chunk of computer memory that is set aside by a program to store the value of a variable so that it can call upon that value when it is needed.

Now that you have the general idea of what a buffer is, let us describe how a buffer overflow works. Note the accompanying picture of a sample buffer (Figure 5-3), which can be thought of as part of our CD rack. As you can see, this stack should have both Oldies (1-100) and Classical (101-200) CDs in the slots. For the point of this example, let us consider this to be your friend’s CD collection. Since you hate all oldies, classical, and pop rock, how can you trick your friend into playing your rock CD?

A sample buffer overflow

Figure 5-3. A sample buffer overflow

What do you know about your friend’s CD setup? You know the layout of his CD rack: the 1-100, 101-200, and 201-300 slot separation. You also know that your friend’s Oldies section (1-100) is almost full, with only 4 open slots (97-100), and you know that his Classical section is completely empty. Using this information to your advantage, you could give your friend a five-CD set of Barry Manilow (whom we’re considering an oldies singer, for the sake of this example), which has your rock CD concealed in the place of CD number five. Assuming your friend does not pay any attention to the slot number into which he places the gift, your rock CD would end up in slot 101. Now, you simply have to ask your friend if he would be so kind as to play something from his Classical collection. Your friend would check the slot numbers, see that there is one CD in the Classical section, and grab it. Much to his surprise, hard-core rock would come streaming out of the speakers instead of Beethoven.

This is similar to the way a hacker performs a buffer overflow attack on your computer. First, the hacker needs to find a program that you are running that has a buffer overflow vulnerability. Even if the hole does not allow the execution of malicious code, it will most likely crash the target computer. A hacker also needs to know the exact size of the buffer he is trying to overflow. In the CD rack case, it was just a matter of providing five CDs, which was one too many for the Oldies segment. For a computer, it is often just as easy.

Ideally, a well-written program will not allow anything to overflow: it’s the same as having three separate CD racks that have 100 slots each, instead of having one 300-slot CD rack. If your friend had three separate racks, he probably would have noticed that there was one CD too many in his Oldies collection and taken action to resolve the problem. This would have led him to discover your rock CD hidden in the gift.

The next part of a buffer overflow attack is to launch the payload . The payload is usually a command to allow remote access, or some other command that would get the hacker one step closer to owning the target computer. For example, Microsoft’s Internet Information Server had a buffer overflow vulnerability that allowed a hacker to make a copy of any file and place it in a location on the web server. This file could be anything that would allow remote access, from passwords to an executable file.

A successful buffer overflow hack is difficult to execute. However, even if the buffer overflow fails somewhere during its execution, it will most likely cause problems for the target. A failed buffer overflow attack often results in a program crash or, better yet, a computer crash. The program that originally allocated the segment of memory that was overwritten will not check to see if the data has changed. Therefore, it will attempt to use the information stored there and assume it is the same information it had placed there previously. For example, when the program goes to look for a number that is used to calculate the price of tea, and instead it gets the word “Bob”, the program will not know what to do.

Smashing the Stack

This section describes a typical buffer overflow. Figure 5-4 shows an example of a stack structure after a function is called. The stack pointer points at the top of the stack, which is at the bottom in the figure.

Representation of stack structure after a function call

Figure 5-4. Representation of stack structure after a function call

C++ uses the area at the top of the stack in the following order: local variables, the previous frame pointer, the return address, and the arguments of the function. This data is called the frame of the function, and it represents the status of the function. The frame pointer locates the current frame, and the previous frame pointer stores the frame pointer of the calling function.

When an attacker overflows a buffer on the stack (e.g., with extra input), the buffer will grow toward the return address. The hacker is attempting to change the return address. When the function executes, the return address is popped off the stack and the new address is executed. By overwriting this address, a hacker attempts to take control of the processor. If malicious code is located at the address, it is executed with the same privilege level as the application.

Heap Overflows

Because of increased publicity, as well as the prevention techniques mentioned in the next section, buffer overflows are becoming less frequent in well-designed code. Consequently, we can expect to see heap overflow exploits becoming more common.

The heap refers to memory that is dynamically allocated by an application for variable storage. In a heap overflow , the hacker attempts to overwrite variables such as passwords, filenames, and UIDs in the heap.

What is the difference between a buffer overflow and a heap overflow? In a buffer overflow, we are attempting to execute machine-level commands by overwriting the return address on the stack. In contrast, a heap overflow attempts to increase the level of system privilege by overwriting dynamically stored application variables. Heap overflow exploits include format bugs and malloc()/free( ) overwrites.

Researchers have also come to recognize a related class of overflows known as format bugs. The vulnerability caused by format bugs is that in C, a %n format token exists for printf format strings that commands printf to write back the number of bytes formatted so far to the corresponding argument to printf, presuming that the corresponding argument exists and is of type int *. This can be exploited if a program permits unfiltered user input to be passed directly as the first argument to printf. The varargs mechanism of C++ allows functions (e.g., printf) to accept a variable number of arguments by “popping” as many arguments off the call stack as they wish, trusting the early arguments to indicate how many additional arguments (and of what type) are to be popped. The fix to this problem is to use printf("%s", buf) instead of printf(buf).

Preventing Buffer Overflows

The ideal way to prevent buffer overflows is for the programmer to follow proper programming practices. These include the following:

  • Always check the bounds of an array before writing it to a buffer.

  • Use functions that limit the number and/or format of input characters.

  • Avoid using dangerous C functions such as the following: scanf( ), strcpy( ), strcat( ), getwd( ), gets( ), strcmp( ), sprintf( ).

How can we prevent buffer overflows in practice? Programmers use the strcpy( ) function to copy a source string to a destination buffer. Unfortunately, the destination array may not be large enough to handle the source string. If your user inputs a very long source string, she will be able to force a buffer overflow on the destination.

To prevent this error, you can specifically check each source string for length before copying it. However, a simpler alternative is strncpy( ). This function is similar to strcpy( ), except that in strncpy( ) only the first n bytes of the source string are copied, which helps to prevent a buffer overflow.

Automated Source-Code Checking

There has never been a programmer born who can code without error 100% of the time. Thus, we now examine automated tools for testing overflow conditions.

Until recently, there has been a paucity of effective tools for automated source code level auditing for buffer overflows. This is because it is horribly difficult to take into account all of the possible errors inherent in a program that is thousands of lines long.

One commercial example is PolySpace (http://www.polyspace.com), which has come up with a tool to detect buffer overflows in ANSI C applications at compilation time. While the Viewer module currently can be run on Windows, the Verifier itself requires a Linux box to run. Windows-only programmers will have to break down and install a dedicated Linux box to run PolySpace as a batch tool; the results can then be explored under Windows. If you currently do not run Linux, we recommend doing so immediately; a true security expert should be able to move between Windows and Linux with ease. However, for those who are completely Linophobic, PolySpace has started porting the Verifier engine to Windows.

Compiler Add-Ons

Linux provides various compiler add-ons and libraries that perform runtime bounds checking in C/C++. StackGuard (http://immunix.org) is one example. StackGuard detects stack smashing attacks by protecting the return address on the stack from being altered. It places a “canary” word next to the return address when a function is called. If a buffer overflow is attempted, StackGuard detects that the canary word has been altered when the function returns. If this happens, the StackGuarded program logs an adminstrator alert and terminates.

StackGuard is implemented as a small patch to the gcc code generator in the function_prolog() and function_epilog( ) routines. StackGuard utilizes function_prolog( ) to insert canaries on the stack when functions start, then uses function_epilog( ) to check canary integrity when the functions exit. It can thus detect any attempt at corrupting the return address before the function returns.

Another useful program from immunix.org is FormatGuard, which guards against format bug exploits. FormatGuard uses the ability of C++ to distinguish macros with identical names but a different number of arguments. FormatGuard provides a macro definition of the printf function for each of anywhere from 1 to 100 arguments. Each of these macros in turn calls a safe wrapper that counts the number of % characters in the format string and rejects the call if the number of arguments does not match the number of % directives.

In order for an application to be protected with FormatGuard, the application needs to be recompiled against the FormatGuard glibc headers. FormatGuard is a wrapper around the following libc calls: syslog( ), printf( ), fprintf( ), sprintf( ), snprintf( ).

Miscellaneous Protection Methods

Another way to prevent buffer overflows is to make the stack and data memory nonexecutable. This is not a complete solution, as it still allows the attacker to make the code jump into unexpected positions. However, it does make exploits more difficult to execute. This solution is available in Linux in the form of a patch.

Automatic bounds-checking tools can add another layer of protection to the above techniques. Unlike C++, Perl and Java provide innate bounds checking, saving the programmer from extensive security coding. However, automatic bounds checking can also be provided for C++ using tools under various operating systems. Examples of such tools include BOWall, Compuware’s Boundschecker, and Rational’s Purify.

A Live Challenge

Now that you have reviewed buffer overflows, the following example will let you test what you have learned using a special crackme (test application).

Tip

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.

weird.exe buffer overflow crackme

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

String disassembly of weird.exe

Figure 5-6. String disassembly of weird.exe

Screenshot of target code in IDA Pro

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:

AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA1234

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

Program crash

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 ;)
Congratulations message

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 --
");
    printf("---------------------------------
");
    printf("enter your serial please:
");
 
    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 ;)
");          
 
    theend:
    
    getch(  );
    return 0;

References

  • Building Secure Software: How to Avoid Security Problems the Right Way, by John Viega and Gary McGraw. Addison-Wesley Professional, 2001.

  • The Analyst’s weird crackme, published by +Tsehp, 2001.

  • Smashing the Stack for Fun and Profit, by Aleph One. Phrack issue #49-14, November 1996. (http://www.phrack.org)

  • “Endian Issues,” by William Stallings. Byte.com, September 1995.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset