Skip to content

gum_interceptor_replace trampoline breaks ARM PC-relative conditional branches (beq) on ARMv7 #1095

Description

@AkosBuhler

Summary

On ARMv7 (ARM mode), gum_interceptor_replace() sometimes produces a broken trampoline when the overwritten prologue contains a PC-relative conditional branch (e.g. beq). The branch instruction appears to be copied into the trampoline without relocating/fixing up its target, so the branch target becomes incorrect when executed from the trampoline address.

GumReplaceReturn gum_interceptor_replace (GumInterceptor * self,
    gpointer function_address, gpointer replacement_function,
    gpointer replacement_data, gpointer * original_function);

If the original function’s prologue includes a PC-relative beq, the trampoline’s copy of that instruction branches to the wrong target (because it’s now running from a different address). This can lead to incorrect control-flow / crashes.

Reproduction notes

  • Hook created with gum_interceptor_replace()
  • function_address redirects correctly to replacement_function
  • Calling original_function executes the trampoline
  • Trampoline does not correctly handle relocation of PC-relative conditional branches in the copied prologue

Original function (ARM, disassembly)

000002d8 <BEF_RIO_EBD1318_ReadDin>:
 2d8:	e3500000 	cmp	r0, #0
 2dc:	0a000012 	beq	32c <BEF_RIO_EBD1318_ReadDin+0x54>
 2e0:	e92d4070 	push	{r4, r5, r6, lr}
 2e4:	e1a05002 	mov	r5, r2
 2e8:	e5902004 	ldr	r2, [r0, #4]
 2ec:	e3a03001 	mov	r3, #1
 2f0:	e1a03113 	lsl	r3, r3, r1
 2f4:	e1a04001 	mov	r4, r1
 2f8:	e1d33002 	bics	r3, r3, r2
 2fc:	0a000007 	beq	320 <BEF_RIO_EBD1318_ReadDin+0x48>
 300:	e5900000 	ldr	r0, [r0]
 304:	ebfffffe 	bl	0 <ebd1318_get_input>
 308:	e7eb3050 	ubfx	r3, r0, #0, #12
 30c:	e3a00000 	mov	r0, #0
 310:	e1a03453 	asr	r3, r3, r4
 314:	e2033001 	and	r3, r3, #1
 318:	e5c53000 	strb	r3, [r5]
 31c:	e8bd8070 	pop	{r4, r5, r6, pc}
 320:	e30b05c4 	movw	r0, #46532	; 0xb5c4
 324:	e34f0fff 	movt	r0, #65535	; 0xffff
 328:	e8bd8070 	pop	{r4, r5, r6, pc}
 32c:	e3e0000d 	mvn	r0, #13
 330:	e12fff1e 	bx	lr

Trampoline (disassembly)

BEF_RIO_EBD1318_ReadDin:
0x00000000b6ef0924:  00 00 50 E3    cmp r0, #0
0x00000000b6ef0928:  00 F0 9F E5    ldr pc, [pc]
0x00000000b6ef092c:  00 F0 9F E5    ldr pc, [pc]
0x00000000b67aa90c:  70 D7 14 00    address: 0x14d770

in my binary BEF_RIO_EBD1318_ReadDin was located at 0x14d71c so jump to 0x14d770 is an offset of 0x54 the error branch of the original function.

Why this looks wrong

beq encodes a PC-relative offset. If it’s copied into a trampoline at a different address (without rewriting it to an absolute/relocated form), the branch destination changes. The trampoline needs to relocate conditional branches (e.g. transform into an inverted conditional + absolute jump, or similar).

Environment

  • CPU: Cortex-A9, ARMv7l, Little Endian (ARM mode, not Thumb)

  • OS: Linux 5.10

  • frida-gum: version 17.7.2

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions