1 /* radare - LGPL - Copyright 2009-2020 - pancake, maijin, thestr4ng3r */
2
3 #include "r_util.h"
4 #include "r_anal.h"
5
6 #define VTABLE_BUFF_SIZE 10
7
8 #define VTABLE_READ_ADDR_FUNC(fname, read_fname, sz) \
9 static bool fname(RAnal *anal, ut64 addr, ut64 *buf) {\
10 ut8 tmp[sz];\
11 if (!anal->iob.read_at (anal->iob.io, addr, tmp, sz)) {\
12 return false;\
13 }\
14 *buf = read_fname (tmp);\
15 return true;\
16 }
17 VTABLE_READ_ADDR_FUNC (vtable_read_addr_le8, r_read_le8, 1)
18 VTABLE_READ_ADDR_FUNC (vtable_read_addr_le16, r_read_le16, 2)
19 VTABLE_READ_ADDR_FUNC (vtable_read_addr_le32, r_read_le32, 4)
20 VTABLE_READ_ADDR_FUNC (vtable_read_addr_le64, r_read_le64, 8)
21 VTABLE_READ_ADDR_FUNC (vtable_read_addr_be8, r_read_be8, 1)
22 VTABLE_READ_ADDR_FUNC (vtable_read_addr_be16, r_read_be16, 2)
23 VTABLE_READ_ADDR_FUNC (vtable_read_addr_be32, r_read_be32, 4)
24 VTABLE_READ_ADDR_FUNC (vtable_read_addr_be64, r_read_be64, 8)
25
r_anal_vtable_info_free(RVTableInfo * vtable)26 R_API void r_anal_vtable_info_free(RVTableInfo *vtable) {
27 if (!vtable) {
28 return;
29 }
30 r_vector_clear (&vtable->methods);
31 free (vtable);
32 }
33
r_anal_vtable_info_get_size(RVTableContext * context,RVTableInfo * vtable)34 R_API ut64 r_anal_vtable_info_get_size(RVTableContext *context, RVTableInfo *vtable) {
35 return (ut64)vtable->methods.len * context->word_size;
36 }
37
r_anal_vtable_begin(RAnal * anal,RVTableContext * context)38 R_API bool r_anal_vtable_begin(RAnal *anal, RVTableContext *context) {
39 context->anal = anal;
40 context->abi = anal->cpp_abi;
41 context->word_size = (ut8) (anal->bits / 8);
42 const bool is_arm = anal->cur->arch && r_str_startswith (anal->cur->arch, "arm");
43 if (is_arm && context->word_size < 4) {
44 context->word_size = 4;
45 }
46 switch (context->word_size) {
47 case 1:
48 context->read_addr = anal->big_endian ? vtable_read_addr_be8 : vtable_read_addr_le8;
49 break;
50 case 2:
51 context->read_addr = anal->big_endian ? vtable_read_addr_be16 : vtable_read_addr_le16;
52 break;
53 case 4:
54 context->read_addr = anal->big_endian ? vtable_read_addr_be32 : vtable_read_addr_le32;
55 break;
56 case 8:
57 context->read_addr = anal->big_endian ? vtable_read_addr_be64 : vtable_read_addr_le64;
58 break;
59 default:
60 return false;
61 }
62 return true;
63 }
64
vtable_addr_in_text_section(RVTableContext * context,ut64 curAddress)65 static bool vtable_addr_in_text_section(RVTableContext *context, ut64 curAddress) {
66 //section of the curAddress
67 RBinSection *value = context->anal->binb.get_vsect_at (context->anal->binb.bin, curAddress);
68 //If the pointed value lies in .text section
69 return value && strstr (value->name, "text") && (value->perm & 1) != 0;
70 }
71
vtable_is_value_in_text_section(RVTableContext * context,ut64 curAddress,ut64 * value)72 static bool vtable_is_value_in_text_section(RVTableContext *context, ut64 curAddress, ut64 *value) {
73 //value at the current address
74 ut64 curAddressValue;
75 if (!context->read_addr (context->anal, curAddress, &curAddressValue)) {
76 return false;
77 }
78 //if the value is in text section
79 bool ret = vtable_addr_in_text_section (context, curAddressValue);
80 if (value) {
81 *value = curAddressValue;
82 }
83 return ret;
84 }
85
vtable_section_can_contain_vtables(RBinSection * section)86 static bool vtable_section_can_contain_vtables(RBinSection *section) {
87 if (section->is_segment) {
88 return false;
89 }
90 return !strcmp (section->name, ".rodata") ||
91 !strcmp (section->name, ".rdata") ||
92 !strcmp (section->name, ".data.rel.ro") ||
93 !strcmp (section->name, ".data.rel.ro.local") ||
94 r_str_endswith (section->name, "__const");
95 }
96
section_can_contain_rtti(RBinSection * section)97 static bool section_can_contain_rtti(RBinSection *section) {
98 if (!section) {
99 return false;
100 }
101 if (section->is_data) {
102 return true;
103 }
104 return !strcmp (section->name, ".data.rel.ro") ||
105 !strcmp (section->name, ".data.rel.ro.local") ||
106 r_str_endswith (section->name, "__const");
107 }
108
vtable_is_addr_vtable_start_itanium(RVTableContext * context,RBinSection * section,ut64 curAddress)109 static bool vtable_is_addr_vtable_start_itanium(RVTableContext *context, RBinSection *section, ut64 curAddress) {
110 ut64 value;
111 if (!curAddress || curAddress == UT64_MAX) {
112 return false;
113 }
114 if (curAddress && !vtable_is_value_in_text_section (context, curAddress, NULL)) { // Vtable beginning referenced from the code
115 return false;
116 }
117 if (!context->read_addr (context->anal, curAddress - context->word_size, &value)) { // get the RTTI pointer
118 return false;
119 }
120 RBinSection *rtti_section = context->anal->binb.get_vsect_at (context->anal->binb.bin, value);
121 if (value && !section_can_contain_rtti (rtti_section)) { // RTTI ptr must point somewhere in the data section
122 return false;
123 }
124 if (!context->read_addr (context->anal, curAddress - 2 * context->word_size, &value)) { // Offset to top
125 return false;
126 }
127 if ((st32)value > 0) { // Offset to top has to be negative
128 return false;
129 }
130 return true;
131 }
132
vtable_is_addr_vtable_start_msvc(RVTableContext * context,ut64 curAddress)133 static bool vtable_is_addr_vtable_start_msvc(RVTableContext *context, ut64 curAddress) {
134 RAnalRef *xref;
135 RListIter *xrefIter;
136
137 if (!curAddress || curAddress == UT64_MAX) {
138 return false;
139 }
140 if (curAddress && !vtable_is_value_in_text_section (context, curAddress, NULL)) {
141 return false;
142 }
143 // total xref's to curAddress
144 RList *xrefs = r_anal_xrefs_get (context->anal, curAddress);
145 if (r_list_empty (xrefs)) {
146 r_list_free (xrefs);
147 return false;
148 }
149 r_list_foreach (xrefs, xrefIter, xref) {
150 // section in which currenct xref lies
151 if (vtable_addr_in_text_section (context, xref->addr)) {
152 ut8 buf[VTABLE_BUFF_SIZE];
153 context->anal->iob.read_at (context->anal->iob.io, xref->addr, buf, sizeof(buf));
154
155 RAnalOp analop = { 0 };
156 r_anal_op (context->anal, &analop, xref->addr, buf, sizeof(buf), R_ANAL_OP_MASK_BASIC);
157
158 if (analop.type == R_ANAL_OP_TYPE_MOV
159 || analop.type == R_ANAL_OP_TYPE_LEA) {
160 r_list_free (xrefs);
161 r_anal_op_fini (&analop);
162 return true;
163 }
164
165 r_anal_op_fini (&analop);
166 }
167 }
168 r_list_free (xrefs);
169 return false;
170 }
171
vtable_is_addr_vtable_start(RVTableContext * context,RBinSection * section,ut64 curAddress)172 static bool vtable_is_addr_vtable_start(RVTableContext *context, RBinSection *section, ut64 curAddress) {
173 if (context->abi == R_ANAL_CPP_ABI_MSVC) {
174 return vtable_is_addr_vtable_start_msvc (context, curAddress);
175 }
176 if (context->abi == R_ANAL_CPP_ABI_ITANIUM) {
177 return vtable_is_addr_vtable_start_itanium (context, section, curAddress);
178 }
179 r_return_val_if_reached (false);
180 }
181
r_anal_vtable_parse_at(RVTableContext * context,ut64 addr)182 R_API RVTableInfo *r_anal_vtable_parse_at(RVTableContext *context, ut64 addr) {
183 ut64 offset_to_top;
184 if (!context->read_addr (context->anal, addr - 2 * context->word_size, &offset_to_top)) {
185 return NULL;
186 }
187
188 RVTableInfo *vtable = calloc (1, sizeof (RVTableInfo));
189 if (!vtable) {
190 return NULL;
191 }
192
193 vtable->saddr = addr;
194
195 r_vector_init (&vtable->methods, sizeof (RVTableMethodInfo), NULL, NULL);
196
197 RVTableMethodInfo meth;
198 while (vtable_is_value_in_text_section (context, addr, &meth.addr)) {
199 meth.vtable_offset = addr - vtable->saddr;
200 if (!r_vector_push (&vtable->methods, &meth)) {
201 break;
202 }
203
204 addr += context->word_size;
205
206 // a ref means the vtable has ended
207 RList *ll = r_anal_xrefs_get (context->anal, addr);
208 if (!r_list_empty (ll)) {
209 r_list_free (ll);
210 break;
211 }
212 r_list_free (ll);
213 }
214 return vtable;
215 }
216
r_anal_vtable_search(RVTableContext * context)217 R_API RList *r_anal_vtable_search(RVTableContext *context) {
218 RAnal *anal = context->anal;
219 if (!anal) {
220 return NULL;
221 }
222
223 RList *vtables = r_list_newf ((RListFree)r_anal_vtable_info_free);
224 if (!vtables) {
225 return NULL;
226 }
227
228 RList *sections = anal->binb.get_sections (anal->binb.bin);
229 if (!sections) {
230 r_list_free (vtables);
231 return NULL;
232 }
233
234 r_cons_break_push (NULL, NULL);
235
236 RListIter *iter;
237 RBinSection *section;
238 r_list_foreach (sections, iter, section) {
239 if (r_cons_is_breaked ()) {
240 break;
241 }
242
243 if (!vtable_section_can_contain_vtables (section)) {
244 continue;
245 }
246
247 ut64 startAddress = section->vaddr;
248 ut64 endAddress = startAddress + (section->vsize) - context->word_size;
249 ut64 ss = endAddress - startAddress;
250 if (ss > ST32_MAX) {
251 break;
252 }
253 while (startAddress <= endAddress) {
254 if (r_cons_is_breaked ()) {
255 break;
256 }
257 if (!anal->iob.is_valid_offset (anal->iob.io, startAddress, 0)) {
258 break;
259 }
260
261 if (vtable_is_addr_vtable_start (context, section, startAddress)) {
262 RVTableInfo *vtable = r_anal_vtable_parse_at (context, startAddress);
263 if (vtable) {
264 r_list_append (vtables, vtable);
265 ut64 size = r_anal_vtable_info_get_size (context, vtable);
266 if (size > 0) {
267 startAddress += size;
268 continue;
269 }
270 }
271 }
272 startAddress += context->word_size;
273 }
274 }
275
276 r_cons_break_pop ();
277
278 if (r_list_empty (vtables)) {
279 // stripped binary?
280 r_list_free (vtables);
281 return NULL;
282 }
283 return vtables;
284 }
285
r_anal_list_vtables(RAnal * anal,int rad)286 R_API void r_anal_list_vtables(RAnal *anal, int rad) {
287 RVTableContext context;
288 r_anal_vtable_begin (anal, &context);
289
290 const char *noMethodName = "No Name found";
291 RVTableMethodInfo *curMethod;
292 RListIter *vtableIter;
293 RVTableInfo *table;
294
295 RList *vtables = r_anal_vtable_search (&context);
296
297 if (rad == 'j') {
298 PJ *pj = pj_new ();
299 if (!pj) {
300 return;
301 }
302 pj_a (pj);
303 r_list_foreach (vtables, vtableIter, table) {
304 pj_o (pj);
305 pj_kN (pj, "offset", table->saddr);
306 pj_ka (pj, "methods");
307 r_vector_foreach (&table->methods, curMethod) {
308 RAnalFunction *fcn = r_anal_get_fcn_in (anal, curMethod->addr, 0);
309 const char *const name = fcn ? fcn->name : NULL;
310 pj_o (pj);
311 pj_kN (pj, "offset", curMethod->addr);
312 pj_ks (pj, "name", r_str_get_fail (name, noMethodName));
313 pj_end (pj);
314 }
315 pj_end (pj);
316 pj_end (pj);
317 }
318 pj_end (pj);
319 r_cons_println (pj_string (pj));
320 pj_free (pj);
321 } else if (rad == '*') {
322 r_list_foreach (vtables, vtableIter, table) {
323 r_cons_printf ("f vtable.0x%08"PFMT64x" %"PFMT64d" @ 0x%08"PFMT64x"\n",
324 table->saddr,
325 r_anal_vtable_info_get_size (&context, table),
326 table->saddr);
327 r_vector_foreach (&table->methods, curMethod) {
328 r_cons_printf ("Cd %d @ 0x%08"PFMT64x"\n", context.word_size, table->saddr + curMethod->vtable_offset);
329 RAnalFunction *fcn = r_anal_get_fcn_in (anal, curMethod->addr, 0);
330 const char *const name = fcn ? fcn->name : NULL;
331 if (name) {
332 r_cons_printf ("f %s=0x%08"PFMT64x"\n", name, curMethod->addr);
333 } else {
334 r_cons_printf ("f method.virtual.0x%08"PFMT64x"=0x%08"PFMT64x"\n", curMethod->addr, curMethod->addr);
335 }
336 }
337 }
338 } else {
339 r_list_foreach (vtables, vtableIter, table) {
340 ut64 vtableStartAddress = table->saddr;
341 r_cons_printf ("\nVtable Found at 0x%08"PFMT64x"\n", vtableStartAddress);
342 r_vector_foreach (&table->methods, curMethod) {
343 RAnalFunction *fcn = r_anal_get_fcn_in (anal, curMethod->addr, 0);
344 const char *const name = fcn ? fcn->name : NULL;
345 r_cons_printf ("0x%08"PFMT64x" : %s\n", vtableStartAddress, r_str_get_fail (name, noMethodName));
346 vtableStartAddress += context.word_size;
347 }
348 r_cons_newline ();
349 }
350 }
351 r_list_free (vtables);
352 }
353