【Windows】フォーマットしたUSBメモリから画像を復元した|何度もフォーマットしたUSBで戻ったのは36枚だった

スポンサーリンク
ClaudeCode
スポンサーリンク

昔の画像を保存していたUSBメモリ(1GB)を、別のデータを入れてはフォーマットして、何度も使い回していた。あとになって、その昔の画像を取り出したくなった。

Claude Codeに「フォーマットしてしまったUSBから昔のデータを取り出せるか」と頼んで調べてもらった。専用の復元ソフトは入れず、PCにもともとあったPowerShellとPythonだけで作業した。

結論から書くと、取り出せた画像は36枚(うち1枚は下半分が欠けていた)。それより昔の画像は、あとから書き込んだデータで上書きされていて戻らなかった。フォーマットしただけなら戻る見込みはあるが、そのあと何度もデータを書き込んでいると、昔のものほど戻らない。

この記事では、USBをまるごとコピーしてから画像を切り出した手順と、使ったスクリプト、結果をまとめる。

まず:USBに何も書き込まない

フォーマットしたUSBから復元したいなら、最初に守ることは1つだけだ。そのUSBにファイルを保存しない、もう一度フォーマットしない。Windowsに「ドライブを修復しますか」と聞かれても応じない。書き込むたびに、残っている古いデータが上書きされていく。

私は、USBの中身をまるごと1つのファイル(イメージファイル)にしてPCのDドライブにコピーし、以後はそのコピーだけを使って作業した。USBには読み取りしかしていない。

フォーマットの種類で、戻る見込みが変わる

Windowsのフォーマットには「クイックフォーマット」と、そうでない通常のフォーマットがある。

  • クイックフォーマット:ファイルの一覧(どこに何があるかの表)を作り直すだけ。データそのものはたいてい残っている
  • 通常のフォーマット:Windows Vista以降は、ドライブ全体に0を書き込む。こうなるとデータは戻らない

通常のフォーマットで全体に0を書き込むことは、MicrosoftのChange in the behavior of the format command in Windows Vista and later versions - Microsoft Learnに書かれている。エクスプローラーのフォーマット画面で「クイック フォーマット」にチェックが入っていれば、0は書き込まれない。

私はどちらでフォーマットしたか覚えていなかった。ただ、コピーしたUSBの中身を調べると、全体の99.5%のセクターに0以外のデータが入っていた。少なくとも最後のフォーマットでは、全体を0で消してはいなかったことになる。

USBの状態

PowerShellで見たUSBの状態はこうだった。

  • 容量:約1GB(1,039,859,712バイト)
  • ファイルシステム:FAT
  • 中身:Windowsが自動で作るSystem Volume Informationだけで、空

確認に使ったコマンドはこれだ(FはUSBのドライブ文字)。

Get-Volume -DriveLetter F | Format-List DriveLetter,FileSystem,DriveType,Size,SizeRemaining
Get-Partition -DriveLetter F | Format-List DiskNumber,Offset,Size

手順①:USBをまるごとイメージファイルにコピーする

USBを\\.\F:という形で開くと、ファイルの一覧を通さずに、USBの最初から最後までを直接読める。これをそのままファイルに書き出す。WindowsのCreateFileという機能を、読み取り専用で使っている。

次のスクリプトをPowerShellに貼り付けて実行する。最初の3行は自分の環境に合わせて書き換える。保存先は、USBとは別のドライブにする。

$drive = 'F'
$out = 'D:\usb_recovery\usb.img'
$start = 0

$end = (Get-Partition -DriveLetter $drive).Size
New-Item -ItemType Directory -Force -Path (Split-Path $out) | Out-Null
Add-Type -TypeDefinition @"
using System; using System.IO; using System.Runtime.InteropServices; using Microsoft.Win32.SafeHandles;
public static class RawCopy {
  [DllImport("kernel32.dll", SetLastError=true, CharSet=CharSet.Unicode)]
  public static extern SafeFileHandle CreateFile(string n, uint a, uint s, IntPtr sa, uint c, uint f, IntPtr t);
  public static string Dump(string dev, string outp, long start, long end) {
    var h = CreateFile(dev, 0x80000000, 3, IntPtr.Zero, 3, 0, IntPtr.Zero);
    if (h.IsInvalid) return "OPEN FAILED err=" + Marshal.GetLastWin32Error();
    long pos = start; long bad = 0;
    using (var fs = new FileStream(h, FileAccess.Read, 4096, false))
    using (var o = File.Create(outp)) {
      var buf = new byte[65536];
      while (pos < end) {
        int want = (int)Math.Min(buf.Length, end - pos);
        bool ok = false;
        for (int t = 0; t < 3 && !ok; t++) {
          try { fs.Position = pos; int n = fs.Read(buf, 0, want); if (n <= 0) return "EOF at " + pos + " bad=" + bad; o.Write(buf, 0, n); pos += n; ok = true; }
          catch (Exception e) {
            if (e.Message.Contains("does not exist")) return "DEVICE LOST at " + pos + " bad=" + bad;
            System.Threading.Thread.Sleep(200);
          }
        }
        if (!ok) {
          var sec = new byte[512];
          for (int i = 0; i < want; i += 512) {
            try { fs.Position = pos; fs.Read(sec, 0, 512); }
            catch (Exception e) {
              if (e.Message.Contains("does not exist")) return "DEVICE LOST at " + pos + " bad=" + bad;
              Array.Clear(sec, 0, 512); bad++;
            }
            o.Write(sec, 0, 512); pos += 512;
          }
        }
      }
    }
    return "OK end=" + pos + " badSectors=" + bad;
  }
}
"@
[RawCopy]::Dump("\\.\$($drive):", $out, $start, $end)

