-
Notifications
You must be signed in to change notification settings - Fork 5
/
Copy pathpatcher-payload.c
206 lines (161 loc) · 6.3 KB
/
patcher-payload.c
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
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <stdint.h>
#include <string.h>
#include <time.h>
#include "patcher-payload.h"
#define LOGFILE "/data/local/tmp/patcher-payload.log"
#define LOGFILE_MODE "w" // fopen() mode; 'a'ppend or 'w'rite (with truncation).
#define OWN_LIBNAME "patcher-payload" // Our own name, for skipping the GOT patching.
#define MAIN_PROCESS_NAME "dtv_svc" // Name of the main binary (dl* just return "").
#define UNUSED __attribute__((unused))
// Log file, opened in init().
static FILE *logf = NULL;
// A machine word.
typedef uint32_t word;
// Log handler; to be used with the log() macro that passes file and line.
__attribute__((format(printf, 3, 4)))
static void do_log(const char *filename, int lineno, const char *format, ...) {
if (!logf)
return;
char tsbuf[256] = {0};
time_t t = time(NULL);
if (!strftime(tsbuf, sizeof(tsbuf)-1, "[%F %T] ", localtime(&t)))
tsbuf[0] = '\0';
fprintf(logf, "%s%s:%d: ", tsbuf, filename, lineno);
va_list args;
va_start(args, format);
vfprintf(logf, format, args);
va_end(args);
if (*format && format[strlen(format)-1] != '\n')
fputc('\n', logf);
fflush(logf);
}
// Log with current filename and line.
#define log(...) do_log(__FILE__, __LINE__, __VA_ARGS__)
// A request to patch each loaded library’s GOT with pointers to oldval pointing to newval.
struct patch_got_req {
word oldval;
word newval;
};
// An item in the undo_list, for unpatching GOTs in fini().
struct undo_item {
word *addr;
word oldval;
};
// Patched words with their old value.
#define UNDO_LIST_SIZE 256
static struct undo_item undo_list[UNDO_LIST_SIZE];
// Number of undo_list elements populated. Top of stack is undo_list[undo_size-1].
static unsigned int undo_size = 0;
// Define a function to be overridden. The original function is available as orig_symbol, this
// macro emits the declaration for my_symbol.
#define DEFINE_OVERRIDE(rettype, symname, ...) \
static rettype (*orig_##symname)(__VA_ARGS__) = NULL; \
\
static rettype my_##symname(__VA_ARGS__)
// Initializes an override defined by DEFINE_OVERRIDE.
#define INIT_OVERRIDE(libname, symname) \
do_override(libname, #symname, (void (**)())&orig_##symname, my_##symname)
// Override a_mtktvapi_config_get_value and print args and return value.
DEFINE_OVERRIDE(char *, a_mtktvapi_config_get_value, int16_t grp, char *cfg, int32_t *value) {
char *ret = orig_a_mtktvapi_config_get_value(grp, cfg, value);
log("a_mtktvapi_config_get_value(grp=%d, cfg=%s, *value=%ld) = %s", grp, cfg, (value?*value:0xDEAD), ret);
return ret;
}
DEFINE_OVERRIDE(int32_t, mtktvapi_config_custom_map_id_2_string, int32_t id, char *string) {
int32_t ret = orig_mtktvapi_config_custom_map_id_2_string(id, string);
log("mtktvapi_config_custom_map_id_2_string(%ld, %s) = %ld", id, string, ret);
return ret;
}
// Patch a GOT as described in req. The GOT is expected to be within start-end and contain the
// value to patch at most once. (We can’t really easily tell where the GOT in the ELF segment
// is, so we search all of it and if we found the value twice, the chances of either of them
// being some other random data is nonzero.)
static int patch_got(word *start, word *end, const char *filename, struct patch_got_req *req) {
if (strstr(filename, OWN_LIBNAME))
return 0;
if (!*filename)
filename = MAIN_PROCESS_NAME;
word *off = NULL;
for (word *p = start; p < end; p++) {
if (*p != req->oldval)
continue;
if (off) {
log("patch_got(%p, %p, \"%s\"): found old value twice (at %p and at %p), not patching.",
start, end, filename, off, p);
return -1;
}
off = p;
}
if (!off)
return 0;
if (++undo_size > sizeof(undo_list)) {
log("Can't store more than %d undo items, increase UNDO_LIST_SIZE.", sizeof(undo_list));
return -1;
}
undo_list[undo_size-1].addr = off;
undo_list[undo_size-1].oldval = *off;
*off = req->newval;
log("Patched GOT of %s at %p (from 0x%08lx to 0x%08lx)", filename, off,
undo_list[undo_size-1].oldval, *off);
return 0;
}
// Callback for dl_iterate_phdr. Walks over all ELF segments and calls patch_got for each that
// is likely to contain a GOT (i.e. is of type LOAD and read- and writable).
static int find_got_phdr(struct dl_phdr_info *info, size_t size UNUSED, struct patch_got_req *req) {
for (int i = 0; i < info->phnum; i++) {
if (info->phdr[i].type != PT_LOAD || (info->phdr[i].flags & PF_RW) != PF_RW)
continue;
void *start = info->addr + info->phdr[i].vaddr;
void *end = start + info->phdr[i].memsz;
if (patch_got(start, end, info->name, req) < 0)
return -1;
}
return 0;
}
// Overrides symname from libname with override, storing the original symbol address in *orig.
static void do_override(const char *libname, const char *symname, void (**orig)(), void *override) {
if (*orig) {
log("orig_%s already points to %p, not overriding again", symname, *orig);
return;
}
void *hdl = dlopen(libname, RTLD_NOW|RTLD_NOLOAD);
*orig = dlsym(hdl, symname);
if (hdl)
dlclose(hdl);
if (!*orig) {
log("Can't find %s in hdl %p (%s)", symname, hdl, libname);
return;
}
log("Patching GOTs referencing %s = %p", symname, *orig);
struct patch_got_req req = {
.oldval = (word)*orig,
.newval = (word)override,
};
dl_iterate_phdr(find_got_phdr, &req);
}
// Sets up logging and installs all hooks.
__attribute__((constructor)) static void init() {
if (logf)
fclose(logf);
logf = fopen(LOGFILE, LOGFILE_MODE);
if (!logf)
return;
log("Initializing");
INIT_OVERRIDE("libmtkapp.so", a_mtktvapi_config_get_value);
INIT_OVERRIDE("libmtkapp.so", mtktvapi_config_custom_map_id_2_string);
log("Initialized");
}
// Uninstalls all hooks and closes the logfile.
__attribute__((destructor)) static void fini() {
log("Tearing down.");
while (undo_size > 0) {
*(undo_list[undo_size-1].addr) = undo_list[undo_size-1].oldval;
log("Undid GOT patch at %p", undo_list[undo_size-1].addr);
undo_size--;
}
if (fclose(logf) == 0)
logf = NULL;
}