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
import os
import struct
import re
import mmap
import gdb

NULL = 0

class Error(Exception):
    pass

class dlopen(object):
    # True on Linux and Mac OS X
    RTLD_LAZY = 1
    RTLD_NOW = 2

    _DLOPEN_FORMAT = '((void *(*)())dlopen)((char *)%s, (int)%d)'
    _DLSYM_FORMAT = '((void *(*)())dlsym)((void *)0x%x, (char *)%s)'
    _DLCLOSE_FORMAT = '((int (*)())dlclose)((void *)0x%x)'

    def __init__(self, path, flags=None):
        self.handle = None
        if flags is None:
            flags = self.RTLD_LAZY
        if path != NULL:
            if os.path.isfile(path):
                path = os.path.abspath(path)
            path = '"%s"' % (path,)
        expr = self._DLOPEN_FORMAT % (str(path), int(flags))
        handle = gdb.parse_and_eval(expr)
        if not handle:
            raise Error(self._dlerror())
        self.handle = handle

    def dlsym(self, symbol):
        symbol = '"%s"' % (symbol,)
        expr = self._DLSYM_FORMAT % (long(self.handle), symbol)
        result = gdb.parse_and_eval(expr)
        if not result:
            raise Error(self._dlerror())
        return result

    def _dlerror(self):
        msg = gdb.parse_and_eval("((char *(*)())dlerror)()")
        if not msg:
            return None
        return msg.string()

class Detour(gdb.Command):
    """Redirect all invocations of one function to another."""

    _ADDR_RE = re.compile(r'^(?:[=][>])? *(0x[0-9a-fA-F]+)')
    _MMAP_FORMAT = '((void *(*)())mmap)((void *)0x%x, (size_t)0x%x, ' \
                                       '(int)%d, (int)%d, (int)%d, ' \
                                       '(off_t)0x%x)'

    _JMP_INSN = {4: "\x68%s\xc3", 8: "\xff%%\x00\x00\x00\x00%s"}
    _REL_INSNS = {4: ["\xff\x25", "\xe8"], 8: ["\xff\x25", "\xe8"]}
    _ADDR_MAX = {4: 0xffffffff, 8: 0xffffffffffffffff}

    def __init__(self):
        super(Detour, self).__init__(
            "detour", gdb.COMMAND_RUNNING, gdb.COMPLETE_NONE)

    def _absjmp(self, daddr):
        format = "@Q" if self._width == 8 else "@I"
        daddr = struct.pack(format, long(daddr))
        return self._JMP_INSN[self._width] % (daddr,)

    def _mmap(self, addr, length, prot, flags, fd, offset):
        expr = self._MMAP_FORMAT % (addr, length, prot, flags, fd, offset)
        result = gdb.parse_and_eval(expr)
        if not result:
            raise Error('failed to map memory for trampoline')
        return result

    def _fixup_insn(self, saddr, taddr, bytes, aoff):
        saddr, taddr = saddr + aoff, taddr + aoff
        reloff, = struct.unpack("@i", bytes[aoff:aoff+4])
        daddr = (saddr + reloff) & self._ADDR_MAX[self._width]
        reloff = (daddr - taddr) & 0xffffffff
        reloff = struct.pack("@I", reloff)
        bytes[aoff:aoff+4] = reloff

    def _fixup_insns(self, saddr, taddr, bytes, offsets):
        for offset in offsets:
            for insn in self._REL_INSNS[self._width]:
                aoff = offset + len(insn)
                if bytes[offset:aoff] == insn:
                    self._fixup_insn(saddr, taddr, bytes, aoff)

    def _create_tramp(self, inferior, saddr, nbytes):
        saddr = long(saddr)
        endjmp = saddr + nbytes
        expr = 'x/%di 0x%x' % (nbytes, long(saddr))
        disass = gdb.execute(expr, to_string=True)
        nexti = None
        offsets = []
        for line in disass.split('\n'):
            match = self._ADDR_RE.match(line)
            if match is None:
                continue
            addr = long(match.group(1), 0)
            if addr >= endjmp:
                nexti = addr
                break
            offsets.append(addr - saddr)
        else:
            raise Error('could not determine next insn for trampoline')
        nbytes = nexti - saddr
        bytes = inferior.read_memory(saddr, nbytes)
        prot = mmap.PROT_EXEC | mmap.PROT_READ | mmap.PROT_WRITE
        flags = mmap.MAP_PRIVATE | mmap.MAP_ANONYMOUS
        taddr = self._mmap(NULL, len(bytes), prot, flags, -1, 0)
        self._fixup_insns(saddr, long(taddr), bytes, offsets)
        bytes += self._absjmp(saddr + nbytes)
        inferior.write_memory(taddr, bytes)
        return taddr

    def _detour(self, saddr, daddr, taddrp):
        inferior = gdb.inferiors()[0]
        bytes = self._absjmp(daddr)
        if taddrp:
            tramp = self._create_tramp(inferior, saddr, len(bytes))
            expr = "set *(void **)0x%x = (void *)0x%x" % (taddrp, tramp)
            gdb.execute(expr)
        inferior.write_memory(saddr, bytes)

    def _lookup(self, arg):
        if ':' in arg:
            path, symbol = arg.split(':', 1)
        else:
            path, symbol = NULL, arg
        return dlopen(path).dlsym(symbol)

    def _invoke(self, args, from_tty):
        self.dont_repeat()
        args = gdb.string_to_argv(args)
        if len(args) == 2:
            src, dst = args
            tramp = None
        elif len(args) == 3:
            src, dst, tramp = args
        else:
            raise Error("incorrect number of arguments")
        self._width = int(gdb.parse_and_eval('sizeof(void *)'))
        saddr = self._lookup(src)
        daddr = self._lookup(dst)
        taddrp = self._lookup(tramp) if tramp else None
        self._detour(saddr, daddr, taddrp)

    def invoke(self, args, from_tty):
        try:
            return self._invoke(args, from_tty)
        except (Error,), e:
            print "error: %s" % (str(e),)

Detour()