CUBE SUGAR CONTAINER

技術系のこと書きます。

パスワード付き ZIP ファイルを hashcat + JtR + GPU で総当たりしてみる

少し前に以下のツイートが話題になっていた。 hashcat というツールと GTX 2080 Ti を 4 台積んだマシンで ZIP ファイルのパスワードを探索するというもの。 このツイートでは 15 桁までわずか 15 時間 (!) で探索できたとしている。 その探索速度はなんと 22.7 ZH/s (Z = ゼッタ = Giga<Tera<Peta<Exa<Zetta) に及ぶらしい。

ただし、これは PKWARE 社の暗号化方式に存在する脆弱性を利用して計算量を削減した場合の結果らしい。 一般的に用いられている形式については、ここまで高速には探索できないとのこと。 今回のエントリは、上記を見て一般的なものはどれくらいのスピードで探索できるのか気になって実際に試してみた。

使った環境は次の通り。 GPU には Tesla V100 を 1 台使っている。

$ cat /etc/lsb-release 
DISTRIB_ID=Ubuntu
DISTRIB_RELEASE=18.04
DISTRIB_CODENAME=bionic
DISTRIB_DESCRIPTION="Ubuntu 18.04.2 LTS"
$ uname -r
4.15.0-1033-gcp
$ nvidia-smi
Sat Jun  8 05:14:29 2019       
+-----------------------------------------------------------------------------+
| NVIDIA-SMI 418.67       Driver Version: 418.67       CUDA Version: 10.1     |
|-------------------------------+----------------------+----------------------+
| GPU  Name        Persistence-M| Bus-Id        Disp.A | Volatile Uncorr. ECC |
| Fan  Temp  Perf  Pwr:Usage/Cap|         Memory-Usage | GPU-Util  Compute M. |
|===============================+======================+======================|
|   0  Tesla V100-SXM2...  On   | 00000000:00:04.0 Off |                    0 |
| N/A   36C    P0    37W / 300W |      0MiB / 16130MiB |      0%      Default |
+-------------------------------+----------------------+----------------------+
                                                                               
+-----------------------------------------------------------------------------+
| Processes:                                                       GPU Memory |
|  GPU       PID   Type   Process name                             Usage      |
|=============================================================================|
|  No running processes found                                                 |
+-----------------------------------------------------------------------------+

下準備

まずは必要となるパッケージを最初に一通りインストールしておく。

$ sudo apt-get update
$ sudo apt-get -y install clinfo wget git zip p7zip-full build-essential libssl-dev zlib1g-dev 

OpenCL (NVIDIA CUDA Runtime) をインストールする

hashcat の動作には OpenCL のランタイムが必要になる。 そこで CUDA のランタイムをインストールする。

まず、以下の Web サイトから CUDA のインストール用リポジトリの入った deb ファイルを取得する。

developer.nvidia.com

wget などを使ってダウンロードしてくれば良い。

$ wget http://developer.download.nvidia.com/compute/cuda/repos/ubuntu1804/x86_64/cuda-repo-ubuntu1804_10.1.168-1_amd64.deb

リポジトリを登録して CUDA をインストールする。

$ sudo dpkg -i cuda-repo-ubuntu1804_10.1.168-1_amd64.deb
$ sudo apt-key adv --fetch-keys https://developer.download.nvidia.com/compute/cuda/repos/ubuntu1804/x86_64/7fa2af80.pub
$ sudo apt-get update
$ sudo apt-get -y install cuda

インストールが終わったら、一旦マシンを再起動しておく。

$ sudo shutdown -r now

すると、次のように NVIDIA のグラフィックドライバと CUDA がインストールされた。

$ nvidia-smi
Sat Jun  8 05:14:29 2019       
+-----------------------------------------------------------------------------+
| NVIDIA-SMI 418.67       Driver Version: 418.67       CUDA Version: 10.1     |
|-------------------------------+----------------------+----------------------+
| GPU  Name        Persistence-M| Bus-Id        Disp.A | Volatile Uncorr. ECC |
| Fan  Temp  Perf  Pwr:Usage/Cap|         Memory-Usage | GPU-Util  Compute M. |
|===============================+======================+======================|
|   0  Tesla V100-SXM2...  On   | 00000000:00:04.0 Off |                    0 |
| N/A   36C    P0    37W / 300W |      0MiB / 16130MiB |      0%      Default |
+-------------------------------+----------------------+----------------------+
                                                                               
+-----------------------------------------------------------------------------+
| Processes:                                                       GPU Memory |
|  GPU       PID   Type   Process name                             Usage      |
|=============================================================================|
|  No running processes found                                                 |
+-----------------------------------------------------------------------------+

次のように OpenCL のランタイムが認識されている。

