0175 Bad CRC1 で文鎮化したThinkPad X30を、Arduinoで復活させる ―― EEPROM(24RF08)のチェックサム比較で壊れた2バイトを特定する

ヤフオクで落札したThinkPad X30のスーパーバイザーパスワードを、定番のEEPROMショート法で解除しようとして0175 Bad CRC1の文鎮に。Arduino UnoでATMEL 24RF08を吸い出し、チェックサムの整合から壊れた2バイトを特定して書き戻すまでの全手順。

始まりは、ヤフオクで落札した1台の IBM ThinkPad X30 だった。

出品ページには「スーパーバイザーパスワード(SVP)が設定されています」と最初から明記されていた。電源を入れればパスワードのプロンプトで弾かれ、OSが起動しないどころかBIOS設定画面にすら入れない ―― それを承知のうえで入札した。

理由は単純で、「EEPROMのピンをショートさせればSVPは解除できる」という手法をネットで見つけていたからだ。この世代のSVPはCMOS電池を抜いても消えず、基板上のEEPROMに保存されている。だから電池リセットは効かないが、逆にそのチップさえ相手にすれば解除できる ―― そう読んで、解除できる前提でジャンク機を買ったわけである。

そして実際にその手法を試し ―― 機体を完全な文鎮(起動不能)にしてしまった。

この記事は、そこからEEPROMを直接読み書きして救出するまでの記録だ。読み物としてだけでなく、同じ症状に出くわしたときの手順書として使えるように、解析の過程をそのまま書く。結論から言えば、壊れていたのはたった2バイトだった。

ThinkPad X30を開腹し、EEPROMからArduino Unoへ赤い配線を引き出した作業環境
作業環境。左がArduino Uno、中央がはんだ付けステーション、右が開腹したX30。EEPROMから引き出した極細線をワニ口クリップで中継している。
出品ページに掲載されていた、パスワード入力待ちの錠前アイコンが表示されたThinkPad X30の画面
落札した出品ページに載っていた写真。画面中央の錠前アイコンが、スーパーバイザーパスワードの入力待ちを示している。この状態から先へは一切進めない。

⚠️ 本記事は自分で所有する機体に対する修復・調査の記録です。他人の機器のロック解除を推奨するものではありません。またEEPROMのダンプにはシリアル番号・UUID・MACアドレスが含まれるため、公開にあたっては伏せています。

対象機 IBM ThinkPad X30
症状 0175: Bad CRC1, stop POST task → 起動不能
原因 SDA/SCLショート法の誤用によるEEPROM破損
主な道具 Arduino Uno(専用プログラマ不要)
壊れた量 2バイト
結果 全エラー解消・完動。元のパスワードまで判明

前提:なぜCMOSクリアでBIOSパスワードが消えないのか

X30世代のスーパーバイザーパスワードは、CMOSバックアップ電池を抜いても消えない。マザーボード上の ATMEL 24RF08 ―― I2C接続のEEPROM に保存されているからだ。電池で保持しているCMOS RAMとは別の、電源が落ちても中身が残る不揮発メモリである。

だからCMOSクリアは無力で、このチップへ直接アクセスするしかない。

ThinkPad X30の基板。ATMEL 24RF08CN(U37)と、黄色いPanasonic製リチウムCMOS電池が並んで写っている
今回の主役。U37 の位置にある8ピンSOIC、ATMEL 24RF08CN(中央左)。そして右下の黄色い包みが CMOSバックアップ電池(Panasonic製リチウム電池)だ。この電池を抜いてもパスワードは消えない ―― 保存先が電池ではなく左のチップだから、というのがこの1枚に写っている。手前の 32.768k はRTC用のクリスタル。

そして、そのチップを相手にする定番の攻略法が「起動中に SDA/SCL ピンを一瞬ショートさせ、パスワード照合の読み出しを化けさせて検証を素通りさせる」というものだ。私がこの機体を落札した決め手も、この手法の存在だった。さっそく試した ―― これが地獄の入口だった。

事故:ショート法で文鎮化する(0175 Bad CRC1)

I2Cは SDA(データ)と SCL(クロック)が独立して上下することで通信が成立する。ところが「ショートし続ければいい」と誤解し、両ピンを繋ぎっぱなしにしてしまった。両線が同電位に固定されればバスは死に、BIOSはEEPROMの応答を待って固まる。何度かリトライした末に――

0175: Bad CRC1, stop POST task

POSTが完全停止した。OSはおろかBIOS設定にもフロッピー起動にも進めない。ショートのタイミング次第でEEPROMが「読み出し」ではなく「書き込み」と誤動作し、中身の一部が破壊されてしまったのだ。完全な文鎮化である。

ハマりどころ その1 ―― ショート法は「一瞬だけ触れて即離す」技。繋ぎっぱなしはバスを殺すうえ、EEPROMそのものを壊す。そして着手前にダンプを取っておけば、この後の苦労はまるごとゼロだった。

