ropnroll 0.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
ropnroll/__init__.py ADDED
File without changes
File without changes
ropnroll/cli/main.py ADDED
@@ -0,0 +1,356 @@
1
+ from __future__ import annotations
2
+
3
+ import argparse
4
+ import sys
5
+
6
+ from rich.console import Console
7
+ from rich.markup import escape
8
+ from rich.table import Table
9
+
10
+ from .. import libcdb
11
+ from ..core import loader, output, scanner, security
12
+ from ..core.gadget import Terminator
13
+ from ..solve import callchain, chain as chainmod, jop, onegadget, pivot, srop, syscallchain
14
+ from ..solve.pool import GadgetPool
15
+ from ..semantics import query as querymod
16
+ from ..verify.emulate import verify_chain
17
+
18
+ console = Console()
19
+
20
+
21
+ def _load_pool(paths: list[str], scan_opts: scanner.ScanOptions) -> tuple[GadgetPool, list]:
22
+ pool = GadgetPool()
23
+ images = []
24
+ for p in paths:
25
+ img = loader.load(p)
26
+ gs = scanner.scan_image(img, scan_opts)
27
+ pool.add(img, gs)
28
+ images.append(img)
29
+ return pool, images
30
+
31
+
32
+ def _scan_opts(args) -> scanner.ScanOptions:
33
+ bad = bytes.fromhex(args.bad_bytes) if getattr(args, "bad_bytes", None) else b""
34
+ return scanner.ScanOptions(
35
+ max_insns=args.max_insns, rop=not args.no_rop, jop=not args.no_jop,
36
+ sys=not args.no_sys, bad_bytes=bad,
37
+ )
38
+
39
+
40
+ def cmd_scan(args):
41
+ pool, images = _load_pool([args.binary], _scan_opts(args))
42
+ gadgets = pool.all()
43
+ if args.regex:
44
+ import re
45
+ rx = re.compile(args.regex)
46
+ gadgets = [g for g in gadgets if rx.search(g.text)]
47
+ gadgets.sort(key=lambda g: g.address)
48
+ if args.out:
49
+ with open(args.out, "w") as f:
50
+ for g in gadgets:
51
+ f.write(f"0x{g.address:016x} : {g.text}\n")
52
+ console.print(f"[green]wrote {len(gadgets)} gadgets to {args.out}[/green]")
53
+ return
54
+ for g in gadgets[: args.limit]:
55
+ console.print(f"0x{g.address:016x} : {escape(g.text)}")
56
+ console.print(f"[bold]{len(gadgets)}[/bold] gadgets total"
57
+ + (f" (showing first {args.limit})" if len(gadgets) > args.limit else ""))
58
+
59
+
60
+ def cmd_security(args):
61
+ img = loader.load(args.binary)
62
+ opts = _scan_opts(args)
63
+ gs = scanner.scan_image(img, opts)
64
+ report = security.build_report(img, gs)
65
+ t = Table(title=f"security report: {args.binary}")
66
+ t.add_column("property")
67
+ t.add_column("value")
68
+ for k, v in report.lines:
69
+ t.add_row(k, v)
70
+ console.print(t)
71
+
72
+
73
+ def cmd_search(args):
74
+ pool, images = _load_pool(args.binary, _scan_opts(args))
75
+ try:
76
+ results = querymod.search(pool, args.query, limit=args.limit)
77
+ except ValueError as e:
78
+ console.print(f"[red]{e}[/red]")
79
+ sys.exit(1)
80
+ if not results:
81
+ console.print("[yellow]no gadgets matched[/yellow]")
82
+ return
83
+ for g, eff, field in results:
84
+ console.print(f"0x{g.address:016x} : {escape(g.text)} [dim]sp_delta={eff.sp_delta}[/dim]")
85
+
86
+
87
+ def cmd_pivot(args):
88
+ pool, images = _load_pool(args.binary, _scan_opts(args))
89
+ pivots = pivot.find_pivots(pool)
90
+ t = Table(title="stack pivots")
91
+ t.add_column("address")
92
+ t.add_column("gadget")
93
+ t.add_column("kind")
94
+ for p in pivots[: args.limit]:
95
+ t.add_row(f"0x{p.gadget.address:x}", escape(p.gadget.text), p.kind)
96
+ console.print(t)
97
+
98
+
99
+ def cmd_jop(args):
100
+ pool, images = _load_pool(args.binary, _scan_opts(args))
101
+ disp = jop.find_dispatchers(pool, img=images[0])
102
+ t = Table(title="JOP dispatcher gadgets (self-advancing jmp/call-through-register)")
103
+ t.add_column("address")
104
+ t.add_column("gadget")
105
+ t.add_column("reg")
106
+ t.add_column("advance")
107
+ for d in disp[: args.limit]:
108
+ t.add_row(f"0x{d.gadget.address:x}", escape(d.gadget.text), d.reg, f"0x{d.advance:x}")
109
+ console.print(t)
110
+ console.print(f"[bold]{len(disp)}[/bold] dispatcher(s) found")
111
+
112
+
113
+ def cmd_onegadget(args):
114
+ img = loader.load(args.binary)
115
+ if img.arch not in ("x86_64", "x86"):
116
+ console.print(f"[red]one-gadget search is scoped to x86/x86-64 (target is {img.arch})[/red]")
117
+ sys.exit(1)
118
+ console.print("[dim]searching for shell-string references and emulating forward "
119
+ "from each one -- this concretely runs the real code, so it can take "
120
+ "a few seconds...[/dim]")
121
+ rep = onegadget.find_one_gadgets(img, max_insns=args.max_insns)
122
+ t = Table(title="one-gadgets (empirically confirmed via emulation, not a curated database)")
123
+ t.add_column("address")
124
+ t.add_column("string")
125
+ t.add_column("starting state that worked")
126
+ t.add_column("instructions run")
127
+ for c in rep.candidates:
128
+ t.add_row(f"0x{c.address:x}", c.string.decode(), c.constraint, str(c.instructions_run))
129
+ console.print(t)
130
+ console.print(f"searched {rep.searched_sites} shell-string reference site(s), "
131
+ f"[bold]{len(rep.candidates)}[/bold] confirmed")
132
+
133
+
134
+ def cmd_libcid(args):
135
+ offsets = {}
136
+ for item in args.symbol:
137
+ name, _, off = item.partition("=")
138
+ if not off:
139
+ console.print(f"[red]expected name=0xoffset, got {item!r}[/red]")
140
+ sys.exit(1)
141
+ offsets[name] = int(off, 0)
142
+ try:
143
+ matches = libcdb.identify(offsets)
144
+ except ConnectionError as e:
145
+ console.print(f"[red]{e}[/red]")
146
+ sys.exit(1)
147
+ if not matches:
148
+ console.print("[yellow]no matching libc build found[/yellow]")
149
+ return
150
+ for m in matches:
151
+ console.print(f"[bold]{m.id}[/bold] buildid={m.buildid}")
152
+ console.print(f" download: {m.download_url}")
153
+ for k, v in sorted(m.symbols.items()):
154
+ console.print(f" {k} = 0x{v:x}")
155
+ if args.download:
156
+ if len(matches) > 1:
157
+ console.print(f"[yellow]{len(matches)} matches -- downloading the first ({matches[0].id})[/yellow]")
158
+ path = libcdb.download(matches[0], args.download)
159
+ console.print(f"[green]saved to {path}[/green]")
160
+
161
+
162
+ def cmd_srop(args):
163
+ pool, images = _load_pool(args.binary, _scan_opts(args))
164
+ if pool.ai.arch != "x86_64":
165
+ console.print("[red]SROP support here is scoped to Linux x86-64[/red]")
166
+ sys.exit(1)
167
+ args_list = [int(a, 0) for a in args.args.split(",")] if args.args else [0, 0, 0]
168
+ while len(args_list) < 3:
169
+ args_list.append(0)
170
+ res = srop.build_srop_execve(pool, path_ptr=args_list[0], argv_ptr=args_list[1], envp_ptr=args_list[2])
171
+ for l in res.solve.log:
172
+ console.print(f"[dim]{l}[/dim]")
173
+ if not res.ok:
174
+ console.print(f"[red]could not build SROP chain: {res.solve.unresolved}[/red]")
175
+ sys.exit(1)
176
+ console.print(output.stack_layout(res.chain), markup=False)
177
+ expect = {"rax": 59, "rdi": args_list[0], "rsi": args_list[1], "rdx": args_list[2]}
178
+ consistent = srop.verify_srop_frame(res.chain, res.frame_offset_words, expect)
179
+ console.print(f"\nframe self-consistency check: {'[green]OK[/green]' if consistent else '[red]FAIL[/red]'}")
180
+ if args.verify:
181
+ ok, result = srop.emulate_srop_chain(images[0], res.chain, expect)
182
+ console.print(f"emulated (simulated sigreturn semantics -- see srop.py docstring): "
183
+ f"{'[green]OK[/green]' if ok else '[red]FAIL[/red]'}")
184
+ if args.emit:
185
+ _emit(res.chain, args.emit, args.out)
186
+
187
+
188
+ def _emit(chain, fmt: str, out: str | None):
189
+ if fmt == "pwntools":
190
+ text = output.to_pwntools(chain)
191
+ elif fmt == "c":
192
+ text = output.to_c_array(chain)
193
+ elif fmt == "json":
194
+ text = output.to_json(chain)
195
+ elif fmt == "raw":
196
+ data = output.to_raw(chain)
197
+ if out:
198
+ with open(out, "wb") as f:
199
+ f.write(data)
200
+ console.print(f"[green]wrote {len(data)} raw bytes to {out}[/green]")
201
+ else:
202
+ sys.stdout.buffer.write(data)
203
+ return
204
+ else:
205
+ raise ValueError(fmt)
206
+ if out:
207
+ with open(out, "w") as f:
208
+ f.write(text)
209
+ console.print(f"[green]wrote to {out}[/green]")
210
+ else:
211
+ console.print(text, markup=False)
212
+
213
+
214
+ def _do_verify(images, chain, final_target, goal_regs):
215
+ rep = verify_chain(images[0], chain, final_target=final_target, goal_regs=goal_regs)
216
+ console.print(f"\n[bold]-- concrete verification (Unicorn) --[/bold]")
217
+ console.print(f"reached target: {rep.reached_target} fault: {rep.fault} "
218
+ f"insns executed: {rep.instructions_executed}")
219
+ for reg, (ok, want, got) in rep.goal_results.items():
220
+ mark = "[green]OK[/green]" if ok else "[red]MISMATCH[/red]"
221
+ console.print(f" {reg}: expected 0x{want:x}, got "
222
+ f"{'0x%x' % got if got is not None else '?'} {mark}")
223
+ if rep.fault:
224
+ console.print(f"[red]fault at 0x{rep.fault_address:x} near: {rep.last_gadget_context}[/red]")
225
+ console.print(f"[bold]{'PASS' if rep.ok else 'FAIL'}[/bold]")
226
+
227
+
228
+ def cmd_call(args):
229
+ pool, images = _load_pool(args.binary, _scan_opts(args))
230
+ target = int(args.target, 0) if args.target.startswith("0x") or args.target.isdigit() else None
231
+ if target is None:
232
+ for img in images:
233
+ if args.target in img.symbols:
234
+ target = img.symbols[args.target]
235
+ break
236
+ if target is None:
237
+ console.print(f"[red]symbol {args.target!r} not found[/red]")
238
+ sys.exit(1)
239
+ args_list = [int(a, 0) for a in args.args.split(",")] if args.args else []
240
+ res = callchain.build_call(pool, target, args_list)
241
+ for l in res.solve.log:
242
+ console.print(f"[dim]{l}[/dim]")
243
+ if not res.ok:
244
+ console.print(f"[red]could not build call chain; unresolved: {res.solve.unresolved}[/red]")
245
+ sys.exit(1)
246
+ console.print(output.stack_layout(res.chain), markup=False)
247
+ if args.emit:
248
+ _emit(res.chain, args.emit, args.out)
249
+ if args.verify:
250
+ goal = {r: v for r, v in zip(pool.ai.call_arg_regs, args_list)}
251
+ _do_verify(images, res.chain, target, goal)
252
+
253
+
254
+ def cmd_syscall(args):
255
+ pool, images = _load_pool(args.binary, _scan_opts(args))
256
+ args_list = [int(a, 0) for a in args.args.split(",")] if args.args else []
257
+ res = syscallchain.build_syscall(pool, args.nr, args_list)
258
+ for l in res.solve.log:
259
+ console.print(f"[dim]{l}[/dim]")
260
+ if not res.ok:
261
+ console.print(f"[red]could not build syscall chain; unresolved: {res.solve.unresolved}[/red]")
262
+ sys.exit(1)
263
+ console.print(output.stack_layout(res.chain), markup=False)
264
+ if args.emit:
265
+ _emit(res.chain, args.emit, args.out)
266
+ if args.verify:
267
+ _do_verify(images, res.chain, res.gadget_addr, {})
268
+
269
+
270
+ def build_parser() -> argparse.ArgumentParser:
271
+ p = argparse.ArgumentParser(prog="ropnroll", description="ROP/JOP gadget finder and chain synthesizer")
272
+ common = argparse.ArgumentParser(add_help=False)
273
+ common.add_argument("--max-insns", type=int, default=6)
274
+ common.add_argument("--no-rop", action="store_true")
275
+ common.add_argument("--no-jop", action="store_true")
276
+ common.add_argument("--no-sys", action="store_true")
277
+ common.add_argument("--bad-bytes", help="hex string, e.g. 000a0d")
278
+
279
+ sub = p.add_subparsers(dest="cmd", required=True)
280
+
281
+ s = sub.add_parser("scan", parents=[common])
282
+ s.add_argument("binary")
283
+ s.add_argument("--regex")
284
+ s.add_argument("--limit", type=int, default=50)
285
+ s.add_argument("--out")
286
+ s.set_defaults(func=cmd_scan)
287
+
288
+ s = sub.add_parser("security", parents=[common])
289
+ s.add_argument("binary")
290
+ s.set_defaults(func=cmd_security)
291
+
292
+ s = sub.add_parser("search", parents=[common])
293
+ s.add_argument("binary", nargs="+")
294
+ s.add_argument("--query", required=True, help='e.g. "rdi=rax+8", "[rbx]=rax", "rax=0"')
295
+ s.add_argument("--limit", type=int, default=20)
296
+ s.set_defaults(func=cmd_search)
297
+
298
+ s = sub.add_parser("pivot", parents=[common])
299
+ s.add_argument("binary", nargs="+")
300
+ s.add_argument("--limit", type=int, default=30)
301
+ s.set_defaults(func=cmd_pivot)
302
+
303
+ s = sub.add_parser("jop", parents=[common])
304
+ s.add_argument("binary", nargs="+")
305
+ s.add_argument("--limit", type=int, default=30)
306
+ s.set_defaults(func=cmd_jop)
307
+
308
+ s = sub.add_parser("onegadget")
309
+ s.add_argument("binary")
310
+ s.add_argument("--max-insns", type=int, default=2000,
311
+ help="instruction budget per candidate site during emulation")
312
+ s.set_defaults(func=cmd_onegadget)
313
+
314
+ s = sub.add_parser("srop", parents=[common], help="build a sigreturn-oriented execve chain")
315
+ s.add_argument("binary", nargs="+")
316
+ s.add_argument("--args", default="", help="path_ptr,argv_ptr,envp_ptr (comma-separated)")
317
+ s.add_argument("--emit", choices=["pwntools", "raw", "c", "json"])
318
+ s.add_argument("--out")
319
+ s.add_argument("--verify", action="store_true")
320
+ s.set_defaults(func=cmd_srop)
321
+
322
+ s = sub.add_parser("libcid", help="identify a libc build from leaked symbol offsets via libc.rip")
323
+ s.add_argument("--symbol", action="append", required=True,
324
+ help="name=0xoffset (offset from the leaked libc's own base), repeatable")
325
+ s.add_argument("--download", metavar="PATH", help="save the (first) matched libc here")
326
+ s.set_defaults(func=cmd_libcid)
327
+
328
+ s = sub.add_parser("call", parents=[common])
329
+ s.add_argument("binary", nargs="+")
330
+ s.add_argument("--target", required=True, help="address (0x...) or symbol name")
331
+ s.add_argument("--args", default="", help="comma-separated integers, e.g. 0x1000,0,0")
332
+ s.add_argument("--emit", choices=["pwntools", "raw", "c", "json"])
333
+ s.add_argument("--out")
334
+ s.add_argument("--verify", action="store_true")
335
+ s.set_defaults(func=cmd_call)
336
+
337
+ s = sub.add_parser("syscall", parents=[common])
338
+ s.add_argument("binary", nargs="+")
339
+ s.add_argument("--nr", type=int, required=True)
340
+ s.add_argument("--args", default="")
341
+ s.add_argument("--emit", choices=["pwntools", "raw", "c", "json"])
342
+ s.add_argument("--out")
343
+ s.add_argument("--verify", action="store_true")
344
+ s.set_defaults(func=cmd_syscall)
345
+
346
+ return p
347
+
348
+
349
+ def main(argv=None):
350
+ parser = build_parser()
351
+ args = parser.parse_args(argv)
352
+ args.func(args)
353
+
354
+
355
+ if __name__ == "__main__":
356
+ main()
File without changes
@@ -0,0 +1,145 @@
1
+ """Per-architecture Capstone/Unicorn wiring and register tables."""
2
+ from __future__ import annotations
3
+
4
+ from dataclasses import dataclass
5
+
6
+ import capstone as cs
7
+ import unicorn as uc
8
+ import unicorn.x86_const as ux86
9
+ import unicorn.arm_const as uarm
10
+ import unicorn.arm64_const as uarm64
11
+ import unicorn.mips_const as umips
12
+
13
+ from .loader import ARCH_X86, ARCH_X86_64, ARCH_ARM, ARCH_ARM64, ARCH_MIPS, ARCH_MIPS64
14
+
15
+
16
+ @dataclass
17
+ class ArchInfo:
18
+ arch: str
19
+ bits: int
20
+ cs_arch: int
21
+ cs_mode: int
22
+ uc_arch: int
23
+ uc_mode: int
24
+ sp_reg: str
25
+ ip_reg: str
26
+ gpr: list[str] # general purpose regs usable for semantic fitting
27
+ call_arg_regs: list[str] # argument-passing registers, in order (empty => stack-only)
28
+ ret_reg: str
29
+ reg_width: int # bytes
30
+ uc_reg_const: dict[str, int] # name -> unicorn constant
31
+ max_gadget_bytes: int
32
+ max_gadget_insns: int
33
+ insn_alignment: int # 1 for x86 (byte-granular), 4 for arm64/mips, 2 for thumb/arm-mixed
34
+
35
+
36
+ def _uc_x86_regmap():
37
+ return {
38
+ "rax": ux86.UC_X86_REG_RAX, "rbx": ux86.UC_X86_REG_RBX, "rcx": ux86.UC_X86_REG_RCX,
39
+ "rdx": ux86.UC_X86_REG_RDX, "rsi": ux86.UC_X86_REG_RSI, "rdi": ux86.UC_X86_REG_RDI,
40
+ "rbp": ux86.UC_X86_REG_RBP, "rsp": ux86.UC_X86_REG_RSP,
41
+ "r8": ux86.UC_X86_REG_R8, "r9": ux86.UC_X86_REG_R9, "r10": ux86.UC_X86_REG_R10,
42
+ "r11": ux86.UC_X86_REG_R11, "r12": ux86.UC_X86_REG_R12, "r13": ux86.UC_X86_REG_R13,
43
+ "r14": ux86.UC_X86_REG_R14, "r15": ux86.UC_X86_REG_R15, "rip": ux86.UC_X86_REG_RIP,
44
+ "eflags": ux86.UC_X86_REG_EFLAGS,
45
+ }
46
+
47
+
48
+ def _uc_x86_32_regmap():
49
+ return {
50
+ "eax": ux86.UC_X86_REG_EAX, "ebx": ux86.UC_X86_REG_EBX, "ecx": ux86.UC_X86_REG_ECX,
51
+ "edx": ux86.UC_X86_REG_EDX, "esi": ux86.UC_X86_REG_ESI, "edi": ux86.UC_X86_REG_EDI,
52
+ "ebp": ux86.UC_X86_REG_EBP, "esp": ux86.UC_X86_REG_ESP, "eip": ux86.UC_X86_REG_EIP,
53
+ "eflags": ux86.UC_X86_REG_EFLAGS,
54
+ }
55
+
56
+
57
+ def _uc_arm_regmap():
58
+ m = {f"r{i}": getattr(uarm, f"UC_ARM_REG_R{i}") for i in range(13)}
59
+ m.update({"sp": uarm.UC_ARM_REG_SP, "lr": uarm.UC_ARM_REG_LR, "pc": uarm.UC_ARM_REG_PC,
60
+ "cpsr": uarm.UC_ARM_REG_CPSR})
61
+ return m
62
+
63
+
64
+ def _uc_arm64_regmap():
65
+ m = {f"x{i}": getattr(uarm64, f"UC_ARM64_REG_X{i}") for i in range(29)}
66
+ m.update({"sp": uarm64.UC_ARM64_REG_SP, "x29": uarm64.UC_ARM64_REG_X29,
67
+ "x30": uarm64.UC_ARM64_REG_X30, "lr": uarm64.UC_ARM64_REG_X30,
68
+ "pc": uarm64.UC_ARM64_REG_PC})
69
+ return m
70
+
71
+
72
+ def _uc_mips_regmap():
73
+ names = ["zero", "at", "v0", "v1", "a0", "a1", "a2", "a3",
74
+ "t0", "t1", "t2", "t3", "t4", "t5", "t6", "t7",
75
+ "s0", "s1", "s2", "s3", "s4", "s5", "s6", "s7",
76
+ "t8", "t9", "k0", "k1", "gp", "sp", "fp", "ra"]
77
+ m = {}
78
+ for n in names:
79
+ const = getattr(umips, f"UC_MIPS_REG_{n.upper()}", None)
80
+ if const is not None:
81
+ m[n] = const
82
+ m["pc"] = umips.UC_MIPS_REG_PC
83
+ return m
84
+
85
+
86
+ def get_archinfo(arch: str, little_endian: bool = True) -> ArchInfo:
87
+ if arch == ARCH_X86_64:
88
+ return ArchInfo(
89
+ arch=arch, bits=64, cs_arch=cs.CS_ARCH_X86, cs_mode=cs.CS_MODE_64,
90
+ uc_arch=uc.UC_ARCH_X86, uc_mode=uc.UC_MODE_64,
91
+ sp_reg="rsp", ip_reg="rip",
92
+ gpr=["rax", "rbx", "rcx", "rdx", "rsi", "rdi", "rbp",
93
+ "r8", "r9", "r10", "r11", "r12", "r13", "r14", "r15"],
94
+ call_arg_regs=["rdi", "rsi", "rdx", "rcx", "r8", "r9"],
95
+ ret_reg="rax", reg_width=8, uc_reg_const=_uc_x86_regmap(),
96
+ max_gadget_bytes=24, max_gadget_insns=6, insn_alignment=1,
97
+ )
98
+ if arch == ARCH_X86:
99
+ return ArchInfo(
100
+ arch=arch, bits=32, cs_arch=cs.CS_ARCH_X86, cs_mode=cs.CS_MODE_32,
101
+ uc_arch=uc.UC_ARCH_X86, uc_mode=uc.UC_MODE_32,
102
+ sp_reg="esp", ip_reg="eip",
103
+ gpr=["eax", "ebx", "ecx", "edx", "esi", "edi", "ebp"],
104
+ call_arg_regs=[], # cdecl/stdcall: stack-based
105
+ ret_reg="eax", reg_width=4, uc_reg_const=_uc_x86_32_regmap(),
106
+ max_gadget_bytes=20, max_gadget_insns=6, insn_alignment=1,
107
+ )
108
+ if arch == ARCH_ARM:
109
+ mode = cs.CS_MODE_ARM if little_endian else (cs.CS_MODE_ARM | cs.CS_MODE_BIG_ENDIAN)
110
+ return ArchInfo(
111
+ arch=arch, bits=32, cs_arch=cs.CS_ARCH_ARM, cs_mode=mode,
112
+ uc_arch=uc.UC_ARCH_ARM, uc_mode=uc.UC_MODE_ARM,
113
+ sp_reg="sp", ip_reg="pc",
114
+ gpr=[f"r{i}" for i in range(13)],
115
+ call_arg_regs=["r0", "r1", "r2", "r3"],
116
+ ret_reg="r0", reg_width=4, uc_reg_const=_uc_arm_regmap(),
117
+ max_gadget_bytes=24, max_gadget_insns=6, insn_alignment=4,
118
+ )
119
+ if arch == ARCH_ARM64:
120
+ return ArchInfo(
121
+ arch=arch, bits=64, cs_arch=cs.CS_ARCH_ARM64, cs_mode=cs.CS_MODE_ARM,
122
+ uc_arch=uc.UC_ARCH_ARM64, uc_mode=uc.UC_MODE_ARM,
123
+ sp_reg="sp", ip_reg="pc",
124
+ gpr=[f"x{i}" for i in range(29)],
125
+ call_arg_regs=["x0", "x1", "x2", "x3", "x4", "x5", "x6", "x7"],
126
+ ret_reg="x0", reg_width=8, uc_reg_const=_uc_arm64_regmap(),
127
+ max_gadget_bytes=32, max_gadget_insns=6, insn_alignment=4,
128
+ )
129
+ if arch in (ARCH_MIPS, ARCH_MIPS64):
130
+ bits = 64 if arch == ARCH_MIPS64 else 32
131
+ cs_mode = (cs.CS_MODE_MIPS64 if bits == 64 else cs.CS_MODE_MIPS32)
132
+ cs_mode |= (cs.CS_MODE_LITTLE_ENDIAN if little_endian else cs.CS_MODE_BIG_ENDIAN)
133
+ uc_mode = (uc.UC_MODE_MIPS64 if bits == 64 else uc.UC_MODE_MIPS32)
134
+ uc_mode |= (uc.UC_MODE_LITTLE_ENDIAN if little_endian else uc.UC_MODE_BIG_ENDIAN)
135
+ return ArchInfo(
136
+ arch=arch, bits=bits, cs_arch=cs.CS_ARCH_MIPS, cs_mode=cs_mode,
137
+ uc_arch=uc.UC_ARCH_MIPS, uc_mode=uc_mode,
138
+ sp_reg="sp", ip_reg="pc",
139
+ gpr=["v0", "v1", "a0", "a1", "a2", "a3", "t0", "t1", "t2", "t3",
140
+ "t4", "t5", "t6", "t7", "s0", "s1", "s2", "s3", "s4", "s5", "s6", "s7", "t8", "t9"],
141
+ call_arg_regs=["a0", "a1", "a2", "a3"],
142
+ ret_reg="v0", reg_width=8 if bits == 64 else 4, uc_reg_const=_uc_mips_regmap(),
143
+ max_gadget_bytes=32, max_gadget_insns=8, insn_alignment=4,
144
+ )
145
+ raise ValueError(f"no ArchInfo for arch={arch!r}")
@@ -0,0 +1,41 @@
1
+ """Core gadget representation."""
2
+ from __future__ import annotations
3
+
4
+ from dataclasses import dataclass, field
5
+ from enum import Enum, auto
6
+
7
+
8
+ class Terminator(Enum):
9
+ RET = auto()
10
+ RET_IMM = auto()
11
+ JMP_REG = auto()
12
+ CALL_REG = auto()
13
+ JMP_MEM = auto()
14
+ CALL_MEM = auto()
15
+ SYSCALL = auto()
16
+ INT80 = auto()
17
+ OTHER = auto()
18
+
19
+
20
+ @dataclass
21
+ class Gadget:
22
+ address: int
23
+ raw: bytes
24
+ text: str # "pop rdi ; ret"
25
+ insns: list # list of capstone CsInsn (kept for semantic lifting)
26
+ terminator: Terminator
27
+ module: str = "" # which Image/module this came from (for multi-binary pools)
28
+
29
+ @property
30
+ def size(self) -> int:
31
+ return len(self.raw)
32
+
33
+ @property
34
+ def n_insns(self) -> int:
35
+ return len(self.insns)
36
+
37
+ def __hash__(self):
38
+ return hash((self.module, self.address))
39
+
40
+ def __repr__(self):
41
+ return f"<Gadget {self.module + '!' if self.module else ''}0x{self.address:x}: {self.text}>"