More compression, in RAM init

this is meant to be quick and lazy so i'm gonna write like this. also this isn't meant to be a tutorial, i've probably forgotten to explain some stuff here and there but oh well.

in a random firmware image (undisclosed not cause i don't want to tell you just cause it's not relevant) i got a bit caught up finding out how any strings, or. well. any of the ui was drawn to the lcd. there's a bit of a twisty maze of lcd-related function calls at various levels of the stack (drivers for the specific lcd, more generic drawing routines, stuff that read bitmaps from spi flash but seems to be a general bitmap drawing function that just sometimes reads stuff from flash? based on global flags, not on arguments? anyway).

there seemed to be some sort of vtable or dynamic dispatch table for the lcd drawing routines, but where the pointer got initialised was kind of a mystery. i could find where it copied one pointer into the "context" structure1, where the "context" structure's vtable pointer was what a bunch of other stuff (potentially?) made calls through. ok hindsight says i didn't really need to find the initialisation of the vtable, but "draw horizontal line" and "draw vertical line" do look very similar when you can't see the pixels. so it seemed like a decent foothold to gain.

*(undefined4 *)(param_1 + 0x48) = _DAT_20000adc;

the offending vtable pointer initialisation

so ok. vtable pointer comes from a ram address. but nothing seems to - there's no write references to _DAT_20000adc. so ok, probably comes from the initialised section of ram, .data or whatever. usually it's pretty easy to uncover where in flash the data which is to be copied into the initialised section of ram is - you find some function near the entry point that's inscrutable and looks nothing like the rest of the firmware, and references some kind of table-y thing somewhere in flash. that table usually contains either just (source, dest, length) tuples, sometimes (function pointer, argument list) tuples to allow more than just copying (zero init!)

here's where it gets odd

so this had a function like that, and a table like that.

void FUN_080e8b98(void) {
  int *piVar1;

  for (piVar1 = &DAT_080e91f0; piVar1 != (int *)&UNK_080e9218;
      piVar1 = (int *)(*(code *)((int)piVar1 + *piVar1))(piVar1 + 1)) {
  }
  return;
}
ddw FFFFDE55h # DAT_080e91f0 (the start pointer)
ddw B784h
ddw 10000000h
ddw 1BC70h
ddw 20002698h
ddw 0h
ddw FFFFBB49h
ddw 5F7Fh
ddw 1CCEh
ddw 20000000h
...           # UNK_080e9218 (the end pointer)

some things seem familiar at first glance: these things love to use start and end pointers in their loops rather than lengths, and i see a function pointer being called. that looks a lot like our "function pointers and argument lists" style. but uh.

0xffffde55 is not a function pointer. and why are we adding stuff to the function pointer. don't do that

