david942j/one_gadget: The best tool for finding one gadget RCE in libc.so.6

OneGadget When playing ctf pwn challenges we usually need the one-gadget RCE (remote code execution),

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!

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

Supported Architectures

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.
#     -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.
#         --base BASE_ADDRESS          The base address of libc.
#                                      Default: 0
#         --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

x86_64

Given BuildID

$ 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

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(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
#

near

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
#

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
# 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

Other Architectures

i386
$ 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

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')
#=> [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]

To Python Lovers

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]

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 😬

Source Article