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):
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:
<td>
<div class="c codecolorer">
<span class="co1">// TAG for downloadable image (kernel plus file system)</span><br /> <span class="kw4">typedef</span> <span class="kw4">struct</span> _FILE_TAG<br /> <span class="br0">{</span><br /> <span class="kw4">char</span> tagVersion<span class="br0">[</span><span class="nu0">4</span><span class="br0">]</span><span class="sy0">;</span> <span class="co1">// tag version. Will be 2 here.</span><br /> <span class="kw4">char</span> signiture_1<span class="br0">[</span><span class="nu0">20</span><span class="br0">]</span><span class="sy0">;</span> <span class="co1">// text line for company info</span><br /> <span class="kw4">char</span> signiture_2<span class="br0">[</span><span class="nu0">14</span><span class="br0">]</span><span class="sy0">;</span> <span class="co1">// additional info (can be version number)</span><br /> <span class="kw4">char</span> chipId<span class="br0">[</span><span class="nu0">6</span><span class="br0">]</span><span class="sy0">;</span> <span class="co1">// chip id</span><br /> <span class="kw4">char</span> boardId<span class="br0">[</span><span class="nu0">16</span><span class="br0">]</span><span class="sy0">;</span> <span class="co1">// board id</span><br /> <span class="kw4">char</span> bigEndian<span class="br0">[</span><span class="nu0">2</span><span class="br0">]</span><span class="sy0">;</span> <span class="co1">// if = 1 - big, = 0 - little endia of the host</span><br /> <span class="kw4">char</span> totalImageLen<span class="br0">[</span><span class="nu0">10</span><span class="br0">]</span><span class="sy0">;</span> <span class="co1">// the sum of all the following length</span><br /> <span class="kw4">char</span> cfeAddress<span class="br0">[</span><span class="nu0">12</span><span class="br0">]</span><span class="sy0">;</span> <span class="co1">// if non zero, cfe starting address</span><br /> <span class="kw4">char</span> cfeLen<span class="br0">[</span><span class="nu0">10</span><span class="br0">]</span><span class="sy0">;</span> <span class="co1">// if non zero, cfe size in clear ASCII text.</span><br /> <span class="kw4">char</span> rootfsAddress<span class="br0">[</span><span class="nu0">12</span><span class="br0">]</span><span class="sy0">;</span> <span class="co1">// if non zero, filesystem starting address</span><br /> <span class="kw4">char</span> rootfsLen<span class="br0">[</span><span class="nu0">10</span><span class="br0">]</span><span class="sy0">;</span> <span class="co1">// if non zero, filesystem size in clear ASCII text.</span><br /> <span class="kw4">char</span> kernelAddress<span class="br0">[</span><span class="nu0">12</span><span class="br0">]</span><span class="sy0">;</span> <span class="co1">// if non zero, kernel starting address</span><br /> <span class="kw4">char</span> kernelLen<span class="br0">[</span><span class="nu0">10</span><span class="br0">]</span><span class="sy0">;</span> <span class="co1">// if non zero, kernel size in clear ASCII text.</span><br /> <span class="kw4">char</span> imageSequence<span class="br0">[</span><span class="nu0">4</span><span class="br0">]</span><span class="sy0">;</span> <span class="co1">// incrments everytime an image is flashed</span><br /> <span class="kw4">char</span> psiLength<span class="br0">[</span><span class="nu0">10</span><span class="br0">]</span><span class="sy0">;</span> <span class="co1">//save psi length</span><br /> <span class="kw4">char</span> iniLength<span class="br0">[</span><span class="nu0">10</span><span class="br0">]</span><span class="sy0">;</span> <span class="co1">// save INI length</span><br /> <span class="kw4">char</span> backupLength<span class="br0">[</span><span class="nu0">10</span><span class="br0">]</span><span class="sy0">;</span> <span class="co1">//save backup length</span><br /> <span class="kw4">char</span> reserved<span class="br0">[</span><span class="nu0">44</span><span class="br0">]</span><span class="sy0">;</span> <span class="co1">// reserved for later use</span><br /> <span class="kw4">uint32</span> crc_image<span class="sy0">;</span><br /> <span class="kw4">uint32</span> crc_rootfs<span class="sy0">;</span><br /> <span class="kw4">uint32</span> crc_kernel<span class="sy0">;</span><br /> <span class="kw4">char</span> imageValidationToken<span class="br0">[</span><span class="nu0">8</span><span class="br0">]</span><span class="sy0">;</span><span class="co1">// image validation token - can be crc, md5, sha; for</span><br /> <span class="co1">// now will be 4 unsigned char crc</span><br /> <span class="kw4">uint32</span> crc_header<span class="sy0">;</span><br /> <span class="kw4">char</span> tagValidationToken<span class="br0">[</span><span class="nu0">16</span><span class="br0">]</span><span class="sy0">;</span> <span class="co1">// validation token for tag(from signiture_1 to end of</span><br /> <span class="co1">// mageValidationToken)</span><br /> <span class="br0">}</span> FILE_TAG<span class="sy0">,</span> <span class="sy0">*</span>PFILE_TAG<span class="sy0">;</span>
</div>
</td>
</tr>
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:
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:
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:
<td>
<div class="text codecolorer">
getTagFromPartition @ 800f6f38<br /> ...<br /> 800f6fec: jal 0x800f62b0<br /> 800f6ff0: li a2,-1<br /> 800f6ff4: addiu a0,sp,16 // a0 = nvram_ptr (onde a NVRAM será lida)<br /> 800f6ff8: jal 0x800f6ab0<br /> 800f6ffc: move s0,v0 // v0 = ~crc32(tag, 236)<br /> 800f7000: li v1,-1<br /> 800f7004: bne v0,v1,0x800f7020<br /> 800f7008: nop<br /> 800f700c: lui a0,0x801e<br /> 800f7010: jal 0x8002d874 800f7014: addiu a0,a0,4492 "readNvramData failed!\n"<br /> 800f7018: j 0x800f703c<br /> 800f701c: move a0,zero<br /> 800f7020: move a0,s0 // s0 = v0<br /> 800f7024: jal 0x801042fc // ? a0 = ~crc32 da tag, a1 = posição 0x2cc da NVRAM<br /> 800f7028: addiu a1,sp,732 // nvram_ptr = sp + 16, então a1 = nvram_ptr + 716 (0x2cc)<br /> 800f702c: lw v1,236(s1) // v1 = tokenCrc (tag + 236)<br /> 800f7030: beq v0,v1,0x800f703c // tokenCrc == aesCrc?<br /> 800f7034: move a0,s1<br /> 800f7038: move a0,zero<br /> 800f703c: lw ra,1048(sp)<br /> 800f7040: lw s1,1044(sp)<br /> 800f7044: lw s0,1040(sp)<br /> 800f7048: move v0,a0<br /> 800f704c: jr ra // return<br /> 800f7050: addiu sp,sp,1056<br /> ...
</div>
</td>
</tr>
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:
<td>
<div class="c codecolorer">
<span class="kw4">uint32</span> getNewCrc<span class="br0">(</span><span class="kw4">uint32</span> crc_original<span class="sy0">,</span> <span class="kw4">void</span> <span class="sy0">*</span>nvram_0x2cc<span class="br0">)</span><span class="sy0">;</span>
</div>
</td>
</tr>
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/bcm_hwdefs.h, que contém a estrutura da NVRAM (NVRAM_DATA), revela que este campo é:
<td>
<div class="c codecolorer">
<span class="kw4">char</span> szSecurityCode<span class="br0">[</span>NVRAM_SECURITY_CODE_LEN<span class="br0">]</span><span class="sy0">;</span>
</div>
</td>
</tr>
Como havia feito um dump completo da flash, nela havia a região da NVRAM. Podemos ver qual é este “security code” ;):
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:
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!
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.
Link pool
- f1704_180212_dev.bin
- getaescrc.txt
- router_hacking repo – Sagemcom FW image build 0.1 (“sagemcom”)