riiight, so. the function pointers are, offsets, relative to where those offsets are stored. you take the address where the 0xffffde55 is stored, add 0xffffde55 to it, and you land at a function. that offset is negative (32-bit, 2's complement) so it's somewhere in the firmware before this table. yuck.

but that's fine, and in a way it's nice - we know the 0xffffbb49 later on in this table is probably another function pointer, or at minimum another self-relative offset. that gives us info and we like info.

ok so we inspect that first function, and it's not too bad. the pointer it gets passed is to the next value in the table, 0xb784, and that's a length. the function reads another value, 0x10000000 - that's the destination pointer. and then zeros that many bytes at that address. it reads the next two values, uses those in the same way, reads that the next length is 0, realises that's the end marker, and returns. it returns the address of the 0xffffbb49, because it needs to tell the init table processing function how much data it just read and where the caller should pick up from.

i'm not confused yet, this is fine

the next one is the curveball. it reads those three values after it - 0x5f7f, 0x1cce, and 0x20000000. we know 0x20000000 is gonna be ram, the other two look a bit like lengths. maybe one's another self-relative offset, but just a positive one. we'll have to look at the 0xffffbb49 function.

int * FUN_080e4d50(int *param_1)

{
  byte bVar1;
  byte *pbVar2;
  byte *pbVar3;
  byte *pbVar4;
  uint uVar5;
  uint uVar6;
  int iVar7;
  uint uVar8;
  int unaff_r9;

  uVar5 = param_1[1];
  pbVar4 = (byte *)param_1[2];
  pbVar2 = (byte *)(*param_1 + (int)param_1);
  if ((int)(uVar5 << 0x1f) < 0) {
    pbVar4 = pbVar4 + unaff_r9;
  }
  while (pbVar2 != pbVar2 + (uVar5 >> 1)) {
    bVar1 = *pbVar2;
    uVar8 = bVar1 & 3;
    pbVar3 = pbVar2 + 1;
    if ((bVar1 & 3) == 0) {
      pbVar3 = pbVar2 + 2;
      uVar8 = pbVar2[1] + 3;
    }
    uVar6 = (uint)(bVar1 >> 4);
    if (uVar6 == 0xf) {
      uVar6 = *pbVar3 + 0xf;
      pbVar3 = pbVar3 + 1;
    }
    while (uVar8 -= 1, uVar8 != 0) {
      *pbVar4 = *pbVar3;
      pbVar3 = pbVar3 + 1;
      pbVar4 = pbVar4 + 1;
    }
    pbVar2 = pbVar3;
    if (uVar6 != 0) {
      uVar8 = bVar1 >> 2 & 3;
      pbVar2 = pbVar3 + 1;
      if (uVar8 == 3) {
        pbVar2 = pbVar3 + 2;
        uVar8 = (uint)pbVar3[1];
      }
      pbVar3 = pbVar4 + -((uint)*pbVar3 + uVar8 * 0x100);
      for (iVar7 = uVar6 + 2; iVar7 != 0; iVar7 += -1) {
        *pbVar4 = *pbVar3;
        pbVar3 = pbVar3 + 1;
        pbVar4 = pbVar4 + 1;
      }
    }
  }
  return param_1 + 3;
}

w h a t

the loop about half way down - while (uVar8 -= 1, uVar8 != 0) and its body - looks like a pretty boring source to dest copy. what uh. what goes on in that last for loop, though.

compression

i'm not gonna show you the annotated and named and cleaned up version of that function, again, cause it's not important. you can re it yourself if you want - param_1 points to the 0x5f7f.

we were a bit right. the 0x5f7f is another self-relative offset, to some more data. a bunch more - 0x1cce >> 1 = 0xe67 bytes of data to be specific (this is the first little bit of that function!). it's, it's another table.

00 FE 63 7C 77 7B F2 6B 6F C5 30 01 67 2B FE D7 AB 76 CA 82 C9 7D FA 59 47 F0 AD D4 A2 AF 9C A4 72 C0 B7 FD 93 26 36 3F F7 CC 34 A5 E5 F1 71 D8 31 15 04 C7 23 C3 18 96 05 9A 07 12 80 E2 EB 27 B2 75 09 83 2C 1A 1B 6E 5A ...

truncated for your safety

sooo. d'ya know about lzss? turns out this is a similar-ish "dictionary coding" compression algorithm. uh, go read about that on wikipedia or something i wrote a while ago.

each entry in this table starts with a tag byte.

0bAAAABBCC
    AAAA - compressed data length
    BB   - compressed data source offset (high byte)
    CC   - literal data length

then, since the 4 and 2 bit fields are pretty small, it does a bit of stuff to allow bigger values (in this order). if the literal data length was 0, read another byte, add 3 to the value, and use that as the literal data length instead. then, if the compressed data length was 15, read another byte, add 15 to the value, and use that as the compressed data length instead.

then, from wherever you've ended up advancing the pointer to, read in the literal data. i.e., read literal data length bytes, and write them to the output.

noooow we deal with the compression. if the compressed data length is 0, we go on to reading the next table entry straight away. if it's not 0, we immediately read another byte to act as the low byte of the compressed data source offset. then if the high byte of the offset from the tag was equal to 3, we read a byte (no adding 3!) and use it as the high byte. combine the two for the full 16-bit offset.

once you've got it, seek that many bytes backwards in the output, read compressed data length bytes !plus two! (why) and write them out to the output (also keeping in mind it can totally read bytes that got written out to the file in this process back again - it's an easy way to create a repeating pattern).

rinse and repeat. keep reading tags and their literal/compressed stuff until you've read 0x1cce >> 1 = 0xe67 bytes from the table (it's not the decompressed length). or however many you have.

that's a lot of text for it to all boil down to, that. also, idk if this is actually an existing compression format or not, but eh. i got my data out and my lcd vtable so i'm happy

lcd_default_vtable:
080e6fc4 dd 20 07 08       lcd_vtable
         ed 20 07 08 
         f9 20 07 08 ...
    080e6fc4 dd 20 07 08   void *   lcda_color_to_lcd+1      color_to_lcd
    080e6fc8 ed 20 07 08   void *   lcda_color_from_lcd+1    color_from_lcd
    080e6fcc f9 20 07 08   void *   FUN_080720f8+1           field2_0x8
    080e6fd0 47 75 02 08   void *   lcda_draw_bitmap+1       draw_bitmap_kinda
    080e6fd4 bd 74 02 08   void *   lcda_draw_hline+1        draw_hline
    080e6fd8 f3 74 02 08   void *   lcda_draw_vline+1        draw_vline
    080e6fdc 29 75 02 08   void *   lcda_draw_rect+1         draw_rect
    080e6fe0 85 74 02 08   void *   lcda_read_pixel+1        read_pixel
    080e6fe4 01 21 07 08   void *   lcda_get_bounds+1        get_bounds
    080e6fe8 61 74 02 08   void *   lcda_write_pixel+1       write_pixel
    080e6fec 9b 74 02 08   void *   lcda_invert_pixel+1      invert_pixel
    080e6ff0 b9 76 02 08   void *   FUN_080276b8+1           field11_0x2c

yay

did you come across this? here's some python. change the offset and length, give it the input file (with the compressed data) as the first arg and the output file (where to put the decompressed data) as the second and you should be good to go.


  1. context is one of many reverse engineering ways of saying "thingy". i guess it's more specifically "thingy with a bunch of state for another thingy", but yeah 

You might also be interested in:

See all posts