$ clinfo | grep -A 3 "Number of platforms"
Number of platforms                               1
  Platform Name                                   NVIDIA CUDA
  Platform Vendor                                 NVIDIA Corporation
  Platform Version                                OpenCL 1.2 CUDA 10.1.152

hashcat をインストールする

hashcat は各種ハッシュ値を探索するためのツール。

現時点でリリース済みのバージョン (v5.1.0) から HEAD はだいぶ差分があるようなので Git のリポジトリからインストールする。

$ git clone https://github.com/hashcat/hashcat.git
$ cd hashcat
$ make && sudo make install

hashcat -I コマンドで以下のように GPU が認識されていれば上手くいっている。

$ hashcat -I
hashcat (v5.1.0-1138-g581839d4) starting...

CUDA Info:
==========

CUDA.Version.: 10.1

Backend Device ID #1 (Alias: #2)
  Name...........: Tesla V100-SXM2-16GB
  Processor(s)...: 80
  Clock..........: 1530
  Memory.........: 16130 MB

OpenCL Info:
============

OpenCL Platform ID #1
  Vendor..: NVIDIA Corporation
  Name....: NVIDIA CUDA
  Version.: OpenCL 1.2 CUDA 10.1.152

  Backend Device ID #2 (Alias: #1)
    Type...........: GPU
    Vendor.ID......: 32
    Vendor.........: NVIDIA Corporation
    Name...........: Tesla V100-SXM2-16GB
    Version........: OpenCL 1.2 CUDA
    Processor(s)...: 80
    Clock..........: 1530
    Memory.........: 4032/16130 MB allocatable
    OpenCL.Version.: OpenCL C 1.2 
    Driver.Version.: 418.67

JtR (JohnTheRipper) をインストールする

hashcat はハッシュの探索に特化したツールなので、肝心のハッシュ値は別のツールを使って調べる必要がある。 ZIP ファイルに関しては JohnTheRipper というツールを使うのが一般的なようだ。

こちらも Git のリポジトリからインストールしておく。

$ git clone https://github.com/magnumripper/JohnTheRipper.git
$ cd JohnTheRipper/src
$ ./configure
$ make -s clean && make -sj$(grep processor /proc/cpuinfo | wc -l)
$ sudo make install
$ sudo ln -s $(pwd)/../run/zip2john /usr/local/bin/

以下のように zip2john コマンドが使えるようになっていれば良い。

$ zip2john
Usage: zip2john [options] [zip file(s)]
Options for 'old' PKZIP encrypted files only:
 -a <filename>   This is a 'known' ASCII file. This can be faster, IF all
    files are larger, and you KNOW that at least one of them starts out as
    'pure' ASCII data.
 -o <filename>   Only use this file from the .zip file.
 -c This will create a 'checksum only' hash.  If there are many encrypted
    files in the .zip file, then this may be an option, and there will be
    enough data that false possitives will not be seen.  If the .zip is 2
    byte checksums, and there are 3 or more of them, then we have 48 bits
    knowledge, which 'may' be enough to crack the password, without having
    to force the user to have the .zip file present.
 -m Use "file magic" as known-plain if applicable. This can be faster but
    not 100% safe in all situations.
 -2 Force 2 byte checksum computation.

NOTE: By default it is assumed that all files in each archive have the same
password. If that's not the case, the produced hash may be uncrackable.
To avoid this, use -o option to pick a file at a time.

パスワード付き ZIP ファイルを用意する

以下のようにしてパスワードが password の ZIP ファイルを作る。 辞書攻撃であれば一瞬で解ける脆弱なパスワードだけど、今回は総当たりなのでサンプルとしては構わないかな。

$ echo "Hello, World" > greet.txt
$ zip -e --password=password greet.txt.zip greet.txt
  adding: greet.txt (stored 0%)
$ file greet.txt.zip
greet.txt.zip: Zip archive data, at least v1.0 to extract

ハッシュ値を取得する

zip2john コマンドを使って次のようにハッシュ値を記録したファイルを作成する。

$ zip2john greet.txt.zip | cut -d ":" -f 2 > greet.txt.zip.hash
ver 1.0 efh 5455 efh 7875 greet.txt.zip/greet.txt PKZIP Encr: 2b chk, TS_chk, cmplen=25, decmplen=13, crc=40F63A90

取得できたハッシュ値は以下。

$ cat greet.txt.zip.hash
$pkzip2$1*2*2*0*19*d*40f63a90*0*43*0*19*40f6*2adb*e6c233aef1ba5a982f025c9bcdcdc86c4fa27c949c7871dc01*$/pkzip2$

hashcat でハッシュ値を探索する

hashcat は計算対象のハッシュ値を自動では認識してくれない。 なので、先ほどのハッシュ値の内容と以下のページの内容を見比べて適切なハッシュ形式を探す。 今回であれば 172xx のいずれかだろう、となる。

