shadow v2 public release
About four months ago (April 2017), Vasilis Tsaousoglou and myself presented our work on exploiting Android's libc allocator at the 2017 INFILTRATE conference (Miami, Florida). Since version 5.0, Android has adopted the jemalloc allocator as its default libc malloc(3) implementation. For our talk we extended our previously released jemalloc heap exploration and exploitation tool called 'shadow' to support Android (both ARM32 and ARM64), and demonstrated its use on understanding the impact of libc heap corruption vulnerabilities. We also presented new jemalloc/Android-specific exploitation techniques for double free and arbitrary free vulnerabilities.
This research constitutes a continuation of our work on the exploitation of the jemalloc memory allocator, a brief history of which is presented below:
- Our 2012 Phrack paper on exploiting the standalone version of jemalloc. Focused mainly on metadata corruption attacks, and included an exploit against jemalloc/FreeBSD-libc in the context of the VLC media player.
- Our 2012 Black Hat USA talk on jemalloc metadata corruption attacks in the context of the Mozilla Firefox browser.
- Our 2015 INFILTRATE talk on jemalloc/Firefox application-specific exploitation methodologies.
- Our 2017 INFILTRATE talk on Android's use of jemalloc, that this blog post refers to.
We are now pleased to announce that version 2 of our shadow tool is available as open source software on the official CENSUS GitHub page. Apart from the tool's source code, the repository also includes work-in-progress documentation on setting up an Android userland debugging environment for utilizing shadow, a quick overview of Android's jemalloc structures using shadow, and some notes on how double, unaligned and arbitrary free() bugs behave on Android's jemalloc.
Let's see a brief walkthrough of shadow using an emulated double free bug in jemalloc/Android.
Initially, we view the thread cache ("tcache") bin stack for bin index 2:
(gdb) jetcache -b 2
index lg_fill_div ncached low_water ncached_max
---------------------------------------------------------------
2 1 3 0x2 8
stack
----------------
0x7160571180
0x715efa55a0
0x715efa55c0
Bin index 2 corresponds to sizes 0x11-0x20:
(gdb) jebininfo
index region_size run_size no_regions
--------------------------------------------------
0 0x8 0x1000 512
1 0x10 0x1000 256
2 0x20 0x1000 128
3 0x30 0x3000 256
...
We now emulate the bug condition by calling free() with the same address twice using gdb:
(gdb) jeinfo 0x715efa5580
parent address size
--------------------------------------
arena 0x717a402200 -
chunk 0x715ee00000 0x200000
run 0x715efa5000 0x1000
region 0x715efa5580 0x20
(gdb) p free(0x715efa5580)
$4 = 32
(gdb) p free(0x715efa5580)
$5 = 32
We now parse the heap again and view the tcache bin stack:
(gdb) jeparse
[shadow] parsing structures from memory...
[shadow] structures parsed
(gdb) jetcache -b 2
index lg_fill_div ncached low_water ncached_max
---------------------------------------------------------------
2 1 5 0x2 8
stack
----------------
0x715efa5580
0x715efa5580
0x7160571180
0x715efa55a0
0x715efa55c0
The freed address was pushed in the tcache bin stack twice. This means that the next two malloc() requests will return the same address, opening a lot of attack avenues, like turning the double free bug into a use-after-free, or a corruption of an object with attacker-controlled data.
For more details on this attack methodology and others, see our INFILTRATE 2017 talk's slides, and shadow's source code.