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
\neq

道高一尺,魔高一丈

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,227