Introduction to the Binary Ninja API

As ninjas we value time, and the faster we are able to do something the better. Because of this I try to find tools to expedite my work, and so I have started messing around with Binary Ninja from the cool dudes over at Vector 35 whom I love dearly for the incredible contributions they have made to CTF over the years, my favorite being Hacking Time and PwnAdventureZ.

While the UI is super sexy and clean, this post is focused on the Python API to see how quickly I can build a tool to Decode ARM64 syscalls based on my last post. Thanks to Jordan for inviting me to the BETA and thanks to Peter who spent a couple hours solving my super novice issues and made this post possible. As always, thanks to rotlogix who continues to inspire me to write these posts.

note: binary ninja has been lovingly abbrieviated binja, and will be referred to as such through the post

Setting up

Typical of my luck, I happened to run into every known and unknown issue while getting set up. Luckily the V35 guys were super helpful getting it straightened out but will document here in case you run into these problems. After getting my copy of the software, (an App Bundle for OS X) I ran into a problem with homebrew version of Python 2.7 causing this issue which was later documented here with a temporary solution of simply using the native OS X version of Python:

PYTHONPATH=$PYTHONPATH:/Applications/Binary\ Ninja.app/Contents/Resources/python python

After this I ran into some errors resulting from a phantom dylib on my system causing version mismatches and preventing me from using the Mach-O support. You’re unlikely to run into this, however it is convient to ensure that your UI version matches your API version by running:

python -c "import binaryninja; print binaryninja.core_version"

After spending some more time with different setups (and trust me, the homebrew vs native python issue is a smash-laptop-against-a-wall type of problem) I would recommend you install virtualenv for binja which is what I am running now and it’s working great. The main reason is that virtualenv will install a fresh copy of Python without messing with whatever is on your main system, you can get the dependencies installed and run workon binja to quickly get back to your environment.

A great guide to follow would be the Hitchhiker’s Guide to Python

My Setup

Requires virtualenvwrapper.sh

  • mkvirtualenv binja
  • workon binja

pip freeze > requirements.txt yields:

appnope==0.1.0
decorator==4.0.9
gnureadline==6.3.3
ipython==4.1.2
ipython-genutils==0.1.0
path.py==8.1.2
pexpect==4.0.1
pickleshare==0.6
ptyprocess==0.5.1
pycrypto==2.6.1
pyreadline==2.1
simplegeneric==0.8.1
traitlets==4.1.0

If you put the above into a requirements.txt file you can run pip install -r requirements.txt inside your virtualenv binja and have the same setup. I like to run inside iPython mostly because of the tab-complete features for quickly accessing objects.

Throwing Down

I was given a block of sample code to leverage the Python API:

from binaryninja import *
import time
bv = BinaryViewType['Mach-O'].open("stack1")
bv.update_analysis()
time.sleep(5)
for func in bv.functions:
   print func.symbol.name
   for block in func.basic_blocks:
      print hex(block.start), hex(block.end)

which results in the following for Gera’s stack1 binary:

_main
0x100000ea0L 0x100000ef3L
0x100000f04L 0x100000f1eL
0x100000ef3L 0x100000f04L
0x100000f2aL 0x100000f36L
0x100000f1eL 0x100000f2aL
___stack_chk_fail
0x100000f30L 0x100000f36L
_gets
0x100000f36L 0x100000f3cL
_printf
0x100000f3cL 0x100000f42L

It should be noted that currently in BETA the time.sleep(5) function is required due the lack of a callback, so there is no way to know when your analysis is complete. While this makes things blazingly fast for larger binaries, adjusting time.sleep(5) is annoying and I was warned that this is being solved by implementing proper callback functionality.

Despite this we see that binja performs as expected, it finds the symbols: _main, __stack_chk_fail, _gets, _printf and displays the start and ending addresses for each basic block associated with each symbol.

Binja plugin to decode ARM64 syscalls

