1 // taken from http://www.codeproject.com/Articles/70302/Redirecting-functions-in-shared-ELF-libraries.
2 module elfhook;
3 
4 import core.sys.linux.elf;
5 import core.sys.posix.unistd;
6 import core.sys.posix.sys.mman;
7 
8 import core.stdc.errno;
9 
10 import std.exception;
11 import std.string : toStringz;
12 import std.conv : to;
13 import std.range : only;
14 
15 import sharedlib;
16 import elf;
17 import elf.low;
18 
19 
20 version(linux):
21 @system:
22 
23 version(X86_64)
24 {
25     alias Elf_Rel = Elf64_Rela;
26     alias ELF_R_SYM = ELF64_R_SYM;
27 }
28 else version(X86)
29 {
30     alias Elf_Rel = Elf32_Rela;
31     alias ELF_R_SYM = ELF32_R_SYM;
32 }
33 else static assert(false, "Unsupported architecture.");
34 
35 
36 void* elfHook(ELF elf, in void* address, in string name, in void* substitution)
37 {
38     assert(address !is null);
39     assert(name !is null);
40     assert(substitution !is null);
41 
42     size_t pagesize = sysconf(_SC_PAGESIZE);
43 
44     ELFSection dynsym;  // ".dynsym"
45     ELFSection rel_plt;  // ".rela.plt"
46     ELFSection rel_dyn;  // ".rela.dyn"
47     ELFSymbol symbol;  //symbol table entry for symbol named "name"
48 
49     Elf_Rel *rel_plt_table;  //array with ".rel.plt" entries
50     Elf_Rel *rel_dyn_table;  //array with ".rel.dyn" entries
51 
52     size_t name_index = void;
53     size_t rel_plt_amount = void;  // amount of ".rel.plt" entries
54     size_t rel_dyn_amount = void;  // amount of ".rel.dyn" entries
55     size_t *name_address = null;
56 
57     void *original;  //address of the symbol being substituted
58 
59     foreach (section; elf.sections)
60     {
61         if (section.type == SectionType.dynamicLoaderSymbolTable)
62             dynsym = section;
63         if (section.name.to!string == ".rela.plt")
64             rel_plt = section;
65         if (section.name.to!string == ".rela.dyn")
66             rel_dyn = section;
67     }
68 
69     foreach (section; only(".symtab", ".dynsym"))
70     {
71         auto s = elf.getSection(section);
72         int i = 0;
73         foreach (sym; SymbolTable(s).symbols)
74         {
75             if (name == sym.name)
76             {
77                 symbol = sym;
78                 name_index = cast(size_t) i;
79                 goto L0;
80             }
81             ++i;
82         }
83     }
84 
85 L0:
86 
87     rel_plt_table = cast(Elf_Rel*) ((cast(size_t) address) + rel_plt.address);
88     rel_plt_amount = rel_plt.size / Elf_Rel.sizeof;
89 
90     rel_dyn_table = cast(Elf_Rel*) ((cast(size_t) address) + rel_dyn.address);
91     rel_dyn_amount = rel_dyn.size / Elf_Rel.sizeof;
92 
93     foreach (i; 0 .. rel_plt_amount)
94     {
95         if (ELF_R_SYM(rel_plt_table[i].r_info) == name_index)
96         {
97             name_address = cast(size_t *) ((cast(size_t) address) + rel_plt_table[i].r_offset);
98 
99             // mark a memory page that contains the relocation as writable.
100             mprotect(cast(void*) ((cast(size_t) name_address) & (((size_t.sizeof)-1) ^ (pagesize - 1))), pagesize, PROT_READ | PROT_WRITE);
101 
102             // save the original function address.
103             original = cast(void*)* name_address;
104 
105             // and replace it with the substitution.
106             *name_address = cast(size_t) substitution;
107 
108             break;  // the target symbol appears in ".rel.plt" only once.
109         }
110     }
111 
112     if (original)
113         return original;
114 
115     foreach (i;  0 .. rel_dyn_amount)
116     {
117         if (ELF_R_SYM(rel_dyn_table[i].r_info) == name_index)
118         {
119             // get the relocation address (address of a relative CALL (0xE8) instruction's argument)
120             name_address = cast(size_t*) ((cast(size_t) address) + rel_dyn_table[i].r_offset);
121 
122             if (!original)
123             {
124                 // calculate an address of the original function by a relative CALL (0xE8) instruction's argument.
125                 original = cast(void*) (*name_address + cast(size_t) name_address + size_t.sizeof);
126             }
127 
128             // mark a memory page that contains the relocation as writable.
129             mprotect(cast(void*) ((cast(size_t) name_address) & (((size_t.sizeof)-1) ^ (pagesize - 1))), pagesize, PROT_READ | PROT_WRITE);
130 
131             if (errno)
132             {
133                 errnoEnforce(false, "failed to mprotect.");
134             }
135 
136             // calculate a new relative CALL (0xE8) instruction's argument for the substitutional function and write it down.
137             *name_address = cast(size_t) substitution - cast(size_t) name_address - size_t.sizeof;
138 
139             // mark a memory page that contains the relocation back as executable.
140             mprotect(cast(void*) ((cast(size_t) name_address) & (((size_t.sizeof)-1) ^ (pagesize - 1))), pagesize, PROT_READ | PROT_EXEC);
141 
142             if (errno)
143             {
144                 // then restore the original function address.
145                 *name_address = cast(size_t) original - cast(size_t) name_address - size_t.sizeof;
146                 errnoEnforce(false, "failed to mprotect.");
147             }
148         }
149     }
150     return original;
151 }
152 
153 
154 void* hook(in string filename, in string functionName, in void* substitutionAddress)
155 {
156     assert(filename !is null, "No file given.");
157 
158     auto lib = new SharedLibrary(filename, RTLD_LAZY);
159     const address = lib.getLoadedAddr;
160     assert(address !is null, "failed to get address that libarary loaded.");
161     return elfHook(ELF.fromFile(filename), address, functionName, substitutionAddress);
162 }