BioErrorLog Tech Blog

試行錯誤の記録

Ubuntuでpip/pip3がインストールできないときの対処法 | Python

Ubuntuでは、Pythonのpip/pip3がプリインストールされていませんでした。
これらのインストールに予想外にもつまずいたので、記録を残します。

はじめに

こんにちは、@bioerrorlogです。

Pythonにパッケージをインストールするときには、pipコマンドが便利です。 しかし、Ubuntuにはデフォルトでpipがインストールされていません。

試しにpandasをpipインストールしてみると、

$ pip install pandas

Command 'pip' not found, but can be installed with:

sudo apt install python-pip

エラーが表示され、代わりにpipのインストール手順が示されます。

しかし、この提示されたインストール手順も、そのままではうまく機能しませんでした。

試行錯誤の結果pip / pip3のインストールに成功したので、その記録を残します。

作業環境

Ubuntu18.04.1 LTS を
Windows10の上に、VMwareによって構築した仮想環境で起動しています。
www.bioerrorlog.work

Ubuntuでpip/pip3をインストールする

pipとpip3は違う

まず、前提としてpipとpip3の違いについて確認させてください。

pip3はPython3を指定したpipコマンド、pipはPython2/3のどちらにも設定できるメインのpipコマンド、という整理のようです。

Pythonの「pip」と「pip3」は何が違う?

お恥ずかしい話、当初pipコマンドはすべてpipで、pip3はおまけのようなものだと思っていました。

しかし、UbuntuのようにPython2とPython3が両方インストールされている場合には、この違いが無視できません。 デフォルトではpipと打てばPython2の、pip3と打てばPython3のpipとして機能します。

逆に、Python2かPython3のどちらか片方しかインストールしていない場合は、pipと打てば該当のPythonが指定されます。

まさに、Pythonプログラムを実行する時のpython/python3コマンドと同じでしょう。 Pyhon3で実行するときはpython3を用い、単にpythonで実行すれば、Python2で実行されます。

今回は、pipとpip3をそれぞれ別々にインストールしていきます。

apt installが機能しない
: pip× / pip3×

それでは、pipとpip3をインストールしてみます。

上述のように、pipを使おうとすると次のインストール手順が提示されます。

pip:sudo apt install python-pip

$ pip install pandas

Command 'pip' not found, but can be installed with:

sudo apt install python-pip


pip3:sudo apt install python3-pip

$ pip3 install pandas

Command 'pip3' not found, but can be installed with:

sudo apt install python3-pip


しかし、このどちらもエラーを吐かれてしまい上手くいきません。 とりあえず別の方法を探します。

"get-pip.py"からpipをインストールする
: pip○ / pip3×

しばらく解決方法を探していると、pipをインストールする他の方法を見つけました。

How to Install Pip on Ubuntu 16.04 LTS - Liquid Web

これによると、"get-pip.py"をあるURLから取得・実行すればいいようです。

URLから"get-pip.py"ファイルを取得するにはcurlコマンドを使います。 デフォルトではインストールされていないので、aptでインストールします。

$ sudo apt install curl

インストールが終わったら、"get-pip.py"ファイルをcurlで取得します。

$ curl "https://bootstrap.pypa.io/get-pip.py" -o "get-pip.py"

これで、"get-pip.py"ファイルが取得できました。

この"get-pip.py"を、pythonコマンドで実行します。

$ sudo python get-pip.py

これで"get-pip.py"が実行され、pipがインストールされました。

pipのインストールを--versionで確認すると、

$ pip --version
pip 19.0.3 from /usr/local/lib/python2.7/dist-packages/pip (python 2.7)

確かにpipがインストールされています。


次は、pip3のインストールを試みます。 "get-pip.py"のコードの中身を少し読んでみるとPython2とPython3の両方の環境に対応していたため、pipと同じ要領で"get-pip.py"をpython3で実行してみます。

$ sudo python3 get-pip.py 
~
ModuleNotFoundError: No module named 'distutils.util'

しかしこのやり方ではエラーを吐かれてしまい、なかなか解消することが出来ませんでした。

apt updateしてからapt installする
: pip○ / pip3○

解決策を探して長いこと右往左往していましたが、実に単純な操作で解決しました。 apt installする前にアップデートしてみたら、上手くいったのです。

アップデートは次のふたつの手順で行いました。

$ sudo apt update
~
$ sudo apt upgrade
~

アップデート終了後に再びpip3をインストールしてみると、

$ sudo apt install python3-pip

見事、今度はエラーなくインストールに成功しました。

pip3がちゃんとインストールされたかを--versionで確認すると、

$ pip3 --version
pip 9.0.1 from /usr/lib/python3/dist-packages (python 3.6)

確かにpip3がインストールされていることが分かります。

結論:アップデートしてからapt installすべし

まず行うべきはアップデートです。

$ sudo apt update
$ sudo apt upgrade

ただし、このupdate/upgradeの両方が必要なのか、updateだけで大丈夫なのか、私にはいまいち理解できていません1


その後、pipとpip3をそれぞれapt installします。

$ sudo apt install python-pip
$ sudo apt install python3-pip


上手くいかなければ、"get-pip.py"からpipとpip3をそれぞれインストールします。

$ curl "https://bootstrap.pypa.io/get-pip.py" -o "get-pip.py"
$ sudo python get-pip.py
$ sudo python3 get-pip.py

おわりに

今回は、Ubuntuでpip/pip3をインストールときの試行錯誤を記録しました。

蓋を開ければ解決方法は単純で、インストール前にアップデートしておくというものです。 これはpip/pip3のインストールのみならず、他の多くのパッケージでも適応できる教訓だと思うので、肝に銘じておきます。

ところで、updateとupgradeは具体的にどのような操作を行っているのでしょうか。 漠然とイメージが掴めるばかりに、実際の処理を私は理解できていません。

知らないことが増えていくのは、楽しいことです。

[関連記事]

www.bioerrorlog.work

www.bioerrorlog.work

参考

Pythonの「pip」と「pip3」は何が違う?

How to Install Pip on Ubuntu 16.04 LTS - Liquid Web


  1. 多くのネット上の情報で助言されているのは、updateのみでした。

実験 | Ubuntuディスプレイ解像度を"1x1"に設定してみたい

Ubuntuディスプレイの解像度を、最小の"1x1"に設定できるのか、検証してみました。


はじめに

おはよう。@bioerrorlogです。

以前、Ubuntuディスプレイ解像度を1920x1080に設定した時のこと、xrandrコマンドを実行すると大変面白そうな記述に出会いました。

$ xrandr
Screen 0: minimum 1 x 1, current 800 x 600, maximum 8192 x 8192
~

ここで、解像度maximum 8192 x 8192はまだわかります。 しかし、minimum 1 x 1とは一体どういうことでしょうか。

これは是非とも試してみたいです。

ディスプレイ解像度を変更するやり方は、過去の記事で既に把握しています。

www.bioerrorlog.work

さっそく、本当にディスプレイ解像度を1x1に設定できるのか、試してみます。


※注意※
操作不能になっても構わない仮想環境で試しました。 PCに直接インストールしているOSでは試さない方がよいです。


作業環境

Ubuntu18.04.1 LTS を
Windows10の上に、VMwareによって構築した仮想環境で起動しています。
www.bioerrorlog.work


Ubuntuディスプレイ解像度を"1x1"に設定してみたい

1x1解像度のパラメータを取得する | cvt

とりあえず、解像度を1920x1080に変更したときの手順に則って進めていきます。

まずはcvtコマンドでディスプレイパラメータを取得します。

$ cvt 1 1
# 8x1 0.00 Hz (CVT) hsync: 0.00 kHz; pclk: 0.00 MHz
Modeline "8x1_60.00"    0.00  8 16 8 8  1 4 14 17 -hsync +vsync

少しおかしなことが起きました。 解像度1x1を渡したはずなのに、帰ってきたのは8x1解像度です。

解せませんが、とりあえず8x1解像度でもいいので、進めていきます。


解像度を追加する | xrandr

次に、xrandrコマンドを使って、解像度を設定に追加していきます。 cvtコマンドで得たパラメータを用いて、次のように解像度8x1を追加しました。

$ xrandr --newmode "8x1_60.00"    0.00  8 16 8 8  1 4 14 17 -hsync +vsync

次に、ディスプレイVirtual1に8x1解像度を追加します。

$ xrandr --addmode Virtual1 8x1_60.00

ここで、一旦解像度がデフォルトの小さいサイズに変わりますが、焦る必要はありません。 xrandrコマンドで確認します。

$ xrandr
Screen 0: minimum 1 x 1, current 800 x 600, maximum 8192 x 8192
Virtual1 connected primary 800x600+0+0 (normal left inverted right x axis y axis) 0mm x 0mm
   800x600       60.00*+  60.32  
   2560x1600     59.99  
   1920x1440     60.00  
~
   640x480       59.94  
   8x1_60.00      0.00  

ディスプレイVirtual1に解像度8x1が追加されました。

さて、ここからが本番です。


解像度を変更する

設定から手動で変更

ここまでは過去記事にならって解像度の変更手順を踏んできましたが、ここからはいよいよ解像度を変更していきます。

まずは、解像度を設定から手動で変更します。


左上の「Activities」から「Displays」を検索

「Resolusion」から"8x1"を選択


すると、画面が真っ暗になりました。 解像度の変更に失敗したのか、解像度変更に成功したが目視出来ていないのか、これでは判断がつきません。

もっと悪いことに、この解像度の変更は20秒でもとに戻ってしまいます。 20秒以内に"Keep Changes"ボタンを押さなくてはならないからです(Fig. 1)。

f:id:BioErrorLog:20190318220758j:plain
Fig. 1 通常の解像度に手動で変更するときに表示されるウィンドウ。20秒以内に"Keep Changes"を押さなくてはならない。

8x1のウィンドウの中でこの操作を試みるのはあまり気が進みません。 他の解像度の変更方法を探します。


コマンドで変更

手動ではなく、コマンドでも解像度を変更できることを見つけました。 コマンドでの変更なら、上述の厄介なウィンドウは表示されません。

早速実行してみます。

$ xrandr --output Virtual1 --mode 8x1_60.00

見事、真っ暗な画面が表示されました(Fig. 2)。 20秒でもとに戻ることもありません。

f:id:BioErrorLog:20190318223934j:plain
Fig. 2 解像度8x1に変更した後の仮想環境ウィンドウ。完全な真っ暗に見える。


さて、ここからは、この全くの無に見えるディスプレイが、果たしてちゃんと機能し続けているのか、検証します。

Ubuntuは機能し続けているのか

スクリーンショットから目視で検証する

まずは、虚無に見えるこのウィンドウの中に、8x1のディスプレイが本当に存在しているのか、目視で確認してみます。 このウィンドウの画像をスクリーンショットで撮影し(Fig. 2)、1pxサイズが十分に見える倍率までズームして、8x1ディスプレイを探します。

しかし、頑張って探してもなかなか暗闇の中にUbuntuディスプレイらしきものを見つけることができませんでした。

せっかく生み出した8x1ディスプレイなのに、目に見えないというのは悔しいことです。

操作音を確認する

次に、Ubuntuの操作音を確認してみました。

Ubuntuは、例えばターミナルのスクロールが限界まで来ると"ビープ音"が鳴ります。 こいつを聞くことができれば、Ubuntuがまだ機能し続けていると言えるでしょう。

早速、この暗闇のウィンドウにカーソルを当てて、下カーソルボタン"↓"を押し続けると、

ビープ音が鳴りました!

まだこのUbuntuは生き続けていると確信できた瞬間です。


おわりに

今回は、Ubuntuディスプレイを1x1に設定しようと試みました。

結果としては、解像度1x1は設定できず、8x1に設定することはできましたが、機能し続けているものの、ディスプレイを目視することは出来ませんでした。

今回頑張って設定した解像度は、冷静になると明らかに無意味なものでしょう。 しかし、機械の限界を試すというのは、未知への憧れが掻き立てられて、なかなか良いものです。

それにしても、8x1ディスプレイを目視出来なかったことは悔しいです。 本当に表示されていないのでしょうか。 だとしたら、どのくらいの解像度までは表示されるのでしょう。

まだまだ、気になることばかりです。


関連記事

www.bioerrorlog.work

www.bioerrorlog.work

UbuntuにAtomをインストールする | Atom公式推奨の手順を読み解く

Atom公式の推奨する手順に従って、AtomをUbuntuにインストールしました。

結論を言うと、次の4行のコマンドを実行します。

$ wget -qO - https://packagecloud.io/AtomEditor/atom/gpgkey | sudo apt-key add -
$ sudo sh -c 'echo "deb [arch=amd64] https://packagecloud.io/AtomEditor/atom/any/ any main" > /etc/apt/sources.list.d/atom.list'
$ sudo apt update
$ sudo apt install atom

このコマンドで行っている処理をそれぞれ読み解いていきます。


はじめに

おはよう。@bioerrorlogです。

Linuxではエディタとしてvimを使っています。 使いこなせばとても優秀なエディタだそうですが、初心者としては使いにくい部分があるのは事実です。

そこでとりあえず、Windowsでいつも私が使っているエディタAtomを、Ubuntuにもインストールしてみました。

インストール方法がAtom公式ホームページで解説されていましたが、初心者である私にとってその処理内容は謎ばかりでした。 今回は、その一つ一つの手順を読み解いていきます。

↓Atom公式のインストール方法ヘルプ
Installing Atom


作業環境

Ubuntu18.04.1 LTS を
Windows10の上に、VMwareによって構築した仮想環境で起動しています。
www.bioerrorlog.work


UbuntuにAtomをインストールする

1行目 | 認証キーのダウンロード・登録

$ wget -qO - https://packagecloud.io/AtomEditor/atom/gpgkey | sudo apt-key add -

wgetコマンドをとあるURLに対して実行し、その結果をパイプでsudo apt-key add -に渡しているようです。

まずはwgetコマンドとそのオプション-pOの機能について、マニュアルから調べます。

$man wget
NAME
       Wget - The non-interactive network downloader.

~
-q
--quiet
    Turn off Wget's output.

~
-O file
--output-document=file
    The documents will not be written to the appropriate files, but all will be
    concatenated together and written to file.  If - is used as file, documents will be
    printed to standard output, disabling link conversion.  (Use ./- to print to a file
    literally named -.)

    Use of -O is not intended to mean simply "use the name file instead of the one in the
    URL;" rather, it is analogous to shell redirection: wget -O file http://foo is
    intended to work like wget -O - http://foo > file; file will be truncated immediately,
    and all downloaded content will be written there.

wgetコマンドはネットワークダウンローダーであり、オプション-pはただ出力を表示しないという意味みたいです。

面白いのは次の出力オプション-Oで、出力ファイルとして-を指定すると、結果が標準出力にプリントされると書いてあります。 今回実行するwgetコマンドの前半部分は$ wget -qO - <URL>ですので、まさにURLからダウンロードした結果の出力先に-が指定されており、標準出力として後半のsudo apt-key add -へとパイプされることになります。


