Project

one_gadget

0.74
A long-lived project that still receives updates
When playing ctf pwn challenges we usually needs the one-gadget of execve('/bin/sh', NULL, NULL). This gem provides such gadget finder, no need to use IDA-pro every time like a fool :p. Typing `one_gadget /path/to/libc` in terminal and having fun!
2005
2006
2007
2008
2009
2010
2011
2012
2013
2014
2015
2016
2017
2018
2019
2020
2021
2022
2023
2024
 Dependencies

Development

~> 13.0
~> 3.7
~> 1.0
~> 0.9
~> 0.21

Runtime

>= 1.0.2, < 1.4.0
 Project Readme

Gem Version Build Status Downloads Code Climate Issue Count Test Coverage Inline docs Yard Docs MIT License

OneGadget

When playing ctf pwn challenges we usually need the one-gadget RCE (remote code execution), which leads to call execve('/bin/sh', NULL, NULL).

This gem provides such gadgets finder, no need to use objdump or IDA-pro every time like a fool 😉

To use this tool, type one_gadget /path/to/libc in command line and enjoy the magic 😆

Installation

Available on RubyGems.org!

$ gem install one_gadget

Note: requires ruby version >= 2.1.0, you can use ruby --version to check.

Supported Architectures

  • i386
  • amd64 (x86-64)
  • aarch64 (ARMv8)

Implementation

OneGadget uses symbolic execution to find the constraints of gadgets to be successful.

The article introducing how I develop this tool can be found on my blog.

Usage

Command Line Interface

$ one_gadget
# Usage: one_gadget <FILE|-b BuildID> [options]
#     -b, --build-id BuildID           BuildID[sha1] of libc.
#     -f, --[no-]force-file            Force search gadgets in file instead of build id first.
#     -l, --level OUTPUT_LEVEL         The output level.
#                                      OneGadget automatically selects gadgets with higher successful probability.
#                                      Increase this level to ask OneGadget show more gadgets it found.
#                                      Default: 0
#     -n, --near FUNCTIONS/FILE        Order gadgets by their distance to the given functions or to the GOT functions of the given file.
#     -o, --output-format FORMAT       Output format. FORMAT should be one of <pretty|raw|json>.
#                                      Default: pretty
#     -r, --raw                        Alias of -o raw. Output gadgets offset only, split with one space.
#     -s, --script exploit-script      Run exploit script with all possible gadgets.
#                                      The script will be run as 'exploit-script $offset'.
#         --info BuildID               Show version information given BuildID.
#         --base BASE_ADDRESS          The base address of libc.
#                                      Default: 0
#         --version                    Current gem version.
$ one_gadget /lib/x86_64-linux-gnu/libc.so.6
# 0xe3afe execve("/bin/sh", r15, r12)
# constraints:
#   [r15] == NULL || r15 == NULL || r15 is a valid argv
#   [r12] == NULL || r12 == NULL || r12 is a valid envp
#
# 0xe3b01 execve("/bin/sh", r15, rdx)
# constraints:
#   [r15] == NULL || r15 == NULL || r15 is a valid argv
#   [rdx] == NULL || rdx == NULL || rdx is a valid envp
#
# 0xe3b04 execve("/bin/sh", rsi, rdx)
# constraints:
#   [rsi] == NULL || rsi == NULL || rsi is a valid argv
#   [rdx] == NULL || rdx == NULL || rdx is a valid envp

x86_64

Given BuildID

$ one_gadget -b aad7dbe330f23ea00ca63daf793b766b51aceb5d
# 0x4557a execve("/bin/sh", rsp+0x30, environ)
# constraints:
#   [rsp+0x30] == NULL || {[rsp+0x30], [rsp+0x38], [rsp+0x40], [rsp+0x48], ...} is a valid argv
#
# 0xf1651 execve("/bin/sh", rsp+0x40, environ)
# constraints:
#   [rsp+0x40] == NULL || {[rsp+0x40], [rsp+0x48], [rsp+0x50], [rsp+0x58], ...} is a valid argv
#
# 0xf24cb execve("/bin/sh", rsp+0x60, environ)
# constraints:
#   [rsp+0x60] == NULL || {[rsp+0x60], [rsp+0x68], [rsp+0x70], [rsp+0x78], ...} is a valid argv

