BioErrorLog Tech Blog

試行錯誤の記録

ハードコードされたパスワードを読み出す | リバースエンジニアリング入門#4

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.

ファイルに記述された文字列を表示させることができます。 これは、テキストファイルではないファイルに対して有用です。 テキストファイルであれば普通に中身を見れば良いのですが、例えばバイナリファイルでは大部分が表示可能な文字列ではないため1stringsによって読める文字列を抽出させることができます。 その中に運良くそのままパスワードが記載されていれば、儲けものです。

さっそく、バイナリコード"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: %sHello, 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

アドレスの5555555547fffffffdは無視し、下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を記録しました。

こうしてみると、ソースコードに重要な情報を書き込むことは自殺行為であるということを身に染みて感じます。 なにも難しい技術を使うことなく、コード中の文字列を読むことができます。

ではどのようにプログラムを書けば安全なのでしょうか。 これもまた難しい問題であり、面白いトピックなのでしょう。

[関連記事]

www.bioerrorlog.work

www.bioerrorlog.work


  1. 例えばvimでバイナリコードを開いてみると、大部分が@^@^R^@^@^@^@^@^@^@^@^@^@^@^@^@^@のように表示されます。これは、ASCIIが対応していない範囲の値が読み込まれたことが原因でしょう。

  2. ハードコーディング - Wikipedia

  3. 前回の記事でもこのアドレスを占める数字について触れましたが、いまだに何を意味しているのかよく分かりません。とても気になります。

  4. 16進数の計算は、pythonで>>> hex(0x7e0 + 0x077)のようにやると簡単にできました。

  5. Simple Tools and Techniques for Reversing a binary - bin 0x06 - YouTubeに多くを参考させていただきました。