david942j / one_gadget Goto Github PK
View Code? Open in Web Editor NEWThe best tool for finding one gadget RCE in libc.so.6
License: MIT License
The best tool for finding one gadget RCE in libc.so.6
License: MIT License
Seems like version 1.8.0 prefers the posix_spawn
gadgets and does not list execve
-gadgets with default output-level 0. Those posix_spawn
gadgets have much harder contraints and shouldn't be displayed instead of the easier execve
-gadgets.
Tests were conducted on the libc of the Ubuntu Docker: FROM ubuntu@sha256:a02c32cf0c2a7e8743c74deef66637aa70e063c9bd40e9e1f8c0b3ea0750b0ba
/usr/lib/x86_64-linux-gnu/libc.so.6: ELF 64-bit LSB shared object, x86-64, version 1 (GNU/Linux), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=89c3cb85f9e55046776471fed05ec441581d1969, for GNU/Linux 3.2.0, stripped
Output with one_gadget 1.7.4:
root@ecb44960ff3e:/home/ctf# one_gadget --version
OneGadget Version 1.7.4
root@ecb44960ff3e:/home/ctf# one_gadget /usr/lib/x86_64-linux-gnu/libc.so.6
0xebcf1 execve("/bin/sh", r10, [rbp-0x70])
constraints:
address rbp-0x78 is writable
[r10] == NULL || r10 == NULL
[[rbp-0x70]] == NULL || [rbp-0x70] == NULL
0xebcf5 execve("/bin/sh", r10, rdx)
constraints:
address rbp-0x78 is writable
[r10] == NULL || r10 == NULL
[rdx] == NULL || rdx == NULL
0xebcf8 execve("/bin/sh", rsi, rdx)
constraints:
address rbp-0x78 is writable
[rsi] == NULL || rsi == NULL
[rdx] == NULL || rdx == NULL
Output with 1.8.0:
root@ecb44960ff3e:/home/ctf# gem install one_gadget --version 1.8.0
Fetching one_gadget-1.8.0.gem
Successfully installed one_gadget-1.8.0
Parsing documentation for one_gadget-1.8.0
Installing ri documentation for one_gadget-1.8.0
Done installing documentation for one_gadget after 3 seconds
1 gem installed
root@ecb44960ff3e:/home/ctf# one_gadget --version
OneGadget Version 1.8.0
root@ecb44960ff3e:/home/ctf# one_gadget /usr/lib/x86_64-linux-gnu/libc.so.6
0x50a37 posix_spawn(rsp+0x1c, "/bin/sh", 0, rbp, rsp+0x60, environ)
constraints:
rsp & 0xf == 0
rcx == NULL
rbp == NULL || (u16)[rbp] == NULL
0x10dbc2 posix_spawn(rsp+0x64, "/bin/sh", [rsp+0x40], 0, rsp+0x70, [rsp+0xf0])
constraints:
[rsp+0x70] == NULL
[[rsp+0xf0]] == NULL || [rsp+0xf0] == NULL
[rsp+0x40] == NULL || (s32)[[rsp+0x40]+0x4] <= 0
0x10dbca posix_spawn(rsp+0x64, "/bin/sh", [rsp+0x40], 0, rsp+0x70, r9)
constraints:
[rsp+0x70] == NULL
[r9] == NULL || r9 == NULL
[rsp+0x40] == NULL || (s32)[[rsp+0x40]+0x4] <= 0
0x10dbcf posix_spawn(rsp+0x64, "/bin/sh", rdx, 0, rsp+0x70, r9)
constraints:
[rsp+0x70] == NULL
[r9] == NULL || r9 == NULL
rdx == NULL || (s32)[rdx+0x4] <= 0
Objdump that supports architecture "amd64" is not found
系统版本 ubuntu 16.04
objdump 版本 GNU objdump (GNU Binutils for Ubuntu) 2.26.1
in libc-2.27.so on ubuntu 18.04:
4f2ec: 48 89 44 24 08 mov QWORD PTR [rsp+0x8],rax
4f2f1: 0f 16 44 24 08 movhps xmm0,QWORD PTR [rsp+0x8]
4f2f6: 0f 29 44 24 40 movaps XMMWORD PTR [rsp+0x40],xmm0
This should be considered as equivalent to mov [rsp+0x48], rax
For the libc file with the hash 4fcd76645607f38d91f65654ddd6b9770b5ea54a
, we have the following gadgets:
Here is where these gadgets are located:
As you can see, just before calling execve
, there is an instruction mov [rbp+var_78], r11
. If you use a buffer overflow to manipulate the return address with strcpy
or strncpy
, it is possible that you have to overwrite rbp
value and you are unable to use any null bytes. Therefore, rbp+var_78
becomes an invalid address and this gadget crashes silently.
I think it should be listed under constraints and indicate that rbp+var_78
needs to be a valid and writeable address.
Refactor to a library so that one_gadget can be a dependency of pwntools-ruby
Need fix the hash argument incompatibility in Ruby 2.8
https://travis-ci.com/github/david942j/one_gadget/jobs/350965465
AppVeyor can only run one job, try if Travis has better performance
Observing this on glibc 2.31 (https://gitlab.com/david942j/libcdb/blob/master/libc/libc6_2.31-0ubuntu10_amd64/lib/x86_64-linux-gnu/libc-2.31.so):
87315: 48 8d 05 8b 02 13 00 lea rax,[rip+0x13028b] # -c
8731c: 48 8d 0d 8c 02 13 00 lea rcx,[rip+0x13028c] # sh
87323: 4c 89 e2 mov rdx,r12
87326: 48 89 6c 24 70 mov QWORD PTR [rsp+0x70],rbp
8732b: 66 48 0f 6e c8 movq xmm1,rax
87330: 48 8b 05 79 3b 16 00 mov rax,QWORD PTR [rip+0x163b79] # environ_ptr
87337: 66 48 0f 6e c1 movq xmm0,rcx
8733c: 31 c9 xor ecx,ecx
8733e: 66 0f 6c c1 punpcklqdq xmm0,xmm1
87342: 48 8d bb e0 00 00 00 lea rdi,[rbx+0xe0]
87349: 4c 8d 44 24 60 lea r8,[rsp+0x60]
8734e: 31 ed xor ebp,ebp
87350: 4c 8b 08 mov r9,QWORD PTR [rax]
87353: 48 8d 35 50 02 13 00 lea rsi,[rip+0x130250] # /bin/sh
8735a: 0f 29 44 24 60 movaps XMMWORD PTR [rsp+0x60],xmm0
8735f: 48 c7 44 24 78 00 00 00 00 mov QWORD PTR [rsp+0x78],0x0
87368: e8 73 84 08 00 call 10f7e0 <posix_spawn@@GLIBC_2.15>
I haven't tried but it looks like if we jump to 0x8731c (skip the assignment of "-c" to rax
), we will execute
posix_spawn(rbx+0xe0, "/bin/sh", r12, 0, rsp+0x60, environ)
, and the array at rsp+0x60
is { "sh", rax, rbp, 0 }
The constraints are
r12 == NULL
or (u32)[r12 + 4] == 0
(posix_spawn_file_actions_t.__used == 0
)rax == NULL
The example above is called in _IO_new_proc_open()
, original source is
__posix_spawn (&((_IO_proc_file *) fp)->pid, _PATH_BSHELL, fa, 0,
(char *const[]){ (char*) "sh", (char*) "-c",
(char *) command, NULL }, __environ)
prototype of posix_spawn: posix_spawn (pid_t *pid, const char *path, const posix_spawn_file_actions_t *file_actions, const posix_spawnattr_t *attrp, char *const argv[], char *const envp[])
Checked with https://gitlab.com/david942j/libcdb/blob/master/libc/libc6_2.31-0ubuntu10_amd64/lib/x86_64-linux-gnu/libc-2.31.so
Current findings:
0xe6d43 execve("/bin/sh", r10, r12)
constraints:
[r10] == NULL || r10 == NULL
[r12] == NULL || r12 == NULL
0xe6d46 execve("/bin/sh", r10, rdx)
constraints:
[r10] == NULL || r10 == NULL
[rdx] == NULL || rdx == NULL
0xe6d49 execve("/bin/sh", rsi, rdx)
constraints:
[rsi] == NULL || rsi == NULL
[rdx] == NULL || rdx == NULL
Expected more gadgets such as:
e6b4e: 4c 89 e2 mov rdx,r12
e6b51: 4c 89 fe mov rsi,r15
e6b54: 48 8d 3d 4f 0a 0d 00 lea rdi,[rip+0xd0a4f] # 1b75aa <_libc_intl_domainname@@GLIBC_2.2.5+0x1a5>
e6b5b: e8 60 f6 ff ff call e61c0 <execve@@GLIBC_2.2.5>
0xe6b4e execve("/bin/sh", r15, r12)
this pattern is exactly same as the one at 0xe6d43, assuming a bug exists.
As mentioned here, for XMM instructions the register needs to be aligned: #26
Can we show this as a constraint in the output?
Hello sir.
You have a great app, unfortunately this app does not have a logo yet, may I donate a logo for your app?
I am the maintainer of LibcDB (https://gitlab.com/libcdb/libcdb) which is a database of all of the libc for various distributions.
You can perform a lookup of a given libc via BuildID, SHA1, MD5, or SHA256.
https://gitlab.com/libcdb/libcdb/tree/master/hashes
Here's an example of one of the existing builds, below. If you clone the repo (warning: 30+GB) it'll be a symlink to the actual file.
To download the actual file directly, you can use the following link (i.e., replace blob
with raw
in the URL)
https://gitlab.com/libcdb/libcdb/raw/master/hashes/build_id/369de0e1d833caa693af17f17c83ba937f0a4dad
It may be useful to integrate your tool with this functionality, since LibcDB is updated daily (though there are not always new libc to actually update, it just runs as a cron job):
Currently one_gadget
is based on objdump's output, so the biggest issue is how to make users to have the specific version of objdump.
I have two ways to resolve this issue:
Need discussions @scwuaptx
There's a weird behavior when using one_gadget
on the filename that ends with a number.
This bug can be reproduced by something like:
$ objdump --version
GNU objdump (GNU Binutils for Ubuntu) 2.38
Copyright (C) 2022 Free Software Foundation, Inc.
This program is free software; you may redistribute it under the terms of
the GNU General Public License version 3 or (at your option) any later version.
This program has absolutely no warranty.
$ one_gadget --version
OneGadget Version 1.8.1
$ cp /lib/x86_64-linux-gnu/libc.so.6 .
$ one_gadget /lib/x86_64-linux-gnu/libc.so.6 > a
$ one_gadget libc.so > b
$ mv libc.so x.1337
$ one_gadget x.1337 > c
$ md5sum a
55a458aadc6b2c93fccc164ad5bedd6d a
$ md5sum b
16db62efc9d108f06c6d2aecec205484 b
$ md5sum c
55a458aadc6b2c93fccc164ad5bedd6d c
As you can see, although libc.so.6
, libc.so
, and x.1337
are actually the same file, the output of libc.so
is different.
Seems like this bug is caused by this line:
When the filename is something like: libc.so.6
, grep -E '[0-9a-f]+:'
will also match the filename in the objdump output:
$ objdump --no-show-raw-insn -w -d -M intel /lib/x86_64-linux-gnu/libc.so.6 | grep -E '[0-9a-f]+:' | head
/lib/x86_64-linux-gnu/libc.so.6: file format elf64-x86-64
28000: push QWORD PTR [rip+0x1f1002] # 219008 <_GLOBAL_OFFSET_TABLE_+0x8>
28006: bnd jmp QWORD PTR [rip+0x1f1003] # 219010 <_GLOBAL_OFFSET_TABLE_+0x10>
2800d: nop DWORD PTR [rax]
28010: endbr64
28014: push 0x35
28019: bnd jmp 28000 <__abi_tag+0x27c5c>
2801f: nop
28020: endbr64
28024: push 0x34
Then it somehow causes some bugs and makes the candidates in jmp_case_candidates
never pass.
This issue can be fixed by adding the --dward-start=0
option when running the objdump.
Since the description in the manual of the objdump is a little bit unclear for me, here's the source code reference that shows why --dward-start=0
works:
https://github.com/bminor/binutils-gdb/blob/a89e364b45a93acd20f48abd787ef5cb7c07f683/binutils/objdump.c#L6158-L6163
https://github.com/bminor/binutils-gdb/blob/a89e364b45a93acd20f48abd787ef5cb7c07f683/binutils/objdump.c#L5586-L5589
Thanks for this project, but I got such error when I run the script on some stripped binary:
/home/anciety/.gem/ruby/2.4.0/gems/one_gadget-1.4.0/lib/one_gadget/fetchers/i386.rb:116:in `block in got_offset': undefined method `tag_by_type' for nil:NilClass (NoMethodError)
from /home/anciety/.gem/ruby/2.4.0/gems/one_gadget-1.4.0/lib/one_gadget/fetchers/i386.rb:114:in `open'
from /home/anciety/.gem/ruby/2.4.0/gems/one_gadget-1.4.0/lib/one_gadget/fetchers/i386.rb:114:in `got_offset'
from /home/anciety/.gem/ruby/2.4.0/gems/one_gadget-1.4.0/lib/one_gadget/fetchers/i386.rb:13:in `find'
from /home/anciety/.gem/ruby/2.4.0/gems/one_gadget-1.4.0/lib/one_gadget/fetcher.rb:45:in `from_file'
from /home/anciety/.gem/ruby/2.4.0/gems/one_gadget-1.4.0/lib/one_gadget.rb:32:in `gadgets'
from /home/anciety/.gem/ruby/2.4.0/gems/one_gadget-1.4.0/bin/one_gadget:48:in `<top (required)>'
from /home/anciety/.gem/ruby/2.4.0/bin/one_gadget:22:in `load'
from /home/anciety/.gem/ruby/2.4.0/bin/one_gadget:22:in `<main>'
Is it impossible to run this on stripped binary? According to this error message I assume it is caused by the stripping. Since I really don't know about Ruby, I can do little help with this..
And the binary is just defcon quals 2016 feedme challenge
According to ABI (https://github.com/hjl-tools/x86-psABI/wiki/x86-64-psABI-r252.pdf,
B.2 "B.2 Optimize GOTPCRELX Relocations"),
it's valid to convert call *foo@GOTPCREL(%rip)
to nop call foo
, or <inst> call foo
where <inst>
can be any one-byte instruction.
Found use case in libc-2.25.so, which invokes:
addr32 call bbfb0 <execve>
This leads OneGadget fail to find gadgets.
Checked on glibc 2.31, https://gitlab.com/david942j/libcdb/blob/master/libc/libc6_2.31-0ubuntu10_amd64/lib/x86_64-linux-gnu/libc-2.31.so
e6df7: 48 8d 05 ac 07 0d 00 lea rax,[rip+0xd07ac] # 1b75aa <_libc_intl_domainname@@GLIBC_2.2.5+0x1a5>
e6dfe: 49 89 e3 mov r11,rsp
e6e01: 4c 8d 55 b0 lea r10,[rbp-0x50]
e6e05: 48 89 45 b0 mov QWORD PTR [rbp-0x50],rax
e6e09: 48 8b 45 98 mov rax,QWORD PTR [rbp-0x68]
e6e0d: 48 89 45 b8 mov QWORD PTR [rbp-0x48],rax
e6e11: e9 25 ff ff ff jmp e6d3b <execvpe@@GLIBC_2.11+0x46b>
<...>
e6d3b: 49 c7 42 10 00 00 00 00 mov QWORD PTR [r10+0x10],0x0
e6d43: 4c 89 e2 mov rdx,r12
e6d46: 4c 89 d6 mov rsi,r10
e6d49: 48 8d 3d 5a 08 0d 00 lea rdi,[rip+0xd085a] # 1b75aa <_libc_intl_domainname@@GLIBC_2.2.5+0x1a5>
e6d50: 4c 89 5d 88 mov QWORD PTR [rbp-0x78],r11
e6d54: e8 67 f4 ff ff call e61c0 <execve@@GLIBC_2.2.5>
It calls execve("/bin/sh", rbp-0x50, r12)
, the "array" of rbp-0x50
is { "/bin/sh", [rbp-0x68], 0 }
, which is a valid one gadget with [rbp-0x68] == NULL
as the constraint. (and rbp-0x50
has to be writable)
Some time I got update check information in one_gadget -r
, pretty annoying when I use python to get those offsets automatically.
I think -r
option should not contain update info. Any way to avoid this exclude changing my script?
Is it possible to use one_gadget on a normal binary? Sometimes in CTFs there are calls to execve("/bin/sh") somewhere in user code.
One hackish way I found to make one_gadget work on a normal binary is by converting it to a shared object with ld -r binary
but that does not work with non-PIE binaries, among other possible problems :)
I'm trying to find gadgets on my own GLIBC using one_gagdet, but I can't seem to find anything.
Using the command like so:
$ one_gadget /usr/lib/libc.so.6
or
$ one_gadget /lib/libc.so.6
Gives no output at all. Using it on a binary that is not GLIBC gives an error so I know one_gadget is working.
GLIBC version is release 2.33
.
Is it possible that there are no gadgets, am I doing something wrong, or is it a bug that I've encountered in one_gadget?
Apparently this tool find no gadgets on my machine: (wrapped mine)
$ file /lib/libc-2.26.so
/lib/libc-2.26.so: ELF 64-bit LSB shared object, x86-64, version 1 (GNU/Linux),
dynamically linked, interpreter /usr/lib/ld-linux-x86-64.so.2,
BuildID[sha1]=f46739d962ec152b56d2bdb7dadaf8e576dbf6eb, for GNU/Linux 3.2.0, not stripped
$ ~/.gem/ruby/2.4.0/bin/one_gadget --version
OneGadget Version 1.4.0
$ ~/.gem/ruby/2.4.0/bin/one_gadget /lib/libc-2.26.so
$
This build is distributed by Arch Linux glibc 2.26-5. Until next update, you can also download a copy from any Arch Linux mirror.
I don't know if it's a bug or glibc has hardened itself though.
/var/lib/gems/2.3.0/gems/one_gadget-1.6.0/lib/one_gadget/fetchers/amd64.rb:32:in `to_s': wrong number of arguments (given 1, expected 0) (ArgumentError)
from /var/lib/gems/2.3.0/gems/one_gadget-1.6.0/lib/one_gadget/fetchers/amd64.rb:32:in `jmp_case_candidates'
from /var/lib/gems/2.3.0/gems/one_gadget-1.6.0/lib/one_gadget/fetchers/amd64.rb:21:in `candidates'
from /var/lib/gems/2.3.0/gems/one_gadget-1.6.0/lib/one_gadget/fetchers/base.rb:19:in `find'
from /var/lib/gems/2.3.0/gems/one_gadget-1.6.0/lib/one_gadget/fetcher.rb:32:in `from_file'
from /var/lib/gems/2.3.0/gems/one_gadget-1.6.0/lib/one_gadget.rb:35:in `gadgets'
from /var/lib/gems/2.3.0/gems/one_gadget-1.6.0/bin/one_gadget:67:in `<top (required)>'
from /usr/local/bin/one_gadget:23:in `load'
from /usr/local/bin/one_gadget:23:in `<main>'
any suggestions?
To have coverage report on the CLI bin.
when i use this tool , i'm bothered about having to add an offset to each gadget . Could you please add this feature to this tool ? Thanks a lot !
Some nonstandard versions of GLIBC can cause one_gadget v1.8.0 and later to error with "bad value for range" in jmp_case_candidates()
from lib/one_gadget/fetchers/amd64.rb
git bisect suggests this problem was introduced in commit 8765130
The problem seems to be a change to this line:
-A
argument passed to egrep was changed from 3 to 8. Anything below 8 doesn't seem to cause problems, but 8 will trigger the error later in the function.
I'm not a Ruby developer so this is as far as I could take this issue. I also appreciate this only appears to affect nonstandard GLIBC builds, so I understand if it's not a priority. Attached is a GLIBC build that triggers this bug: libc-2.30.zip
Thanks for building this great tool and for making it available!
EDIT: After further testing, this problem seems to only manifest in Ubuntu 18.04, later versions of the same OS aren't affected. Perhaps this points to a binutils issue?
when I tried gem install one_gadget,it appeared. but in article it only need ruby version >= 2.1.0, by the way ,my os is Ubuntu 16.04.7 LTS and ruby version is ruby 2.3.1p112 (2016-04-26) [x86_64-linux-gnu] , save me plz!
What does it mean by GOT address of libc in one_gadget's output?
E.g>
`
0x3d123 execve("/bin/sh", esp+0x34, environ)
constraints:
esi is the GOT address of libc
[esp+0x34] == NULL
0x3d125 execve("/bin/sh", esp+0x38, environ)
constraints:
esi is the GOT address of libc
[esp+0x38] == NULL
0x3d129 execve("/bin/sh", esp+0x3c, environ)
constraints:
esi is the GOT address of libc
[esp+0x3c] == NULL
0x3d130 execve("/bin/sh", esp+0x40, environ)
constraints:
esi is the GOT address of libc
[esp+0x40] == NULL
0x67b4f execl("/bin/sh", eax)
constraints:
esi is the GOT address of libc
eax == NULL
0x67b50 execl("/bin/sh", [esp])
constraints:
esi is the GOT address of libc
[esp] == NULL
0x1380be execl("/bin/sh", eax)
constraints:
ebx is the GOT address of libc
eax == NULL
0x1380bf execl("/bin/sh", [esp])
constraints:
ebx is the GOT address of libc
[esp] == NULL
`
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.