Welcome

Injecting code

Ofcourse, if I wanted to change something in the cache, I couldn't scan the complete 64MB of RAM every time: I needed to know how the cache works. For that, I would need to dump, disassemble and understand the hard disk firmware at least enough to make sense of the caching functions.

Disassembling this firmware is not a trivial task. First of all, the code mixes ARM and thumb-style instructions, which is irritating if you don't have a disassembler which can automatically switch between the two. Furthermore, something that usually makes disassembling software a lot easier is absent: Usually, routines are coded to spit out messages like "Couldn't open logfile! when something goes wrong. These messages are a huge help in figuring out what a routine does. This firmware, however, has none of these strings: you need to figure out what a routine does purely by the code. The codebase seems to be a bit old, though, and sometimes the disassembly feels like some features have been 'bolted on' to the code later, making everything a bit more complicated.

There also are a few things that make life easier, though. First of all, it seems Western Digital hasn't been intentionally obfuscating the code: no tricks like jumping in the middle of an instruction have been used. Also, because the JTAG-interface is available, you can meddle with the code, set breakpoints or change it on-the-fly, making figuring out what routine gets run when immensely easier.

After a long time of staring at the code, trying to make sense of things and sometimes jumping into the debugger to see if a guess was correct, I managed to get to the core of the caching system: a table in RAM I call the 'cache descriptor table':

Every entry in the cache descriptor table describes a block in the cache. It contains the start LBA of the disk sectors that are or should be cached, how much of the cache actually is filled with disk data, some flags indicating the state of the cache entry and a number indicating where in memory the cached data resides.

Now, with the secrets of the cache descriptor table unraveled, could I intercept a disk read before it'd go out the SATA-port to the PC? To do that, I'd need to be able to execute my own code on the hard disk controller. Moreover, I would have to make sure the code would get run on the correct time: if it modified the cache too soon, the data wouldn't be in there yet; if it modified the cache too late, the data would've already gone to the PC.

The way I did this was by hooking an existing routine. My hack would be in Feroceon 2, and that CPU did all the SATA transfers, so there must be some routine that's responsible for setting up the SATA hardware to pick up the data from cache. If I could find this routine, I could perhaps run my own code before it.

After a lot of browsing, setting breakpoints, failing and trying again, I finally found some routine that fit the bill. I modified it to run my code before it by hooking it. Here's the original code:

000167BE ; r0 - slot in sata_req
000167BE sub_0_167BE:
000167BE                 PUSH    {R4-R7,LR}
000167C0                 MOVS    R7, R0
000167C2                 LSLS    R1, R0, #4
000167C4                 LDR     R0, =sata_req
000167C6                 SUB     SP, SP, #0x14
000167C8                 ADDS    R6, R1, R0
000167CA                 LDRB    R1, [R6,#0xD]
000167CC                 LDR     R2, =stru_0_40028DC
000167CE                 STR     R1, [SP,#0x28+var_1C]
000167D0                 LDRB    R0, [R6,#(off_0_FFE3F108+2 - 0xFFE3F0FC)]
000167D2                 LDRB    R5, [R6,#(off_0_FFE3F108 - 0xFFE3F0FC)]
000167D4                 LSLS    R0, R0, #4
And here's what happens when the code is hooked to call my code:
000167BE ; r0 - slot in sata_req
000167BE sub_0_167BE:
000167BE                 PUSH    {R4-R7,LR}
000167C0                 MOVS    R7, R0
000167C2                 LD      R6, =hookedAddr
000167C4                 BX      R6
000167C6                 .dw     checksumFix
000167C8                 .dd     hookedAddr
000167CC                 LDR     R2, =stru_0_40028DC
000167CE                 STR     R1, [SP,#0x28+var_1C]
000167D0                 LDRB    R0, [R6,#(off_0_FFE3F108+2 - 0xFFE3F0FC)]
000167D2                 LDRB    R5, [R6,#(off_0_FFE3F108 - 0xFFE3F0FC)]
000167D4                 LSLS    R0, R0, #4
...
FFE3F000                 PUSH    {R0-R12, LR}
FFE3F004                 BX      changeThingsInCache
FFE3F008                 POP     {R0-R12, LR}
FFE3F00C                 LSLS    R1, R0, #4
FFE3F010                 LDR     R0, =sata_req
FFE3F014                 SUB     SP, SP, #0x14
FFE3F018                 ADDS    R6, R1, R0
FFE3F01C                 LDRB    R1, [R6,#0xD]
FFE3F020                 BX      0x167CC

As you can see, some original instructions are replaced with a jump to new code in an otherwise unused bit of ram at address 0xFFE3F000 and an extra word to make sure the checksum of the code region still is valid. If this isn't done, the HD will try to load a backup from its platters, which isn't what we want. The code that's jumped to executes a routine called changeThingsInCache and then does what the replaced code would've done. It then continues execution in the original routine like nothing has happened.

Now all I need to write was a routine to modify the cached data. For a first test, I decided on a routine that in pseudocode went something like this:

void hook() {
  foreach (cache_struct in cache_struct_table) {
    if (is_valid(cache_struct)) {
      foreach (sector in cache_struct.sectors) {
        sector[0]=0x12345678;
      }
    }
  }
}

This little bit of code would replace the first 4 bytes of every sector in cache with 0x12345678 every time it's called, so if I uploaded all this to the hard disk, I should see that number on the start of every sector I read. I uploaded the bits of code over JTAG...

And lo and behold:

« Prev 4 Next »


© 2006-2022 Sprite_tm - Contact