Sagemcom Router F@st 1704 – Derrotando o checksum do firmware

Feb 18, 2012   #crypto  #f1704  #firmware  #hacking  #mod  #sagemcom 

Como havia dito no post anterior, apesar da SC ter disponibilizado os fontes para o modem/router F@ST1704, um algoritmo de checksum diferente foi utilizado para validar o cabeçalho da imagem (ou “tag”). É usado CRC32 com os bits invertidos para validar o kernel e o rootfs, e assim deveria ser para validar a tag também, como demonstra os fontes os fornecidos.

Mas não é esta a realidade. Claramente a SC não pretende que você altere o software rodando no F1704, mesmo que você tenha os fontes e ferramentas em mãos.

Veremos como chegar à implementação do checksum e entender do que se trata.

“You bought it. You own it. (…)” – https://jailbreakingisnotacrime.org

O F1704, por ser baseado em plataforma Broadcom, deve ter o firmware semelhante ao de outros produtos BCM. Confirmamos usando o analyzetag (de Daniel Dickinson). Ele revela informações razoavelmente corretas (todas as informações aqui têm com base a versão 4.42 GVT como base):

$ ./analyzetag -t bc310 -i F1704_4_42a4GL_GVT_A2pB030t_fs_kernel_NONE
Broadcom image analyzer - v0.1.0
Copyright (C) 2009 Daniel Dickinson
Tag Version: 6
Signature 1: Broadcom Corporatio
Signature 2: ver. 2.0
Chip ID: 6338
Board ID: F@ST1704
Bigendian: true
Image size: 0038c49d, 3720349
CFE Address: 00000000, 0
CFE Length: 00000000, 0
Flash Root Address: bfc10100, 3217096960
Flash Root Length: 002e4000, 3031040
Flash Kernel Address: bfef4100, 3220128000
Flash Kernel Length: 000a849d, 689309
Vendor information: 0
Image CRC: 76cba0a6 [Computed Value: 76cba0a6]
Rootfs CRC: [Computed Value: d80230f8]
Image CRC from sections: 76cba0a6 [Computed Value: 76cba0a6]
Header CRC: 80688d9c [Computed Value: 3fc93269]
Kernel CRC: 374e0ab9 [Computed Value: 374e0ab9]
Rootfs CRC: f83002d8 [Computed Value: f83002d8]

Esta é a tag: um cabeçalho no início da imagem que informa ao router onde gravar o rootfs e o kernel, qual o tamanho destes, informações da imagem e checksums para validar e garantir a integridade dos dados. A própria tag tem um checksum, que é gerado após todos os outros dados já estarem corretamente inseridos. Note que o Header CRC difere do calculado. Para confirmar a estrutura da tag, basta olhar nos fontes fornecidos, em especial no arquivo shared/opensource/include/bcmTag.h:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
// TAG for downloadable image (kernel plus file system)
typedef struct _FILE_TAG
{
char tagVersion[4]; // tag version. Will be 2 here.
char signiture_1[20]; // text line for company info
char signiture_2[14]; // additional info (can be version number)
char chipId[6]; // chip id
char boardId[16]; // board id
char bigEndian[2]; // if = 1 - big, = 0 - little endia of the host
char totalImageLen[10]; // the sum of all the following length
char cfeAddress[12]; // if non zero, cfe starting address
char cfeLen[10]; // if non zero, cfe size in clear ASCII text.
char rootfsAddress[12]; // if non zero, filesystem starting address
char rootfsLen[10]; // if non zero, filesystem size in clear ASCII text.
char kernelAddress[12]; // if non zero, kernel starting address
char kernelLen[10]; // if non zero, kernel size in clear ASCII text.
char imageSequence[4]; // incrments everytime an image is flashed
char psiLength[10]; //save psi length
char iniLength[10]; // save INI length
char backupLength[10]; //save backup length
char reserved[44]; // reserved for later use
uint32 crc_image;
uint32 crc_rootfs;
uint32 crc_kernel;
char imageValidationToken[8];// image validation token - can be crc, md5, sha; for
// now will be 4 unsigned char crc
uint32 crc_header;
char tagValidationToken[16]; // validation token for tag(from signiture_1 to end of
// mageValidationToken)
} FILE_TAG, *PFILE_TAG;

Veja que limpei o código, coloquei os tamanhos dos vetores e desmembrei os campos de “Token” nos CRCs. A tag tem 256 bytes no total e todos os valores são em big-endian, enquanto os campos de endereços e tamanhos (*Address e *Len) são ASCII. Os campos de CRC referem-se a:

  • crc_image – ~CRC32 do rootfs + kernel (concatenados, nesta ordem)
  • crc_rootfs – ~CRC32 do rootfs
  • crc_kernel – ~CRC32 do kernel
  • crc_header – Checksum desconhecido dos primeiros 236 bytes desta tag. São 236 bytes para retirar este próprio campo da verificação, pois é onde ele ficará e não pode entrar no algoritmo.