Now that we have a basic example working, we are ready to attempt to write a plug-in that decodes ARM64 syscalls - the binary I will be using is cbd, Samsung’s CP Boot Daemon - a crude algorithm for doing this is:

  • Search for supervisor call exceptions in ARM64
  • Check the immediate value being moved to X8
  • Lookup the value to identify the syscall

To do this in binja we start similar to the above example, by recursing through each basic block but instead of just printing the addresses, we look for all SVC instructions. If you don’t remember why, go back and read my post on the subject.

Printing each basic_block object in binja is easy, and looks like this:

# Isolate a function object, f1:

f1 = bv.functions[0]
f1.basic_blocks

>>> [<block: aarch64@0x400f2c-0x400f44>]

and we can get the basic_block start and end address with:

# Isolate a basic_block object, f1b1:
f1b1 = f1.basic_blocks[0]

# Function 1, Basic Block 1:
hex(f1b1.start)
>>> '0x400f2cL'

hex(f1b1.end)
>>> '0x400f44L'

So we’ll want to generate all instructions between hex(f1b1.start) and hex(f1b1.end) doing so looks like:

ins = []
start = f1b1.start
end = f1b1.end
while start != end:
  x, size = bv.arch.get_instruction_text(bv.read(start, 4), start)
  ins.append(x)
  start += size

The biggest piece of this to understand is the get_instruction_text() function located in __init__.py:

So get_instruction_text() wants data, addr as its arguments, obviously we know the address for our basic_block given above using f1b1.start, but to get the data we use the bv.read() function which requires offset and a length. length is defined as 4 for ARM64 due to its fixed 4-byte instruction size. For x86_64 you would use 16.

bv.read()

The instructions returned from the above code:

[['stp', '   ', 'x29', ', ', 'x30', ', ', '[', 'sp', ', #', '-0x10', ']!'], 
['adrp', '   ', 'x0', ', ', '0x473000'], 
['mov', '    ', 'x29', ', ', 'sp'], 
['add', '    ', 'x0', ', ', 'x0', ', ', '#', '0xf50'], 
['bl', '     ', '0x448214'], ['bl', '     ', '0x439c28']]

Search for supervisor call exceptions in ARM64:

A quick-and-dirty algorithm:

  • Loop:
    • Enter function
    • Enter basic_block
    • Enumerate instructions in basic_block
    • Search for SVC

Because the instruction has spaces and its list type is not string-y enough for Python, we’ll need to do some Pythonic magic (thanks Oren for helping with this part!):

Putting it all together
for func in bv.functions:
     for bb in func.basic_blocks:
     	ins = []
		start = bb.start
		end = bb.end
		while start != end:
    		x, size = bv.arch.get_instruction_text(bv.read(start, 4), start)
    		ins.append(x)
    		start += size
    	for index, item in enumerate(ins):
    		if 'svc' in ''.join(map(str, ins[index])):
        		print "function: %s" % func
        		print "basic block: %s" % bb
        		print "MOV: %s" % ins[index-1]
        		print "SVC: %s" % ins[index]

we found 38 SVC calls in cbd - WOOHOO - the above returns:

function: <func: aarch64@0x434268>
basic block: <block: aarch64@0x434268-0x43427c>
MOV: ['mov', '    ', 'x8', ', ', '#', '0xae']
SVC: ['svc', '    ', '#', '0']

function: <func: aarch64@0x4342f8>
basic block: <block: aarch64@0x4342f8-0x43430c>
MOV: ['mov', '    ', 'x8', ', ', '#', '0x50']
SVC: ['svc', '    ', '#', '0']

function: <func: aarch64@0x434310>
basic block: <block: aarch64@0x434310-0x434318>
MOV: ['mov', '    ', 'x8', ', ', '#', '0x5e']
SVC: ['svc', '    ', '#', '0']

function: <func: aarch64@0x434328>
basic block: <block: aarch64@0x434328-0x43433c>
MOV: ['mov', '    ', 'x8', ', ', '#', '0x65']
SVC: ['svc', '    ', '#', '0']

...

Check the immediate value being moved to X8

