Mastering Gadget Hunting with Ropper, ROPgadget, and One_Gadget: A Practical Guide
- tags
- #Ropper #One_gadget #ROPgadget #ROP
- published
- reading time
- 15 minutes
In the world of binary exploitation, gadgets are crucial building blocks for bypassing modern security defenses. Whether you’re attacking vulnerable software in a CTF or conducting real-world vulnerability research, Return-Oriented Programming (ROP) and other gadget-based techniques can turn the simplest bug into a powerful exploit. But what are gadgets, and how do tools like Ropper, ROPgadget, and one_gadget help us find and use them? In this blog post, we will take a deep dive into gadgets, from understanding what they are and why they’re essential in modern exploitation, to mastering the use of tools like Ropper, ROPgadget, and one_gadget for advanced gadget hunting. Whether you’re just starting out in binary exploitation or looking to sharpen your skills, this guide will walk you through how to harness these tools to build effective ROP chains and exploit binaries.
If you’re new to binary exploitation or need a refresher, I recommend checking out my x86 Linux Binary Exploitation series, which covers fundamental concepts like buffer overflows, control flow hijacking, and introduction to ROP. This post builds on those principles, focusing on the next step: effectively finding and using gadgets for complex exploits.
What Are Gadgets?
At the heart of Return-Oriented Programming (ROP) is the concept of a gadget. A gadget is a short sequence of instructions, typically ending with a ret
(return) instruction, which we can hijack during a control flow attack. These gadgets are typically small, consisting of just a few instructions, but when chained together, they can perform complex operations like calling system functions, manipulating memory, or bypassing defenses like DEP (Data Execution Prevention) and ASLR (Address Space Layout Randomization).
Why Do We Use Gadgets?
In modern exploitation, attackers can no longer rely on simply injecting shellcode into a process due to security measures like DEP, which marks memory regions as non-executable. To bypass these defenses, ROP allows us to reuse existing code in the program’s binary or linked libraries by crafting ROP chains—sequences of gadgets that are executed one after another. By controlling the stack and chaining together gadgets that manipulate registers, call functions, or move data, attackers can achieve the same result as custom shellcode, without ever introducing new code into the program. In essence, ROP lets us “borrow” the program’s own code to execute arbitrary operations.
A Simple Example of Gadgets
Consider a small gadget like pop rdi; ret
. This sequence pops a value from the stack into the rdi register (which is commonly used for passing arguments to functions in the x86-64 calling convention), then returns control to the next instruction in the ROP chain. If we can find and chain multiple gadgets like this together, we can control the flow of the program and eventually make system calls, open files, or spawn a shell.
Meet Ropper: Your Gadget-Hunting Sidekick
Imagine you’re Indiana Jones, but instead of searching for ancient relics, you’re on a quest to find the perfect gadgets hidden deep within a binary. You’ve got your whip (okay, more like a keyboard), and you need a trusty sidekick who knows the terrain. Enter Ropper—your gadget-hunting buddy who’s always ready to dive into the depths of binaries to find those elusive ROP treasures.
Think of Ropper like that friend who remembers exactly where you left your car keys—except instead of keys, it remembers where every pop rdi; ret
or syscall
gadget is hiding. You just give it a binary, ask nicely (or just use a simple command), and it comes back with a list of gadgets like, “Hey, here are all the little pieces you can use to break things… constructively, of course.”
But wait, Ropper doesn’t just stop at pointing out gadgets. It’s like having a treasure map with a giant red “X” marking all the best spots. Whether you’re looking for ROP, JOP, or even Syscall gadgets, Ropper’s got your back. It even works across different architectures—because what’s a good sidekick if they can’t adapt to a little variety, right?
Ropper was created by the talented Sascha Schirra, who has gifted the world of exploitation with this amazing tool. If you want to learn more about it, I definitely recommend checking out the official website.
Installation
pip install ropper
Check it ropper is correctly installed
$ ropper --version
Version: Ropper 1.13.8
Author: Sascha Schirra
Website: http://scoding.de/ropper
(That’s the version I’m currently using.) If your version is different, don’t worry—it just means you’re up-to-date with the latest Ropper release. Now you’re all set and ready to start hunting those gadgets!
Using Ropper: Time to Hunt for Gadgets!
Basic Command Structure The basic command structure for Ropper is straightforward. You just need to specify the binary you want to analyze and what you’re looking for. Here’s how it looks:
ropper --file /path/to/binary
This command will scan the specified binary and list all the available gadgets. It’s like sending Ropper out into the field with a treasure map!
For detailed assistance, simply run ropper --help
.
$ ropper --help
usage: ropper [-h] [--help-examples] [-v] [--console] [-f <file> [<file> ...]]
[-r] [-a <arch>] [--section <section>] [--string [<string>]]
[--hex] [--asm [<asm> [H|S|R] ...]] [--disasm <opcode>]
[--disassemble-address <address:length>] [-i] [-e] [--imagebase]
[-c] [-s] [-S] [--imports] [--symbols] [--set <option>]
[--unset <option>] [-I <imagebase>] [-p] [-j <reg>]
[--stack-pivot] [--inst-count <n bytes>] [--search <regex>]
[--quality <quality>] [--opcode <opcode>]
[--instructions <instructions>] [--type <type>] [--detailed]
[--all] [--cfg-only] [--chain <generator>] [-b <badbytes>]
[--nocolor] [--clear-cache] [--no-load] [--analyse <quality>]
[--semantic constraint] [--count-of-findings <count of gadgets>]
[--single]
You can use ropper to display information about binary files in different file formats and you can search for gadgets to build rop chains for different architectures
supported filetypes:
ELF
PE
Mach-O
Raw
supported architectures:
x86 [x86]
x86_64 [x86_64]
MIPS [MIPS, MIPS64]
ARM/Thumb [ARM, ARMTHUMB]
ARM64 [ARM64]
PowerPC [PPC, PPC64]
SPARC [SPARC64]
available rop chain generators:
execve (execve[=<cmd>], default /bin/sh) [Linux x86, x86_64]
mprotect (mprotect address=0xdeadbeef size=0x10000) [Linux x86, x86_64]
virtualprotect (virtualprotect address=0xdeadbeef) [Windows x86]
options:
-h, --help show this help message and exit
--help-examples Print examples
-v, --version Print version
--console Starts interactive commandline
-f <file> [<file> ...], --file <file> [<file> ...]
The file to load
-r, --raw Loads the file as raw file
-a <arch>, --arch <arch>
The architecture of the loaded file
--section <section> The data of the this section should be printed
--string [<string>] Looks for the string <string> in all data sections
--hex Prints the selected sections in a hex format
--asm [<asm> [H|S|R] ...]
A string to assemble and a format of the output
(H=HEX, S=STRING, R=RAW, default: H)
--disasm <opcode> Opcode to disassemble (e.g. ffe4, 89c8c3, ...)
--disassemble-address <address:length>
Disassembles instruction at address <address>
(0x12345678:L3). The count of instructions to
disassemble can be specified (0x....:L...)
-i, --info Shows file header [ELF/PE/Mach-O]
-e Shows EntryPoint
--imagebase Shows ImageBase [ELF/PE/Mach-O]
-c, --dllcharacteristics
Shows DllCharacteristics [PE]
-s, --sections Shows file sections [ELF/PE/Mach-O]
-S, --segments Shows file segments [ELF/Mach-O]
--imports Shows imports [ELF/PE]
--symbols Shows symbols [ELF]
--set <option> Sets options. Available options: aslr nx
--unset <option> Unsets options. Available options: aslr nx
-I <imagebase> Uses this imagebase for gadgets
-p, --ppr Searches for 'pop reg; pop reg; ret' instructions
[only x86/x86_64]
-j <reg>, --jmp <reg>
Searches for 'jmp reg' instructions (-j reg[,reg...])
[only x86/x86_64]
--stack-pivot Prints all stack pivot gadgets
--inst-count <n bytes>
Specifies the max count of instructions in a gadget
(default: 6)
--search <regex> Searches for gadgets
--quality <quality> The quality for gadgets which are found by search (1 =
best)
--opcode <opcode> Searches for opcodes (e.g. ffe4 or ffe? or ff??)
--instructions <instructions>
Searches for instructions (e.g. "jmp esp", "pop eax;
ret")
--type <type> Sets the type of gadgets [rop, jop, sys, all]
(default: all)
--detailed Prints gadgets more detailed
--all Does not remove duplicate gadgets
--cfg-only Filters out gadgets which fail the Microsoft CFG
check. Only for PE files which are compiled with CFG
check enabled (check DllCharachteristics) [PE]
--chain <generator> Generates a ropchain [generator parameter=value[
parameter=value]]
-b <badbytes>, --badbytes <badbytes>
Set bytes which should not contains in gadgets
--nocolor Disables colored output
--clear-cache Clears the cache
--no-load Don't load the gadgets automatically when start the
console (--console)
--analyse <quality> just used for the implementation of semantic search
--semantic constraint
semantic search for gadgets
--count-of-findings <count of gadgets>
Max count of gadgets which will be printed with
semantic search (0 = undefined, default: 5)
--single No multiple processes are used for gadget scanning
For this demonstration, please use the necessary binaries and the corresponding libc they require. Here’s the link:
Searching for Specific Gadgets
$ #ropper --file /path/to/binary --search "pop rdi"
$ ropper --file shell_x86 --search "ret"
[INFO] Load gadgets from cache
[LOAD] loading... 100%
[LOAD] removing double gadgets... 100%
[INFO] Searching for gadgets: ret
[INFO] File: shell
0x000010e6: ret 0x2f0f;
0x000010a6: ret 0x2f4f;
0x0000100a: ret;
Bingo! Now you have the address of the gadget you need.
Working with Multiple Architectures
Ropper is versatile and works with various architectures. If you’re dealing with a binary for ARM, MIPS, or another architecture, just specify it with the -a/--arch
option:
$ # ropper --file /path/to/binary --arch arm
$ # Supported architectures are: x86, x86_64, MIPS, MIPS64, ARM, ARMTHUMB, ARM64, PPC, PPC64, SPARC64
$ ropper --file shell_arm --arch ARM
Interestingly, even if you don’t specify --arch
, Ropper will still print the gadgets it finds. This means you can get results without worrying about architecture, although specifying it can help narrow down your search.
Searching for strings
Ropper isn’t just a gadget-finding wizard; it can also help you locate specific strings within your binary.
$ # ropper --file /path/to/binary --string "your_string_here"
$ ropper --file shell_arm --string "/bin/sh"
Stack Pivoting
Ropper isn’t just your go-to tool for finding gadgets; it can also assist you in locating pivot gadgets that are essential for stack pivoting techniques. Stack pivoting allows you to change the stack pointer to a controlled area, enabling you to execute your payload even in the face of modern protections.
To find potential stack pivot gadgets, you can use Ropper to search for specific sequences of instructions that manipulate the stack pointer. For example, you might want to look for gadgets that involve pop rsp
or similar instructions.
$ # ropper --file /path/to/binary --stack-pivot
$ ropper --file shell_x86 --stack-pivot
Generating ROP Chains
One of the coolest features of Ropper is its ability to generate complete ROP chains. You can use the --chain
option to create a chain of gadgets that can be directly used for exploitation:
available rop chain generators:
execve (execve[=<cmd>], default /bin/sh) [Linux x86, x86_64]
mprotect (mprotect address=0xdeadbeef size=0x10000) [Linux x86, x86_64]
virtualprotect (virtualprotect address=0xdeadbeef) [Windows x86]
$ # ropper --file /path/to/binary --chain
$ ropper --file libc.so.6 --chain "execve execve=/bin/sh"
$ ropper --file libc.so.6 --chain "mprotect address=0xdeadbeef size=0x10000"
Getting Binary Info Using Ropper
Ropper is not just a tool for finding gadgets; it can also provide detailed information about the binary you’re analyzing. This feature can be invaluable for understanding the structure and properties of the binary, which is essential for effective exploitation.
Viewing Binary Information
To retrieve information about a binary file, you can use the --info
option. This command will give you an overview of various attributes, such as sections, symbols, and architecture.
Here’s how to use it:
$ # ropper --file /path/to/binary --info
$ ropper --file shell_arm --info
This command will output detailed information about the binary.
Some more options:
# Assemble the binary
# ropper --asm [asm_code] --arch ARCH
ropper --asm "mov eax,0x4" --arch x86
# Disassemble the opcode
# ropper --disasm [Opcode] --arch ARCH
ropper --disasm "b804000000" --arch x86
# Show sections of the binary
# ropper --file /path/to/binary -s
ropper --file shell_x86 -s
# Show segments of the binary
# ropper --file /path/to/binary -S
# List imported functions and libraries
# ropper --file /path/to/binary --imports
ropper --file shell_x86 --imports
# List all symbols, including functions and variables
# ropper --file /path/to/binary --symbols
ropper --file shell_x86 --symbols
# Identify gadgets 'pop reg; pop reg; ret' instructions [only x86/x86_64]
# ropper --file /path/to/binary -p/--ppr
ropper --file libc.so.6 --ppr
# Search for jump instructions
# ropper --file /path/to/binary -j <reg>
ropper --file shell_x86 -j eax
# Filter total number of instructions
# ropper --file /path/to/binary --inst-count
ropper --file shell_x86 --inst-count 2
# Search for instructions in the binary
# ropper --file /path/to/binary --instructions <instructions>
ropper --file shell_x86 --instructions "pop ebx"
# Set the type of gadgets
# ropper --file /path/to/binary --type [rop, jop, sys, all]
ropper --file shell_x86 --type jop
# Provide detailed output of the analysis
# ropper --file /path/to/binary --detailed
ropper --file shell_x86 --detailed --inst-count 2
# Define bad bytes to avoid
# ropper --file /path/to/binary --badbytes
ropper --file shell_x86 --inst-count 1 --badbytes "100a"
Filtering by Regex
# Example of Regex Patterns:
# To find gadgets that start with pop:
ropper --file /path/to/binary --search "^pop"
# The caret (^) asserts the position at the start of a line, while the dollar sign ($) asserts the position at the end of a line.
ropper --file /path/to/binary --search "$ret"
# Using Question Marks for Zero or One Character:
ropper --file /path/to/binary --search "^pop e?x"
ROPgadget
ROPgadget was created by Jonathan Salwan, a well-respected figure in the cybersecurity community. You can find more about Jonathan Salwan and his work on his GitHub page. ROPgadget is a powerful tool that assists security researchers and exploit developers in finding useful ROP (Return-Oriented Programming) gadgets within binary files. ROPgadget scans binaries and extracts gadgets that can be used in ROP chains. Ropper is a tool inspired by ROPgadget, built to enhance gadget discovery and facilitate more advanced exploitation techniques. ROPgadget supports ELF/PE/Mach-O/Raw formats on x86, x64, ARM, ARM64, PowerPC, SPARC, MIPS, RISC-V 64, and RISC-V Compressed architectures.
Installation
Refer here
$ pip install capstone
$ pip install ROPgadget
Usage
$ ROPgadget -h
usage: ROPgadget [-h] [-v] [-c] [--binary <binary>] [--opcode <opcodes>]
[--string <string>] [--memstr <string>] [--depth <nbyte>]
[--only <key>] [--filter <key>] [--range <start-end>]
[--badbytes <byte>] [--rawArch <arch>] [--rawMode <mode>]
[--rawEndian <endian>] [--re <re>] [--offset <hexaddr>]
[--ropchain] [--thumb] [--console] [--norop] [--nojop]
[--callPreceded] [--nosys] [--multibr] [--all] [--noinstr]
[--dump] [--silent] [--align ALIGN] [--mipsrop <rtype>]
description:
ROPgadget lets you search your gadgets on a binary. It supports several file formats and architectures and uses the Capstone disassembler for the search engine.
formats supported:
- ELF
- PE
- Mach-O
- Raw
architectures supported:
- x86
- x86-64
- ARM
- ARM64
- MIPS
- PowerPC
- Sparc
- RISC-V 64
- RISC-V Compressed
options:
-h, --help show this help message and exit
-v, --version Display the ROPgadget's version
-c, --checkUpdate Checks if a new version is available
--binary <binary> Specify a binary filename to analyze
--opcode <opcodes> Search opcode in executable segment
--string <string> Search string in readable segment
--memstr <string> Search each byte in all readable segment
--depth <nbyte> Depth for search engine (default 10)
--only <key> Only show specific instructions
--filter <key> Suppress specific mnemonics
--range <start-end> Search between two addresses (0x...-0x...)
--badbytes <byte> Rejects specific bytes in the gadget's address
--rawArch <arch> Specify an arch for a raw file
x86|arm|arm64|sparc|mips|ppc|riscv
--rawMode <mode> Specify a mode for a raw file 32|64|arm|thumb
--rawEndian <endian> Specify an endianness for a raw file little|big
--re <re> Regular expression
--offset <hexaddr> Specify an offset for gadget addresses
--ropchain Enable the ROP chain generation
--thumb Use the thumb mode for the search engine (ARM only)
--console Use an interactive console for search engine
--norop Disable ROP search engine
--nojop Disable JOP search engine
--callPreceded Only show gadgets which are call-preceded
--nosys Disable SYS search engine
--multibr Enable multiple branch gadgets
--all Disables the removal of duplicate gadgets
--noinstr Disable the gadget instructions console printing
--dump Outputs the gadget bytes
--silent Disables printing of gadgets during analysis
--align ALIGN Align gadgets addresses (in bytes)
--mipsrop <rtype> MIPS useful gadgets finder
stackfinder|system|tails|lia0|registers
examples:
ROPgadget.py --binary ./test-suite-binaries/elf-Linux-x86
ROPgadget.py --binary ./test-suite-binaries/elf-Linux-x86 --ropchain
ROPgadget.py --binary ./test-suite-binaries/elf-Linux-x86 --depth 3
ROPgadget.py --binary ./test-suite-binaries/elf-Linux-x86 --string "main"
ROPgadget.py --binary ./test-suite-binaries/elf-Linux-x86 --string "m..n"
ROPgadget.py --binary ./test-suite-binaries/elf-Linux-x86 --opcode c9c3
ROPgadget.py --binary ./test-suite-binaries/elf-Linux-x86 --only "mov|ret"
ROPgadget.py --binary ./test-suite-binaries/elf-Linux-x86 --only "mov|pop|xor|ret"
ROPgadget.py --binary ./test-suite-binaries/elf-Linux-x86 --filter "xchg|add|sub|cmov.*"
ROPgadget.py --binary ./test-suite-binaries/elf-Linux-x86 --norop --nosys
ROPgadget.py --binary ./test-suite-binaries/elf-Linux-x86 --range 0x08041000-0x08042000
ROPgadget.py --binary ./test-suite-binaries/elf-Linux-x86 --string main --range 0x080c9aaa-0x080c9aba
ROPgadget.py --binary ./test-suite-binaries/elf-Linux-x86 --memstr "/bin/sh"
ROPgadget.py --binary ./test-suite-binaries/elf-Linux-x86 --console
ROPgadget.py --binary ./test-suite-binaries/elf-Linux-x86 --badbytes "00|01-1f|7f|42"
ROPgadget.py --binary ./test-suite-binaries/Linux_lib64.so --offset 0xdeadbeef00000000
ROPgadget.py --binary ./test-suite-binaries/elf-ARMv7-ls --depth 5
ROPgadget.py --binary ./test-suite-binaries/elf-ARM64-bash --depth 5
ROPgadget.py --binary ./test-suite-binaries/raw-x86.raw --rawArch=x86 --rawMode=32
ROPgadget.py --binary ./test-suite-binaries/elf-Linux-RISCV_64 --depth 8
Basic Gadget Search
# ROPgadget --binary /path/to/binary
ROPgadget --binary shell_x86
Filtering by Type
# ROPgadget --binary /path/to/binary --only "pop"
ROPgadget --binary shell_x86 --only "ret"
This command will return only those gadgets that contain the ret
instruction.
Using Chains
ROPgadget can also help you create a ROP chain. You can use the –ropchain option:
ROPgadget --binary /path/to/binary --ropchain
Getting Started with One_Gadget
One_Gadget is a powerful tool used in the field of binary exploitation, specifically designed for quickly finding gadgets that can be used to directly call execve("/bin/sh", NULL, NULL)
or similar commands. It leverages existing executable code within a binary to create ROP chains that can be used for exploitation, particularly in situations where traditional shellcode is ineffective or undesirable.
The main advantage of One_Gadget is its ability to reduce the time and effort required to identify usable gadgets, thus streamlining the exploitation process. It automates the discovery of useful gadgets that can facilitate privilege escalation, command execution, or arbitrary code execution.
Installation
Installing One_Gadget is a straightforward process. You can easily install it using gem
, the Ruby package manager, since One_Gadget is written in Ruby. Below are the steps for installation:
sudo apt install ruby
ruby -v
gem install one_gadget
one_gadget --version
Usage of One_Gadget
With One_Gadget installed, you can start using it to find gadgets in your binaries. Here’s a guide on how to use the tool effectively.
Basic Command Structure
To use One_Gadget, you typically invoke it with the following command format:
# one_gadget /path/to/libc.so
one_gadget /lib/i386-linux-gnu/libc.so.6
# To find gadgets near the exit function in libc.so.6, use the following command:
one_gadget /lib/i386-linux-gnu/libc.so.6 --near exit
# Use --raw to get just the offsets without additional information.
one_gadget /lib/i386-linux-gnu/libc.so.6 --near exit --raw
Conclusion
In this guide, we’ve explored the essential tools for gadget hunting: Ropper, ROPgadget, and One_Gadget. Each tool offers unique capabilities that, when combined, provide a comprehensive approach to binary exploitation.
Happy gadget hunting!