-
Notifications
You must be signed in to change notification settings - Fork 4
/
ida_rpyc_server.py
241 lines (172 loc) · 5.05 KB
/
ida_rpyc_server.py
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
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
"""
> "Because IDA sucks"
Embed RPyc in IDA to expose IDA's API externally, by a background thread that runs the TCP server. Also in iPython this
provides autocomplete.
Props to https://github.com/vrtadmin/FIRST-plugin-ida/blob/master/first_plugin_ida/first.py#L87
for the workaround on the threading issue, for IDA Pro >= 7.2
Quick start
```
>>> import rpyc
>>> c = rpyc.connect("ida.rpyc.server", 18812)
#
# IDA namespace will be in `c.root`
#
>>> c.root.idaapi.get_root_filename()
'ntoskrnl.exe'
>>> hex( c.root.idc.here() )
0x140088194
>>> c.root.idaapi.jumpto( 0x1400881EE )
True
```
For more facility, you can alias it:
```
>>> idc = c.root.idc
```
Then, it becomes super readable
```
>>> idc.jumpto( idc.get_name_ea_simple("DriverEntry") )
True
>>> idc.set_cmt( idc.here(), "@hugsy was here", 1)
True
```
For generator objects, you now need to use the wrapper `c.root.iterate()`.
Example:
```
>>> idc = c.root.idc
>>> idautils = c.root.idautils
>>> for ea in c.root.iterate( idautils.Functions() ):
... print( idc.get_func_name(ea) )
```
Blame HexRays for making their API more confusing at every release.
Ref:
- https://www.hex-rays.com/products/ida/support/ida74_idapython_no_bc695_porting_guide.shtml
Demo:
- https://youtu.be/obX2GreSsFU
"""
import sys
import random
import threading
import rpyc
import idc
import idaapi
import idautils
import ida_bytes
import ida_funcs
import ida_hexrays
# exported modules must be imported first
import sys
import os
PLUGIN_NAME = "RunRpycServer"
PLUGIN_HOTKEY = "Ctrl-Alt-K"
PLUGIN_VERSION = "0.3"
PLUGIN_AUTHOR = "@_hugsy_"
HOST, PORT = "0.0.0.0", 18812
DEBUG = False
EXPOSED_MODULES = [idaapi, idc, idautils, ida_hexrays, os, sys]
def xlog(x):
sys.stderr.write("{} - {}\n".format(threading.current_thread().name, x)) and sys.stderr.flush()
def err(msg):
xlog(f"[!] {msg}")
def ok(msg):
xlog(f"[+] {msg}")
def dbg(msg):
if DEBUG:
xlog(f"[*] {msg}")
class IdaWrapper:
def __getattribute__(self, name):
default = "IDoNotExistButNoReallyISeriouslyDoNotAndCannotExist"
dbg("trying to get {}".format(name,))
if name.startswith("exposed_"):
name = name.replace("exposed_", "")
dbg("changed to get {}".format(name,))
for mod in EXPOSED_MODULES:
val = getattr(mod, name, default)
if val != default:
break
if val == default:
raise AttributeError("unknown {}".format(name,))
if hasattr(val, '__call__'):
dbg("{} is callable".format(val,))
def call(*args, **kwargs):
holder = [None] # need a holder, because 'global' sucks
def trampoline():
holder[0] = val(*args, **kwargs)
return 1
idaapi.execute_sync(trampoline, idaapi.MFF_WRITE)
return holder[0]
return call
else:
return val
g_IdaWrapper = IdaWrapper()
class IdaRpycService(rpyc.Service):
ALIASES = ["ida", ]
def on_connect(self, conn):
ok("connect open: {}".format(conn,))
for mod in EXPOSED_MODULES:
setattr(self, f"exposed_{mod.__name__}", g_IdaWrapper)
return
def on_disconnect(self, conn):
ok(f"connection closed: {str(conn)}")
return
def exposed_eval(self, cmd):
return eval(cmd)
def exposed_exec(self, cmd):
return exec(cmd)
def exposed_iterate(self, iterator):
default = f"IDoNotExistButNoReallyISeriouslyDoNotAndCannotExist {random.randint(0, 65535)}"
holder = [default]
def trampoline():
try:
holder[0] = next(iterator)
except StopIteration:
holder[0] = default
return 1
while True:
idaapi.execute_sync(trampoline, idaapi.MFF_WRITE)
if holder[0] == default:
return
yield holder[0]
g_IdaServer = IdaRpycService()
def start():
global g_IdaServer
srv = None
for i in range(1):
port = PORT + i
try:
srv = rpyc.utils.server.OneShotServer(g_IdaServer, hostname=HOST, port=port) if DEBUG \
else rpyc.utils.server.ThreadedServer(g_IdaServer, hostname=HOST, port=port)
break
except OSError:
srv = None
if not srv:
err("failed to start server...")
return
ok("starting server...")
srv.start()
srv.close()
ok("server closed")
return
t = None
def main():
global t
if t is not None:
err(f"thread is already running as {str(t)}")
return
t = threading.Thread(target=start)
t.daemon = True
t.start()
ok(f"service listening on {HOST}:{PORT}...")
class dummy(idaapi.plugin_t):
wanted_name = PLUGIN_NAME
wanted_hotkey = ""
flags = idaapi.PLUGIN_UNL
comment = ""
help = ""
def init(self): return idaapi.PLUGIN_OK
def run(self, arg): pass
def term(self): pass
def PLUGIN_ENTRY():
main()
return dummy()
if __name__ == "__main__":
PLUGIN_ENTRY()