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