build id

Gadgets Near Functions

Why

Consider this scenario when exploiting:

  1. Able to write on GOT (Global Offset Table)
  2. Base address of libc is unknown

In this scenario you can choose to write two low-byte on a GOT entry with one-gadget's two low-byte. If the function offset on GOT is close enough with the one-gadget, you will have at least 1/16 chance of success.

Usage

Reorder gadgets according to the distance of given functions.

$ one_gadget /lib/x86_64-linux-gnu/libc.so.6 --near exit,mkdir
# [OneGadget] Gadgets near exit(0x46a40):
# 0xe3afe execve("/bin/sh", r15, r12)
# constraints:
#   [r15] == NULL || r15 == NULL || r15 is a valid argv
#   [r12] == NULL || r12 == NULL || r12 is a valid envp
#
# 0xe3b01 execve("/bin/sh", r15, rdx)
# constraints:
#   [r15] == NULL || r15 == NULL || r15 is a valid argv
#   [rdx] == NULL || rdx == NULL || rdx is a valid envp
#
# 0xe3b04 execve("/bin/sh", rsi, rdx)
# constraints:
#   [rsi] == NULL || rsi == NULL || rsi is a valid argv
#   [rdx] == NULL || rdx == NULL || rdx is a valid envp
#
# [OneGadget] Gadgets near mkdir(0x10de70):
# 0xe3b04 execve("/bin/sh", rsi, rdx)
# constraints:
#   [rsi] == NULL || rsi == NULL || rsi is a valid argv
#   [rdx] == NULL || rdx == NULL || rdx is a valid envp
#
# 0xe3b01 execve("/bin/sh", r15, rdx)
# constraints:
#   [r15] == NULL || r15 == NULL || r15 is a valid argv
#   [rdx] == NULL || rdx == NULL || rdx is a valid envp
#
# 0xe3afe execve("/bin/sh", r15, r12)
# constraints:
#   [r15] == NULL || r15 == NULL || r15 is a valid argv
#   [r12] == NULL || r12 == NULL || r12 is a valid envp
#

near

Regular expression is acceptable.

$ one_gadget /lib/x86_64-linux-gnu/libc.so.6 --near 'write.*' --raw
# [OneGadget] Gadgets near writev(0x114690):
# 932612 932609 932606
#
# [OneGadget] Gadgets near write(0x10e280):
# 932612 932609 932606
#

Pass an ELF file as the argument, OneGadget will take all GOT functions for processing.

$ one_gadget /lib/x86_64-linux-gnu/libc.so.6 --near spec/data/test_near_file.elf --raw
# [OneGadget] Gadgets near exit(0x46a40):
# 932606 932609 932612
#
# [OneGadget] Gadgets near puts(0x84420):
# 932606 932609 932612
#
# [OneGadget] Gadgets near printf(0x61c90):
# 932606 932609 932612
#
# [OneGadget] Gadgets near strlen(0x9f630):
# 932606 932609 932612
#
# [OneGadget] Gadgets near __cxa_finalize(0x46f10):
# 932606 932609 932612
#
# [OneGadget] Gadgets near __libc_start_main(0x23f90):
# 932606 932609 932612
#

Show All Gadgets

Sometimes one_gadget finds too many gadgets to show them in one screen, by default gadgets would be filtered automatically according to the difficulty of constraints.

Use option --level 1 to show all gadgets found instead of only those with higher probabilities.