さて、次はダウンロードするURLhttps://packagecloud.io/AtomEditor/atom/gpgkeyを調べてみます。 まずはこのURLにブラウザからアクセスしてみました。 するとダウンローダーが立ち上がり、”AtomEditor-atom-4C6E74D6C0A35108.pub.gpg”というファイルのダウンロードを提示されました。 先のwgetコマンドではこのファイルをダウンロードし、sudo apt-key add -へとパイプしたわけでしょう。

さっそくこのファイルを手動でダウンロードし、中身を調べてみます。

まずはfileコマンドでファイルタイプを調べます。

$ file AtomEditor-atom-4C6E74D6C0A35108.pub.gpg
AtomEditor-atom-4C6E74D6C0A35108.pub.gpg: PGP public key block Public-Key (old)

ファイルタイプは"PGP public key block Public-Key"とあります。 これは暗号化でよく聞く公開鍵というやつでしょう。
"PGP"をwikipediaで調べてみる

Pretty Good Privacy (PGP) is an encryption program that provides cryptographic privacy and authentication for data communication.

やはりデータ通信のための暗号化プログラムのようです。

実際に"AtomEditor-atom-4C6E74D6C0A35108.pub.gpg"ファイルの中身を見てみると、

$ cat AtomEditor-atom-4C6E74D6C0A35108.pub.gpg 
-----BEGIN PGP PUBLIC KEY BLOCK-----
Version: GnuPG v1.4.11 (GNU/Linux)

mQINBFo0Ix8BEADWFvFinBKSD+0OTCGrY4F+GxdXdZm3tCT0YxSc54phh6JaDGvs
7gkNYNl4nbGPMK7jfN0X859BOFqBUUZ7A49C3fMVtjT7Q5SIrRCRyuHxy+RHs/gi
1+veNxJU5kQLM2RHC0kzOFczGc83JJvuyecDLfRp9DzpgNFG8BunazByfqz5WhFu
~中略~
z2PiG8U/1G4rcGyRaYbh0YA2GzsM9iGLds7YaMRGhMDytNqZOairMdZJRc5u7x4i
Md1AC6VBgFkelmdqGxyFIFqL3OjzgnCA5xjtKyHctxUldkIVPB/Tuso2pWeybzbt
geP9xVV2t32y5Vnm184J09woo9YPSkdHtwhACy8KUFjcTr+gPK4MuDPi
=xIV1
-----END PGP PUBLIC KEY BLOCK-----

膨大な文字列が記載されています。
これが公開鍵、はじめて出会いました。


さて次は、このダウンロードされた公開鍵がsudo apt-key add -へと渡されています。 apt-keyをマニュアルで調べてみると、

DESCRIPTION
       apt-key is used to manage the list of keys used by apt to authenticate
       packages. Packages which have been authenticated using these keys will
       be considered trusted.
~
COMMANDS
       add filename
           Add a new key to the list of trusted keys. The key is read from the
           filename given with the parameter filename or if the filename is -
           from standard input.

パッケージを認証するための鍵リストを管理する機能があるようです。 今回のケースでは”AtomEditor-atom-4C6E74D6C0A35108.pub.gpg”を新たな認証キーとして登録するということなのでしょう。 add -とすることで、標準入力から読み込むことができるようです。

以上より、1行目では新たな認証キーをダウンロード・登録していることが分かりました。


[関連記事] LinuxではじめてのC言語を実行する | vim + gcc


2行目 | aptにAtomリポジトリを登録

それでは2行目に移ります。

$ sudo sh -c 'echo "deb [arch=amd64] https://packagecloud.io/AtomEditor/atom/any/ any main" > /etc/apt/sources.list.d/atom.list'

sh ~からはじまる前半部分の結果が、/etc/apt/sources.list.d/atom.listへと、sudo権限のもとで出力されています。

まずはshコマンドのマニュアルを調べます。

$ man sh
NAME
     dash — command interpreter (shell)
~
-c       Read commands from the command_string operand instead of from the
         standard input.  Special parameter 0 will be set from the
         command_name operand and the positional parameters ($1, $2, etc.)
         set from the remaining argument operands.

dashコマンドのマニュアルに飛びました。 Ubuntuではshdashという名前だそうです。

Almquist shell - Wikipedia

dashはコマンドインタプリタで、シェルスクリプトの実行などに使われるもののようです。 しかし、ここで-cオプションを与えると、指定したコマンド文字列を実行させることができます。 今回のケースでは'echo "deb [arch=amd64] https://packagecloud.io/AtomEditor/atom/any/ any main" > /etc/apt/sources.list.d/atom.list'が実行される訳です。

こいつは大変面白い工夫です。

例えば今回のこのコマンドをsh -cを使わずに実行してみたとすると、

$ sudo echo "deb [arch=amd64] https://packagecloud.io/AtomEditor/atom/any/ any main" > /etc/apt/sources.list.d/atom.list

となりますが、これはうまく行きません。 リダイレクト> /etc/apt/sources.list.d/atom.listsudo権限が及んでいないため、出力ができないからです。

そこで、echo ~以下のコマンドをすべてひとまとまりとし、sh -cに渡してsudo権限で実行することで、/etc/apt/sources.list.d/atom.listへの出力までを実現できるということでしょう。 実によくできていると思いました。

ちなみに、/etc/apt/sources.list.d/atom.listの中身を確認してみると、

$ cat /etc/apt/sources.list.d/atom.list 

deb [arch=amd64] https://packagecloud.io/AtomEditor/atom/any/ any main

たしかに指定した文字列が書き込まれていました。

この書き込まれたURLを見ると、Atomのpackagecloudリポジトリが指定されています。

AtomEditor/atom - Packages · packagecloud

ここからAtomパッケージをダウンロードできます。

つまりこの行では、aptディレクトリにAtomパッケージのリポジトリを登録した、ということになります。


[関連記事] Ubuntuディスプレイ解像度の変更 | 1920x1080


3-4行目 | aptインストール

$ sudo apt update
$ sudo apt install atom

3-4行目でやっていることは、普段aptでパッケージをインストールするのと変わりありません。

aptをアップデートした後、Atomをインストールしています。

以上によって、Atomをインストールできました。


おわりに

今回は、Atom公式の推奨するインストール手順を読み解いてきました。

無事Atomもインストールできましたし、それぞれのコマンド処理内容もとりあえず把握できましたが、まだまだわからないことだらけです。

登録した公開鍵は実際どのようにして、何の認証に使われているのでしょうか。 そもそも、aptインストールとはつまるところ何を行っているのでしょうか。

単なるAtomのインストール作業から、思いもよらぬ謎が豊富に湧き上がってきました。
こういうのも、なかなか面白いことです。


参考

Installing Atom

Pretty Good Privacy - Wikipedia

Almquist shell - Wikipedia

AtomEditor/atom - Packages · packagecloud

gdbをデフォルトでintel記法にする | 初期化処理の仕組み

gdbシンタックスをデフォルトでintel記法にする方法と、その仕組みを調べます。


はじめに

おはよう。@bioerrorlogです。

GNUデバッガgdbのアセンブラ表記はAT&T記法とintel記法の2つがあり、それぞれ見た目は次のようになっています。

  • AT&T記法
Dump of assembler code for function main:
   0x00000000000006da <+0>:  push   %rbp
   0x00000000000006db <+1>:  mov    %rsp,%rbp
   0x00000000000006de <+4>:  sub    $0x10,%rsp
   0x00000000000006e2 <+8>:  mov    %edi,-0x4(%rbp)
   0x00000000000006e5 <+11>: mov    %rsi,-0x10(%rbp)
   0x00000000000006e9 <+15>: cmpl   $0x2,-0x4(%rbp)


  • intel記法
Dump of assembler code for function main:
   0x00000000000006da <+0>:  push   rbp
   0x00000000000006db <+1>:  mov    rbp,rsp
   0x00000000000006de <+4>:  sub    rsp,0x10
   0x00000000000006e2 <+8>:  mov    DWORD PTR [rbp-0x4],edi
   0x00000000000006e5 <+11>: mov    QWORD PTR [rbp-0x10],rsi
   0x00000000000006e9 <+15>: cmp    DWORD PTR [rbp-0x4],0x2

intel記法のほうが右の部分がスッキリしていて見やすいので、私はintel記法のほうが好きです。

しかし、gdbはデフォルトではAT&T記法に設定されているため、intel記法に変更するには次のコマンドを打つ必要があります1

(gdb) set disassembly-flavor intel

gdbを立ち上げるたびに毎回このコマンドを打つのは面倒です。

そこで、デフォルトでintel記法に設定する方法とその仕組みを調べました。


作業環境

Ubuntu18.04.1 LTS を
Windows10の上に、VMwareによって構築した仮想環境で起動しています。
www.bioerrorlog.work


gdbをデフォルトでintel記法にする

ホームディレクトリ直下で".gdbinit"にコマンドを書き込む

やり方を検索してみると、すでに多くの方々が解決策を示していました。

gdbで初期設定をAT&T記法からIntel記法に変更する方法 | サラリーマンがハッカーを真剣に目指す

ホームディレクトリに".gdbinit"というファイルを作成し、そこに実行させたいコマンドを書き込むだけでいいようです。 さっそく".gdbinit"を作成し、

set disassembly-flavor intel

とだけ書き込みました。 これを保存し、gdbで確認すると、

(gdb) disas main
Dump of assembler code for function main:
   0x00000000000006da <+0>:  push   rbp
   0x00000000000006db <+1>:  mov    rbp,rsp
   0x00000000000006de <+4>:  sub    rsp,0x10
   0x00000000000006e2 <+8>:  mov    DWORD PTR [rbp-0x4],edi
   0x00000000000006e5 <+11>: mov    QWORD PTR [rbp-0x10],rsi
   0x00000000000006e9 <+15>: cmp    DWORD PTR [rbp-0x4],0x2

たしかにデフォルトでintel記法になっており、ひとまず目的を達成しました。

しかし、なぜ".gdbinit"というファイル名が認識され、コマンドが自動で実行されるのでしょうか。

まるで見当がつきません。 ここを調べていきます。


[関連記事] 逆アセンブル解析 - gdb | リバースエンジニアリング入門#2


"."からはじまるドットファイルの意味

まずは、"."からはじまるファイル(ドットファイル)の意味について。

前々から、lsコマンドではドットファイルは表示されず、ls -aとしなければ表示されないことは経験として認識していましたが、その意味はいまいち分かりませんでした。 このドットファイルが隠しファイルである理由については、面白い情報があったので添えておきます。

Unix系の隠しファイルの仕組みは開発者のポカから生まれた悪習だった - Qiita


次に、ドットファイルの役割について調べてみました。 すると、ドットファイルは設定を書き込んで読み込ませるためのファイルとして利用されるようです2。 まさに今回".gdbinit"で行ったような用途で使われるというわけです。

しかし、このドット自体に何か特別な機能が付与されているわけではなく、あくまで設定ファイルを取り扱うための表記のようです。 これだけでは、".gdbinit"というファイルがgdbによって実行される理由にはなりません。

gdbドキュメントから調べる

わからなくなったときは、公式のドキュメントやマニュアルを読むのが結局一番早い、というのが私の経験則です。 gdbについては、オンラインドキュメントがありました。

Debugging with GDB

これを調べてみると、gdbの初期化処理についての記述がありました。 まとめると、次のように39つの初期化処理が順番に実行されるようです。

2.1.3 What GDB Does During Startup
Here’s the description of what GDB does during session startup:

1. Sets up the command interpreter as specified by the command line.
2. Reads the system-wide init file and executes all the commands in that file.
3. Reads the init file in your home directory and executes all the commands in that file.
4. Executes commands and command files specified by the ‘-iex’ and ‘-ix’ options in their specified order.
5. Processes command line options and operands.
6. Reads and executes the commands from init file in the current working directory.
7. If the command line specified a program to debug, or a process to attach to, or a core file, GDB loads any auto-loaded scripts provided for the program or for its loaded shared libraries.
8. Executes commands and command files specified by the ‘-ex’ and ‘-x’ options in their specified order.
9. Reads the command history recorded in the history file.

ここで着目すべきは3番目の処理です。

3. Reads the init file in your home directory and executes all the commands in that file.

ホームディレクトリにある初期化ファイルを読み込み、コマンドをすべて実行する、とあります。 まさしく、今回私は".gdbinit"をホームディレクトリに置いたので、この処理過程において実行されたというのであれば、納得がいきます。 あとは、".gdbinit"が初期化ファイルとしてgdbに認識されていればいい訳です。

初期化ファイルの定義について調べてみると、いくつか記述を見つけました。

2.1.3 What GDB Does During Startup
~
The GDB init files are normally called .gdbinit.

または、

2.1.2 Choosing Modes
~
~/.gdbinit
This is the init file in your home directory.

など。

ここまでわかってくれば、さすがに納得できます。

整理すると以下のようになります。

  • gdb初期化処理は9つのステップが用意されている。
  • そのうち3つ目のステップではホームディレクトリ直下の初期化ファイルが処理される。
  • ホームディレクトリ直下の初期化ファイルは".gdbinit"という名前を持つことになっている。


おわりに

gdbをデフォルトでintel記法にする方法と、その仕組みを記録しました。

今回行った方法はホームディレクトリ直下に".gdbinit"を置くやり方ですが、面白いのはこれ以外にも8つの初期化ステップがgdbに用意されているということです。 Google検索でヒットする解決策が概ねひとつの方法に収束されている今回のような場合でも、ドキュメントを辿ってみるとそれ以外の多くのやり方が開発者から用意されている、というのはなかなか奥ゆかしいことです。

とはいえ、多くの方々が実践しているやり方というのが、最も効率的な方法である場合がほとんどでしょう。 効率性と好奇心のバランスを、上手いこと回すのが大切かもしれません。


参考

Debugging with GDB

DotFiles - Debian Wiki

Dotfiles - ArchWiki

gdbで初期設定をAT&T記法からIntel記法に変更する方法 | サラリーマンがハッカーを真剣に目指す

Unix系の隠しファイルの仕組みは開発者のポカから生まれた悪習だった - Qiita


  1. 逆アセンブル解析 - gdb | リバースエンジニアリング入門#2 - 生物系がゼロから始めるTech Blogも参考に。

  2. DotFiles - Debian WikiまたはDotfiles - ArchWikiなど

  3. 原文からここで不要と思われる部分をカットして載せています。

Pythonで鍵生成 - Keygen | リバースエンジニアリング入門#7

鍵生成プログラムKeygenをPythonで作成します。 まずはradare2を用いてパスワード認証アルゴリズムを解析し、それを開くことができるKeygenを作成しました。


はじめに

おはよう。@bioerrorlogです。

前回は、ごく簡単な暗号アルゴリズムを用いてパスワード認証プログラム"your_pass_2"を作成しました。

www.bioerrorlog.work

今回は、このバイナリコードを自らリバースエンジニアリングして仕様を解析し、パスワード認証を通過できる鍵を生成する"Keygen"をPythonで作成します。