スクリプトのポイントは次のとおり。

  • CreateFileの2つ目の値0x80000000は読み取り(GENERIC_READ)。USBには書き込まない
  • 64KBずつ読み、読めない時は3回までやり直す。それでもだめなら512バイトずつ読み、読めない部分は0で埋めて先へ進む(最後のbadSectorsがその数)
  • USBが抜けた時は、そこまで読んだ位置をDEVICE LOST at 〇〇と表示して止まる

\\.\F:の形で開く方法は、CreateFileW 関数 (fileapi.h) - Microsoft Learnの「物理ディスクとボリューム」に書かれている。そこには管理者権限が必要とあるが、私のPCでは管理者として開いていないPowerShellでもUSBを読めた。OPEN FAILED err=5(アクセス拒否)と出る場合は、PowerShellを管理者として実行し直す。

途中でUSBが外れた時は、続きから読む

私の時は、約740MB(739,246,080バイト)まで読んだところでUSBがPCから見えなくなった。接触が悪かったのか、USBメモリ自体が弱っているのかは分からない。

USBを挿し直し、同じドライブ文字で認識されたのを確かめてから、続きだけを別のファイルに読んだ。上のスクリプトの最初の3行を次のように変えて、もう一度実行する($startには止まった位置の数字を入れる)。

$drive = 'F'
$out = 'D:\usb_recovery\usb_part2.img'
$start = 739246080

今度はOK end=1039859712 badSectors=0で最後まで読めた。読めなかった部分は0だった。2つのファイルは、コマンドプロンプトのcopy /bで1つにつなげる。

cd /d D:\usb_recovery
copy /b usb.img + usb_part2.img usb_full.img

copy /bはファイルをバイナリのまま連結する(copy - Microsoft Learn)。できたusb_full.imgがUSBと同じ1,039,859,712バイトになっていれば、つなげられている。

手順②:イメージファイルからJPEGを探して切り出す

フォーマットでファイルの一覧は消えているので、ファイル名からは探せない。そこで、データの中身から画像を探す。JPEGは必ずFF D8 FFというバイトで始まり、FF D9で終わる。この並びを探して、間を1枚の画像として切り出す。「ファイルカービング」と呼ばれる方法だ。

ただ、JPEGの中にはサムネイル画像が入っていることがあり、最初に見つかったFF D9がサムネイルの終わりのこともある。なので、JPEGの構造(各ブロックの長さ)を順にたどって、本体の画像データの後ろにあるFF D9を探している。

使ったのはPython(3.14)と、画像を扱うライブラリのPillowだ。Pillowが入っていなければ、先に入れる(Basic Installation - Pillow)。

python -m pip install --upgrade Pillow

次の内容をcarve_jpeg.pyとして保存する。実際に使ったものを、読みやすく整えたものだ。

import re, struct, sys, os
from PIL import Image

img_path = sys.argv[1]
out_dir = sys.argv[2]
os.makedirs(out_dir, exist_ok=True)
data = open(img_path, 'rb').read()

def find_end(s):
    # JPEGの構造をたどって、画像データの後ろの FF D9 を探す
    p = s + 2
    while p + 4 <= len(data):
        if data[p] != 0xFF:
            return None
        m = data[p + 1]
        if m == 0xFF:
            p += 1
            continue
        if m in (0xD8, 0x01) or 0xD0 <= m <= 0xD7:
            p += 2
            continue
        length = struct.unpack('>H', data[p + 2:p + 4])[0]
        if m == 0xDA:
            q = data.find(b'\xff\xd9', p + 2 + length)
            return q + 2 if q >= 0 else None
        p += 2 + length
    return None

n = 0
for hit in re.finditer(rb'\xff\xd8\xff[\xe0\xe1\xdb\xee]', data):
    s = hit.start()
    e = find_end(s)
    if not e:
        print('skip', s)
        continue
    n += 1
    f = os.path.join(out_dir, f'img_{n:03d}_off{s}.jpg')
    with open(f, 'wb') as o:
        o.write(data[s:e])
    try:
        with Image.open(f) as im:
            im.load()
            st = 'OK'
    except Exception as ex:
        st = 'NG ' + str(ex)[:50]
    print(n, s, e - s, st)
