summaryrefslogtreecommitdiffstats
path: root/debug/risc-v-gsp-rm-vm/ghidra/GreenEyeControl.java
blob: abeee82f094fe16d266fe9d3f059674bc22810e1 (plain)
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
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
// GreenEyeControl - drive a live green-eye session from Ghidra.
//
// The script connects to the green-eye JSON-lines bridge (start it with
// `./greeneye.sh ghidra [--kernel <elf>]`), and exposes the operations a
// reverse engineer needs while reading the decompiler:
//
//   ping()               - check the bridge
//   info()               - session name, pc, symbol count, recording path
//   resolve(name)        - symbol/expression -> runtime address
//   setBreakpointHere()  - software breakpoint at currentAddress
//   setBreakpointAt(addr) / clearBreakpointAt(addr)
//   runUntilHere()       - resume and stop when execution reaches currentAddress
//   runUntil(addr)       - same for an explicit address (hex string)
//   readMemoryHere(size) - hex bytes at currentAddress
//   readRegister(name) / writeRegister(name, value)
//   callFunction(addr, args) - Frida-like remote call; returns a0
//   snapshotHere(size)   - record a memory snapshot in the session
//   listHits()           - breakpoint hit counts from the session
//   exportHits(path)     - write greeneye_hits.tsv for MarkGreenEyeHits.java
//
// Usage (Ghidra headless):
//   analyzeHeadless <proj> <name> -process <program> -noanalysis \
//       -scriptPath debug/risc-v-gsp-rm-vm/ghidra \
//       -postScript GreenEyeControl.java <bridge-port> [address]
//
// The optional address argument makes the script run until that address and
// print the hits; in the GUI, omit it to use the current cursor address.
//
//@category OpenIOV
//@menupath Tools.OpenIOV.GreenEyeControl

import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.Socket;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import ghidra.app.script.GhidraScript;
import ghidra.program.model.address.Address;

public class GreenEyeControl extends GhidraScript {

    private Socket socket;
    private BufferedReader reader;
    private PrintWriter writer;
    private int requestId = 0;

    private static final Pattern OK = Pattern.compile("\"ok\"\\s*:\\s*(true|false)");
    private static final Pattern RESULT = Pattern.compile("\"result\"\\s*:\\s*(\\{.*\\}|\\[.*\\]|\"[^\"]*\"|[^,}]+)");

    @Override
    public void run() throws Exception {
        String[] args = getScriptArgs();
        if (args.length < 1) {
            println("usage: GreenEyeControl.java <bridge-port> [address]");
            return;
        }
        int port = Integer.parseInt(args[0]);
        try {
            connect(port);
            println("green-eye: " + info());
            String target = args.length > 1 ? args[1] : null;
            if (target == null && currentAddress != null) {
                target = "0x" + Long.toHexString(currentAddress.getOffset());
            }
            if (target != null) {
                println("green-eye run_until " + target + " -> " + runUntil(target));
                println("green-eye hits: " + listHits());
            }
        } finally {
            disconnect();
        }
    }

    // -- connection --------------------------------------------------------

    public void connect(int port) throws Exception {
        socket = new Socket("127.0.0.1", port);
        reader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
        writer = new PrintWriter(socket.getOutputStream(), true);
    }

    public void disconnect() throws Exception {
        if (socket != null) {
            socket.close();
        }
    }

    /** Send one bridge command and return the raw response line. */
    public String send(String cmd, String argsJson) throws Exception {
        requestId += 1;
        String request = "{\"id\":" + requestId + ",\"cmd\":\"" + cmd + "\",\"args\":"
            + (argsJson == null || argsJson.isEmpty() ? "{}" : argsJson) + "}";
        writer.println(request);
        String response = reader.readLine();
        if (response == null) {
            throw new Exception("green-eye bridge closed the connection");
        }
        Matcher ok = OK.matcher(response);
        if (ok.find() && "false".equals(ok.group(1))) {
            throw new Exception("green-eye error: " + response);
        }
        return response;
    }

    /** Extract the `result` field as a string (numbers, objects or lists). */
    public String resultOf(String response) {
        Matcher matcher = RESULT.matcher(response);
        return matcher.find() ? matcher.group(1) : response;
    }

    // -- documented operations --------------------------------------------

    public String ping() throws Exception {
        return resultOf(send("ping", null));
    }

    public String info() throws Exception {
        return resultOf(send("info", null));
    }

    public String resolve(String name) throws Exception {
        return resultOf(send("resolve", "{\"name\":\"" + name + "\"}"));
    }

    public String setBreakpointAt(String address) throws Exception {
        return resultOf(send("bp_set", "{\"target\":\"" + address + "\"}"));
    }

    public String setBreakpointHere() throws Exception {
        return setBreakpointAt(addressHex(currentAddress));
    }

    public String clearBreakpointAt(long address) throws Exception {
        return resultOf(send("bp_clear", "{\"addr\":" + address + "}"));
    }

    public String runUntil(String address) throws Exception {
        return resultOf(send("run_until", "{\"target\":\"" + address + "\",\"timeout\":10}"));
    }

    public String runUntilHere() throws Exception {
        return runUntil(addressHex(currentAddress));
    }

    public String readMemoryHere(int size) throws Exception {
        return resultOf(send("read_mem", "{\"addr\":\"" + addressHex(currentAddress) + "\",\"size\":" + size + "}"));
    }

    public String readRegister(String name) throws Exception {
        return resultOf(send("read_reg", "{\"name\":\"" + name + "\"}"));
    }

    public String writeRegister(String name, long value) throws Exception {
        return resultOf(send("write_reg", "{\"name\":\"" + name + "\",\"value\":" + value + "}"));
    }

    public String callFunction(String address, long... args) throws Exception {
        StringBuilder json = new StringBuilder("{\"target\":\"" + address + "\",\"args\":[");
        for (int i = 0; i < args.length; i++) {
            if (i > 0) {
                json.append(",");
            }
            json.append(args[i]);
        }
        json.append("]}");
        return resultOf(send("call", json.toString()));
    }

    public String snapshotHere(int size) throws Exception {
        return resultOf(send("snapshot",
            "{\"addr\":\"" + addressHex(currentAddress) + "\",\"size\":" + size + ",\"label\":\"ghidra\"}"));
    }

    public String listHits() throws Exception {
        return resultOf(send("hits", null));
    }

    public String exportHits(String path) throws Exception {
        return resultOf(send("export_hits", "{\"path\":\"" + path + "\"}"));
    }

    private String addressHex(Address address) {
        return "0x" + Long.toHexString(address.getOffset());
    }
}