Skip to content

Commit 96dcdc0

Browse files
committed
This PR is a result of integration with QIRA.
The PR will bring us the following: 1. BAP now supports 25 different architectures. Also Arch module can now infer address size and endianness from the specified architecture. 2. Bap server doesn't require address size or endian. Only arch is required, everything else can be inferred from it. 3. Addresses in Public API is no longer an ADT but a string encoded number, with value specified strictly in hexes. 4. BIL and ADT classes are extended. Added some helpers, and also add named properties to BIL entities. 5. Updated README.md and simplified installation. This will resolve #62 6. Packaged server a little more accurately, although it still not pluginized (see #67) 7. Hardened `bap.py` for multithreaded environment bap will try to autospawn one server per process, and multiple bap proxies will be created per thread 8. bap-mc now accepts more user inputs: 1. bytes can be separated with spaces, commas and semicolons 2. bytes can be left without separators 9. bap-mc will now error in case of error This is to resolve #47 and others 10. bap-mc will now properly handle invalid instructions Previously there was a bug, as a result error handler was applied prematurely. Also, I've extended error handler with pretty-printer that will output dump, highlighting failed piece of code. 11. readbin will linear sweep executable sections if there are no symbol information. 12. bap-server if ordered to disassemble the whole image will disassemble only executable part of the file. I think this is a more reasonable behavior, rather than previously to disassemble the whole file including data sections.
1 parent 1a655b1 commit 96dcdc0

File tree

5 files changed

+236
-68
lines changed

5 files changed

+236
-68
lines changed

__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,6 @@
9999
100100
Where data is actual string of bytes.
101101
"""
102-
__all__ = ['disasm', 'image']
102+
__all__ = ['disasm', 'image', 'adt', 'asm', 'arm', 'bil']
103103

104104
from .bap import disasm, image

adt.py

Lines changed: 27 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -57,25 +57,27 @@ class ADT(object):
5757
5858
"""
5959
def __init__(self, *args):
60-
self.name = self.__class__.__name__
61-
self.val = args if len(args) != 1 else args[0]
60+
self.constr = self.__class__.__name__
61+
self.arg = args if len(args) != 1 else args[0]
6262

6363
def __cmp__(self,other):
6464
return self.__dict__.__cmp__(other.__dict__)
6565

6666
def __repr__(self):
6767
def qstr(x):
68-
if isinstance(x, int) or isinstance(x, ADT):
68+
if isinstance(x, (int,long)):
69+
return '0x{0:x}'.format(x)
70+
elif isinstance(x, ADT):
6971
return str(x)
7072
else:
7173
return '"{0}"'.format(x)
7274
def args():
73-
if isinstance(self.val, tuple):
74-
return ", ".join(qstr(x) for x in self.val)
75+
if isinstance(self.arg, tuple):
76+
return ", ".join(qstr(x) for x in self.arg)
7577
else:
76-
return qstr(self.val)
78+
return qstr(self.arg)
7779

78-
return "{0}({1})".format(self.name, args())
80+
return "{0}({1})".format(self.constr, args())
7981

8082