print('found:', n)

実行するコマンドはこれだ。1つ目がイメージファイル、2つ目が画像の保存先になる。

cd /d D:\usb_recovery
python carve_jpeg.py usb_full.img recovered

切り出した画像はimg_001_off193802240.jpgのような名前で保存される。offの後ろの数字は、USBの先頭から何バイト目にあったかだ。最後の列がOKなら、Pillowで画像として最後まで読み込めている。

このスクリプトはイメージファイルをまとめてメモリに読み込む。1GBのUSBなら問題ないが、数十GBのUSBやSDカードなら、後で書くPhotoRecを使う方がよい。

結果:戻ったのは36枚、昔の画像は残っていなかった

見つかったJPEGは36枚だった。PNG・GIFなど、ほかの画像の形式は見つからなかった。

項目 結果
見つかった画像 JPEG 36枚(1枚あたり約0.3〜0.8MB)
正常に見られた画像 35枚
一部が欠けた画像 1枚(上は表示されるが、下半分が灰色)
画像があった場所 USBの先頭から約190MB、約320MB、約335MBのあたりの3か所にまとまっていた
約342MBより後ろ 最後までランダムに見えるデータで埋まっていて、画像の始まり(FF D8 FF)は1つもなかった

ファイル名や撮影日は戻らない。それらはフォーマットで消えた一覧の側に書かれていたからだ。また、1枚の画像がUSBの中でとびとびに保存されていると、このやり方ではつながらない。下半分が欠けた1枚は、おそらくこのためだと思う(確かめてはいない)。

約342MBより後ろに何が入っていたのかは分からなかった。圧縮ファイルや動画などの断片の可能性がある。いずれにしても、そこには画像ではないデータが書き込まれていた。

何度もフォーマットしたUSBで、昔の画像が戻らない理由

クイックフォーマットで消えるのは一覧だけで、データ本体は残る。しかし、そのあと新しいデータを書き込めば、その場所にあった古いデータは置き換わる。上書きされたデータは、どの復元ソフトでも戻せない。

私のUSBは、別のデータを入れてはフォーマットするのを何度も繰り返していた。今回戻った36枚は、最後のほうの使い方で残ったデータだと思う。もっと昔の画像は、その後に書き込んだデータで上書きされたと考えるのが自然だ。USBの中は99.5%がデータで埋まっていて、空いたまま残っている場所もほとんどなかった。

USBメモリの中には、PCからは見えない予備の領域もある。そこに古いデータが残っている可能性はゼロではないが、読むには業者がメモリチップを取り外して読む必要がある。私は試していない。

普通は無料のPhotoRecを使えばよい

今回はClaude Codeがその場でスクリプトを書いたので、ツールを入れずに済んだ。ただ、同じことをするなら、無料の復元ソフトPhotoRecを使う方が手軽だ。公式の説明(PhotoRec - CGSecurity)には次のように書かれている。

  • ファイルシステムを無視して、データの中身から写真・動画・文書などを取り出す。フォーマットしたドライブにも使える
  • 復元したいドライブは読み取り専用で扱う。取り出したファイルは、元と同じドライブに保存しない
  • FAT・NTFS・exFATなどに対応し、Windowsで動く
  • USBそのものだけでなく、ddで作ったイメージファイルにも使える
  • オープンソース(GPL)で無料

やっている考え方は、今回のスクリプトと同じだ。PhotoRecはJPEG以外の多くの形式にも対応している。手順①で作ったusb_full.imgはそのままのコピー(ddの形式と同じ)なので、PhotoRecにかけることもできるはずだ。私はPhotoRecは試していない。

USBに無かった時に探す場所

USBから戻らなかった画像は、USB以外に残っていないか探す方が見込みがある。

  • 画像を最初に保存した場所(デジカメ、スマホ、昔使っていたPC)
  • Googleフォト、iCloud、OneDriveなどのクラウド
  • 人に送ったことがあれば、メールやLINEの履歴
  • 昔のPCやSDカード、外付けHDD。これらも同じ手順で調べられる

大事なデータは、USB1本に入れておくのではなく、別の場所にもコピーしておく。私はDドライブをNASにバックアップしている。そのやり方はRobocopyでローカルフォルダのデータをID/パスワード付きのNASにバックアップするに書いた。

まとめ

  • フォーマットしたUSBから復元したいなら、まずUSBに何も書き込まない
  • クイックフォーマットならデータ本体は残っていることが多い。Windows Vista以降の通常のフォーマットは全体に0を書き込むので戻らない
  • USBを\\.\F:で開いてまるごとイメージファイルにコピーし、そのコピーからFF D8 FF〜FF D9を探してJPEGを切り出した
  • 何度もフォーマットして使い回していた1GBのUSBで、戻ったのは36枚(1枚は下半分が欠けていた)。昔の画像は上書きされていて戻らなかった
  • 同じことはPhotoRecでもできる。ファイル名や撮影日は戻らない

コメント

タイトルとURLをコピーしました