code that doesn't exist

Many people who are involved in reversing malicious applications to some extent have heard about ROP chains. Meanwhile, return-oriented programming remains a rather interesting direction in the development of malicious applications. In this article, we will begin to understand what this method is.

So, return oriented programming (ROP) is a method of exploiting vulnerabilities in software, using which an attacker can execute the code he needs if the system has protective technologies, for example, technology that prohibits the execution of code from certain memory pages.

Using ROP, an attacker can gain control over the call stack, find sequences of instructions in the code that perform the desired actions and are called “gadgets”, and execute the “gadgets” in the desired sequence.

Looking for gadgets

I think that theory will be enough and then we can see in practice what we are talking about. Let's say we have some set of instructions (on the left). And on the right we can see their opcodes, that is, how these instructions are represented as machine codes.

It would seem that there is nothing unusual. But what will happen if we try to start executing the code not from the first byte, but from the second.

Then we get a completely different set of instructions and, accordingly, a completely different logic of the code. This is the simplest example of a ROP gadget. An important element of any gadget is the presence of the RET instruction at the end. It is thanks to RET that it is possible to return from the gadget to the original program.

Let me remind you that after going through the CALL instruction, a return to the next command after the call is carried out when RET is encountered during code execution.

In reality, such gadgets can be found in data that, for one reason or another, is located in the code section. This is due to the fact that the instruction set of the x86 architecture is quite dense, meaning that there is a high probability that an arbitrary stream of bytes will be interpreted as a stream of valid instructions. And this is exactly what an attacker needs when writing some kind of malware.

Let's look at an example of a gadget in a real program. Here, before the RET instruction, we see a certain set of instructions that perform various operations with registers and memory contents.

Moved back one byte and the instruction set has already changed somewhat. Perhaps the attacker needs to execute a similar set of commands in his program.

Thus, the task of the malware developer is to build a chain of calls to blocks of instructions he needs. That is, the “program” written using gadgets will look like this

CALL 0X111111

CALL 0X222222

CALL 0X333333

....

And from the point of view of security tools, there is nothing suspicious here, although in fact the result of the ROP chain operation may look like this:

We will discuss in detail how to obtain such a set of instructions in practice in the next article, but here we will consider some types of chains and their purpose.

So, the main purpose of ROP chains is to allow the execution of the required set of commands using calls to addresses from the stack. That is, our potential malware will not contain instructions that perform any logical actions that antiviruses and network analyzers are trained to detect. In essence, the main code of the malware consists of calls to someone else's code, which then calls the necessary fragments of the required code.

ROP can be compared to words cut out from newspapers to compose an anonymous letter.

It only remains to figure out in what ways we can build such ROP chains.

Here are the most well-known varieties of ROP:

  • Jump-oriented programming

  • String-oriented programming

  • Sigreturn-oriented programming

  • Blind Return Oriented Programming

In this article we will take a closer look at the first two varieties.

Jump programming

With Jump-oriented programming (JOP), the attacker completely abandons the use of the stack and ret to detect the gadget and create the chain, instead using nothing more than a sequence of indirect jump instructions. Since almost all known defenses against ROP depend on its use of the stack or ret instructions, many of them are unable to detect or defend against this new approach.

Like ROP, the building blocks of JOP are still short code sequences called gadgets. However, instead of ending with a ret symbol, each gadget ends with an indirect JMP.

Some of these jmp instructions are intentionally generated by the compiler. Others are not intended to be, but are present due to the x86 instruction density. However, unlike ROP, where the ret gadget can naturally return control based on the stack contents, the jmp gadget performs a one-way transfer of control flow to its target, making it difficult to restore control back to the execution chain of the next jump-oriented gadget.

The solution to this problem is to propose a new class of gadget, the dispatcher gadget. Such a gadget is designed to manage the flow of control between different blocks of code oriented to a jump. More specifically, if we consider other gadgets as functional gadgets that perform primitive operations, this dispatch gadget is specifically chosen to determine which gadget will be called next. Naturally, the dispatch gadget can maintain an internal dispatch table that explicitly determines the flow of control among the functional gadgets. In addition, it ensures that the final jmp instruction in a functional gadget will always transfer control back to the dispatch gadget. Thus, jump-oriented computation becomes possible.

