
CH55x マイクロプロセッサ用ファームウェア抽出ツール
CH55x ファームウェア抽出ツールは、CH55x 集積回路からファームウェアを読み出すために使用されます。デバイスには、ブートローダーを通じてファームウェアを直接読み出す組み込み機能はありません。ただし、ファームウェアの内容を 8 バイト単位で指定されたデータと照合する機能があります。この機能は教科書どおりのタイミング攻撃に対して脆弱であり、ここではそれを利用してデバイスからファームウェアを抽出します。ブートローダーには USB または UART 経由でアクセスできます。このファームウェア抽出ツールは UART でのみ動作します。USB のレイテンシが高いとこの攻撃が困難になるためです。テスト済みのチップは CH552 と CH554、ブートローダーバージョンは 2.4 と 2.5 です。ファームウェア抽出ツールのハードウェアは STM32 Blue Pill に基づいています。容易に入手でき、安価で、必要な性能を備えているためです。
ブートローダーは事前にデバイスから読み出され、その通信プロトコルはリバースエンジニアリングされています。以下は、おおよそ正しい verify コマンドと、ブートローダーで使用されている verify 関数です。いくつかのガードがあり、長さが 8 の倍数であること、アドレスが 8 バイト境界に揃っていること、アドレスが 0x3800 未満であること、以前の verify エラーがないことが要求されます。最後のガードは、検証が失敗するたびに CH55x の再起動が必要であることを意味します。
検証関数は、検証対象の 1 バイトが失敗するとすぐに戻ることがわかります。つまり、正しいバイトが多いほど、検証関数の実行時間が長くなります。これは、悪用可能なタイミング攻撃の教科書的な例です。
unsigned char verifycmd[] = {
// 0x57, 0xab, // UART magic not included to verify function
0xa6, // Verify command
5 + len, // Constant 5 plus length of data to verify
0, // Unused
addr_low, // Low byte of address
addr_high, // High byte of address
0, 0, 0, // Unused
0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8, // Data to verify against
checksum
}
unsigned char verify(unsignec char *cmdbuffer)
{
static char prev_verify_error;
unsigned char len = cmdbuffer[1]-5
unsigned short addr = cmdbuffer[3] + cmdbuffer[4] << 8;
if (len & 0x07 || addr & 0x07 || addr > 0x3800 || prev_verify_error) {
return 0xfe;
}
for (int i=0; i < len; i++) {
// Key can be set through bootloader, and CBYTE[] means code memory
if(key[i & 0x07] ^ cmdbuffer[8+i] ^ CBYTE[addr+i]) {
prev_verify_error = 1;
return 0xf5;
}
}
return 0;
}
試行錯誤の結果、正しいバイトごとに verify 関数の実行時間が約 4.2 µs 延びることがわかりました。ブートローダーが UART 通信に使用するボーレートは 57600 で(他の文献に何と書いてあっても)、1 ビットの転送に約 17.4 µs かかります。この 2 つの時間の関係は重要です。なぜなら、応答は約 17 µs のジッターを伴って送信されているように見えるからです。私たちは、verify 関数の応答時間が 4.2 µs 異なる応答を区別しようとしていますが、UART のジッター(クロックタイミング)により最大 17 µs 異なる可能性があります。これは困難な作業のように思えますが、統計的な手段で可能です。
あるバイトが正しいかどうかは、そのバイトの検証を複数回試みて結果を記録することで判断できます。たとえば、最初のバイトが間違っている場合に応答を受信する最短時間が 30 µs だとします。すると、UART のジッターにより、最初のバイトが不正な場合の最大応答時間は 30 µs + 17.4 µs = 47.4 µs と予想できます。同時に、最初のバイトが正しく 2 番目のバイトが不正な場合、この「範囲」は 34.2 µs から 51.6 µs に押し上げられます。いくらかの余裕を加えると、応答時間が約 33 µs より短ければ最初の文字は不正、応答時間が 48 µs より長ければ最初の文字は正しいと結論できます。これが、ファームウェアを抽出するために使用されるタイミング攻撃の基礎です。
これは完全に自動のファームウェア抽出ツールではなく、ソースコードの変更と再コンパイル(VS Code + PlatformIO を使用)が必要です。主な理由は、バイトごとの正確なタイミング特性がセットアップごとに異なり、調整が必要になるためです。調整プロセスは自動化可能ですが、このプロジェクトの目標ではありませんでした。主要な調整は prober_limits 変数によって行います。たとえば、prober_limits[0] には、検証対象の 8 バイトのうちバイト 0 のリミットが含まれます。応答時間が .invalid_under_time 未満の場合は、バイトが不正であることがわかります。.valid_over_time を超える場合は、バイトが正しいことがわかります。また、.min_delta もあり、バイトが正しいかどうかを確実に知る前に次のバイトに進むことができます。
struct ProberByteLimits prober_limits[8] = {
{
// Byte 0
.invalid_under_time = 33,
.valid_over_time = 50,
.min_delta = 30
}, // ...
使用する適切な値を見つけるには、.invalid_under_time を 0 に、.valid_over_time を例えば 100 に設定し、.min_delta は 30 のままにすることをお勧めします。この場合、プローバーは最初の正しいバイトを見つけられませんが、進行状況がホスト PC の UART に出力されます。次のような表示になります:
[0x0000]=0x01? min=31 max=47 tries=63
min=31 max=48 tries=127
min=31 max=48 tries=191
min=30 max=48 tries=255
min=30 max=48 tries=319
テストした最初の文字が不正である場合、これらの min/max 値に 1 または 2 のオフセットを加えて、使用する適切なリミットを決定できます。たとえば上記の場合、.invalid_under_time を 33 に、.valid_over_time を 50 に設定できます。するとプローバーは、有効な値を見つけるまでさまざまな値を試行します。その様子は次のようになります:
...
[0x0000]=0x7d? min=31 max=39 tries= 7
[0x0000]=0x02? min=40 max=56 tries= 7
[0x0001]=0x01? min=35 max=52 tries=34
アドレス 0x0000 での最後の試行では最大応答時間が 56 µs であり、これが正しいバイトであることを意味しています。プローバーは次のバイトに進み、このプロセスを続けます。8 バイトすべてのリミットを個別に調整する必要があることに注意してください。ただし、一度調整すればメモリ全体で機能します。8 つの正しいバイトが見つかると、次のような ihex 形式で出力されます:
:0800000002002932ffffffff9f
UART 出力をテキストファイルに記録すると、^: を grep することで、ihex 形式の完全なメモリ内容を抽出できます。
ファームウェア抽出用の回路例を以下に示します。2 つのトランジスタにより、Blue Pill から CH55x への電源をオフにできます。これはソフトウェアリセットのみを使用するよりも推奨されます。ソフトウェアリセットは CH55x がブートローダーモードの場合にのみ機能し(しかもブートローダーにはタイムアウトがあり、その後アプリケーションコードが起動するため)です。CH55x への UART の抵抗は、CH55x の内部プルアップが、電源をオフにした場合でも UART からチップに電力を逆供給する可能性があるという疑いがあるため、含まれています。V33 と P3.6 の間の 10k 抵抗は、CH55x をブートローダーモードにするために必要です。Blue Pill では、PA11 が UART RX ピンに接続され、タイマー 1 を使用して応答時間を測定できるようになっています。

このツールを調整して使用することで、CH55X デバイスのファームウェアを抽出できます。抽出プロセスは高速ではありませんが、14 kb を 1 日から 2 日で抽出します。調整の仕方と、使用される周波数テーブルが実際のファームウェアのアセンブラとどの程度一致するかに少し依存します。残念ながら、ソースコードは少し乱雑です。私は CH554 デバイスからファームウェアが必要だったためこのツールを作成しましたが、それを入手した今、ツール自体に取り組む本当の理由はありません。それでも、CH55x デバイスからファームウェアを抽出する必要がある場合には役立つはずです。