方針転換:Arduino UnoでEEPROMを吸い出す

壊れたのがEEPROMのデータなら、中身を直接読んで何が壊れたかを特定し、直せばいい。24RF08はI2Cなので、Arduino Uno一枚で読み書きできる。チップの3本のピンに極細線をはんだ付けし、Arduinoへ繋ぐ。

チップ側ピン 役割 Arduino Uno
5 SDA(データ) A4
6 SCL(クロック) A5
4 GND GND

VCCは繋がない。チップの電源はThinkPad本体側から供給させる。

Arduino UnoのA4・A5ピンに橙と黄の線を接続したところ
Arduino側。ANALOG INA4(SDA)と A5(SCL)へ差し込む。Unoでは I2C がこの2本に固定されている。
24RF08CNから2本の赤い線が引き出されている基板の全体像
チップ側。0.5mmピッチの足に極細線を直付けし、基板の外まで引き出す。はんだは少なく・短時間で(盛ると隣の足と繋がる)、付けたらテスターで隣ピンとの導通を必ず確認。線は動くと足をもぎ取るので、引き出したらテープで固定してワニ口クリップで中継する。この2本(SDA・SCL)とGNDの計3本だけで、EEPROMの全内容が読める。

ハマりどころ その2:チップに電源が来ていない

最初は全アドレスがタイムアウトして焦った。原因は単純で、チップに電源が来ていなかっただけ。24RF08は本体側から給電されるので、ThinkPadを「0175で固まった状態」のまま維持する必要がある。テスターで実測すると、電源OFF時は 0.2V、起動後は 3.3V。これで解決した。

まずI2Cアドレスをスキャンする

#include <Wire.h>
void setup(){
  Wire.begin();
  Wire.setWireTimeout(25000, true);   // 応答が無いとき固まらず timeout させる
  Serial.begin(9600); while(!Serial);
  for(uint8_t a=0x50; a<=0x57; a++){
    Wire.beginTransmission(a);
    if(Wire.endTransmission()==0){
      Serial.print("Found 0x"); Serial.println(a,HEX);
    }
  }
}
void loop(){}

応答したのは 0x54 0x55 0x56 0x57 の4アドレス。24RF08は256バイト×4ブロックが別々のI2Cアドレスに割り当たる構造だ。オフセットを指定して各ブロックを読み出すと――データは全消去されておらず、シリアル番号やMACアドレスらしき情報も生きていた。希望が見えた。

解析:壊れたのは、たった2バイトだった

ここが今回の山場。ダンプを眺めていると、ブロック 0x57よく似た8バイトのレコードが2つ並んでいるのに気づく。

0x57 offset 56:  2E 16 1F  14 18  32 00 C1
0x57 offset 64:  2E 16 1F  AE 42  32 00 C1
                           ~~~~~ ここだけ違う

この世代のThinkPadのパスワードは「7バイトのデータ + 1バイトのチェックサム(単純な8ビット合計)」形式で、同じ値を2箇所に持っている。そこで各レコードの合計を計算して、格納されたチェックサムと突き合わせた。

スロット データ(7バイト) 格納CS 正しい合計 判定
A(offset 56) 2E 16 1F 14 18 32 00 C1 C1 ✅ 一致
B(offset 64) 2E 16 1F AE 42 32 00 C1 85 ❌ 不一致

決定的だったのは、スロットBに格納されたチェックサム C1 が、スロットAのデータの合計と一致すること。つまり本来AとBは同じ値で、ショートによってBの4〜5バイト目だけが 14 18 → AE 42 に化けたのだ。データだけが書き換わりチェックサムは更新されずに残ったから、0175 Bad CRC1 が出ていた。犯人はこの2バイト。

手計算だけでは不安なので、Pythonで機械的に検証した。

def s8(b): return sum(b) & 0xFF          # 8ビット合計

recA = bytes.fromhex("2E161F14183200C1")
recB = bytes.fromhex("2E161FAE423200C1")
for name, r in [("A", recA), ("B", recB)]:
    calc, stored = s8(r[:7]), r[7]
    print(name, f"calc={calc:02X} stored={stored:02X}",
          "OK" if calc == stored else "BAD")
# A calc=C1 stored=C1 OK
# B calc=85 stored=C1 BAD  <- 破損確定

ついでに全ブロックを走査し、他に整合の取れないレコードが無いかも確認した。破損はこの1レコードだけ。元の値へ戻せばEEPROMは破壊前の状態へ完全に復元される――それはすなわち、上位のCRCも自動的に整合するということだ。

発見:ついでに元のパスワードまで判明する

修復対象のバイト列 2E 16 1F 14 18 32 00 は、実はパスワードそのものだ。ThinkPadはパスワードをキーボードのスキャンコードで保存している。デコードしてみると――