$ one_gadget /lib/x86_64-linux-gnu/libc.so.6 --level 1
# 0x51dfb posix_spawn(rsp+0xc, "/bin/sh", 0, rbp, rsp+0x50, environ)
# constraints:
#   address rsp+0x60 is writable
#   rsp & 0xf == 0
#   {"sh", "-c", rbx, NULL} is a valid argv
#   rbp == NULL || (u16)[rbp] == NULL
#
# 0x51e02 posix_spawn(rsp+0xc, "/bin/sh", 0, rbp, rsp+0x50, environ)
# constraints:
#   address rsp+0x60 is writable
#   rsp & 0xf == 0
#   rax == NULL || {"sh", rax, rbx, NULL} is a valid argv
#   rbp == NULL || (u16)[rbp] == NULL
#
# 0x51e09 posix_spawn(rsp+0xc, "/bin/sh", 0, rbp, rsp+0x50, environ)
# constraints:
#   address rsp+0x60 is writable
#   rsp & 0xf == 0
#   rcx == NULL || {rcx, rax, rbx, NULL} is a valid argv
#   rbp == NULL || (u16)[rbp] == NULL
#
# 0x51e10 posix_spawn(rsp+0xc, "/bin/sh", rdx, rbp, rsp+0x50, environ)
# constraints:
#   address rsp+0x60 is writable
#   rsp & 0xf == 0
#   rcx == NULL || {rcx, (u64)xmm1, rbx, NULL} is a valid argv
#   rdx == NULL || (s32)[rdx+0x4] <= 0
#   rbp == NULL || (u16)[rbp] == NULL
#
# 0x51e15 posix_spawn(rsp+0xc, "/bin/sh", rdx, rbp, rsp+0x50, environ)
# constraints:
#   address rsp+0x60 is writable
#   rsp & 0xf == 0
#   (u64)xmm0 == NULL || {(u64)xmm0, (u64)xmm1, rbx, NULL} is a valid argv
#   rdx == NULL || (s32)[rdx+0x4] <= 0
#   rbp == NULL || (u16)[rbp] == NULL
#
# 0x51e25 posix_spawn(rdi, "/bin/sh", rdx, rbp, rsp+0x50, [rax])
# constraints:
#   address rsp+0x60 is writable
#   rsp & 0xf == 0
#   (u64)xmm0 == NULL || {(u64)xmm0, (u64)(xmm0 >> 64), rbx, NULL} is a valid argv
#   [[rax]] == NULL || [rax] == NULL || [rax] is a valid envp
#   rdi == NULL || writable: rdi
#   rdx == NULL || (s32)[rdx+0x4] <= 0
#   rbp == NULL || (u16)[rbp] == NULL
#
# 0x51e2a posix_spawn(rdi, "/bin/sh", rdx, rbp, r8, [rax])
# constraints:
#   address rsp+0x60 is writable
#   rsp & 0xf == 0
#   [r8] == NULL || r8 is a valid argv
#   [[rax]] == NULL || [rax] == NULL || [rax] is a valid envp
#   rdi == NULL || writable: rdi
#   rdx == NULL || (s32)[rdx+0x4] <= 0
#   rbp == NULL || (u16)[rbp] == NULL
#
# 0x51e2d posix_spawn(rdi, "/bin/sh", rdx, rcx, r8, [rax])
# constraints:
#   address rsp+0x60 is writable
#   rsp & 0xf == 0
#   [r8] == NULL || r8 is a valid argv
#   [[rax]] == NULL || [rax] == NULL || [rax] is a valid envp
#   rdi == NULL || writable: rdi
#   rdx == NULL || (s32)[rdx+0x4] <= 0
#   rcx == NULL || (u16)[rcx] == NULL
#
# 0x51e32 posix_spawn(rdi, "/bin/sh", rdx, rcx, r8, [rax])
# constraints:
#   address rsp+0x68 is writable
#   rsp & 0xf == 0
#   [r8] == NULL || r8 is a valid argv
#   [[rax]] == NULL || [rax] == NULL || [rax] is a valid envp
#   rdi == NULL || writable: rdi
#   rdx == NULL || (s32)[rdx+0x4] <= 0
#   rcx == NULL || (u16)[rcx] == NULL
#
# 0x84135 posix_spawn(rbx+0xe0, "/bin/sh", r12, 0, rsp+0x60, environ)
# constraints:
#   address rsp+0x70 is writable
#   rsp & 0xf == 0
#   {"sh", "-c", rbp, NULL} is a valid argv
#   rbx+0xe0 == NULL || writable: rbx+0xe0
#   r12 == NULL || (s32)[r12+0x4] <= 0
#
# 0x8413c posix_spawn(rbx+0xe0, "/bin/sh", r12, 0, rsp+0x60, environ)
# constraints:
#   address rsp+0x70 is writable
#   rsp & 0xf == 0
#   rax == NULL || {"sh", rax, rbp, NULL} is a valid argv
#   rbx+0xe0 == NULL || writable: rbx+0xe0
#   r12 == NULL || (s32)[r12+0x4] <= 0
#
# 0x84143 posix_spawn(rbx+0xe0, "/bin/sh", r12, 0, rsp+0x60, environ)
# constraints:
#   address rsp+0x70 is writable
#   rsp & 0xf == 0
#   rcx == NULL || {rcx, rax, rbp, NULL} is a valid argv
#   rbx+0xe0 == NULL || writable: rbx+0xe0
#   r12 == NULL || (s32)[r12+0x4] <= 0
#
# 0x84146 posix_spawn(rbx+0xe0, "/bin/sh", rdx, 0, rsp+0x60, environ)
# constraints:
#   address rsp+0x70 is writable
#   rsp & 0xf == 0
#   rcx == NULL || {rcx, rax, rbp, NULL} is a valid argv
#   rbx+0xe0 == NULL || writable: rbx+0xe0
#   rdx == NULL || (s32)[rdx+0x4] <= 0
#
# 0x8414b posix_spawn(rbx+0xe0, "/bin/sh", rdx, 0, rsp+0x60, environ)
# constraints:
#   address rsp+0x78 is writable
#   rsp & 0xf == 0
#   rcx == NULL || {rcx, rax, [rsp+0x70], NULL} is a valid argv
#   rbx+0xe0 == NULL || writable: rbx+0xe0
#   rdx == NULL || (s32)[rdx+0x4] <= 0
#
# 0x84150 posix_spawn(rbx+0xe0, "/bin/sh", rdx, 0, rsp+0x60, environ)
# constraints:
#   address rsp+0x78 is writable
#   rsp & 0xf == 0
#   rcx == NULL || {rcx, (u64)xmm1, [rsp+0x70], NULL} is a valid argv
#   rbx+0xe0 == NULL || writable: rbx+0xe0
#   rdx == NULL || (s32)[rdx+0x4] <= 0
#
# 0x8415c posix_spawn(rbx+0xe0, "/bin/sh", rdx, 0, rsp+0x60, [rax])
# constraints:
#   address rsp+0x78 is writable
#   rsp & 0xf == 0
#   (u64)xmm0 == NULL || {(u64)xmm0, (u64)xmm1, [rsp+0x70], NULL} is a valid argv
#   [[rax]] == NULL || [rax] == NULL || [rax] is a valid envp
#   rbx+0xe0 == NULL || writable: rbx+0xe0
#   rdx == NULL || (s32)[rdx+0x4] <= 0
#
# 0x84162 posix_spawn(rbx+0xe0, "/bin/sh", rdx, rcx, rsp+0x60, [rax])
# constraints:
#   address rsp+0x78 is writable
#   rsp & 0xf == 0
#   (u64)xmm0 == NULL || {(u64)xmm0, (u64)(xmm0 >> 64), [rsp+0x70], NULL} is a valid argv
#   [[rax]] == NULL || [rax] == NULL || [rax] is a valid envp
#   rbx+0xe0 == NULL || writable: rbx+0xe0
#   rdx == NULL || (s32)[rdx+0x4] <= 0
#   rcx == NULL || (u16)[rcx] == NULL
#
# 0x84169 posix_spawn(rdi, "/bin/sh", rdx, rcx, rsp+0x60, [rax])
# constraints:
#   address rsp+0x78 is writable
#   rsp & 0xf == 0
#   (u64)xmm0 == NULL || {(u64)xmm0, (u64)(xmm0 >> 64), [rsp+0x70], NULL} is a valid argv
#   [[rax]] == NULL || [rax] == NULL || [rax] is a valid envp
#   rdi == NULL || writable: rdi
#   rdx == NULL || (s32)[rdx+0x4] <= 0
#   rcx == NULL || (u16)[rcx] == NULL
#
# 0x84170 posix_spawn(rdi, "/bin/sh", rdx, rcx, r8, [rax])
# constraints:
#   address rsp+0x78 is writable
#   rsp & 0xf == 0
#   [r8] == NULL || r8 is a valid argv
#   [[rax]] == NULL || [rax] == NULL || [rax] is a valid envp
#   rdi == NULL || writable: rdi
#   rdx == NULL || (s32)[rdx+0x4] <= 0
#   rcx == NULL || (u16)[rcx] == NULL
#
# 0xe3afe execve("/bin/sh", r15, r12)
# constraints:
#   [r15] == NULL || r15 == NULL || r15 is a valid argv
#   [r12] == NULL || r12 == NULL || r12 is a valid envp
#
# 0xe3b01 execve("/bin/sh", r15, rdx)
# constraints:
#   [r15] == NULL || r15 == NULL || r15 is a valid argv
#   [rdx] == NULL || rdx == NULL || rdx is a valid envp
#
# 0xe3b04 execve("/bin/sh", rsi, rdx)
# constraints:
#   [rsi] == NULL || rsi == NULL || rsi is a valid argv
#   [rdx] == NULL || rdx == NULL || rdx is a valid envp
#
# 0xe3cf3 execve("/bin/sh", r10, r12)
# constraints:
#   address rbp-0x78 is writable
#   [r10] == NULL || r10 == NULL || r10 is a valid argv
#   [r12] == NULL || r12 == NULL || r12 is a valid envp
#
# 0xe3cf6 execve("/bin/sh", r10, rdx)
# constraints:
#   address rbp-0x78 is writable
#   [r10] == NULL || r10 == NULL || r10 is a valid argv
#   [rdx] == NULL || rdx == NULL || rdx is a valid envp
#
# 0xe3d62 execve("/bin/sh", rbp-0x50, r12)
# constraints:
#   address rbp-0x48 is writable
#   r13 == NULL || {"/bin/sh", r13, NULL} is a valid argv
#   [r12] == NULL || r12 == NULL || r12 is a valid envp
#
# 0xe3d69 execve("/bin/sh", rbp-0x50, r12)
# constraints:
#   address rbp-0x48 is writable
#   rax == NULL || {rax, r13, NULL} is a valid argv
#   [r12] == NULL || r12 == NULL || r12 is a valid envp
#
# 0xe3d70 execve("/bin/sh", rbp-0x50, r12)
# constraints:
#   address rbp-0x50 is writable
#   rax == NULL || {rax, [rbp-0x48], NULL} is a valid argv
#   [r12] == NULL || r12 == NULL || r12 is a valid envp
#
# 0xe3da7 execve("/bin/sh", rbp-0x50, r12)
# constraints:
#   address rbp-0x50 is writable
#   [rbp-0x68] == NULL || {"/bin/sh", [rbp-0x68], NULL} is a valid argv
#   [r12] == NULL || r12 == NULL || r12 is a valid envp
#
# 0xe3db1 execve("/bin/sh", rbp-0x50, r12)
# constraints:
#   address rbp-0x50 is writable
#   rax == NULL || {rax, [rbp-0x68], NULL} is a valid argv
#   [r12] == NULL || r12 == NULL || r12 is a valid envp
#
# 0xe3db5 execve("/bin/sh", r10, r12)
# constraints:
#   addresses r10+0x10, rbp-0x50 are writable
#   [r10] == NULL || r10 == NULL || r10 is a valid argv
#   [r12] == NULL || r12 == NULL || r12 is a valid envp
#
# 0xe3dbd execve("/bin/sh", r10, r12)
# constraints:
#   addresses r10+0x10, rbp-0x48 are writable
#   [r10] == NULL || r10 == NULL || r10 is a valid argv
#   [r12] == NULL || r12 == NULL || r12 is a valid envp
#
# 0x1077ca posix_spawn(rsp+0x64, "/bin/sh", [rsp+0x38], 0, rsp+0x70, [rsp+0xf0])
# constraints:
#   [rsp+0x70] == NULL || {[rsp+0x70], [rsp+0x78], [rsp+0x80], [rsp+0x88], ...} is a valid argv
#   [[rsp+0xf0]] == NULL || [rsp+0xf0] == NULL || [rsp+0xf0] is a valid envp
#   [rsp+0x38] == NULL || (s32)[[rsp+0x38]+0x4] <= 0
#
# 0x1077d2 posix_spawn(rsp+0x64, "/bin/sh", [rsp+0x38], 0, rsp+0x70, r9)
# constraints:
#   [rsp+0x70] == NULL || {[rsp+0x70], [rsp+0x78], [rsp+0x80], [rsp+0x88], ...} is a valid argv
#   [r9] == NULL || r9 == NULL || r9 is a valid envp
#   [rsp+0x38] == NULL || (s32)[[rsp+0x38]+0x4] <= 0
#
# 0x1077d7 posix_spawn(rsp+0x64, "/bin/sh", rdx, 0, rsp+0x70, r9)
# constraints:
#   [rsp+0x70] == NULL || {[rsp+0x70], [rsp+0x78], [rsp+0x80], [rsp+0x88], ...} is a valid argv
#   [r9] == NULL || r9 == NULL || r9 is a valid envp
#   rdx == NULL || (s32)[rdx+0x4] <= 0
#
# 0x1077e1 posix_spawn(rdi, "/bin/sh", rdx, 0, r8, r9)
# constraints:
#   [r8] == NULL || r8 is a valid argv
#   [r9] == NULL || r9 == NULL || r9 is a valid envp
#   rdi == NULL || writable: rdi
#   rdx == NULL || (s32)[rdx+0x4] <= 0