Below is a comparison of Return Oriented Programming and Jump Oriented Programming.

In practice, the implementation of JOP may look like this.

String-oriented programming

String-oriented programming, SOP is based on an existing string formatting bug in an application and escalates into any possible code injection attack or control-less attack (ROP or JOP). This section introduces the various building blocks needed to set up SOP and maps these attack vectors to the defense mechanisms that are the standard for modern application security.

A successful attack redirects the application's control flow to an alternative location that would otherwise be unavailable (i.e. new code is injected into the application) or executes existing code in a different context (i.e. existing code is executed with different – malicious – data).

The execution environment must allow control flow to be redirected to alternative locations using control flow transfer instructions. Control flow transfer instructions are jump instructions, indirect jump instructions, conditional jump instructions, call instructions, indirect call instructions, return instructions, interrupts, and system calls.

Indirect control flow transfers (indirect jumps, indirect calls, and return instructions) read an absolute target address from a data area or a register. An attacker can redirect a legitimate indirect control flow transfer by manipulating either a memory location or a specified register (depending on the encoding of the indirect control flow transfer). Exploits either overwrite a register with a value specified by the attacker or overwrite the data area containing the target pointer to achieve the original control flow redirection: for return instructions, overwrite the EIP on the stack, for indirect calls, or the function pointer on the heap.

The exploit must also inject some form of payload into the application. Control-data attacks inject machine code instructions into the application's executable memory region. These instructions are executed after the initial control flow redirection. Data-based attacks, such as ROP or JOP, modify the application's data structures, shared library, or standard loader to execute their malicious payload.

An exploit is only successful if both requirements are met.

The SOP attack exploits the attacker's control over the first parameter of a printf-family function (all functions that take a format string as a parameter, such as printf, fprintf, sprintf, and vprintf). The printf-family parses the format string argument for control tokens (of the form %T) to determine the number of variable parameters that follow it. The token determines how the output at position n on the stack is formatted into a string. Many programmers forget to check user-controlled strings for these control tokens and pass the string directly to the function (e.g. printf(usr str)). A secure implementation would use a static parameter to pass a single string.

(e.g. printf(“%s”, usr str)).

A malicious format string can use tokens like %p to read specific stack pointers and %s to read specific stack addresses as strings. The attacker uses these parameters to obtain information about the application while building a format string attack.

The %n token reverses the input order and records the number of characters already printed for the specified pointer.

Any argument on the stack can be used as the target address for %n, for example %4$hn writes 2 bytes to the specified pointer 4∗4 = 16 bytes more on the stack. The format string itself can be used to store pointers to specific addresses if it is pushed onto the stack. The number of bytes written can be controlled with additional parameters (for example, printf(“%NNc”); prints NN bytes) and increments the counter used for %n. For example, printf(“AAAA%1$49391c%6$hn”) writes 0xc0f3 (2 bytes, 0xc0f3 − 4 = 49391) to 0x41414141 if the string itself is in slot 6 on the stack. In this example, an 18-byte input string is used to generate an attacker-controlled memory write of 2 bytes. Format string attacks write arbitrary values ​​to arbitrary memory locations.

These attacker-controlled memory writes are used, for example, to redirect control flow to the injected code.

Below is an example of a program vulnerable to SOP.

void foo ( char ∗ a r g ) {

char text [1024] ;

if ( strlen ( arg ) >= 1024 ) return;

strcpy( text , arg ) ;

printf ( text ) ;

puts ( ”logged in ” ) ;

}

. . .

foo ( user_str ) ;

. . .

Conclusion

In this article, we started to consider return-oriented programming. Of course, modern security tools can effectively detect malware built on this principle, but the concept itself is quite interesting, and in the next article we will continue to consider ROP.

Taking this opportunity, I would like to remind you about the open lesson “Introduction to exploitation analysis: how to find vulnerabilities”, which will be held on October 18. This open lesson will consider, using the example of a one-day vulnerability, how it can be found and exploited.

The lesson will be held as part of the course “Reverse engineering”, sign up you can follow the link.

Similar Posts

Leave a Reply

Your email address will not be published. Required fields are marked *