john (2)
Table of Contents
black-box check
前回は、パスワード・クラッカーの一種であるjohnで脆弱なパスワードの検出を 始めてみた。どうも、近代的なパスワードには、対応出来ていないみたい(未来は 発明できない)。パスワードの例が出てたんだけど、1ユーザー分しかない。 そこで、変形をかけて、増量してみた。
余談になるけど、流行のAI。世界中のデータを取り尽してしまって、もう無いみたい。 そこで、図形とかだと、斜めにするとか、歪ませるとか、鏡像にするとかして増量 してるらしい。パンダなら、白黒を黒白にするんだな。新種のパンダが発見され ました。名前は、ダンパと付けました。だって色合いが逆ですから。。こういう ユーモアは、まだAIには無理かな? 深刻なのは、テキストデータだそうです。 AIが出力したテキストを材料に追加すると、どんどんと性能が劣化していくそう です。
john用に増量したデータです。ユーザー名を全て別にしました。john,barは、パスワード の最初と最後を別の文字に変更しています。
eq$ cat G user:AZl.zWwxIh15Q john:BZl.zWwxIh15Q who:AZl.zWwxIh15Q bar:AZl.zWwxIh15P
これでクラッキング開始。
eq$ john G Created directory: /home/sakae/.john Loaded 3 password hashes with 2 different salts (descrypt, traditional crypt(3) [DES 128/128 SSE2-16]) Press 'q' or Ctrl-C to abort, almost any other key for status example (who) example (user) Warning: MaxLen = 13 is too large for the current hash type, reduced to 8 2g 0:00:00:50 3/3 0.03928g/s 3622Kp/s 3623Kc/s 3624KC/s cjb3h1..cjbcue Use the "--show" option to display all of the cracked passwords reliably Session aborted
2ユーザー分は直にクラック成功。有効なパスワードは、どうやら3個だけだったようです。 もう1ユーザーのクラックの為に、ひたすら計算してるみたい。途中で中止しました。
log
.johnにログが残っていました。
eq$ tree .john .john |-- john.log |-- john.pot `-- john.rec
このうちの、john.logが面白いです。
0:00:00:00 Starting a new session 0:00:00:00 Loaded a total of 3 password hashes with 2 different salts 0:00:00:00 - Hash type: descrypt, traditional crypt(3) (lengths up to 8, longer passwords split) 0:00:00:00 - Algorithm: DES 128/128 SSE2-16 0:00:00:00 - Candidate passwords will be buffered and tried in chunks of 128 0:00:00:00 - Configured to use otherwise idle processor cycles only 0:00:00:00 Proceeding with "single crack" mode 0:00:00:00 - 1081 preprocessed word mangling rules 0:00:00:00 - Allocated 2 buffers of 128 candidate passwords each 0:00:00:00 - Rule #1: ':' accepted as '' 0:00:00:00 - Rule #2: '-s x**' rejected 0:00:00:00 - Rule #3: '-c (?a c Q' accepted as '(?acQ' : 0:00:00:00 - Rule #1081: 'l Az"1900" <+' accepted as 'lAz"1900"<+' 0:00:00:00 - Processing the remaining buffered candidate passwords, if any 0:00:00:00 Proceeding with wordlist mode 0:00:00:00 - Wordlist file: /mnt/opt/share/john/password.lst 0:00:00:00 - 57 preprocessed word mangling rules 0:00:00:00 - Rule #1: ':' accepted as '' 0:00:00:00 + Cracked who 0:00:00:00 + Cracked user 0:00:00:00 - Rule #2: '-c >3 !?X l Q' accepted as '>3!?XlQ' 0:00:00:00 - Rule #3: '-c (?a >2 !?X c Q' accepted as '(?a>2!?XcQ' : 0:00:00:50 - Trying length 5, fixed @5, character count 27 0:00:00:50 - Switching to length 6 0:00:00:50 - Expanding tables for length 6 to character count 26 0:00:00:50 - Trying length 6, fixed @5, character count 21 0:00:00:50 Session aborted
パスワードの形式を最初に検出して、それに見合う戦略を立ててから、探索を してるっぽいです。
gdb break
パスワードの形式をどうやって推定してるんだろう? 新式の奴はさっぱり対応して くれなかったので、このあたりが第一関門になりそう。特徴的なエラー語句に、FAQ なんて文字が含まれていたんで、家捜し。
eq$ cd john-1.8.0/src/ eq$ grep FAQ *.[chS] john.c: printf("No password hashes %s (see FAQ)\n",
これを出しているのは、 john_load
って関数内。
(gdb) b john_load Function "john_load" not defined. Make breakpoint pending on future shared library load? (y or [n]) y : Created directory: /home/sakae/.john Loaded 3 password hashes with 2 different salts (descrypt, traditional crypt(3) [DES 128/128 SSE2-16]) Press 'q' or Ctrl-C to abort, almost any other key for status example (who) example (user)
そんな関数は無いって言われた。実行すると、スルーされたよ。そんじゃ、別の手で、 パスワード数を数える変数が変化したらブレークしてねって指示。
このwatchって機能は、ハード屋さんの御用達。昔ちょっと触ったミニコンに、この 機能が付いていた。コンソールのSWに監視したいアドレスをセット。ロータリーSWで、 モードを書き込み検出にしとく。そうするとメモリー・アドレスがコンソールSWにセットしたアドレスと 一致して、書き込みが発生した時に、モニターモードになる。もう、これだけで回路 を設計できるよ。実際は、この条件だけではおぼつかないんで、命令の実行時って 条件を加えるといいだろう。
amd64なCPU自身にデバッグの為のハード機構が実装されてるはずなんだけど、現gdbでは この機構を利用していない。シュミレーションで実現してるんで、実行がメチャクチャ 遅いんで注意。
eq$ gdb john GNU gdb 6.3 Copyright 2004 Free Software Foundation, Inc. : (gdb) watch database.password_count Watchpoint 1: database.password_count (gdb) r G Starting program: /mnt/opt/bin/john G Watchpoint 1: database.password_count Watchpoint 1: database.password_count Error while reading shared library symbols: Dwarf Error: wrong version in compilation unit header (is 4, should be 2) [in module /ram/usr/libexec/ld.so] Created directory: /home/sakae/.john
先程は気が付かなかったけど、エラーが発生してる。 コンパイラーの出力するデバッグ情報と、gdbが想定 してる情報に齟齬が有るって。デフォで提供されてるgdbは、歴史の彼方の物だから なあ。20年も経てば、gcc -> clang と変わるしねぇ。ここは腹を括って、パッケージ版 のgdbを入れるか。
その前に、一つ解決しておかねばならない事が有る。定義されてる関数がgdbから、 見えない問題。
eq$ nm /mnt/opt/bin/john | grep john_ 0007cf00 B john_child_count 0007cf08 B john_child_pids 0007d2e0 b john_home_length 0007d2d8 b john_home_path 0007d0d0 b john_loaded_counts.s_loaded_counts 00049e20 D john_main_process 0002fe00 t john_register_all
登録されていないじゃん。これじゃgdbもお手上げだわな。
static void john_load(void) { struct list_entry *current; umask(077);
問題の関数定義は、こうなってました。staticなんて言う甘言に騙されてはいけません。 裏の顔を持っているんです。コンパイル単位(普通はファイル毎)で、プライベートです。 この裏の顔が機能して、シンボルに表われないように抑止したんでしょう。だって名前 が解れば呼び出せますからね。調べてみると、あちこちにstaticが付いてました。
gdb 9.2
パッケージから入れたものは、デフォと名前が衝突しないように、egdbってなってる。
eq$ egdb john GNU gdb (GDB) 9.2 Copyright (C) 2020 Free Software Foundation, Inc. : (gdb) watch database.password_count Hardware watchpoint 1: database.password_count (gdb) r G Starting program: /mnt/opt/bin/john G Created directory: /home/sakae/.john Watchpoint 1: database.password_count Old value = 0 New value = 1 ldr_load_pw_line (db=0xfdef2dd4f18 <database>, line=<optimized out>) at loader.c:577 warning: Source file is more recent than executable. 577 last_pw = current_salt->list; (gdb) bt #0 ldr_load_pw_line (db=0xfdef2dd4f18 <database>, line=<optimized out>) at loader.c:577 #1 0x00000fdef2d88529 in read_file (db=0xfdef2dd4f18 <database>, name=<optimized out>, flags=<optimized out>, process_line=0xfdef2d885c0 <ldr_load_pw_line>) at loader.c:66 #2 0x00000fdef2d874cc in john_load () at john.c:427 #3 john_init (name=<optimized out>, argc=<optimized out>, argv=<optimized out>) at john.c:554 #4 main (argc=<optimized out>, argv=<optimized out>) at john.c:702#
大体どのあたりかが、判ってきたな。
Ask Chatgpt
東大・東大生が公開しているエンジニアが読むべき資料まとめ こういうのを見るまでもなく、良い質問を出来る人が賢人だそうだ。
上の例でパスワード・フィールドがどういう構造か、質問してみたぞ。
DESベースのハッシュ (descrypt) の構造 Salt (ソルト) 長さ: 2文字 使用文字: a-z, A-Z, 0-9, . (ピリオド), / (スラッシュ) ソルトはランダムに生成され、パスワードをハッシュ化する際に追加されます。 これにより、同じパスワードでも異なるソルトが与えられると異なるハッシュ値が 生成されます。 Hash (ハッシュ) 長さ: 11文字 ソルトとパスワードを基に、標準的なDES(Data Encryption Standard)を使用 して計算されます。 DESは、64ビット(56ビットの鍵長)の暗号アルゴリズムで、計算結果が エンコードされます。 したがって、descrypt のハッシュは 13文字(2文字のソルト + 11文字のハッシュ) から成ります。 制限: パスワードの長さは 最大8文字(それ以上は切り捨てられます)。 使用可能な文字セットは大文字、小文字、数字、.、/ のみ。
今となっては、とても弱いパスワードしか生成できない仕様なんだな。
オイラーは、パスワードファイルをユーザー数で捉えていたけど、john君は、salt+hash の文字列の種類数を主体に考えていたのね。
trace
ようやくgdb出来るようになったので、少し追跡してみる。慣れ親しんだemacs+gdbって便利だなあ。
count = ldr_split_line(&login, &ciphertext, &gecos, &home, NULL, &db->format, db->options, line); (gdb) p login $3 = 0x6fedd82c2960 "user" (gdb) p ciphertext $4 = 0x6fedd82c2965 "AZl.zWwxIh15Q"
パスワードファイルの1行を取り出して、分解してる。そして、データベースを 初期化してる。
(gdb) p *db $6 = { loaded = 0, options = 0xb7eb6d19b40, salts = 0x0, salt_hash = 0xb7f3019f000, password_hash = 0xb7f1c399000, password_hash_func = 0xb7c4c0c4390 <binary_hash_4>, cracked_hash = 0x0, plaintexts = 0xb7eb6d19b68, salt_count = 0, password_count = 0, guess_count = 0, format = 0xb7c4c11d050 <fmt_DES> }
取り合えずフォーマットはDESに決め打ちみたいだな。
そして、詳細は、 DES_fmt.c
あたりを見ろとな。
valid blowfish
なんてのは嘘解析だった。 ldr_split_line()
の中に秘密が隠されていた。
この関数の中で、solt+hash の文字列を解析して、この暗号部のフォーマット(DES,MD5,..)
を決定してる。
決定に関わるのは、 XXX_fmt.c
にそれぞれ定義されている、valid()って関数だ。
eq$ ls *_fmt.c AFS_fmt.c BSDI_fmt.c LM_fmt.c c3_fmt.c BF_fmt.c DES_fmt.c MD5_fmt.c trip_fmt.c
8種類のフォーマットに対応してるとな。ひょっとして、BFってblowfishの事?
以前 john --test
した時のメッセージを参照。
Benchmarking: bcrypt ("$2a$05", 32 iterations) [Blowfish 32/64 X2]... DONE Raw: 198 c/s real, 198 c/s virtual
ああ、予想は的中した。これで安心して、valid()関数を調査できるな。
static int valid(char *ciphertext, struct fmt_main *self) { int rounds; char *pos; if (strncmp(ciphertext, "$2a$", 4) && strncmp(ciphertext, "$2x$", 4) && strncmp(ciphertext, "$2y$", 4)) return 0; if (ciphertext[4] < '0' || ciphertext[4] > '9') return 0; if (ciphertext[5] < '0' || ciphertext[5] > '9') return 0; if (ciphertext[6] != '$') return 0; rounds = atoi(ciphertext + 4); if (rounds < 4 || rounds > 31) return 0; for (pos = &ciphertext[7]; atoi64[ARCH_INDEX(*pos)] != 0x7F; pos++); if (*pos || pos - ciphertext != CIPHERTEXT_LENGTH) return 0; if (BF_atoi64[ARCH_INDEX(*(pos - 1))] & 3) return 0; if (BF_atoi64[ARCH_INDEX(ciphertext[28])] & 0xF) return 0; return 1; }
検査に失敗すると0点、成功すると1点と言う事で、blowfishと認定されるんだな。 所で、どんなフォーマット? 素晴しい解説が有った。 bcrypt(blowfish)
johnみたいな不埒な野郎に対抗する為、わざと計算に時間が かかる様に設計してあるそうだ。そしてその計算負荷を可変する事が可能とな。 上の監査では、roundsがそれに当たり、4ー31の範囲にしてくれよ、だな。
break blowfish
ここまで判明すれば、前回作成したパスワードも破る事ができるな。但し前回のものは、 現行のOpenBSDで作成したんで、先頭から、$2b\(09\) となっている。johnに適合 するように、$2a\(09\) と変更しておいた。
eq$ cat >BF joy:$2b$09$j4Z6fdVwOWOMwJQavEfPf.NP0WGPqXms7aPthkFEKJOIC0mko5pOy:1001:1001::0:0:joy:/home/joy:/bin/ksh eq$ emacs BF ;; $2b -> $2a eq$ john BF Loaded 1 password hash (bcrypt [Blowfish 32/64 X2]) Press 'q' or Ctrl-C to abort, almost any other key for status 123456 (joy) 1g 0:00:01:04 100% 2/3 0.01546g/s 12.63p/s 12.63c/s 12.63C/s 123456..12345 Use the "--show" option to display all of the cracked passwords reliably Session completed
他のパスワードだと1秒以内で破れているけど、blowfishだと計算負荷が高い為、 解析完了まで、1分以上かかっている。
そして、その時のログの末尾部分。
eq$ tail .john/john.log 0:00:01:03 - Rule #1079: 'l Az"1902" <+' accepted as 'lAz"1902"<+' 0:00:01:03 - Rule #1080: 'l Az"1901" <+' accepted as 'lAz"1901"<+' 0:00:01:03 - Rule #1081: 'l Az"1900" <+' accepted as 'lAz"1900"<+' 0:00:01:04 - Processing the remaining buffered candidate passwords, if any 0:00:01:04 Proceeding with wordlist mode 0:00:01:04 - Wordlist file: /mnt/opt/share/john/password.lst 0:00:01:04 - 57 preprocessed word mangling rules 0:00:01:04 - Rule #1: ':' accepted as '' 0:00:01:04 + Cracked joy 0:00:01:04 Session completed
続いては、どうやって解析してるんだろーー、を調べるのが筋だと思うんだけど、 それは置いておいて、みんなが寄贈したと言うjohn-jumboを試してみたい。
john-jumbo
パッケージから入れるか? それともソースからか? そんな事で悩むな。Linux野郎 じゃあるまいし。
doc/INSTALLを見ると、 ./configure && make -s clean && make -sj4 ってな事が 書かれている。時間がかかるんで、CPUが手分けしてやる設定だな。少々ビビル。
INSTALL-UBUNTUとかINSTALL-WINDOWSなんてのが有るけど、BSD系は説明無し。 見捨てられてる? いや、説明なんて無くても、どうにかしゃう人達でしょって、好意的 に捉えておこう。それでも、参考に覗いてみると
==== If you have an NVIDIA GPU (OpenCL support) sudo apt-get -y install nvidia-opencl-dev ==== If you have an AMD GPU (OpenCL support) :
やっぱり出てきたね。GPUを使うって、仮想通貨のマイニング屋さんみたいだ。感心 してる場合じゃないでしょ。
古い人向け、に Makefile.legacy が用意されてた。これを使えとな。古いjohnの経験が ここで生きて来るんだな。と思ったら。makeはgmakeを使えって罠が仕掛けられていた。 BSDmakeだと文法違反だってさ。まあ、典型的なエラーなんで、すぐに気がついたけど。
心配してたこんぱいる時間は気にする程でもなかった。どんな風に工程が進むか、 psで眺めているうちに、終了した。んで、インストールはしないで、その場で テストだな。
test
eq$ cd run eq$ ./john --test fopen: /etc/john.conf: No such file or directory eq$ doas cp john.conf /etc eq$ ./john --test >/tmp/LOG
興味が有るものを拾い出してみた。
eq$ less /tmp/LOG Benchmarking: descrypt, traditional crypt(3) [DES 128/128 SSE2]... DONE Many salts: 4305K c/s real, 4263K c/s virtual Only one salt: 4116K c/s real, 4116K c/s virtual : Benchmarking: AndroidBackup [PBKDF2-SHA1 128/128 SSE2 4x2 AES]... DONE Benchmarking: AzureAD [PBKDF2-SHA256 128/128 SSE2 4x]... DONE Benchmarking: Django (x10000) [PBKDF2-SHA256 128/128 SSE2 4x]... DONE Benchmarking: IKE, PSK [HMAC MD5/SHA1 32/64]... DONE Benchmarking: MongoDB, system / network [MD5 32/64]... DONE Benchmarking: Mozilla, Mozilla key3.db [SHA1 3DES 32/64]... DONE Benchmarking: mssql, MS SQL [SHA1 128/128 SSE2 4x2]... DONE Benchmarking: NT [MD4 128/128 SSE2 4x4]... DONE Benchmarking: Office, 2007/2010/2013 [SHA1 128/128 SSE2 4x2 / SHA512 128/128 SSE2 2x AES]... DONE Benchmarking: OpenBSD-SoftRAID [PBKDF2-SHA1 128/128 SSE2 4x2]... DONE Benchmarking: Oracle12C [PBKDF2-SHA512 128/128 SSE2 2x]... DONE Benchmarking: PDF [MD5 SHA2 RC4/AES 32/64]... DONE Benchmarking: postgres, PostgreSQL C/R [MD5 32/64]... DONE Benchmarking: radius, RADIUS authentication [MD5 32/64]... DONE Benchmarking: 7z, 7-Zip (512K iterations) [SHA256 128/128 SSE2 4x AES]... DONE Benchmarking: skey, S/Key [MD4/MD5/SHA1/RMD160 32/64]... DONE Benchmarking: SSH [RSA/DSA/EC/OPENSSH (SSH private keys) 32/64]... DONE Benchmarking: VNC [DES 32/64]... DONE Benchmarking: ZIP, WinZip [PBKDF2-SHA1 128/128 SSE2 4x2]... DONE : All 403 formats passed self-tests!
まるで品評会だな。そんな事に感心してないで、現役のOpenBSD(7.6)をクラックしろよ。
eq$ ./john BF Using default input encoding: UTF-8 Loaded 1 password hash (bcrypt [Blowfish 32/64 X3]) Cost 1 (iteration count) is 512 for all loaded hashes : Proceeding with wordlist:./password.lst, rules:Wordlist 123456 (joy) 1g 0:00:01:39 DONE 2/3 (2024-11-25 07:09) 0.01007g/s 52.41p/s 52.41c/s 52.41C/s 123456..password
そんじゃ、FreeBSD 14.1からのパスワードはどうよ?
eq$ ./john FB Using default input encoding: UTF-8 Loaded 1 password hash (sha512crypt, crypt(3) $6$ [SHA512 128/128 SSE2 2x]) Cost 1 (iteration count) is 5000 for all loaded hashes : Proceeding with wordlist:./password.lst, rules:Wordlist 123456 (joy) 1g 0:00:00:02 DONE 2/3 (2024-11-25 07:19) 0.4950g/s 439.1p/s 439.1c/s 439.1C/s 123456..green
弱いパスワードは、あっという間に突破されますよ。注意せましょう。
README
羽田と成田 なんて本を読んだ。
著者は、国土交通省 航空局長(だった人、今は天下りしてる)。相当なバイアスがかかっていると、思って 読めよ。オイラーが本を読む時、いの一番に確認するのは著者の経歴ね。 それから、前書き、目次を見て、どんな地図かを確認。それから、後書きを 先に読んじゃう。
のっけから、成田国際空港の歴史から始まる。他の候補地として、霞ヶ浦 とかが有ったなんて、初めて知ったよ。成田闘争、散発的には知ってた けど、こうして、まとめを開陳されると、すさまじい事だったのね。
その後遺症は、成田空港駅でよく体験した。パスポートと航空券を拝見。それから 厳重な荷物検査。
この重い章に続いて、韓国がらみで、羽田が国際空港に変貌して行く とっかかりが説明されてる。日韓ワールドカップ開催。韓国の金浦空港から 羽田へチャーター便を飛ばして、輸送力を増強したい。それを契機に 国際化のスタートを切る。成田をなだめる為、役人が姑息な理由付け。
羽田から一番遠くにある国内空港は、石垣島にある。距離にして、1947Km。 この同心円の事をペリメーターと言うらしい。金浦空港は、文句なくこの同心円内 に収まる。だったら、国内線と見做してもいいでしょ。それに、冷え込んでいる 両国の虹のかけ橋、友好の象徴にもなるよね。 この論理は、後々、羽田 - 虹橋(中国・上海)便にも適用 される。理屈なんて、後付けでどうにでもなるって、良い例だわな。
一日4便の枠が設定された。JAL、ANA、大韓航空、アシアナ航空ね。まあ、文句が 出ない選定だわな。
そう言えば、アシナナ航空には思い出があるな。この4社の中で、アシアナ航空の ねーちゃん(CA)が一番美形。乗るならアシアナが前担当からの申し送り事項に はいっていた。ある時、乗客が極端に少ない事があった。そのせいか、キャバレー の顔見せよろしく、次々と酒を持ってねーちゃんが登場。随分と酔わせてもらった。エア・ホステス とは、よく言ったものである。