Chapter 3-07

来源:互联网 发布:切割大小头怎样编程 编辑:程序博客网 时间:2024/05/21 06:52

Please indicate the source if you want to reprint: http://blog.csdn.net/gaoxiangnumber1.
3.10 Putting It Together: Understanding Pointers
Every pointer has an associated type. This type indicates what kind of object the pointer points to. char **cpp: cpp is a pointer to an object that itself is a pointer to an object of type char.
The “void *” type represents a generic pointer. E.g. the malloc function returns a generic pointer, which is converted to a typed pointer via either an explicit cast or by the implicit casting of the assignment operation.
Pointer types are not part of machine code; they are an abstraction provided by C to help programmers avoid addressing errors.
Every pointer has a value that is an address of some object of the designated type. The special NULL (0) value indicates that the pointer does not point anywhere.
Pointers are created with the & operator. This operator can be applied to any C expression that is categorized as an lvalue, meaning an expression that can appear on the left side of an assignment. Machine-code realization of the & operator often uses the leal instruction to compute the expression value, since this instruction is designed to compute the address of a memory reference.
Dereferencing a pointer(by using *) is implemented by a memory reference, either storing to or retrieving from the specified address.
The name of an array can be referenced (but not updated) as if it were a pointer variable. Array referencing (e.g., a[3]) has the exact same effect as pointer arithmetic and dereferencing (e.g., *(a+3)). Both array referencing and pointer arithmetic require scaling the offsets by the object size.
Casting from one type of pointer to another changes its type but not its value. One effect of casting is to change any scaling of pointer arithmetic. If p is a pointer of type char * having value p, then the expression (int ) p+7 computes p + 28, while (int ) (p+7) computes p + 7. (casting has higher precedence than addition.)
Pointers can point to functions.
Assume function:
int fun(int x, int *p);
then declare and assign a pointer fp to this function:
(int) (fp)(int, int );
fp = fun;
We can then invoke the function using this pointer:
int y = 1;
int result = fp(3, &y);
The value of a function pointer is the address of the first instruction in the machine-code representation of the function.
3.11 Life in the Real World: Using the gdb Debugger

Recent versions of gcc employ an extensive set of optimizations at level two, making the mapping between the source code and the generated code more difficult to discern. E.g.:
***The control structures become more entangled. Most procedures have multiple return points, and the stack management code to set up and complete a function is intermixed with the code implementing the operations of the procedure.
***Procedure calls are often inlined, replacing them by the instructions implementing the procedures. This eliminates much of the overhead involved in calling and returning from a function, and it enables optimizations that are specific to individual function calls. On the other hand, if we try to set a breakpoint for a function in a debugger, we might never encounter a call to this function.
***Recursion is often replaced by iteration. Again, this can lead to some surprises when we try to monitor program execution with a debugger.
3.12 Out-of-Bounds Memory References and Buffer Overflow


gets reads a line from the standard input, stopping when either a terminating newline character or some error condition is encountered. It copies this string to the location designated by argument s, and terminates the string with a null character.
The problem with gets is that it has no way to determine whether sufficient space has been allocated to hold the entire string. Any string longer than seven characters will cause an out-of-bounds write.
Assembly code generated by gcc for echo shows how the stack is organized:

Program stores the contents of registers %ebp and %ebx on the stack, and then allocates an additional 20 bytes by subtracting 20 from the stack pointer (line 5). The location of character array buf is computed as 12 bytes below %ebp (line 6), just below the stored value of %ebx, as illustrated in Figure 3.31.

If the user types at most seven characters, the string returned by gets (including the terminating null) will fit within the space allocated for buf. A longer string will cause gets to overwrite some of the information stored on the stack. As the string gets longer, the following information will get corrupted:

As the number of characters increases, more state gets corrupted. Depending on which portions of the state are affected, the program can misbehave in several different ways:
***If the stored value of %ebx is corrupted, then this register will not be restored properly in line 12, and so the caller will not be able to rely on the integrity of this register.
***If the stored value of %ebp is corrupted, then this register will not be restored properly on line 13, and so the caller will not be able to reference its local variables or parameters properly.
***If the stored value of the return address is corrupted, then the ret instruction (line 14) will cause the program to jump to a totally unexpected location.
The program is fed with a string that contains the byte encoding of some executable code, called the exploit code, plus some extra bytes that overwrite the return address with a pointer to the exploit code. The effect of executing the ret instruction is then to jump to the exploit code. In one form of attack, the exploit code then uses a system call to start up a shell program, providing the attacker with a range of operating system functions. In another form, the exploit code performs some otherwise unauthorized task, repairs the damage to the stack, and then executes ret a second time, causing an (apparently) normal return to the caller.
3.12.1 Thwarting Buffer Overflow Attacks
In order to insert exploit code into a system, the attacker needs to inject both the code as well as a pointer to this code as part of the attack string. Generating this pointer requires knowing the stack address where the string will be located. Historically, the stack addresses for a program were highly predictable. If an attacker could determine the stack addresses used by a common Web server, it could devise an attack that would work on many machines.
The idea of stack randomization is to make the position of the stack vary from one run of a program to another. Even if many machines are running identical code, they would all be using different stack addresses. This is implemented by allocating a random amount of space between 0 and n bytes on the stack at the start of a program, for example, by using the allocation function alloca, which allocates space for a specified number of bytes on the stack. This allocated space is not used by the program, but it causes all subsequent stack locations to vary from one execution of a program to another. The allocation range n needs to be large enough to get sufficient variations in the stack addresses, yet small enough that it does not waste too much space in the program.
A simple way to determine a “typical” stack address:
int main()
{
int local;
printf(“local at %p\n”, &local);
return 0;
}
This code prints the address of a local variable in the main function. Running the code 10,000 times on a Linux machine in 32-bit mode, the addresses ranged from 0xff7fa7e0 to 0xffffd7e0, a range of around 223.
Stack randomization has become standard practice in Linux systems. It is one of a larger class of techniques known as address-space layout randomization. With ASLR, different parts of the program are loaded into different regions of memory each time a program is run. That means that a program running on one machine will have very different address mappings than the same program running on other machines. This can thwart some forms of attack.
gcc incorporate a mechanism known as stack protector into the generated code to detect buffer overruns. The idea is to store a special canary value in the stack frame between any local buffer and the rest of the stack state.

This canary value (= guard value) is generated randomly each time the program is run, and there is no easy way for an attacker to determine what it is. Before restoring the register state and returning from the function, the program checks if the canary has been altered by some operation of this function or one that it has called. If so, the program aborts with an error.
A final step is limiting which memory regions hold executable code to eliminate the ability of an attacker to insert executable code into a system.
In typical programs, only the portion of memory holding the code generated by the compiler need be executable. The other portions can be restricted to allow just reading and writing. The virtual memory space is logically divided into pages, typically with 2048 or 4096 bytes per page. The hardware supports different forms of memory protection, indicating the forms of access allowed by both user programs and by the operating system kernel.
Many systems allow control over three forms of access: read (reading data from memory), write (storing data into memory), and execute (treating the memory contents as machine-level code). The x86 architecture merged the read and execute access controls into a single 1-bit flag, so that any page marked as readable was also executable. The stack had to be kept both readable and writable, and therefore the bytes on the stack were also executable. Various schemes were implemented to be able to limit some pages to being readable but not executable, but these generally introduced significant inefficiencies.
AMD introduced an “NX” (for “no-execute”) bit into the memory protection for its 64-bit processors, separating the read and execute access modes, and Intel followed suit. With this feature, the stack can be marked as being readable and writable, but not executable, and the checking of whether a page is executable is performed in hardware, with no penalty in efficiency.
3.13 x86-64: Extending IA32 to 64 Bits
3.13.1 History and Motivation for x86-64
The word size of a machine defines the range of virtual addresses that programs can use, giving a 4-gigabyte virtual address space in the case of 32 bits.
3.13.2 An Overview of x86-64
New versions of gcc generate x86-64 code substantially different from that generated for IA32 machines:
***Pointers and long integers are 64 bits long. Integer arithmetic operations support 8, 16, 32, and 64-bit data types.
***The set of general-purpose registers is expanded from 8 to 16.
***Much of the program state is held in registers rather than on the stack. Integer and pointer procedure arguments (up to 6) are passed via registers. Some procedures do not need to access the stack at all.
***Conditional operations are implemented using conditional move instructions when possible, yielding better performance than traditional branching code.
***Floating-point operations are implemented using the register-oriented instruction set introduced with SSE version 2, rather than the stack-based approach supported by IA32.

This gives programs the ability to access 264 bytes.
long int simple_l(long int *xp, long int y)
{
long int t = *xp + y;
*xp = t;
return t;
}
When gcc is run on an x86-64 Linux machine with the command line
unix> gcc -O1 -S -m32 code.c
it generates code that is compatible with any IA32 machine. instructions read (R) data from memory and which instructions write (W) data to memory:

When we instruct gcc to generate x86-64 code
unix> gcc -O1 -S -m64 code.c

Some of the key differences include:
***The pointers and variables declared as long integers are now 64 bits (quad words) rather than 32 bits (long words).
***We see the 64-bit versions of registers (e.g., %rsi and %rdi, rather than %esi
and %edi). The procedure returns a value by storing it in register %rax.
***No stack frame gets generated in the x86-64 version. This eliminates the instructions that set up (lines 2–3) and remove (line 8) the stack frame in the IA32 code.
***Arguments xp and y are passed in registers (%rdi and %rsi, respectively) rather than on the stack. This eliminates the need to fetch the arguments from memory.
In general, x86-64 code is more compact, requires fewer memory accesses, and runs more efficiently than the corresponding IA32 code.
Please indicate the source if you want to reprint: http://blog.csdn.net/gaoxiangnumber1.

0 0