Other Architectures

i386
$ one_gadget /lib32/libc.so.6
# 0xc890b execve("/bin/sh", [ebp-0x2c], esi)
# constraints:
#   address ebp-0x20 is writable
#   ebx is the GOT address of libc
#   [[ebp-0x2c]] == NULL || [ebp-0x2c] == NULL || [ebp-0x2c] is a valid argv
#   [esi] == NULL || esi == NULL || esi is a valid envp
#
# 0x1421b3 execl("/bin/sh", eax)
# constraints:
#   ebp is the GOT address of libc
#   eax == NULL
#
# 0x1421b4 execl("/bin/sh", [esp])
# constraints:
#   ebp is the GOT address of libc
#   [esp] == NULL

i386

AArch64
$ one_gadget spec/data/aarch64-libc-2.27.so
# 0x3f160 execve("/bin/sh", sp+0x70, environ)
# constraints:
#   address x20+0x338 is writable
#   x3 == NULL
#
# 0x3f184 execve("/bin/sh", sp+0x70, environ)
# constraints:
#   addresses x19+0x4, x20+0x338 are writable
#   [sp+0x70] == NULL
#
# 0x3f1a8 execve("/bin/sh", x21, environ)
# constraints:
#   addresses x19+0x4, x20+0x338 are writable
#   [x21] == NULL || x21 == NULL
#
# 0x63e90 execl("/bin/sh", x1)
# constraints:
#   x1 == NULL

