[[aos_file_format]]
 
Table of Contents

AOS File Format

We can in fact identify 4 kinds of files :

AOS file structure

An AOS file is structured with chunks, which can be :

string hexadecimal decimal
RIFF 5249 4646 21065 17990
CHOS 4348 4f53 17224 20307
SWID 5357 4944 21335 18756
HWID 4857 4944 18519 18756
FLSH 464C 5348 17996 21320
CCOD 4343 4F44 17219 20292
CODE 434F 4445 17231 17477
SIGN 5349 474E 21321 18254
FILE 4649 4C45 17993 19525
LIST


NB: hexadecimal and decimal are usefull for the reverse.

One AOS file can contain one or more files (MAX_AOS_FILES = 32). The first chunk must be a RIFF chunk, and {RIFF, FLSH, SIGN, CCODE} are mandatory chunks.

RIFF chunk

The RIFF Chunk is a container.

offset length description
0 4 chunk type
4 4 total chunk length (with its content)

SWID chunk : SoftWare IDentifier

offset length description
0 4 chunk type
4 4 total chunk length
8 4 version id (eg 1000)
12 version string

HWID chunk : HardWare IDentifier

offset length description
0 4 chunk type
4 4 total chunk length
8 ??? (eg. 0000)

FLSH chunk

offset length description
0 4 chunk type
4 4 total chunk length
8 4 base address (eg. 00 00 60 00)
12 4 ucsize (eg. 00 08 4d c2) : used in CCOD

CCOD / CODE chunks

offset length description
0 4 chunk type
4 4 total chunk length
8 4 csize = AJZ size
12 AJZ content

with AJZ being

offset length description
0 4 header (ZcZc)
4 4 uncompressed size
8 4 compressed size
12 4 checksum
16 csize-16 image

SIGN chunk