hashcat.net

ここまでできたら、あとは hashcat コマンドを使って探索するだけ。 ハッシュ形式は -m オプションで指定する。 -a オプションはアタックモードで、総当たり (Brute-force) なら 3 を指定する。 -w オプションはワークロードプロファイルで、全力で探索するときは 4 を指定する。

$ hashcat -m 17210 -a 3 -w 4 \
    --session helloworld \
    -o result.txt \
    greet.txt.zip.hash

実行すると、次のように探索が始まって状況が表示される。 探索速度は 28043.6 MH/s なので、約 28 GH/s となる。

$ hashcat -m 17210 -a 3 -w 4 \
    --session helloworld \
    -o result.txt \
    greet.txt.zip.hash
...

Session..........: helloworld
Status...........: Running
Hash.Name........: PKZIP (Uncompressed)
Hash.Target......: $pkzip2$1*2*2*0*19*d*40f63a90*0*43*0*19*40f6*2adb*e...kzip2$
Time.Started.....: Sat Jun  8 05:24:42 2019 (15 secs)
Time.Estimated...: Sat Jun  8 05:27:58 2019 (3 mins, 1 sec)
Guess.Mask.......: ?1?2?2?2?2?2?2?3 [8]
Guess.Charset....: -1 ?l?d?u, -2 ?l?d, -3 ?l?d*!$@_, -4 Undefined 
Guess.Queue......: 8/15 (53.33%)
Speed.#1.........: 28043.6 MH/s (93.32ms) @ Accel:32 Loops:1024 Thr:1024 Vec:1
Recovered........: 0/1 (0.00%) Digests, 0/1 (0.00%) Salts
Progress.........: 437382021120/5533380698112 (7.90%)
Rejected.........: 0/437382021120 (0.00%)
Restore.Point....: 5242880/68864256 (7.61%)
Restore.Sub.#1...: Salt:0 Amplifier:6144-7168 Iteration:0-1024
Candidates.#1....: 0uc3aen1 -> Ciskoe86
Hardware.Mon.#1..: Temp: 54c Util: 99% Core:1530MHz Mem: 877MHz Bus:16

...

デフォルトではアルファベットと数字だけが探索対象なので、8 桁でも数分もあれば見つかる。 結果は -o オプションでファイルに書き出しているので、表示させてみよう。

$ cat result.txt 
$pkzip2$1*2*2*0*19*d*40f63a90*0*43*0*19*40f6*2adb*e6c233aef1ba5a982f025c9bcdcdc86c4fa27c949c7871dc01*$/pkzip2$:password

ちなみに探索を中止してもセッションに名前をつけてあれば、以下のように再開できる。

$ hashcat --session helloworld --restore

セッションの情報は以下のように保存されている。

$ ls ~/.hashcat/sessions/
hashcat.log  hashcat.restore  hellosymbol.log  hellosymbol.restore  helloworld.log  helloworld.restore

探索する文字種別に記号を含めてみる

探索が必要な空間は、文字種別と桁数で指数関数的に増える。 そのため探索する文字種別に記号を含めると、単位時間あたりに探せる桁数はぐっと落ちることになる。 そこで、次はパスワードに記号を含めて試してみよう。

$ zip -e --password='pswd_+' greet.txt.zip greet.txt
updating: greet.txt (stored 0%)

ハッシュを取り直す。

$ zip2john greet.txt.zip | cut -d ":" -f 2 > greet.txt.zip.hash
ver 1.0 efh 5455 efh 7875 greet.txt.zip/greet.txt PKZIP Encr: 2b chk, TS_chk, cmplen=25, decmplen=13, crc=40F63A90
$ cat greet.txt.zip.hash 
$pkzip2$1*2*2*0*19*d*40f63a90*0*43*0*19*40f6*2adb*7f04312cbe0aab6e4a19fad645aecda94ea6fee0c2b3710fb0*$/pkzip2$

使用する文字種別は -1 ~ -4 オプションで登録できる。 それをマスク (以下の ?1?1... となっている部分) として利用する。 以下では記号を含む一通りの文字種別で 10 桁までインクリメンタルに探索する設定となる。

$ hashcat -m 17210 -a 3 -w 4 \
    --session hellosymbol \
    -o result.txt \
    -1 ?a \
    --increment \
    greet.txt.zip.hash \
    ?1?1?1?1?1?1?1?1?1?1

ちなみに、今回のケースではわずか 7 桁でも探索に 40 分ほどかかることが分かった。

...

