strings
コマンドやobjdump
コマンドを用いて、プログラム中の正規パスワードを読み出す方法の記録を残します。
はじめに
おはよう。@bioerrorlogです。
前回は、gdbを用いてアセンブラコードを詳細に解析し、レジスタを操作してパスワード認証過程をスキップさせました。 www.bioerrorlog.work
今回は、プログラムに書かれたパスワードを読み出す方法をいくつか記録します。
作業環境
Ubuntu18.04.1 LTS を
Windows10の上に、VMwareによって構築した仮想環境で起動しています。
www.bioerrorlog.work
ハードコードされたパスワードを読み出す
コード中の文字列からパスワードを読む | strings
strings
コマンドは、マニュアルによると、
NAME strings - print the strings of printable characters in files.
ファイルに記述された文字列を表示させることができます。
これは、テキストファイルではないファイルに対して有用です。
テキストファイルであれば普通に中身を見れば良いのですが、例えばバイナリファイルでは大部分が表示可能な文字列ではないため1、strings
によって読める文字列を抽出させることができます。
その中に運良くそのままパスワードが記載されていれば、儲けものです。
さっそく、バイナリコード"your_pass"をstrings
に渡してみます。
$ strings your_pass /lib64/ld-linux-x86-64.so.2 libc.so.6 puts printf __cxa_finalize strcmp __libc_start_main GLIBC_2.2.5 _ITM_deregisterTMCloneTable __gmon_start__ _ITM_registerTMCloneTable =y =W =Z AWAVI AUATL []A\A]A^A_ Checking Your Pass: %s Antoine-Marie-Jean-Baptiste-Roger_comte-de-Saint-Exupery Hello, Master. Access Denied. ;*3$" GCC: (Ubuntu 7.3.0-27ubuntu1~18.04) 7.3.0 crtstuff.c deregister_tm_clones __do_global_dtors_aux completed.7696 __do_global_dtors_aux_fini_array_entry frame_dummy __frame_dummy_init_array_entry your_pass.c __FRAME_END__ __init_array_end _DYNAMIC __init_array_start __GNU_EH_FRAME_HDR _GLOBAL_OFFSET_TABLE_ __libc_csu_fini _ITM_deregisterTMCloneTable puts@@GLIBC_2.2.5 _edata printf@@GLIBC_2.2.5 __libc_start_main@@GLIBC_2.2.5 __data_start strcmp@@GLIBC_2.2.5 __gmon_start__ __dso_handle _IO_stdin_used __libc_csu_init __bss_start main __TMC_END__ _ITM_registerTMCloneTable __cxa_finalize@@GLIBC_2.2.5 .symtab .strtab .shstrtab .interp .note.ABI-tag .note.gnu.build-id .gnu.hash .dynsym .dynstr .gnu.version .gnu.version_r .rela.dyn .rela.plt .init .plt.got .text .fini .rodata .eh_frame_hdr .eh_frame .init_array .fini_array .dynamic .data .bss .comment
長々とした面白そうな文字列が表示されました。 どれも興味深いですが、今回着目すべきはハードコーディング2されたパスワードの文字列です。 つぎの部分が怪しい香りがします。
~ Checking Your Pass: %s Antoine-Marie-Jean-Baptiste-Roger_comte-de-Saint-Exupery Hello, Master. Access Denied. ~
ここで、Checking Your Pass: %s
、Hello, Master.
、Access Denied.
はパスワード認証プログラムとしては有り得そうな文字列ですが、Antoine-Marie-Jean-Baptiste-Roger_comte-de-Saint-Exupery
は文脈に合わない謎の文字列です。
これこそパスワードである可能性があります。
さっそくこの文字列をバイナリコードに渡して実行してみます。
$ ./your_pass Antoine-Marie-Jean-Baptiste-Roger_comte-de-Saint-Exupery Checking Your Pass: Antoine-Marie-Jean-Baptiste-Roger_comte-de-Saint-Exupery Hello, Master.
見事、アクセスが承認されました。 まさしくこれがパスワードだったようです。
.rodataセクションからパスワードを読む | objdump -x / gdb
objdump
コマンドに-x
オプションを渡して実行すると、すべてのヘッダー情報が表示されます。
ヘッダーはどれも面白そうな情報でいっぱいですが、今回は"Sections"の欄に着目します。
$ objdump -x your_pass ~ ~ Sections: Idx Name Size VMA LMA File off Algn 0 .interp 0000001c 0000000000000238 0000000000000238 00000238 2**0 CONTENTS, ALLOC, LOAD, READONLY, DATA 1 .note.ABI-tag 00000020 0000000000000254 0000000000000254 00000254 2**2 CONTENTS, ALLOC, LOAD, READONLY, DATA 2 .note.gnu.build-id 00000024 0000000000000274 0000000000000274 00000274 2**2 CONTENTS, ALLOC, LOAD, READONLY, DATA 3 .gnu.hash 0000001c 0000000000000298 0000000000000298 00000298 2**3 CONTENTS, ALLOC, LOAD, READONLY, DATA 4 .dynsym 000000d8 00000000000002b8 00000000000002b8 000002b8 2**3 CONTENTS, ALLOC, LOAD, READONLY, DATA 5 .dynstr 00000090 0000000000000390 0000000000000390 00000390 2**0 CONTENTS, ALLOC, LOAD, READONLY, DATA 6 .gnu.version 00000012 0000000000000420 0000000000000420 00000420 2**1 CONTENTS, ALLOC, LOAD, READONLY, DATA 7 .gnu.version_r 00000020 0000000000000438 0000000000000438 00000438 2**3 CONTENTS, ALLOC, LOAD, READONLY, DATA 8 .rela.dyn 000000c0 0000000000000458 0000000000000458 00000458 2**3 CONTENTS, ALLOC, LOAD, READONLY, DATA 9 .rela.plt 00000048 0000000000000518 0000000000000518 00000518 2**3 CONTENTS, ALLOC, LOAD, READONLY, DATA 10 .init 00000017 0000000000000560 0000000000000560 00000560 2**2 CONTENTS, ALLOC, LOAD, READONLY, CODE 11 .plt 00000040 0000000000000580 0000000000000580 00000580 2**4 CONTENTS, ALLOC, LOAD, READONLY, CODE 12 .plt.got 00000008 00000000000005c0 00000000000005c0 000005c0 2**3 CONTENTS, ALLOC, LOAD, READONLY, CODE 13 .text 00000202 00000000000005d0 00000000000005d0 000005d0 2**4 CONTENTS, ALLOC, LOAD, READONLY, CODE 14 .fini 00000009 00000000000007d4 00000000000007d4 000007d4 2**2 CONTENTS, ALLOC, LOAD, READONLY, CODE 15 .rodata 00000077 00000000000007e0 00000000000007e0 000007e0 2**3 CONTENTS, ALLOC, LOAD, READONLY, DATA 16 .eh_frame_hdr 0000003c 0000000000000858 0000000000000858 00000858 2**2 CONTENTS, ALLOC, LOAD, READONLY, DATA 17 .eh_frame 00000108 0000000000000898 0000000000000898 00000898 2**3 CONTENTS, ALLOC, LOAD, READONLY, DATA 18 .init_array 00000008 0000000000200da8 0000000000200da8 00000da8 2**3 CONTENTS, ALLOC, LOAD, DATA 19 .fini_array 00000008 0000000000200db0 0000000000200db0 00000db0 2**3 CONTENTS, ALLOC, LOAD, DATA
ここで一番面白そうなのは、15 .rodata
セクションです。
これは"read only data"のセクションを指し示しており、コードに書かれた文字列もここに格納されています。
数字列の意味するところは、
Idx Name Size VMA LMA File off Algn ~ 15 .rodata 00000077 00000000000007e0 00000000000007e0 000007e0 2**3 CONTENTS, ALLOC, LOAD, READONLY, DATA
アドレス0x7e0を開始点として、長さ0x077分のセクションが.rodataセクションである、ということです。 こいつはなかなか面白そうです。
前回、プログラムを解析するなかで、strcmp関数が正規のパスワードと入力された文字列を比較することによって、下流のルート分岐が制御されていることが分かりました。 つまり、このstrcmp関数が実行される時、どれかのレジスタに正規パスワードが格納されていることが予想されます。 そして、そのレジスタはまさしく.rodataセクションを参照していると考えられるわけです。 そのレジスタの中身を覗いてやれば、正規パスワードを表示させることができるでしょう。 さっそくgdbで試してみます。
strcmp関数が呼び出されるところにブレークポイントを設定し、そこまでプログラムを進めたら、レジスタの中身を見てみます。
(gdb) info registers rax 0x7fffffffe30a 140737488347914 rbx 0x0 0 rcx 0x0 0 rdx 0x0 0 rsi 0x555555554800 93824992233472 rdi 0x7fffffffe30a 140737488347914 rbp 0x7fffffffde80 0x7fffffffde80 rsp 0x7fffffffde70 0x7fffffffde70 r8 0x0 0 r9 0x7 7 r10 0xfffffff9 4294967289 r11 0x246 582 r12 0x5555555545d0 93824992232912 r13 0x7fffffffdf60 140737488346976 r14 0x0 0 r15 0x0 0 rip 0x555555554723 0x555555554723 <main+73> eflags 0x212 [ AF IF ] cs 0x33 51 ss 0x2b 43 ds 0x0 0 es 0x0 0 fs 0x0 0 gs 0x0 0
アドレスの555555554
や7fffffffd
は無視し、下3桁に注目します3。
.rodataセクションは0x7e0から0x857の範囲なので4、この範囲を参照しているレジスタはrsi
の0x800のみであることがわかります。
それではx/s
コマンドでレジスタの中身を見てみます。
(gdb) x/s 0x555555554800 0x555555554800: "Antoine-Marie-Jean-Baptiste-Roger_comte-de-Saint-Exupery"
見事、正規のパスワード"Antoine-Marie-Jean-Baptiste-Roger_comte-de-Saint-Exupery"が表示されました。
おわりに
今回は、プログラムに書かれたパスワードを読み出す方法5を記録しました。
こうしてみると、ソースコードに重要な情報を書き込むことは自殺行為であるということを身に染みて感じます。 なにも難しい技術を使うことなく、コード中の文字列を読むことができます。
ではどのようにプログラムを書けば安全なのでしょうか。 これもまた難しい問題であり、面白いトピックなのでしょう。
[関連記事]
-
例えばvimでバイナリコードを開いてみると、大部分が
@^@^R^@^@^@^@^@^@^@^@^@^@^@^@^@^@
のように表示されます。これは、ASCIIが対応していない範囲の値が読み込まれたことが原因でしょう。↩ -
前回の記事でもこのアドレスを占める数字について触れましたが、いまだに何を意味しているのかよく分かりません。とても気になります。↩
-
16進数の計算は、pythonで
>>> hex(0x7e0 + 0x077)
のようにやると簡単にできました。↩ -
Simple Tools and Techniques for Reversing a binary - bin 0x06 - YouTubeに多くを参考させていただきました。↩