ARM64: ADRP+LDR GOT access not symbolified when split across basic blocks
Summary
When an ARM64 binary contains an adrp+ldr GOT access pair split across different basic blocks, ddisasm does not recognize them as GOT accesses. It emits a raw data label for the adrp and a hardcoded numeric offset for the ldr, instead of symbolic :got:/:got_lo12: references. On reassembly, the GOT layout may change, and the hardcoded offsets silently point to the wrong GOT entries.
Details
ddisasm correctly handles the common case where adrp+ldr are in the same block:
adrp x2, :got:__stack_chk_guard
ldr x2, [x2, :got_lo12:__stack_chk_guard]
But when the original binary has them in separate blocks, ddisasm emits:
Block A (function prologue)
adrp x2, .L_368000 # raw data label, not :got:__stack_chk_guard
b .L_146d10
Block B:
.L_146d10:
ldr x2, [x2, #3880] # raw offset, not :got_lo12:__stack_chk_guard
The original binary has __stack_chk_guard at GOT page offset 0xF28 (= 3880). After reassembly, __stack_chk_guard moves to offset 0xFB0 within the same page. The 2914 symbolified instances update correctly; the 2 raw-offset instances do not, and now load from the GOT entry for ospeed instead.
This was found attempting to use ddisasm on /usr/bin/vi from Ubuntu 22.04 aarch64, using ddisasm 1.8.0 (from grammatech/ddisasm Docker image).
After reassembly, the rewritten binary crashes with __stack_chk_fail when saving a file. The function prologue loads the stack canary via the raw offset (wrong GOT entry), while the epilogue checks it via :got_lo12:__stack_chk_guard (correct GOT entry). The mismatch triggers the stack canary check.
I've attached the vi binary as well as the asm output from gtirb-pprinter --asm for the binary. Function FUN_13dff0 demonstrates this issue - the epilogue correctly symbolises __stack_chk_guard but the prologue contains:
adrp x2, .L_368000
stp fp,lr,[sp]
.cfi_offset 29, -4160
.cfi_offset 30, -4152
mov fp,sp
b .L_146d10
...
.L_146d10:
ldr x2,[x2,#3880]
b .L_13e008
which should be loading __stack_chk_guard into x2, but instead loads a data section and a hardcoded offset.
repro.zip
ARM64: ADRP+LDR GOT access not symbolified when split across basic blocks
Summary
When an ARM64 binary contains an
adrp+ldrGOT access pair split across different basic blocks, ddisasm does not recognize them as GOT accesses. It emits a raw data label for theadrpand a hardcoded numeric offset for theldr, instead of symbolic:got:/:got_lo12:references. On reassembly, the GOT layout may change, and the hardcoded offsets silently point to the wrong GOT entries.Details
ddisasm correctly handles the common case where
adrp+ldrare in the same block:But when the original binary has them in separate blocks, ddisasm emits:
Block A (function prologue)
Block B:
The original binary has __stack_chk_guard at GOT page offset 0xF28 (= 3880). After reassembly, __stack_chk_guard moves to offset 0xFB0 within the same page. The 2914 symbolified instances update correctly; the 2 raw-offset instances do not, and now load from the GOT entry for ospeed instead.
This was found attempting to use ddisasm on /usr/bin/vi from Ubuntu 22.04 aarch64, using ddisasm 1.8.0 (from grammatech/ddisasm Docker image).
After reassembly, the rewritten binary crashes with __stack_chk_fail when saving a file. The function prologue loads the stack canary via the raw offset (wrong GOT entry), while the epilogue checks it via :got_lo12:__stack_chk_guard (correct GOT entry). The mismatch triggers the stack canary check.
I've attached the vi binary as well as the asm output from gtirb-pprinter --asm for the binary. Function
FUN_13dff0demonstrates this issue - the epilogue correctly symbolises__stack_chk_guardbut the prologue contains:which should be loading __stack_chk_guard into x2, but instead loads a data section and a hardcoded offset.
repro.zip