Skip to content

Commit afdc83a

Browse files
committed
Name local plt stubs generically from their got relocs in aa ##analysis
radare2 now names PLT stubs for local functions in -fPIC binaries. Instead of per-arch entry-size math in elf.c, the PLT is decoded once during aa: each stub's indirect jump reveals its GOT slot, whose reloc names the local target, and the stub is flagged sym.plt.<target>. r_anal_extract_rarg then matches sym.plt.X to sym.X so args and prototypes propagate to forwarding callers, like sym.imp does for imports. Gated by anal.plt (default true); ppc32 thunks and ppc64 ELFv1 glink stubs are left untouched.
1 parent 69229eb commit afdc83a

9 files changed

Lines changed: 348 additions & 1 deletion

File tree

libr/anal/var.c

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1602,6 +1602,19 @@ R_API void r_anal_extract_rarg(RAnal *anal, RAnalOp *op, RAnalFunction *fcn, int
16021602
int callee_rargs = 0;
16031603
char *callee = NULL;
16041604
ut64 offset = op->jump == UT64_MAX ? op->ptr : op->jump;
1605+
// a plt stub has no args of its own: a sym.plt.X flag names the local
1606+
// function it forwards to, so resolve the args at sym.X instead
1607+
if (anal->flb.f && offset != UT64_MAX) {
1608+
RFlagItem *sf = r_flag_get_by_spaces (anal->flb.f, false, offset, R_FLAGS_FS_SYMBOLS, NULL);
1609+
if (sf && sf->name && r_str_startswith (sf->name, "sym.plt.")) {
1610+
char *tn = r_str_newf ("sym.%s", sf->name + strlen ("sym.plt."));
1611+
RFlagItem *tf = r_flag_get (anal->flb.f, tn);
1612+
if (tf && tf->addr != offset) {
1613+
offset = tf->addr;
1614+
}
1615+
free (tn);
1616+
}
1617+
}
16051618
RAnalFunction *f = r_anal_get_function_at (anal, offset);
16061619
if (!f) {
16071620
RCore *core = (RCore *)anal->coreb.core;

libr/core/canal.c

Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5791,6 +5791,142 @@ R_API void r_core_anal_propagate_noreturn(RCore *core, ut64 addr) {
57915791
r_bitset_free (done);
57925792
}
57935793

5794+
// a -fPIC object calls its own globals through the plt; flag those stubs so
5795+
// calls read sym.plt.<target> instead of an anonymous fcn address
5796+
static void plt_stub_flag(RCore *core, ut64 entry, ut64 size, ut64 slot) {
5797+
const int ptrsz = R_MAX (4, core->anal->config->bits / 8);
5798+
// the reloc tree keeps file vaddrs while the decoded slot is a runtime address
5799+
RBinObject *bo = r_bin_cur_object (core->bin);
5800+
const st64 shift = bo? bo->baddr_shift: 0;
5801+
RBinReloc *rel = r_core_getreloc (core, slot - shift, ptrsz);
5802+
if (!rel || rel->import || !rel->symbol) {
5803+
return;
5804+
}
5805+
RBinSymbol *target = rel->symbol;
5806+
if (!target->vaddr || target->vaddr == UT64_MAX || target->vaddr == entry) {
5807+
return;
5808+
}
5809+
// STT_GNU_IFUNC is the only type r2 maps to LOOS
5810+
if (!target->type || (strcmp (target->type, R_BIN_TYPE_FUNC_STR)
5811+
&& strcmp (target->type, R_BIN_TYPE_LOOS_STR))) {
5812+
return;
5813+
}
5814+
const char *tname = r_bin_name_tostring2 (target->name, 'o');
5815+
if (R_STR_ISEMPTY (tname)) {
5816+
return;
5817+
}
5818+
char *fname = r_str_newf ("sym.plt.%s", tname);
5819+
r_name_filter (fname, -1);
5820+
if (!r_flag_get (core->flags, fname)) {
5821+
r_flag_set (core->flags, fname, entry, size);
5822+
}
5823+
free (fname);
5824+
}
5825+
5826+
// walk the section decoding entries: remember the last lea and load, and when an
5827+
// entry ends in an indirect jump derive the got slot it goes through
5828+
static void plt_stub_scan_section(RCore *core, RBinSection *sec) {
5829+
if (sec->vsize < 8 || sec->vsize > 0x100000) {
5830+
return;
5831+
}
5832+
const ut64 sec_vaddr = r_bin_get_vaddr (core->bin, sec->paddr, sec->vaddr);
5833+
const int len = (int)sec->vsize;
5834+
ut8 *buf = malloc (len);
5835+
if (!buf || !r_io_read_at (core->io, sec_vaddr, buf, len)) {
5836+
free (buf);
5837+
return;
5838+
}
5839+
const int minop = R_MAX (1, r_arch_info (core->anal->arch, R_ARCH_INFO_MINOP_SIZE));
5840+
ut64 entry = sec_vaddr;
5841+
ut64 lea_ptr = UT64_MAX;
5842+
ut64 load_disp = UT64_MAX;
5843+
int i = 0;
5844+
while (i < len) {
5845+
const ut64 at = sec_vaddr + i;
5846+
RAnalOp op;
5847+
const int oplen = r_anal_op (core->anal, &op, at, buf + i, len - i, R_ARCH_OP_MASK_BASIC);
5848+
const int type = op.type & R_ANAL_OP_TYPE_MASK & ~R_ANAL_OP_TYPE_COND;
5849+
const bool indirect = type == R_ANAL_OP_TYPE_UJMP
5850+
|| (type == R_ANAL_OP_TYPE_JMP && (op.type & R_ANAL_OP_TYPE_MEM));
5851+
bool ends = true;
5852+
if (oplen < 1) {
5853+
r_anal_op_fini (&op);
5854+
i += minop;
5855+
entry = sec_vaddr + i;
5856+
lea_ptr = UT64_MAX;
5857+
load_disp = UT64_MAX;
5858+
continue;
5859+
}
5860+
if (indirect) {
5861+
// x86 encodes the slot in one op; arm64-alikes split it lea/load/branch
5862+
ut64 slot = (op.ptr > 0 && op.ptr != -1)? (ut64)op.ptr: UT64_MAX;
5863+
if (slot == UT64_MAX && lea_ptr != UT64_MAX && load_disp != UT64_MAX) {
5864+
slot = lea_ptr + load_disp;
5865+
}
5866+
if (slot != UT64_MAX) {
5867+
plt_stub_flag (core, entry, at + oplen - entry, slot);
5868+
}
5869+
} else {
5870+
switch (type) {
5871+
case R_ANAL_OP_TYPE_LEA:
5872+
case R_ANAL_OP_TYPE_MOV:
5873+
if (op.ptr > 0 && op.ptr != -1) {
5874+
lea_ptr = (ut64)op.ptr;
5875+
}
5876+
ends = false;
5877+
break;
5878+
case R_ANAL_OP_TYPE_LOAD:
5879+
if (op.ptr > 0 && op.ptr != -1) {
5880+
// riscv-style loads resolve the slot in the op itself
5881+
lea_ptr = (ut64)op.ptr;
5882+
load_disp = 0;
5883+
} else {
5884+
load_disp = op.disp;
5885+
}
5886+
ends = false;
5887+
break;
5888+
case R_ANAL_OP_TYPE_JMP:
5889+
case R_ANAL_OP_TYPE_CALL:
5890+
case R_ANAL_OP_TYPE_UCALL:
5891+
case R_ANAL_OP_TYPE_RET:
5892+
case R_ANAL_OP_TYPE_TRAP:
5893+
case R_ANAL_OP_TYPE_SWI:
5894+
case R_ANAL_OP_TYPE_ILL:
5895+
case R_ANAL_OP_TYPE_UNK:
5896+
case R_ANAL_OP_TYPE_NOP: // trailing padding belongs to no entry
5897+
break;
5898+
default:
5899+
ends = false;
5900+
break;
5901+
}
5902+
}
5903+
r_anal_op_fini (&op);
5904+
i += oplen;
5905+
if (ends) {
5906+
entry = sec_vaddr + i;
5907+
lea_ptr = UT64_MAX;
5908+
load_disp = UT64_MAX;
5909+
}
5910+
}
5911+
free (buf);
5912+
}
5913+
5914+
R_API void r_core_anal_plt_stubs(RCore *core) {
5915+
R_RETURN_IF_FAIL (core);
5916+
RVecRBinSection *sections = r_bin_get_sections_vec (core->bin);
5917+
if (!sections) {
5918+
return;
5919+
}
5920+
r_flag_space_push (core->flags, R_FLAGS_FS_SYMBOLS);
5921+
RBinSection *sec;
5922+
R_VEC_FOREACH (sections, sec) {
5923+
if (sec->name && strstr (sec->name, "plt") && (sec->perm & R_PERM_X)) {
5924+
plt_stub_scan_section (core, sec);
5925+
}
5926+
}
5927+
r_flag_space_pop (core->flags);
5928+
}
5929+
57945930
R_API char *r_core_anal_get_comments(RCore *core, ut64 addr) {
57955931
if (core) {
57965932
const char *type = r_meta_get_string (core->anal, R_META_TYPE_VARTYPE, addr);

libr/core/cconfig.c

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4103,6 +4103,7 @@ R_API int r_core_config_init(RCore *core) {
41034103
SETCB ("anal.ignbithints", "false", &cb_anal_ignbithints, "ignore the ahb hints (only obey asm.bits)");
41044104
SETI ("anal.symsort", 0, "sort symbols before 'aaa'nalysis (-1: backward, 0: no sort, 1: forward");
41054105
SETB ("anal.imports", "true", "run af@@@i in aa for better noreturn propagation");
4106+
SETB ("anal.plt", "true", "name plt stubs of locally defined functions as sym.plt.* during aa");
41064107
SETB ("anal.calls", "false", "make basic af analysis walk into calls");
41074108
SETB ("anal.autoname", "false", "speculatively set a name for the functions, may result in some false positives");
41084109
SETB ("anal.hasnext", "false", "continue analysis after each function");

libr/core/cmd_anal.inc.c

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15137,6 +15137,11 @@ static bool cmd_aa(RCore *core, bool aaa) {
1513715137
r_cons_break_push (core->cons, NULL, NULL);
1513815138
r_cons_break_timeout (core->cons, r_config_get_i (core->config, "anal.timeout"));
1513915139

15140+
if (r_config_get_b (core->config, "anal.plt")) {
15141+
logline (core, 8, "Name local plt stubs from their got relocs (anal.plt)");
15142+
r_core_anal_plt_stubs (core);
15143+
}
15144+
1514015145
// required for noreturn
1514115146
if (r_config_get_b (core->config, "anal.imports")) {
1514215147
logline (core, 10, "Analyze imports (af@@@i)");

libr/include/r_core.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -734,6 +734,7 @@ R_API int r_core_get_stacksz(RCore *core, ut64 from, ut64 to);
734734
R_API RAnalOp* r_core_anal_op(RCore *core, ut64 addr, int mask);
735735
R_IPI int core_type_by_addr(RCore *core, ut64 addr);
736736
R_API void r_core_anal_esil(RCore *core, const char *str, const char *addr);
737+
R_API void r_core_anal_plt_stubs(RCore *core);
737738
R_API void r_core_anal_fcn_merge(RCore *core, ut64 addr, ut64 addr2);
738739
R_API const char *r_core_anal_optype_colorfor(RCore *core, ut64 addr, ut8 ch, bool verbose);
739740
R_API ut64 r_core_anal_address(RCore *core, ut64 addr);

test/db/anal/autoname

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ EXPECT=<<EOF
2727
| | '--------.
2828
| | |
2929
.--------------------. .---------------------------. .----------------------------.
30-
| sub.add2_1050 | | sub.__cxa_finalize_1040 | | sym.deregister_tm_clones |
30+
| sym.plt.add2 | | sub.__cxa_finalize_1040 | | sym.deregister_tm_clones |
3131
`--------------------' `---------------------------' `----------------------------'
3232
EOF
3333
RUN

test/db/anal/plt-local

Lines changed: 183 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,183 @@
1+
NAME=x86_64 shared lib: aa names the plt stub of a locally defined function
2+
FILE=bins/elf/pltrel/add.so
3+
CMDS=<<EOF
4+
aa
5+
fs symbols
6+
f~plt.
7+
EOF
8+
EXPECT=<<EOF
9+
0x00001050 10 sym.plt.add2
10+
EOF
11+
RUN
12+
13+
NAME=x86_64 shared lib: call through a local plt stub names its target
14+
FILE=bins/elf/pltrel/add.so
15+
CMDS=<<EOF
16+
aa
17+
pd 1 @ 0x1135
18+
EOF
19+
EXPECT=<<EOF
20+
| 0x00001135 e816ffffff call sym.plt.add2
21+
EOF
22+
RUN
23+
24+
NAME=x86_64 shared lib: the stub function takes the flag name
25+
FILE=bins/elf/pltrel/add.so
26+
CMDS=<<EOF
27+
aa
28+
s 0x1050
29+
afn
30+
EOF
31+
EXPECT=<<EOF
32+
sym.plt.add2
33+
EOF
34+
RUN
35+
36+
NAME=x86_64 shared lib: a locally defined stub target is not an import
37+
FILE=bins/elf/pltrel/add.so
38+
CMDS=<<EOF
39+
aa
40+
ii~add2
41+
EOF
42+
EXPECT=<<EOF
43+
EOF
44+
RUN
45+
46+
NAME=glibc ld.so: interposable locals get their plt stubs named
47+
FILE=bins/elf/ld-2.31.so
48+
CMDS=<<EOF
49+
aa
50+
fs symbols
51+
f~plt.
52+
EOF
53+
EXPECT=<<EOF
54+
0x00002080 11 sym.plt.free
55+
0x00002090 11 sym.plt._dl_catch_exception
56+
0x000020a0 11 sym.plt.malloc
57+
0x000020b0 11 sym.plt._dl_signal_exception
58+
0x000020c0 11 sym.plt.calloc
59+
0x000020d0 11 sym.plt.realloc
60+
0x000020e0 11 sym.plt._dl_signal_error
61+
0x000020f0 11 sym.plt._dl_catch_error
62+
EOF
63+
RUN
64+
65+
NAME=aarch64: adrp+ldr stubs of local functions get named
66+
FILE=bins/elf/librsjni_androix.so
67+
CMDS=<<EOF
68+
aa
69+
fs symbols
70+
f~plt.
71+
EOF
72+
EXPECT=<<EOF
73+
0x0000b7f0 16 sym.plt._Z11loadSymbolsPvR13dispatchTablei
74+
0x0000b830 16 sym.plt._Z14loadIOSuppSymsPvR8ioSuppDT
75+
EOF
76+
RUN
77+
78+
NAME=x86_64 caller forwarding its args through a local plt stub inherits them
79+
FILE=bins/elf/pltrel/add.so
80+
ARGS=-e io.cache=true
81+
CMDS=<<EOF
82+
wx e832ffffffc3 @ 0x1119
83+
aa
84+
afs @ 0x1119
85+
EOF
86+
EXPECT=<<EOF
87+
void sym.add (int64_t arg1, int64_t arg2);
88+
EOF
89+
RUN
90+
91+
NAME=plt stub args survive a non default base address
92+
FILE=bins/elf/pltrel/add.so
93+
ARGS=-B 0x400000 -e io.cache=true
94+
CMDS=<<EOF
95+
wx e832ffffffc3 @ 0x401119
96+
aa
97+
fs symbols
98+
f~plt.
99+
afs @ 0x401119
100+
EOF
101+
EXPECT=<<EOF
102+
0x00401050 10 sym.plt.add2
103+
void sym.add (int64_t arg1, int64_t arg2);
104+
EOF
105+
RUN
106+
107+
NAME=a typedef prototype propagates through a local plt stub by name
108+
FILE=bins/elf/pltrel/add.so
109+
ARGS=-e io.cache=true
110+
CMDS=<<EOF
111+
"td int add2(int a, int b);"
112+
wx e832ffffffc3 @ 0x1119
113+
aa
114+
afs @ 0x1119
115+
EOF
116+
EXPECT=<<EOF
117+
void sym.add (int a, int b);
118+
EOF
119+
RUN
120+
121+
NAME=x86_64 caller that sets up its own arg registers is unaffected by stubs
122+
FILE=bins/elf/pltrel/add.so
123+
CMDS=<<EOF
124+
aa
125+
afs @ sym.add
126+
EOF
127+
EXPECT=<<EOF
128+
void sym.add (int64_t arg1, int64_t arg2);
129+
EOF
130+
RUN
131+
132+
NAME=anal.plt=false disables local plt stub naming
133+
FILE=bins/elf/pltrel/add.so
134+
ARGS=-e anal.plt=false
135+
CMDS=<<EOF
136+
aa
137+
fs symbols
138+
f~plt.~?
139+
EOF
140+
EXPECT=<<EOF
141+
0
142+
EOF
143+
RUN
144+
145+
NAME=imports-only executable grows no local plt stubs
146+
FILE=bins/elf/crackme0x05
147+
CMDS=<<EOF
148+
aa
149+
fs symbols
150+
f~plt.~?
151+
EOF
152+
EXPECT=<<EOF
153+
0
154+
EOF
155+
RUN
156+
157+
NAME=ppc64 ELFv1 glink stubs are not resolved by decoding (known limitation)
158+
FILE=bins/elf/ppc64v1-libz.so
159+
CMDS=<<EOF
160+
aa
161+
fs symbols
162+
f~plt.~?
163+
EOF
164+
EXPECT=<<EOF
165+
0
166+
EOF
167+
RUN
168+
169+
NAME=glibc libc.so: local jump-slot and plt.got stubs both get named
170+
FILE=bins/elf/libc-2.31.so
171+
CMDS=<<EOF2
172+
aa
173+
fs symbols
174+
f~plt.
175+
EOF2
176+
EXPECT=<<EOF2
177+
0x00025310 11 sym.plt.malloc
178+
0x00025330 11 sym.plt.free
179+
0x00025370 11 sym.plt.realloc
180+
0x00025400 11 sym.plt.memalign
181+
0x00025550 11 sym.plt.calloc
182+
EOF2
183+
RUN

test/db/cmd/r2pipe2

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,11 @@ EXPECT=<<EOF
5454
"origin": "logline",
5555
"message": "Analyze imports (af@@@i)"
5656
},
57+
{
58+
"type": "INFO",
59+
"origin": "logline",
60+
"message": "Name local plt stubs from their got relocs (anal.plt)"
61+
},
5762
{
5863
"type": "INFO",
5964
"origin": "logline",

0 commit comments

Comments
 (0)