Certainly it is influenced by many factors. For example, an already fairly experienced programmer during a private conversation said to me, “Well, why should I check the security of my own code? It will take so much time. And my manager will not ask from me if somewhere in my code I inadvertently wrote printf (buf) instead of printf (“% s”, buf) or stuff like that. But if I do not complete my work in time, I’ll have big problems. And the code works fine. Who cares about the fact that there is something wrong regarding the security of the code? After all, everything works.” This is the most harmless case. A huge number of programmers do not have a clue about how write secure code. But this isn’t even what the worst is. In my opinion, the main problem lies in the fact that programmers do not want to learn how to write secure code. During my 20+ year career, I have seen many coding virtuosos which had only one problem – they did not pay any attention to the security of their code. The problems in securing code are more significant that they were 10-15 years ago because every computer is now connected to high-speed Internet. A programmer’s minor mistake can lead to very serious consequences, from the theft of credit card data, to billion-dollar losses due to compromised trade secrets. Due to numerous errors in modern software, a hacker located in Australia can effortlessly raise the backdoor on a computer located in the U.S., so much so that no one will notice this security incident. That’s why software developers need to pay particular attention to the quality of the software from a security standpoint.
Vulnerabilities cannot be avoided 100%
The exploitation of vulnerabilities in executable code is perhaps the most frequently used type of attacks to gain control over a victim’s computer. Of course, developing absolutely invulnerable programs is an impossible task, because programs are created by people and people make mistakes. The main idea would be to minimize the risks of creating vulnerabilities for software, or at least to be able to quickly detect these vulnerabilities before they are discovered by guys with bad intentions. And that is most important factor to which attention should be paid, because the threats associated with attacks on applications are increasing. A vast number of programming errors which lead to vulnerabilities are related to the improper processing of external data. During the exploitation of such errors, the goal of the attacker is to force the program to fulfill a scenario that is different from the developer’s scenario. Ultimately the attacker wants to execute the code which they are transmitting. Such examples are endless, but to understand the basic idea, let’s consider probably the most widespread example of an attack on an application – a buffer overflow. The emergence of technologies such as DEP and ASLR have become a real headache for hackers that exploit buffer overflow errors. However, there are techniques to bypass even these technologies.
How Does It Work
To show an example of an attack on a remote system through a buffer overflow, I’ll use a small code in C++. This code implements a simple server that I’ve prepared for this article. In order not to distract from the main topic, I’ll assume that the reader is familiar with the principles of using stack memory for IA32. The server opens a socket and expects a connection from client. Once the client connects our server will read and attempt to process the data passed to it. Please note that the server uses the memcpy function to copy the data into its internal buffer allocated on the stack for further processing. The size of the received data is not analyzed before copying – and in this lies the main danger. Once the size of the data exceeds a buffer size (64 bytes) of array chNetMsg, the stack frame will be overwritten by the contents of chpBuf. To better understand what’s going on, let’s look at the instructions of the processor requiring the allocation of memory for the current stack frame. At the beginning of the function SaveData4FurtherProcessing1: void SaveData4FurtherProcessing1(char* chpBuf, int iszBuf) { char iFake[32]={0}; SaveData4FurtherProcessing2(chpBuf, iszBuf); } Here are our first instructions. It prescribes to allocate a stack frame for the function SaveData4FurtherProcessing1.
Thus the size of the stack frame for the function SaveData4FurtherProcessing1 is equal to the size of the array chFake 20h(32) bytes. There are many ways to organize function calls, and in each of these cases the stack frame is being created differently. In some cases the responsibility for cleaning up the stack after completion lies in the calling function, or a function which has been called and parameters passed on the stack. In other cases, as with the fastcall, the compiler tries to pass parameters in registers, rather than on the stack. In the above example I use the cdecl convention. With this method of calling, parameters pass on the stack in reverse order, and the caller is responsible for cleaning up the stack after the end of the function. Now let’s try the SaveData4FurtherProcessing2 function. void SaveData4FurtherProcessing2(char* chpBuf, int iszBuf) { char chStore[64]={0}; memcpy(chStore, chpBuf, iszBuf } The SaveData4FurtherProcessing2 function has a local array chStore 64 (40h) bytes, hence the size of the current stack frame is 64(40h) bytes.
Immediately after the 44h bytes (40+4 for register ebp a.k.a. frame pointer) of the stack frame, there is the return address from the current function, which will be used as a value for the EIP register (instruction pointer) to return back to function-caller. Right after this return address, there is a previous stack frame of function SaveData4FurtherProcessing1 and so on. The main point here is that if there is something wrong inside the body of SaveData4FurtherProcessing2 ,then the memory outside the current stack frame will be overwritten. It will simply crash the program because the return address will be incorrect. Also the previous stack frame pointer address will be incorrect or the whole previous stack frame will be invalid. That is exactly what memcpy is doing when it is copying data into the local (read stack) variable without checking the boundary of array chStore. Now the server has an “excellent” security hole and we can test to see how this hole can be used for remote attacks. But we still need client code that will use this hole. To implement the client I wrote a little Perl script. Below is the code of this script for the attack:
The script does the following: – Opens a binary file; – Connects to a remote server; – Sends content from the opened file to the remote server; Let’s launch a server application on the host computer. It opens port 12345 and waits until a client connects to this port. Once the connection is created the server application will be receiving data from the client and passing them to the function SaveData4rterProessing1 for further processing. Before launching client script, let’s attach the debugger WinDbg.exe to the server. Now, switch to the client machine and launch the Perl script. The appearance in the command line “* Complete” means that a 1 kilobyte sequence has been sent to the server. Switch back to the host machine. The server is crashed. The crash happened because the stack memory has been overwritten by the data – the size is greater than the size of the current stack frame of SaveData4rterProessing2 function (64 bytes). After finishing this function, random data has been loaded into register EIP (instruction pointer) as return address of the function SaveData4rterProessing1. The processor tried to read and execute instructions from the invalid address 0x59304656 and the program got a General Protection Fault (GPF) error. To keep things simple, let’s not pay any attention to the other data on the stack memory that may also have been damaged. Let’s keep focused on the return address function SaveData4rterProessing1. How can this address be used to change a program’s behavior with a simple Perl script? The answer is quite simple. It is possible, because the SaveData4rterProessing2 function is vulnerable to the buffer overflow attack. Hence, it is possible to execute arbitrary code. Note that I am not going to develop a working exploit. I just want to show how easy it is to change the behavior of vulnerable code on a remote computer. This is a really huge problem, because many programmers think that is too hard of a task. In other words, it is an abstract algorithm that shows how easy a hacker can exploit vulnerabilities. To exploit the stack overflow in our server, we need to do something. After the completion of the SaveData4FurtherProcessing2 function we will force the vulnerable code onto the server to call function msgStackOver,which will serve as a hypothetical shell code. Thus, as a result of the network attack on vulnerable code, we will be able to disrupt the normal execution of SaveData4FurtherProcessing2 and execute msgStackOver. We will achieve this effect by the way of simply substituting the return address from the SaveData4FurtherProcessing2, so the code execution will not be transferred to the SaveData4FurtherProcessing1. We have to find out location of the return address into SaveData4FurtherProcessing1 function. The following (seqgen.pl) Perl script generates a sequence of non-repeatable symbols.
It is necessary to determine the exact location of the memory cell, reflecting offset in the sending sequence of the return address. So, let’s create such a sequence and save it info the testseq.bin file: perl seqgen.pl 512 > testseq.bin Now we can send this sequence to the server. But before that, let’s attach a debugger to the server application on the host computer. Run the client script. We specify an IP address, port and the file testseq.bin, containing the non-repeatable sequence. perl client.pl 172.16.1.138 12345 testseq.bin The server takes a sequence that has sent by the client, then passes it on to the function SaveData4FurtherProcessing1, then to SaveData4FurtherProcessing2, where stack overflow is occurring and the server is crashing. Take a look into the debugger and note the address where the crash occurred. This is the address that the processor took when it was executing the last (RET) instruction in the body of SaveData4FurtherProcessing2 function, as the return address to the SaveData4FurtherProcessing1 function. The processor has loaded this address to the EIP (instruction pointer) and tried to execute the memory content which the address points to. But because this memory location is invalid, the GPF error has occurred and the server has crashed. If this value to change on address of function msgStackOver then code execution will be returned to the msgStackOver function instead of SaveData4FurtherProcessing1. It should be noted that really the picture will be slightly different, if we are going to create a real-working exploit. In this case, a shell code is usually located inside of the sending sequence and must not contain zero bytes, because if vulnerable code contains strcpy instead of memcpy, strcpy will stop copying on it. The same thing with the return address value. To simplify our example we’ll not consider these factors. To find the place in testseq.bin, where we need to change the return address, let’s use the following script:
perl findseq.pl testseq.bin 6a7a3678 The script successfully finds the desired sequence. Now it knows exactly where in the overflowed stack a return address to the SaveData4FurtherProcessing1 function is located. This is an offset 68 in testseq.bin, so let’s create a new sequence: PERL -e “print qq{x90} x68 . qq{x02xC1x71x75} . qq{xB8x00x10x40x00xFFxE0}” > msgStackOverCall.bin Where 0x7571C102 is an address of instructions PUSH ESP – RET in the ws2_32.dll, and xB8x00x10x40x00xFFxE0 is small shell-code. It just loaded to the EAX address of msgStackOver (0x401000) function and then executd JMP EAX. Now, launch the server on 172.16.1.138. Start the Perl client: perl client.pl 172.16.1.138 12345 msgstackovercall.bin The server prints the message “Stack is Over!” Everything works.
Of course, the example discussed in this article is purely hypothetical. My goal is to show how easy it is to mislead a vulnerable program, which works fine at first glance. It’s really very easy to do, even without powerful special tools like Metasploit, for example. Thus, I would like to draw attention to the problem of developers who still believe that safe code is not their deal and their job is to just build working code. Errors in the code that lead to the possibility of buffer overflow are only a small part of all the possible errors that relate to security. In future articles I’ll try to cover other common errors and ways to prevent them.