Mastering Gadget Hunting with Ropper, ROPgadget, and One_Gadget: A Practical Guide

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!