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