Now, that we managed to get the ROM code of the OMAP5948 we can start disassembling it and try to understand what is does. Maybe this lead us to some backdoors we can use (e.g. boot from external devices, USB, HDD, SD-Card, CF-Card, or whatever
I've attached the image and an raw IDA disasm of it. If you are using IDA Pro, load the image, set the CPU-Type to "ARM" and in the Processor-Options choose "ARMv5TEJ" as architecture (little endian) and here you go!
Like every other ARM-CPU the one in OMAP will start execution at address 0x00000000 after reset. At this address we usually find the "reset vector table", containing only jump instructions, used on certain conditions of the CPU. On is "RESET" and it will set PC to 0x00000000. Another one is "Undefined Instruction" which will put PC to 0x00000004, and so on. There is a table you can find on the internet.
Well, after RESET the code at 0x00000000 jumps to 0x00001638, containing the first lines of code:
Code: Select all
ROM:00001638 loc_1638 ; CODE XREF: ROM:00000000↑j
ROM:00001638 LDR R0, =0xFFFECE14
ROM:0000163C LDR R1, =1
ROM:00001640 LDRH R2, [R0]
ROM:00001644 ORR R1, R1, R2
ROM:00001648 STRH R1, [R0]
ROM:0000164C LDR R0, =0xFFFECE24
ROM:00001650 LDR R1, =0x15
ROM:00001654 LDRH R2, [R0]
ROM:00001658 ORR R1, R1, R2
ROM:0000165C STRH R1, [R0]
ROM:00001660 LDR R0, =0xFFFEC808
ROM:00001664 LDR R1, =0xF500F5
ROM:00001668 STRH R1, [R0]
ROM:0000166C LDR R1, =0xA000A0
ROM:00001670 STRH R1, [R0]
ROM:00001674 ADR R0, (loc_167C+1)
ROM:00001678 BX R0 ; loc_167C
I try to go deep into this now, and also to explain how IDA (or other Disassemblers) work, so you can read and understand whats going on. The first instruction (called "Mnemonic") here is "LDR", one of the most common found in all ARM architectures. It is used to load a 4 Byte (1 Word) value from memory into a register. The target register is given first, here "R0" (R0 is one of 16 general purpose 32 Bit registers), the source memory address next. But wait! normally this should be written as "LDR R0, [Rx]" (where Rx is another register, like R1 or so). The brackets denotes that the current value of Rx is used to indicate the memory location where the word is to be loaded.
But why the hell IDA writes "=0xFFFECE14" here instead? Well, the answer is easy, this is not a valid ARM instruction, it is a "pseudo instruction" comming from the Disassembler. It is normally used to put a "label" here, like "=myvar" when writing Assembler. The compiler will translate this to "LDR R0, [R15, #CC]" which means "Load the word found at address calculated by current content of R15 (PC) with offset 0xCC added and put that into R0".
Here is what's working behind the scenes:
When we enable IDA to show the opcodes of each disassembled instruction (Options -> General -> Disassembly -> Number of opcode-bytes = 4) the instruction looks like this:
Code: Select all
ROM:00001638 CC 00 9F E5 LDR R0, =0xFFFECE14
So "LDR R0, =0xFFFECE14" is made up of the four bytes in opcode "CC 00 9F E5". As we use Little-Endian, this is the binary representation of the opcode (swapped to E59F00CC):
Code: Select all
1110 0101 1 0 0 1 1111 0000 000011001100
The leftmost 4 bits are the "condition" Bits. They tell under which condition the command is to be executed. Here "1110" means "always execute" (condition = "AL").
The next 4 Bits "0101" tells that this is an "Load or Store unsigned word with immediate offset" ARM instruction.
The following Bit "1" tells that the offset is added to the base register.
The next Bit "0" then tells it is a "word" operation.
The next Bit "0" is fixed.
The following Bit "1" tells it is a LDR operation.
Then 4 bits with value "1111" giving the base register to calculate the memory location. "1111" equals to R15 (used as PC). NOTE: PC always points 8 bytes ahead of the start of the current instruction. So the base is current instruction memory location + 8
After that another 4 Bits follows "0000" telling the destination register of the value loaded from address. Here it is R0 then.
The last 12 bits are the offset, added to the base address. Here the offset is 0xCC.
Now a little math: Current instruction (PC) ist at 0x1638, plus 8 = 0x1640, plus 0xCC = 0x170C. When we go to that memory location we find these bytes "14 CE FE FF". Note the Little-Endianes, so it's "FF FE CE 14"... Tadaa, this is what we seen in the LDR-instruction above. In IDA at this location it looks like:
Code: Select all
ROM:0000170C 14 CE FE FF off_170C DCD 0xFFFECE14 ; DATA XREF: ROM:loc_1638↑r
So the register is not loaded with a constant value of 0xFFFECE14 directly, but loads it from an memory location. This is because ARM can only use memory locations to store and read values and each ARM instruction is exactly 4 bytes (1 word) long. But IDA knows how ARM is doing this, reads the value at the given memory location an name the label like the value to aid us.
In the comment on the location 0x170C you see "DATA XREF: ROM:loc_1638↑r". IDA points us to the instruction using this value, here "loc_1638". This is called "DATA_XREF" in IDA. The arrow shows that the code which is using this memory location is before in address space (lower) and the small "r" denotes that this location is "referenced to".
Cool, isn't it?
But what does the OMAP do with the address? Let's view it with the next lines of code (i've added line numbers to refer easily):
Code: Select all
1. ROM:00001638 LDR R0, =0xFFFECE14
2. ROM:0000163C LDR R1, =1
3. ROM:00001640 LDRH R2, [R0]
4. ROM:00001644 ORR R1, R1, R2
5. ROM:00001648 STRH R1, [R0]
In the 1. line it loads register R0 with the value 0xFFFECE14
In the 2. line, register R1 is loaded with 0x00000001
The 3. line loads the content at memory location R0 (0xFFFECE14) into R2. Here "LDRH" is only handling 16 Bit ("H" stands for "Half Word"), not 32 Bit as usual. So Only the Byte from 0xFFFE CE14 and 0xFFFE CE15 are loaded and put in the lower 16 Bits of register R2.
The 4. instruction do an bitwise OR of the contents of R1 and R2 and put the result back into R1. It's purpose is to ensure that Bit 0 of the 16 Bit value read from memory is set to "1". We will se later what this means for the periphal, addressed by this location.
In the 5. and last instruction the lower half-word of the manipulated value in R1 is stored back at memory location R0.
What is the meaning of this all?
When we look into the datasheet of the OMAP5912 (which is nearly the same as the OMAP5948), we found that this address is used by the "Peripherals Reset Register (ARM_RSTCT2)". After RESET this register has a value of 0x0000 0000 and it is a 32 Bit register. But the datasheet tell us that "Bits 1-31 are RESERVED - Reading these bits gives undefined values. Writing to them has no effect", so we don't care. But Bit 0 is "PER_EN - MPU Peripheral reset. Resets and/or enables the external peripherals connected to MPU TIPB (controls 3.2 ARMPER_nRST). Value of 0 Resets MPU peripherals, Value of 1 Enables MPU peripherals"
Remember what the code above did? It loads the current value of this register and set Bit 0 to "1" and wrote it back.
So in fact it enables the MPU periphals.
Now that we know what this small code block is doing, we find some more in the first part of the init routine of the OMAPs ROM. The next in line is (i will comment in code):
Code: Select all
ROM:0000164C LDR R0, =0xFFFECE24 ; Load R0 with location address of "MPU Idle Enable Control Register 3 (ARM_IDLECT3)"
ROM:00001650 LDR R1, =0x15 ; Load R1 with value 0b0000 0000 0001 0101
ROM:00001654 LDRH R2, [R0] ; Load current value of register ARM_IDLECT3 into R2 (after reset it is: 0b0000 0000 0001 0101)
ROM:00001658 ORR R1, R1, R2 ; Add "1" Bits to current value (which makes no difference to the default reset value here)
ROM:0000165C STRH R1, [R0] ; Set new value into ARM_IDLECT3
What it means at all is:
- Bit 0 = "1" = The L3 OCPI clock is active.
- Bit 1 = "0" = The L3 OCP-I clock remains active when the MPU enters the idle mode
- Bit 2 = "1" = The TC1_CK clock is active
- Bit 3 = "0" = The TC1 clock remains active when the MPU enters the idle mode (ARM_CK stopped)
- Bit 4 = "1" = The TC2_CK clock is active
- Bit 5 = "0" = The TC2 clock remains active when the MPU enters the idle mode (ARM_CK stopped)
- The Bits 6..31 have not effect
Currently i have no clue what those clocks are used for
Let's take a look at the next block:
Code: Select all
ROM:00001660 LDR R0, =0xFFFEC808 ; Load R0 with address of "OMAP Watchdog TIMER_MODE Register"
ROM:00001664 LDR R1, =0xF500F5 ; Load R1 with value 0x00F5 00F5
ROM:00001668 STRH R1, [R0] ; Set OMAP register with value
ROM:0000166C LDR R1, =0xA000A0 ; Load R1 with other value of 0x00A0 00A0
ROM:00001670 STRH R1, [R0] ; Again set new value
Oh, this looks very familar to me!
It is the reprogram method to disable the watchdog timers of the OMAP. The datasheet tells us: "Disable the MPU watchdog timer by writing the sequence F5, A0 in the TIMER_MODE register:
- Command WR32 0xFFFE C808 0x0000 00F5
- Command WR32 0xFFFE C808 0x0000 00A0
Remeber that? I also use this to disable the OMAP's watchdog for JTAG programming the Satnav device
Loading R1 with 0x00F500F5 and then use STRH, which only stores the lower half-word of the value seems senseless to me. Loading a value of 0x00F5 would be sufficient, but so what...
Now head to the last part of this routine:
Code: Select all
ROM:00001674 01 00 8F E2 ADR R0, (loc_167C+1)
ROM:00001678 10 FF 2F E1 BX R0 ; loc_167C
ROM:0000167C ; ---------------------------------------------------------------------------
ROM:0000167C CODE16
ROM:0000167C
ROM:0000167C loc_167C ; CODE XREF: ROM:00001678↑j
ROM:0000167C ; DATA XREF: ROM:00001674↑o
ROM:0000167C 1D 48 LDR R0, =0x20000030
ROM:0000167E 85 46 MOV SP, R0
But what is this? The ADR instruction is used to set register R0 with an memory location the subsequent branch instruction "BX R0" uses to jump to. Why not using "LDR" like above? Well, near jumps whith addresses in a range of 1000 bytes can be done as relative jumps inside a single ARM instruction, without loading the address from an variable in memory. This is why ADR is more efficient here thant LDR. As you see the destination location is just a byte behind the jump instruction. But, does this make sense anyhow? The answer is YES, but you need to now something about ARM architectures to understand it.
Some ARM CPUs support an alternative instruction set called "THUMB". As a rule of thumb (haha) you can tell that every architecture having a "T" in its name supports this mode, as in "ARMv5
TJ" which is the microarchitecture of the ARM family member ARM926EJS which is the MPU in OMAP. Thumb instructions are only 2 Bytes long but much more limited. It is possible to switch between those instruction sets in a running program. IDA hints us of this change by placing a "CODE16" in front of the first Thumb instruction, or "CODE32" for ARM instruction. So in ARM the PC will be only valid on even addresses divideable by 4. To switch from ARM to Thumb it is common to use the BX instruction. In fact it jumps to a location given in the register provided (here R0) but also change the instruction set, depending on whether or not the Bit 0 of the destination address is set. If it is set, the CPU will use Thumb instructions from that address on, if not it will continue using ARM. Therefore the ADR instruction is loaded with the loc_167C plus 1 (just forgett about the parenthesis, they are used by the assembler only) The current instruction set is reflected in the "T"-Bit of the CPSR (current program status register). You can also see the switch by the number of opcode bytes used per instruction changes from 4 to 2.
So what those first two instructions does is simply changing from ARM to Thumb. Simple, eh?
What i'm going to do next is to find a way to change the automatically given label "=0xFFFECE14" of IDA to a more meaningfull name like the register name of OMAP "OMAP_ARM_RSTCT2" to find description more easily.