前回 は、Pwnable問題に取り組みました。CTF のカテゴリの中でも、一番難しいと言われるだけあって、かなり苦労しました。
今回は、前回の Pwnable問題でも使用した、実行ファイルのセキュリティ機構(脆弱性緩和技術とも言う)について整理したいと思います。セキュリティ機構を調べるツールは、従来は「checksec」を使用していましたが、問題があったので、現在は、pwntools の checksec を使用しています。さらに、pwntools がインストールされていない環境でも、pwndbg が入っていれば、checksec を実行できるので、それについても言及します。
実行ファイルのセキュリティ機構とは、もし、実行ファイルに脆弱性が存在していたとしても、その脆弱性に対する攻撃をやりにくくする仕組みのことです。
例えば、スタックカナリヤは、関数開始時にスタックにランダムな値を格納しておき、関数終了時に、その値が変化していないかをチェックします。これによって、スタックを使った攻撃を成功させにくくすることが出来ます。
実行ファイルのセキュリティ機構には、いくつか種類があるので、それらの詳細と、実際に、コンパイルしたり、GDB で確認したりしてみたいと思います。
それでは、やっていきます。
- 参考文献
- はじめに
- pwndbgのchecksecの使い方
- pwntoolsのchecksecの使い方
- checksecの準備
- 簡単なC言語のプログラムを用意する
- C言語プログラムをデフォルトでビルドした実行ファイルのセキュリティ機構を見てみる
- RELROについて具体的に確認する
- STACK CANARY(SSP)について具体的に確認する
- NXについて具体的に確認する
- PIEとASLRについて具体的に確認する
- おわりに
参考文献
はじめに
「セキュリティ」の記事一覧です。良かったら参考にしてください。
・第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で実行したときの時間を見積もってみる
・第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のスタックベースエクスプロイトを読んだ
checksec の公式サイトは以下です。
環境は、VirtualBox+ParrotOS 6.1 です。
それでは、やっていきます。
pwndbgのchecksecの使い方
※2025/11/09更新:pwntools がインストールされていない環境でも、pwndbg がインストールされていれば、checksec を実行できるので、ここに追記します。
pwndbg を有効にした状態で、gdb で対象のプログラムを起動すれば、checksec というコマンドを実行することで、下の pwntools と同様にセキュリティ機構を調べることが出来ます。
gdb を起動するのが面倒と感じる場合は、以下のようにワンライナーで実行することもできます。
$ pwndbg -q --batch -ex 'file ./chall_stack' -ex 'checksec' -ex 'quit' pwndbg: loaded 211 pwndbg commands. Type pwndbg [filter] for a list. pwndbg: created 13 GDB functions (can be used with print/break). Type help function to see them. This GDB supports auto-downloading debuginfo from the following URLs: <https://debuginfod.ubuntu.com> Debuginfod has been disabled. To make this setting permanent, add 'set debuginfod enabled off' to .gdbinit. File: /home/ubuntu/svn/experiment-old/shokai_security_contest/files/pwnable/99_challs/stack/chall_stack Arch: amd64 RELRO: Full RELRO Stack: Canary found NX: NX enabled PIE: PIE enabled SHSTK: Enabled IBT: Enabled Stripped: No
エイリアスに設定したいところですが、このワンライナーをエイリアスに設定するのは無理とのことでした(from ChatGPT)。そこで、~/.bashrc にシェル関数も併せて記述することで実現できるとのことです。以下を ~/.bashrc に貼り付けました。
checksec() { # 引数が無ければカレントディレクトリの a.out を使う local bin="${1:-./a.out}" # 存在チェック(任意) if [ ! -e "$bin" ]; then echo "Error: '$bin' が見つかりません。" >&2 return 1 fi # 実行(引用でスペースを扱う) pwndbg -q --batch -ex "file $bin" -ex "checksec" -ex "quit" }
実行してみます。
イメージ通りの実行ができました。
$ checksec ./chall_stack pwndbg: loaded 211 pwndbg commands. Type pwndbg [filter] for a list. pwndbg: created 13 GDB functions (can be used with print/break). Type help function to see them. This GDB supports auto-downloading debuginfo from the following URLs: <https://debuginfod.ubuntu.com> Debuginfod has been disabled. To make this setting permanent, add 'set debuginfod enabled off' to .gdbinit. File: /home/ubuntu/svn/experiment-old/shokai_security_contest/files/pwnable/99_challs/stack/chall_stack Arch: amd64 RELRO: Full RELRO Stack: Canary found NX: NX enabled PIE: PIE enabled SHSTK: Enabled IBT: Enabled Stripped: No
pwntoolsのchecksecの使い方
※2025/1/30更新:checksec が誤った表示をすることがあったので、pwntools の checksec に移行しました
以前は、後述する checksec を使っていたのですが、誤った表示をする場合があったので、pwntools に付属している checksec に移行しました。
具体的には、以下の記事で、chall_stack というスタックカナリアが有効なプログラムを判定した際、オリジナルの checksec はスタックカナリアが無効と表示されてしまいました。しかし、実際には、chall_stack は、スタックカナリアが有効であり、pwntools の checksec で調べてみると、スタックカナリアが有効と正しく表示されました。
daisuke20240310.hatenablog.com
実際にやってみます。最初にオリジナルの checksec で chall_stack の判定を行い、その後、pwntools の checksec で同じ chall_stack の判定を行いました。chall_stack は、スタックカナリアが有効な場合の問題なので、実際はスタックカナリアが有効です。
$ ~/bin/checksec --file=chall_stack RELRO STACK CANARY NX PIE RPATH RUNPATH Symbols FORTIFY Fortified Fortifiable FILE Full RELRO No canary found NX enabled PIE enabled No RPATH No RUNPATH 1884 Symbols N/A 0 21 chall_stack $ pwn checksec --file=chall_stack [*] '/home/user/svn/experiment/shokai_security_contest/files/pwnable/99_challs/stack/chall_stack' Arch: amd64-64-little RELRO: Full RELRO Stack: Canary found NX: NX enabled PIE: PIE enabled SHSTK: Enabled IBT: Enabled Stripped: No
pwntools の checksec の使い方は以下の通りです。ちなみに、pwn を付けなくても、なぜか実行できるようです。
$ pwn checksec -h usage: pwn checksec [-h] [--file [elf ...]] [elf ...] Check binary security settings positional arguments: elf Files to check options: -h, --help show this help message and exit --file [elf ...] File to check (for compatibility with checksec.sh)
なぜ、誤った表示がされたのかの理由は分かりませんが、間違えられても困るので、今後は pwntools の checksec を使っていきたいと思います。
checksecの準備
checksec は、従来からシェルスクリプトで実装されていましたが、シェルスクリプト版として、2.7.x(現在の最新版は、2.7.1)が最終リリースで、以降の 3.x からは、Go言語による実装に代わるそうです。
上の URL にアクセスして、右側に見える Releases をクリックします。checksec.sh-2.7.1.zip がダウンロードできますので、任意の場所に解凍します。解凍したフォルダの中に「checksec」というファイル名のシェルスクリプトが入っていると思います。
ファイルの確認と、バージョンを確認してみます。
$ file ../../tools/checksec.sh-2.7.1/checksec ../../tools/checksec.sh-2.7.1/checksec: Bourne-Again shell script, ASCII text executable $ ../../tools/checksec.sh-2.7.1/checksec --version checksec v2.7.1, Brian Davis, github.com/slimm609/checksec.sh, Dec 2015 Based off checksec v1.5, Tobias Klein, www.trapkit.de, November 2011
以降では、この checksec を使っていきます。
簡単なC言語のプログラムを用意する
実際に、実行ファイルのセキュリティ機構がどうなっているかを見るために、簡単なプログラムを用意します。
このプログラムを実行すると、ユーザから2回入力してもらって、その合計値が、0 より大きかったら 0(正常終了)を返し、0 以下だったら 1(異常終了)を返します。
ソースコードは以下の通りです。アドレスの確認が必要なので、先頭で、main関数のアドレス(実行するプログラムのアドレス)、ローカル変数のアドレス(スタックのアドレス)、malloc関数で確保したメモリのアドレス(ヒープのアドレス)、malloc関数のアドレス(共有ライブラリのアドレス)を表示しています。
#include <stdio.h> #include <stdlib.h> int sub( int data ) { int data2; printf( "input data2: " ); scanf( "%d", &data2 ); return data + data2; } int main( int argc, void *argv[] ) { int ret, data; char buf[20], *mbuf; mbuf = malloc( 20 ); printf( " main: %p\n", main ); printf( " buf: %p\n", buf ); printf( " mbuf: %p\n", mbuf ); printf( "malloc: %p\n", malloc ); printf( "input data: " ); scanf( "%d", &data ); ret = sub( data ); printf( "result: %d", ret ); if( ret > 0 ) return 0; else return 1; }
ビルドには gcc を使います。v12.2 のようです。
ビルドして、実行してみます。1 と 2 を入力して、足し合わせた 3 が表示されて、正常終了しています。
$ gcc -v Using built-in specs. COLLECT_GCC=gcc COLLECT_LTO_WRAPPER=/usr/lib/gcc/x86_64-linux-gnu/12/lto-wrapper OFFLOAD_TARGET_NAMES=nvptx-none:amdgcn-amdhsa OFFLOAD_TARGET_DEFAULT=1 Target: x86_64-linux-gnu Configured with: ../src/configure -v --with-pkgversion='Debian 12.2.0-14' --with-bugurl=file:///usr/share/doc/gcc-12/README.Bugs --enable-languages=c,ada,c++,go,d,fortran,objc,obj-c++,m2 --prefix=/usr --with-gcc-major-version-only --program-suffix=-12 --program-prefix=x86_64-linux-gnu- --enable-shared --enable-linker-build-id --libexecdir=/usr/lib --without-included-gettext --enable-threads=posix --libdir=/usr/lib --enable-nls --enable-clocale=gnu --enable-libstdcxx-debug --enable-libstdcxx-time=yes --with-default-libstdcxx-abi=new --enable-gnu-unique-object --disable-vtable-verify --enable-plugin --enable-default-pie --with-system-zlib --enable-libphobos-checking=release --with-target-system-zlib=auto --enable-objc-gc=auto --enable-multiarch --disable-werror --enable-cet --with-arch-32=i686 --with-abi=m64 --with-multilib-list=m32,m64,mx32 --enable-multilib --with-tune=generic --enable-offload-targets=nvptx-none=/build/gcc-12-bTRWOB/gcc-12-12.2.0/debian/tmp-nvptx/usr,amdgcn-amdhsa=/build/gcc-12-bTRWOB/gcc-12-12.2.0/debian/tmp-gcn/usr --enable-offload-defaulted --without-cuda-driver --enable-checking=release --build=x86_64-linux-gnu --host=x86_64-linux-gnu --target=x86_64-linux-gnu Thread model: posix Supported LTO compression algorithms: zlib zstd gcc version 12.2.0 (Debian 12.2.0-14) $ gcc -g -o hello_hello.out hello_hello.c $ ./hello_hello.out main: 0x55555555518d buf: 0x7fffffffdf30 mbuf: 0x5555555592a0 malloc: 0x7ffff7e62860 input data: 1 input data2: 2 result: 3
このプログラムを使って、実行ファイルのセキュリティ機構を見ていきます。
C言語プログラムをデフォルトでビルドした実行ファイルのセキュリティ機構を見てみる
先ほど、ビルドして実行した hello_hello.out に対して、checksec を実行してみたいと思います。1行目は、セキュリティ機構の項目名で、2行目がその結果です。例えば、スタックカナリヤはデフォルトでは無効のようです。
$ ../../tools/checksec.sh-2.7.1/checksec --file=./hello_hello.out RELRO STACK CANARY NX PIE RPATH RUNPATH Symbols FORTIFY Fortified Fortifiable FILE Partial RELRO No canary found NX enabled PIE enabled No RPATH No RUNPATH 38 Symbols No 0 1 ./hello_hello.out
現状の ASLR(Address Space Layout Randomization)も確認しておきます。/proc/sys/kernel/randomize_va_space を見ると確認できます。0 の場合はランダム化されません。1 は、一部をランダム化されます(共有ライブラリ、スタックなど)。2 の場合は完全にランダム化されます。1 の場合に加えて、brk() で管理されるメモリの開始アドレスもランダム化されるそうです(あまり分かってないですが、追加でヒープを確保するときの領域?)。
$ cat /proc/sys/kernel/randomize_va_space 2
セキュリティ機構について、簡単にまとめておきます。
| 項目 | 内容 |
|---|---|
| RELRO | RELocation Read Only のことで、No RELRO(GOT は書き込み可能)、Partial RELRO(GOT のごく一部は書き込み禁止 ※__libc_start_main など)、Full RELRO(GOT は書き込み禁止)のどれかになる。共有ライブラリのアドレスが格納されている GOT を書き込み禁止にする |
| STACK CANARY | 関数開始時にスタックにランダムな値(カナリヤ)を格納しておき、関数終了時に、その値が変化していないかをチェックする(SSP とも言う) |
| NX | No eXecute のことで、スタック領域のコードの実行を禁止にする |
| PIE | Position Independent Executable のことで、この実行ファイルが配置されるアドレスをランダム化する |
| RPATH | 実行ファイルに共有ライブラリのサーチするリストを格納していること示す(攻撃に利用されることがあるので、RPATH が有効かどうかを表示している) |
| RUNPATH | RPATH と機能は同じだが、LD_LIBRALY_PATHが優先されるため、RPATHより安全と言われている(RUNPATH が有効かどうかを表示している) |
| Symbols | 実行ファイルに含まれているシンボル情報の数(strip されてないことを表示する) |
| FORTIFY | GCC、GLIBC におけるセキュリティ機能が有効かどうかを示す |
| Fortified | FORTIFY の機能を有効にした関数の数 |
| Fortifiable | FORTIFY の機能数(有効にできる関数の数) |
| FILE | 今回 checksec の対象としたプログラム |
| SHSTK | Shadow Stack(Intel)のことでリターンアドレスを監視 |
| IBT | Indirect BranchTracking(Intel)のことで不正な間接分岐を監視 |
RELROについて具体的に確認する
まず、GOT(Global Offset Table)と PLT(Procefure Linkage Table)について調べます。
GOTとPLTについて具体的に確認する
GDB(gdb-peda 導入済み)を起動します。先頭の malloc関数をコールする直前で止まりました。
$ gdb -q hello_hello.out : no key sequence terminator: Reading symbols from hello_hello.out... gdb-peda$ start Warning: 'set logging off', an alias for the command 'set logging enabled', is deprecated. Use 'set logging enabled off'. Warning: 'set logging on', an alias for the command 'set logging enabled', is deprecated. Use 'set logging enabled on'. [Thread debugging using libthread_db enabled] Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1". [----------------------------------registers-----------------------------------] RAX: 0x55555555518d (<main>: push rbp) RBX: 0x7fffffffe358 --> 0x7fffffffe5d2 ("/home/user/svn/experiment/c/hello_hello.out") RCX: 0x555555557dd0 --> 0x555555555100 (<__do_global_dtors_aux>: endbr64) RDX: 0x7fffffffe368 --> 0x7fffffffe5fe ("SHELL=/bin/bash") RSI: 0x7fffffffe358 --> 0x7fffffffe5d2 ("/home/user/svn/experiment/c/hello_hello.out") RDI: 0x1 RBP: 0x7fffffffe240 --> 0x1 RSP: 0x7fffffffe200 --> 0x7fffffffe358 --> 0x7fffffffe5d2 ("/home/user/svn/experiment/c/hello_ hello.out") RIP: 0x55555555519c (<main+15>: mov edi,0x14) R8 : 0x0 R9 : 0x7ffff7fcf680 (<_dl_fini>: push rbp) R10: 0x7ffff7fcb878 --> 0xc00120000000e R11: 0x7ffff7fe1930 (<_dl_audit_preinit>: mov eax,DWORD PTR [rip+0x1b4e2] # 0x 7ffff7ffce18 <_rtld_global_ro+888>) R12: 0x0 R13: 0x7fffffffe368 --> 0x7fffffffe5fe ("SHELL=/bin/bash") R14: 0x555555557dd0 --> 0x555555555100 (<__do_global_dtors_aux>: endbr64) R15: 0x7ffff7ffd020 --> 0x7ffff7ffe2e0 --> 0x555555554000 --> 0x10102464c457f EFLAGS: 0x206 (carry PARITY adjust zero sign trap INTERRUPT direction overflow) [-------------------------------------code-------------------------------------] 0x555555555191 <main+4>: sub rsp,0x40 0x555555555195 <main+8>: mov DWORD PTR [rbp-0x34],edi 0x555555555198 <main+11>: mov QWORD PTR [rbp-0x40],rsi => 0x55555555519c <main+15>: mov edi,0x14 0x5555555551a1 <main+20>: call 0x555555555050 <malloc@plt> 0x5555555551a6 <main+25>: mov QWORD PTR [rbp-0x8],rax 0x5555555551aa <main+29>: lea rax,[rip+0xffffffffffffffdc] # 0x55555555518d <m ain> 0x5555555551b1 <main+36>: mov rsi,rax [------------------------------------stack-------------------------------------] 0000| 0x7fffffffe200 --> 0x7fffffffe358 --> 0x7fffffffe5d2 ("/home/user/svn/experiment/c/hello _hello.out") 0008| 0x7fffffffe208 --> 0x100000000 0016| 0x7fffffffe210 --> 0x0 0024| 0x7fffffffe218 --> 0x0 0032| 0x7fffffffe220 --> 0x0 0040| 0x7fffffffe228 --> 0x0 0048| 0x7fffffffe230 --> 0x0 0056| 0x7fffffffe238 --> 0x0 [------------------------------------------------------------------------------] Legend: code, data, rodata, value Temporary breakpoint 1, main (argc=0x1, argv=0x7fffffffe358) at hello_hello.c:20 20 mbuf = malloc( 20 );
まず、メモリ配置を確認しておきます。開始アドレスは、0x555555554000 です。
$ i proc map process 319238 Mapped address spaces: Start Addr End Addr Size Offset Perms objfile 0x555555554000 0x555555555000 0x1000 0x0 r--p /home/user/svn/experiment/c/hello_hello.out 0x555555555000 0x555555556000 0x1000 0x1000 r-xp /home/user/svn/experiment/c/hello_hello.out 0x555555556000 0x555555557000 0x1000 0x2000 r--p /home/user/svn/experiment/c/hello_hello.out 0x555555557000 0x555555558000 0x1000 0x2000 r--p /home/user/svn/experiment/c/hello_hello.out 0x555555558000 0x555555559000 0x1000 0x3000 rw-p /home/user/svn/experiment/c/hello_hello.out 0x7ffff7dc7000 0x7ffff7dca000 0x3000 0x0 rw-p 0x7ffff7dca000 0x7ffff7df0000 0x26000 0x0 r--p /usr/lib/x86_64-linux-gnu/libc.so.6 0x7ffff7df0000 0x7ffff7f45000 0x155000 0x26000 r-xp /usr/lib/x86_64-linux-gnu/libc.so.6 0x7ffff7f45000 0x7ffff7f98000 0x53000 0x17b000 r--p /usr/lib/x86_64-linux-gnu/libc.so.6 0x7ffff7f98000 0x7ffff7f9c000 0x4000 0x1ce000 r--p /usr/lib/x86_64-linux-gnu/libc.so.6 0x7ffff7f9c000 0x7ffff7f9e000 0x2000 0x1d2000 rw-p /usr/lib/x86_64-linux-gnu/libc.so.6 0x7ffff7f9e000 0x7ffff7fab000 0xd000 0x0 rw-p 0x7ffff7fc3000 0x7ffff7fc5000 0x2000 0x0 rw-p 0x7ffff7fc5000 0x7ffff7fc9000 0x4000 0x0 r--p [vvar] 0x7ffff7fc9000 0x7ffff7fcb000 0x2000 0x0 r-xp [vdso] 0x7ffff7fcb000 0x7ffff7fcc000 0x1000 0x0 r--p /usr/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2 0x7ffff7fcc000 0x7ffff7ff1000 0x25000 0x1000 r-xp /usr/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2 0x7ffff7ff1000 0x7ffff7ffb000 0xa000 0x26000 r--p /usr/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2 0x7ffff7ffb000 0x7ffff7ffd000 0x2000 0x30000 r--p /usr/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2 0x7ffff7ffd000 0x7ffff7fff000 0x2000 0x32000 rw-p /usr/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2 0x7ffffffde000 0x7ffffffff000 0x21000 0x0 rw-p [stack]
malloc関数は一度しか使っておらず、遅延バインドの確認が出来ないため、次の printf関数を使って、GOT、PLT について確認します。逆アセンブラを表示して、全体を見ます。+20 のところで、malloc関数をコールして、+54 のところで printf関数をコールしています。そして、+81 のところで、2回目の printf関数をコールしています。
gdb-peda$ disas Dump of assembler code for function main: 0x000055555555518d <+0>: push rbp 0x000055555555518e <+1>: mov rbp,rsp 0x0000555555555191 <+4>: sub rsp,0x40 0x0000555555555195 <+8>: mov DWORD PTR [rbp-0x34],edi 0x0000555555555198 <+11>: mov QWORD PTR [rbp-0x40],rsi => 0x000055555555519c <+15>: mov edi,0x14 0x00005555555551a1 <+20>: call 0x555555555050 <malloc@plt> 0x00005555555551a6 <+25>: mov QWORD PTR [rbp-0x8],rax 0x00005555555551aa <+29>: lea rax,[rip+0xffffffffffffffdc] # 0x55555555518d <main> 0x00005555555551b1 <+36>: mov rsi,rax 0x00005555555551b4 <+39>: lea rax,[rip+0xe5a] # 0x555555556015 0x00005555555551bb <+46>: mov rdi,rax 0x00005555555551be <+49>: mov eax,0x0 0x00005555555551c3 <+54>: call 0x555555555030 <printf@plt> 0x00005555555551c8 <+59>: lea rax,[rbp-0x30] 0x00005555555551cc <+63>: mov rsi,rax 0x00005555555551cf <+66>: lea rax,[rip+0xe4b] # 0x555555556021 0x00005555555551d6 <+73>: mov rdi,rax 0x00005555555551d9 <+76>: mov eax,0x0 0x00005555555551de <+81>: call 0x555555555030 <printf@plt> 0x00005555555551e3 <+86>: mov rax,QWORD PTR [rbp-0x8] 0x00005555555551e7 <+90>: mov rsi,rax 0x00005555555551ea <+93>: lea rax,[rip+0xe3c] # 0x55555555602d 0x00005555555551f1 <+100>: mov rdi,rax 0x00005555555551f4 <+103>: mov eax,0x0 0x00005555555551f9 <+108>: call 0x555555555030 <printf@plt>
malloc関数や、printf関数は、@plt というのが付いてます。MAPファイルを確認してみます。先頭の 0x555555554000 に 0x1020 を足すと、PLT のアドレスになります。printf関数は、0x1030 と書かれてるので、0x555555554000+0x1030=0x555555555030 となり、上の逆アセンブラ表示のアドレスと一致しています。1つの PLT のエントリが 16byte となっています。パーミッション(上の i proc map の出力より)によると、PLT の領域は、hello_hello.out の書き込み可能な領域に含まれています。
.plt 0x0000000000001020 0x30 *(.plt) .plt 0x0000000000001020 0x30 /usr/lib/gcc/x86_64-linux-gnu/12/../../../x86_64-linux-gnu/Scrt1.o 0x0000000000001030 printf@@GLIBC_2.2.5 0x0000000000001040 __isoc99_scanf@@GLIBC_2.7
では、printf@plt に飛んだ後の状態を見てみます。16byte に格納されているコードは、printf@got.plt に格納されているアドレスへのジャンプと、0x555555555020 へのジャンプを含む 3行でした。
[-------------------------------------code-------------------------------------] 0x555555555021: xor eax,0x2fca 0x555555555026: jmp QWORD PTR [rip+0x2fcc] # 0x555555557ff8 0x55555555502c: nop DWORD PTR [rax+0x0] => 0x555555555030 <printf@plt>: jmp QWORD PTR [rip+0x2fca] # 0x555555558000 <printf@got.plt> | 0x555555555036 <printf@plt+6>: push 0x0 | 0x55555555503b <printf@plt+11>: jmp 0x555555555020 | 0x555555555040 <__isoc99_scanf@plt>: jmp QWORD PTR [rip+0x2fc2] # 0x555555558008 <__isoc99_scanf@got.plt> | 0x555555555046 <__isoc99_scanf@plt+6>: push 0x1 |-> 0x555555555036 <printf@plt+6>: push 0x0 0x55555555503b <printf@plt+11>: jmp 0x555555555020 0x555555555040 <__isoc99_scanf@plt>: jmp QWORD PTR [rip+0x2fc2] # 0x555555558008 <__isoc99_scanf@got.plt> 0x555555555046 <__isoc99_scanf@plt+6>: push 0x1 JUMP is taken
MAPファイルで、printf@got.plt と書かれたアドレスを見てみます。_GLOBAL_OFFSET_TABLE_ と書かれています。0x555555554000+0x3fe8=0x555555557fe8 から、0x555555554000+0x3fe8+0x28=0x555555558010 が got.plt の領域になります。.got と .got.plt は、両方とも GOT領域と呼ばれるそうです。0x555555557000 から 0x555555558000 までは読み取り専用の領域であり、0x555555558000 から 0x555555559000 は書き込み可能な領域です。
.got.plt 0x0000000000003fe8 0x28 *(.got.plt) .got.plt 0x0000000000003fe8 0x28 /usr/lib/gcc/x86_64-linux-gnu/12/../../../x86_64-linux-gnu/Scrt1.o 0x0000000000003fe8 _GLOBAL_OFFSET_TABLE_ *(.igot.plt)
printf@got.plt に入ってるアドレスを確認します。gdb-peda が下に -> で表示してくれているように、0x555555555036 の push 0x0 です。次の命令ですね。1回目は、0x555555555020 のジャンプ先でアドレス解決をして、printf@got.plt に printf関数のアドレスを格納してくれるはずです。それによって、2回目は、直接 printf関数にジャンプできるようになります。一応、xコマンドで値を確認しておきます。
gdb-peda$ x/g 0x555555558000 0x555555558000 <printf@got.plt>: 0x0000555555555036
printf@got.plt に格納されているアドレスにジャンプしてみます。push 0x0 に飛びました。
[-------------------------------------code-------------------------------------] 0x555555555026: jmp QWORD PTR [rip+0x2fcc] # 0x555555557ff8 0x55555555502c: nop DWORD PTR [rax+0x0] 0x555555555030 <printf@plt>: jmp QWORD PTR [rip+0x2fca] # 0x555555558000 <printf@got.plt> => 0x555555555036 <printf@plt+6>: push 0x0 0x55555555503b <printf@plt+11>: jmp 0x555555555020 0x555555555040 <__isoc99_scanf@plt>: jmp QWORD PTR [rip+0x2fc2] # 0x555555558008 <__isoc99_scanf@got.plt> 0x555555555046 <__isoc99_scanf@plt+6>: push 0x1 0x55555555504b <__isoc99_scanf@plt+11>: jmp 0x555555555020
この後、0x555555555020 にジャンプして、シングルステップ実行を1回すると、以下になります。1つ前の命令が変に見えます(0x555555555020 だったのが、0x555555555021 に変わった)が、そこは気にしないでおきます。_dl_runtime_resolve_xsave というアドレス解決をしてくれるモジュールに飛ぶようです。
[-------------------------------------code-------------------------------------] 0x55555555501d: add BYTE PTR [rax],al 0x55555555501f: add bh,bh 0x555555555021: xor eax,0x2fca => 0x555555555026: jmp QWORD PTR [rip+0x2fcc] # 0x555555557ff8 | 0x55555555502c: nop DWORD PTR [rax+0x0] | 0x555555555030 <printf@plt>: jmp QWORD PTR [rip+0x2fca] # 0x555555558000 <printf@got.plt> | 0x555555555036 <printf@plt+6>: push 0x0 | 0x55555555503b <printf@plt+11>: jmp 0x555555555020 |-> 0x7ffff7fdd060 <_dl_runtime_resolve_xsave>: push rbx 0x7ffff7fdd061 <_dl_runtime_resolve_xsave+1>: mov rbx,rsp 0x7ffff7fdd064 <_dl_runtime_resolve_xsave+4>: and rsp,0xffffffffffffffc0 0x7ffff7fdd068 <_dl_runtime_resolve_xsave+8>: sub rsp,QWORD PTR [rip+0x1fbe1] # 0x7ffff7ffcc50 <_rtld_global_ro+432> JUMP is taken
この中は、ちょっと長そうなので、次の printf関数の呼び出しまで進めます。直前まで進めました。
[-------------------------------------code-------------------------------------] 0x5555555551cf <main+66>: lea rax,[rip+0xe4b] # 0x555555556021 0x5555555551d6 <main+73>: mov rdi,rax 0x5555555551d9 <main+76>: mov eax,0x0 => 0x5555555551de <main+81>: call 0x555555555030 <printf@plt> 0x5555555551e3 <main+86>: mov rax,QWORD PTR [rbp-0x8] 0x5555555551e7 <main+90>: mov rsi,rax 0x5555555551ea <main+93>: lea rax,[rip+0xe3c] # 0x55555555602d 0x5555555551f1 <main+100>: mov rdi,rax Guessed arguments: arg[0]: 0x555555556021 (" buf: %p\n") arg[1]: 0x7fffffffe210 --> 0x0
中に入ります。先ほどの 1回目の printf関数と同じところにきました。
[-------------------------------------code-------------------------------------] 0x555555555021: xor eax,0x2fca 0x555555555026: jmp QWORD PTR [rip+0x2fcc] # 0x555555557ff8 0x55555555502c: nop DWORD PTR [rax+0x0] => 0x555555555030 <printf@plt>: jmp QWORD PTR [rip+0x2fca] # 0x555555558000 <printf@got.plt> | 0x555555555036 <printf@plt+6>: push 0x0 | 0x55555555503b <printf@plt+11>: jmp 0x555555555020 | 0x555555555040 <__isoc99_scanf@plt>: jmp QWORD PTR [rip+0x2fc2] # 0x555555558008 <__isoc99_scanf@got.plt> | 0x555555555046 <__isoc99_scanf@plt+6>: push 0x1 |-> 0x7ffff7e1c5b0 <__printf>: sub rsp,0xd8 0x7ffff7e1c5b7 <__printf+7>: mov QWORD PTR [rsp+0x28],rsi 0x7ffff7e1c5bc <__printf+12>: mov QWORD PTR [rsp+0x30],rdx 0x7ffff7e1c5c1 <__printf+17>: mov QWORD PTR [rsp+0x38],rcx JUMP is taken
今度は、0x7ffff7e1c5b0 にある printf関数の実体に飛んでくれそうです。0x555555558000 に格納されている値を見てみます。先ほどは次の行の push 0x0 のアドレスが格納されていましたが、変化しています。
gdb-peda$ x/g 0x555555558000 0x555555558000 <printf@got.plt>: 0x00007ffff7e1c5b0
先ほどの i proc map の出力の一部を貼ります。0x00007ffff7e1c5b0 は、2行目の実行可能な x の付いてるところですね。libc なので、printf関数の実装が格納されていると思われます。
0x7ffff7dca000 0x7ffff7df0000 0x26000 0x0 r--p /usr/lib/x86_64-linux-gnu/libc.so.6 0x7ffff7df0000 0x7ffff7f45000 0x155000 0x26000 r-xp /usr/lib/x86_64-linux-gnu/libc.so.6 0x7ffff7f45000 0x7ffff7f98000 0x53000 0x17b000 r--p /usr/lib/x86_64-linux-gnu/libc.so.6 0x7ffff7f98000 0x7ffff7f9c000 0x4000 0x1ce000 r--p /usr/lib/x86_64-linux-gnu/libc.so.6 0x7ffff7f9c000 0x7ffff7f9e000 0x2000 0x1d2000 rw-p /usr/lib/x86_64-linux-gnu/libc.so.6
ここまでの内容をまとめます。まず、plt(hello_hello.out に含まれている 16byteのプログラムだった)には、got.plt(hello_hello.out に含まれている、共有ライブラリをコールするためのアドレスを格納したテーブルだった)からアドレスを取得してジャンプする処理と、1回目の printf関数で実行されていた _dl_runtime_resolve_xsave というアドレス解決をしてくれそうな関数にジャンプする処理が配置されていました。
2回目の printf関数では、同じように、plt に入って、同様に、got.plt からアドレスを取得しましたが、この got.plt に格納されたアドレスが、printf関数のアドレスに変化していました。1回目でアドレス解決して、got.plt に printf関数のアドレスを格納してくれたので、2回目以降は直接 printf関数にジャンプできるようになったということです。
今回確認したのは、plt と got.plt でした。しかし、GOT領域は、got と got.plt というセクションのことです。plt.got という領域がありますが、ここについては、今は分からないので、調査できたら、ここに追記します。
Partial RELROとFULL RELROについて
RELRO は、GOT の書き込みを禁止するセキュリティ機構です。
コンパイルオプションの -Wl,-z,relro は、リンカに RELROセクションを作るように指示します。また、コンパイルオプションの -Wl,-z,now は、リンカにプログラム起動時にすべてのシンボルの解決を行うよう指示します。
以下に、3通りを試しました。コンパイルオプションに何も指定しない場合は、Partial RELRO になっていました。この結果から、checksec の出力を Full RELRO にするには、-Wl,-z,now を指定するだけでいいということになります。
$ gcc -g -Wl,-z,relro -Wl,-Map=hello_hello_relro.map -o hello_hello_relro.out hello_hello.c $ ../../tools/checksec.sh-2.7.1/checksec --file=./hello_hello_relro.out RELRO STACK CANARY NX PIE RPATH RUNPATH Symbols FORTIFY Fortified Fortifiable FILE Partial RELRO No canary found NX enabled PIE enabled No RPATH No RUNPATH 39 Symbols No 0 1 ./hello_hello_relro.out $ gcc -g -Wl,-z,now -Wl,-Map=hello_hello_now.map -o hello_hello_now.out hello_hello.c $ ../../tools/checksec.sh-2.7.1/checksec --file=./hello_hello_now.out RELRO STACK CANARY NX PIE RPATH RUNPATH Symbols FORTIFY Fortified Fortifiable FILE Full RELRO No canary found NX enabled PIE enabled No RPATH No RUNPATH 39 Symbols No 0 1 ./hello_hello_now.out $ gcc -g -Wl,-z,relro -Wl,-z,now -Wl,-Map=hello_hello_relro_now.map -o hello_hello_relro_now.out hello_hello.c $ ../../tools/checksec.sh-2.7.1/checksec --file=./hello_hello_relro_now.out RELRO STACK CANARY NX PIE RPATH RUNPATH Symbols FORTIFY Fortified Fortifiable FILE Full RELRO No canary found NX enabled PIE enabled No RPATH No RUNPATH 39 Symbols No 0 1 ./hello_hello_relro_now.out
最初に、コンパイルオプションの relro について調べます。relro を指定してない通常のプログラムと比較します。MAPファイルと逆アセンブラで比較しました。結果を見る限り、コンパイルオプションの relro を付けても変化はありませんでした。上の結果でも relro は影響が見られなかったので、そういうものかもしれません。ChatGPT に聞いたところ、relro はデフォルトのオプションで、relro を無効にするには、-z norelro を指定する必要があるそうです。
$ objdump -M intel -d hello_hello.out > hello_hello.s $ objdump -M intel -d hello_hello_relro.out > hello_hello_relro.s $ diff hello_hello.s hello_hello_relro.s --- hello_hello.s 2024-09-14 16:37:21.724997508 +0900 +++ hello_hello_relro.s 2024-09-14 16:37:10.970791296 +0900 @@ -1,5 +1,5 @@ -hello_hello.out: file format elf64-x86-64 +hello_hello_relro.out: file format elf64-x86-64 $ diff hello_hello.map hello_hello_relro.map --- hello_hello.map 2024-09-13 20:24:36.926962157 +0900 +++ hello_hello_relro.map 2024-09-13 22:50:58.807749077 +0900 @@ -6,7 +6,7 @@ As-needed library included to satisfy reference by file (symbol) -libc.so.6 /tmp/ccVKmQjn.o (malloc@@GLIBC_2.2.5) +libc.so.6 /tmp/cc5qA2Go.o (malloc@@GLIBC_2.2.5) Discarded input sections @@ -19,7 +19,7 @@ .note.gnu.property 0x0000000000000000 0x20 /usr/lib/gcc/x86_64-linux-gnu/12/crtbeginS.o .note.GNU-stack - 0x0000000000000000 0x0 /tmp/ccVKmQjn.o + 0x0000000000000000 0x0 /tmp/cc5qA2Go.o .note.GNU-stack 0x0000000000000000 0x0 /usr/lib/gcc/x86_64-linux-gnu/12/crtendS.o .note.gnu.property @@ -37,7 +37,7 @@ LOAD /usr/lib/gcc/x86_64-linux-gnu/12/../../../x86_64-linux-gnu/Scrt1.o LOAD /usr/lib/gcc/x86_64-linux-gnu/12/../../../x86_64-linux-gnu/crti.o LOAD /usr/lib/gcc/x86_64-linux-gnu/12/crtbeginS.o -LOAD /tmp/ccVKmQjn.o +LOAD /tmp/cc5qA2Go.o LOAD /usr/lib/gcc/x86_64-linux-gnu/12/libgcc.a LOAD /usr/lib/gcc/x86_64-linux-gnu/12/libgcc_s.so START GROUP @@ -179,7 +179,7 @@ .text 0x0000000000001082 0x0 /usr/lib/gcc/x86_64-linux-gnu/12/../../../x86_64-linux-gnu/crti.o *fill* 0x0000000000001082 0xe .text 0x0000000000001090 0xb9 /usr/lib/gcc/x86_64-linux-gnu/12/crtbeginS.o - .text 0x0000000000001149 0x13c /tmp/ccVKmQjn.o + .text 0x0000000000001149 0x13c /tmp/cc5qA2Go.o 0x0000000000001149 sub 0x000000000000118d main .text 0x0000000000001285 0x0 /usr/lib/gcc/x86_64-linux-gnu/12/crtendS.o @@ -201,7 +201,7 @@ *(.rodata .rodata.* .gnu.linkonce.r.*) .rodata.cst4 0x0000000000002000 0x4 /usr/lib/gcc/x86_64-linux-gnu/12/../../../x86_64-linux-gnu/Scrt1.o 0x0000000000002000 _IO_stdin_used - .rodata 0x0000000000002004 0x59 /tmp/ccVKmQjn.o + .rodata 0x0000000000002004 0x59 /tmp/cc5qA2Go.o .rodata1 *(.rodata1) @@ -220,7 +220,7 @@ .eh_frame 0x00000000000020c8 0x40 /usr/lib/gcc/x86_64-linux-gnu/12/../../../x86_64-linux-gnu/Scrt1.o .eh_frame 0x0000000000002108 0x18 /usr/lib/gcc/x86_64-linux-gnu/12/../../../x86_64-linux-gnu/Scrt1.o 0x30 (size before relaxing) - .eh_frame 0x0000000000002120 0x40 /tmp/ccVKmQjn.o + .eh_frame 0x0000000000002120 0x40 /tmp/cc5qA2Go.o 0x58 (size before relaxing) .eh_frame 0x0000000000002160 0x4 /usr/lib/gcc/x86_64-linux-gnu/12/crtendS.o *(.eh_frame.*) @@ -334,7 +334,7 @@ .data.rel.local 0x0000000000004018 0x8 /usr/lib/gcc/x86_64-linux-gnu/12/crtbeginS.o 0x0000000000004018 __dso_handle - .data 0x0000000000004020 0x0 /tmp/ccVKmQjn.o + .data 0x0000000000004020 0x0 /tmp/cc5qA2Go.o .data 0x0000000000004020 0x0 /usr/lib/gcc/x86_64-linux-gnu/12/crtendS.o .data 0x0000000000004020 0x0 /usr/lib/gcc/x86_64-linux-gnu/12/../../../x86_64-linux-gnu/crtn.o @@ -359,7 +359,7 @@ .bss 0x0000000000004020 0x0 /usr/lib/gcc/x86_64-linux-gnu/12/../../../x86_64-linux-gnu/Scrt1.o .bss 0x0000000000004020 0x0 /usr/lib/gcc/x86_64-linux-gnu/12/../../../x86_64-linux-gnu/crti.o .bss 0x0000000000004020 0x1 /usr/lib/gcc/x86_64-linux-gnu/12/crtbeginS.o - .bss 0x0000000000004021 0x0 /tmp/ccVKmQjn.o + .bss 0x0000000000004021 0x0 /tmp/cc5qA2Go.o .bss 0x0000000000004021 0x0 /usr/lib/gcc/x86_64-linux-gnu/12/crtendS.o .bss 0x0000000000004021 0x0 /usr/lib/gcc/x86_64-linux-gnu/12/../../../x86_64-linux-gnu/crtn.o *(COMMON) @@ -406,7 +406,7 @@ *(.comment) .comment 0x0000000000000000 0x1f /usr/lib/gcc/x86_64-linux-gnu/12/crtbeginS.o 0x20 (size before relaxing) - .comment 0x000000000000001f 0x20 /tmp/ccVKmQjn.o + .comment 0x000000000000001f 0x20 /tmp/cc5qA2Go.o .comment 0x000000000000001f 0x20 /usr/lib/gcc/x86_64-linux-gnu/12/crtendS.o .gnu.build.attributes @@ -427,29 +427,29 @@ .debug_aranges 0x0000000000000000 0x30 *(.debug_aranges) .debug_aranges - 0x0000000000000000 0x30 /tmp/ccVKmQjn.o + 0x0000000000000000 0x30 /tmp/cc5qA2Go.o .debug_pubnames *(.debug_pubnames) .debug_info 0x0000000000000000 0x1ad *(.debug_info .gnu.linkonce.wi.*) - .debug_info 0x0000000000000000 0x1ad /tmp/ccVKmQjn.o + .debug_info 0x0000000000000000 0x1ad /tmp/cc5qA2Go.o .debug_abbrev 0x0000000000000000 0x10d *(.debug_abbrev) - .debug_abbrev 0x0000000000000000 0x10d /tmp/ccVKmQjn.o + .debug_abbrev 0x0000000000000000 0x10d /tmp/cc5qA2Go.o .debug_line 0x0000000000000000 0x93 *(.debug_line .debug_line.* .debug_line_end) - .debug_line 0x0000000000000000 0x93 /tmp/ccVKmQjn.o + .debug_line 0x0000000000000000 0x93 /tmp/cc5qA2Go.o .debug_frame *(.debug_frame) .debug_str 0x0000000000000000 0xdf *(.debug_str) - .debug_str 0x0000000000000000 0xdf /tmp/ccVKmQjn.o + .debug_str 0x0000000000000000 0xdf /tmp/cc5qA2Go.o 0x11e (size before relaxing) .debug_loc @@ -483,7 +483,7 @@ 0x0000000000000000 0x7a *(.debug_line_str) .debug_line_str - 0x0000000000000000 0x7a /tmp/ccVKmQjn.o + 0x0000000000000000 0x7a /tmp/cc5qA2Go.o 0xb2 (size before relaxing) .debug_loclists @@ -511,4 +511,4 @@ *(.note.GNU-stack) *(.gnu_debuglink) *(.gnu.lto_*) -OUTPUT(hello_hello.out elf64-x86-64) +OUTPUT(hello_hello_relro.out elf64-x86-64)
次に、コンパイルオプションの now について調べます。now を指定してない通常のプログラムと比較します。ChatGPT によると、デフォルトは now が指定されない(デフォルトは、-z lazy を指定した遅延バインドを許可した状態)とのことです。
MAPファイルを比較したところ、差異が多く、全部は貼れませんが、セクションのサイズが異なるところだけ貼ります。dynamic というセクションと、got のセクションのサイズが異なっていました。
また、ちょっと分かりにくいのですが、それぞれの .got 以降の行に注目します。now を指定しない方は、.got が 0x3fb8 から始まり、0x30 のサイズがあり(0x3fb8+0x30=0x3fe8)、次に、.got.plt が 0x3fe8 から始まり、0x28 のサイズがあります(0x3fe8+0x28=0x4010)。0x555555554000 から 0x555555558000 までは書き込み禁止であり、0x555555558000 以降は書き込み可能です。つまり、.got.plt の末尾の 16byte だけが書き込み可能ということになります。
一方、now を指定した方は、.got が 0x3fa8 から始まり、0x58 のサイズがあるだけです(0x3fa8+0x58=0x4000)。.got.plt があるように見えますが、これはサブセクションというらしく(from ChatGPT)、.got の中に含まれています。つまり、now を指定した方は、.got.plt というセクションは無くなっていて、.got に含まれる形になっていました。readelfコマンドの -S オプションで確認したところ、.got.plt というセクションはありませんでした。GOT領域は、書き込み禁止ということになります。
/* hello_hello.map */ .dynamic 0x0000000000003dd8 0x1e0 *(.dynamic) .dynamic 0x0000000000003dd8 0x1e0 /usr/lib/gcc/x86_64-linux-gnu/12/../../../x86_64-linux-gnu/Scrt1.o 0x0000000000003dd8 _DYNAMIC .got 0x0000000000003fb8 0x30 *(.got) .got 0x0000000000003fb8 0x30 /usr/lib/gcc/x86_64-linux-gnu/12/../../../x86_64-linux-gnu/Scrt1.o *(.igot) 0x0000000000003fe8 . = DATA_SEGMENT_RELRO_END (., (SIZEOF (.got.plt) >= 0x18)?0x18:0x0) .got.plt 0x0000000000003fe8 0x28 *(.got.plt) .got.plt 0x0000000000003fe8 0x28 /usr/lib/gcc/x86_64-linux-gnu/12/../../../x86_64-linux-gnu/Scrt1.o 0x0000000000003fe8 _GLOBAL_OFFSET_TABLE_ *(.igot.plt) .data 0x0000000000004010 0x10 /* hello_hello_now.map */ .dynamic 0x0000000000003db8 0x1f0 *(.dynamic) .dynamic 0x0000000000003db8 0x1f0 /usr/lib/gcc/x86_64-linux-gnu/12/../../../x86_64-linux-gnu/Scrt1.o 0x0000000000003db8 _DYNAMIC .got 0x0000000000003fa8 0x58 *(.got.plt) .got.plt 0x0000000000003fa8 0x28 /usr/lib/gcc/x86_64-linux-gnu/12/../../../x86_64-linux-gnu/Scrt1.o 0x0000000000003fa8 _GLOBAL_OFFSET_TABLE_ *(.igot.plt) *(.got) .got 0x0000000000003fd0 0x30 /usr/lib/gcc/x86_64-linux-gnu/12/../../../x86_64-linux-gnu/Scrt1.o *(.igot) 0x0000000000004000 . = DATA_SEGMENT_RELRO_END (., 0x0)
逆アセンブラを比較すると、これも差異が多くて貼れませんが、コードとしては一緒で、アドレスがズレていただけのようでした。
続いて、コンパイルオプションの now を指定した方を GDB で確認します。1回目の printf@plt まで進めました。先ほどの now を指定しなかったプログラムでは、0x555555555030 で、printf@got.plt に格納されていたアドレスは、次の行の 0x555555555036 でしたが、now を指定したプログラムでは、1回目の printf関数から、0x7ffff7e1c5b0(printf関数の実装が配置されたアドレス)が格納されていました。コンパイルオプションの now は、プログラム起動時に全てのシンボルを解決するという影響が確認できました。
[-------------------------------------code-------------------------------------] 0x555555555021: xor eax,0x2f8a 0x555555555026: jmp QWORD PTR [rip+0x2f8c] # 0x555555557fb8 0x55555555502c: nop DWORD PTR [rax+0x0] => 0x555555555030 <printf@plt>: jmp QWORD PTR [rip+0x2f8a] # 0x555555557fc0 <printf@got.plt> | 0x555555555036 <printf@plt+6>: push 0x0 | 0x55555555503b <printf@plt+11>: jmp 0x555555555020 | 0x555555555040 <__isoc99_scanf@plt>: jmp QWORD PTR [rip+0x2f82] # 0x555555557fc8 <__isoc99_scanf@got.plt> | 0x555555555046 <__isoc99_scanf@plt+6>: push 0x1 |-> 0x7ffff7e1c5b0 <__printf>: sub rsp,0xd8 0x7ffff7e1c5b7 <__printf+7>: mov QWORD PTR [rsp+0x28],rsi 0x7ffff7e1c5bc <__printf+12>: mov QWORD PTR [rsp+0x30],rdx 0x7ffff7e1c5c1 <__printf+17>: mov QWORD PTR [rsp+0x38],rcx JUMP is taken
i proc map を確認します。printf関数のアドレスが格納されている 0x555555557fc0 は、書き込みのフラグがなく、読み取り専用になっています。これによって、GOT を書き込む攻撃を防いでいるということになります。
gdb-peda$ i proc map process 397724 Mapped address spaces: Start Addr End Addr Size Offset Perms objfile 0x555555554000 0x555555555000 0x1000 0x0 r--p /home/user/svn/experiment/c/hello_hello_now.out 0x555555555000 0x555555556000 0x1000 0x1000 r-xp /home/user/svn/experiment/c/hello_hello_now.out 0x555555556000 0x555555557000 0x1000 0x2000 r--p /home/user/svn/experiment/c/hello_hello_now.out 0x555555557000 0x555555558000 0x1000 0x2000 r--p /home/user/svn/experiment/c/hello_hello_now.out 0x555555558000 0x555555559000 0x1000 0x3000 rw-p /home/user/svn/experiment/c/hello_hello_now.out 0x555555559000 0x55555557a000 0x21000 0x0 rw-p [heap] 0x7ffff7dc7000 0x7ffff7dca000 0x3000 0x0 rw-p 0x7ffff7dca000 0x7ffff7df0000 0x26000 0x0 r--p /usr/lib/x86_64-linux-gnu/libc.so.6 0x7ffff7df0000 0x7ffff7f45000 0x155000 0x26000 r-xp /usr/lib/x86_64-linux-gnu/libc.so.6 0x7ffff7f45000 0x7ffff7f98000 0x53000 0x17b000 r--p /usr/lib/x86_64-linux-gnu/libc.so.6 0x7ffff7f98000 0x7ffff7f9c000 0x4000 0x1ce000 r--p /usr/lib/x86_64-linux-gnu/libc.so.6 0x7ffff7f9c000 0x7ffff7f9e000 0x2000 0x1d2000 rw-p /usr/lib/x86_64-linux-gnu/libc.so.6 0x7ffff7f9e000 0x7ffff7fab000 0xd000 0x0 rw-p 0x7ffff7fc3000 0x7ffff7fc5000 0x2000 0x0 rw-p 0x7ffff7fc5000 0x7ffff7fc9000 0x4000 0x0 r--p [vvar] 0x7ffff7fc9000 0x7ffff7fcb000 0x2000 0x0 r-xp [vdso] 0x7ffff7fcb000 0x7ffff7fcc000 0x1000 0x0 r--p /usr/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2 0x7ffff7fcc000 0x7ffff7ff1000 0x25000 0x1000 r-xp /usr/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2 0x7ffff7ff1000 0x7ffff7ffb000 0xa000 0x26000 r--p /usr/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2 0x7ffff7ffb000 0x7ffff7ffd000 0x2000 0x30000 r--p /usr/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2 0x7ffff7ffd000 0x7ffff7fff000 0x2000 0x32000 rw-p /usr/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2 0x7ffffffde000 0x7ffffffff000 0x21000 0x0 rw-p [stack]
now が付いてないプログラムの方の got.plt の領域を GDB で確認します。1回目のprintf関数に飛んだところです。0x555555558000 が対象のアドレスで、書き込みフラグが付いていることが確認できます。
[-------------------------------------code-------------------------------------] 0x555555555021: xor eax,0x2fca 0x555555555026: jmp QWORD PTR [rip+0x2fcc] # 0x555555557ff8 0x55555555502c: nop DWORD PTR [rax+0x0] => 0x555555555030 <printf@plt>: jmp QWORD PTR [rip+0x2fca] # 0x555555558000 <printf@got.plt> | 0x555555555036 <printf@plt+6>: push 0x0 | 0x55555555503b <printf@plt+11>: jmp 0x555555555020 | 0x555555555040 <__isoc99_scanf@plt>: jmp QWORD PTR [rip+0x2fc2] # 0x555555558008 <__isoc99_scanf@got.plt> | 0x555555555046 <__isoc99_scanf@plt+6>: push 0x1 |-> 0x555555555036 <printf@plt+6>: push 0x0 0x55555555503b <printf@plt+11>: jmp 0x555555555020 0x555555555040 <__isoc99_scanf@plt>: jmp QWORD PTR [rip+0x2fc2] # 0x555555558008 <__isoc99_scanf@got.plt> 0x555555555046 <__isoc99_scanf@plt+6>: push 0x1 JUMP is taken gdb-peda$ i proc map process 397764 Mapped address spaces: Start Addr End Addr Size Offset Perms objfile 0x555555554000 0x555555555000 0x1000 0x0 r--p /home/user/svn/experiment/c/hello_hello.out 0x555555555000 0x555555556000 0x1000 0x1000 r-xp /home/user/svn/experiment/c/hello_hello.out 0x555555556000 0x555555557000 0x1000 0x2000 r--p /home/user/svn/experiment/c/hello_hello.out 0x555555557000 0x555555558000 0x1000 0x2000 r--p /home/user/svn/experiment/c/hello_hello.out 0x555555558000 0x555555559000 0x1000 0x3000 rw-p /home/user/svn/experiment/c/hello_hello.out 0x555555559000 0x55555557a000 0x21000 0x0 rw-p [heap] 0x7ffff7dc7000 0x7ffff7dca000 0x3000 0x0 rw-p 0x7ffff7dca000 0x7ffff7df0000 0x26000 0x0 r--p /usr/lib/x86_64-linux-gnu/libc.so.6 0x7ffff7df0000 0x7ffff7f45000 0x155000 0x26000 r-xp /usr/lib/x86_64-linux-gnu/libc.so.6 0x7ffff7f45000 0x7ffff7f98000 0x53000 0x17b000 r--p /usr/lib/x86_64-linux-gnu/libc.so.6 0x7ffff7f98000 0x7ffff7f9c000 0x4000 0x1ce000 r--p /usr/lib/x86_64-linux-gnu/libc.so.6 0x7ffff7f9c000 0x7ffff7f9e000 0x2000 0x1d2000 rw-p /usr/lib/x86_64-linux-gnu/libc.so.6 0x7ffff7f9e000 0x7ffff7fab000 0xd000 0x0 rw-p 0x7ffff7fc3000 0x7ffff7fc5000 0x2000 0x0 rw-p 0x7ffff7fc5000 0x7ffff7fc9000 0x4000 0x0 r--p [vvar] 0x7ffff7fc9000 0x7ffff7fcb000 0x2000 0x0 r-xp [vdso] 0x7ffff7fcb000 0x7ffff7fcc000 0x1000 0x0 r--p /usr/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2 0x7ffff7fcc000 0x7ffff7ff1000 0x25000 0x1000 r-xp /usr/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2 0x7ffff7ff1000 0x7ffff7ffb000 0xa000 0x26000 r--p /usr/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2 0x7ffff7ffb000 0x7ffff7ffd000 0x2000 0x30000 r--p /usr/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2 0x7ffff7ffd000 0x7ffff7fff000 0x2000 0x32000 rw-p /usr/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2 0x7ffffffde000 0x7ffffffff000 0x21000 0x0 rw-p [stack]
これまでの調べで、got の領域について推測ができます。got.plt は実行中にシンボル解決を行い、一部は書き込み可能な領域であるが、got はプログラム起動時にシンボル解決(リロケーション)を行い読み取り専用の領域であるということです。
もう少し確認してみます。now を指定していない普通のプログラムで、GDB を起動して、以下は、malloc関数の中に入ったところです。
printf関数とは異なり、1回しか呼ばれないからなのか、最初から、libc のアドレスが格納されています。また、少し下に見えている __cxa_finalize という関数も、最初から libc のアドレスが格納されています。そもそも、0x555555557000 から 0x555555558000 までは読み取り専用です。
[-------------------------------------code-------------------------------------] 0x555555555040 <__isoc99_scanf@plt>: jmp QWORD PTR [rip+0x2fc2] # 0x555555558008 <__isoc99_scanf@got.plt> 0x555555555046 <__isoc99_scanf@plt+6>: push 0x1 0x55555555504b <__isoc99_scanf@plt+11>: jmp 0x555555555020 => 0x555555555050 <malloc@plt>: jmp QWORD PTR [rip+0x2f7a] # 0x555555557fd0 | 0x555555555056 <malloc@plt+6>: xchg ax,ax | 0x555555555058 <__cxa_finalize@plt>: jmp QWORD PTR [rip+0x2f82] # 0x555555557fe0 | 0x55555555505e <__cxa_finalize@plt+6>: xchg ax,ax | 0x555555555060 <_start>: xor ebp,ebp |-> 0x7ffff7e62860 <__GI___libc_malloc>: push r12 0x7ffff7e62862 <__GI___libc_malloc+2>: push rbp 0x7ffff7e62863 <__GI___libc_malloc+3>: push rbx 0x7ffff7e62864 <__GI___libc_malloc+4>: mov rbx,rdi JUMP is taken gdb-peda$ x/g 0x555555557fe0 0x555555557fe0: 0x00007ffff7e07f40
だいぶ長くなりましたが、Partial RELRO について、だいぶ分かった気がします。ごく一部の GOT が書き込み禁止ということでしたが、もしかしたら、一度しか呼ばれない関数は書き込み禁止なのかもしれません。ですが、大部分の GOT は書き込み可能なんだと思います。
STACK CANARY(SSP)について具体的に確認する
冒頭で調べた通り、デフォルトでは、STACK CANARY(スタックカナリア)は無効でした。スタック保護ということで、SSP(Stack Smashing Protection)とも呼ばれます。というよりは、SSP の手段の一つが STACK CANARY かもしれませんね。ここでは、SSP と呼ぶことにします。
SSP を有効にする方法は、いくつかあるようですが、まずは、一番簡単なやつにします。-fstack-protector を指定してみました。checksec の結果が、Canary found に変わりました。逆アセンブラを確認したところ、main関数には、チェックが入っていましたが、sub関数にはチェックが入っていませんでした。
$ gcc -g -fstack-protector -Wl,-Map=hello_hello_ssp.map -o hello_hello_ssp.out hello_hello.c $ ../../tools/checksec.sh-2.7.1/checksec --file=./hello_hello_ssp.out RELRO STACK CANARY NX PIE RPATH RUNPATH Symbols FORTIFY Fortified Fortifiable FILE Partial RELRO Canary found NX enabled PIE enabled No RPATH No RUNPATH 40 Symbols No 0 1 ./hello_hello_ssp.out $ objdump -M intel -d hello_hello_ssp.out > hello_hello_ssp.s
続いて、もう少し SSP を強化した -fstack-protector-all をやってみます。checksec は上と同じ結果でした。こちらは、main関数に加えて、sub関数もスタックカナリアのチェックが入っていました。
$ gcc -g -fstack-protector-all -Wl,-Map=hello_hello_ssp_all.map -o hello_hello_ssp_all.out hello_hello.c $ ../../tools/checksec.sh-2.7.1/checksec --file=./hello_hello_ssp_all.out RELRO STACK CANARY NX PIE RPATH RUNPATH Symbols FORTIFY Fortified Fortifiable FILE Partial RELRO Canary found NX enabled PIE enabled No RPATH No RUNPATH 40 Symbols No 0 1 ./hello_hello_ssp_all.out $ objdump -M intel -d hello_hello_ssp_all.out > hello_hello_ssp_all.s
sub関数の 逆アセンブラを表示します。1164 の行で、乱数の値(FSセグメントのオフセット 0x28)を RAX に設定していて、116d の行で、スタックに格納しています。sub関数の終了直前の 11aa の行で、スタックから格納しておいた値を RDX に取り出し、11ae の行で、乱数の値と RDX を引き算して、11ae の行で、イコール(スタックカナリアに変化がない)なら leave命令にジャンプし、スタックカナリアに変化があったら __stack_chk_fail@plt を呼び出し、プログラムを終了させます。
FSセグメントとは、x86 のプロセッサが持つセグメントレジスタで管理されたセグメント(メモリ)のことだそうです。
0000000000001159 <sub>: 1159: 55 push rbp 115a: 48 89 e5 mov rbp,rsp 115d: 48 83 ec 20 sub rsp,0x20 1161: 89 7d ec mov DWORD PTR [rbp-0x14],edi 1164: 64 48 8b 04 25 28 00 mov rax,QWORD PTR fs:0x28 116b: 00 00 116d: 48 89 45 f8 mov QWORD PTR [rbp-0x8],rax 1171: 31 c0 xor eax,eax 1173: 48 8d 05 8a 0e 00 00 lea rax,[rip+0xe8a] # 2004 <_IO_stdin_used+0x4> 117a: 48 89 c7 mov rdi,rax 117d: b8 00 00 00 00 mov eax,0x0 1182: e8 b9 fe ff ff call 1040 <printf@plt> 1187: 48 8d 45 f4 lea rax,[rbp-0xc] 118b: 48 89 c6 mov rsi,rax 118e: 48 8d 05 7d 0e 00 00 lea rax,[rip+0xe7d] # 2012 <_IO_stdin_used+0x12> 1195: 48 89 c7 mov rdi,rax 1198: b8 00 00 00 00 mov eax,0x0 119d: e8 ae fe ff ff call 1050 <__isoc99_scanf@plt> 11a2: 8b 55 f4 mov edx,DWORD PTR [rbp-0xc] 11a5: 8b 45 ec mov eax,DWORD PTR [rbp-0x14] 11a8: 01 d0 add eax,edx 11aa: 48 8b 55 f8 mov rdx,QWORD PTR [rbp-0x8] 11ae: 64 48 2b 14 25 28 00 sub rdx,QWORD PTR fs:0x28 11b5: 00 00 11b7: 74 05 je 11be <sub+0x65> 11b9: e8 72 fe ff ff call 1030 <__stack_chk_fail@plt> 11be: c9 leave 11bf: c3 ret
スタックカナリア(SSP)については以上です。
NXについて具体的に確認する
NX(No eXecute)は、メモリ領域に置かれたコードを実行できなくします。Windowsでは DEP(Data Execution Prevention)と呼ばれるそうです。デフォルトで有効になっていました。無効にするには、コンパイルオプション -z execstack を付けると出来るようです。やってみたところ、NX disabled に変化しました。
$ gcc -g -z execstack -Wl,-Map=hello_hello_nx.map -o hello_hello_nx.out hello_hello.c $ ../../tools/checksec.sh-2.7.1/checksec --file=./hello_hello_nx.out RELRO STACK CANARY NX PIE RPATH RUNPATH Symbols FORTIFY Fortified Fortifiable FILE Partial RELRO No canary found NX disabled PIE enabled No RPATH No RUNPATH 39 Symbols No 0 1 ./hello_hello_nx.out
NX disabled になったプログラムを GDB で確認します。最後の行の [stack] に注目します。rwxp と実行可能になっています。ちなみに、NX enabled の通常のプログラムは、この記事の冒頭の結果を見てみると、0x7ffffffde000 0x7ffffffff000 0x21000 0x0 rw-p [stack] となっており、実行はできません。
$ gdb -q hello_hello_nx.out gdb-peda$ i proc map process 398709 Mapped address spaces: Start Addr End Addr Size Offset Perms objfile 0x555555554000 0x555555555000 0x1000 0x0 r--p /home/user/svn/experiment/c/hello_hello_nx.out 0x555555555000 0x555555556000 0x1000 0x1000 r-xp /home/user/svn/experiment/c/hello_hello_nx.out 0x555555556000 0x555555557000 0x1000 0x2000 r--p /home/user/svn/experiment/c/hello_hello_nx.out 0x555555557000 0x555555558000 0x1000 0x2000 r--p /home/user/svn/experiment/c/hello_hello_nx.out 0x555555558000 0x555555559000 0x1000 0x3000 rw-p /home/user/svn/experiment/c/hello_hello_nx.out 0x7ffff7dc7000 0x7ffff7dca000 0x3000 0x0 rw-p 0x7ffff7dca000 0x7ffff7df0000 0x26000 0x0 r--p /usr/lib/x86_64-linux-gnu/libc.so.6 0x7ffff7df0000 0x7ffff7f45000 0x155000 0x26000 r-xp /usr/lib/x86_64-linux-gnu/libc.so.6 0x7ffff7f45000 0x7ffff7f98000 0x53000 0x17b000 r--p /usr/lib/x86_64-linux-gnu/libc.so.6 0x7ffff7f98000 0x7ffff7f9c000 0x4000 0x1ce000 r--p /usr/lib/x86_64-linux-gnu/libc.so.6 0x7ffff7f9c000 0x7ffff7f9e000 0x2000 0x1d2000 rw-p /usr/lib/x86_64-linux-gnu/libc.so.6 0x7ffff7f9e000 0x7ffff7fab000 0xd000 0x0 rw-p 0x7ffff7fc3000 0x7ffff7fc5000 0x2000 0x0 rw-p 0x7ffff7fc5000 0x7ffff7fc9000 0x4000 0x0 r--p [vvar] 0x7ffff7fc9000 0x7ffff7fcb000 0x2000 0x0 r-xp [vdso] 0x7ffff7fcb000 0x7ffff7fcc000 0x1000 0x0 r--p /usr/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2 0x7ffff7fcc000 0x7ffff7ff1000 0x25000 0x1000 r-xp /usr/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2 0x7ffff7ff1000 0x7ffff7ffb000 0xa000 0x26000 r--p /usr/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2 0x7ffff7ffb000 0x7ffff7ffd000 0x2000 0x30000 r--p /usr/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2 0x7ffff7ffd000 0x7ffff7fff000 0x2000 0x32000 rw-p /usr/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2 0x7ffffffde000 0x7ffffffff000 0x21000 0x0 rwxp [stack]
NX については以上です。
PIEとASLRについて具体的に確認する
現在は、普通にビルドすると、PIE が有効になっていて、実行するプログラムの配置アドレスは変化します。ASLR は、Linux なら通常は 2 になってるそうで、上で言った通り、共有ライブラリ、ヒープ、スタックの配置アドレスをランダム化します。
具体的に確認してみます。実行するプログラム、スタック、ヒープ、共有ライブラリのアドレスの全てが、確かに変化しています。
$ ./hello_hello.out main: 0x558b2bd4018d buf: 0x7ffdfa3c3df0 mbuf: 0x558b2cd972a0 malloc: 0x7f5973187860 input data: 1 input data2: 2 result: 3 $ ./hello_hello.out main: 0x563e7e40d18d buf: 0x7ffc769104a0 mbuf: 0x563e7fd652a0 malloc: 0x7efd5c029860 input data: 2 input data2: 3 result: 5
ASLR を一時的に 1 に変更してみます。1 でも、全て変化しています。
$ sudo su # cat /proc/sys/kernel/randomize_va_space 2 # sysctl -w kernel.randomize_va_space=1 kernel.randomize_va_space = 1 # cat /proc/sys/kernel/randomize_va_space 1 # exit $ ./hello_hello.out main: 0x55bf00e6518d buf: 0x7fffbf786810 mbuf: 0x55bf00e692a0 malloc: 0x7f386ad62860 input data: 1 input data2: 2 result: 3 $ ./hello_hello.out main: 0x55c26238418d buf: 0x7fffcde31f50 mbuf: 0x55c2623882a0 malloc: 0x7f3cd7205860 input data: 1 input data2: 3 result: 4
では、0 にして、同じように確認してみます。全て一致しました。実行プログラムは変化するかな?と思いましたが、一致しました。
$ sudo su # sysctl -w kernel.randomize_va_space=0 kernel.randomize_va_space = 0 # cat /proc/sys/kernel/randomize_va_space 0 #exit $ ./hello_hello.out main: 0x55555555518d buf: 0x7fffffffdf30 mbuf: 0x5555555592a0 malloc: 0x7ffff7e62860 input data: 1 input data2: 2 result: 3 $ ./hello_hello.out main: 0x55555555518d buf: 0x7fffffffdf30 mbuf: 0x5555555592a0 malloc: 0x7ffff7e62860 input data: 3 input data2: 4 result: 7
ASLR は、元の 2 に戻しておきます。
$ sudo su # sysctl -w kernel.randomize_va_space=2 kernel.randomize_va_space = 2 # cat /proc/sys/kernel/randomize_va_space 2 # exit
一方で、PIE を無効にした場合も確認してみます。-no-pie を付けると、PIE を無効にできます。
checksec を実行すると、No PIE ということで、PIE を無効化できています。実行してみると、上と比べて、ずいぶん小さいアドレスになりました。実行するプログラムは同じアドレスに配置されていますが、それ以外の領域は変化しています。
ASLR を 0 にしたときは、-no-pie を付けなくても、実行するプログラムも同じアドレスに配置されました。アドレスを見ると、同じアドレスですが、大きなアドレスに配置されていましたので、ASLR の効果は出ていることが分かります。PIE は ASLR が 0 ではないことが前提のセキュリティ機構なのかもしれません。
$ gcc -g -no-pie -Wl,-Map=hello_hello_nopie.map -o hello_hello_nopie.out hello_hello.c $ ../../tools/checksec.sh-2.7.1/checksec --file=./hello_hello_nopie.out RELRO STACK CANARY NX PIE RPATH RUNPATH Symbols FORTIFY Fortified Fortifiable FILE Partial RELRO No canary found NX enabled No PIE No RPATH No RUNPATH 37 Symbols No 0 1 ./hello_hello_nopie.out $ ./hello_hello_nopie.out main: 0x40118a buf: 0x7ffeb1e5e4d0 mbuf: 0x5fd2a0 malloc: 0x7fad9d01d860 input data: 1 input data2: 2 result: 3 $ ./hello_hello_nopie.out main: 0x40118a buf: 0x7ffd6570c2d0 mbuf: 0x21102a0 malloc: 0x7fb018f7a860 input data: 2 input data2: 3 result: 5
おわりに
今回は、checksec の結果を理解するために、いろいろ調べたり、実際に GDB で実行して確認をしました。だいぶ理解が進んだ気がします。
checksec が対応していないセキュリティ機構もあるらしいので、また分かったらこの記事に追記しようと思います。
今回は、ChatGPT にロゴを作ってもらいました。1日2回まで無料アカウントでも作ってもらえるそうです。今回は、運よく1回でいい感じの画像を生成してくれました。
お願いした内容は「checksec というツールのロゴ(PNG画像)を 200x200 のサイズで作ってください」です(笑)。PNG画像とお願いしましたが、webpという拡張子の画像ファイルが作られました。オンラインのツールでPNG画像に簡単に変換できました。
最後になりましたが、エンジニアグループのランキングに参加中です。
気楽にポチッとよろしくお願いいたします🙇
今回は以上です!
最後までお読みいただき、ありがとうございました。