aarch64

Combine with Script

Pass your exploit script as one_gadget's arguments, it can try all gadgets one by one, so you don't need to try every possible gadgets manually.

$ one_gadget ./spec/data/libc-2.19.so -s 'echo "offset ->"'

--script

In Ruby Scripts

require 'one_gadget'
OneGadget.gadgets(file: '/lib/x86_64-linux-gnu/libc.so.6')
#=> [932606, 932609, 932612]

# or in shorter way
one_gadget('/lib/x86_64-linux-gnu/libc.so.6', level: 1)
#=> [335355, 335362, 335369, 335376, 335381, 335397, 335402, 335405, 335410, 540981, 540988, 540995, 540998, 541003, 541008, 541020, 541026, 541033, 541040, 932606, 932609, 932612, 933107, 933110, 933218, 933225, 933232, 933287, 933297, 933301, 933309, 1079242, 1079250, 1079255, 1079265]

# from build id
one_gadget('b417c0ba7cc5cf06d1d1bed6652cedb9253c60d0')
#=> [324286, 324293, 324386, 1090444]

To Python Lovers

import subprocess
def one_gadget(filename):
  return [int(i) for i in subprocess.check_output(['one_gadget', '--raw', filename]).decode().split(' ')]

one_gadget('/lib/x86_64-linux-gnu/libc.so.6')
#=> [932606, 932609, 932612]

Make OneGadget Better

Any suggestion or feature request is welcome! Feel free to send a pull request.

Please let me know if you find any libc that make OneGadget fail to find gadgets. And, if you like this work, I'll be happy to be starred 😬