Using this we can see we have addresses for the func and address ranges for the basic_blocks. This should be all we need to disassemble the functions and look for the MOV X8, <immediate> we need to decode the correct syscall.

for index, item in enumerate(ins):
  count = 0
  if 'svc' in ''.join(map(str, ins[index])):
    for iter in ins[index-1]:
      if count == 5:
        print "syscall: %s @ func: %s " % (iter, func)
        count += 1

Don’t judge me, it’s late and the above code “works” by maybe one sense of the defintion. In any case we get the syscalls printed out along with the function it is associated with. I print the function because there is a very large chance that the function is just a handler for the syscall and can be renamed or labeled as such. (ex.: 0x22 is sys_nice and func@0x42a19c can be labeled sys_nice_handler.)

syscall: 0x22 @ func: <func: aarch64@0x42a19c>
syscall: 0x4f @ func: <func: aarch64@0x42b57c>
syscall: 0x40 @ func: <func: aarch64@0x434250>
syscall: 0xae @ func: <func: aarch64@0x434268>
syscall: 0x3f @ func: <func: aarch64@0x434280>
syscall: 0x2b @ func: <func: aarch64@0x4342e0>
syscall: 0x50 @ func: <func: aarch64@0x4342f8>
syscall: 0x5e @ func: <func: aarch64@0x434310>
syscall: 0x65 @ func: <func: aarch64@0x434328>
syscall: 0x19 @ func: <func: aarch64@0x434358>
syscall: 0x42 @ func: <func: aarch64@0x434388>
syscall: 0x39 @ func: <func: aarch64@0x4343a0>
syscall: 0x3e @ func: <func: aarch64@0x4343e8>
syscall: 0x84 @ func: <func: aarch64@0x434400>
syscall: 0x51 @ func: <func: aarch64@0x434418>
syscall: 0x92 @ func: <func: aarch64@0x434430>
syscall: 0xde @ func: <func: aarch64@0x434448>
syscall: 0xa7 @ func: <func: aarch64@0x434460>
syscall: 0x60 @ func: <func: aarch64@0x434490>
syscall: 0xe2 @ func: <func: aarch64@0x4344a8>
syscall: 0xd7 @ func: <func: aarch64@0x4344c0>
syscall: 0x5b @ func: <func: aarch64@0x4344f0>
syscall: 0x38 @ func: <func: aarch64@0x434508>
syscall: 0x77 @ func: <func: aarch64@0x434550>
syscall: 0x49 @ func: <func: aarch64@0x434568>
syscall: 0xa6 @ func: <func: aarch64@0x434580>
syscall: 0xac @ func: <func: aarch64@0x439fec>
syscall: 0xce @ func: <func: aarch64@0x43a0a8>
syscall: 0x1d @ func: <func: aarch64@0x43ea1c>
syscall: 0xdc @ func: <func: aarch64@0x449760>
The Ninja Way

Ok so, the above code was pretty sloppy thanks to a lack of sleep, and with some time spent in the Binary Ninja Slack Channel today, Peter was able to squash some bugs having to do with the get_reg_value_at_low_level_il_instruction function and assist in getting a way cleaner version of this decode syscall done. The following uses the very powerful Binary Ninja Low-Level Intermediate Language (LLIL) and makes me super excited, because it has replaced hours of work with only a few lines of code:

bv.functions[65].low_level_il[1]
>>> <il: syscall>

syscall = bv.functions[65].low_level_il[1]

bv.functions[65].get_reg_value_at_low_level_il_instruction(syscall.address, "x8")
>>> <const 0xae>

The bv.functions[65].low_level_il looks like this:

x8 = 0xae
syscall
add.q{*}(x0, 1 << 0xc)
unimplemented
if (u>) then 6 @ 0x42b4b8 else 22 @ 0x43427c
goto 22 @ 0x43427c
sp = sp - 0x20
[sp] = x29
[sp + 8] = x30
x29 = sp
[sp + 0x10] = x19
w19 = w0
call(0x428de8)
x1 = x0
x0 = -1
[x1 + 0].q = w19
x19 = [sp + 0x10].q
x29 = [sp].q
x30 = [8 + sp].q
sp = sp + 0x20
<return> jump(x30)
jump(0x42b4e4)
<return> jump(x30)
jump(0x434280)

