文章將整理從入門棧溢出到堆house系列的梳理。
0x01棧知識
call
將當前的IP或者CS和IP壓入棧中。
轉移
彙編:call xxx
push IP
jmp xxx
ret
將程序的返回地址彈出到ip寄存器中
程序繼續執行
彙編:
pop IP
有call就要有傳參,32位系統用棧來傳參參數是倒着傳進去也就是最後的參數先push
64位系統用寄存器來傳參前三個參數是用rdi,rsi,rdx
32位函數調用(__cdecl 調用約定)
C語言函數
write(1, "Hello, World!\n", 0xEu);
彙編語言
push 0Eh ; n 傳遞第三個參數
push offset aHelloWorld ; "Hello, World!\n" 第二個參數
push 1 ; fd 第一個參數
call _write
add esp, 10h 在call返回之後平衡堆棧
64位傳參
read(0, &buf,0x200uLL);
mov edx, 200h ; nbytes
mov rsi, rax ; buf
mov edi, 0 ; fd
call _read
運行時程序的棧:
上面高地址處存放的是環境變量和main函數的信息
往下是主調函數的棧內存,包括臨時變量和控制信息(控制信息包括返回地址和ebp等用來保存的寄存器的值)
調用約定:
每個函數調用的時候都會自己平衡自己的堆棧。
每個被調函數被調用的時候先push ebp所以棧裏面會有返回地址和ebp
兩個表
plt表:函數調用的時候先使用的。PLT表中的每一項的-數據內容都是對應的GOT表中一項的地址這個是固定不變的,PLT表中的數據是跳轉到GOT表項的地址。
got表:一個指向真正運行函數首地址的指針。
注意函數
scanf函數
scanf("%d %d",&a,&b);
遇到空格(0x20)停止讀取
read函數
ssize_t read (int fd, void *buf, size_t count);
讀取數據遇到\n(0x0a)結束,\x0a會讀進去
fd爲0從鍵盤讀取
gets函數
gets(str);
輸入遇到\n(0x0a)結束
\x0a不會讀進去
printf函數
printf("%s", i);
輸出直到\x00
write函數
ssize_t write(int fd, const void *buf, size_t nbyte);
fd爲1輸出到顯示器
puts函數
puts(char *)
相當於printf("%s\n",s)
輸出字符串(遇到\x00結束)
strcpy函數
char *strcpy(char *dst, const char *src);
一直複製直到遇到\x00
memcpy函數
void *memcpy(void *dest, const void *src, size_t n);
複製任意內容
strncpy函數
char *strncpy(char *dest,char *src,int size_t n);
如果n<src的長度,只是將src的前n個字符複製到dest的前n個字符,不自動添加'\0'。如果src的長度小於n個字節,則以NULL填充dest直到複製完n個字節
32位利用方式
舉例https://blog.csdn.net/qq_38204481/article/details/80944927
64位利用方式
https://blog.csdn.net/qq_38204481/article/details/80955065
通用rop作爲rop的進階
disassemble __libc_csu_init
0x0000000000400690 <+64>: mov rdx,r13
0x0000000000400693 <+67>: mov rsi,r14
0x0000000000400696 <+70>: mov edi,r15d
0x0000000000400699 <+73>: call QWORD PTR [r12+rbx*8]
0x000000000040069d <+77>: add rbx,0x1
0x00000000004006a1 <+81>: cmp rbx,rbp
0x00000000004006a4 <+84>: jne 0x400690 <__libc_csu_init+64>
0x00000000004006a6 <+86>: add rsp,0x8
0x00000000004006aa <+90>: pop rbx
0x00000000004006ab <+91>: pop rbp
0x00000000004006ac <+92>: pop r12
0x00000000004006ae <+94>: pop r13
0x00000000004006b0 <+96>: pop r14
0x00000000004006b2 <+98>: pop r15
0x00000000004006b4 <+100>: ret
https://blog.csdn.net/qq_38204481/article/details/80984318
0x02保護
-
RELRO
介紹:got表不可寫
繞過方法:hook函數,覆蓋返回地址,導入shellcode -
NX
棧內數據不可執行
不能導入shellcode
繞過方法:ROP -
STACK
介紹:程序會在棧裏面放入一個canary返回時檢測canary是否發生了改變,如果改變會輸出程序名字然後退出。
繞過方法:leak canary,SSP(Stack Smashes Protect),爆破canary,用格式化字符串複寫check_got再觸發。 -
PIE
程序加載地址隨機
SSP
fork函數會複製原進程的環境,然後運行新內容。
當棧被損壞canary會打印出程序的名字,如果用覆蓋掉name程序會打印出需要的數據。
但是這種方法會讓程序退出,所以一般需要伴隨着fork函數
https://blog.csdn.net/qq_38204481/article/details/82318179(GUESS)
leak canary
1.覆蓋掉canary最後的00輸出的時候會把canary輸出.但是要求在泄露之後函數返回之前要獲取shell
2.如果在設置數組數組的時候沒有清空棧空間,保留了上次調用留下的canary也可以泄露,一般需要遞歸。
擡高棧幀
https://tower111.github.io/2018/09/09/%E7%BD%91%E9%BC%8E%E6%9D%AF4-impossible
執行pop之後邏輯上棧內數據不存在但是物理上依然存在。
如果直接通過覆蓋\x00泄露ret2的canary改變了打印出之後會退出,所以可以利用其它函數寫入canary,然後leakret_addr1的canary
GOT表不可寫(RELRO)
大部分函數都有一個hook函數。
以__hook_malloc爲例。
1.得到libc就能得到它的真是加載地址
默認地址設置位0,如果地址處不爲0,執行malloc之前會執行這裏的內容。(一般複寫的是_free_hook函數)
0x03格式化字符串
輸出
printf("%s %d %d %d %x %x\n",buf,a,b,c)
%s會輸出buf指向的內容
三個%d會輸出a,b,c
二個%x會輸出繼續往下的棧空間的內容
buf=test
printf(“%s %d %d %d %x %x”,buf)
如圖反映了上圖的輸出和棧的內存。
分別輸出test 1 2 3 2f 804a000
寫入
%n是一個不經常用到的格式符,它的作用是把前面已經打印的長度寫入某個內存地址
可以看到我們的num值被改爲了100,
這裏可以看出1.用%.100可以控制寫入數據是十進制100(0x64)
2.寫入的地址是%n對應的參數指向的內容
補充
用%2$x(或n)可以實現定點輸出(或輸入)不加$
利用
用%n的字符加上got表數據可以實現複寫
****
0x04堆漏洞
分配過的chunk
- 32位機,8字節對齊,64位機,16字節對齊
- 位P指明前一個chunk的狀態 空閒(0),使用(1)
- prev_size表示前一個chunk的大小
當前chunk釋放時,可用於定位前一個chunk,並將兩個chunk合併爲一個更大的chunk
當P爲1時,prev_size無意義,該區域(4B)可被前一個chunk使用
fast bin
- 在chunk被free的時候如果大小小於0x80會被放入fast bin - 需要注意的是這裏的prive_inuse位不會置0
- 可以在這裏泄露出來heap的地址。(因爲fd指向了下一個chunk的開始)
unsort bin
- 在chunk合併之後大於0x80或者是大於0x80的chunk被釋放之後會放入unsort bin
- 這裏是雙向鏈表連接
- 其中main_arena和libc_base之間的偏移是固定的,可以用來leak libc_base
注意點 - 定義的read函數
可以輸入比size多一位。
可以輸入size之後又加上\x00 - free掉chunk之後指針是否清零
- 是否存在堆溢出
利用方法fastbin attack
注意在申請空間時有一個size驗證。
上圖由
malloc(0x20) malloc(0x20)
free(1) free(2) #到這裏形成上圖第一列
edit(2,my_addr) #形成上圖第二列
(my_addr=0x6020xx
my_addr_size=0x31)#括號內的是說明需要的條件不是代碼
malloc(0x20) #形成上圖第三列
malloc(0x20) #申請到僞造chunk空間
完成上述過程需要條件:
my_addr_size需要滿足
1.0x80以內
2.prive_inuse爲1
https://blog.csdn.net/qq_38204481/article/details/82318179
補充利用FILE結構
struct _IO_FILE_plus
{
_IO_FILE file;
IO_jump_t *vtable;
}
void * vtablt[] = {
1 NULL, // "extra word"
2 NULL, // DUMMY
3 exit, // finish
4 NULL, // overflow
5 NULL, // underflow
6 NULL, // uflow
7 NULL, // pbackfail
8 NULL, // xsputn #printf
9 NULL, // xsgetn
安裝gdb的pwndbg插件用print *(struct _IO_FILE_plus*)0x6020a0
命令可以輸出結構
詳情參考https://ctf-wiki.github.io/ctf-wiki/pwn/linux/io_file/introduction/
舉例參考https://blog.csdn.net/qq_38204481/article/details/82318094(blind)
利用方式double free
上圖形成過程:
free chunk1 free chunk2 free chunk1(途中三個箭頭對應三個操作)
說明
- 所謂的free其實就是數據結構裏面的頭插。就是一些指針的變化
- 達到上圖在經過三次malloc再次malloc就能申請到想要的空間
- 限制:需要一個合適的size
舉例參見https://tower111.github.io/2018/08/30/ISG-babynote/(文中提到的獲取執行流的 方法總結:https://blog.csdn.net/qq_38204481/article/details/82318227)
unlink
unlink的發生: - glibc 判斷這個塊是 small chunk。
- 判斷前向合併,發現前一個 chunk 處於使用狀態,不需要前向合併。
- 判斷後向合併,發現後一個 chunk 處於空閒狀態,需要合併。
- 繼而對 nextchunk 採取 unlink 操作。
可見,要進行unlink的不是剛釋放的chunk而是與這個chunk物理相鄰的chunk
執行函數unlink§ 可以看出:unlink函數知道的只有一個指針,函數的第一步是找到前一個邏輯相鄰的chunk,後一個邏輯相鄰的chunk(其實就是指針的賦值操作,包括上圖描述的unlink過程)
實際上進行unlink的時候會進行一個教研P->fd->bk==P
上圖三列其實佔了同一個空間,也就是地址是重合的,這樣就能滿足上述的校驗
例子參考https://blog.csdn.net/qq_38204481/article/details/82808011
0x05補充堆分配與釋放過程
內存分配
內存釋放