Skip to content

Commit de9f3dc

Browse files
committed
attempting a version of core utils for random binaries (as opposed to python core dumps)
1 parent 046dabb commit de9f3dc

File tree

1 file changed

+346
-0
lines changed

1 file changed

+346
-0
lines changed

browse_core.py

+346
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,346 @@
1+
# -*- Mode: Python -*-
2+
3+
# attempting a version of core utils for random binaries (as opposed to python core dumps)
4+
5+
import os
6+
import parse_elf
7+
import struct
8+
import sys
9+
from pprint import pprint as pp
10+
11+
W = sys.stderr.write
12+
13+
elf_data = {}
14+
# this should be set by the elf data
15+
psize = None
16+
17+
def read_map (filename, base=0):
18+
global elf_data
19+
info = parse_elf.go (filename)
20+
elf_data[filename] = (base, info)
21+
ehdr, phdrs, shdrs, syms, core_info = info
22+
result = []
23+
for phdr in phdrs:
24+
if phdr['type'] == 'load':
25+
result.append ((phdr['memsz'], base + phdr['vaddr'], phdr['offset'], phdr['filesz']))
26+
result.sort()
27+
return result
28+
29+
class searchable_file:
30+
31+
block_size = 1<<16
32+
33+
def __init__ (self, fd, size):
34+
self.fd = fd
35+
self.size = size
36+
37+
def find (self, needle, position, size=None):
38+
if size is None:
39+
size = self.size - position
40+
while position < self.size:
41+
os.lseek (self.fd, position, 0)
42+
block = os.read (self.fd, self.block_size)
43+
maybe = block.find (needle)
44+
if maybe != -1:
45+
return position + maybe
46+
else:
47+
# fuzz the block size in case needle straddles the boundary
48+
position += (self.block_size - (len(needle) - 1))
49+
return None # Not found
50+
51+
def seek (self, position):
52+
os.lseek (self.fd, position, 0)
53+
54+
def read (self, size):
55+
return os.read (self.fd, size)
56+
57+
def valid_address (addr):
58+
for mmap, mfd, msize, mfile, base in maps:
59+
for memsz, vaddr, offset, filesz in mmap:
60+
if vaddr <= addr < (vaddr + memsz):
61+
return (addr - vaddr) + offset, mfile
62+
return None
63+
64+
def to_disk (addr):
65+
probe = valid_address (addr)
66+
if probe is None:
67+
raise ValueError ("address out of range")
68+
else:
69+
return probe
70+
71+
def from_disk (pos):
72+
for mmap, mfd, msize, mfile, base in maps:
73+
for memsz, vaddr, offset, filesz in mmap:
74+
if offset <= pos < (offset + filesz):
75+
return (pos - offset) + vaddr
76+
raise ValueError, "address out of range"
77+
78+
def read (address, nbytes=4):
79+
# verify all addresses before trying to read them.
80+
probe = valid_address (address)
81+
if probe is not None:
82+
pos, mm = probe
83+
mm.seek (pos)
84+
#print 'addr: %x, pos: %d, mm=%s' % (address, pos, mm)
85+
return mm.read (nbytes)
86+
else:
87+
raise ValueError, "address out of range"
88+
89+
def read_long (address):
90+
return struct.unpack (long_struct, read (address, psize))[0]
91+
92+
def read_struct (address, format):
93+
return struct.unpack (format, read (address, struct.calcsize (format)))
94+
95+
def read_string (address):
96+
if not address:
97+
return '<null>'
98+
else:
99+
r = []
100+
while 1:
101+
ch = read (address, 1)
102+
if ch == '\000':
103+
break
104+
else:
105+
r.append (ch)
106+
address += 1
107+
return ''.join (r)
108+
109+
class finder:
110+
def __init__ (self, s):
111+
self.last = 0
112+
self.s = s
113+
def next (self):
114+
mmap, mfd, msize, mfile, base = maps[0]
115+
addr = mfile.find (self.s, self.last)
116+
if addr is None:
117+
return None
118+
else:
119+
self.last = addr + len (self.s)
120+
return from_disk (addr)
121+
def all (self):
122+
result = []
123+
while 1:
124+
try:
125+
n = self.next()
126+
except KeyboardInterrupt:
127+
sys.stderr.write ('\n')
128+
return result
129+
else:
130+
sys.stderr.write ('+')
131+
if n is None:
132+
break
133+
else:
134+
result.append (n)
135+
sys.stderr.write ('\n')
136+
return result
137+
138+
def find_all (s):
139+
"find all occurrences of string <s> in the core file"
140+
return finder(s).all()
141+
142+
def find (s):
143+
"find the string <s> in the core file"
144+
global _f, next
145+
# save this away so we can continue the search
146+
_f = finder (s)
147+
next = _f.next
148+
return next()
149+
150+
def who_points_to (addr, max_items=30, aligned=True):
151+
# assumes maps[0] is the core file
152+
mmap, mfd, msize, mfile, base = maps[0]
153+
results = []
154+
# address in string form
155+
if psize == 4:
156+
s = struct.pack ('<l', addr)
157+
else:
158+
s = struct.pack ('<q', addr)
159+
found = base
160+
for i in range (max_items):
161+
found = mfile.find (s, found + psize)
162+
sys.stderr.write ('.')
163+
if found is None:
164+
break
165+
else:
166+
in_mem = from_disk (found)
167+
if aligned and (in_mem % psize) != 0:
168+
pass
169+
else:
170+
results.append (in_mem)
171+
sys.stderr.write ('\n')
172+
return results
173+
174+
def WP (addr=None):
175+
if addr is None:
176+
addr = _
177+
return who_points_to (addr)
178+
179+
symbols = None
180+
181+
def read_symbols():
182+
global symbols
183+
r = {}
184+
for path, (base, (ehdr, phdrs, shdrs, syms, core_info)) in elf_data.items():
185+
for sym in syms:
186+
if sym['type'] in ('func', 'object'):
187+
name = sym['name']
188+
p = r.get (name, None)
189+
if p is None:
190+
r[name] = p = []
191+
p.append ((sym['type'], base + sym['value'], path))
192+
symbols = r
193+
194+
def get_sym (name, address_of=0, which=None):
195+
probe = symbols.get (name)
196+
if probe is None:
197+
return None
198+
else:
199+
if which is not None:
200+
raise NotImplementedError
201+
kind, val, path = probe[0]
202+
if kind == 'func' or address_of:
203+
return val
204+
else:
205+
return read_long (val)
206+
207+
# elf_common.h
208+
DT_DEBUG = 21
209+
210+
def find_solibs():
211+
debug_base = None
212+
for filename, (base, info) in elf_data.items():
213+
if filename == exe_path:
214+
ehdr, phdrs, shdrs, syms, core_info = info
215+
for d in shdrs:
216+
if d['type'] == 'dynamic':
217+
# ugh, why did I have to undo 64-bit support...
218+
#assert (d['entsize'] == 8)
219+
#exe_file.seek (d['offset'])
220+
offset = d['addr']
221+
# Note: 'P' won't work because we may
222+
# not be running on the same machine!
223+
if d['entsize'] == 8:
224+
spec = '<LL'
225+
elif d['entsize'] == 16:
226+
spec = '<QQ'
227+
for i in range (0, d['size'], d['entsize']):
228+
tag, val = read_struct (offset + i, spec)
229+
if tag == DT_DEBUG:
230+
debug_base = val
231+
link_map = read_long (debug_base + psize)
232+
# ok, now we have the link map, we can walk it and find all so's.
233+
result = []
234+
# Note: 'P' won't work because we may
235+
# not be running on the same machine!
236+
if psize == 4:
237+
spec = '<LLLLL'
238+
elif psize == 8:
239+
spec = '<QQQQQ'
240+
while 1:
241+
addr, name, ld, next, prev = read_struct (link_map, spec)
242+
result.append ((addr, read_string (name)))
243+
if not next:
244+
break
245+
else:
246+
link_map = next
247+
return result
248+
249+
def map_file (path, base=0):
250+
global maps
251+
print '%16x %s' % (base, path)
252+
mmap = read_map (path, base)
253+
mfd = os.open (path, os.O_RDONLY)
254+
msize = os.lseek (mfd, 0, 2)
255+
mfile = searchable_file (mfd, msize)
256+
maps.append ((mmap, mfd, msize, mfile, base))
257+
258+
def set_psize():
259+
global psize, long_struct
260+
# pick a file randomly
261+
base, info = elf_data[exe_path]
262+
ehdr, phdrs, shdrs, syms, core_info = info
263+
ident_class = ehdr['ident']['class']
264+
if ident_class == '32-bit':
265+
psize = 4
266+
long_struct = '<L'
267+
elif ident_class == '64-bit':
268+
psize = 8
269+
long_struct = '<Q'
270+
else:
271+
raise ValueError, "I'm confused"
272+
273+
if __name__=='__main__':
274+
275+
usage = """\
276+
usage: python %s <exe-file> <core-file>
277+
python %s -h|--help""" %(sys.argv[0], sys.argv[0])
278+
279+
if '-h' in sys.argv or '--help' in sys.argv:
280+
print """\
281+
To use this, do:
282+
283+
%s
284+
285+
If there were any shared libraries, either make sure they are in the same
286+
location from where the binary imported them, or stick them all into the
287+
current directory.
288+
""" %(usage,)
289+
sys.exit()
290+
if len(sys.argv) < 3:
291+
print usage
292+
sys.exit()
293+
294+
exe_path = sys.argv[1]
295+
core_path = sys.argv[2]
296+
297+
maps = []
298+
299+
# core file must be first...
300+
map_file (core_path)
301+
map_file (exe_path)
302+
303+
# set the size of a pointer
304+
set_psize()
305+
306+
# skip first one, it's the exe, which is already mapped
307+
solibs = find_solibs()[1:]
308+
309+
transplant = 0
310+
for addr, path in solibs:
311+
if not os.path.isfile (path):
312+
# try the current directory
313+
probe = os.path.split(path)[-1]
314+
if os.path.isfile (probe):
315+
transplant = 1
316+
path = probe
317+
else:
318+
print 'unable to find %s' % (path,)
319+
path = None
320+
if path:
321+
map_file (path, addr)
322+
323+
if transplant:
324+
print '[transplant]'
325+
326+
read_symbols()
327+
328+
base, info = elf_data[core_path]
329+
ehdr, phdrs, shdrs, syms, core_info = info
330+
command = core_info.get('command')
331+
death_signal = core_info.get('signal')
332+
if command:
333+
print 'Core was generated by "%s"' %(command)
334+
if death_signal:
335+
print 'Program terminated with signal %d' %(death_signal)
336+
337+
# -------------------------------------------------------------------------
338+
# this eats up about 10MB of memory, which we probably don't need any more.
339+
# -------------------------------------------------------------------------
340+
elf_data.clear()
341+
342+
import code
343+
banner = """\
344+
Welcome to browse_core.
345+
"""
346+
code.interact(banner=banner, local=locals())

0 commit comments

Comments
 (0)