and in the UI like this:

The gist of this is basically bv.functions[65] is a function I know has a syscall, the .low_level_il points to the LowLevelILFunction instance and [1] tells it to give me the 2nd element in the instance. Setting that to syscall and calling get_reg_value_at_low_level_il_instruction allows me to pass it the address of that syscall IL instance and tell it what register I am interested in x8. You can check all available registers by using: bv.arch.regs.

Lookup value to identify the syscall

Now that we have the <immediate> we can do a look-up to enumerate which syscall it belongs to. The quick-and-dirty way is to just browse to syscalls.kernelgrok.com and look it up manually. I’m going to work on cleaning up this code and make it an actual Binary Ninja Plug-in, should be uploaded here in the next few days. Binary Ninja is still in BETA so I don’t feel too rushed to get it out. Any questions as always, tweet me up!

Thanks for reading.

@theqlabs

Decoding Syscalls in ARM64

Eventually as you are reverse-engineering an ARM binary you will come across a function that looks like the following:

Even if you understand what this code is doing (as I suspect you may) read on, as this post intends to bring to light several security models specific to the ARMv8-a architecture.

To understand what this code is doing, you need to understand a few concepts first. Since this is an ARM specific blog, this is what we will focus on. Specifically in the context of the ARMv8-a architecture. An extremely helpful overview of this architecture can be found here - I suggest you read it before continuing.

Exception Levels

The most important concept is rather new, these are the Exception Levels ARMv8-a uses for privilege separation (such as rings in the Intel architecture) there are 4 levels, notably:

Exception Level Description Usage* Status
EL0 Unprivileged Applications are executed here Required
EL1 Privileged Linux (or other OS) Kernel Required
EL2 Hypervisor Virtualization Optional
EL3 Secure Monitor Security States Optional

*: aarch64 does not dictate how software can use the exception levels, these are simply a common usage model.

Now, as you can imagine, applications running in EL0 may need to access or modify the system in some way. The Linux kernel provides a safe portable way to access these system-level functions. This Application Programming Interface or API between the unprivileged EL0 and the privileged EL1 execution levels are called system calls or syscalls.

The ARMv8-a architecture has strict rules about how to leverage syscalls, as you can imagine abuse of this API is commmon and could lead to an unprivileged application modifying the system beyond what it should be allowed to. This technique has been used countless times to gain root on a device or escalate privileges of a user. One of the biggest issues with mobile devices is there is not much quality control for software that interfaces with the kernel, things like device drivers get abused far too often.

Before we get into exceptions, it should be noted that ARMv8-a has a (harrowingly complicated) Security Model, whose general principles are as follows. If EL3 is implemented in the system there are two security states Secure and Non-Secure each with their own physical memory address space. If EL3 is not implemented, AND does not include EL2 then it’s IMPLEMENTATION DEFINED. If EL2 is present then it is Non-Secure state. Changing states occurs in the same fashion as the exceptions described below.

Exceptions

ARMv8-a can operate in two execution states Aarch64 and Aarch32 (compatible with ARMv7-a). It is possible to move between these two states using what the architecture defines as interprocessing though it is not useful for this exercise.

In Aarch64 state, you can change exception levels only by taking an exception, or returning from one. Perhaps the best way to explain it is with pseudo-code:

64-bit:

if state == aarch64 && take_exception {
	 target_exception_level = exception_level or exception_level+1
}

if state == aarch64 && return_from_exception {
	target_exception_level = exception_level or exception_level-1
}

There are a few types of exceptions ARMv8-a allows that will interrupt the processor and change the control flow of the program. These are:

  • SVC Supervisor Call attempts to access EL1 from EL0.
  • HVC Hypervisor Call attempts to access EL2
  • SMC Secure Monitor Call attempts to access EL3
  • HLT Halting Software Breakpoint Instruction
  • BRK Software Breakpoint Instruction