なお、内容はこのLiveOverflow氏の動画を参考に、自分で少し改変を加えつつ流れを再現するという形になります。

youtu.be


作業環境

Ubuntu18.04.1 LTS を
Windows10の上に、VMwareによって構築した仮想環境で起動しています。
www.bioerrorlog.work


Pythonで鍵生成

radare2でアルゴリズムを解析する

radare2でバイナリコード"your_pass_2"を逆アセンブルし、パスワード認証のアルゴリズムを解析します。 まずは、ビジュアルモードで着目すべき場所を探します。

なお、radare2の使い方については過去記事に詳しく記録しました。

www.bioerrorlog.work

それでは、さっそくradare2のビジュアルモードでmain関数を解析します。

$ r2 your_pass_2
~
> aaa
~
> s sym.main
> VV
Rendering graph...

ビジュアルモードの結果を入念に見ると、一箇所だけループ構造が発生していました(Fig. 1)。 ここを詳しく解析してみます。

f:id:BioErrorLog:20190223232422p:plain
Fig. 1 main関数で見られたループ部分のアセンブラコード構造

forループの特定

ループ構造として、まずはforループを疑ってみます。 これが典型的なforループならば、ループに入る前に0にセットされ、ループのたびに1ずつ増える変数(ループカウンタ" i ")があるはずです。 探してみると、ループ前に0にセットされている変数が2つありました(Fig. 1, ➀ブロック)。

mov dword [local_18h], 0 
mov dword [local_14h], 0

この2つの変数dword [local_18h]dword [local_14h]の行く末を見てみると、dword [local_14h]はループごとに1が加算されているのがわかります(Fig. 1, ➂ブロック)。

add dword [local_14h], 1 

ここからdword [local_14h]がループカウンタ" i "である期待が高まります。

つぎに、dword [local_14h]がループカウンタであれば、forループの終了条件に絡んでいるはずです。 そこでforループの分岐条件を辿ってみると(Fig. 1, ➁)、

mov eax, dword [local_14h]
~
movsxd rbx, eax
~
cmp rbx, rax
jb 0x723

dword [local_14h]eaxrbxと、分岐条件cmp rbx, raxに繋がっていました。

以上のことから、このループ構造はdword [local_14h]をループカウンタ" i "としたforループであると判断しました。

forループの処理を解析する

次に、forループ内の処理(Fig. 1, ➂ブロック)を解析しました。 とても複雑で難解なので、Cで書かれたソースコード1を書き置きます。

#include <string.h>
#include <stdio.h>

int main(int argc, char *argv[]) {
        if(argc==2) {
                printf("Checking Your Pass: %s\n", argv[1]);
                int sum = 0;
                for (int i=0; i<strlen(argv[1]); i++) {
                        sum += (int)argv[1][i];
                }
                if(sum==5345) {
                        printf("Hello, Master.\n");
                } else {
                        printf("Access Denied.\n");
                }

        } else {
                printf("Access Denied.\n");
        }
        return 0;
}

このソースコードによると、forループ内では、入力された文字argv[1]を一文字ずつint値として変数sumに加算していることがわかります。 この処理を、radare2で逆アセンブルしたコードから読み解きます。

forループ内(Fig. 1, ➂ブロック)は、

mov rax, qword [s]  
add rax, 8  
mov rdx, qword [rax] 
mov eax, dword [local_14h]
cdqe  
add rax, rdx   
movzx eax, byte [rax]    
movsx eax, al   
add dword [local_18h], eax
add dword [local_14h], 1

となっています。 最初から順に読み解いていきます


まず最初の2行に着目しました。

mov rax, qword [s]  
add rax, 8

レジスタraxに値が格納された後、8が加算されている点に注目します。 これは、raxが配列のアドレスを格納していることを示唆しています。

通常、64 bitコンピュータではメモリが8 byteずつに分割されています。 よって、1行目でraxに何かの配列のアドレスが格納された場合、2つ目の配列要素にアクセスしたいときにはraxに8を加算すればよい、というわけです。

ここで「2つ目の配列要素にアクセス」ということから考えられるのは、入力文字argv[1]でしょう。 argv[]配列は、プログラムの実行時に渡される引数を格納しています。 たとえばこのプログラム"your_pass_2"を、"Wrong-Pass"という文字列を渡して実行した場合、

$ ./your_pass_2 Wrong-Pass

argv[]配列は次のように格納されています。

./your_pass_2 Wrong-Pass
argv[0] argv[1]

よって、上記の2行は次のように解釈できそうです。

アセンブラコード レジスタの解釈
mov rax, qword [s] rax: argv[0]のアドレス
add rax, 8 rax: argv[1]のアドレス


では、次のコードに移ります

mov rdx, qword [rax] 
mov eax, dword [local_14h]
cdqe  
add rax, rdx   

前述の通り、raxargv[1]のアドレスを示しているので、1行目でrdxに格納されたのはargv[1]の中身を指すアドレスでしょう。 通常、先頭が指定されます。

アセンブラコード レジスタの解釈
mov rdx, qword [rax] rdx: argv[1]の中身の先頭を指すアドレス


次の行mov eax, dword [local_14h]では、dword [local_14h]が登場します。 これは、上述したようにforループで1ずつ増えるループカウンタです。 よってループカウンタの値がeaxに格納されるわけです。

その次の行cdqeは、eaxraxに設定する操作です2。 よって最後のadd rax, rdxは、先頭を指すアドレスrdxに、ループカウンタの値raxを足して、raxに格納しています。 ここまでをまとめると、

アセンブラコード レジスタの解釈
mov eax, dword [local_14h] eax: ループカウンタの値
cdqe rax: ループカウンタの値
add rax, rdx rax: 先頭アドレス+ループカウンタ


それでは最後の部分です。

movzx eax, byte [rax]    
movsx eax, al   
add dword [local_18h], eax

まずmovzx eax, byte [rax]で、raxの指し示すアドレスから1バイトを読み込んでいます。 この値はまさに入力文字のASCII値であると考えられます。 例えば、入力文字が"Wrong-Pass"で、ループカウンタが2の場合、

入力文字: W r o n g
ASCII値: 0x57 0x72 0x6F 0x6E 0x67
rax(rdx+ループカウンタ): rdx+1 rdx+2 rdx+3 rdx+4 rdx+5

eaxには"r"のASCII値0x72が格納されるというわけです。

次の行movsx eax, alでは、eaxの下位8ビット部分がeaxに再格納されています。 というのも、aleaxの下位8ビットを示すレジスタだからです3

そして最後には、add dword [local_18h], eaxeaxの値がdword [local_18h]に加算されています。 ここで、先に述べたようにdword [local_18h]は0で初期化された変数です。 つまり、ループごとにdword [local_18h]に入力文字が加算され、最後には入力文字の合計ASCII値の総和が格納されている、ということになります。


かなり長く複雑な解析をしてきました。 ここまでわかったforループの処理をまとめます。

アセンブラコード レジスタの解釈
mov rax, qword [s] rax: argv[0]のアドレス
add rax, 8 rax: argv[1]のアドレス
mov rdx, qword [rax] rdx: argv[1]の中身の先頭を指すアドレス
mov eax, dword [local_14h] eax: ループカウンタの値
cdqe rax: ループカウンタの値
add rax, rdx rax: 先頭アドレス+ループカウンタ
movzx eax, byte [rax] eax: 入力文字のASCII値
movsx eax, al eax: 下位8ビットを再格納
add dword [local_18h], eax dword [local_18h]: 入力文字ASCII値の総和


アクセス可否の判定条件

ここまで、forループ内で入力文字の総和dword [local_18h]が計算されていることを明らかにしました。 つぎは、この処理結果がどのようにしてアクセス可否の判定に使われているかを調べます。

これは簡単に分かりました。 forループ終了後の処理を見ると(Fig. 1, ➃)、次のようになっています。

cmp dword [local_18h], 0x14e1 
jne 0x778

つまり、入力文字の総和dword [local_18h]が0x14e1であるかどうかを判定し、プログラムが分岐しているわけです。

ついに、このプログラムの挙動が明らかになりました。 各入力文字ASCII値の総和が0x14e1であればアクセス承認、そうでなければアクセス拒否です。


Pythonで鍵生成 - Keygen

このパスワード認証プログラムの仕組みがわかったので、これを破るためのKeygenを作成します。 仕様は単純です。 ASCII値の総和が0x14e1となる文字列を自動生成するプログラムを、Pythonで書きます4

import random

#文字列keyの合計ASCII値を算出する関数
def check_key(key):
    char_sum = 0
    for c in key:
        char_sum += ord(c)
    return char_sum

#合計ASCII値が5345(0x14e1)である文字列を無限ループで生成
key = ""
while True:
    key += random.choice("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_")
    s = check_key(key)
    if s > 5345:
        key = ""
    elif s == 5345:
        print ("Key: {0}".format(key))

さっそくこのPythonプログラム"keygen_your_pass_2.py"実行してみます。

$ python3 keygen_your_pass_2.py 
Key: 3beg-iCw2Xg7OP7E1xkn-HEaxai-KRl43pj5OkJuViZJnIozP9-q-XKOG7v0vWy6
Key: kI_MoMol9yqM6gImi2lyfVhNn6npOo7eRXxelQ4VHh88_BIfb5NtKvsyVHF1
Key: 9qFkJQVe74CTJz5EpF8h6E6bukw1XFYJ7YhndKM58SlymLooMra30RfgUUFUN5kK
Key: PLySl4Mu_eDoOaDQ7BV09K6WGrtOZFLP--pojSBFq8GPxIsgIxX_I0YkNQmBb8f7
Key: ui-IAbPFU2cR20ZMCMYSbyMqbuNpUqNutqwtll-YxeZx83KcI--u-PJF5B3RuAO
Key: ZL6AZgMIlphS0spzgWEsy_sFKusCS_46ahjGm2T7PQBf7h3ifrmytkX89s5z
Key: LfX9lEUp5vGDTVOlo2O7NyJQR_yne6Dd8BBTCQxfBleMmIz8RJPYYa9JuGpfdg
Key: 9yMjf2YKivb84KuhF6iLdvTssguiQne6kVKyjJvAvlV13qskhNgfLOHQBy6
Key: 5tzqUevjCYFoZ1lEnJWoIJJOHD4OMTv3qB5A3W2SBlHlhuXThrqksLV8FuHc-j
Key: _mcHUIbIM5QEAyGQOTqvpY-5TKsVBJEdGuT86fLcP3KrqAUH_fD1s0fR2JNqVS0NL
Key: 6t0pv0F3SbtKXwEjsp47xrfnC9zr2VjtqTOrTEYGcmwoRvpkdBT2bj_T6dO
Key: BjOLf49V_xPvaONmPyiyKoQAP0WYc3PNrq7VD-UQqT8I3otgILvmADOrGo3sDf
Key: nNBYpcTZAXdx8aXA5EUwSgXsKT2lcQpaH7JnuEuvmO2bvvXKNkSdwVPlkIm
Key: YnvH_JbZsDwKmJg7_fA4xnrn-UwM_d_N_G-xQS47wsOI5q12Cwc9hVIx_m3C3D
#以下略

即座に大量の鍵が生成されました。 ちなみに無限ループなので、処理を終了するには"Ctrl+c"を押す必要があります。

それでは、はたしてこれらの鍵でこのプログラム"your_pass_2"のアクセス承認が引き出せるのかを確かめます。

$ ./your_pass_2 A2viBkKm4IU-I_pEGF6-7YKONqAEk1Vl6KIAAg88fHKjFMN2DAqBjBvlwaqib-_xe5
Checking Your Pass: A2viBkKm4IU-I_pEGF6-7YKONqAEk1Vl6KIAAg88fHKjFMN2DAqBjBvlwaqib-_xe5
Hello, Master.

"Hello, Master."
見事、アクセス承認を引き出すことができました。


おわりに

今回は、radare2を用いてパスワード認証アルゴリズムを解析し、それに基づいてPythonで鍵生成プログラムKeygenを作成しました。

以前のようにデバッグ上でレジスタを操作する5のでなく、完全に外側からアクセスを開くのはなかなか趣深いものでした。

今回はソースコードを知っている状態で、かつ極めて簡単な暗号アルゴリズムをリバースしましたが、自分が知らないプログラムの解析にも挑戦してみたいです。 そういうことをするための安全なコミュニティがネット上にはたくさんある6ようなので、今後手を出していけたらと思っています。

[関連記事]

www.bioerrorlog.work

www.bioerrorlog.work

www.bioerrorlog.work


  1. パスワード認証の暗号化 | リバースエンジニアリング入門#6 - 生物系がゼロから始めるTech Blog

  2. Assembly Programming on x86-64 Linux (07)

  3. http://milkpot.sakura.ne.jp/note/x86.htmlより、"eax は rax の下位 32 ビット部分を指す。 また ax は eax の下位 16 ビット部分を指す。 さらに ax は上下 2 つの 8 ビットレジスタ ah, al に分割して使うことができる。"

  4. GitHub - LiveOverflow/liveoverflow_youtube: Material for the YouTube seriesを参考にしています。

  5. レジスタ操作でアクセス承認を引き出す - gdb | リバースエンジニアリング入門#3 - 生物系がゼロから始めるTech Blog

  6. 逆に、禁じられているソフトウェアを解析することは違法ですから、そこの一線は絶対に超えてはなりません。

パスワード認証の暗号化 | リバースエンジニアリング入門#6

ASCII値の合計を比較する簡単な暗号アルゴリズムを用いて、自らリバースエンジニアリングするためのパスワード認証プログラムを作成します。


はじめに

おはよう。@bioerrorlogです。

このリバースエンジニアリングシリーズの初回には、単純に入力文字と正規パスワードを比較するパスワード認証プログラムをCで作成しました。

www.bioerrorlog.work

#include <string.h>
#include <stdio.h>

int main(int argc, char *argv[]) {
        if(argc==2) {
                printf("Checking Your Pass: %s\n", argv[1]);
                if(strcmp(argv[1], "Antoine-Marie-Jean-Baptiste-Roger_comte-de-Saint-Exupery")==0) {
                        printf("Hello, Master.\n");
                } else {
                        printf("Access Denied.\n");
                }

        } else {
                printf("Access Denied.\n");
        }
        return 0;
}

前回まではこのCコードのバイナリコードを解析してきましたが、コード上に正規パスワードが直接記述されているため、stringsコマンドなどで簡単にパスワードを調べることができてしまいました1

そこで、今回は簡単な暗号アルゴリズムを用いて、パスワード認証プログラムをこれまでより少し破りにくいものに改良します2

作業環境

Ubuntu18.04.1 LTS を
Windows10の上に、VMwareによって構築した仮想環境で起動しています。
www.bioerrorlog.work


パスワード認証の暗号化

簡単な暗号アルゴリズム

ごく簡単な暗号アルゴリズムとして、パスワードのASCII3値の合計を比較するやり方を使用します。 たとえば、パスワードが"PASS"だった場合、