Existem alguns campos proprietários da SC, mas não são usados e podem ser ignorados.

Faremos um teste alterando um byte apenas na tag, pois assim invalidará apenas o checksum da tag e não alterará os checksums da imagem. Assim veremos como o router responde (e recusa) a imagem inválida. E enquanto isso acontece, monitorar mensagens de log via telnet:

DRIVER-board.c: inCrc=7de1d8a7, tokenCrc=80688d9c
DRIVER-board.c: aesCrc=9897004e

Há 4 coisas interessantes aí:

  • o arquivo “DRIVER-board.c” não existe nos fontes;
  • o inCrc representa o ~CRC32 da tag com a minha modificação (ou seja, em algum ponto ele usa o CRC32 convencional);
  • o tokenCrc representa o CRC desconhecido que está na imagem (campo crc_header), que obviamente está errado pois não sabemos calculá-lo;
  • aesCrc, algum outro CRC, provavelmente o “especial” que estamos procurando, e com um nome bastante sugestivo: aesCrc.

Em uma imagem oficial, o tokenCrc e o aesCrc batem. São o CRC informado e o calculado, respectivamente. Então, para a modificação que eu fiz, o CRC da tag deveria ser 0x9897004e. Precisamos saber como chegar a este número. E, “aesCrc”…

Instantaneamente, vem a cifra AES em mente (ou Rijndael, como preferir). Mesmo Rijndael não sendo uma função hash, faz sentido, em algum ponto, usá-la para gerar o checksum final da imagem.

Uma busca por referências a “aesCrc” nos fontes não retornam nada, enquanto no kernel original sim. Prova que não foram fornecidos tais mecanismos nos fontes liberados. :(

Para comprovar que há a implementação do algoritmo de Rijndael no kernel, basta buscar pelas constantes que formam as tabelas com os S-box do algoritmo (considerando tabelas pré-calculadas). O signsrch confirma a presença:

offset num description [bits.endian.size]
 --------------------------------------------
 001f37e0 165 AES Rijndael S / ARIA S1 [..256]
 001f38e0 166 AES Rijndael Si / ARIA X1 [..256]

Buscaremos referências ao processo de validação da imagem no kernel original. Esta validação ocorre em dois momentos: ao receber uma nova tentativa de atualização da imagem, seja pela interface Web, FTP ou CFE; e em estágios iniciais da carga do kernel quando ele se prepara para montar a rootfs.

Usaremos o momento da validação que acontece na carga do kernel para analisar o processo de checksum, onde a checagem deste pode ser encontrada na função getTagFromPartition do _bcmdrivers/opensource/char/board/bcm963xx/impl1/board.c._ No kernel original temos:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
getTagFromPartition @ 800f6f38
...
800f6fec: jal 0x800f62b0
800f6ff0: li a2,-1
800f6ff4: addiu a0,sp,16 // a0 = nvram_ptr (onde a NVRAM será lida)
800f6ff8: jal 0x800f6ab0
800f6ffc: move s0,v0 // v0 = ~crc32(tag, 236)
800f7000: li v1,-1
800f7004: bne v0,v1,0x800f7020
800f7008: nop
800f700c: lui a0,0x801e
800f7010: jal 0x8002d874 800f7014: addiu a0,a0,4492 "readNvramData failed!\n"
800f7018: j 0x800f703c
800f701c: move a0,zero
800f7020: move a0,s0 // s0 = v0
800f7024: jal 0x801042fc // ? a0 = ~crc32 da tag, a1 = posição 0x2cc da NVRAM
800f7028: addiu a1,sp,732 // nvram_ptr = sp + 16, então a1 = nvram_ptr + 716 (0x2cc)
800f702c: lw v1,236(s1) // v1 = tokenCrc (tag + 236)
800f7030: beq v0,v1,0x800f703c // tokenCrc == aesCrc?
800f7034: move a0,s1
800f7038: move a0,zero
800f703c: lw ra,1048(sp)
800f7040: lw s1,1044(sp)
800f7044: lw s0,1040(sp)
800f7048: move v0,a0
800f704c: jr ra // return
800f7050: addiu sp,sp,1056
...

No fonte fornecido não há leitura da NVRAM neste momento.  A função 0x801042fc também não existe e seu protótipo, de acordo com o trecho acima, seria:

1
uint32 getNewCrc(uint32 crc_original, void *nvram_0x2cc);

Vemos que seu retorno é comparado com o tokenCrc, que é exatamente o CRC da tag informado pela imagem. Uma olhada rápida no arquivo _/shared/opensource/include/bcm963xx/bcmhwdefs.h, que contém a estrutura da NVRAM (_NVRAMDATA), revela que este campo é:

1
char szSecurityCode[NVRAM_SECURITY_CODE_LEN];

Como havia feito um dump completo da flash, nela havia a região da NVRAM. Podemos ver qual é este “security code” ;):

