首页 > > 详细

辅导 program、c++语言程序讲解

Software Vulnerabilities Exercise 2 - Basic buffer overffow exploits for
the receiver application
P Evans
October 31, 2024
1 Overview and Objectives
In the previous exercise you saw that by sending too much data to the networking application you can cause it
to crash or behave in an unexpected way. This session will look at how if you send a vulnerable program too
much data, but this data is carefully chosen, you can alter the commands that the program executes and change
its behaviour in a controlled way. In Exercise3 you’ll then look at how you can cause the programs to execute
arbitrary commands that the programmer never intended.
After the this exercise you should:
ˆ Know what a stack buffer overffow is and why it is dangerous
ˆ Understand how a buffer overffow can be used to force a vulnerable program to behave in ways that the
original programmer didn’t intend.
ˆ Understand how a debugger can be used to analyse a program to determine the memory locations used
to store variables and functions.
1.1 Objective 2.1: Simplify the receiver application
ˆ To analyse the buffer overffow vulnerability we dont need to include all of the networking code developed
in the last exercise.
ˆ You will create a copy of the receiver application Code::Blocks project that contains the same processMessage()
function, but without the networking code.
ˆ This is just to make the following exercises a bit easier, you will use the networked application again later.
1.2 Objective 2.2: Analyse the consequences of a buffer overffow in more detail
ˆ Last week you saw that by putting too much data in either the username or content ffelds could cause the
program to crash.
ˆ The point of this exercise is to show why the program crashes (the mechanism that actually causes the
crash)
ˆ You will need to learn how to use the debugger to do this.
1.3 Objective 2.3: Exploiting the overffow to falsify (or spoof ) the sender’s username
ˆ
By sending a carefully chosen bad message, you can trick the receiving application into telling the user
that the message came from someone it didnt.
ˆ You need to understand how to create this message and why this works.
11.4 Objective 2.4: Changing the ffow of a program’s execution
ˆ The spooffng example above just illustrates how overffowing a character string can inffuence other variables
with a program.
ˆ This example (and the advanced examples on the next work sheet) show how an overffow can actually
change the behaviour of the program - in this case that is to skip over lines of code that should normally
be executed.
22 Guidance for Objective 2.1: Simplify the receiver application
To analyse the buffer overffow vulnerability we don’t need to include all of the networking code developed in
the last exercise. We will introduce this again later but its better to simplify the program for now. The ffrst
task is to create a new Code::Blocks project and enter the following code in main.c. This code is the same as
for the receiver program you looked at last week except that the message is speciffed in the code, rather than
being received through a network socket. We will go back to the networked code later, this just makes testing
easier as you don’t need to run two programs!
Create the new project and make this change to the project settings:
ˆ Right click on the project name in the project viewer (tree view to left of screen) and select Build Options.
ˆ Go to the Compiler settings tab, then Other compiler options and add the text: -fno-unwind-tables -o0.
This just simpliffes the code that the compiler generates which may help later if you disassemble the
program, the methods demonstrated should still work without these options - we are not cheating!
Now enter the following code as your program. You can download the code from Moodle if you prefer (look for
SupplementaryMaterial/oftest.c):
1 #inc lude
2 #inc lude
3 #inc lude
4 #inc lude
5
6 /* Use o f g l o b a l v a r i a b l e not i d e a l but i t
7 * makes some the examples much e a s i e r ! */
8 char message [ 4 0 9 6 ] ;
9
10
11 void proc e s sMe s sage () {
12 char senderName [ 7 0 ] ;
13 char messageContent [ 1 8 0 ] ;
14
15 /* Ext ract the message */
16 s t r cpy ( senderName , message ) ;
17 s t r cpy ( messageContent , message+s t r l e n ( senderName )+1 ) ;
18
19 /* Di splay the message */
20 p r i n tf ( ”\n
===============================================================================\n”
21 ”Message from : %.69 s \n”
22 ”===============================================================================\n”
23 ”%.179 s \n”
24 ”===============================================================================\n\
n\n\n” ,
25 senderName , messageContent ) ;
26
27 r e turn ;
28 }
29
30
31 i n t readInput ( char * input ) {
32 char * inpt r , * outpt r , hex [ 3 ] ;
33
34 // read l i n e from keyboard input
35 g e t s ( input ) ;
36
37 // need to conve r t any hexadecimal cha r a c t e r codes to c h a r a c t e r s
38 // This a ll ows you to ent e r non=a s c i i c h a r a c t e r s in l a t e r e x e r c i s e s
39 hex [ 2 ] = ’ \0 ’ ;
40 in pt r = outpt r = input ;
41 whil e ( * in pt r ) {
42 i f ( * in pt r == ’ \\ ’ && *( in pt r +1) == ’ x ’ ) {
343 hex [ 0 ] = *( in pt r +2) ;
44 hex [ 1 ] = *( in pt r +3) ;
45 * outpt r++ = ( char ) s t r t o l ( hex , NULL, 16 ) ;
46 in pt r += 4 ;
47 } e l s e {
48 * outpt r++ = * in pt r++;
49 }
50 }
51
52 * outpt r = ’ \0 ’ ;
53
54 r e turn s t r l e n ( input ) ;
55 }
56
57
58 i n t main () {
59 i n t nameSize , c ont entSi z e ;
60 i n t i ;
61 i n t j = 0 ;
62
63 /* Put the username and message int o a b u ff e r to s imul a t e the data that
64 * would be r e c e i v e d in the networked a p p l i c a ti o n */
65 p r i n tf ( ”Enter username : ” ) ;
66 nameSize = readInput ( message ) ;
67 p r i n tf ( ”Enter message : ” ) ;
68 c ont entSi z e = readInput ( message+s t r l e n ( message )+1 ) ;
69
70 /* Di splay inf o rma ti on about the mes s sage */
71 p r i n tf ( ”\nUsername : %s \n” , message ) ;
72 p r i n tf ( ”Message : %s \n” , message+s t r l e n ( message )+1 ) ;
73
74 p r i n tf ( ”\nBytes : ” ) ;
75 f o r ( i = 0 ; i < nameSize + c ont entSi z e + 2 ; i++ ) {
76 p r i n tf ( ”%02x ” , ( uns igned char ) message [ i ] ) ;
77 }
78 p r i n tf ( ”\n [ username=%d char s , cont ent=%d char s ] \ n\n” , nameSize , c ont entSi z e ) ;
79
80 /* Call the pr o c e s s message func ti on same as in Exe r c i s e 1 */
81 proc e s sMe s sage ( message ) ;
82
83 /* Some a r b it r a r y commands to exe cut e a ft e r the proc e s sMe s sage func ti on
84 * ( t h i s w i l l make s ens e l a t e r ! ) */
85 j = j + 1 ;
86 j = j + 1 ;
87 j = j + 1 ;
88 p r i n tf ( ”The f i n a l value o f j i s %d\n” , j ) ;
89
90 r e turn 0 ;
91 }
Note that there are a few arbitrary commands after processMessage() is called - these will be used to illustrate
how an overffow can change the code that is executed when the program runs.
Build and run the application and if all is well, you should see the output shown in Figure 1
4Figure 1: Expected output from the simpliffed application
3 Guidance for Objective 2.2: Analyse the consequences of a buffer
overffow in more detail
Last week you saw that by putting too much data in either the username or content ffelds could cause the
program to crash. The point of this exercise is to show why the program crashes (the mechanism that actually
causes the crash) and then to show why this mechanism is actually a signiffcant cybersecurity vulnerability.
Based on what we covered in the previous lectures we can start by drawing a diagram of the stack region of
memory in the processMemory function, this is shown in Figure 2.
Figure 2: Structure of stack within processMessage()
The stack region of memory is used to store the variables that are used within the function (senderName and
processMessage). The basic principle behind a buffer overffow is that by putting too much information in one
variable you can also overwrite another variable that you are not supposed to be able to access. Since the stack
5grows downwards on most common systems, overflowing a variable allows you to modify any variable that was
created previously.
3.1 Find Offset1 using the gdb debugger
First you will confirm the value for Offset1 in Figure 2. You would expect that the memory address for senderName
should be equal to the memory address for messageContent + 180bytes but you will need to check that
this is the case as in some cases the compiler will insert padding between variables on the stack.
To do this you can use the debugger that is built into Code::Blocks. A debugger is used to analyse a program
in detail while is running and is usually used to find reasons why a program may be crashing or behaving
unexpectedly. In this case you will use the debugger in a very simple way to find the memory addresses of some
variables.
For some reason the path to the debugger executable (gdb32.exe) is not always configured in Code::Blocks.
To check/correct this, go the the Settings item in the toolbar, then choose Debugger. Click the default tab, and
look at the Executable Path option, if this is red then the debugger isn’t configured. You’ll need to select the
gdb32.exe executable at: Code::Blocks directory/MinGW/bin/gdb32.exe.
Figure 3: Configuring the debugger in Code::Blocks.
Before running the debugger you need to define a breakpoint, a breakpoint is just a specific point in the program
where the program will pause to allow you to use the debugger. In this case you want to use the debugger to
analyse the variables in the processMessage() function so you need to set a breakpoint there, we will set the
breakpoint at the end of the function so we can see what the final value of all variables is. Left click in the
margin of the Code::Blocks editor just before the return; statement in processMessage(). A red circle will appear
to show that the breakpoint has been set. Once you’ve added the breakpoint you need to run the program using
the debugger. Click the red arrow in the toolbar and the program should run but then pause at the location
you specified. See figure 4.
A yellow arrow will appear in the margin to show where the program has paused and this will also be displayed
in the debugging window, a command window should be availble at the bottom of the screen where you
can type debugging commands. See figure 5.
You now need to find the memory addresses used for the senderName and messageContent buffers. To do this
you use the examine command. Type x senderName in the debugger command window and you’ll get an output
that consists of two hexadecimal numbers:
1 > x senderName
2 0 x 2 8 f e a 0 : 0 x6c756150
The first number (0x28fea0 ) is the address stored in the string pointer and the second number (0x6c756150 ) is
the first 4 bytes of data that can be found at this address but in reverse order (0x6c = l, 0x75 = u, 0x61 =
a, 0x50 = P - see an ASCII table). You can ignore the second number for now as we’re just interested in the
6Figure 4: Setting a breakpoint in the processMessage() function and running the debugger using Code::Blocks.
address of these variables. Find the address of both senderName and messageContent and from these confirm
the value of Offset1:
ˆ (a) Address of senderName: ........
Figure 5: The debugger is now running and has paused at a breakpoint in the processMessage() function
(indicated by yellow arrow). The debugger command window is available for you to type commands.
7ˆ (b) Address of messageContent: ........
ˆ (c) Offset1 = (a) - (b): ........
You can use the Windows calculator in programmer mode to perform calculations on hexadecimal numbers.
Note on memory address formatting: The 0xXXXXXXXX formatting string is memory address
in hexadecimal formal, prefixed by 0x, i.e. the XXXXXXXX part is a hecadecimal number
representing the memory addresses. This is a common format for displaying memory addresses
and other hexadecimal numbers in many applications.
3.2 Use Offset1 to investigate the effects of an overflow
If you computed offset 1 to be 180, you should be able to set the message content string to contain up to 179
characters without overflowing (remember that 1 character is needed for the NULL byte string terminator),
Check you are happy with your values for offset 1 though - don’t just assume it is 180!. Now
you need to find out what happens when you put too many characters in the string and run the program, it
is a good idea to prepare your strings in a text editor and paste them into the program so you don’t need to
rewrite them everytime. If you download a programming text editor like Notepad++, it will tell you how many
characters are on each line which will help you generate strings that are the correct length.
ˆ Try changing the message content string so that it is exactly (offset1-1) bytes long.The program will tell
you how long each string you’ve entered. Make the string longer by adding some characters before ”hello”
so hello remains at the end of the string.
ˆ Add 1 more padding character and rerun the program. What happens? (Look at the displayed username)
ˆ Add a few more padding characters before Hello. What happens? Why is this?
84 Guidance for Objective 2.3: Exploiting the overflow to falsify (or
spoof ) the sender’s username
Hopefully you have seen how putting too much data in a buffer can overflow that buffer and cause other variables
that are stored nearby to be modified. This can allow an attacker to modify variables within a program that
they shouldn’t be able to, in this case it allows someone to change the contents of the senderName buffer by
sending a message whose content exceeds the length of the messageContent buffer.
You now need to demonstrate how this overflowing technique could be dangerous. The idea is to trick the
processMessage function into displaying an incorrect username for the message sender.
Enter the username and content messages as something like:
1 Enter username : A Hacker
2 Enter message : H ell o
If you run your program the output should be:
1 ===============================================================================
2 Message from : A Hacker
3 ===============================================================================
4 H ell o
5 ===============================================================================
You need to add some additional information to the message content string so that it overflows the messageContent
buffer and overwrites the senderName buffer in processMessage(). The idea is to overwrite the real
username ”A Hacker” with something else. The overflow padding you use should be transparent to the user
when printed to screen by the processMessage() function.
Once you’ve worked out what the message content should be, try it out on your sender/receiver applications.
Open the sender application and enter your username as ”A Hacker” and then try to send a message that
appears to come from another username. Develop the correct string using the application from this exercise,
then test on the networked application from last week.
You should be able to reproduce the results in figure 6. Make a note of the username and message strings
that you used.
9Figure 6: Spoofed username in sender & receiver applications using a buffer overflow. Despite the username
specified in the sender program being ”A Hacker”, the second message appears to come from a user called
”Paul”
105 Guidance for Objective 2.4: Changing the flow of a program’s
execution
The spoofing example above just illustrates how overflowing a character string can influence other variables
with a program. In many real cyber attacks the overflow technique is actually used in a different and more
dangerous way.
5.1 The theory
As discussed in the previous lectures, the processor relies on one of its registers (EIP for a 32bit application) to
tell it where to find the next instruction to execute. When the processor encounters a function call, it has two
possible values for EIP:
ˆ The address of the next instruction if the processor enters the function.
ˆ The address of the next instruction if the processor does not enter the function.
Take the lab2 example program:
1 /* ** ======================= E x t r ac t from main ( ) ======================= ** */
2 p r o c e s sM e s s a g e ( message ) ;
3
4 j = j + 1 ;
5 j = j + 1 ;
6 j = j + 1 ;
7 /* ** ====================== /E x t r ac t from main ( ) ======================= ** */
1 /* ** ================== E x t r ac t from p r o c e s sM e s s a g e ( ) ================== ** */
2 v oid p r o c e s sM e s s a g e ( c o n s t ch a r * message ) {
3 ch a r senderName [ 7 0 ] ;
4 ch a r messageContent [ 1 8 0 ] ;
5
6 s t r c p y ( senderName , message ) ;
7 s t r c p y ( messageContent , message+s t r l e n ( senderName )+1 ) ;
8 .
9 .
10 .
11 r e t u r n ;
12 /* ** ================= /E x t r ac t from p r o c e s sM e s s a g e ( ) ================== ** */
When the processor reaches the line processMessage( message ), the flow of execution must transfer into the
processMessage() function. This means that the EIP register will be set to the address of the first instruction in
processMessage() - somewhere just before strcpy( senderName, message ). Had this jump into processMessage()
not occured, the next instruction would have been j = j + 1 in main().
Before the processor jumps into processMessage(), it needs to remember where it would have gone if the function
call hadn’t happened - because this is where it needs to go back to when processMessage() returns. It does this
by saving the alternative EIP value onto the stack so that it can be reloaded when processMessage returns.
In addition to saving the return EIP value onto the stack, one more variable is saved. This is the content
of another register called EBP, this register tells the processor where in the stack it can find the variables belonging
to the current function. When the processor enters processMessage(), EBP changes to the address where
processMessage()’s variables (senderName, messageContent) are located. When the function returns back to
main(), EBP must be changed back to point to the location where main()’s variables are located (username,
content, messageSize, i, j). The old EBP value is therefore also saved to the stack so that it can be restored
when processMessage() returns.
Important: The key point to understand is that when the processMessage() is running, there are two variables
stored on the stack just above senderName and messageContent. These two variables describe how to get back
to main() from processMessage(): one tells the processor where the next instruction in main() is, and one tells it
where to find main()’s variables again. If you overflow senderName or messageContent and overwrite either the
saved EBP or EIP variables, the program will most likely crash when it tries to return from processMessage()
11because you will have corrupted the information that the processor needs to succesfully return to main(). The
clever bit is that if you overflow senderName or messageContent and overwrite the saved EIP with a carefully
chosen value, you can actually take control of the program. Now, when processMessage() returns, it no longer
jumps back to ‘j = j + 1;‘, it can jump to any bit of code you want it to.
Advanced Suggestion You can see the contents of the processor’s registers when you pause the program
using the GDB debugger. To do this type info registers (or i r for short) in the debugger command window.
5.2 Determine Offset2 and Offset3
The first thing you need to know is where the saved EBP and EIP are in relation to a known memory address.
If the saved EBP is corrupted the program will crash so the easiest way to find Offset2 is to keep making the
username string longer, 4 bytes at a time, until the program crashes and then look at the length of the string.
Figure 7: Structure of stack within processMessage() - Offset2 is the number of bytes from the start of the
username buffer to the location where EBP is saved. Offset3 is the number of bytes from the start of the
username buffer to the location where EIP is saved and since EBP is a 32bit number, Offset3 = Offset2+4
bytes.
Start with a username string (in main()) that is 69 characters long, this will completely fill the senderName
buffer (including the NULL string terminator, the string is actually 70 bytes long). Make sure the content string
is less than 179 characters long so it doesn’t overflow and cause problems.
Construct your string in a text editor then paste them in to the program so that you can easily add a few
characters at a time, your strings will look something like:
1 |================ 59 Bytes =============|4B=|4B=|4B=| |
2 ˆ=NULL t e rmi n a t o r .
3 Username : xxxxxxxxxx . . . . . . . . . . . . . . . . . . . . . . . . xxxxxxxxAAAABBBB
4 Message : H ell o
ˆ Keep adding 4 characters at a time to the username string and re-run the program. When you overwrite
the saved EBP the program will crash.
ˆ When the program crashes, compute Offset2. If the program didnt crash last time, but crashed when you
added another 4 bytes your string is now offset2+4 bytes long (including the NULL character)
12ˆ Any 32bit value (such as the saved EBP) is 4 bytes long, therefore Offset3 = Offset2 + 4.
Note: With Code::Blocks and Windows 10, it is not always obvious that the program has crashed. You need
to pay attention to the output from the program, normally you will see something like:
1 P r o c e s s r e t u r n e d 0 ( 0 x0 ) e x e c u ti o n time : 0. 0 1 4 s
2 P r e s s any key t o c o n ti n u e .
If the program crashes, the process will return an error code:
1 P r o c e s s r e t u r n e d =1073741819 ( 0 xC0000005 ) e x e c u ti o n time : 1. 5 3 6 s
2 P r e s s any key t o c o n ti n u e .
5.3 Manually changing the saved EIP value
Now you know Offset3 you are able to modify the saved EIP and change the execution of the program when
processMessage returns. To start with you will do this by adding a line of code in the program that will allow
you to overwrite the saved EIP with a value of your choosing. This isn’t a viable way of exploiting the program
(an attacker wouldnt normally be able to modify and recompile a target program) but it does easily allow you
to understand how changing the saved EIP can affect the program. The EIP value saved on the stack can
be accessed using the following code somewhere in processMessage():
1 * ( ( un si gned i n t *) (&senderName [ 0 ] + O f f s e t 3 ) )
This just says ”I want to access the number located at the memory address ’&senderName[0] + Offset3’ bytes.”.
Obviously you need to substitute in your value for offset3!
5.4 How does changing the saved EIP modify the program’s behaviour?
If you increase the saved EIP, you should be able modify what happens when the program returns from processMessage().
Normally it would return to the first instruction after the call to processMessage() in main()
(the first j = j+1). If you increase the saved EIP value slightly, processMessage will return to main at a slightly
different point. Notice that there are three j = j + 1 instructions after the call of processMessage(). How much
do you need to increase the saved EIP by to cause the program to skip over one or more of these instructions?
Use:
1 * ( ( un si gned i n t *) (&senderName [ 0 ] + O f f s e t 3 ) ) += 1 ;
to increase the saved EIP by 1 byte, use:
1 * ( ( un si gned i n t *) (&senderName [ 0 ] + O f f s e t 3 ) ) += 2 ;
to increase the saved EIP by 2 bytes etc. You’ll need to increase EIP 1 byte at a time until you manage to
cleanly jump over a j = j + 1 instruction, when you do this the final output of the program will change from:
1 The f i n a l v al u e o f j i s 3
to:
1 The f i n a l v al u e o f j i s 2
The program will crash if you only partially jump over an instruction.
ˆ How many bytes do you need to increase the saved EIP by to skip over 1 j = j + 1 instruction?
ˆ How many bytes do you need to increase the saved EIP by to skip over 2 j = j + 1 instructions?
ˆ How many bytes of memory does a j = j + 1 instruction take up?
ˆ Add other, different instructions at the end of main and compute the number of bytes required to jump
them if you want.
13

联系我们
  • QQ:99515681
  • 邮箱:99515681@qq.com
  • 工作时间:8:00-21:00
  • 微信:codinghelp
热点标签

联系我们 - QQ: 99515681 微信:codinghelp
程序辅导网!