Platform for Experts 
Mobile & Backend Security Testing 
Our Company 
Blog
Contact us
Back to all articles
Vulnerability Analysis

Pixel 6 bootloader: Emulation, ROP (part 2)

8 min read
Edit by Gagnerot Georges Oct 4, 2022
Share

[Part 1 here] Once you find an exploitable flaw, the next step is to look for how you could exploit it, in our context the use case is pretty easy, we return to a value stored on the stack with a controlled value. There’s no countermeasures present in the firmware, so the exploitation is pretty straightforward, using ROP programming. The goal will be at term to get read/write primitives, then shellcode execution.

 

ROP

First you launch ropper, with the good architecture and console flag: screenshot.791.jpg

And then you can start hunting gadgets in the binary, here's 2 notable things i did stumble upon:

Stack pivot

0x000000000000a988: ldp x29, x30, [sp], #0x10; ret; 0x000000000001593c: mov sp, x29; ldp x20, x19, [sp, #0x30]; ldp x22, x21, [sp, #0x20]; ldp x24, x23, [sp, #0x10]; ldp x29, x30, [sp], #0x40; ret;

Usually a stack pivot is pretty useful for ROP programming. It’s used when you lack space on the stack and have another part of memory you control. You take control of the stack pointer X29 and make it point to the memory you do control to have more space available for your ROP.

stack_pivot.jpeg Credits goes to @bellis1000

Load x0,x1,x2 execute

# 0x000000000000c82c: ldp x20, x19, [sp, #0x30]; ldp x22, x21, [sp, #0x20]; ldp x24, x23, [sp, #0x10]; ldp x29, x30, [sp], #0x40; ret; # 0x0000000000083b48: ldr x1, [x23, #0x10]; mov x0, x21; mov x2, x20; blr x22;

This widget will help us calls some functions with 3 or less arguments, the blr x22 can be hard to use sometimes but it will be enough for us to implement a memmove primitive

 

Read/Write primitives

Looking at the fastboot source, or IDA disassembly from our previous blog post, we can see that there are different methods that can be used to have a read primitive and write primitive.

Let’s start with the write primitive, we have a method called “download” that cannot be called directly by the stock fastboot binary, but thanks to the method we added in the previous blog post “rawdl” we can use it and send an arbitrary file to the bootloader.

screenshot.794.jpg

Let’s have a quick look at the function, if the download_size (0xFFFF0000F8ACBD88) and the download_ptr (0xFFFF0000F8ACBD80) are empty, they are initialized to the virtual_address of 0x90700000 (0xffff000090700000) we will see later on how we can find this virtual address easily. We know that we can upload any data that we want to this buffer 0xffff000090700000 by using our fastboot rawdl <filename>! Nice we have a buffer we control in memory!

Let’s follow with the read primitive, we have for instance the method the method “upload” that can be used like this

> fastboot get_staged <filename>

If we look at the disassembly it’s pretty simple screenshot.793.jpg

If we have a valid ptr in 0xFFFF0000F8ACBD68 and size in 0xFFFF0000F8ACBD70, the method will upload the resulting memory. Great, now we just have to modify those 2 variables...

Ok, so if we were to have a write primitive we could download whatever memory part we want thanks to the “get_staged” command, and at the same time write any data we want by first uploading our data to 0xffff000090700000 thanks to “fastboot rawdl” then using memmove on that data.

 

Debugging

I did not had time to make my own SuzyQ cable for this project (assuming it works on pixel 6) so I had to find another way to get information to debug the exploits (it’s hard to make everything work directly on the first try right? )

The first guess was to work with the information provided by “fastboot oem dmesg”. When you don’t totally mess up the kernel thread, but still a bit (think invalid data access, or no execute page permission) fastboot is nice enough to reboot and give you some backtrace (hint: that is the easy way i spoke about in the first part)

Let’s say that I try naively to execute some code in our download buffer

(bootloader) instruction abort: PC at 0xffff000090700100 (bootloader) ESR 0x8600000d: ec 0x21, il 0x2000000, iss 0xd (bootloader) iframe 0xffff0000f8e5d640: (bootloader) x0 0xffff0000f8acbc88 x1 0xffff0000f8900002 x2 0xffff00 (bootloader) 0090700100 x3 0x0000000000000001 (bootloader) x4 0x0000000000000000 x5 0xffff0000f8b1c438 x6 0x000000 (bootloader) 0000000000 x7 0x0000000000000000 (bootloader) x8 0x0000000000000002 x9 0x0000000000000000 x10 0x000000 (bootloader) 0099999999 x11 0x0000000000000027 (bootloader) x12 0x0000000000000000 x13 0xffff0000f8ddd3c8 x14 0xffff00 (bootloader) 00f8ac7720 x15 0xffff0000f8b9c370 (bootloader) x16 0x0000000000010000 x17 0x00000000ffffffff x18 0x000000 (bootloader) 0000000000 x19 0xffff0000f8acbc88 (bootloader) x20 0xffff000090700100 x21 0xffff0000f8acbc88 x22 0xffff00 (bootloader) 00f8854e04 x23 0xffff000090700000 (bootloader) x24 0xffff0000f8802c68 x25 0x0000000000000000 x26 0x000000 (bootloader) 0000000000 x27 0x0000000000000000 (bootloader) x28 0x0000000000000000 x29 0x0000000000000000 lr 0xffff00 (bootloader) 0090700100 usp 0x0000000000000000 (bootloader) elr 0xffff000090700100 (bootloader) spsr 0x0000000000000305 (bootloader) backtrace: (bootloader) [<ffff000090700100>] unknown+0x0/0x0 (bootloader) [<ffff000090700100>] unknown+0x0/0x0 (bootloader) panic (caller 0xffff0000f880125c): die (bootloader) [ 14.728729] [I] [GS] halt action=0, reason=10 (bootloader) [ 14.728735] [C] [GS] Rebooting in 5 seconds, press 'c' to (bootloader) cancel: 0

