mirror of
https://github.com/david942j/one_gadget.git
synced 2026-10-11 22:09:54 +00:00
Find MIPS gadgets (#448)
* Find gadgets in a MIPS libc * Pass over the fixtures that state no BuildID
This commit is contained in:
@@ -15,6 +15,12 @@ releases are cut by giving that section a version and a date.
|
||||
#422, #423, #425). Every gadget the riscv64 fixture reports has been run under
|
||||
a debugger and seen to spawn a real shell, the same bar the other
|
||||
architectures are held to (#424).
|
||||
- MIPS (32-bit o32) support, in both byte orders (#448) -- big-endian is as widely
|
||||
deployed as little-endian on this architecture, so both are read. Two things
|
||||
about it are unlike the other architectures and are handled without the rest of
|
||||
the engine having to know: a call names no target, going through the GOT
|
||||
instead, and a branch or call takes effect one instruction late, so what follows
|
||||
it runs first.
|
||||
- Reading a libc that ships without section headers, as an embedded one commonly
|
||||
does -- OpenWrt strips them from musl to save flash (#432). Nothing is missing
|
||||
from such a file, so the symbols are read through `PT_DYNAMIC` and the code is
|
||||
|
||||
@@ -33,6 +33,7 @@ $ gem install one_gadget
|
||||
- [x] aarch64 (ARMv8)
|
||||
- [x] arm (ARMv7, A32/Thumb-2)
|
||||
- [x] riscv64 (RV64GC)
|
||||
- [x] mips (MIPS32 o32, big- and little-endian)
|
||||
|
||||
## Implementation
|
||||
|
||||
@@ -600,6 +601,33 @@ $ one_gadget spec/data/riscv64-libc-2.39.so
|
||||
|
||||
```
|
||||
|
||||
##### MIPS
|
||||
|
||||
Big-endian is as common as little-endian here, and both are read. This one is
|
||||
OpenWrt's musl for ath79, which ships with no section headers -- the shape a
|
||||
router's libc usually arrives in.
|
||||
|
||||
```bash
|
||||
$ one_gadget spec/data/mips-musl-1.2.4.so
|
||||
# 0x56fd4 execl("/bin/sh", a1-0x7c70)
|
||||
# constraints:
|
||||
# gp is the GOT address of libc
|
||||
# a1-0x7c70 == NULL
|
||||
#
|
||||
# 0x77b44 posix_spawn(sp+0x20, "/bin/sh", s2, 0, sp+0x2c, environ)
|
||||
# constraints:
|
||||
# gp is the GOT address of libc
|
||||
# {"sh", "-c", s7, NULL} is a valid argv
|
||||
# s2 == NULL || (s32)[s2+0x4] <= 0x0
|
||||
#
|
||||
# 0x77b48 posix_spawn(sp+0x20, "/bin/sh", s2, 0, sp+0x2c, environ)
|
||||
# constraints:
|
||||
# gp is the GOT address of libc
|
||||
# v0-0x7c70 == NULL || {v0-0x7c70, "-c", s7, NULL} is a valid argv
|
||||
# s2 == NULL || (s32)[s2+0x4] <= 0x0
|
||||
|
||||
```
|
||||
|
||||
#### Combine with Script
|
||||
Pass your exploit script to `one_gadget` and it runs the script once per gadget, so you
|
||||
don't have to work through them by hand.
|
||||
|
||||
@@ -33,6 +33,7 @@ $ gem install one_gadget
|
||||
- [x] aarch64 (ARMv8)
|
||||
- [x] arm (ARMv7, A32/Thumb-2)
|
||||
- [x] riscv64 (RV64GC)
|
||||
- [x] mips (MIPS32 o32, big- and little-endian)
|
||||
|
||||
## Implementation
|
||||
|
||||
@@ -166,6 +167,16 @@ SHELL_OUTPUT_OF(one_gadget spec/data/arm-libc-2.39.so)
|
||||
SHELL_OUTPUT_OF(one_gadget spec/data/riscv64-libc-2.39.so)
|
||||
```
|
||||
|
||||
##### MIPS
|
||||
|
||||
Big-endian is as common as little-endian here, and both are read. This one is
|
||||
OpenWrt's musl for ath79, which ships with no section headers -- the shape a
|
||||
router's libc usually arrives in.
|
||||
|
||||
```bash
|
||||
SHELL_OUTPUT_OF(one_gadget spec/data/mips-musl-1.2.4.so)
|
||||
```
|
||||
|
||||
#### Combine with Script
|
||||
Pass your exploit script to `one_gadget` and it runs the script once per gadget, so you
|
||||
don't have to work through them by hand.
|
||||
|
||||
@@ -869,6 +869,7 @@ libc-2.35-a91d1bc05ce8f54989de1172c24b2b736378901f
|
||||
libc-2.35-ab265082cac9486923c709d48ee5dde080e243ff
|
||||
libc-2.35-c376d41cff4473142a97ac1ff1eab433859dc3d4
|
||||
libc-2.35-dfca8b65dd2d2ca67f70dc7a556a6cfa8ba96ed8
|
||||
libc-2.36-019d4db0c5d400a83e3b5f3bebee6ac38dee3c48
|
||||
libc-2.39-328820b908de8ea1ef79afa8995e302e819163d7
|
||||
libc-2.39-54ae5337044aaabab70d65e0c3444f7e4735e059
|
||||
libc-2.39-70d6616bcfd43eaa73962220df98dd9c82ca6687
|
||||
|
||||
@@ -111,7 +111,7 @@ Reading the flow:
|
||||
|
||||
The last three have defaults in `Base` that read the `$base`-relative form an arch
|
||||
produces once it concretizes a pc-relative operand, so an arch that renders
|
||||
addresses that way (aarch64, arm, amd64, riscv64) implements none of them. Override
|
||||
addresses that way (aarch64, arm, amd64, mips, riscv64) implements none of them. Override
|
||||
them only where the address reads differently — i386 reaches a global through its
|
||||
GOT register, so all three are its own.
|
||||
| `branch_kind(line)` | classify an instruction: `:conditional`, `:unconditional`, `:terminator`, or `nil` (not a branch) |
|
||||
|
||||
@@ -0,0 +1,168 @@
|
||||
require 'one_gadget/gadget'
|
||||
# spec/data/mipsel-libc-2.36.so
|
||||
#
|
||||
# MIPS R3000
|
||||
#
|
||||
# GNU C Library (Debian GLIBC 2.36-9+deb12u14) stable release version 2.36.
|
||||
# Copyright (C) 2022 Free Software Foundation, Inc.
|
||||
# This is free software; see the source for copying conditions.
|
||||
# There is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A
|
||||
# PARTICULAR PURPOSE.
|
||||
# Compiled by GNU CC version 12.2.0.
|
||||
# libc ABIs: MIPS_PLT UNIQUE MIPS_O32_FP64 ABSOLUTE MIPS_XHASH
|
||||
# Minimum supported kernel: 3.2.0
|
||||
# For bug reporting instructions, please see:
|
||||
# <http://www.debian.org/Bugs/>.
|
||||
|
||||
build_id = File.basename(__FILE__, '.rb').split('-').last
|
||||
OneGadget::Gadget.add(build_id, 308164,
|
||||
constraints: ["gp is the GOT address of libc", "a0 != v1", "readable: [sp+0x28]", "{\"sh\", \"-c\", s5, NULL} is a valid argv", "sp+0x1fc == NULL || (u16)[sp+0x1fc] == 0x0"],
|
||||
effect: "posix_spawn(sp+0x3c, \"/bin/sh\", 0, sp+0x1fc, sp+0x5c, environ)")
|
||||
OneGadget::Gadget.add(build_id, 308164,
|
||||
constraints: ["gp is the GOT address of libc", "a0 == v1", "readable: [sp+0x28]", "{\"sh\", \"-c\", s5, NULL} is a valid argv", "sp+0x1fc == NULL || (u16)[sp+0x1fc] == 0x0"],
|
||||
effect: "posix_spawn(sp+0x3c, \"/bin/sh\", 0, sp+0x1fc, sp+0x5c, environ)")
|
||||
OneGadget::Gadget.add(build_id, 308168,
|
||||
constraints: ["gp is the GOT address of libc", "a0 != v1", "readable: [sp+0x28]", "{\"sh\", \"-c\", s5, NULL} is a valid argv", "sp+0x1fc == NULL || (u16)[sp+0x1fc] == 0x0"],
|
||||
effect: "posix_spawn(sp+0x3c, \"/bin/sh\", 0, sp+0x1fc, sp+0x5c, environ)")
|
||||
OneGadget::Gadget.add(build_id, 308168,
|
||||
constraints: ["gp is the GOT address of libc", "a0 == v1", "readable: [sp+0x28]", "{\"sh\", \"-c\", s5, NULL} is a valid argv", "sp+0x1fc == NULL || (u16)[sp+0x1fc] == 0x0"],
|
||||
effect: "posix_spawn(sp+0x3c, \"/bin/sh\", 0, sp+0x1fc, sp+0x5c, environ)")
|
||||
OneGadget::Gadget.add(build_id, 308172,
|
||||
constraints: ["gp is the GOT address of libc", "readable: [sp+0x28]", "{\"sh\", \"-c\", s5, NULL} is a valid argv", "sp+0x1fc == NULL || (u16)[sp+0x1fc] == 0x0"],
|
||||
effect: "posix_spawn(sp+0x3c, \"/bin/sh\", 0, sp+0x1fc, sp+0x5c, environ)")
|
||||
OneGadget::Gadget.add(build_id, 308176,
|
||||
constraints: ["gp is the GOT address of libc", "readable: [sp+0x28]", "{\"sh\", \"-c\", s5, NULL} is a valid argv", "sp+0x1fc == NULL || (u16)[sp+0x1fc] == 0x0"],
|
||||
effect: "posix_spawn(sp+0x3c, \"/bin/sh\", 0, sp+0x1fc, sp+0x5c, environ)")
|
||||
OneGadget::Gadget.add(build_id, 308180,
|
||||
constraints: ["gp is the GOT address of libc", "readable: [sp+0x28]", "{\"sh\", \"-c\", s5, NULL} is a valid argv", "sp+0x1fc == NULL || (u16)[sp+0x1fc] == 0x0"],
|
||||
effect: "posix_spawn(sp+0x3c, \"/bin/sh\", 0, sp+0x1fc, sp+0x5c, environ)")
|
||||
OneGadget::Gadget.add(build_id, 308184,
|
||||
constraints: ["gp is the GOT address of libc", "readable: [sp+0x28]", "{\"sh\", \"-c\", s5, NULL} is a valid argv", "sp+0x1fc == NULL || (u16)[sp+0x1fc] == 0x0"],
|
||||
effect: "posix_spawn(sp+0x3c, \"/bin/sh\", 0, sp+0x1fc, sp+0x5c, environ)")
|
||||
OneGadget::Gadget.add(build_id, 308188,
|
||||
constraints: ["gp is the GOT address of libc", "readable: [sp+0x28]", "writable: s1", "{\"sh\", \"-c\", s5, NULL} is a valid argv", "(u16)[s1] == 0x0"],
|
||||
effect: "posix_spawn(sp+0x3c, \"/bin/sh\", 0, s1, sp+0x5c, environ)")
|
||||
OneGadget::Gadget.add(build_id, 308192,
|
||||
constraints: ["gp is the GOT address of libc", "readable: [sp+0x28]", "writable: s1", "{\"sh\", \"-c\", s5, NULL} is a valid argv", "(u16)[s1] == 0x0"],
|
||||
effect: "posix_spawn(sp+0x3c, \"/bin/sh\", 0, s1, sp+0x5c, environ)")
|
||||
OneGadget::Gadget.add(build_id, 308196,
|
||||
constraints: ["gp is the GOT address of libc", "readable: [sp+0x28]", "writable: s1", "{\"sh\", \"-c\", s5, NULL} is a valid argv", "(u16)[s1] == 0x0"],
|
||||
effect: "posix_spawn(sp+0x3c, \"/bin/sh\", 0, s1, sp+0x5c, environ)")
|
||||
OneGadget::Gadget.add(build_id, 308200,
|
||||
constraints: ["gp is the GOT address of libc", "readable: [sp+0x28]", "writable: s1", "{\"sh\", \"-c\", s5, NULL} is a valid argv", "(u16)[s1] == 0x0"],
|
||||
effect: "posix_spawn(sp+0x3c, \"/bin/sh\", 0, s1, sp+0x5c, environ)")
|
||||
OneGadget::Gadget.add(build_id, 308204,
|
||||
constraints: ["gp is the GOT address of libc", "readable: [sp+0x28]", "writable: a0", "writable: s1", "{\"sh\", \"-c\", s5, NULL} is a valid argv", "(u16)[s1] == 0x0"],
|
||||
effect: "posix_spawn(sp+0x3c, \"/bin/sh\", 0, s1, sp+0x5c, environ)")
|
||||
OneGadget::Gadget.add(build_id, 308208,
|
||||
constraints: ["gp is the GOT address of libc", "readable: [sp+0x28]", "writable: a0", "writable: s1", "{\"sh\", \"-c\", s5, NULL} is a valid argv", "(u16)[s1] == 0x0"],
|
||||
effect: "posix_spawn(sp+0x3c, \"/bin/sh\", 0, s1, sp+0x5c, environ)")
|
||||
OneGadget::Gadget.add(build_id, 308212,
|
||||
constraints: ["gp is the GOT address of libc", "readable: [sp+0x28]", "writable: a0", "writable: s1", "{\"sh\", \"-c\", s5, NULL} is a valid argv", "(u16)[s1] == 0x0"],
|
||||
effect: "posix_spawn(sp+0x3c, \"/bin/sh\", 0, s1, sp+0x5c, environ)")
|
||||
OneGadget::Gadget.add(build_id, 308216,
|
||||
constraints: ["gp is the GOT address of libc", "writable: s1", "{\"sh\", \"-c\", s5, NULL} is a valid argv", "(u16)[s1] == 0x0"],
|
||||
effect: "posix_spawn(sp+0x3c, \"/bin/sh\", 0, s1, sp+0x5c, environ)")
|
||||
OneGadget::Gadget.add(build_id, 308220,
|
||||
constraints: ["gp is the GOT address of libc", "writable: s1", "{\"sh\", \"-c\", s5, NULL} is a valid argv", "(u16)[s1] == 0x0"],
|
||||
effect: "posix_spawn(sp+0x3c, \"/bin/sh\", 0, s1, sp+0x5c, environ)")
|
||||
OneGadget::Gadget.add(build_id, 308224,
|
||||
constraints: ["gp is the GOT address of libc", "readable: a1", "writable: s1", "{\"sh\", \"-c\", s5, NULL} is a valid argv", "(u16)[s1] == 0x0"],
|
||||
effect: "posix_spawn(sp+0x3c, \"/bin/sh\", 0, s1, sp+0x5c, environ)")
|
||||
OneGadget::Gadget.add(build_id, 308228,
|
||||
constraints: ["gp is the GOT address of libc", "readable: a1", "writable: s1", "{\"sh\", \"-c\", s5, NULL} is a valid argv", "(u16)[s1] == 0x0"],
|
||||
effect: "posix_spawn(sp+0x3c, \"/bin/sh\", 0, s1, sp+0x5c, environ)")
|
||||
OneGadget::Gadget.add(build_id, 308232,
|
||||
constraints: ["gp is the GOT address of libc", "readable: a1", "writable: s1", "{\"sh\", \"-c\", s5, NULL} is a valid argv", "(u16)[s1] == 0x0"],
|
||||
effect: "posix_spawn(sp+0x3c, \"/bin/sh\", 0, s1, sp+0x5c, environ)")
|
||||
OneGadget::Gadget.add(build_id, 308236,
|
||||
constraints: ["gp is the GOT address of libc", "writable: s1", "{\"sh\", \"-c\", s5, NULL} is a valid argv", "(u16)[s1] == 0x0"],
|
||||
effect: "posix_spawn(sp+0x3c, \"/bin/sh\", 0, s1, sp+0x5c, environ)")
|
||||
OneGadget::Gadget.add(build_id, 308240,
|
||||
constraints: ["gp is the GOT address of libc", "writable: s1", "{\"sh\", \"-c\", s5, NULL} is a valid argv", "(u16)[s1] == 0x0"],
|
||||
effect: "posix_spawn(sp+0x3c, \"/bin/sh\", 0, s1, sp+0x5c, environ)")
|
||||
OneGadget::Gadget.add(build_id, 308244,
|
||||
constraints: ["gp is the GOT address of libc", "writable: s1", "{\"sh\", \"-c\", s5, NULL} is a valid argv", "(u16)[s1] == 0x0"],
|
||||
effect: "posix_spawn(sp+0x3c, \"/bin/sh\", 0, s1, sp+0x5c, environ)")
|
||||
OneGadget::Gadget.add(build_id, 308248,
|
||||
constraints: ["gp is the GOT address of libc", "writable: s1", "{\"sh\", \"-c\", s5, NULL} is a valid argv", "(u16)[s1] == 0x0"],
|
||||
effect: "posix_spawn(sp+0x3c, \"/bin/sh\", 0, s1, sp+0x5c, environ)")
|
||||
OneGadget::Gadget.add(build_id, 308252,
|
||||
constraints: ["gp is the GOT address of libc", "writable: s1", "{\"sh\", \"-c\", s5, NULL} is a valid argv", "(u16)[s1] == 0x0"],
|
||||
effect: "posix_spawn(sp+0x3c, \"/bin/sh\", 0, s1, sp+0x5c, environ)")
|
||||
OneGadget::Gadget.add(build_id, 308256,
|
||||
constraints: ["gp is the GOT address of libc", "{\"sh\", \"-c\", s5, NULL} is a valid argv", "s1 == NULL || (u16)[s1] == 0x0"],
|
||||
effect: "posix_spawn(sp+0x3c, \"/bin/sh\", 0, s1, sp+0x5c, environ)")
|
||||
OneGadget::Gadget.add(build_id, 308260,
|
||||
constraints: ["gp is the GOT address of libc", "{\"sh\", \"-c\", s5, NULL} is a valid argv", "s1 == NULL || (u16)[s1] == 0x0"],
|
||||
effect: "posix_spawn(sp+0x3c, \"/bin/sh\", 0, s1, sp+0x5c, environ)")
|
||||
OneGadget::Gadget.add(build_id, 308264,
|
||||
constraints: ["gp is the GOT address of libc", "[v1] == NULL || v1 is a valid argv", "s1 == NULL || (u16)[s1] == 0x0"],
|
||||
effect: "posix_spawn(sp+0x3c, \"/bin/sh\", 0, s1, v1, environ)")
|
||||
OneGadget::Gadget.add(build_id, 308268,
|
||||
constraints: ["gp is the GOT address of libc", "[v1] == NULL || v1 is a valid argv", "s1 == NULL || (u16)[s1] == 0x0"],
|
||||
effect: "posix_spawn(sp+0x3c, \"/bin/sh\", 0, s1, v1, environ)")
|
||||
OneGadget::Gadget.add(build_id, 308272,
|
||||
constraints: ["gp is the GOT address of libc", "[v1] == NULL || v1 is a valid argv", "a2 == NULL || (s32)[a2+0x4] <= 0x0", "s1 == NULL || (u16)[s1] == 0x0"],
|
||||
effect: "posix_spawn(sp+0x3c, \"/bin/sh\", a2, s1, v1, environ)")
|
||||
OneGadget::Gadget.add(build_id, 308276,
|
||||
constraints: ["gp is the GOT address of libc", "[v1] == NULL || v1 is a valid argv", "a0 == NULL || writable: a0", "a2 == NULL || (s32)[a2+0x4] <= 0x0", "s1 == NULL || (u16)[s1] == 0x0"],
|
||||
effect: "posix_spawn(a0, \"/bin/sh\", a2, s1, v1, environ)")
|
||||
OneGadget::Gadget.add(build_id, 308280,
|
||||
constraints: ["gp is the GOT address of libc", "[v1] == NULL || v1 is a valid argv", "a0 == NULL || writable: a0", "a2 == NULL || (s32)[a2+0x4] <= 0x0", "s1 == NULL || (u16)[s1] == 0x0"],
|
||||
effect: "posix_spawn(a0, \"/bin/sh\", a2, s1, v1, environ)")
|
||||
OneGadget::Gadget.add(build_id, 308284,
|
||||
constraints: ["gp is the GOT address of libc", "[v1] == NULL || v1 is a valid argv", "a0 == NULL || writable: a0", "a2 == NULL || (s32)[a2+0x4] <= 0x0", "a3 == NULL || (u16)[a3] == 0x0"],
|
||||
effect: "posix_spawn(a0, \"/bin/sh\", a2, a3, v1, environ)")
|
||||
OneGadget::Gadget.add(build_id, 308288,
|
||||
constraints: ["gp is the GOT address of libc", "[v1] == NULL || v1 is a valid argv", "a0 == NULL || writable: a0", "a2 == NULL || (s32)[a2+0x4] <= 0x0", "a3 == NULL || (u16)[a3] == 0x0"],
|
||||
effect: "posix_spawn(a0, \"/bin/sh\", a2, a3, v1, environ)")
|
||||
OneGadget::Gadget.add(build_id, 308292,
|
||||
constraints: ["gp is the GOT address of libc", "readable: v0", "[v1] == NULL || v1 is a valid argv", "[[v0]] == NULL || [v0] == NULL || [v0] is a valid envp", "a0 == NULL || writable: a0", "a2 == NULL || (s32)[a2+0x4] <= 0x0", "a3 == NULL || (u16)[a3] == 0x0"],
|
||||
effect: "posix_spawn(a0, \"/bin/sh\", a2, a3, v1, [v0])")
|
||||
OneGadget::Gadget.add(build_id, 467588,
|
||||
constraints: ["gp is the GOT address of libc", "[$base+0x1d32fc] == 0x0", "writable: s2+0x4", "{\"sh\", \"-c\", s0, NULL} is a valid argv", "s7+0xa0 == NULL || writable: s7+0xa0", "s1 == NULL || (s32)[s1+0x4] <= 0x0"],
|
||||
effect: "posix_spawn(s7+0xa0, \"/bin/sh\", s1, 0, sp+0x58, environ)")
|
||||
OneGadget::Gadget.add(build_id, 467592,
|
||||
constraints: ["gp is the GOT address of libc", "[v0+0x32fc] == 0x0", "writable: s2+0x4", "{\"sh\", \"-c\", s0, NULL} is a valid argv", "s7+0xa0 == NULL || writable: s7+0xa0", "s1 == NULL || (s32)[s1+0x4] <= 0x0"],
|
||||
effect: "posix_spawn(s7+0xa0, \"/bin/sh\", s1, 0, sp+0x58, environ)")
|
||||
OneGadget::Gadget.add(build_id, 467596,
|
||||
constraints: ["gp is the GOT address of libc", "[v0+0x32fc] == 0x0", "writable: s2+0x4", "{\"sh\", \"-c\", s0, NULL} is a valid argv", "s7+0xa0 == NULL || writable: s7+0xa0", "s1 == NULL || (s32)[s1+0x4] <= 0x0"],
|
||||
effect: "posix_spawn(s7+0xa0, \"/bin/sh\", s1, 0, sp+0x58, environ)")
|
||||
OneGadget::Gadget.add(build_id, 467600,
|
||||
constraints: ["gp is the GOT address of libc", "[v1+0x32fc] == 0x0", "writable: s2+0x4", "{\"sh\", \"-c\", s0, NULL} is a valid argv", "s7+0xa0 == NULL || writable: s7+0xa0", "s1 == NULL || (s32)[s1+0x4] <= 0x0"],
|
||||
effect: "posix_spawn(s7+0xa0, \"/bin/sh\", s1, 0, sp+0x58, environ)")
|
||||
OneGadget::Gadget.add(build_id, 467604,
|
||||
constraints: ["gp is the GOT address of libc", "[v1+0x32fc] == 0x0", "writable: s2+0x4", "{\"sh\", \"-c\", s0, NULL} is a valid argv", "s7+0xa0 == NULL || writable: s7+0xa0", "s1 == NULL || (s32)[s1+0x4] <= 0x0"],
|
||||
effect: "posix_spawn(s7+0xa0, \"/bin/sh\", s1, 0, sp+0x58, environ)")
|
||||
OneGadget::Gadget.add(build_id, 467608,
|
||||
constraints: ["gp is the GOT address of libc", "[v1+0x32fc] == 0x0", "writable: s2+0x4", "{\"sh\", \"-c\", s0, NULL} is a valid argv", "s7+0xa0 == NULL || writable: s7+0xa0", "s1 == NULL || (s32)[s1+0x4] <= 0x0"],
|
||||
effect: "posix_spawn(s7+0xa0, \"/bin/sh\", s1, 0, sp+0x58, environ)")
|
||||
OneGadget::Gadget.add(build_id, 467612,
|
||||
constraints: ["gp is the GOT address of libc", "s5 == 0x0", "writable: s2+0x4", "{\"sh\", \"-c\", s0, NULL} is a valid argv", "s7+0xa0 == NULL || writable: s7+0xa0", "s1 == NULL || (s32)[s1+0x4] <= 0x0"],
|
||||
effect: "posix_spawn(s7+0xa0, \"/bin/sh\", s1, 0, sp+0x58, environ)")
|
||||
OneGadget::Gadget.add(build_id, 467616,
|
||||
constraints: ["gp is the GOT address of libc", "s5 == 0x0", "writable: s2+0x4", "{\"sh\", \"-c\", s0, NULL} is a valid argv", "s7+0xa0 == NULL || writable: s7+0xa0", "s1 == NULL || (s32)[s1+0x4] <= 0x0"],
|
||||
effect: "posix_spawn(s7+0xa0, \"/bin/sh\", s1, 0, sp+0x58, environ)")
|
||||
OneGadget::Gadget.add(build_id, 467620,
|
||||
constraints: ["gp is the GOT address of libc", "writable: s2+0x4", "{\"sh\", \"-c\", s0, NULL} is a valid argv", "s7+0xa0 == NULL || writable: s7+0xa0", "s1 == NULL || (s32)[s1+0x4] <= 0x0"],
|
||||
effect: "posix_spawn(s7+0xa0, \"/bin/sh\", s1, 0, sp+0x58, environ)")
|
||||
OneGadget::Gadget.add(build_id, 467664,
|
||||
constraints: ["gp is the GOT address of libc", "{\"sh\", \"-c\", s0, NULL} is a valid argv", "s7+0xa0 == NULL || writable: s7+0xa0", "s1 == NULL || (s32)[s1+0x4] <= 0x0"],
|
||||
effect: "posix_spawn(s7+0xa0, \"/bin/sh\", s1, 0, sp+0x58, environ)")
|
||||
OneGadget::Gadget.add(build_id, 467668,
|
||||
constraints: ["gp is the GOT address of libc", "readable: v0", "{\"sh\", \"-c\", s0, NULL} is a valid argv", "[[v0]] == NULL || [v0] == NULL || [v0] is a valid envp", "s7+0xa0 == NULL || writable: s7+0xa0", "s1 == NULL || (s32)[s1+0x4] <= 0x0"],
|
||||
effect: "posix_spawn(s7+0xa0, \"/bin/sh\", s1, 0, sp+0x58, [v0])")
|
||||
OneGadget::Gadget.add(build_id, 467672,
|
||||
constraints: ["gp is the GOT address of libc", "readable: v0", "[v1] == NULL || v1 is a valid argv", "[[v0]] == NULL || [v0] == NULL || [v0] is a valid envp", "s7+0xa0 == NULL || writable: s7+0xa0", "s1 == NULL || (s32)[s1+0x4] <= 0x0"],
|
||||
effect: "posix_spawn(s7+0xa0, \"/bin/sh\", s1, 0, v1, [v0])")
|
||||
OneGadget::Gadget.add(build_id, 1604260,
|
||||
constraints: ["gp is the GOT address of libc", "a2-0x6a84 == NULL"],
|
||||
effect: "execl(\"/bin/sh\", \"sh\", a2-0x6a84)")
|
||||
OneGadget::Gadget.add(build_id, 1604264,
|
||||
constraints: ["gp is the GOT address of libc", "a2-0x6a84 == NULL"],
|
||||
effect: "execl(\"/bin/sh\", \"sh\", a2-0x6a84)")
|
||||
OneGadget::Gadget.add(build_id, 1604268,
|
||||
constraints: ["gp is the GOT address of libc", "a1-0x6a88 == NULL"],
|
||||
effect: "execl(\"/bin/sh\", a1-0x6a88)")
|
||||
|
||||
@@ -93,6 +93,8 @@ module OneGadget
|
||||
resolve_pending_branch(cmd)
|
||||
@cur_addr = cmd[/\A\s*([0-9a-f]+):/, 1]&.to_i(16)
|
||||
|
||||
@got_value = cmd[GOT_VALUE, 1]&.to_i(16)
|
||||
|
||||
mnem = mnemonic(cmd)
|
||||
if transfer?(mnem)
|
||||
@delayed = [mnem, cmd]
|
||||
@@ -108,6 +110,12 @@ module OneGadget
|
||||
|
||||
private
|
||||
|
||||
# What a GOT slot holds, stated beside the load by
|
||||
# +OneGadget::Fetchers::Mips+ because this architecture reaches its globals
|
||||
# through that table rather than through the instruction's own address.
|
||||
GOT_VALUE = /#\s*([0-9a-f]+)\s*\z/
|
||||
private_constant :GOT_VALUE
|
||||
|
||||
# The calls, which are delayed exactly as the branches are.
|
||||
CALLS = %w[jal jalr bal].freeze
|
||||
private_constant :CALLS
|
||||
@@ -232,6 +240,8 @@ module OneGadget
|
||||
# @return [void]
|
||||
def load_value(dst, mem, size)
|
||||
check_register!(dst)
|
||||
# a GOT slot holds an address, and the file says which
|
||||
return registers[dst] = libc_base + @got_value if @got_value && size == size_t
|
||||
|
||||
value = read_value(arg_to_lambda(mem_operand(mem)))
|
||||
registers[dst] = size == size_t ? value : clobbered_value
|
||||
|
||||
@@ -5,6 +5,7 @@ require 'one_gadget/fetchers/aarch64'
|
||||
require 'one_gadget/fetchers/amd64'
|
||||
require 'one_gadget/fetchers/arm'
|
||||
require 'one_gadget/fetchers/i386'
|
||||
require 'one_gadget/fetchers/mips'
|
||||
require 'one_gadget/fetchers/riscv64'
|
||||
require 'one_gadget/gadget'
|
||||
require 'one_gadget/helper'
|
||||
@@ -23,6 +24,7 @@ module OneGadget
|
||||
amd64: OneGadget::Fetchers::Amd64,
|
||||
arm: OneGadget::Fetchers::Arm,
|
||||
i386: OneGadget::Fetchers::I386,
|
||||
mips: OneGadget::Fetchers::Mips,
|
||||
riscv64: OneGadget::Fetchers::Riscv64
|
||||
}.freeze
|
||||
|
||||
|
||||
@@ -245,9 +245,14 @@ module OneGadget
|
||||
# @return [Object, nil] Truthy when +envp+ is acceptable, +nil+ to reject the gadget.
|
||||
def check_envp(processor, envp_ptr)
|
||||
# A doubly-dereferenced pointer that names a global variable is believed to
|
||||
# be environ; one that doesn't drops the gadget.
|
||||
return global_var?(envp_ptr.to_s) if envp_ptr.is_a?(OneGadget::Emulators::Lambda) &&
|
||||
envp_ptr.deref_count >= 2
|
||||
# be environ; one that doesn't drops the gadget. The two dereferences are
|
||||
# the slot the address is read from and the variable itself, so an
|
||||
# architecture that resolves the first of them lands one earlier -- naming
|
||||
# the variable, which it then has to be able to recognise.
|
||||
if envp_ptr.is_a?(OneGadget::Emulators::Lambda)
|
||||
return global_var?(envp_ptr.to_s) if envp_ptr.deref_count >= 2
|
||||
return true if envp_ptr.deref_count == 1 && environ?(envp_ptr.to_s)
|
||||
end
|
||||
|
||||
envp_ptr = resolve_stack_deref(processor, envp_ptr)
|
||||
# A concrete integer, or a register with nothing useful tracked for it,
|
||||
|
||||
@@ -171,6 +171,19 @@ module OneGadget
|
||||
!branch_kind(line).nil? || call_line?(line)
|
||||
end
|
||||
|
||||
# What a libc calls the variable holding the environment a process started
|
||||
# with, allowing for the aliases exported beside it (+_environ+, +__environ+).
|
||||
ENVIRON = /\A_*environ\z/
|
||||
private_constant :ENVIRON
|
||||
|
||||
# Whether +str+ names the address of that variable. An architecture that can
|
||||
# say which symbol an address belongs to answers this; the rest cannot, and
|
||||
# recognise the environment by the shape of the pointer instead (see
|
||||
# {ArgumentResolution#check_envp}).
|
||||
# @param [String] _str A rendered value.
|
||||
# @return [Boolean]
|
||||
def environ?(_str) = false
|
||||
|
||||
# Whether +str+ references a libc global, i.e. an address the caller does not
|
||||
# choose. The default reads the +$base+-relative form an arch produces once it
|
||||
# concretizes a pc-relative operand; one that reaches its globals through a
|
||||
|
||||
@@ -24,8 +24,44 @@ module OneGadget
|
||||
# branch's target leaves from the delay slot rather than the branch
|
||||
# ({#branch_pred_map}).
|
||||
class Mips < Base
|
||||
# Everything this arch reaches -- its calls and its globals alike -- goes
|
||||
# through the GOT base in +gp+, which is a precondition the caller arranges
|
||||
# by setting that register. Say so, and drop the read/write requirements
|
||||
# rooted there: the GOT is a fixed, mapped libc address, so reaching through
|
||||
# it asks nothing further of the caller (as i386 does for its own GOT
|
||||
# register).
|
||||
# @param [OneGadget::Emulators::Processor] processor
|
||||
# @return [Hash, nil]
|
||||
def resolve(processor)
|
||||
res = super
|
||||
return if res.nil?
|
||||
|
||||
got = got_base_constraint(processor, GOT_BASE) or return nil
|
||||
|
||||
res[:constraints].unshift(got)
|
||||
res[:constraints].reject! { |con| con.match?(/\A(?:writable|readable): \[*#{GOT_BASE}\b/) }
|
||||
res
|
||||
end
|
||||
|
||||
# This arch reads a global through the GOT and resolves the slot to the
|
||||
# address it holds, so what reaches a call is one dereference of the variable
|
||||
# rather than two of the slot naming it. Which variable that is comes from
|
||||
# the symbols already read for the table.
|
||||
# @param [String] str A rendered value.
|
||||
# @return [Boolean]
|
||||
def environ?(str)
|
||||
got = mips_got or return false
|
||||
offset = string_file_offset(str.delete('[]')) or return false
|
||||
|
||||
ENVIRON.match?(got[:names][offset].to_s)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
# The register o32 states the GOT base in.
|
||||
GOT_BASE = 'gp'
|
||||
private_constant :GOT_BASE
|
||||
|
||||
# A call, however it is spelled: through a register (which is how PIC code
|
||||
# reaches everything) or directly.
|
||||
def call_str = '(?:jalr|jal|bal)'
|
||||
@@ -48,7 +84,7 @@ module OneGadget
|
||||
# Rewrite the disassembly into what the engine reads everywhere else: every
|
||||
# call named.
|
||||
def objdump_lines(start: nil, stop: nil, extra: [])
|
||||
name_got_calls(super)
|
||||
state_got_values(name_got_calls(super))
|
||||
end
|
||||
|
||||
# A window ends at the call that ends the gadget, but that call's delay slot
|
||||
@@ -114,6 +150,28 @@ module OneGadget
|
||||
end
|
||||
end
|
||||
|
||||
# A libc global is reached through the GOT as well: the slot holds the
|
||||
# address, and an +addiu+ applies the offset within it. What the slot holds
|
||||
# is in the file, so state it beside the load -- which is what lets the
|
||||
# value read as +$base+<off>+, the form every other architecture produces
|
||||
# for a global.
|
||||
# @param [Array<String>] lines
|
||||
# @return [Array<String>]
|
||||
# @example
|
||||
# 'lw a0,-32496(gp)' #=> 'lw a0,-32496(gp) # b0000'
|
||||
def state_got_values(lines)
|
||||
lines.map do |line|
|
||||
m = line.match(GOT_GLOBAL_LOAD) or next line
|
||||
|
||||
address = got_address(m[1].to_i)
|
||||
address.nil? || address.zero? ? line : "#{line} # #{format('%x', address)}"
|
||||
end
|
||||
end
|
||||
|
||||
# Loading anything but the call target out of the GOT.
|
||||
GOT_GLOBAL_LOAD = /:\s*lw\s+(?!t9,)\w+,(-?\d+)\(gp\)/
|
||||
private_constant :GOT_GLOBAL_LOAD
|
||||
|
||||
# Loading the call target out of the GOT, which is +gp+-relative.
|
||||
GOT_LOAD = /:\s*lw\s+t9,(-?\d+)\(gp\)/
|
||||
private_constant :GOT_LOAD
|
||||
@@ -132,20 +190,32 @@ module OneGadget
|
||||
TARGET_WRITE = /:\s*(?!s[whb]\b)\S+\s+t9,/
|
||||
private_constant :TARGET_WRITE
|
||||
|
||||
# The symbol a +gp+-relative GOT slot names, or +nil+ for one that names
|
||||
# nothing this can read. This arch states its GOT in the dynamic segment, so
|
||||
# the answer is there even for a file with no sections at all.
|
||||
# What a +gp+-relative GOT slot holds, as +[address, name]+, or +nil+ for one
|
||||
# this cannot read. This arch states its GOT in the dynamic segment, so the
|
||||
# answer is there even for a file with no sections at all. An entry below
|
||||
# +DT_MIPS_LOCAL_GOTNO+ holds the address outright; the rest correspond one
|
||||
# for one with the dynamic symbols.
|
||||
# @param [Integer] gp_offset The offset as the instruction writes it.
|
||||
# @return [String, nil]
|
||||
def got_symbol(gp_offset)
|
||||
# @return [(Integer, String), nil]
|
||||
def got_entry(gp_offset)
|
||||
got = mips_got or return nil
|
||||
|
||||
index = (GP_BIAS + gp_offset) / 4
|
||||
return got[:names][local_got_entry(got, index)] if index < got[:local]
|
||||
|
||||
got[:symbols][got[:gotsym] + index - got[:local]]&.name
|
||||
if index < got[:local]
|
||||
address = local_got_entry(got, index)
|
||||
return address && [address, got[:names][address]]
|
||||
end
|
||||
got[:symbols][got[:gotsym] + index - got[:local]]
|
||||
end
|
||||
|
||||
# @param [Integer] gp_offset
|
||||
# @return [String, nil] The symbol the slot names.
|
||||
def got_symbol(gp_offset) = got_entry(gp_offset)&.last
|
||||
|
||||
# @param [Integer] gp_offset
|
||||
# @return [Integer, nil] The address the slot holds.
|
||||
def got_address(gp_offset) = got_entry(gp_offset)&.first
|
||||
|
||||
# +gp+ points this far into the GOT, so that one signed 16-bit offset reaches
|
||||
# the most of it. Every +gp+-relative offset is read against it.
|
||||
GP_BIAS = 0x7ff0
|
||||
@@ -178,12 +248,12 @@ module OneGadget
|
||||
gotsym = dynamic.tag_by_type(:mips_gotsym)&.value or return nil
|
||||
segment = elf.segments_by_type(:load).find { |seg| seg.vma_in?(address) } or return nil
|
||||
|
||||
names = {}
|
||||
dynamic.symbols.each do |symbol|
|
||||
value = symbol.header.st_value.to_i
|
||||
names[value] = symbol.name unless value.zero? || symbol.name.empty?
|
||||
end
|
||||
{ local:, gotsym:, symbols: dynamic.symbols, names:, big: elf.endian == :big,
|
||||
# Read out of the symbols now, rather than holding them: they are lazy, and
|
||||
# the file they would read from is closed as soon as this returns.
|
||||
symbols = dynamic.symbols.map { |symbol| [symbol.header.st_value.to_i, symbol.name] }
|
||||
names = symbols.to_h { |value, name| [value, name] }
|
||||
.reject { |value, name| value.zero? || name.empty? }
|
||||
{ local:, gotsym:, symbols:, names:, big: elf.endian == :big,
|
||||
offset: segment.vma_to_offset(address) }
|
||||
end
|
||||
end
|
||||
|
||||
Executable
BIN
Binary file not shown.
Executable
BIN
Binary file not shown.
@@ -0,0 +1,59 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require 'one_gadget/one_gadget'
|
||||
|
||||
describe 'one_gadget_mips' do
|
||||
describe 'from file' do
|
||||
before(:each) do
|
||||
skip_unless_objdump
|
||||
end
|
||||
|
||||
# OpenWrt's musl, which is what a real router runs. Both byte orders are in
|
||||
# wide use on this architecture -- ath79 is big-endian, ramips little -- so
|
||||
# both are read here.
|
||||
it 'musl 1.2.4, big-endian' do
|
||||
path = data_path('mips-musl-1.2.4.so')
|
||||
expect(OneGadget.gadgets(file: path, force_file: true)).to eq [0x56fd4, 0x77b44, 0x77b48]
|
||||
expect(OneGadget.gadgets(file: path, force_file: true, level: 1)).to eq [0x56fd4, 0x77b44, 0x77b48]
|
||||
end
|
||||
|
||||
it 'musl 1.2.4, little-endian' do
|
||||
path = data_path('mipsel-musl-1.2.4.so')
|
||||
expect(OneGadget.gadgets(file: path, force_file: true)).to eq [0x56f98, 0x77b00, 0x77b04]
|
||||
expect(OneGadget.gadgets(file: path, force_file: true, level: 1)).to eq [0x56f98, 0x77b00, 0x77b04]
|
||||
end
|
||||
|
||||
it 'libc-2.36' do
|
||||
path = data_path('mipsel-libc-2.36.so')
|
||||
expect(OneGadget.gadgets(file: path, force_file: true)).to eq [0x4b42c, 0x4b440]
|
||||
expect(OneGadget.gadgets(file: path, force_file: true, level: 1))
|
||||
.to eq [0x4b3d8, 0x4b424, 0x4b42c, 0x4b440, 0x722d0, 0x722d8, 0x187aa8, 0x187aac]
|
||||
end
|
||||
|
||||
# Everything this arch reaches goes through the GOT base register, including
|
||||
# the "/bin/sh" it passes: the table holds the address and the instruction
|
||||
# after the load applies the offset within it. So the caller has to have set
|
||||
# that register, exactly as i386 must set its own.
|
||||
it 'requires the GOT base register the whole architecture reads through' do
|
||||
gadget = OneGadget.gadgets(file: data_path('mipsel-libc-2.36.so'), force_file: true, details: true, level: 1)
|
||||
.find { |g| g.offset == 0x4b440 }
|
||||
expect(gadget.effect).to eq 'posix_spawn(a0, "/bin/sh", a2, a3, v1, environ)'
|
||||
expect(gadget.constraints).to include 'gp is the GOT address of libc'
|
||||
end
|
||||
|
||||
# Resolving the GOT slot means the environment arrives as one dereference of
|
||||
# the variable rather than two of the slot naming it, so it is recognised by
|
||||
# which variable it is -- otherwise the gadget would carry a constraint asking
|
||||
# the caller to arrange what is already environ.
|
||||
it 'names the environment it passes' do
|
||||
gadget = OneGadget.gadgets(file: data_path('mips-musl-1.2.4.so'), force_file: true, details: true, level: 1)
|
||||
.find { |g| g.offset == 0x77b44 }
|
||||
expect(gadget.effect).to end_with 'environ)'
|
||||
expect(gadget.constraints.join).not_to include 'valid envp'
|
||||
end
|
||||
|
||||
it 'reports the same gadgets for a libc with no section headers' do
|
||||
expect_same_gadgets_when_stripped('mipsel-libc-2.36.so')
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -21,6 +21,15 @@ UBUNTU_MIRRORS = {
|
||||
# A glibc security fix is published to one pocket or the other, so ask both and
|
||||
# keep whichever answer is newer.
|
||||
UBUNTU_POCKETS = %w[-updates -security].freeze
|
||||
# Fixtures that state no BuildID, and so can never become a shipped entry: they
|
||||
# are built without the note segment that carries one, as OpenWrt builds are.
|
||||
# They are here to be searched, not to be shipped, so a refresh passes over them
|
||||
# rather than reporting that it could not read them. Named rather than detected,
|
||||
# so a fixture that unexpectedly lost its BuildID still says so.
|
||||
NO_BUILD_ID = %w[
|
||||
mips-musl-1.2.4.so
|
||||
mipsel-musl-1.2.4.so
|
||||
].freeze
|
||||
|
||||
namespace :builds do
|
||||
desc 'Regenerates the shipped build database from Ubuntu LTS libcs and the spec fixtures'
|
||||
@@ -71,8 +80,11 @@ end
|
||||
|
||||
def refresh_fixtures
|
||||
Dir.glob(File.join(repo_root, 'spec', 'data', '*.so')).filter_map do |libc|
|
||||
print "[spec] #{File.basename(libc)} .. "
|
||||
regenerate(libc, File.join('spec', 'data', File.basename(libc)))
|
||||
name = File.basename(libc)
|
||||
next puts("[spec] #{name} .. states no BuildID, not shipped") if NO_BUILD_ID.include?(name)
|
||||
|
||||
print "[spec] #{name} .. "
|
||||
regenerate(libc, File.join('spec', 'data', name))
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
Reference in New Issue
Block a user