The SVC instruction is the most common, and the one we are dealing with in the following example. This instruction causes a Supervisor Call exception, which provides this unprivileged program the ability to make a system call to the privileged operating system. When SVC is executed, the target_exception_level becomes EL1 from EL0.

Let’s walk-through this function to see what’s going on:

MOV X8, #0x40

Moves the immediate value 0x40 into the X8 register.

While this looks like a simple instruction there’s a lot going on here that you may not be familiar with. Prefacing a call to the kernel (SVC 0) we have to setup that call, which generally means you need two things:

  • a system call, defined by a number (in our case 0x40)
  • a register to place the return value (typically X0)

It is also important to note, that (quite annoyingly) these syscall numbers change based on the architecture you are executing the instruction. In this case, 0x40 is defined in the arm64 kernel as a call to write. And of course since ARM is a load/store based architecture we require X8 to act as a catalyst to move the value since we can not write the value directly to memory (like you can in other architectures.)

Note: In ARMv7 the R7 register was used, which you can remember by: v7 uses R7, v8 uses X8.

SVC 0

Generates supervisor call exception, targeting EL1

The call looks like this: AArch64.TakeException(EL1, exception, preferred_exception_return, vect_offset);

Now is a good time to break and talk about what vect_offset is:


Exception Vector Tables

From ARM Infocenter:

When an exception occurs, the processor must execute handler code which corresponds to the exception. The location in memory where the handler is stored is called the exception vector. In the ARM architecture, exception vectors are stored in a table, called the exception vector table. Each Exception level has its own vector table, that is, there is one for each of EL3, EL2 and EL1. The table contains instructions to be executed, rather than a set of addresses. Vectors for individual exceptions are located at fixed offsets from the beginning of the table. The virtual address of each table base is set by the Vector Based Address Registers VBAR_EL3, VBAR_EL2 and VBAR_EL1.

This means that after SVC 0 is called, AArch64.TakeException() executes using VBAR_EL1 + vect_offset 0x280 to retrieve the exception handler instructions to carry out the exception - see Table 10.2 in the infocenter reference for information about calculating offsets.

Accessing VBAR_EL1 is done through the MRS instruction and looks like this for our example:


CMN X0, #1, LSL#12

CINV X0, X0, HI

B.HI loc_42B4B8

ARM has a number of potential conditions set by the 4-bit prefix in an instruction word. The prefix we are intersted in is the HI condition as shown by our instructions. The HI condition is met when the Carry flag is set and the Zero flag is false, which simply means there was a non-zero value returned from the system call into X0.

The above instructions are checking the returned value from the system call, stored in X0, for a non-zero value (an error) and setting the HI condition to branch accordingly into loc_42B4B8.

RET

Branches to the address stored in the Link Register (LR)

Now that you understand what is happening with this function it is a good idea to rename it in IDA so that you can identify when a function is calling the write handler! (TODO: ignore the sys_getppid, too lazy to take another screen shot ATM will fix.)

I left some details out about the above process because this was meant as an intro. Some of the topics I did not discuss are Exception Syndrome Registers, Exception Link Registers, and PSTATE.

NOTE: To learn more about exceptions in ARMv8-a check out Chapter D1 in the Aarch64 Reference Manual

Thanks to reddit user SidJenkins who explained why I’m an idiot WRT to ARM syscall values, and the conditional compare instructions. You can see the comment thread here as well as a better technical description about how the CMN and CINV instructions function in this use-case.

A great tip by @michalmalik to use the man pages to reference Architecture Calling Conventions in case you forget what registers are used. Thanks Michal!

Reverse Engineering Samsung S6 Modem

So I was a little late to the game, and just got my hands on a Samsung Galaxy S6, specifically the SM-G920F which will be the topic of discussion in this post. I am quite curious as to understanding the structure of the device’s modem.bin file. While I haven’t been able to get a de-obfuscated/decrypted version of modem.bin yet, hopefully this post will help others quickly get up-to-speed and assist in the pursuit of one.