000002cc  53 41 47 45 4d 00 00 00  00 00 00 00 00 00 00 00  |SAGEM...........|

_Beauty!_ Só resta analisar esta função desconhecida. Não vou detalhar todo o processo, pois seria muito extenso para postar aqui. Vou resumir e mostrar os pontos importantes.

Dentro da função, temos uma referência para uma região de memória interessante e outras inúmeras outras funções. Estas funções depois de analisadas, revelaram referências para as tabelas de S-Box que encontramos antes. Logo, Rijndael realmente está envolvido. Caso alguém tenha interesse em ver a função getNewCrc (que agora chamamos de getAesCrc) comentada, colocarei posteriormente no link pool. Meu MIPS-fu é horrível, ignorem qualquer deslize. 😉

Eis a região de memória interessante:

001f411c 53 54 43 00 00 00 00 00 00 00 00 00 00 00 00 00 |STC.............|
001f412c 5d 3e 15 42 28 ae d2 a6 ab f7 15 62 09 1d 4f 3c |]>.B(......b..O

São pares security code + chave inicial. Para cada security code gravado na NVRAM (que referem-se a rebrands do F1704?), o kernel usa a chave inicial respectiva. No nosso caso, a chave usada é a 2b 7e 15 16 28 ae d2 a6 ab f7 15 88 09 cf 4f 3c. Exatamente 16 bytes, o que corresponde a uma chave 128 bits.

Após analisar a função e identificar os trechos que usam o Rijndael, chegamos que o CRC correto, é calculado da seguinte forma:

  • Criptografar o security code (“SAGEM”) com a chave inicial;
  • Criptografar o CRC convencional da tag (inCrc), usando o produto do passo anterior como chave;
  • Calcular o CRC deste produto, usando como valor inicial o CRC convencional.

Implementei rapidamente um código para confirmar e bingo!

crc = 821e2758, crc_flipped = 7de1d8a7
skey = 2b 7e 15 16 28 ae d2 a6 ab f7 15 88 09 cf 4f 3c +...(.........O<
buf1 = 53 41 47 45 4d 00 00 00 00 00 00 00 00 00 00 00 SAGEM...........
buf2 = c0 f0 e8 be 89 78 32 92 09 ff 15 8f 42 58 03 98 .....x2.....BX..
buf1 = 7d e1 d8 a7 00 00 00 00 00 00 00 00 00 00 00 00 }...............
buf3 = 93 92 c3 ea d9 11 e3 ac ce 24 6c 7e c4 f9 85 60 .........$l....`
crc = 6768ffb1
~crc = 9897004e

Construí um novo rootfs com algumas modificações para testar uma imagem custom “assinada” com o novo AES CRC. E funciona! :)Se alguém quiser testar, basta baixá-la do pool. Não se preocupem, testei várias vezes e está perfeitamente funcional. O kernel é o mesmo do original. As modificações foram para auxiliar o teste de novos aplicativos no router. Como por exemplo, embutir o netcat para que não tenhamos que enviar pelo sendbin. Bem, nem precisamos do netcat mais, veja as diferenças:

  • Busybox com novos applets ativados (e com tab completion!!1!eleven!);
  • bftpd rodando na porta 2021 – faça login com as credenciais do router, ele serve a / (somente /var é +w);
  • Crond;
  • uClibc do toolchain (mais completa);
  • Removido alguns restos de arquivos de controle SVN (.svn) que ficaram no firmware (duh, SC!).

Mesmo que não pretenda compilar nada para rodar no router, não há problema algum em atualizar para esta imagem. O F1704 é muito difícil de danificar por atualizações de firmware. Mesmo que falhe por motivo que seja, o CFE nunca é tocado. E não havendo imagem válida na flash, o CFE fornece uma interface Web para resgatar o router (neste caso, ele ficará em 192.168.1.1), onde você pode enviar outra imagem correta.

Finalizarei a ferramenta de CRC e em breve publico os fontes. Por enquanto, é só um hacky PoC. Enviado para o git. 😀

Stay classy, Sagemcom.