A CTF Example Shows You the Easy and Powerful One Gadget Tool
Read the following article on using One Gadget for exploitation. The tutorial shows how to spawn a shell with the One Gadget tool.
Join the DZone community and get the full member experience.Join For Free
One Gadget for Exploitation
One gadget is a line of C code:
execve(“/bin/sh”, 0, 0);. Apparently, this code spawns a shell. If you are able to find and run it in the memory, you get a shell! See? Easy and powerful!
Fortunately, it exists in Libc. And Libc is used in most programs! Libc is a C library file. The one_gadget tool finds all the occurrences of the code in such files.
(Linux OS): Download the tool by running gem install one_gadget. Then, you can easily run it in a terminal like so:
one_gadget /usr/lib/x86_64-linux-gnu/libc-2.31.so. The argument is the path to your Libc file. It may change on your machine. This will produce the following output:
execve(“/bin/sh”, r12, r13) is the one gadget. Before running this, you need to make sure the two lines of constraints are all true/satisfied. The one gadget becomes
execve(“/bin/sh”, 0, 0) only if the constraints are satisfied. 0xcbcda is the offset of the gadget within the Libc file. In order to know the address of the gadget in the memory, you also need to know the base address of the Libc file in the memory: the gadget’s memory address = the Libc file’s memory base address plus the offset of the gadget within the Libc file.
here is the gift for u:0x7f784418acb0 Give me your one gadget:
If you disassemble the executable, you will find that the gift is actually the address of the Libc function
printf. It may change on each different execution. And you will also find that the executable requires you to input the address of one gadget. The executable will execute the one gadget at the given address. Now let’s see how we are gonna execute one gadget.
Here are the steps:
- Install some kind of plugin to gdb that supports the command
vmmap. I use gef.
gdb problem. Now, gdb reads in the executable file. Then, run the program under gdb with the command “r”. Next, press “ctrl + c” to give the control back to gdb. Now, we are able to inspect the memory with the command
vmmap. In the output, you will notice something like the following picture that shows the path to the Libc file. This is the Libc used in this program. Now we want to locate some of one gadget in the Libc file.
- Quit the gdb process. Now, run
one_gadget /usr/lib/x86_64-linux-gnu/Libc-2.31.so. You need to change the path to yours. You will observe something like the picture in section 3 in the output. So, now we have the one gadget’s offset within the Libc file: 0xcbcda.
- Well, you also need to find the offset of the Libc function
printf. Run this command:
objdump -tT /usr/lib/x86_64-linux-gnu/Libc-2.31.so | grep " printf". You need to replace the path to the Libc file with yours. The following picture is the output. The first column is the offset. So the offset of
- Start gdb again: gdb problem. Set a breakpoint at main:
b main. Run the program:
r. Now, you will see that the program stops at the main function. Run a line of code at a time with the command:
n. If you see an output like this:
here is the gift for u:0x7ffff7e3bcb0, calculate the one gadget’s address with the formula I gave you in section 3:
gadget address = Libc base address + gadget offset = printf address – printf offset + gadget offset = 0x7ffff7e3bcb0 – 0x56cb0 + 0xcbcda = 0x7ffff7eb0cda = 1407373527646.
- Continue to run the program with n. Now, look at the following picture. After the
printffunction is executed, you will see the output: Give me your one gadget. But, now is not the right time to input the one gadget address. You need to wait until the red
scanffunction is executed. That’s when you need to input the one gadget address.
- After running the
scanffunction, now is the time to input your one gadget address. Type in the address in decimal format instead of hex format! In my case, it’s
140737352764634. Please be careful from now on, because some magic is about to happen. After the
scanffunction, the following is the machine code that’s gonna be executed. Beginning from the
$pcpointer, you see what the three lines of code are doing? They move a value in the memory [rbp-0x18] to rdx. After you run the three lines of code, you will find out that RDX stores the one gadget address! Then
call rdxgoes to execute the one gadget!
- Run the code until
call rdx, but don’t run
call rdx! Why? Recall that there are constraints that need to be satisfied before we are able to successfully execute the one gadget. The constraints are:
r12 == NULL && r13 == NULL. Now, let me observe what they are (shown in the following picture). Apparently,
r12is not null. We need to set it to null with the command: set
$r12 = 0. Now if you run this command:
p $r12, you will find that
- Now, we can run
call rdx. This time, we continue our execution with the command:
c. A shell will be immediately spawned!
This post used a concrete CTF problem to show you step-by-step how to find and execute one gadget and eventually spawn a shell. If you like this post, please help me share it on your social media. Thank you so much!
Opinions expressed by DZone contributors are their own.