Advanced Exploits: Overcoming Restrictions with GOT and PLT
13 min read
July 22, 2023
Table of contents
Introduction: Navigating Complex Exploits with GOT and PLT
In the world of exploit development, we often encounter scenarios that don't fit the straightforward mold. The previous chapters provided us with insights and techniques for simpler exploits. However, the real challenge begins when we face vulnerable codes that defy conventional exploitation methods. This chapter is dedicated to addressing such complexities.
Expanding Our Exploitation Horizons:
- Beyond Basic Exploits: We're moving away from the simplicity of earlier exploits to tackle more intricate and less straightforward vulnerabilities.
- Utilizing Dynamic Function Resolution: Our focus shifts to leveraging the knowledge of dynamic function resolution, particularly the intricacies of the Global Offset Table (GOT) and Procedure Linkage Table (PLT). These concepts, initially introduced in chapter 4, will now become our primary tools in overcoming advanced restrictions.
- Adapting to Code Constraints: We'll explore strategies to navigate around coding structures that prevent traditional exploit methods. This includes codes ending with functions like 'exit()' or trapped in infinite loops, which pose unique challenges in exploitation.
- Expanding the Toolkit: As we dive deeper into the nuances of GOT and PLT, the importance of a versatile toolkit becomes evident. We'll continue to harness tools like radare2 and pwntools, not just as aids but as essential elements in our exploit development process.
Embarking on a New Chapter:
As we embark on this chapter, we're not just learning a new technique; we're adapting to the evolving landscape of exploit development. This journey will enhance our ability to think critically, adapt our strategies, and effectively utilize the tools at our disposal. Let's dive in and add another powerful technique to our growing repertoire of exploit development.
Exploiting GOT for Program Flow Hijacking
In the intricate world of binary exploitation, we often encounter seemingly impervious functions within binaries, such as those terminating with "exit()" or trapped in infinite loops. These situations present a unique challenge: they hinder our ability to overwrite the return address and hijack the program's execution flow. However, there's a silver lining in this complex scenario, and it lies in the strategic use of the Global Offset Table (GOT).
Understanding GOT's Role in Exploits:
- GOT as an Arbitrary Writing Point: Introduced in Chapter 4, the GOT plays a crucial role in dynamic function linking. Its structure offers us a potential target for exploitation. If we can exploit a vulnerability that permits overwriting memory data, we can manipulate the GOT entries of dynamically linked functions. This manipulation paves the way for redirecting the execution flow to our desired destination – our crafted shellcode.
- Hijacking with Precision: The exploitation of GOT revolves around altering specific entries. By rewriting these entries, we can ensure that the next time the program calls a dynamically linked function, it doesn’t jump to the standard library code. Instead, it leaps straight into the jaws of our shellcode, effectively hijacking the program’s behavior.
- Bypassing Conventional Barriers: This approach circumvents the limitations posed by secure functions and infinite loops. By focusing on the GOT, we target a universal aspect of dynamically linked binaries, opening up new avenues for exploit development.
Crafting the Exploit:
The key to a successful GOT exploit lies in a deep understanding of the binary's structure and the behavior of its dynamically linked functions. It requires meticulous planning and precision in execution. The rewards, however, are substantial – the ability to control program flow even in the most resilient binaries.
This chapter will guide you through the nuances of exploiting the GOT, demonstrating techniques to turn this table to your advantage. Whether you're bypassing security measures or overcoming structural challenges, mastering GOT manipulation is a powerful addition to your exploit toolkit.
Illustrating GOT Exploitation with a Vulnerable Code Example
To provide a practical understanding of how GOT can be exploited, let's delve into an example of vulnerable code, adapted from the "Guia de Exploits." This code, while seemingly complex at first glance, offers a perfect scenario to demonstrate GOT manipulation.
#include <stdlib.h>
#include <string.h>
int main(int argv,char **argc) {
char *pbuf=malloc(4);
char buf[256];
strcpy(buf,argc[1]);
for (;*pbuf++=*(argc[2]++););
exit(1);
}
Breaking Down the Vulnerable Code:
- Memory Allocation and Pointers:
- The code begins by allocating a 4-byte memory space using the
malloc
function. This allocated space is referenced by the pointerpbuf
. - Essentially,
pbuf
acts as a marker, pointing to a specific location in memory where we can store or manipulate data.
- The code begins by allocating a 4-byte memory space using the
- Buffer Creation and Data Copy:
- A buffer,
buf
, is declared with a capacity of 256 characters. This buffer is designed to store data passed as an argument (argc[1]
). - The
strcpy
function copies the content ofargc[1]
intobuf
, replicating the input data within the program's memory.
- A buffer,
- Iterative Data Transfer:
- The subsequent
for
loop iterates over the characters inargc[2]
, transferring each character to the memory space pointed to bypbuf
. - This loop continues until it reaches the end of the string in
argc[2]
, effectively copying its content into the space allocated bymalloc
.
- The subsequent
- Program Termination:
- The final act of the program is to invoke the
exit
function, terminating the execution.
- The final act of the program is to invoke the
Identifying the Vulnerability
At the core of this code lies a critical vulnerability: the lack of bounds checking. The strcpy
function and the for
loop do not verify the length of the input data, leading to potential buffer overflows. This oversight opens a window for attackers to manipulate memory, particularly the GOT, to divert the program flow.
Exploitation Strategy
The exploit strategy involves carefully crafting input that overflows the buf
buffer and manipulates the memory space pointed to by pbuf
. By doing so, we aim to overwrite specific GOT entries, redirecting function calls to our shellcode. This requires precise knowledge of the memory layout and the functions used by the binary.
In the following sections, we'll walk through the steps of constructing and deploying an exploit that leverages this vulnerability, turning a seemingly benign program into a gateway for GOT manipulation and control flow hijacking.
Exploit Strategy: Redirecting Control Flow via GOT Manipulation
In this scenario, we encounter a unique challenge: the inability to directly modify the return address due to the program's use of the exit
function. To navigate this obstacle, we'll employ a strategy centered around manipulating the Global Offset Table (GOT). This approach diverges from the methods used in previous chapters, focusing instead on altering GOT entries to redirect the program's execution flow.
The Core of the Attack Strategy
- Targeting the GOT:
- Our primary goal is to modify an entry within the GOT. By altering this entry, we can redirect the dynamic linking process, causing the program to execute our shellcode instead of the intended library code.
- The chosen target for modification will be the GOT entry for the
exit
function.
- Shellcode Placement:
- We'll store our shellcode within the
buf
variable. This placement is strategic, leveraging the buffer overflow vulnerability to store our malicious code.
- We'll store our shellcode within the
- Exploiting Buffer Overflow:
- The program's lack of bounds checking when copying data to
buf
will be exploited to trigger a buffer overflow. - Through this overflow, we aim to modify the
pbuf
pointer, redirecting it to the GOT entry of theexit
function.
- The program's lack of bounds checking when copying data to
- Visualizing the GOT:
- It's helpful to conceptualize the GOT as a table containing entries for functions requiring dynamic linking. Each entry points to the actual code to be executed.
- In our exploit, we'll manipulate the
pbuf
pointer to point to theexit
function's GOT entry, preparing to overwrite it with the address of our shellcode.
- Modifying the GOT Entry:
- The program's loop that modifies the content of the memory space pointed to by
pbuf
plays a crucial role. This loop will be used to inject the stack address of thebuf
variable (containing our shellcode) into the GOT. - By doing so, we effectively change the program's execution flow to our shellcode when the
exit
function is called.
- The program's loop that modifies the content of the memory space pointed to by
Gathering Required Addresses
To successfully execute this exploit, we need to obtain specific memory addresses:
- The GOT entry for the exit function: This crucial address can be directly retrieved using radare2. By executing commands like
pd @ got.plt
, we can swiftly pinpoint the GOT entry corresponding to the exit function. - The stack address of the buf variable: Unlike the exit function's GOT entry, the stack address of the buf variable will be determined dynamically as we develop and refine the exploit. Observing how our payload interacts with the program's memory during execution will guide us in identifying this critical address.
Summary of the Attack
The following diagram provides a visual summary of our exploit strategy, highlighting the critical points of GOT manipulation and buffer overflow to achieve control flow redirection. This methodical approach sets the stage for a successful exploitation of the vulnerability, turning a constrained environment into an opportunity for shellcode execution.
Breaking Down the Code in Radare2: A Step-by-Step Analysis
We dive into the heart of our vulnerable program using radare2, an essential tool in our hacking toolkit. Using straightforward commands, we unravel the mystery behind the assembly code, translating it effortlessly back to our source code discussed at the beginning of the chapter.
r2 ./vulnerable
aaa
pdf @dbg.main
The initial parts of the code, involving dynamic memory allocation to the pbuf
pointer and copying user input into the buf
variable, are fairly straightforward.
It's the third segment, representing the "for" loop, where things get spicy!
The first highlighted instruction is crucial: it moves the memory address pointing to argv[2]
into edx
. Think of it as setting the stage for the critical third point in our journey. By using the pxr
command in radare2, we can peek into the destination of this memory address, much like a detective piecing together clues.
Moving to the second highlight, we see the loading of the pbuf
variable value into eax
. This is where our exploit takes shape: eax
needs to hold the memory address of the GOT entry we want to tweak. Imagine aiming your hacking skills precisely at the bullseye of the GOT table, as shown in the earlier diagram.
The final highlight in our code analysis reveals the two instructions that crucially copy the value from argv[2]
(our key to executing the shellcode) into the location pointed to by pbuf
. This dance of bytes and addresses continues until al
hits zero, signaling the end of our string.
To bring this to life, consider the example in the dynamic code execution diagram. In it, eax
aligns with the memory address of the exit
entry in the GOT, while edx
holds a byte from argv[2]
. It's like watching the final pieces of our hacking puzzle snap into place.
Crafting the GOT Exploit: Two-Pronged Approach Using Radare2
In our exploit journey, we're set to develop two separate payloads, each tailored for specific arguments (argv[1] and argv[2]). Our toolkit? Primarily radare2, stepping away from pwndbg for this round.
Exploit argv[1]: Buffer Overflow and NOPs Strategy
The first exploit plays a critical role in our overall strategy. It leverages a buffer overflow to manipulate the pbuf
pointer, directing it to our desired GOT entry. But there's more - it's not just about redirection. We also need to ensure the execution of our shellcode, and for that, we introduce NOPs (No Operation instructions).
NOPs are our safety net - they don't alter the code but create a slide, a runway for our shellcode to execute smoothly. Let's say the exact start of our buf
is a mystery; NOPs extend our landing zone, giving us a better chance at a successful exploit.
So, imagine a stack peppered with NOPs, leading to our shellcode. It's a cushion, a preparation for the final act - executing the "Hello" message via our shellcode. For this, we employ pwntools' "shellcraft" utility, a familiar friend from our previous exploits.
#!/usr/bin/env python3
from pwn import *
context.update(arch="i386", os="linux")
context.terminal = ["kitty", "-e", "sh", "-c"]
shellcode = shellcraft.echo("Hello\n", constants.STDOUT_FILENO)
payload = b'\x90' * 50 # The NOP slide
payload += asm(shellcode) # Our crafted message
payload += cyclic(256 - 50 - len(asm(shellcode))) # Precise padding
payload += p32(0x0804c018) # Target GOT entry
sys.stdout.buffer.write(payload)
Validating Our Approach with Radare2
To test the mettle of our exploit, we turn to radare2. It's our window into the inner workings of the vulnerable program, allowing us to validate if our strategic manipulations bear fruit.
r2 ./vulnerable
ood `!python3 exploit.py` AAAA
Executing our exploit, we observe the desired shift in execution flow. The program, now hijacked, obediently follows our redirect to "0x41414141" (AAAA). It's a moment of triumph, a testament to our exploit's effectiveness.
Crafting the Second Wave: Exploit for argv[2]
Our journey through the treacherous waters of exploitation now brings us to crafting the second argument – the key to unlocking the destination of our shellcode.
Navigating to the Shellcode's Hideout
Our primary task with this argument is straightforward yet crucial. We need to ensure that the 'pbuf' pointer is set sail directly to the memory address housing our shellcode – nestled within the 'buf' variable.
Setting the Course with Precision
Our exploit's success hinges on pinpoint accuracy. We need the exact memory address where 'buf' begins, as this is where our shellcode awaits its cue. To pinpoint this location, we drop an anchor right after the strcpy function.
Charting the Stack's Depths
With the strategic placement of NOPs at the outset of our exploit, identifying the start of our shellcode becomes easier. These NOPs create a visible trail on the stack, guiding us to the exact location of 'buf'. The illustration below reveals how our payload appears on the stack, with the NOPs making our target location unmistakable.
Preparing the Second Payload
Armed with the knowledge of where our shellcode begins, we can now prepare the second payload. This payload's mission is singular – to ensure 'pbuf' points to our shellcode's starting line. The payload would be a simple yet precise command, leading 'pbuf' to the memory address of 'buf'.
#!/usr/bin/env python3
from pwn import *
context.update(arch="i386", os="linux")
payload = b' '
payload += p32(0xffffc774)
sys.stdout.buffer.write(payload)
Upon deploying this payload, we can validate our success in radare with the following command: ood '!python3 exploit.py' '!python3 address.py'
. If our calculations are correct, we should witness the successful execution of our code, a testament to our meticulous planning and execution.
Conclusion: Mastering GOT Exploitation
In this chapter, we've delved deep into the intricacies of exploiting the Global Offset Table (GOT) to navigate around the constraints of a program that uses functions like 'exit()' or is locked in an infinite loop. Our journey has shown that even when direct control over the return address is not feasible, there's still a path to successful exploitation.
Key Takeaways:
- Strategic Use of GOT: We've learned that the GOT, a crucial part of dynamic function linking, can be a valuable target for exploitation. By altering a GOT entry, we redirect the dynamic linking process to execute our shellcode.
- Buffer Overflow and Shellcode Execution: Our exploits demonstrated the effective use of buffer overflow to manipulate the 'pbuf' pointer. We also saw the importance of precise shellcode placement within the 'buf' variable and the role of NOPs in ensuring successful execution.
- Two-Part Exploit Approach: The development of two separate payloads for argv[1] and argv[2] highlighted the need for a multifaceted strategy in complex exploits. The first exploit leveraged a buffer overflow, while the second precisely directed the 'pbuf' pointer to our shellcode.
- Tool Utilization: This chapter underscored the importance of tools like radare2 and pwntools in developing and testing exploits. Radare2 was pivotal in examining the stack and determining the exact location for our shellcode, while pwntools facilitated the creation of effective payloads.
- Adaptability in Exploit Development: Our journey emphasized the need for adaptability in the face of challenging exploit scenarios. When traditional methods were not applicable, we adapted our approach to use the GOT, demonstrating that there are multiple pathways to achieve exploitation goals.
Looking Ahead:
As we continue to explore the realm of exploitation, the lessons learned in this chapter will serve as a foundation for tackling even more complex scenarios. The ability to think creatively, adapt strategies, and utilize various tools will be invaluable in overcoming the challenges that lie ahead in the art of exploitation.
Tips of the article
What can we use to get our exploit executed in case the program is in an infinite loop or terminates using the "exit()" function?
We could use the GOT to modify dynamic linking so that when the relocation process of a function is performed, the code pointed to is the one introduced by our exploit.
How can we find the .got section inside the binary with radare2?
We can use the "iS" command to display the different sections of the binary. Then using the "pd" command we can display the assembler code of the memory address corresponding to the .got.
What does the "NOP" instruction do and how can it be useful in exploit development?
The "NOP" instruction, as its name indicates "No Operation", does nothing. However it is really useful to put in our exploit when we do not know for sure where our exploit is going to start.
What can I do to make a python script show its output in binary so that we can use it to test our explot together with radare2 ?
I have to make the output displayed, by our script, this normally being the payload, to be something like the following:I have to make the output displayed, by our script, this normally being the payload, to be something like the following:
sys.stdout.buffer.write(payload)
Then, using the following radare2 command, we can see how the code responds to our exploit:
ood `!python3 exploit.py` `!python3 address.py`
References
Chapters
Previous chapter
Next chapter