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 ๐
Available on RubyGems.org!
$ gem install one_gadget
Note: requires ruby version >= 2.1.0, you can use ruby --version
to check.
- i386
- amd64 (x86-64)
- aarch64 (ARMv8)
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.
$ 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.
# -r, --[no-]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.
# --version Current gem version.
$ one_gadget /lib/x86_64-linux-gnu/libc.so.6
# 0x4f2c5 execve("/bin/sh", rsp+0x40, environ)
# constraints:
# rsp & 0xf == 0
# rcx == NULL
#
# 0x4f322 execve("/bin/sh", rsp+0x40, environ)
# constraints:
# [rsp+0x40] == NULL
#
# 0x10a38c execve("/bin/sh", rsp+0x70, environ)
# constraints:
# [rsp+0x70] == NULL
$ one_gadget -b aad7dbe330f23ea00ca63daf793b766b51aceb5d
# 0x45526 execve("/bin/sh", rsp+0x30, environ)
# constraints:
# rax == NULL
#
# 0x4557a execve("/bin/sh", rsp+0x30, environ)
# constraints:
# [rsp+0x30] == NULL
#
# 0xf1651 execve("/bin/sh", rsp+0x40, environ)
# constraints:
# [rsp+0x40] == NULL
#
# 0xf24cb execve("/bin/sh", rsp+0x60, environ)
# constraints:
# [rsp+0x60] == NULL
Consider this scenario when exploiting:
- Able to write on GOT (Global Offset Table)
- 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.
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(0x43120):
# 0x4f2c5 execve("/bin/sh", rsp+0x40, environ)
# constraints:
# rsp & 0xf == 0
# rcx == NULL
#
# 0x4f322 execve("/bin/sh", rsp+0x40, environ)
# constraints:
# [rsp+0x40] == NULL
#
# 0x10a38c execve("/bin/sh", rsp+0x70, environ)
# constraints:
# [rsp+0x70] == NULL
#
# [OneGadget] Gadgets near mkdir(0x10fbb0):
# 0x10a38c execve("/bin/sh", rsp+0x70, environ)
# constraints:
# [rsp+0x70] == NULL
#
# 0x4f322 execve("/bin/sh", rsp+0x40, environ)
# constraints:
# [rsp+0x40] == NULL
#
# 0x4f2c5 execve("/bin/sh", rsp+0x40, environ)
# constraints:
# rsp & 0xf == 0
# rcx == NULL
#
Regular expression is acceptable.
$ one_gadget /lib/x86_64-linux-gnu/libc.so.6 --near 'write.*' --raw
# [OneGadget] Gadgets near writev(0x1166a0):
# 1090444 324386 324293
#
# [OneGadget] Gadgets near write(0x110140):
# 1090444 324386 324293
#
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(0x43120):
# 324293 324386 1090444
#
# [OneGadget] Gadgets near puts(0x809c0):
# 324386 324293 1090444
#
# [OneGadget] Gadgets near printf(0x64e80):
# 324386 324293 1090444
#
# [OneGadget] Gadgets near strlen(0x9dc70):
# 324386 324293 1090444
#
# [OneGadget] Gadgets near __cxa_finalize(0x43520):
# 324293 324386 1090444
#
# [OneGadget] Gadgets near __libc_start_main(0x21ab0):
# 324293 324386 1090444
#
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
# 0x4f2c5 execve("/bin/sh", rsp+0x40, environ)
# constraints:
# rsp & 0xf == 0
# rcx == NULL
#
# 0x4f322 execve("/bin/sh", rsp+0x40, environ)
# constraints:
# [rsp+0x40] == NULL
#
# 0xe569f execve("/bin/sh", r14, r12)
# constraints:
# [r14] == NULL || r14 == NULL
# [r12] == NULL || r12 == NULL
#
# 0xe5858 execve("/bin/sh", [rbp-0x88], [rbp-0x70])
# constraints:
# [[rbp-0x88]] == NULL || [rbp-0x88] == NULL
# [[rbp-0x70]] == NULL || [rbp-0x70] == NULL
#
# 0xe585f execve("/bin/sh", r10, [rbp-0x70])
# constraints:
# [r10] == NULL || r10 == NULL
# [[rbp-0x70]] == NULL || [rbp-0x70] == NULL
#
# 0xe5863 execve("/bin/sh", r10, rdx)
# constraints:
# [r10] == NULL || r10 == NULL
# [rdx] == NULL || rdx == NULL
#
# 0x10a38c execve("/bin/sh", rsp+0x70, environ)
# constraints:
# [rsp+0x70] == NULL
#
# 0x10a398 execve("/bin/sh", rsi, [rax])
# constraints:
# [rsi] == NULL || rsi == NULL
# [[rax]] == NULL || [rax] == NULL
$ one_gadget /lib32/libc.so.6
# 0x3cbea execve("/bin/sh", esp+0x34, environ)
# constraints:
# esi is the GOT address of libc
# [esp+0x34] == NULL
#
# 0x3cbec execve("/bin/sh", esp+0x38, environ)
# constraints:
# esi is the GOT address of libc
# [esp+0x38] == NULL
#
# 0x3cbf0 execve("/bin/sh", esp+0x3c, environ)
# constraints:
# esi is the GOT address of libc
# [esp+0x3c] == NULL
#
# 0x3cbf7 execve("/bin/sh", esp+0x40, environ)
# constraints:
# esi is the GOT address of libc
# [esp+0x40] == NULL
#
# 0x6729f execl("/bin/sh", eax)
# constraints:
# esi is the GOT address of libc
# eax == NULL
#
# 0x672a0 execl("/bin/sh", [esp])
# constraints:
# esi is the GOT address of libc
# [esp] == NULL
#
# 0x13573e execl("/bin/sh", eax)
# constraints:
# ebx is the GOT address of libc
# eax == NULL
#
# 0x13573f execl("/bin/sh", [esp])
# constraints:
# ebx is the GOT address of libc
# [esp] == NULL
$ 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
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 ->"'
require 'one_gadget'
OneGadget.gadgets(file: '/lib/x86_64-linux-gnu/libc.so.6')
#=> [324293, 324386, 1090444]
# or in shorter way
one_gadget('/lib/x86_64-linux-gnu/libc.so.6', level: 1)
#=> [324293, 324386, 939679, 940120, 940127, 940131, 1090444, 1090456]
# from build id
one_gadget('b417c0ba7cc5cf06d1d1bed6652cedb9253c60d0')
#=> [324293, 324386, 1090444]
import subprocess
def one_gadget(filename):
return map(int, subprocess.check_output(['one_gadget', '--raw', filename]).split(' '))
one_gadget('/lib/x86_64-linux-gnu/libc.so.6')
#=> [324293, 324386, 1090444]
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 ๐ฌ