Obtaining Files

  • Download Samsung SM-G920F Galaxy S6 Firmware

Extracting Files

I had some issues using UnZip 5.52 on OS X (PK compat. error), so instead I used UnZip 6.00 on Ubuntu 15.04.

unzip <firmware.zip>
mv <firmware.tar.md5> <firmware.tar>
tar xvf <firmware.tar>

You should end up with something like:

boot.img
cache.img
cm.bin
hidden.img
modem.bin
recovery.img
sboot.bin
system.img

Deconstructing modem.bin

Endianness: Most of what we will be seeing in modem.bin is comprised of Little-Endian format. That is, the Most Significant Byte is located at the highest memory address. Example: If you look at SIZE of BOOT below, the bytes are ordered as 48 2B 00 00 but read and sent as 0x2B48 dropping the 00s as you would in decimal for anything before a non-zero number.

TOC[0] = TOC

Opening modem.bin in a Hex Editor gives some immediate insight into what is happening with this file.

The first 0x200 bytes are called the TOC, I am going to make a slightly ambitious guess that this stands for Table of Contents. Its function is to provide information about the file itself including all [5] of its sections, namely: TOC, BOOT, MAIN, NV and OFFSET, as well as providing an index into these sections.

header

While the above screen shot shows only addresses 0000h-0090h the remaining bytes are all zero-padded 0x200 bytes in total.

TOC[1] = BOOT

If you want to isolate the BOOT section of the file you would do so by calculating the offsets based on the SIZE parameter from the header file. So for our example it would look like the following:

dd if=modem.bin bs=1 skip=512 count=11080 of=modem.bin_boot

# calculated by using the 0x200 byte offset in decimal.
skip=512
 
# calculated by using the 0x2B48 byte SIZE of BOOT in decimal.
count=11080

Address Bytes Length Description Value
0020-002C 42 4F 4F 54 12 Bytes NAME of Section BOOT
002C-0030 00 02 00 00 4 Bytes Unknown  
0030-0034 00 00 00 00 4 Bytes Unknown  
0034-0038 48 2B 00 00 4 Bytes SIZE of BOOT 0x2B48 bytes
0038-003C 45 03 27 5E 4 Bytes CRC of BOOT 0x5E270345
003C-0040 00 00 00 00 4 Bytes INDEX Index into TOC

TOC[2] = MAIN

Similar to BOOT you would isolate MAIN with the following dd command:

dd if=modem.bin bs=1 skip=11592 count=40394816 of=modem.bin_main

MAIN

Address Bytes Length Description Value
0040 - 004C 4D 41 49 4E 12 Bytes NAME of Section MAIN
004C - 0050 60 2D 00 00 4 Bytes VERSION* 0x2D60 1.16.16
0050 - 0054 00 00 00 40 4 Bytes Unknown  
0054 - 0058 40 60 68 02 4 Bytes SIZE of MAIN 0x2686040 or 40,394,816 bytes or ~40MB
0058 - 005C 24 BD DF 93 4 Bytes CRC of MAIN 0x93DFBD24
005C - 0060 02 00 00 00 4 Bytes INDEX Has to do with index into TOC

* - VERSION is a guess based on analyzing multiple firmwares, seen both 0x2D00 and 0x2D60. Will confirm, should be able to reverse cbd prepare_boot_args or related functions to ensure the above is correct.

TOC[3] = NV

Address Bytes Length Description Value
0060 - 006C 4E 56 00 00 12 Bytes NAME of Section NV
006C - 0070 00 00 00 00 4 Bytes Unknown  
0070 - 0074 00 00 EE 47 4 Bytes Unknown  
0074 - 0078 00 00 10 00 4 Bytes SIZE of NV 0x2B48 bytes
0078 - 007C 00 00 00 00 4 Bytes CRC of NV N/A
007C - 0080 03 00 00 00 4 Bytes INDEX Has to do with index into TOC

TOC[4] = OFFSET

I have never seen cbd process or send this section, so I’m assuming its use is local to the modem.bin file and not to the CP. Perhaps the BOOT is using it in some way?