2E=C  16=U  1F=S  14=T  18=O  32=M  00=(終端)
                                          ↓
                     PASSWORD  =  "CUSTOM"

ランダムなバイト列ではなく実在する単語になった。デコードの解釈が正しいことの、これ以上ない裏付けだ。「解除」を目指していたのに、元のパスワードまで復元してしまった。解析とはこういう瞬間が楽しい。

修復:安全弁を付けて2バイトを書き戻す

あとは 0x57 のオフセット67・68を、化けた AE 42 から元の 14 18 に書き戻すだけ。事故を繰り返さないよう、「対象が想定どおり壊れた値であること」を確認してから書く安全弁を入れた。

// 書き込み前に現在値を確認 → AE,42 でなければ中止する
if (readByte(0x57,67)!=0xAE || readByte(0x57,68)!=0x42){
  Serial.println("想定外の値。中止。"); return;
}
writeByte(0x57, 67, 0x14);   // AE -> 14
writeByte(0x57, 68, 0x18);   // 42 -> 18
// 書き込み後は必ず読み戻してチェックサムを検証する
// writeByte: beginTransmission → write(offset) → write(val) → endTransmission、
//            直後に 15ms 待つ(EEPROMの書き込みサイクル待ち)

実行結果――

=== AFTER ===
  2E 16 1F 14 18 32 00 C1
checksum: computed 0xC1  stored 0xC1  MATCH - repaired!
EEPROMの読み書きに使ったArduino Unoのクローズアップ
読み書きに使ったのは普通の Arduino Uno 一枚。専用のEEPROMプログラマは要らなかった。

復活:0175が消え、残りのエラーも自然に片づいた

電源を入れ直すと 0175 は消え、BIOS画面に入れるようになった。Securityを見るとスーパーバイザーパスワードは Disabled。当初の目的も達成である。

ただし今度は別のエラーが顔を出した。

0271: Check date and time settings
0251: System CMOS checksum bad - Default configuration used
0188: Invalid RFID Serialization Information Area or Bad CRC2
ThinkPad X30の画面に0271・0251・0188の3つのエラーが表示されている
実際の画面。0175が消えて、ここまで進めるようになったこと自体が前進だ。文鎮のときはこの画面すら出なかった。

02710251 はCMOSがリセットされただけの無害なもの。問題は 0188 で、これは別の領域(シリアライゼーション情報)のCRC2を指す。ショートはCRC1だけでなくCRC2領域も巻き込んでいて、CRC1を直してPOSTが先へ進めるようになったことで、それまで隠れていたエラーが表面化した形だ。

同じ手順でCRC2も解析するつもりで身構えたが、結末はあっけなかった。BIOSで日付と時刻を設定して保存(F10)したら、3つとも消えた。設定保存の過程でBIOS自身がCMOSとシリアライゼーション領域のCRC2を書き直したものと思われる。再起動しても再発しない。手動でのCRC2修復は不要だった。

再現手順:もう一度やるための8ステップ

同じ症状(0175 Bad CRC1 でのブリック、あるいは未知のSVP)に出くわしたとき、この順番でやれば最短で済む。

  1. 何をおいても、先にダンプを取る。壊す前の正常な中身が最強の保険になる。
  2. チップの SDA / SCL / GND に極細線を直付けする。隣ピンとのブリッジをテスターで確認。
  3. ThinkPadを起動した状態で、SDA→A4 / SCL→A5 / GND→GND でArduinoへ接続。VCCは繋がず本体側給電を使う(実測で 3.3V を確認)。
  4. I2Cアドレスをスキャンする。24RF08は 0x54–0x57 に応答。各ブロック256バイトをオフセット指定で読む。
  5. ダンプ内の「7バイト+1バイト合計チェックサム」レコードを探し、合計が合わないものが破損箇所。
  6. 同じ値を持つ健全なコピーがあれば、そこから正しいバイトを復元して書き戻す。書き込み前に現在値を確認する安全弁を必ず入れる。
  7. パスワード文字列が要るなら、バイト列をキーボードのスキャンコードとしてデコードする。
  8. 復活後に 0271 / 0251 / 0188 が出たら、まずBIOSで日時を設定して保存。多くはこれで消える。

まとめ:教訓

  • ショート法は最終手段、しかも一瞬だけ。繋ぎっぱなしはI2Cバスを殺すだけでなく、EEPROMの破壊行為に等しい。
  • 着手前のダンプが全て。正常な中身さえ手元にあれば、今回の救出は数十秒で終わっていた。
  • データは意外と生きている。ダンプさえ取れれば、チェックサムの整合を追うことで多くは復元できる。壊れるのは往々にして、ほんの数バイトだ。
  • ThinkPadのパスワードはスキャンコードで保存されている。読めれば元の文字列まで戻せる。

破損2バイト、復元完了。0175 / 0251 / 0271 / 0188 いずれも解消。IBM ThinkPad X30 · ATMEL 24RF08 · Arduino Uno。