Ok so I can get some information like, are my registers correct, to help me build my ROP. But still it’s pretty hard and tiresome, and having to always keep the button on the “vol down” button to stay in the bootloader (or use a rubber band) is not fun! Do any weird side effects happen while the code is running? Even with the OS helping you by giving some insight about what happened on last exception

 

Emulation

How can we do better? Well let’s work with unicorn and it’s friends keystone and capstone. You feel that the website looks like they have been copy/pasted? Nah must be your imagination.

Unicorn will help us work with emulation, it’s quicker than writing directly a new board on QEMU and putting hooks in it (can take some time to become proficient there!), keystone will help us quickly assemble code, and capstone will take care of the disassembly (the other way).

What we want is to be able to emulate a function quickly and instrument it to see what it's doing. I will give you a few tips I use when playing with unicorn.

The typical workflow looks like this unicorn_process.svg

Pretty straightforward from the unicorn documentation. As a side note, I prefer to work with the unicorn version available in angr, they have different standard behavior, and having directly a version I will be able to play in angr if I want to is nice.

gen_shellcode(data,address)

generate binary shellcode from assembly instructions

def gen_shellcode(data,address): ks = keystone.Ks(keystone.KS_ARCH_ARM64,keystone.KS_MODE_LITTLE_ENDIAN) ret=ks.asm(data,address) return bytes(ret[0])

disassemble(code,addr)

Quick disassembler

disassembler = capstone.Cs(capstone.CS_ARCH_ARM64,capstone.CS_MODE_ARM) def disassemble(code, addr): for insn in disassembler.disasm(code, addr): print("0x%x:\t%s\t%s" % (insn.address, insn.mnemonic, insn.op_str))

hook_mem_invalid_auto(uc,uc_mem_type,addr,size,value,user_data)

This hook allows you to automatically map memory when you have invalid memory during early access, you can then copy/paste the mu.mem_map to make the mapping yourself

#Auto allocate pages of memory of size 10Mega on invalid memory access PAGE_SIZE=10*1024*1024 def hook_mem_invalid_auto(uc,uc_mem_type,addr,size,value,user_data): pc = uc.reg_read(UC_ARM64_REG_PC) start = addr & ~(PAGE_SIZE-1) print(f"~~~~~~~~~~~~~~ mu.mem_map(0x{start:08x}, PAGE_SIZE)") mu.mem_map(start,PAGE_SIZE) return True

With that in mind, we can setup a script (or notebook) to run the main fastboot code from the bootloader.

https://github.com/eshard/pixel6-boot/blob/main/run_abl_public.ipynb

Nothing really fancy there, we setup SIMD thanks to a small shellcode and verify that everything works, then add some hooks to be able to play with the binary and launch commands as if we sent them through USB. You can change directly the “commands” array to put your own commands. I added a command named “outofloop” to quickly exit the loop without going too much into the internals of the bootloader. You can just drop the binary extracted from the previous post abl_210817 and play with it.

screenshot.804.jpg

We can see that flashing unlocks make the device returns “device already unlocked”. That’s what happened on the real device if you try to execute fastboot flashing unlock, so from this point we have the basic of emulation working.

Thanks to that, we can debug and prepare our exploit much easily!

 

Exploitation

Some pointers here for the ROP chain I used to get a memmove primitive memmove_rop.svg

The 0x0000000000083b48 should not be necessary ideally, but on our stack the value for x21 is overwritten and then cannot be used reliably...

From this memmove, you can create some download / upload primitives and read and write memory on the bootloader (we’ll speak about page access in the next post!) pixel6_bootloader.gif

With a memmove primitive we can replace as seen in the previous gif the serial for instance if we change the value at a good offset in two steps: first upload data to the buffer we control, then memmove from it to our wanted destination. Feel free to try other offsets, if you try to access a Read Only zone, your device will reboot most likely, or become bricked in case you hit some unlucky (very) spot! https://media.giphy.com/media/l3diT8stVH9qImalO/giphy.gif

Let’s see later for code exploitation or symbolic execution.

Greetings to my teammates from eShard for their help and review.

Pulsar

 

We have open positions @ eShard

If you are interested by this first blog post and want to work on research projects around bootloaders and internals of Android/iOS, we are looking for kind people! Patch Analysis, Fuzzing and Code Emulation will be 80% of your daily tasks.

If you are interested please contact us!

Top 120 European Mobile Banking App Benchmark

Share

Categories

All articles
(40)
Chip Security
(13)
Corporate
(4)
Mobile App Security
(17)
Vulnerability Analysis
(6)

you might also be interested in

Mobile App Security

Interview with Giovana Assis, AppSec Tech Manager at Nubank

4 min read
Edit by Balangué Rémy Oct 26, 2022
CopyRights eShard 2022.
All rights reserved
Privacy policy | Legal Notice
PLATFORM FOR EXPERTS
Side Channel AnalysisLaser & EM Fault InjectionFirmware Security AnalysisSecurity Failure AnalysisVulnerability Research
PROFESSIONAL SERVICES