パスワード: P A S S 合計
ASCII値: 0x50 0x41 0x53 0x53 311

ASCII値の合計は10進数で311となります。 この値を正規パスワードと入力パスワードで比較することで、パスワードの認証を行います。

正規パスワードの合計ASCII値を算出する

それでは、正規パスワードのASCII値の合計を算出します。 上記のパスワード認証プログラムのソースコードでは、正規パスワードが"Antoine-Marie-Jean-Baptiste-Roger_comte-de-Saint-Exupery"となっています。 この正規パスワードのASCII値の合計を算出するために、次のようなCコード"ascii_sum.c"を書きました。

#include <string.h>
#include <stdio.h>

int main(int argc, char *argv[]) {
        if(argc==2) {
                int sum = 0;
                for (int i=0; i<strlen(argv[1]); i++) {
                        sum += (int)argv[1][i];
                }
                printf("ASCII sum: %d\n", sum);

        } else {
                printf("Usage: <key>\n");
        }
        return 0;
}

これで、引数に渡した文字列の合計ASCII値が出力されるはずです。 コンパイルしてから、試しに上に示した"PASS"を渡してみると、

$ ./ascii_sum PASS
ASCII sum: 311

正しく合計値311が表示され、プログラムが予想通りに機能することが伺えます。

そこで、正規パスワードのASCII合計値を調べると、

$ ./ascii_sum Antoine-Marie-Jean-Baptiste-Roger_comte-de-Saint-Exupery
ASCII sum: 5345

5345であることが分かりました。

パスワード認証プログラムを作成する

それでは、パスワード認証プログラムを変更して、渡された文字列の合計ASCII値を5345と比較することでアクセス可否を判断するプログラムに書き換えます。

#include <string.h>
#include <stdio.h>

int main(int argc, char *argv[]) {
        if(argc==2) {
        printf("Checking Your Pass: %s\n", argv[1]);
        int sum = 0;
                for (int i=0; i<strlen(argv[1]); i++) {
                        sum += (int)argv[1][i];
                }
        if(sum==5345) {
            printf("Hello, Master.\n");
        } else {
                    printf("Access Denied.\n");
            }

    } else {
        printf("Access Denied.\n");
    }
    return 0;
}

これを"your_pass_2.c"とし、コンパイルしてプログラムの挙動を確認します。

まずは間違ったパスワード"WRONG-PASS"を渡して実行します。

$ ./your_pass_2 WRONG-PASS
Checking Your Pass: WRONG-PASS
Access Denied.

予想通りにアクセスが拒否されました。

次に、正規パスワード"Antoine-Marie-Jean-Baptiste-Roger_comte-de-Saint-Exupery"を渡して実行します。

$ ./your_pass_2 Antoine-Marie-Jean-Baptiste-Roger_comte-de-Saint-Exupery
Checking Your Pass: Antoine-Marie-Jean-Baptiste-Roger_comte-de-Saint-Exupery
Hello, Master.

見事、アクセスが承認されました。 これで、ソースコードに直接正規パスワードを記述しない簡単なパスワード認証プログラムができました。


おわりに

今回は、ごく簡単な暗号アルゴリズムを用いて、パスワード認証プログラムを改良しました。

次回は、このバイナリコードをリバースエンジニアリングし、正解パスワードを生成する"Keygen"プログラムを作成します。

それにしても、少し調べてみると暗号アルゴリズムというものは大変奥が深そうです。 今回は可能な限り簡単なやり方をしましたが、現代においても暗号技術は一つの学問として日々研究成果が報告されています。

ハードウェアについてまるで知識がありませんが、たとえば量子コンピュータが飛躍的に高い性能で実現した場合、現在使われている暗号技術が解読可能になったりはしないのでしょうか。 仮に解読可能になった場合、この複雑にシステムが絡み合うIT社会の中で、新しい暗号技術への移行はスムーズに行えるものなのでしょうか。

妄想は膨らみます。

[関連記事]

www.bioerrorlog.work

www.bioerrorlog.work


  1. ハードコードされたパスワードを読み出す | リバースエンジニアリング入門#4 - 生物系がゼロから始めるTech Blogを参照。

  2. LiveOverflow氏の動画https://youtu.be/qS4VWL5R_OMを参考にしています。

  3. ASCIIとは、最もよく使用される文字コードです。数値と文字が対応したASCIIコードによって、コンピュータによる文字の表示を実現します。ASCIIコードは、ターミナルで$ man asciiとすることで見ることができます。

radare2の使い方 | リバースエンジニアリング入門#5

radare2のインストール方法から簡単な使い方までを記録しました。
直感では使いにくい面も感じましたが、使い慣れれば強力な武器となってくれるでしょう。

はじめに

おはよう。@bioerrorlogです。

これまでは、gdbやobjdumpコマンドなどを用いてバイナリコードのリバースエンジニアリングを試みてきました。

www.bioerrorlog.work

今回は、radare2という便利なツールを使ってバイナリコードを解析していきます。 radare2を使えば、バイナリコードの逆アセンブルからプログラム分岐の可視化、デバッグを一手に行うことができます。

さっそくやっていきます。

作業環境

Ubuntu18.04.1 LTS を
Windows10の上に、VMwareによって構築した仮想環境で起動しています。
www.bioerrorlog.work

radare2の使い方

radare2をインストールする

radare2をインストールするには、まず製作者のGitHubからradare2を"clone"1する必要があります。 そのために、まずはgit2をインストールします。

$ sudo apt install git

これでgitがインストールされました。

それではこのgitを使って、製作者のGitHubからradare2をcloneします。

$ git clone https://github.com/radare/radare2.git

cloneが終わったら、radare2の中に入ってインストールファイルを実行します。

$ cd radare2
$ ./sys/install.sh

すると、次のようなエラーを吐かれてしまいました。

You need GNU Make to build me

ビルドするには"GNU Make"が必要ですよと怒られてしまったわけです。
"GNU Make" とは、wikipediaによると、

makeは、プログラムのビルド作業を自動化するツール。コンパイル、リンク、インストール等のルールを記述したテキストファイル (makefile) に従って、これらの作業を自動的に行う。

いまいちよくわかりませんが、ビルドに必要なツールということで今は納得しておきます。 それではこのmakeをインストールします。

$ sudo apt install make

これでmakeがインストールされたので、radare2をインストールできるはずです。

$ ./sys/install.sh

すると、順調にインストールが進んだかに見えましたが、今度は次のエラーが表示されました。

~
ERROR: gcc cannot create executables

コンパイラであるgccが実行ファイルを作成できませんでした、とのことです。 それもそのはず、gccをまだインストールしていませんでした。 gccをインストールします。

$ sudo apt install gcc

これでgccもインストールされたので、今度こそradare2がインストールできるはずです。

$ ./sys/install.sh

長い処理の後、無事インストールが完了しました3

radare2を起動する

radare2を使って解析を始めるには、単純にバイナリコードをr2に渡してやれば良いです。 今回も、以前作成したパスワード認証プログラムのバイナリコード"your_pass"を解析します。

$ r2 your_pass
 -- Stop debugging me!
[0x000005d0]>

これでradare2による解析が開始しました。 >のあとにコマンドを入力することで操作することができます。

ちなみに、radare2を開始したときに表示されたコメント-- Stop debugging me!は、毎回変わります。 例えば、

-- Did you know that r2 is 10 years old?
 -- Heisenbug: A bug that disappears or alters its behavior when one attempts to probe or isolate it
 -- Please register your copy of r2 today! Only £29.90!

などなど。製作者のユーモアが感じられて私は好きです。

radare2の使い方を調べる | ? - ヘルプ

新しいツールを使い始めるときは、何よりもまず"使い方の調べ方"を知ることが大切です。 radare2では、?でヘルプを見ることができます。