offset length description
0 4 chunk type
4 4 total chunk length = 0×6C = 108 (96 + 12)
8 2 ??
10 2 0 or other : determine base values for descrambling
Content
12 10 Cyphered HD serial number
  0000050: 0000 0010 0000 6000 0008 4dc2 5349 474e              SIGN
  0000060: 0000 006c 0000 0000 c4db f207 4fc9 9bc8  ...l........O...
  0000070: 1adb bffb f5d7 9a23 9571 597b 3e07 d8e2  .......#.qY{>...
  0000080: 600d 6324 bfff 7bfe ea24 d186 056d d733  `.c$..{..$...m.3
  0000090: c6a1 4711 6bab 793b 78b2 7756 c524 61fd  ..G.k.y;x.wV.$a.
  00000a0: fc42 9bd4 d448 b17b 7443 b398 1239 e014  .B...H.{tC...9..
  00000b0: eed5 06cd 1e98 3c9a 7c1c e1c7 5074 be0c  ......<.|...Pt..
  00000c0: 081e 6fcf 9e9d 5e13 4343 4f44 0004 2ab8  ..o...^.

Memory image of the AOS file

offset offset {110/?} description
0×05F9D6 0×064500 pDecryptKey / 0×7c9ch copied here when R2 = 0 / 0×7d08 otherwise
0×05F9DA 0×064504 pDECIPHERED_STUFF (computed by scramble_B)
offset size description
0 4 768 – this is the bitlength of the RSA message
2 10 deciphered HD serial number
12 16 md5 digest of the code chunk
28 32 deciphered zaza key
60 44? random padding ?
0×05F9DE 0×064508 pFILE[32]
0×05FA5E 0×064588 pCCODE
0×05FA62 0×06458C pSIGN
0×05FA66 0×064590 pFLSH
0×05FA6A 0×064594 pHWID
0×05FA6E 0×064598 pSWID
0×05FA72 0×06459C pRIFF

SIGN chunk decyphering

bignum structure

A variable size buffer is used to represent a long number b=b0b1…bn-2bn-1

+---------------------+----------+----------+- - - - - -+----------+----------+
|                     |          |          |           |          |          |
| size in             |    b     |    b     |           |    b     |    b     |
|   bits              |     n-1  |     n-2  |           |     1    |     0    |
|                     |          |          |           |          |          |
|  N = 8n             |   LSB    |          |           |          |   MSB    |
|                     |          |          |           |          |          |
+---------------------+----------+----------+- - - - - -+----------+----------+
<----------------------------------------------------------------------------->
                              n+2 bytes
___________________________
fig.1: the bignum structure

bignum operations

We have chosen to use the gmp library for basic operations on bignums.

Therefore, the following code snippets must be linked with -lgmp -lgmpxx.

descrambling code

This is a draft of a reverse-engineering of the descrambling code. There are errors but it is enough to give a general feel. Basically, the important function is scrambleB. I bet my ass it does the exponent of two bignums (modulo another). In turn, this feels very much like RSA public key decryption algorithm. Thus, I expect the sign chunk data to be scrambled with Archos’ private key.

  #include <gmpxx.h> // requires libgmp
  
  typedef mpz_class bignum;
  
  typedef unsigned char u8;
  typedef unsigned short u16;
  
  bignum table[200];
  
  int table_length;
  
  bignum one = 1;
  
  void scrambleA(bignum k) {
    table[0] = k*k >> 8;
    table[1] = 8; // ?
    table[2] = k;
    int r6 = 2;
    while (table[0] > table[r6]) {
      table[r6+1] = table[1];   
      table[r6+1] *= table[r6];
      r6++;
    }
    table_length = r6;
  }
  
  bignum table_multimodulo(bignum p1, bignum p2) {
    int r6 = table_length;
    while (r6 >= 2) {
      bignum q = table[r6];
      if (p1 <= q)
        r6--;
      else
        p1 -= q;
    }
    // length p1 is set to length p2. Why for? Ouch!
    return p1;
  }
  
  void  bignum_doSomething1(bignum &p1, bignum p2, bignum p3) {
    p1 = table_multimodulo(p1 * p2, p3);
  }
  
  
  void scrambleB(bignum p1, u8* data) {
    bignum tmp3, tmp4;
  
    int length = *(u16*)data; // A12 in bits most probably
    data += 2; 
  
    tmp3 = p1;
  
    int r1 = 0;
    p1 = 1;
    
    r1 = 0;
    u8 curdata; // r6
    int curbit; // r2
    while (r1 < length) {
      //r2 = (*(u16*)0x05F5C2) % 2;
      // computation result ignored!
      
      //lcdStuff(0x28, (*(u16*)0x04CB64) + ((17 * r1) % length));
      //clearWatchdog();
      // this has probably nothing to do with descrambling
      
      if ((r1 % 8) == 0) { //load next byte.
        curdata = *data;
        data++;
      }
      
      curbit = curdata & 1; // load next bit.
      curdata >>= 1;
  
      if (curbit != 0) {
        bignum_doSomething1(p1, tmp3, p1);
        p1 = table_multimodulo(p1, p1);
      }
  
      tmp4 = tmp3;
  
      bignum_doSomething1(tmp3, tmp4, p1);
      p1 = table_multimodulo(p1, p1);
      
      r1++;
    }
  
  }

Archos RSA keys

Public keys can be retrieved from the gmini memory via the following algorithm.

#include <gmpxx.h> // requires libgmp
#include <iostream.h>

unsigned char keydata1[] = {0xad,0x77,0x47,0xa2,0xb0,0x4c,0x11,0x6f,0xdc,0x8a,0x8b,0x9d,0xd6,0x3e,0xa,0xdc,0xe6,0xa5,0x21,0x4a,0x3b,0x75,0xd,0xd2,0x84,0x1b,0xa7,0xc,0x6b,0x4a,0xca,0x2a,0x41,0x56,0xc9,0x99,0xf2,0x3f,0xa,0xfa,0xac,0xe,0xd5,0x38,0x6,0x3d,0x52,0xa6,0x8e,0xb7,0xc8,0x9d,0xd5,0xb2,0x98,0x11,0x2e,0x59,0xd2,0x1f,0x64,0xbf,0x18,0x7d,0x3e,0xb8,0x4c,0x73,0xa7,0x11,0xff,0x36,0x7d,0xe6,0x60,0xbd,0x4a,0xbe,0x77,0xdc,0x2b,0x6c,0x42,0x74,0xfe,0x4d,0x25,0x1d,0x20,0xe0,0x2a,0xcd,0x38,0x12,0xfa,0x3b};

unsigned char keydata1b[] = {0x18,0xe5,0x37,0x3b,0x45,0x59,0xc6,0x14};

unsigned char keydata2[] = {0x39,0x41,0x44,0x6e,0xbc,0x83,0xc3,0xee,0x63,0x86,0x98,0xb7,0xd4,0xff,0x79,0x4b,0xd1,0xcb,0x91,0x92,0x78,0xe0,0xb9,0x8c,0x3a,0x73,0x77,0x64,0x91,0xdf,0xda,0xb5,0xa0,0xc0,0x44,0xd7,0xdd,0xd9,0x3b,0xd9,0x21,0x38,0x12,0x93,0xfd,0xf8,0xfe,0x6c,0x4f,0x90,0x51,0x51,0xd6,0xd9,0x59,0x58,0xc3,0xa1,0xda,0x20,0xe4,0xe3,0xea,0xae,0xe,0x9b,0xf8,0x47,0x29,0x1d,0xa9,0x9,0x8e,0x9b,0x13,0xa9,0x9c,0x2a,0xf8,0xe1,0xc9,0xe5,0x25,0xee,0xbc,0x48,0x90,0xa1,0x9,0x35,0xf5,0xa0,0xbf,0x2a,0xe8,0xfa};

unsigned char keydata2b[] = {0xf4,0x8b,0x58,0x64,0xcd,0xaa,0xf8,0x10};

bignum loadDecryptKey(unsigned char* key, int len) {
  bignum tmp = 0;
  for (int i = len-2; i >= 0; i-=2) {
    tmp = tmp << 8;
    tmp += key[i];
    tmp = tmp << 8;
    tmp += key[i+1];
  }
  return tmp;
}

int main() {
  cout << loadDecryptKey(keydata1, 96) << ',' << loadDecryptKey(keydata1b, 8) << endl;
  cout << loadDecryptKey(keydata2, 96) << ',' << loadDecryptKey(keydata2b, 8) << endl;
}

Data in the previous code is found at the following addresses:

0×0031FE key1aLen
0×003260 key1bLen
0×003200 key1aData
0×003262 key1bData
0×00326A key2aLen
0×00326C key2bLen
0×0032CC hey2aData
0×0032CE key2bData

The result is:

n e
15175338213866630… 14273109368524970213
14129095978200489… 17875013052544644235

Where m’ = (m^e) mod n

Interestingly, those two keys are the same across the whole gmini line. (I’ve checked 100_v1100, SP_v130, 220_v1100).

Decrypted result

The above code can be complemented with the actual decryption algorithm in order to retrieve the decrypted sign chunk.

unsigned char f100_1100sign[] = {0x93,0xf9,0x58,0x2e,0x57,0x98,0x6b,0x94,0xf4,0xda,0x6f,0x62,0xf5,0xe5,0xde,0x61,0xb,0xbb,0x84,0x71,0x64,0xd,0xd3,0x3a,0x39,0x5a,0x95,0xc9,0x43,0xc5,0x9,0x10,0x11,0x98,0x40,0x63,0xf0,0xbd,0xc4,0x7b,0x10,0xf4,0x88,0x1f,0x7,0x1b,0xfc,0x40,0x24,0xbf,0xd6,0x9c,0x7a,0x44,0xf1,0x99,0x6b,0x54,0x1,0xe8,0x3b,0x18,0x5c,0x60,0x80,0x3c,0xe8,0xe3,0x21,0x87,0x71,0x6e,0xce,0x74,0x74,0xf2,0xf,0x5f,0xdb,0xe9,0xfb,0xc4,0x8c,0x55,0x11,0x58,0xf0,0x80,0x98,0x55,0xf8,0xa4,0x8e,0x5a,0x1d,0x5d};

bignum loadMessage(unsigned char* msg, int len) {
  bignum tmp = 0;
  for (int i = len-1; i >= 0; i--) {
    tmp = tmp << 8;
    tmp += msg[i];
  }
  return tmp;
}

void storeMessage(unsigned char* msg, int len, bignum n) {
  for (int i = 0; i < len; i++) {
    bignum m = n % 256;
    unsigned long l = m.get_ui();
    msg[i] = l;
    n = n >> 8;
  }
}

void printMessage(unsigned char* msg, int len) {
  for (int i = 0; i<96; i++) {
    unsigned char c = msg[i];
    if (c >= 32 && c < 127) {
      cout << c;
    } else {
      cout << '.';
    }
  }
  cout << endl;
}

int main() {
  bignum sign = loadMessage(f100_1100sign, 96);
  bignum n = loadDecryptKey(keydata1, 96);
  bignum d = loadDecryptKey(keydata1b, 8);

  bignum result;
  mpz_powm (result.get_mpz_t(), sign.get_mpz_t(), 
	    d.get_mpz_t(), n.get_mpz_t());

  printMessage(f100_1100sign, 96);
  storeMessage(f100_1100sign, 96, result);
  printMessage(f100_1100sign, 96);
}

Giving the output:

..X.W.k...ob...a...qd..:9Z..C.....@c...{.......@$...zD..kT..;.\`.<..!.qn.tt.._.....U.X...U...Z.]
 e...8.l..jp.)...3../...3RDNALORDNAXELARIMIDALVXRBJXSOHCRA}.l.tS..Njq=....X../=...W..$"..9......

Which proves the RSA theory. Notice we find the XOR key in there (you were right gromit ;)

Come on, Vladimir, Alex, Roland… Lend us your private key ;)

SN verification

Once the SIGN chunk operations are performed, the HD serial number is the firmware file is checked (LOS files only in fact). There is a comparison between the deciphered HD SN (which we suppose is in the SIGN chunk) and the Gmini HD SN. It is kinda license system, probably used at the beginning to sell plugins to the customer.

The debug messages inform us about ‘md5 digests’… weird…

Getting the device SN / Product key

#define PRODUCT_KEY_FILE_ORIGIN 05D208h /* for the 220 */

int flag = 0;

for (i = 10; i < 20; i++) {
        if (*(PRODUCT_KEY_FILE_ORIGIN + i) != 32) {
                flag = 1;
        }
        if (flag == 1) {
                *(param + 1) = *(PRODUCT_KEY_FILE_ORIGIN + i)
        }
}
*param+i = 0

s = strlen(param);
if (s >= 10) then return;
ss = 10 - s;

for (i = 9; i => ss; i--)
{
        R5R4 = i - ss
        *(param+i) = *(param+i)         Sign extension....
}
*A13 + 10 = 0
for (i = 0; i < ss; i++)
{
        *(param+i) = 48
}

Getting the deciphered HD SN

for (i = 0; i < 10; i++) {
        memory[A15 + 13 + i] = memory[*064504h + 2 + i] & FFh
}

CCODE digest verification

Computing the CCODE digest

028C88:( 120) 9FC3 FC40: JSR something_like_md5_digest_of_the_CCODE_chunk /* TODO */

Getting the deciphered digest

#define pDECIPHERED_STUFF 0x064504
#define DECIPHERED_CCODE_DIGEST 0x0515F8

memcpy(DECIPHERED_CCODE_DIGEST, *pDECIPHERED_STUFF + 12, 16);

Files creation

#define NB_AOS_FILES 0x007C88

int nb_err = 0;
int i;

for (i = 0; i < NB_AOS_FILES; i++) {
        set_R1R0=R11R10*2^R12_bool(R11R10>=R3R2)();
        nb_err += CreateFile(FILE[i]);
}

ZaZa deciphering

Il we already have a CODE section, and not CCOD, this step is ignored.

Getting the ZaZa key

#define DECIPHERED_ZAZA_KEY 0x071F38
#define RELATED_TO_ZAZA_KEY 0x071F58

memcpy(DECIPHERED_ZAZA_KEY, *pDECIPHERED_STUFF+28, 32);
*RELATED_TO_ZAZA_KEY = 0;
 
  aos_file_format.txt · Last modified: 2004/11/17 07:46
 
Recent changes RSS feed Creative Commons License Donate Powered by PHP Valid XHTML 1.0 Valid CSS Driven by DokuWiki