Session..........: hellosymbol                     
Status...........: Exhausted
Hash.Name........: PKZIP (Uncompressed)
Hash.Target......: $pkzip2$1*2*2*0*19*d*40f63a90*0*43*0*19*40f6*2adb*7...kzip2$
Time.Started.....: Sat Jun  8 05:41:19 2019 (28 secs)
Time.Estimated...: Sat Jun  8 05:41:47 2019 (0 secs)
Guess.Mask.......: ?1?1?1?1?1?1 [6]
Guess.Charset....: -1 ?a, -2 Undefined, -3 Undefined, -4 Undefined 
Guess.Queue......: 6/10 (60.00%)
Speed.#1.........: 26579.9 MH/s (86.36ms) @ Accel:32 Loops:1024 Thr:1024 Vec:1
Recovered........: 0/1 (0.00%) Digests, 0/1 (0.00%) Salts
Progress.........: 735091890625/735091890625 (100.00%)
Rejected.........: 0/735091890625 (0.00%)
Restore.Point....: 81450625/81450625 (100.00%)
Restore.Sub.#1...: Salt:0 Amplifier:8192-9025 Iteration:0-1024
Candidates.#1....: 3<5x$~ ->  ~ ~}z
Hardware.Mon.#1..: Temp: 53c Util: 99% Core:1530MHz Mem: 877MHz Bus:16

とはいえ、実際に使ったパスワードの長さは 6 桁なので、数十秒あれば見つかる。

$ cat result.txt 
$pkzip2$1*2*2*0*19*d*40f63a90*0*43*0*19*40f6*2adb*e6c233aef1ba5a982f025c9bcdcdc86c4fa27c949c7871dc01*$/pkzip2$:password
$pkzip2$1*2*2*0*19*d*40f63a90*0*43*0*19*40f6*2adb*7f04312cbe0aab6e4a19fad645aecda94ea6fee0c2b3710fb0*$/pkzip2$:pswd_+

より強固な暗号化方式を用いる

例えば、もっと強固な方式を用いると探索速度はどのように変化するだろうか? 例として AES256 を使って暗号化してみることにした。

$ 7za a -tzip -ppassword -mem=AES256 greet.txt.zip greet.txt

ハッシュを取り直す。

$ zip2john greet.txt.zip | cut -d ":" -f 2 > greet.txt.zip.hash
$ cat greet.txt.zip.hash 
$zip2$*0*3*0*cf86e2828c42995d3a631f9dfe159ce8*2fa8*d*0a5619e079e4f6389e7d8da029*55fd0328aae560645e58*$/zip2$

実行してみよう。

$ hashcat -m 13600 -a 3 -w 4 \
    --session zipaes \
    -o result.txt \
    -1 ?a \
    --increment \
    greet.txt.zip.hash \
    ?1?1?1?1?1?1?1?1?1?1

すると、このパターンでは探索速度がわずか 2194 kH/s (2 MH/s) しか出ていない。 先ほどと比べると、およそ 10,000 分の 1 となった。

...

Session..........: zipaes
Status...........: Running
Hash.Name........: WinZip
Hash.Target......: $zip2$*0*3*0*cf86e2828c42995d3a631f9dfe159ce8*2fa8*.../zip2$
Time.Started.....: Sat Jun  8 05:45:57 2019 (11 secs)
Time.Estimated...: Sat Jun  8 05:46:34 2019 (26 secs)
Guess.Mask.......: ?1?1?1?1 [4]
Guess.Charset....: -1 ?a, -2 Undefined, -3 Undefined, -4 Undefined 
Guess.Queue......: 4/10 (40.00%)
Speed.#1.........:  2194.1 kH/s (74.89ms) @ Accel:32 Loops:249 Thr:1024 Vec:1
Recovered........: 0/1 (0.00%) Digests, 0/1 (0.00%) Salts
Progress.........: 23149125/81450625 (28.42%)
Rejected.........: 0/23149125 (0.00%)
Restore.Point....: 0/857375 (0.00%)
Restore.Sub.#1...: Salt:0 Amplifier:27-28 Iteration:249-498
Candidates.#1....: oari -> o ~}
Hardware.Mon.#1..: Temp: 50c Util: 99% Core:1530MHz Mem: 877MHz Bus:16

まとめ

  • パスワード付きの ZIP ファイルを hashcat + JtR + GPU で総当たりしてみた
    • さほど恐ろしさを覚える探索速度は出なかった
    • 仮に並列度を上げても総当たりなら定数倍の改善にとどまるはず
    • また、より強固な暗号化方式を使うとさらに総当たりが難しくなる
    • ただしパスワードは十分に長く記号を含んだものを使うことが前提となる
  • 今回使った環境での長さに関する相場感
    • 数字のみ: 12 桁の探索に 1 分
    • 数字 + アルファベット: 8 桁の探索に 3 分
    • 数字 + アルファベット + 記号: 7 桁の探索に 40 分

いじょう。