> ?
Usage: [.][times][cmd][~grep][@[@iter]addr!size][|>pipe] ; ...   
Append '?' to any char command to get detailed help
Prefix with number to repeat command N times (f.ex: 3x)
| %var=value              alias for 'env' command
| *[?] off[=[0x]value]    pointer read/write data/values (see ?v, wx, wv)
| (macro arg0 arg1)       manage scripting macros
| .[?] [-|(m)|f|!sh|cmd]  Define macro or load r2, cparse or rlang file
| _[?]                    Print last output
| =[?] [cmd]              send/listen for remote commands (rap://, http://, <fd>)
| <[...]                  push escaped string into the RCons.readChar buffer
| /[?]                    search for bytes, regexps, patterns, ..
| ![?] [cmd]              run given command as in system(3)
| #[?] !lang [..]         Hashbang to run an rlang script
| a[?]                    analysis commands
| b[?]                    display or change the block size
| c[?] [arg]              compare block with given data
~
#以下略

上の方にAppend '?' to any char command to get detailed helpとあるように、更に詳細なヘルプを見るにはコマンドに再度?を付ければいいようです。 例えば、コマンドaの次に何のコマンドを追加すればいいかを調べたければ、a?とすればaに付け加えるべきコマンドが表示されます。

このようにしてヘルプを活用すれば、コマンドの意味を調べることができるわけです。 例えばコマンドaaaの意味をヘルプから調べるなら、

>?
~
| a[?]                    analysis commands
~

>a?
~
| aa[?]              analyze all (fcns + bbs) (aa0 to avoid sub renaming)
~

>aa?
~
| aaa[?]              autoname functions after aa (see afna)
~

つまり、aaaは"autoname function"を"すべて" "解析"するコマンドである、ということになります。

ここまでは下準備です。 次はさっそく解析を始めていきます。

使用される関数を表示する | afl

aflコマンドを打つことで、解析しているバイナリコードで使用される関数のリストを表示させることができます。

その前にまずradare2を開始したら、私はaaaで関数を解析させるようにしています。

>aaa
[x] Analyze all flags starting with sym. and entry0 (aa)
[x] Analyze function calls (aac)
[x] Analyze len bytes of instructions for references (aar)
[x] Constructing a function name for fcn.* and sym.func.* functions (aan)
[x] Type matching analysis for all functions (afta)
[x] Use -AA or aaaa to perform additional experimental analysis.

そしたら、使用されている関数をaflで表示させます。

> afl
0x00000000    6 292  -> 318  sym.imp.__libc_start_main
0x00000560    3 23           sym._init
0x00000590    1 6            sym.imp.puts
0x000005a0    1 6            sym.imp.printf
0x000005b0    1 6            sym.imp.strcmp
0x000005c0    1 6            sub.__cxa_finalize_5c0
0x000005d0    1 43           entry0
0x00000600    4 50   -> 40   sym.deregister_tm_clones
0x00000640    4 66   -> 57   sym.register_tm_clones
0x00000690    5 58   -> 51   sym.__do_global_dtors_aux
0x000006d0    1 10           entry.init0
0x000006da    6 129          main
0x00000760    3 101  -> 92   sym.__libc_csu_init
0x000007d0    1 2            sym.__libc_csu_fini
0x000007d4    1 9            sym._fini

たくさんの面白そうな関数名が表示されました。 ここから、putsprintfstrcmp関数などが使われていることがわかるわけです。

逆アセンブル | pdf

つぎに、main関数の逆アセンブルを行います。 まずは、sコマンドでmain関数に移動します。

[0x000005d0]> s sym.main 
[0x000006da]> 

>の前の文字列[0x000005d0]は現在の位置を示しており、main関数の開始地点[0x000006da]に移動したことがわかります。 ここでpdfと打つことによって、逆アセンブルを行うことができます。

[0x000006da]> pdf
/ (fcn) main 129
|   main (int argc, char **argv, char **envp);
|           ; var char **s1 @ rbp-0x10
|           ; var unsigned int local_4h @ rbp-0x4
|           ; arg unsigned int argc @ rdi
|           ; arg char **argv @ rsi
|           ; DATA XREF from entry0 (0x5ed)
|           0x000006da      55             push rbp
|           0x000006db      4889e5         mov rbp, rsp
|           0x000006de      4883ec10       sub rsp, 0x10
|           0x000006e2      897dfc         mov dword [local_4h], edi   ; argc
|           0x000006e5      488975f0       mov qword [s1], rsi         ; argv
|           0x000006e9      837dfc02       cmp dword [local_4h], 2
|       ,=< 0x000006ed      7559           jne 0x748
|       |   0x000006ef      488b45f0       mov rax, qword [s1]
|       |   0x000006f3      4883c008       add rax, 8
|       |   0x000006f7      488b00         mov rax, qword [rax]
|       |   0x000006fa      4889c6         mov rsi, rax
|       |   0x000006fd      488d3de40000.  lea rdi, str.Checking_Your_Pass:__s ; 0x7e8 ; "Checking Your Pass: %s\n" ; const char *format
|       |   0x00000704      b800000000     mov eax, 0
|       |   0x00000709      e892feffff     call sym.imp.printf         ; int printf(const char *format)
|       |   0x0000070e      488b45f0       mov rax, qword [s1]
|       |   0x00000712      4883c008       add rax, 8
|       |   0x00000716      488b00         mov rax, qword [rax]
|       |   0x00000719      488d35e00000.  lea rsi, str.Antoine_Marie_Jean_Baptiste_Roger_comte_de_Saint_Exupery ; 0x800 ; "Antoine-Marie-Jean-Baptiste-Roger_comte-de-Saint-Exupery" ; const char *s2
|       |   0x00000720      4889c7         mov rdi, rax                ; const char *s1
|       |   0x00000723      e888feffff     call sym.imp.strcmp         ; int strcmp(const char *s1, const char *s2)
|       |   0x00000728      85c0           test eax, eax
|      ,==< 0x0000072a      750e           jne 0x73a
|      ||   0x0000072c      488d3d060100.  lea rdi, str.Hello__Master. ; 0x839 ; "Hello, Master." ; const char *s
|      ||   0x00000733      e858feffff     call sym.imp.puts           ; int puts(const char *s)
|     ,===< 0x00000738      eb1a           jmp 0x754
|     |||   ; CODE XREF from main (0x72a)
|     |`--> 0x0000073a      488d3d070100.  lea rdi, str.Access_Denied. ; 0x848 ; "Access Denied." ; const char *s
|     | |   0x00000741      e84afeffff     call sym.imp.puts           ; int puts(const char *s)
|     |,==< 0x00000746      eb0c           jmp 0x754
|     |||   ; CODE XREF from main (0x6ed)
|     ||`-> 0x00000748      488d3df90000.  lea rdi, str.Access_Denied. ; 0x848 ; "Access Denied." ; const char *s
|     ||    0x0000074f      e83cfeffff     call sym.imp.puts           ; int puts(const char *s)
|     ||    ; CODE XREFS from main (0x738, 0x746)
|     ``--> 0x00000754      b800000000     mov eax, 0
|           0x00000759      c9             leave
\           0x0000075a      c3             ret

プログラムの分岐を示す矢印や、ハードコードされた文字列( "Hello, Master." など)が表示されており、以前gdbで逆アセンブルしたときより圧倒的に多くの情報が得られました。

Visualモードでプログラムの流れを表示する | VV

Visualモードを使うことによって、プログラムの流れを表示させることができます。 Visualモードに移るにはVVと打ちます。

[0x000006da]> VV

すると、次のようなプログラムの流れが表示されます(Fig. 1)。

f:id:BioErrorLog:20190211135605p:plain
Fig. 1 Visualモードのスクリーンショット

Visualモードで実行できることは、"?"キーを押すことで調べることができます。 例えば"p"を押せば表示モードの変更、矢印キーで画面のスクロール、"tab"キーで選択ノード4の変更、などなど。 特に、色合いを変更できる"R"は面白い機能でした5

デバッグモードで解析する | -d / ood

radare2では、gdbのようにデバッグモードで解析を行うことができます。

改めてデバッグモードでradara2を起動するには、一旦"q"あるいは"^D"(Ctrl + d)を押してradare2を終了します。 そしたら、デバッグモードでradare2を起動するため、-dを渡してバイナリコード”your_pass”の解析を開始します。

$ r2 -d your_pass

あるいは、radare2上でコマンドoodを打てば、デバッグモードで開き直すことができます。

>ood

次に、gdbでデバッグするときと同じように、main関数の開始地点にブレークポイントを設置します。 main関数のアドレスは、上述したのと同じ手順で確認します。 しかしここで、aaaで解析すると何故かうまく行きませんでした。 代わりにaaで解析するとうまく行きました。 ここららへんの細かいところは正直まだ理解できていないので、今後扱い慣れていきたいものです。

>aa
~
>s sym.main
~
>pdf
/ (fcn) main 129
|   main (int argc, char **argv, char **envp);
|           ; var int local_10h @ rbp-0x10
|           ; var int local_4h @ rbp-0x4
|           ; arg int argc @ bp
|           ; arg char **argv @ r15b
|           ; DATA XREF from entry0 (0x55e86a7d05ed)
|           0x55e86a7d06da      55             push rbp
|           0x55e86a7d06db      4889e5         mov rbp, rsp
|           0x55e86a7d06de      4883ec10       sub rsp, 0x10
~
#以下略

main関数の先頭のアドレスが0x55e86a7d06daであることが分かりました。 それではdbコマンドでブレークポイントを設置します。

> db 0x55e86a7d06da

では、さっそくVVでVisualモードに移行し、デバッグを開始します。

Visualモードでは、vimのように” : ”を押すことでコマンドを受け付けるようになります。 " : "を押したあと、"dc"コマンドでプログラムを実行させます

:> dc
hit breakpoint at: 0x55e86a7d06da

ブレークポイントに到達したら、"Shift + s"で1ステップずつ実行していきます。 処理している現在のアドレスは;-- rip:6によって示されます(Fig. 2)。

f:id:BioErrorLog:20190211162736p:plain
Fig. 2 ";--rip:"が1ステップずつ進んでいく

細かい機能はもちろんたくさんありますが、とりあえず以上のようにして、デバッグを行うことができます。


おわりに

今回は、radare2の使い方を記録しました。

これまでgdbをつかって自力でやっていたことを自動化できるため、大変強力なツールだと感じました。 また、随所にみられる製作者の遊び心には趣深いものがあります。 ただ、挙動や機能に慣れていないせいか、直感では使いづらい面もありました。 ほかにも類似のツールはたくさんあるのでしょうが、それでもとりあえずはこのradare2で遊んでいきたいと思っています。

[関連記事]

www.bioerrorlog.work

www.bioerrorlog.work

www.bioerrorlog.work

参考

Simple Tools and Techniques for Reversing a binary - bin 0x06 - YouTube

メイク - Wikipedia


  1. cloneとは、製作者の公開するソフトウェア(正しくはリポジトリ)を自分のPCに複製することを言います。

  2. gitはバージョン管理システムです。GitHubはこのgitを利用して、ソフトウェア開発者が自分のソフトウェアを公開できる有名なプラットフォームです。

  3. こんなにもエラーが多かった理由は、新しく仮想環境で立ち上げた、生まれたてのUbuntuで再度インストールを行ったからです。ふつうはmakeやgccは既にインストールしたことがあるでしょうから、こんなに躓くことはないでしょう。

  4. 選択ノードは青枠で表示されます。

  5. 実に多様で美しい色彩が実装されています。製作者の遊び心でしょうか。

  6. “rip"は処理中のアドレスを指し示すレジスタの名前です。

ハードコードされたパスワードを読み出す | リバースエンジニアリング入門#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に多くを参考させていただきました。

レジスタ操作でアクセス承認を引き出す - gdb | リバースエンジニアリング入門#3

gdbを用いてプログラムの各ステップを解析・操作することで、アクセス承認を目指します。


はじめに

おはよう。@bioerrorlogです。

前回は、アセンブラコードを解析してプログラムの流れを把握しました。
www.bioerrorlog.work 具体的には、次のアセンブラコードから、全体の流れ図(Fig. 1)を書き出しました。

Dump of assembler code for function main:
   0x00000000000006da <+0>:  push   rbp
   0x00000000000006db <+1>:  mov    rbp,rsp
   0x00000000000006de <+4>:  sub    rsp,0x10
   0x00000000000006e2 <+8>:  mov    DWORD PTR [rbp-0x4],edi
   0x00000000000006e5 <+11>: mov    QWORD PTR [rbp-0x10],rsi
   0x00000000000006e9 <+15>: cmp    DWORD PTR [rbp-0x4],0x2
   0x00000000000006ed <+19>: jne    0x748 <main+110>
   0x00000000000006ef <+21>: mov    rax,QWORD PTR [rbp-0x10]
   0x00000000000006f3 <+25>: add    rax,0x8
   0x00000000000006f7 <+29>: mov    rax,QWORD PTR [rax]
   0x00000000000006fa <+32>: mov    rsi,rax
   0x00000000000006fd <+35>: lea    rdi,[rip+0xe4]        # 0x7e8
   0x0000000000000704 <+42>: mov    eax,0x0
   0x0000000000000709 <+47>: call   0x5a0 <printf@plt>
   0x000000000000070e <+52>: mov    rax,QWORD PTR [rbp-0x10]
   0x0000000000000712 <+56>: add    rax,0x8
   0x0000000000000716 <+60>: mov    rax,QWORD PTR [rax]
   0x0000000000000719 <+63>: lea    rsi,[rip+0xe0]        # 0x800
   0x0000000000000720 <+70>: mov    rdi,rax
   0x0000000000000723 <+73>: call   0x5b0 <strcmp@plt>
   0x0000000000000728 <+78>: test   eax,eax
   0x000000000000072a <+80>: jne    0x73a <main+96>
   0x000000000000072c <+82>: lea    rdi,[rip+0x107]        # 0x83a
   0x0000000000000733 <+89>: call   0x590 <puts@plt>
   0x0000000000000738 <+94>: jmp    0x754 <main+122>
   0x000000000000073a <+96>: lea    rdi,[rip+0x108]        # 0x849
   0x0000000000000741 <+103>:    call   0x590 <puts@plt>
   0x0000000000000746 <+108>:    jmp    0x754 <main+122>
   0x0000000000000748 <+110>:    lea    rdi,[rip+0xfa]        # 0x849
   0x000000000000074f <+117>:    call   0x590 <puts@plt>
   0x0000000000000754 <+122>:    mov    eax,0x0
   0x0000000000000759 <+127>:    leave  
   0x000000000000075a <+128>:    ret    
End of assembler dump.

f:id:BioErrorLog:20190202175344j:plain
Fig. 1 アセンブラコードから解析したプログラムの流れ
今回は、gdbのデバッグ機能を利用してそれぞれのステップを解析し、パスワード認証過程の特定と迂回を目指します。


作業環境

Ubuntu18.04.1 LTS を
Windows10の上に、VMwareによって構築した仮想環境で起動しています。
www.bioerrorlog.work


レジスタ操作でアクセス承認を引き出す

プログラムの実行 | 第1分岐 - ハズレルート

プログラムを実行させ、ステップごとの挙動を解析することで、より詳細な情報を得ることができます。

main関数にブレークポイント1を設置し、まずは引数を与えずにプログラムを実行してみます。

(gdb) break *main
Breakpoint 1 at 0x6da
(gdb) run
Starting program: /home/user/your_pass 

Breakpoint 1, 0x00005555555546da in main ()

main関数の開始点0x6daにブレークポイントが設置されたことによって、プログラムを実行runした際にmain関数で処理が一時停止している状態です。

ここでよくわからないのは、ブレークポイントを設置したときのアドレスは0x6da、同様にアセンブラコードで表示されるmain関数の開始点は0x00000000000006daなのに、runしたときのブレークポイントは0x00005555555546daを指していることです。 この555555554という数字は何を意味しているのでしょうか。 "gdb 555555554"などで検索すると、やはりgdbのブレークポイント関連でこの"555555554"というアドレスが登場しています。 とても気になるところですが、ひとまずこの数字は置いておいて、下三桁6daのみに着目して進めていきたいと思います。

それでは、niコマンド2を用いて、プログラムを一行ずつ実行していきます。

(gdb) ni
0x00005555555546db in main ()
(gdb) 
0x00005555555546de in main ()
(gdb) 
0x00005555555546e2 in main ()
(gdb) 
0x00005555555546e5 in main ()
(gdb) 
0x00005555555546e9 in main ()
(gdb) 
0x00005555555546ed in main ()

アドレス6edまで来ました3。 ここは、プログラムの流れ図でみると、最初の分岐点であることがわかります(Fig. 2, 赤矢頭)。

f:id:BioErrorLog:20190202175653j:plain
Fig. 2 第1分岐点
さて、どちらの分岐ルートに行くのでしょうか。 niコマンドで先へ進むと、

(gdb) ni
0x0000555555554748 in main ()

アドレス748、つまり左の分岐ルートに行ったことがわかります。 これは、アドレス6e9において、ある変数と0x2を比較cmpした結果が"not equal"だったため、アドレス748jne(jump not equal)した、ということを意味しています。

このままniコマンドで先へ行くと、

(gdb) ni
0x000055555555474f in main ()
(gdb) 
Access Denied.
0x0000555555554754 in main ()
(gdb) 
0x0000555555554759 in main ()
(gdb) 
0x000055555555475a in main ()

アドレス74f<puts>関数4によってAccess Denied.の文字列が表示された後、main関数の終了点75aに至りました。 つまり、このルートはアクセスが拒否されてしまうハズレルートであるということです。

ここまで、引数を何も与えずにプログラムを実行してきました。 次は、引数を与えて実行してみます。

プログラムの実行 | 第1分岐 - 当たりルート

それでは、引数wrong-passwordを与えて、同様の解析を行います。

(gdb) run wrong-password
Starting program: /home/user/your_pass wrong-password

Breakpoint 1, 0x00005555555546da in main ()
(gdb) ni
0x00005555555546db in main ()
(gdb) 
0x00005555555546de in main ()
(gdb) 
0x00005555555546e2 in main ()
(gdb) 
0x00005555555546e5 in main ()
(gdb) 
0x00005555555546e9 in main ()
(gdb) 
0x00005555555546ed in main ()

分岐点6edにやってきました。 niコマンドで先へ進むと、

(gdb) 
0x00005555555546ef in main ()

先程と違い、アドレス748へ飛ばず、6efへ行きました。 つまり今回は、流れ図での右側ルート、おそらく当たりルートに行けたということです。 お見事。

プログラムの実行 | 第2分岐 - ハズレルート

それでは引き続き、niコマンドで最後まで進んでみます。

~
(gdb) 
0x0000555555554709 in main ()
(gdb) 
Checking Your Pass: wrong-password
~
(gdb) 
0x000055555555472a in main ()
(gdb) 
0x000055555555473a in main ()
(gdb) 
0x0000555555554741 in main ()
(gdb) 
Access Denied.
~

709printf関数ではChecking Your Pass: wrong-passwordが表示され、その後の分岐点72aでは左側のルートに行き、741puts関数ではAccess Denied.が表示されました(Fig. 3)。 つまり、この左ルートはハズレです。

f:id:BioErrorLog:20190202182709j:plain
Fig. 3 第2分岐 左ルートはハズレ

プログラムの実行 | 第2分岐 - アクセス承認ルート

残るは、第2分岐の右ルートです。 おそらく、ここがアクセス承認のルートでしょう。
そこで、レジスタの値を操作することによって、このアクセス承認ルートへの突入を目指します。

分岐点0x72a jneは、直前の判定を受けてルートの分岐を決定しています。 改めてアセンブラコードからこの辺りを読むと、

~
   0x0000555555554723 <+73>: call   0x5555555545b0 <strcmp@plt>
   0x0000555555554728 <+78>: test   eax,eax
   0x000055555555472a <+80>: jne    0x55555555473a <main+96>
~

ここから読み取れる条件判定の流れは、次のようになります。

strcmpで入力パスワードと正規パスワードを比較し、eaxに結果を格納

testeaxの値をチェック

testの結果に応じてjne

下から順に詳細を考えていきます。
jneは、"zero flag" ZF==0のときにジャンプするコードです5。 そしてtestは、2つの変数のAND演算の結果を受けて、ZFを設定しています6。 今回の場合eaxeaxのAND演算を行っているので、eax==0のときのみ、ZFに1が格納されます。 つまり、jneによってジャンプせず、右の分岐ルートに行くには、eaxに0が格納されている必要があるわけです。

しかし、eax==0となるのは、strcmpが入力パスワードと正規パスワードを同一であるとみなした時だけです7。 正規パスワードがわからない以上、正面からのやり方では突破できません。

そこで、直接eaxの値を操作することによって、アクセス承認を目指します。 eaxは、レジスタraxの下位 32 ビットを指しています8。 そこで、testが行われるアドレス728で、raxレジスタを確認してみました。 ブレークポイントを0x0000555555554728に設定してcontinueコマンドを実行することで、一つずつniせずともアドレス728に行くことができます。 そこでレジスタを見ると、

Breakpoint 2, 0x0000555555554728 in main ()
(gdb) info registers
rax            0xfffffff0  4294967280
rbx            0x0 0
rcx            0x0 0
rdx            0x41    65
rsi            0x555555554800  93824992233472
rdi            0x7fffffffe30e  140737488347918
rbp            0x7fffffffde80  0x7fffffffde80
rsp            0x7fffffffde70  0x7fffffffde70
r8             0x0 0
r9             0x3 3
r10            0xfffffffd  4294967293
r11            0x246   582
r12            0x5555555545d0  93824992232912
r13            0x7fffffffdf60  140737488346976
r14            0x0 0
r15            0x0 0
rip            0x555555554728  0x555555554728 <main+78>
eflags         0x287   [ CF PF SF IF ]
cs             0x33    51
ss             0x2b    43
ds             0x0 0
es             0x0 0
fs             0x0 0
gs             0x0 0

raxには0xfffffff0が格納されていました。 それではここでeaxレジスタを操作して、0を格納します。 目論見が正しければ、それでアクセス承認が引き出せるはずです。

(gdb) set $eax=0

これでeaxに0を格納しました。 レジスタを確認すると、

(gdb) info registers
rax            0x0 0
~

確かにraxの値が0になっています。
それでは、このままniで先へ進むと、

(gdb) ni
0x000055555555472a in main ()
(gdb) 
0x000055555555472c in main ()
(gdb) 
0x0000555555554733 in main ()
(gdb) 
Hello, Master.
0x0000555555554738 in main ()

すばらしい。
見事、右の分岐ルートに入り、アクセス承認Hello, Master.を表示させることができました。


おわりに

今回は、gdbを用いてプログラムを実行させ、ルート分岐条件を解析することで、最終的にアクセス承認を引き出すことができました。

今回解析でわかったことを書き出すと、次のようになります。

f:id:BioErrorLog:20190202213942j:plain
Fig. 4 プログラムの解析結果

細かくプログラムを解析してきたので、だいぶプログラミングの仕組みといったものもわかってきました。 なかなか複雑で面白いものです。

ただし、アクセス承認を引き出す方法は、他にもたくさんあります。 というよりも、もっとずっと簡単な方法が何個もあります。 次回は、それをまとめたいと思います。

なお、今回もLiveOverflow氏の動画9を参考にしました。

[関連記事]
www.bioerrorlog.work

www.bioerrorlog.work


  1. ブレークポイントとは、その名の通り"一時停止"する場所です。runコマンドによってプログラムを実行した時、ブレークポイントを設置していればそこで立ち止まってくれます。逆に、ブレークポイントを設置せずに実行すると、最後まで一気に実行されてしまいます。

  2. niとよく似たコマンドに、siがあります。niは関数内部に入らずに実行し、siは関数内部に入り込んで実行します。今回はmain関数の流れのみに興味があるので、関数内部に入り込まないniコマンドを使っています。詳しくは別途記事にまとめる予定です。

  3. 同じコマンドを繰り返すときは、そのままEnterキーを押します。

  4. puts関数とは、マニュアルによるとputs - output of characters and strings、つまりprintf関数と同じようなものでしょう。

  5. http://dukedog.webcrow.jp/CheatEngineHelp/asm-basics-3.htm

  6. TEST (x86 instruction) - Wikipedia

  7. strcmpのマニュアルによると、It returns an integer less than, equal to, or greater than zero if s1 is found, respectively, to be less than, to match, or be greater than s2.つまりstrcmpは、比較する2つの変数が等しい時、0を返します。

  8. x64 アセンブリーの概要 | iSUS

  9. Reversing and Cracking first simple Program - bin 0x05 - YouTube

逆アセンブル解析 - gdb | リバースエンジニアリング入門#2

gdbを用いてバイナリコードを逆アセンブル (ディスアセンブル)し、Cで書かれたプログラムの全体像を解析します。


はじめに

おはよう。@bioerrorlogです。

パスワード認証プログラムをリバースエンジニアリングして、アクセス承認を引き出したいシリーズpart2です。

前回は、C言語で簡単なパスワード認証プログラムを作成しました。
www.bioerrorlog.work

今回はいよいよ、GNUデバッガgdbを用いて、パスワード認証プログラムを逆アセンブルしていきます。

wikipediaによると1、逆アセンブラとは次のようなものを指します。

実行ファイルないしオブジェクトファイルの機械語コード(とシンボルテーブルなどの付随情報)を基に、アセンブリ言語ソースコードを生成する、すなわちアセンブラの逆の作用をするものを特に指す。

Cコードでは、コンパイルによってバイナリコード(機械語コード)を生成しました。 逆アセンブラはそれとは逆方向に、バイナリコードからアセンブラコードを生成するわけです。
ただし、アセンブリ言語はC言語などよりも機械語よりの低水準言語ですので、入念な読み込みが必要です。

それでは、前回作成したソースコードについての記憶を一旦忘れ去り、バイナリコード"your_pass"のみを与えられたと想定して、逆アセンブル解析していきます。

なお、前回に引き続きLiveOverflow氏の動画2を参考にしています。


作業環境

Ubuntu18.04.1 LTS を
Windows10の上に、VMwareによって構築した仮想環境で起動しています。
www.bioerrorlog.work


逆アセンブル解析

gdbとは

gdbとは何たるかを知るため、まずはマニュアルを読んで概要を把握します。

$ man gdb
NAME
       gdb - The GNU Debugger
~
DESCRIPTION
       The purpose of a debugger such as GDB is to allow you to see what is going on "inside"
       another program while it executes -- or what another program was doing at the moment it
       crashed.

GNUデバッガgdbとは、実行中あるいはクラッシュしたプログラムの"中身"をみるためのもの、だそうです。

実際にgdbができることとは、

       ·   Start your program, specifying anything that might affect its behavior.

       ·   Make your program stop on specified conditions.

       ·   Examine what has happened, when your program has stopped.

       ·   Change things in your program, so you can experiment with correcting the effects of
           one bug and go on to learn about another.

意訳すると、
・プログラムを開始して、挙動に影響を与えるものを特定する。
・特定の条件で、プログラムを停止させる。
・プログラムが停止したとき、何が起こったのか確かめる。
・プログラムに変更を加えることで、バグを一つずつ検証できる。
といったことができるそうです。

なんとなく言わんとしてることはわかりますが、手を動かしてみないと正直イメージは湧いてきません。

アセンブラコードを表示する | (gdb) disassemble

それでは、gdbを使っていきます。
gdbにバイナリコード"your_pass"を渡し、解析を開始します。

$ gdb your_pass

これでgdbデバッガが開始されました。
さっそくmain関数を逆アセンブルします。

(gdb) disassemble main
Dump of assembler code for function main:
   0x00000000000006da <+0>:  push   %rbp
   0x00000000000006db <+1>:  mov    %rsp,%rbp
   0x00000000000006de <+4>:  sub    $0x10,%rsp
   0x00000000000006e2 <+8>:  mov    %edi,-0x4(%rbp)
   0x00000000000006e5 <+11>: mov    %rsi,-0x10(%rbp)
   0x00000000000006e9 <+15>: cmpl   $0x2,-0x4(%rbp)
   0x00000000000006ed <+19>: jne    0x748 <main+110>
   0x00000000000006ef <+21>: mov    -0x10(%rbp),%rax
   0x00000000000006f3 <+25>: add    $0x8,%rax
   0x00000000000006f7 <+29>: mov    (%rax),%rax
   0x00000000000006fa <+32>: mov    %rax,%rsi
   0x00000000000006fd <+35>: lea    0xe4(%rip),%rdi        # 0x7e8
   0x0000000000000704 <+42>: mov    $0x0,%eax
   0x0000000000000709 <+47>: callq  0x5a0 <printf@plt>
   0x000000000000070e <+52>: mov    -0x10(%rbp),%rax
   0x0000000000000712 <+56>: add    $0x8,%rax
   0x0000000000000716 <+60>: mov    (%rax),%rax
   0x0000000000000719 <+63>: lea    0xe0(%rip),%rsi        # 0x800
   0x0000000000000720 <+70>: mov    %rax,%rdi
   0x0000000000000723 <+73>: callq  0x5b0 <strcmp@plt>
   0x0000000000000728 <+78>: test   %eax,%eax
   0x000000000000072a <+80>: jne    0x73a <main+96>
   0x000000000000072c <+82>: lea    0x107(%rip),%rdi        # 0x83a
   0x0000000000000733 <+89>: callq  0x590 <puts@plt>
   0x0000000000000738 <+94>: jmp    0x754 <main+122>
   0x000000000000073a <+96>: lea    0x108(%rip),%rdi        # 0x849
   0x0000000000000741 <+103>:    callq  0x590 <puts@plt>
   0x0000000000000746 <+108>:    jmp    0x754 <main+122>
   0x0000000000000748 <+110>:    lea    0xfa(%rip),%rdi        # 0x849
   0x000000000000074f <+117>:    callq  0x590 <puts@plt>
   0x0000000000000754 <+122>:    mov    $0x0,%eax
   0x0000000000000759 <+127>:    leaveq 
   0x000000000000075a <+128>:    retq   
End of assembler dump.

こいつは面白そうな文字列が表示されました。
一行目には Dump of assembler code for function main:とあります。 つまり、main関数のアセンブラコードが表示されている3わけです。

ちなみに、アセンブリ言語の表示形式は、AT&Tとintelの2つの形式があります4。 gdbのデフォルトの表示方法はAT&Tであり、ご覧のようにすごく読みにくいものです。 これをintel方式にセットします。

(gdb) set disassembly-flavor intel

これで再び逆アセンブルすると、

Dump of assembler code for function main:
   0x00000000000006da <+0>:  push   rbp
   0x00000000000006db <+1>:  mov    rbp,rsp
   0x00000000000006de <+4>:  sub    rsp,0x10
   0x00000000000006e2 <+8>:  mov    DWORD PTR [rbp-0x4],edi
   0x00000000000006e5 <+11>: mov    QWORD PTR [rbp-0x10],rsi
   0x00000000000006e9 <+15>: cmp    DWORD PTR [rbp-0x4],0x2
   0x00000000000006ed <+19>: jne    0x748 <main+110>
   0x00000000000006ef <+21>: mov    rax,QWORD PTR [rbp-0x10]
   0x00000000000006f3 <+25>: add    rax,0x8
   0x00000000000006f7 <+29>: mov    rax,QWORD PTR [rax]
   0x00000000000006fa <+32>: mov    rsi,rax
   0x00000000000006fd <+35>: lea    rdi,[rip+0xe4]        # 0x7e8
   0x0000000000000704 <+42>: mov    eax,0x0
   0x0000000000000709 <+47>: call   0x5a0 <printf@plt>
   0x000000000000070e <+52>: mov    rax,QWORD PTR [rbp-0x10]
   0x0000000000000712 <+56>: add    rax,0x8
   0x0000000000000716 <+60>: mov    rax,QWORD PTR [rax]
   0x0000000000000719 <+63>: lea    rsi,[rip+0xe0]        # 0x800
   0x0000000000000720 <+70>: mov    rdi,rax
   0x0000000000000723 <+73>: call   0x5b0 <strcmp@plt>
   0x0000000000000728 <+78>: test   eax,eax
   0x000000000000072a <+80>: jne    0x73a <main+96>
   0x000000000000072c <+82>: lea    rdi,[rip+0x107]        # 0x83a
   0x0000000000000733 <+89>: call   0x590 <puts@plt>
   0x0000000000000738 <+94>: jmp    0x754 <main+122>
   0x000000000000073a <+96>: lea    rdi,[rip+0x108]        # 0x849
   0x0000000000000741 <+103>:    call   0x590 <puts@plt>
   0x0000000000000746 <+108>:    jmp    0x754 <main+122>
   0x0000000000000748 <+110>:    lea    rdi,[rip+0xfa]        # 0x849
   0x000000000000074f <+117>:    call   0x590 <puts@plt>
   0x0000000000000754 <+122>:    mov    eax,0x0
   0x0000000000000759 <+127>:    leave  
   0x000000000000075a <+128>:    ret    
End of assembler dump.

右の部分がだいぶスッキリしました。


[関連記事] radare2の使い方 | リバースエンジニアリング入門#5


アセンブラコードの流れを解析する

私のひとまずの目的は、上のアセンブラコードの全体的な流れを理解することです。 そのためには、ほとんどの部分は無視し、論理構造の上で重要な部分だけ読んでいきます。
その部分を抜き出すと、次のようになります。

~
   0x00000000000006e9 <+15>: cmp    DWORD PTR [rbp-0x4],0x2
   0x00000000000006ed <+19>: jne    0x748 <main+110>
~
   0x0000000000000709 <+47>: call   0x5a0 <printf@plt>
~
   0x0000000000000723 <+73>: call   0x5b0 <strcmp@plt>
   0x0000000000000728 <+78>: test   eax,eax
   0x000000000000072a <+80>: jne    0x73a <main+96>
~
   0x0000000000000733 <+89>: call   0x590 <puts@plt>
   0x0000000000000738 <+94>: jmp    0x754 <main+122>
~
   0x0000000000000741 <+103>:    call   0x590 <puts@plt>
   0x0000000000000746 <+108>:    jmp    0x754 <main+122>
~
   0x000000000000074f <+117>:    call   0x590 <puts@plt>
~

着目したのは、callと、jne(jmp)です。
callは、関数の呼び出しを行います5jneは”jump not equal”で、直前の判定(cmptest)にしたがった処理の分岐をしています。 普通のプログラム言語で言うところのif分岐のようなものでしょう。 jmpはそのまま"jump"で、条件による判定なしにjumpします。

さっそく、上から読み解いていきます。

~
   0x00000000000006e9 <+15>: cmp    DWORD PTR [rbp-0x4],0x2
   0x00000000000006ed <+19>: jne    0x748 <main+110>
~

最初のこの塊は、未知の文字列(DWORD PTR [rbp-0x4])を、2(0x2)6と比較(cmp)しています。 そしてその結果を受けた上で、アドレス0x748にジャンプ(jne)しています。 アドレス0x748の先を読んでみると、

   0x0000000000000748 <+110>:    lea    rdi,[rip+0xfa]        # 0x849
   0x000000000000074f <+117>:    call   0x590 <puts@plt>
   0x0000000000000754 <+122>:    mov    eax,0x0
   0x0000000000000759 <+127>:    leave  
   0x000000000000075a <+128>:    ret    

着目すべきはアドレス0x74fのみです。 ここでcall 0x590 <puts@plt>を実行した後、終了(ret)しています。 つまり、

   0x6e9:   cmp    ?,0x2
   0x6ed:   jne    0x748
            ↓
            ↓
   0x748:
   0x74f:   call   0x590 <puts@plt>
   おわり

これで一つの流れが分かりました。

こんな感じで、その他の分岐もすべて解析すると、次のようなプログラムの全体像が浮かび上がります(Fig. 1)。

f:id:BioErrorLog:20190202175344j:plain
Fig. 1 アセンブラコードから解析したプログラムの流れ


おわりに

今回は、gdbを用いてバイナリコードを逆アセンブルし、プログラムの流れを解析しました。 いよいよプログラムの裏の仕組みが見えてきたようで、面白くなってきました。

次回はもっと詳細の機能を解析し、パスワードが認証されるステップの特定を目指します。

[関連記事]

www.bioerrorlog.work

www.bioerrorlog.work


  1. 逆アセンブラ - Wikipedia

  2. Reversing and Cracking first simple Program - bin 0x05 - YouTube

  3. “Dump"とは、dump (program) - Wikipedia

  4. AT&Tとintelの違いはhttps://imada.sdu.dk/Employees/kslarsen-bak/Courses/dm18-2007-spring/Litteratur/IntelnATT.htmなどが詳しい。

  5. CPUがどのようにして関数を呼び出しているのか、あるいはアセンブラについての詳しい説明はHow a CPU works and Introduction to Assembler - bin 0x04 - YouTube

  6. “0x2"のように、頭に"0x"がつく数字は16進数を表します。

Linuxコマンドまとめ | 使用例とマニュアル

おはよう。@bioerrorlogです。

Linux基本コマンドを一覧でまとめていきます。

ネット上には良質なLinuxコマンドのまとめがたくさんありますが1、自分が覚えておきたいコマンドの一覧は、やはり自分で作るしかありません。

備忘録としてまとめます。



基本コマンド

man

マニュアルを表示します。

使用例:

$ man ls

これでlsコマンドのマニュアルが表示されます。

詳しくは別途記事にしました。
www.bioerrorlog.work

manコマンドのマニュアル: man(1) - Arch manual pages


cd

ディレクトリを移動します。

使用例#1:

$ cd ./Desktop

"./"は相対パスで、カレントディレクトリを示します。
つまりこの例では、カレントディレクトリ直下にある"Desktop"ディレクトリに移動します。

使用例#2:

$ cd

引数を与えないと、ホームディレクトリに戻ります。

使用例#3:

$ cd ../

"../"は相対パスで、一つ上の親ディレクトリを示します。
よって"../"を引数に渡すと、一つ上のディレクトリに移動します。

使用例#4:

$ cd /

"/"を引数に渡すと、ルートディレクトリに移動します。

cdコマンドのマニュアル: cd(1p) - Arch manual pages


ls

ディレクトリに含まれるファイルを表示します。

使用例#1:

$ ls

これでカレントディレクトリにあるファイルが列挙されます。
カレントディレクトリ以外のファイルは、パスを引数に渡すことで調べることができます。

使用例#2:

$ ls -l

長いリスト形式でファイルが表示され、より多くの情報が得られます。
ここで得られるパーミッション情報については、別途記事にしました。
www.bioerrorlog.work

使用例#3:

$ ls -a

隠しファイルを含め、ディレクトリ下にあるすべてのファイルを表示します。

なお、上記のオプションは ls -la のように組み合わせることも可能です。

lsコマンドのマニュアル: ls(1) - Arch manual pages


cat

ファイルの中身を表示します。

使用例#1:

$ cat test.txt

カレントディレクトリ直下のファイル名(test.txt)を指定した場合、ファイルの中身が表示されます。
カレントディレクトリ以外のファイルは、パスを指定することで中身を見ることができます。

catコマンドのマニュアル: cat(1) - Arch manual pages


echo

文字列を表示します。

使用例:

$ echo Hello, Lain.
Hello, Lain.

文字列Hello, Lain.が出力されました。

echoコマンドのマニュアル: echo(1) - Arch manual pages


pwd

カレントディレクトリを表示します。

使用例:

$ pwd

例えば /home/user/Desktop のように、カレントディレクトリが表示されます。

pwdコマンドのマニュアル: pwd(1) - Arch manual pages


touch

新規ファイルを作成します。

使用例:

$ touch test.txt

これで、空のファイル"test.txt"が作成されます。

touchコマンドのマニュアル: touch(1) - Arch manual pages


mkdir

新規ディレクトリを作成します。

使用例:

$ mkdir testdir

これで、空のディレクトリ"testdir"が作成されます。

mkdirコマンドのマニュアル: mkdir(1) - Arch manual pages

rm

ファイルやディレクトリを削除します。

使用例#1:

$ rm test.txt

ファイル(test.txt)が削除されます。

使用例#2:

$ rm -r test
#testはディレクトリ

ディレクトリ(test)が削除されます。

rmコマンドのマニュアル: rm(1) - Arch manual pages


入出力制御

" | " パイプ

|の左側のコマンドの出力を、|の右側のコマンドの入力として渡します。

使用例:

$ man ls | grep all
       Sort entries alphabetically if none of -cftuvSUX nor --sort  is  speci‐
       -a, --all
       -A, --almost-all
              print the allocated size of each file, in blocks
       -X     sort alphabetically by entry extension
       Written by Richard M. Stallman and David MacKenzie.
       or available locally via: info '(coreutils) ls invocation'

man lsコマンドの結果を、grep allコマンドに渡したことで、lsコマンドマニュアルから"all"という文字列を含む列が検索されました。


" > " リダイレクト

>の左側の出力を、>の右側に渡します。

使用例:

$ echo Hello, Lain. > hello.txt
#hello.txt
Hello, Lain.

Hello, Lain.という文字列が、hello.txtファイルとして出力されました。


ファイル分析

file

ファイルの形式を表示します。

使用例:

$ file your_pass
your_pass: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 3.2.0, BuildID[sha1]=92e8f5e9bc90478dfdcb5fc4bd6c63f414ed2f6e, not stripped

"your_pass"という名前のファイルの形式が表示されました。 たとえば、実行ファイル形式ELF 64-bitであることなどがわかります。

fileコマンドのマニュアル: file(1) - Arch manual pages


tree

ディレクトリ内容を木構造で表示します。

使用例:

$ tree radare2
radare2
├── AUTHORS.md
├── autogen.sh
├── binr
│   ├── blob
│   │   ├── main.c
│   │   ├── Makefile
│   │   └── version.c
│   ├── Makefile
│   ├── preload
│   │   ├── alloc.c
│   │   ├── libr2.c
│   │   ├── Makefile
│   │   ├── trap-darwin-x86-32.asm
│   │   ├── trap-darwin-x86-64.asm
│   │   ├── trap-linux-x86-32.asm
│   │   └── trap-linux-x86-64.asm
│   ├── r2agent
│   │   ├── index.h
│   │   ├── Makefile
│   │   ├── meson.build
│   │   ├── r2agent
│   │   ├── r2agent.c
│   │   ├── r2agent.d
│   │   └── r2agent.o
│   ├── r2bb
│   │   ├── Makefile
│   │   └── r2bb.c
│   ├── r2pm
│   │   ├── meson.build
│   │   └── r2pm
#以下略

radare2ディレクトリの構造が視覚的に分かりやすく表示されました。

なお、デフォルトではインストールされていないので、自分でインストールする必要があります。

$ sudo apt install tree


オプション-Lで、表示する階層を指定することが出来ます。

$ tree -L 3


オプション-aで、すべてのファイルを表示します。

$ tree -a


treeコマンドのマニュアル: tree(1) - Arch manual pages


ファイル検索系

locate

ファイルを名前で検索します。

使用例:

$ locate gdbinit
/etc/gdb/gdbinit
/home/nori/.gdbinit
/usr/share/gdb/system-gdbinit
/usr/share/gdb/system-gdbinit/elinos.py
/usr/share/gdb/system-gdbinit/wrs-linux.py

"gdbinit"を含むファイルを検索し、その結果が出力されました。

※findコマンドとの違い:
locateはupdatedbによって予め作成されたデータベースから検索されます。 findはリアルタイムにファイルが検索されます。 個人的には、findコマンドの挙動がいまいち把握できていないので、locateコマンドを使っています。

locateコマンドのマニュアル: locate(1) - Arch manual pages


grep

与えたパターンにマッチした行を出力します。

使用例:

$ man ls | grep help
       --help display this help and exit
       GNU coreutils online help: <http://www.gnu.org/software/coreutils/>

lsコマンドのマニュアルのなかで、"help"という文字列がある行が2つ抽出されました。
あらゆるコマンドの後に| grep <key>と付け加えて検索できるので、大変重宝しています。

grepを使ってある文字列を含むファイルを検索する方法は、以下の記事に別途まとめています: www.bioerrorlog.work

grepコマンドのマニュアル: grep(1) - Arch manual pages

whereis

コマンドのバイナリファイル、ソースファイル、マニュアルページファイルのパスを表示します。

使用例:

$ whereis ls
ls: /bin/ls /usr/share/man/man1/ls.1.gz

lsコマンドを渡したことで、lsコマンドのバイナリファイルとマニュアルページファイルのパス
ls: /bin/ls /usr/share/man/man1/ls.1.gz
が表示されました。

whreisコマンドのマニュアル: whereis(1) - Arch manual pages


which

コマンドのパスを表示します。

使用例:

$ which ls
/bin/ls

whichコマンドのマニュアル: which(1) - Arch manual pages


OS関連

gnome-system-monitor | システムモニターの起動

システムモニター(Windowsのタスクマネージャーのようなもの)を起動します。

使用例:

$ gnome-system-monitor

システムモニターのヘルプ: System Monitor - GNOME Library


free

メモリの使用状況を表示します。

使用例#1:

$ free

freeコマンドを実行すると、次のようにメモリ使用量が表示されます。

              total        used        free      shared  buff/cache   available
Mem:        5934616      962216     3785240        3132     1187160     4686364
Swap:        969960           0      969960

メモリとスワップ領域について、使用中・未使用のメモリサイズが表示されます。

使用例#2:

$ free -h

"-h"オプションをつけると見やすくなります。

              total        used        free      shared  buff/cache   available
Mem:           5.7G        972M        3.4G        5.1M        1.3G        4.4G
Swap:          947M          0B        947M

freeコマンドのマニュアル: free(1) - Arch manual pages


df

ディスクの使用状況を表示します。

使用例:

$ df -h
Filesystem      Size  Used Avail Use% Mounted on
udev            2.9G     0  2.9G   0% /dev
tmpfs           580M  1.8M  578M   1% /run
/dev/sda1        20G  7.3G   12G  40% /
tmpfs           2.9G  4.0K  2.9G   1% /dev/shm
~
#以下略


dfコマンドのマニュアル: df(1) -Arch manual pages


ps

処理しているプロセスの状況を表示します。

使用例#1:

$ ps
   PID TTY          TIME CMD
  1769 pts/0    00:00:00 bash
  4424 pts/0    00:00:00 ps

このPID, TTY, TIME, CMDは、psコマンドのマニュアルによると、

PID: the process ID (プロセスID)
TTY: the terminal associated with the process (プロセスを行っているターミナル)
TIME: the cumulated CPU time (累積CPU使用時間)
CMD: the executable name (実行コマンド)

(括弧内は私の意訳)
ということでした。

使用例#2:

$ ps aux

プロセスに関して、すべての情報が表示されます。

psコマンドのマニュアル: ps(1) - Arch manual pages

uname

OSの情報を表示します。

使用例:

$ uname -a
Linux my-virtual-machine 4.15.0-43-generic #46-Ubuntu SMP Thu Dec 6 14:45:28 UTC 2018 x86_64 x86_64 x86_64 GNU/Linux

64bitなのか、32bitなのか等も、ここで表示されています。

unameコマンドのマニュアル: uname(1) - Arch manual pages


Web関連

curl

URLからデータを移行します。

使用例:

$ curl "https://bootstrap.pypa.io/get-pip.py" -o "get-pip.py"

Pythonのpipパッケージをインストールする際に行った操作。 URL"https://bootstrap.pypa.io/get-pip.py"のデータを取得し、"get-pip.py"として出力されました。

curlコマンドのマニュアル: curl(1) - Arch manual pages




  1. 例えばCore utilities - ArchWikiは、とてもお世話になっている記事です。

パスワード認証プログラムの作成 | リバースエンジニアリング入門#1

パスワード認証プログラムを作成し、それを自らリバースエンジニアリングしてアクセス承認を引き出したいシリーズのpart1です。

今回は下準備として、今後解析していくための簡単なパスワード認証プログラムを作成しました。


はじめに

おはよう。@bioerrorlogです。

プログラミングの勉強をしたい、という声をよく目にします。

もちろん、私もプログラミングを身につけたいと思っています。
しかし、よくある「プログラミング学習」類のものではあまり興奮が得られなかった1ので、気分転換を図ろうと思いました。

そこで、プログラミングの知識もろくにない私ですが、リバースエンジニアリングなるものに挑戦することにしました。
wikipediaによると、リバースエンジニアリングとは2

リバースエンジニアリング(Reverse engineeringから。直訳すれば逆行工学という意味)とは、機械を分解したり、製品の動作を観察したり、ソフトウェアの動作を解析するなどして、製品の構造を分析し、そこから製造方法や動作原理、設計図などの仕様やソースコードなどを調査することを指す。

今回の私の場合、ソフトウェアの動作を解析して、仕様を調査すること、ということになるでしょう。
こいつは面白そうです。

まずは、LiveOverflow氏の動画3を参考にしながら、自分で改変を加えつつ、流れを再現することを目指します。
今回は下準備として、これから使うパスワード認証プログラムを作成するところまで記しました。


作業環境

Ubuntu18.04.1 LTS を
Windows10の上に、VMwareによって構築した仮想環境で起動しています。
www.bioerrorlog.work


パスワード認証プログラムの作成

まずは、パスワード認証プログラムを作成しました。
後にバイナリコードを解析して、逆アセンブルするためのものです。
自分で作ったパスワード認証プログラムを自分で暴く、というのはなんとも滑稽なことですが、初心者の私にはもってこいの方法でしょう。

LiveOverflow氏のコード4を参考に、C言語で次のコード"your_pass.c"を書きました。

#include <string.h>
#include <stdio.h>

int main(int argc, char *argv[]) {
        if(argc==2) {
                printf("Checking Your Pass: %s\n", argv[1]);
                if(strcmp(argv[1], "Antoine-Marie-Jean-Baptiste-Roger_comte-de-Saint-Exupery")==0) {
                        printf("Hello, Master.\n");
                } else {
                        printf("Access Denied.\n");
                }

        } else {
                printf("Access Denied.\n");
        }
        return 0;
}

まずはコードを少し読み上げます。
"argc"と""argv"はコマンドライン引数と呼ばれ、、コード実行時の引数に関する情報を持っています。

例えばこのコード"your_pass.c"をコンパイルしてバイナリコード"your_pass"を作成し、次のように引数"mypass"を渡して実行してみます。

$ ./your_pass mypass

このときのコマンドライン引数は、

./your_pass mypass
argv[0] argv[1]

argc = 2 (引数の数)
となります。

つまり、コードの最初のif分岐 if(argc==2)は、実行時に引数が一つ渡されたかどうかを判定し、このif分岐がFALSEの場合は、 Access Denied. が表示されます。

一方、このif分岐がTRUEの場合、printf("Checking Your Pass: %s\n", argv[1]);を実行した後、渡された引数のチェックに移ります。

if(strcmp(argv[1], "Antoine-Marie-Jean-Baptiste-Roger_comte-de-Saint-Exupery")==0)

strcmp()は、2つの文字列が等しいときに0を返す関数です。
argv[1](実行時に渡した引数)が、正解のパスワード("Antoine-Marie-Jean-Baptiste-Roger_comte-de-Saint-Exupery")と等しいかどうか、を判定しています。

以上のことから、このコードの挙動は次のようになるはずです。

  • 正しいパスワード:Antoine-Marie-Jean-Baptiste-Roger_comte-de-Saint-Exupery
    を引数として渡したときのみ、"Hello, Master"が表示される。

  • それ以外では、"Access Denied."が表示される。

さっそくyour_pass.cをコンパイルして試してみます。

#コンパイル
$ gcc your_pass.c -o your_pass
#正解パスワードで実行
$ ./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.
#不正解パスワードで実行
$ ./your_pass wrong_pass
Checking Your Pass: wrong_pass
Access Denied.
#引数なしで実行
$ ./your_pass
Access Denied.

見事、予想通りの挙動をしてくれました。


おわりに

今回は、これから解析していくパスワード認証プログラムを下準備として作成しました。

それでは、一旦このソースコードについての記憶を消去します。

次回からの目的は、正解パスワードを渡さずしてアクセス承認("Hello, Master."の表示)を引き出すことです。
様々なやり方があるようですが、まずはバイナリコードを逆アセンブラ解析することによってプログラムの挙動を暴き、パスワード認証過程の迂回を目指します。

[関連記事]

www.bioerrorlog.work

www.bioerrorlog.work


  1. もちろん、うまくいかないまでも何かを作ろうと試行錯誤するのはとても楽しいです。しかし、いわゆる「プログラミング言語の勉強」なるものをやっていると、自分でも何がしたいのか分からなくなってきます。

  2. リバースエンジニアリング - Wikipedia

  3. Reversing and Cracking first simple Program - bin 0x05 - YouTube

  4. GitHub - LiveOverflow/liveoverflow_youtube: Material for the YouTube series

LinuxではじめてのC言語を実行する | vim + gcc

Linux (Ubuntu) + vimでC言語を実行する環境構築手順をまとめます。

はじめに

おはよう。@bioerrorlogです。

Linuxを入れたばかりだと、テキストファイルをどうやって作成するのか、どうやってコードを実行するのか、よくわかりません。

今回はvimを用いたC言語のコード編集から、コード実行までの手順をまとめます。

作業環境

Ubuntu18.04.1 LTS を
Windows10の上に、VMwareによって構築した仮想環境で起動しています。
www.bioerrorlog.work

LinuxでC言語を実行する

以下の手順を進めていきます。

  1. vimのインストール
  2. vimでテキストの作成
  3. gccのインストール/コンパイル
  4. ファイルの実行

vimのインストール

まずはテキストエディタvimをインストールします。 インストールする前に、とりあえずターミナルにvimについて聞いてみます。

$ vim
Command 'vim' not found, but can be installed with:

sudo apt install vim       
sudo apt install vim-gtk3  
sudo apt install vim-tiny  
sudo apt install neovim    
sudo apt install vim-athena
sudo apt install vim-gtk   
sudo apt install vim-nox  

vimのインストール方法を教えてくれました。 さっそくインストールします。

$ sudo apt install vim     

これでvimのインストールが完了です

vimでテキストの作成

vimを使ってC言語用の".c"ファイルを作成します。

$ vim test.c

このようにすると、test.cファイルのvimエディット画面に移行します。
vimのエディット画面は、慣れないと難しい挙動なので注意が必要です。

まず、"i"を押すことで"-- INSERT --"モードになり、普通に文字を編集することができます。
例えば、次のようにしてコードを書きます。

#include <stdio.h>

int main(int argc, char *argv[]) {
    printf("Hello, Lain.\n");
    return 0;
}

いろいろと書きましたが、とりあえずはprintf("Hello, Lain.\n");によって文字列”Hello, Lain.”が出力される、という理解でよしとします。

書き終わったら、”Esc”を押して"-- INSERT --"モードを終了します。
この状態で":"を押すと、オプションを受けつけるようになります。
例えば、

:wで保存、
:qでvimの終了、
:wqと組み合わせると保存して終了、です。

他にも色々ありますが、今はこれを把握すれば十分でしょう。

ちなみに作成したファイルは、次のコマンドで削除できます。

$ rm test.c

gccのインストール/コンパイル

C言語で書かれたソースファイルは、コンパイルして実行ファイルを生成する必要があります。
gccは、そのコンパイラの一つです。 マニュアルを読む1と、

~
NAME
       gcc - GNU project C and C++ compiler
~

CとC++のコンパイラであることがわかります。 ちなみにこのgccマニュアルは、様々な拡張子の解説なども載っていて、なかなか面白いものです。

vimと同じように、まずはインストールします。

$ sudo apt install gcc

インストールが完了したら、さっそく先程作成したtest.cをコンパイルします。

$ gcc test.c -o test

test.cをコンパイルし、testという実行ファイルを作成する、というコマンドです。

これでコンパイルは完了です。

ファイルの実行

ファイルを実行するには、実行ファイルのディレクトリをターミナルに打ち込みます。

$ ./test
Hello, Lain.

記念すべきはじめての文字列が表示されました。

ちなみに今回のtestファイルでは意味がありませんが、実行時に引数を与えることで、

$ ./test arg

testの実行時に引数argを渡して実行させることができます。

パッケージのアンインストール

最後に、今回インストールしたパッケージをアンインストールする方法も記しておきます。

インストール時に使用したaptコマンドのマニュアルを読みます。

~
           Removing a package removes all packaged data, but leaves usually small
           (modified) user configuration files behind, in case the remove was an
           accident. Just issuing an installation request for the accidentally removed
           package will restore its function as before in that case. On the other hand
           you can get rid of these leftovers by calling purge even on already removed
           packages. Note that this does not affect any data or configuration stored in
           your home directory.
~

removeはユーザーの設定ファイルを残してアンインストールするのに対し、purgeはすべてのファイルをアンインストールするようです。
使い方はそれぞれ次の通り:

$ sudo apt remove vim
$ sudo apt purge vim

おわりに

C言語でプログラミングするための簡単な環境構築、実行する手順を記録しました。

どうやってプログラミングするのか、という情報はたくさん目にしますが、どうやってプログラミングされているのか、という話題ははあまり見かけません。

面白そうなリソースはたくさんあります。 これらも参考にしながら、仕組みの理解を深めていきたいと思います。

[関連記事]

www.bioerrorlog.work

www.bioerrorlog.work

参考

Binary Exploitation / Memory Corruption by LiveOverflow - YouTube

manコマンドの基本的な使い方を理解する | Linuxコマンド - 生物系がゼロから始めるTech Blog


  1. マニュアルの読み方は、過去記事にまとめました。

manコマンドの基本的な使い方を理解する | Linuxコマンド

manコマンドの基本的な使い方を整理します。

はじめに

こんにちは、@bioerrorlogです。

manコマンドは、マニュアルを表示できるコマンドです。

manコマンドを使いこなせれば、コマンドのことをよく知らなくても自分で調べることができます。 もちろん、googleで検索すればコマンドの使い方を解説する記事はたくさん出てきますが、やはり偉大な製作陣の手によるマニュアルが一番正確です。

今回は、このmanコマンドの使い方を整理します。

manコマンドの基本的な使い方を理解する

manの使い方の初歩

まず一番素朴な使い方は、man [コマンド名]です。

試しにmanコマンド自身のマニュアルをmanコマンドで表示してみます。

$ man man
MAN(1)                                                                               Manual pager utils                                                                               MAN(1)

NAME
       man - an interface to the on-line reference manuals

SYNOPSIS
       man  [-C file] [-d] [-D] [--warnings[=warnings]] [-R encoding] [-L locale] [-m system[,...]] [-M path] [-S list] [-e extension] [-i|-I] [--regex|--wildcard] [--names-only] [-a] [-u]
       [--no-subpages] [-P pager] [-r prompt] [-7] [-E encoding] [--no-hyphenation] [--no-justification] [-p string] [-t] [-T[device]] [-H[browser]] [-X[dpi]]  [-Z]  [[section]  page[.sec‐
       tion] ...] ...
       man -k [apropos options] regexp ...
       man -K [-w|-W] [-S list] [-i|-I] [--regex] [section] term ...
       man -f [whatis options] page ...
       man  -l  [-C  file]  [-d] [-D] [--warnings[=warnings]] [-R encoding] [-L locale] [-P pager] [-r prompt] [-7] [-E encoding] [-p string] [-t] [-T[device]] [-H[browser]] [-X[dpi]] [-Z]
       file ...
       man -w|-W [-C file] [-d] [-D] page ...
       man -c [-C file] [-d] [-D] page ...
       man [-?V]

~(以下略)

マニュアルは"q"を押せば終了されます。

同じ形式で、その他のコマンドのマニュアルも表示できます。

$ man ls
LS(1)                           User Commands                          LS(1)

NAME
       ls - list directory contents
~(以下略)


ここで注目しておきたいは、"セクション"という概念です。

上記man ls結果も、よく見ると最初の行にLS(1)とあります。
この(1)が、lsマニュアルのセクション 1であることを示しています。

マニュアルのセクションを指定する

マニュアルのセクションは、man [セクション番号] [コマンド名]で指定することが出来ます。

セクションは1から9までの番号で分類されています。 manマニュアルに各セクション内容が記載されているので、忘れたときはman manしましょう。

       1   Executable programs or shell commands
       2   System calls (functions provided by the kernel)
       3   Library calls (functions within program libraries)
       4   Special files (usually found in /dev)
       5   File formats and conventions eg /etc/passwd
       6   Games
       7   Miscellaneous  (including  macro  packages and conventions), e.g. man(7), groff(7)
       8   System administration commands (usually only for root)
       9   Kernel routines [Non standard]

ただ、それぞれのマニュアルにセクションが1から9まですべて存在しているわけではないので注意が必要です。

マニュアルを検索する

つぎに、マニュアルを検索するオプション"-k"を紹介します。

       -k, --apropos
              Equivalent to apropos.  Search the short manual page descriptions for keywords and display any matches.

オプション"-k"に文字列を渡せば、全てのマニュアルページ説明文を検索し、マッチしたものを一覧表示してくれます。

例:

$ man -k printf
asprintf (3)         - print to allocated string
dprintf (3)          - formatted output conversion
fprintf (3)          - formatted output conversion
fwprintf (3)         - formatted wide-character output conversion
printf (1)           - format and print data
printf (3)           - formatted output conversion
snprintf (3)         - formatted output conversion
sprintf (3)          - formatted output conversion
swprintf (3)         - formatted wide-character output conversion
vasprintf (3)        - print to allocated string
vdprintf (3)         - formatted output conversion
vfprintf (3)         - formatted output conversion
vfwprintf (3)        - formatted wide-character output conversion
vprintf (3)          - formatted output conversion
vsnprintf (3)        - formatted output conversion
vsprintf (3)         - formatted output conversion
vswprintf (3)        - formatted wide-character output conversion
vwprintf (3)         - formatted wide-character output conversion
wprintf (3)          - formatted wide-character output conversion

この例ではprintfという文字列を説明文に含むマニュアルページが一覧表示されています。

関連するmanページを探したいときに便利です。

manページ内を検索する

manページそのものを検索するのではなく、manページ内で文字列を検索したい状況もよくあります。

文字列を検索するには、manページが表示されている状態で

スラッシュ/ + 文字列

を入力し、Enterを押すと検索を開始できます。

次の検索ヒットに移動するには n を、
前の検索ヒットに移動するには Shift + n を押下します。

おわりに

以上、manコマンドの使い方を簡単に整理しました。

manコマンドを使いこなして、困ったときはまず公式のマニュアル/ドキュメントを読む、というのを身体に染み込ませたいものです。

[関連記事]

www.bioerrorlog.work

www.bioerrorlog.work

www.bioerrorlog.work

パーミッションを理解する | [ ls -l ] Linuxコマンド

パーミッションとは、ファイルやディレクトリへの操作権限のことです。
今回、Linuxでのパーミッションの調べ方を記録しました。


はじめに

おはよう。@bioerrorlogです。

前回の記事1では、書き込みが許されていないディレクトリ"/etc/profile.d"に遭遇し、パーミッションという概念を思い知らされました。
パーミッションのことを知っておかなければ、Linuxの中を自由に探索することができません。

そこで今回は、パーミッションとは何か、どうやって調べるのかを記録します。


パーミッションを理解する

パーミッションとは

パーミッションとは、Archwikiによると2

File systems use permissions and attributes to regulate the level of interaction that system processes can have with files and directories.

つまり、ファイルに対する操作権限を制御するもの、だそうです。

例えば、前回私はOS起動時の自動操作を設定するため、"/etc/profile.d"ディレクトリに新しくファイルを追加しようとしました。
しかし、その時表示されたエラーは、

E212: Can't open file for writing

つまり、あなたはファイルに書き込めませんよ、と言われてしまったわけです。
このように、読み込みや書き込みの権限は、ファイルごとに決まっています。
これがパーミッションです。


パーミッションの調べ方 | ls -l

次に、どうすればパーミッションを見ることができるのかを調べました。
すると、どうやらlsコマンドのオプション-lでパーミッションを見れるようです。

それでは"/etc/profile.d"ディレクトリのパーミッションを確認してみます。

/etc$ ls -l
~
drwxr-xr-x  2 root root    4096 Jan 15 22:33 profile.d
~

とても面白そうな文字列が表示されましたが、何を意味しているのかさっぱりわかりません。
lsコマンドのマニュアル3から、" -l "オプションで表示される情報を調べました。

If the -l option is specified, the following information shall be written for files other than character special and block special files:
"%s %u %s %s %u %s %s\n", [file mode], [number of links], [owner name], [group name], [size], [date and time], [pathname]

つまり、ls -lコマンドを実行すると、左から順に
[ファイルモード]、[リンク数]、[オーナー名]、[グループ名]、[サイズ]、[日時]、[パス名]
が表示されるということです。

ここで着目すべきは「ファイルモード」と「オーナー名」、「グループ名」です。
「ファイルのモード」は、最初の文字列 "drwxr-xr-x" のことで、まさしくファイルに対する操作権限を示しています。
"drwxr-xr-x"の文字列は、4つに分けて捉えます。

d / rwx / r-x / r-x

これらは左から順に、
ファイル種類 / オーナーの権限 / グループの権限 / その他ユーザーの権限
を示しています。
一番左の"ファイル種類"は今はあまり重要ではありません。"d"とあればディレクトリ、"-"とあれば普通のファイルだそうです。

大事なのは3つの"rwx"の文字列です。
"r"は読み込み、
"w"は書き込み、
"x"は実行、の権限を示しています。

例えば
"rwx"は読み込み、書き込み、実行のすべてが許可されていることを意味し、
"r-x"では書き込みが許可されていません。
この権限がオーナー、グループ、その他ユーザーに割り当てられています。

オーナーとグループの定義は
drwxr-xr-x 2 root root 4096 Jan 15 22:33 profile.d
この左から3、4つ目のかたまりがそれぞれ[オーナー名]、[グループ名]を定義しています。 この場合は、オーナーが"root"、グループが"root"グループということなので、許可されている権限は、

"root" : rwx
"root"グループ : r-x
その他ユーザー : r-x

ということになります。
つまり、書き込み"w"が許可されているのは"root"権限のみであるわけです。

もしもこのディレクトリに書き込みを行いたいのであれば、
$ sudo vim XXX.txt
のように、sudoコマンドでroot権限を取得する必要があります。


おわりに

今回は、ls -lコマンドによるパーミッションの調べ方を記録しました。

Linuxの中のファイルを探索して回るときには、私はよくls -lを使用するので、パーミッションに目を向けてみるのも面白そうです。

また、このパーミッションは変更させることもできます。
気が向いたときにもっと深くまで遊んでみたいと思います。