smoldd.py 4.79 KB
Newer Older
PoroCYon's avatar
PoroCYon committed
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
#!/usr/bin/env python3

import argparse
import glob
import os.path
import shutil
import subprocess
import struct
import sys

from smolshared import *

def readbyte(blob, off):
    return struct.unpack('<B', blob[off:off+1])[0], (off+1)

def readint(blob, off):
    return struct.unpack('<I', blob[off:off+4])[0], (off+4)

def readlong(blob, off):
    return struct.unpack('<Q', blob[off:off+8])[0], (off+8)

def readstr(blob, off):
    text = bytearray()
    while True:
        char, off = readbyte(blob, off)
        if char == 0:
            break

        text.append(char)

    return text.decode('utf-8'), off

33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
def get_def_libpaths(cc_bin, is32bit):
    if is32bit:
        return ['/usr/lib32/','/lib32/']

    out = subprocess.check_output([cc_bin, '-print-search-dirs'],
                                     stderr=subprocess.DEVNULL)

    stuff = dict({})
    for l in out.decode('utf-8').splitlines():
        blah = l.split(': ')
        stuff[blah[0]] = blah[1].lstrip('=').split(':')

    return stuff["libraries"]

def find_libs(bits, deflibs, libname):
    dirs = os.environ['LD_LIBRARY_PATH'].split(':') + deflibs
PoroCYon's avatar
PoroCYon committed
49
50

    for d in dirs:
51
        for f in glob.glob(glob.escape(d + libname) + '*'):
PoroCYon's avatar
PoroCYon committed
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
            yield f

def build_hashtab(scanelf_bin, lib):
    out = subprocess.check_output([scanelf_bin, '-B', '-F', '%s', '-s', '%pd%*', lib],
                                     stderr=subprocess.DEVNULL)

    blah = set(out.decode('utf-8').split('\n'))
    ret = dict({})

    for x in blah:
        y = x.split()
        if len(y) != 7:
            continue
        ret[hash_djb2(y[6])] = y[6]

    return ret

def main():
    parser = argparse.ArgumentParser()
    parser.add_argument('input', type=argparse.FileType('rb'),
                        default=sys.stdin.buffer, help="input file")
73
74
    parser.add_argument('--cc',
                        default=shutil.which('cc'), help="C compiler binary")
PoroCYon's avatar
PoroCYon committed
75
76
77
78
79
80
81
82
83
84
85
    parser.add_argument('--scanelf',
                        default=shutil.which('scanelf'), help="scanelf binary")

    args = parser.parse_args()

    blob = args.input.read()

    machnum = struct.unpack('<H', blob[18:18+2])[0]

    is32bit = machnum == archmagic['i386']

86
87
    deflibs = get_def_libpaths(args.cc, is32bit)

PoroCYon's avatar
PoroCYon committed
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
    phoff, phsz, phnum = 0, 0, 0
    if is32bit:
        phoff = struct.unpack('<I', blob[28:28+4])[0]
        phsz  = struct.unpack('<H', blob[42:42+2])[0]
        phnum = struct.unpack('<H', blob[44:52+2])[0]
    elif machnum == archmagic['x86_64']:
        phoff = struct.unpack('<Q', blob[32:32+8])[0]
        phsz  = struct.unpack('<H', blob[54:54+2])[0]
        phnum = struct.unpack('<H', blob[56:56+2])[0]
    else:
        eprintf("Unknown architecture " + str(machnum))
        sys.exit(1)

    for i in range(phnum):
        off = phoff + i * phsz
        #print(hex(off))

        ptyp, poff, pva, ppa, pfsz, pmsz, pfl, pal = 0,0,0,0,0,0,0,0
        if is32bit:
            ptyp, poff, pva, ppa, pfsz, pmsz, pfl, pal = \
                struct.unpack('<ILLLIIII', blob[off:off+phsz])
        else:
            ptyp, pfl, poff, pva, ppa, pfsz, pmsz, pal = \
                struct.unpack('<IIQQQQQQ', blob[off:off+phsz])

        if ptyp != 2: # PT_DYNAMIC
            continue

        #print(hex(poff))

        # right after the dynamic section, the smol 'symtab'/'hashtab' is found
        #
        # note that on i386, every lib name is followed by an E9 byte
        # if the next libname/first byte of the hash is null, the table has
        # come to an end.

PoroCYon's avatar
PoroCYon committed
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
        if is32bit:
            j = poff
            strtaboff = 0
            while j < poff + pfsz:
                tag, j = readint(blob, j)
                ptr, j = readint(blob, j)

                if tag == 5: # DT_STRTAB
                    strtaboff = ptr
                elif tag == 1: # DT_NEEDED
                    bakoff = j

                    smoltaboff = strtaboff + ptr - (pva - poff)
                    j = smoltaboff

                    libname, j = readstr(blob, j)
                    if len(libname) == 0:
                        break
PoroCYon's avatar
PoroCYon committed
142

PoroCYon's avatar
PoroCYon committed
143
                    sys.stdout.write("* " + libname)
PoroCYon's avatar
PoroCYon committed
144

PoroCYon's avatar
PoroCYon committed
145
146
147
                    libs = list(find_libs(32, deflibs, libname))
                    print(" -> NOT FOUND" if len(libs) == 0 else (" -> " + libs[0]))
                    ht = dict({}) if len(libs) == 0 else build_hashtab(args.scanelf, libs[0])
PoroCYon's avatar
PoroCYon committed
148

PoroCYon's avatar
PoroCYon committed
149
150
                    while True:
                        hashv, j = readint(blob, j)
PoroCYon's avatar
PoroCYon committed
151

PoroCYon's avatar
PoroCYon committed
152
153
                        if (hashv & 0xFF) == 0:
                            break
PoroCYon's avatar
PoroCYon committed
154

PoroCYon's avatar
PoroCYon committed
155
156
                        sys.stdout.write("  * " + hex(hashv))
                        print(" -> NOT FOUND" if hashv not in ht else (" -> " + ht[hashv]))
PoroCYon's avatar
PoroCYon committed
157

PoroCYon's avatar
PoroCYon committed
158
                    j = bakoff
PoroCYon's avatar
PoroCYon committed
159

PoroCYon's avatar
PoroCYon committed
160
161
162
163
            break
        else: # 64-bit
            eprintf("Currently unsuppored, sorry.")
            sys.exit(1)
PoroCYon's avatar
PoroCYon committed
164
165
166
167

if __name__ == '__main__':
    main()