8183
class Visitor(object):
@@ -96,9 +98,12 @@ def visit_ADT(self, adt):
9698
no specific visitors. It will recursively descent into all
9799
ADT values.
98100
"""
99-
if isinstance(adt.val, tuple):
100-
for e in adt.val:
101+
if isinstance(adt.arg, tuple):
102+
for e in adt.arg:
101103
self.run(e)
104+
elif isinstance(adt.arg, ADT):
105+
self.run(adt.arg)
106+
102107

103108
def run(self, adt):
104109
"""ADT.run(adt-or-iterable) -> None
@@ -126,14 +131,14 @@ def __init__(self):
126131
self.count = 0
127132
128133
def visit_Int(self, int):
129-
if int.val < 0 and not self.neg \
130-
or int.val > 0 and self.neg:
134+
if int.arg < 0 and not self.neg \
135+
or int.arg > 0 and self.neg:
131136
self.count += 1
132137
133138
def visit_NEG(self, op):
134139
was = self.neg
135140
self.neg = not was
136-
self.run(op.val)
141+
self.run(op.arg)
137142
self.neg = was
138143
139144
We need to keep track on the unary negation operator, and, of
@@ -147,9 +152,6 @@ def visit_NEG(self, op):
147152
and recurse into the operand. After we return from the recursion, we restore
148153
the sign.
149154
"""
150-
if isinstance(adt, Iterable):
151-
for s in adt:
152-
self.run(s)
153155
if isinstance(adt, ADT):
154156
for c in adt.__class__.mro():
155157
name = ("visit_%s" % c.__name__)
@@ -158,6 +160,16 @@ def visit_NEG(self, op):
158160
return fn(adt)
159161

160162

163+
def visit(visitor, adt):
164+
165+
if isinstance(adt, Iterable):
166+
for x in adt:
167+
visitor.run(x)
168+
else:
169+
visitor.run(adt)
170+
return visitor
171+
172+
161173
if __name__ == "__main__":
162174
class Fruit(ADT) : pass
163175
class Bannana(Fruit) : pass

asm.py

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ def map_eval(ss):
3434

3535

3636
class Insn(object) :
37-
def __init__(self, name, addr, size, asm, kinds, operands, target=None, bil=[], **kw):
37+
def __init__(self, name, addr, size, asm, kinds, operands, target=None, bil=None, **kw):
3838
self.name = name
3939
self.addr = int(addr)
4040
self.size = int(size)
@@ -45,8 +45,11 @@ def __init__(self, name, addr, size, asm, kinds, operands, target=None, bil=[],
4545
self.bil = bil
4646
self.__dict__.update(kw)
4747

48+
def has_kind(self, k):
49+
return exists(self.kinds, lambda x: isinstance(x,k))
50+
4851
def __repr__(self):
49-
return 'Insn("{name}", {addr:#010x}, {size}, "{asm}", {kinds}, {operands}, {target}, {bil})'.\
52+
return 'Insn("{name}", {addr:#010x}, {size}, "{asm}", {kinds}, {operands})'.\
5053
format(**self.__dict__)
5154

5255
class Op(ADT) : pass
@@ -55,6 +58,14 @@ class Imm(Op) : pass
5558
class Fmm(Op) : pass
5659

5760

61+
def exists(cont,f):
62+
try:
63+
r = (x for x in cont if f(x)).next()
64+
return True
65+
except StopIteration:
66+
return False
67+
68+
5869
if __name__ == "__main__":
5970
print Reg('R0')
6071
for insn in ["Reg(\"R0\")", "Imm(5)", "Imm(14)", "Reg(\"Nil\")", "Reg(\"Nil\")"]:

bap.py

Lines changed: 51 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
# -*- coding: utf-8 -*-
33

44
import os, time, atexit
5+
from signal import signal, SIGTERM
56
import requests
67
from subprocess import Popen
78
from mmap import mmap
@@ -10,41 +11,64 @@
1011
import json
1112
import adt, arm, asm, bil
1213

14+
import threading
15+
16+
from pprint import pprint
17+
1318

1419
__all__ = ["disasm", "image"]
1520

1621
DEBUG_LEVEL = ["Critical", "Error"]
1722

18-
instance = None
23+
24+
storage = threading.local()
25+
servers = dict()
26+
server_lock = threading.Lock()
1927

2028
def del_instance():
29+
instance = getattr(storage, 'instance', None)
2130
if instance is not None:
2231
instance.close()
2332

2433
def get_instance(**kwargs):
25-
global instance
26-
if 'server' in kwargs or instance is None:
27-
if instance is not None:
28-
instance.close()
34+
instance = getattr(storage, 'instance', None)
35+
if instance is None:
2936
args = kwargs.get('server', {})
30-
instance = Bap(args)
31-
return instance
37+
storage.instance = Bap(args)
38+
return storage.instance
3239

3340
atexit.register(del_instance)
41+
signal(SIGTERM, lambda x,y: del_instance)
42+
43+
44+
def spawn_server(**kwargs):
45+
port = str(kwargs.get('port', 8080))
46+
name = kwargs.get('name', 'bap-server')
47+
with server_lock:
48+
if port in servers:
49+
return servers[port]
50+
else:
51+
process = Popen([name, '--port=' + port])
52+
server = {
53+
'server' : process,
54+
'url' : "http://127.0.0.1:{0}".format(port)
55+
}
56+
servers[port] = server
57+
return server
58+
3459

3560
def disasm(obj, **kwargs):
3661
r""" disasm(obj) disassembles provided object.
3762
Returns a generator object yield instructions.
38-
3963
"""
40-
def ret(obj):
41-
return get_instance(**kwargs).insns(obj)
64+
def run(obj):
65+
return get_instance(**kwargs).insns(obj, **kwargs)
4266
if isinstance(obj, Id):
43-
return ret(obj)
67+
return run(obj)
4468
elif isinstance(obj, Resource):
45-
return ret(obj.ident)
69+
return run(obj.ident)
4670
else:
47-
return ret(load_chunk(obj, **kwargs))
71+
return run(load_chunk(obj, **kwargs))
4872

4973
def image(f, **kwargs):
5074
bap = get_instance(**kwargs)
@@ -218,10 +242,12 @@ def __init__(self, server={}):
218242
raise RuntimeError("Failed to connect to BAP server")
219243

220244
self.data = {}
221-
self.temp = NamedTemporaryFile('rw+b')
245+
self.temp = NamedTemporaryFile('rw+b', prefix="bap-")
222246

223-
def insns(self, src):
224-
res = self.call({'get_insns' : {'resource' : src}})
247+
def insns(self, src, **kwargs):
248+
req = {'resource' : src}
249+
req.update(kwargs)
250+
res = self.call({'get_insns' : req})
225251
for msg in res:
226252
if 'error' in msg:
227253
err = Error(msg)
@@ -242,9 +268,13 @@ def get_resource(self, name):
242268

243269
def load_chunk(self, data, **kwargs):
244270
kwargs.setdefault('url', self.mmap(data))
245-
kwargs.setdefault('arch', 'x86_32')
246-
kwargs.setdefault('address', bil.Int(0,32))
247-
kwargs.setdefault('endian', bil.LittleEndian())
271+
kwargs.setdefault('arch', 'i386')
272+
kwargs.setdefault('addr', 0)
273+
addr = kwargs['addr']
274+
if isinstance(addr, str):
275+
addr = long(addr, 0)
276+
kwargs['addr'] = '0x{0:x}'.format(addr)
277+
248278
return self._load_resource({'load_memory_chunk' : kwargs})
249279

250280
def __exit__(self):
@@ -270,8 +300,8 @@ def dumps(dic):
270300
def mmap(self, data):
271301
url = "mmap://{0}?offset=0&length={1}".format(
272302
self.temp.name, len(data))
273-
os.ftruncate(self.temp.fileno(), len(data))
274-
mm = mmap(self.temp.fileno(), len(data))
303+
os.ftruncate(self.temp.fileno(), 4096)
304+
mm = mmap(self.temp.fileno(), 4096)
275305
mm.write(data)
276306
mm.close()
277307
return url
@@ -282,14 +312,6 @@ def _load_resource(self, res):
282312
raise ServerError(rep)
283313
return Id(rep['resource'])
284314

285-
def spawn_server(**kwargs):
286-
port = kwargs.get('port', 8080)
287-
name = kwargs.get('name', 'bap-server')
288-
server = Popen([name, '--port=' + str(port)])
289-
return {
290-
'server' : server,
291-
'url' : "http://127.0.0.1:{0}".format(port)
292-
}
293315

294316
def jsons(r, p=0):
295317
dec = json.JSONDecoder(encoding='utf-8')

0 commit comments

Comments
 (0)