1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
|
"""green-eye: Frida-like runtime instrumentation for GSP/uproc firmware in a
RISC-V QEMU VM.
green-eye talks to QEMU's gdbstub directly (no GDB dependency) and layers a
scriptable instrumentation API on top: breakpoints, hooks, watchpoints,
memory inspection, structure decoding, session recording and post-run analysis.
Public API (stable-ish for v0.1):
import greeneye as ge
s = ge.launch(kernel="firmware/uproc_demo/build/uproc_demo.elf")
fn = s.symbols.lookup("rpc_sum")
h = s.hook(fn, on_enter=lambda hit: print(hit))
s.run(duration=2.0)
s.close()
Everything is development tooling; see docs/00-OVERVIEW.md.
"""
from __future__ import annotations
__version__ = "0.1.0"
from .errors import ( # noqa: F401
GreenEyeError,
RspError,
RspTimeout,
RspProtocolError,
SymbolNotFound,
TargetError,
StructError,
)
from .rsp import RspClient, StopReply, Register # noqa: F401
from .elf import FirmwareImage, Segment, ElfSymbol, load_elf # noqa: F401
from .symbols import Symbol, SymbolDB # noqa: F401
from .memory import Memory # noqa: F401
from .structs import Field, Schema, decode, schema_from_json # noqa: F401
from .record import Recorder, NullRecorder, load_events # noqa: F401
from .session import Session, Hit, HookHandle, Breakpoint, Watchpoint # noqa: F401
from .target import QemuTarget # noqa: F401
__all__ = [
"attach",
"launch",
"Session",
"Hit",
"HookHandle",
"Breakpoint",
"Watchpoint",
"QemuTarget",
"RspClient",
"SymbolDB",
"Symbol",
"Memory",
"Recorder",
]
def attach(
host: str = "127.0.0.1",
port: int = 4444,
*,
timeout: float = 10.0,
record: str | None = None,
symbols: SymbolDB | None = None,
name: str = "attach",
) -> Session:
"""Attach to an already-running QEMU gdbstub and return a Session."""
client = RspClient(host, port, timeout=timeout)
client.connect()
client.sync()
return Session(client=client, symbols=symbols, record=record, name=name)
def launch(
*,
kernel: str | None = None,
machine: str = "virt",
memory_mb: int = 256,
gdb_port: int = 0,
bios: str = "none",
extra_args: list[str] | None = None,
timeout: float = 10.0,
record: str | None = None,
symbols: SymbolDB | None = None,
name: str = "session",
load_symbols: bool = True,
keep_alive: bool = False,
) -> Session:
"""Launch QEMU (paused), attach, and return a ready Session.
The target starts paused at its reset vector; use Session.load_image() to
load an ELF (which also sets PC), or QemuTarget.run_kernel() style helper
launch with kernel= for the fast `-kernel` path.
"""
target = QemuTarget(
machine=machine,
memory_mb=memory_mb,
gdb_port=gdb_port,
bios=bios,
extra_args=extra_args,
kernel=kernel,
)
if keep_alive:
target.keep_alive = True
target.start(paused=True)
client = RspClient(target.host, target.port, timeout=timeout)
client.connect()
client.sync()
symdb = symbols or SymbolDB()
sess = Session(client=client, target=target, symbols=symdb, record=record, name=name)
if kernel:
image = load_elf(kernel)
sess.load_image(image, load_symbols=load_symbols)
return sess
|