Decoding BOOT

So you may have noticed some patterns while looking at the BOOT code, in our example located from 0200-02D48h and in case you didn’t I’m going to show you a trick I learned from the Practical Reverse Engineering book by Dang et al. As they so correctly state:

The ability to recognize instruction boundaries in a seemingly random blob of data is important. Maybe you will appreciate it later.

Also, say Dang et al. out loud. Ha!

Let’s review a chunk of BOOT:

TL;DR - These are ARM instructions and we’re about to disassemble the sh*t out of them.

Looking at this blob, it’s easy to notice a pattern. Almost every 4th byte ends in 0xE* - as it turns out, ARM branch instructions use the Most Significant Bits for a conditional code. These codes control the execution of instructions and are typically based on the flags set in the Application Program Status Registers or APSR. So if you want to tell an instruction to Always Execute, you would use the AL condition which is 1110b or 0xE. If you have any experience with crackmes this is analogous to switching the Z or Zero Flag when you wanted to alter the state of a conditional JMP (x86) or B (ARM).

TODO: Reversing BOOT will be part of a later post.

CP Boot Process

The following is what I know based on crashing the modem many times, reversing cbd and BOOT

cbd	 # CP Boot Daemon lives in /sbin on the device
CP 	 # Cell Processor or Modem
AP	 # Application Processor, where Android OS lives
BOOT	 # BOOT section of the modem.bin

/dev/umts_boot0

Opened by rild used for most I/O involving CP boot chain.

misc_ioctl:
	IOCTL_MODEM_ON
	IOCTL_MODEM_BOOT_ON
	IOCTL_MODEM_DL_START	 # Starts `mem_start_download`
	IOCTL_MODEM_SET_TX_LINK	 # Sets Boot Link / Main Link
	IOCTL_MODEM_FW_UPDATE	 # Called after stages are sent to CP
	IOCTL_MODEM_BOOT_OFF	 # Sent after Stage 3 is sent to CP
	IOCTL_MODEM_BOOT_DONE	 # Final IOCTL for CP boot chain

start_shannon333_boot

shannon_normal_boot

LLI STATUS mount

prepare_boot_args

dmsg

mem_start_download

Triggered by IOCTL_MODEM_DL_START which grabs the BOOT section of modem.bin and sends it to the CP to start the boot code. The code appears to come from link_device_bootdump.c in the Android kernel.

magic == 0x424F4F54 or BOOT

std_dl_send_bin

Sends parsed modem.bin to CP RAM via BOOT code. I’m thinking the CMD= are private IOCTL command IDs.

Stage 1: 0x200 bytes header

Stage 2: 0x2686040 bytes MAIN

Checks CRC for MAIN

I’m guessing it sends the MAIN CRC to the BOOT code to verify

Stage 3: 0x100000 bytes of NVRAM

check_factory_log_path

Set to /sdcard/log/ annoyingly the SM-G920F does not have an SD CARD slot so uses a shitty Virtual SD daemon that does not appear to work correctly because I can not get anything saved into the directory. Will work on a way to set this, should be fairly simple.

std_udl_req_resp

I’m not sure why cbd calls these functions, but called before std_dl_send_bin

Service Mode Functions

There are many service mode functions that Samsung kindly provides that will help during reversing.

# Enable CP Debugging
am broadcast -a android.provider.Telephony.SECRET_CODE -d android_secret_code://66336

<device resets>

# Enable CP RAMDUMP
am broadcast -a android.provider.Telephony.SECRET_CODE -d android_secret_code://9090

<device resets>

# Cause CP RAMDUMP
am broadcast -a android.provider.Telephony.SECRET_CODE -d android_secret_code://CP_RAMDUMP

# SysDump, Copy to SD Card
am broadcast -a android.provider.Telephony.SECRET_CODE -d android_secret_code://9900

Working on writing up my notes on reversing CP Boot Daemon and BOOT, hopefully will post soon. I still need to figure out how to organize this damn thing. Hopefully this helps those interested jump-start their research, and feel free to reach out via Twitter.