Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

CFG party foul: B.W on thumb and ret-to-grandparent functions cause incorrect function recovery #1286

Closed
subwire opened this issue Nov 1, 2018 · 2 comments
Assignees
Labels
feature Adding a new control knob to something

Comments

@subwire
Copy link
Contributor

subwire commented Nov 1, 2018

Disclaimer: The angr suite is maintained by a small team of volunteers. While we cannot guarantee any timeliness for fixes and enhancements, we will do our best. For more real-time help with angr, from us and the community, join our Slack.


Describe the bug
In some Thumb/Cortex-M binaries, the compiler makes heavy use of the B.W instruction to do a frameless call. This happens a lot in various kinds of wrapper functions.
In certain cases, this will cause functions to become part of other functions when they should not.
It's pretty clear, from at least the way GCC uses b.w, that this should be the boundary between one function and another. It's not a call, it's a jump-out, as the thing jumped into will do the real ret.

Here's an example. This is fputs in the attached binary. Note that the real fputs is at 0x08002efd, and the B.W at the bottom points to another function _fputs_r, with the real prologue there at 0x08002eb1

0x8002eb1:	push	{r4, r5, lr}
0x8002eb3:	mov	r5, r0
0x8002eb5:	sub	sp, #0x1c
0x8002eb7:	mov	r0, r1
0x8002eb9:	mov	r4, r2
0x8002ebb:	str	r1, [sp, #4]
0x8002ebd:	bl	#0x8004a61
0x8002ec1:	add	r2, sp, #4
0x8002ec3:	movs	r3, #1
0x8002ec5:	str	r0, [sp, #0x14]
0x8002ec7:	str	r0, [sp, #8]
0x8002ec9:	str	r2, [sp, #0xc]
0x8002ecb:	str	r3, [sp, #0x10]
0x8002ecd:	cbz	r5, #0x8002ed3
0x8002ecf:	ldr	r3, [r5, #0x38]
0x8002ed1:	cbz	r3, #0x8002ef5
0x8002ed3:	ldrh	r3, [r4, #0xc]
0x8002ed5:	lsls	r2, r3, #0x12
0x8002ed7:	bmi	#0x8002ee7
0x8002ed9:	ldr	r2, [r4, #0x64]
0x8002edb:	orr	r3, r3, #0x2000
0x8002edf:	bic	r2, r2, #0x2000
0x8002ee3:	strh	r3, [r4, #0xc]
0x8002ee5:	str	r2, [r4, #0x64]
0x8002ee7:	mov	r0, r5
0x8002ee9:	mov	r1, r4
0x8002eeb:	add	r2, sp, #0xc
0x8002eed:	bl	#0x8003445
0x8002ef1:	add	sp, #0x1c
0x8002ef3:	pop	{r4, r5, pc}
0x8002ef5:	mov	r0, r5
0x8002ef7:	bl	#0x8002db1
0x8002efb:	b	#0x8002ed3
0x8002efd:	ldr	r3, [pc, #8]
0x8002eff:	mov	r2, r1
0x8002f01:	mov	r1, r0
0x8002f03:	ldr	r0, [r3]
0x8002f05:	b.w	#0x8002eb1

In other words, if we jump to a real prologue (or something we already consider the start of a function) let's not try to merge them. Heck, if the program uses B.W at all, and the offset is less than 16 bits, this is very much definitely 100% for certain a jumpout. (note that if the size is more than 16 bits, it could be one of those long-jumps chosen by the compiler to jump around in a large function, but who does that??)

We can deal with this nicely when there are symbols (because CFG will just use the symbols) but when there aren't, we get it wrong.
Attached are the ELF and stripped ELF versions of the same program that triggers this.
You may want to be on fix/arm_cfg_party_2 or things will not work

Environment Information
Many common issues are caused by problems with the local Python environment.
Before submitting, double-check that your versions of all modules in the angr suite (angr, cle, pyvex, ...) are up to date.
Please include the output of python -m angr.misc.bug_report here.

(angr-pypy3) edg@syrinx:~/code/angr-py3k$ python -m angr.misc.bug_report
angr environment report
=============================
Date: 2018-10-31 11:10:56.014870
Running in virtual environment at /home/edg/.virtualenvs/angr-pypy3
Platform: linux-x86_64
Python version: 3.5.3 (fdd60ed87e94, Apr 24 2018, 06:10:04)
[PyPy 6.0.0 with GCC 6.2.0 20160901]
######## angr #########
Python found it in /home/edg/code/angr-py3k/angr/angr
Pip version angr 8.18.10.1
Git info:
	Current commit 1d2dd00f953e8bc4f09ce7105f9003efce6be49c from branch fix/arm_cfg_party_2
	Checked out from remote origin: git@github.com:angr/angr
######## ailment #########
Python found it in /home/edg/code/angr-py3k/ailment/ailment
Pip version ailment 8.18.10.1
Git info:
	Current commit db8dea661a45044fd32795df86715053f6da12e9 from branch fix/clinic_clinic
Could not resolve tracking branch or remote info!
######## cle #########
Python found it in /home/edg/code/angr-py3k/cle/cle
Pip version cle 8.18.10.1
Git info:
	Current commit 70601f8cc273982e9a72fa7a3427b2b37ff2f65e from branch fix/arm_cfg_party_2
	Checked out from remote origin: git@github.com:angr/cle
######## pyvex #########
Python found it in /home/edg/code/angr-py3k/pyvex/pyvex
Pip version pyvex 8.18.10.5
Git info:
	Current commit e82cfd75f106130253140055a69c29eb8afd3b98 from branch fix/arm_cfg_party_2
	Checked out from remote origin: git@github.com:angr/pyvex
######## claripy #########
Python found it in /home/edg/code/angr-py3k/claripy/claripy
Pip version claripy 8.18.10.1
Git info:
	Current commit 49336fa3d3ad0ed941a47ab9ed8edbd86c2d2386 from branch master
	Checked out from remote origin: git@github.com:angr/claripy
######## archinfo #########
Python found it in /home/edg/code/angr-py3k/archinfo/archinfo
Pip version archinfo 8.18.10.5
Git info:
	Current commit 74fa132436858bd5c5c4d8017178e8e2f8222d19 from branch fix/arm_cfg_party_2
	Checked out from remote origin: git@github.com:angr/archinfo
######## ana #########
Python found it in /home/edg/code/angr-py3k/ana/ana
Pip version ana 0.5
Git info:
	Current commit 48657dc6500e33f5818b98003f1f179bfe03e1cc from branch master
	Checked out from remote origin: https://git:@github.com/zardus/ana
######## z3 #########
Python found it in /home/edg/.virtualenvs/angr-pypy3/site-packages/z3
Pip version not found!
Couldn't find git info
######## unicorn #########
Python found it in /home/edg/.virtualenvs/angr-pypy3/site-packages/unicorn
Pip version unicorn 1.0.1
Couldn't find git info
######### Native Module Info ##########
angr: <CDLL '/home/edg/code/angr-py3k/angr/angr/lib/angr_native.so', handle 3f51b60 at 0x7efc3aeeeec8>
unicorn: <CDLL '/home/edg/.virtualenvs/angr-pypy3/site-packages/unicorn/lib/libunicorn.so', handle 2550090 at 0x7efc3e3b0368>
pyvex: <cffi.api._make_ffi_library.<locals>.FFILibrary object at 0x00007efc3f12a9f8>
z3: <CDLL '/home/edg/.virtualenvs/angr-pypy3/site-packages/z3/lib/libz3.so', handle 5024e80 at 0x7efc39fc2bb8>

To Reproduce

import angr
p = angr.Project("a_thing-stripped.elf")
cfg = p.analyses.CFGFast(resolve_indirect_jumps=True, force_complete_scan=False, normalize=True, symbols=False)
f = p.kb.functions[0x08002efd]
assert len(list(f.blocks)) == 1

a_thingy.zip

Additional context
Add any other context about the problem here.

ltfish added a commit that referenced this issue Nov 16, 2018
I was assuming that a block can be split into at most two different
blocks, and the first half only has a boring jump targeting the second
half. Unfortunately, this is not the case in ARM. This bug is triggered
when normalizing CFG for ARM binary "a_thingy-stripped.elf" (see angr
issue #1286), function 0x8002b71. The block 0x8002b70 can be split into
four small blocks.
ltfish added a commit that referenced this issue Nov 16, 2018
- Implemented a StackPointerTracker analysis.
- Tail-call optimization detection is controlled by option
  "detect_tail_calls" in CFGFast. It is disabled by default (since tail
  call optimization detection is usually not important, and tracking stack
  pointer offets can be very costly sometimes).
@ltfish ltfish added the feature Adding a new control knob to something label Nov 16, 2018
@ltfish
Copy link
Member

ltfish commented Nov 16, 2018

Implemented (proper) tail-call optimization detection. A good evening spent.

@ltfish
Copy link
Member

ltfish commented Nov 16, 2018

@subwire Can you please write a test case and provide a sharable binary (unless a_thingy-stripped.elf is safe to be shared in angr/binaries repo) for this issue?

ltfish added a commit that referenced this issue Nov 17, 2018
* Fix a bug in CFG/Function normalization.

I was assuming that a block can be split into at most two different
blocks, and the first half only has a boring jump targeting the second
half. Unfortunately, this is not the case in ARM. This bug is triggered
when normalizing CFG for ARM binary "a_thingy-stripped.elf" (see angr
issue #1286), function 0x8002b71. The block 0x8002b70 can be split into
four small blocks.

* Fix a comparison between None and addr.
ltfish added a commit that referenced this issue Nov 17, 2018
- Implemented a StackPointerTracker analysis.
- Tail-call optimization detection is controlled by option
  "detect_tail_calls" in CFGFast. It is disabled by default (since tail
  call optimization detection is usually not important, and tracking stack
  pointer offets can be very costly sometimes).
@ltfish ltfish closed this as completed in dca96c3 Nov 17, 2018
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
feature Adding a new control knob to something
Projects
None yet
Development

No branches or pull requests

2 participants