Security is becoming an increasingly important topic for developers in all types of environments, even embedded systems that used to think security is not a problem. This article describes several types of coding vulnerabilities, identifies what vulnerabilities are, how to reduce the risk of the code being attacked, and how to better identify such flaws in your code.
Inject attack
By injecting information into a running process, an attacker can compromise the running state of a process to reflect a certain ultimate goal that developers cannot protect. For example, an attacker could inject code into a process through a stack overflow (corruption) to execute code selected by the attacker. In addition, an attacker might try to inject data into a database for future use, or inject an unprotected string into a database query to obtain more information than the developer. For whatever purpose, injection is always a bad thing and always needs to be treated with caution.
Perhaps the worst form of injection attack is code injection-placing new code into the memory space of a running process and then instructing the running process to execute the code. If such an attack succeeds, almost anything can be done, because the running process is completely hijacked, executing any code that the attacker wishes to execute.
One of the most famous examples of such attacks is the Windows animated cursor attack, which is the pattern discussed in this article. An attacker uses a simple Web page to download the improperly-created animated cursor file to the viewer's PC, causing the browser to invoke this animated cursor, which may occur when arbitrary code is injected. In fact, this is a perfect attack carrier: because it does not require any actual access to the attacked machine, the end user is not aware of any possible problems, and if the malicious effect of the attack is moderate, then the external impact on the end user is almost zero.
Consider Example 1 (a), which, of course, is rewritten from Windows attacks, which form the basis of such an attack vector. Developers here have made basic assumptions about the reliability of incoming streams. Trust flow and and believe everything is okay. Using a stack-based type that will be deserialized (called), unknown data streams and code injection will certainly appear at some point in time.
(a)
void LoadTypeFromStream(unsigned char* stream, SOMETYPE* typtr)
{
int len;
// Get the size of our type's serialized form
memcpy(&len, stream, sizeof(int));
// De-serialize the type
memcpy(typtr, stream + sizeof(int), len);
}
(b)
void foo(unsigned char* stream)
{
SOMETYPE ty;
LoadTypeFromStream(stream, &ty);
}
(c)
void LoadTypeFromStream
(unsigned char* stream, SOMETYPE* typtr)
{
int len;
// Get the size of our type's serialized form
memcpy(&len, stream, sizeof(int));
// GUARD
if( len < 0 || len > sizeof(SOMETYPE) )
throw TaintedDataException();
// De-serialize the type
memcpy(typtr, stream + sizeof(int), len);
}
Example 1 injection attack.
How did this happen? Suppose you invoke the function in Example 1 (b). We've got an easy to use attack carrier. The problem here is that the size of the SOMETYPE is fixed at compile time. Suppose this type uses 128 byte representations in memory. Let's assume that when you build an incoming stream, the reading of the first 4 bytes (the length of the content to be not serialized) is 256. Now, instead of checking the validity of the content being processed, you copy 256 bytes into a reserved stack space of only 128 bytes.
Given the typical layout of the release pattern stack, you're obviously in trouble. View the stack for reasons. Each called function lays its local data in one frame of the stack, usually by subtracting the known size of the local data from the stack pointer at input, plus any management data required to handle the call chain itself. The ideal function prolog (pseudo code) emitted by the compiler looks like this:
.foo
sub sp, 128 ; sizeof SOMETYPE
Subsequently, the invocation of the available function should look like the following:
push sp ; push the SOMETYPE
local variable
push ap ; push the stream
pointer (comes from 1st argument)
call LoadTypeFromStream
ret