一步一步學ROP之linux_x86篇

来源:http://www.cnblogs.com/alisecurity/archive/2016/07/06/5646656.html
-Advertisement-
Play Games

ROP的全稱為Return-oriented programming(返回導向編程),這是一種高級的記憶體攻擊技術可以用來繞過現代操作系統的各種通用防禦(比如記憶體不可執行和代碼簽名等)。雖然現在大家都在用64位的操作系統,但是想要扎實的學好ROP還是得從基礎的x86系統開始,但看官請不要著急。 ...


一步一步學ROP之linux_x86篇

作者:蒸米@阿裡聚安全

 

一、序

ROP的全稱為Return-oriented programming(返回導向編程),這是一種高級的記憶體攻擊技術可以用來繞過現代操作系統的各種通用防禦(比如記憶體不可執行和代碼簽名等)。雖然現在大家都在用64位的操作系統,但是想要扎實的學好ROP還是得從基礎的x86系統開始,但看官請不要著急,在隨後的教程中我們還會帶來linux_x64以及android (arm)方面的ROP利用方法,歡迎大家繼續學習。

小編備註:文中涉及代碼可在文章最後的github鏈接找到。

 

二、Control Flow Hijack 程式流劫持

比較常見的程式流劫持就是棧溢出,格式化字元串攻擊和堆溢出了。通過程式流劫持,攻擊者可以控制PC指針從而執行目標代碼。為了應對這種攻擊,系統防禦者也提出了各種防禦方法,最常見的方法有DEP(堆棧不可執行),ASLR(記憶體地址隨機化),Stack Protector(棧保護)等。但是如果上來就部署全部的防禦,初學者可能會覺得無從下手,所以我們先從最簡單的沒有任何保護的程式開始,隨後再一步步增加各種防禦措施,接著再學習繞過的方法,循序漸進。

首先來看這個有明顯緩衝區溢出的程式:

 

這裡我們用

#bash
gcc -fno-stack-protector -z execstack -o level1 level1.c

 

這個命令編譯程式。-fno-stack-protector和-z execstack這兩個參數會分別關掉DEP和Stack Protector。同時我們在shell中執行:

這幾個指令。執行完後我們就關掉整個linux系統的ASLR保護。

接下來我們開始對目標程式進行分析。首先我們先來確定溢出點的位置,這裡我推薦使用pattern.py這個腳本來進行計算。我們使用如下命令:

來生成一串測試用的150個位元組的字元串:

隨後我們使用gdb ./level1調試程式。

我們可以得到記憶體出錯的地址為0x37654136。隨後我們使用命令:

就可以非常容易的計算出PC返回值的覆蓋點為140個位元組。我們只要構造一個”A”*140+ret字元串,就可以讓pc執行ret地址上的代碼了。

接下來我們需要一段shellcode,可以用msf生成,或者自己反編譯一下。

這裡我們使用一段最簡單的執行execve ("/bin/sh")命令的語句作為shellcode。

溢出點有了,shellcode有了,下一步就是控制PC跳轉到shellcode的地址上:

[shellcode][“AAAAAAAAAAAAAA”….][ret]
^------------------------------------------------|

 

對初學者來說這個shellcode地址的位置其實是一個坑。因為正常的思維是使用gdb調試目標程式,然後查看記憶體來確定shellcode的位置。但當你真的執行exp的時候你會發現shellcode壓根就不在這個地址上!這是為什麼呢?原因是gdb的調試環境會影響buf在記憶體中的位置,雖然我們關閉了ASLR,但這隻能保證buf的地址在gdb的調試環境中不變,但當我們直接執行./level1的時候,buf的位置會固定在別的地址上。怎麼解決這個問題呢?

最簡單的方法就是開啟core dump這個功能。

開啟之後,當出現記憶體錯誤的時候,系統會生成一個core dump文件在tmp目錄下。然後我們再用gdb查看這個core文件就可以獲取到buf真正的地址了。

因為溢出點是140個位元組,再加上4個位元組的ret地址,我們可以計算出buffer的地址為$esp-144。通過gdb的命令 “x/10s $esp-144”,我們可以得到buf的地址為0xbffff290。

OK,現在溢出點,shellcode和返回值地址都有了,可以開始寫exp了。寫exp的話,我強烈推薦pwntools這個工具,因為它可以非常方便的做到本地調試和遠程攻擊的轉換。本地測試成功後只需要簡單的修改一條語句就可以馬上進行遠程攻擊。

最終本地測試代碼如下:

執行exp:

接下來我們把這個目標程式作為一個服務綁定到伺服器的某個埠上,這裡我們可以使用socat這個工具來完成,命令如下:

隨後這個程式的IO就被重定向到10001這個埠上了,並且可以使用 nc 127.0.0.1 10001來訪問我們的目標程式服務了。

因為現在目標程式是跑在socat的環境中,exp腳本除了要把p = process('./level1')換成p = remote('127.0.0.1',10001) 之外,ret的地址還會發生改變。解決方法還是採用生成core dump的方案,然後用gdb調試core文件獲取返回地址。然後我們就可以使用exp進行遠程溢出啦!

 

三、Ret2libc – Bypass DEP 通過ret2libc繞過DEP防護

現在我們把DEP打開,依然關閉stack protector和ASLR。編譯方法如下:

這時候我們如果使用level1的exp來進行測試的話,系統會拒絕執行我們的shellcode。如果你通過sudo cat /proc/[pid]/maps查看,你會發現level1的stack是rwx的,但是level2的stack卻是rw的。

level1: bffdf000-c0000000 rw-p 00000000 00:00 0 [stack]
level2: bffdf000-c0000000 rwxp 00000000 00:00 0 [stack]

 

那麼如何執行shellcode呢?我們知道level2調用了libc.so,並且libc.so里保存了大量可利用的函數,我們如果可以讓程式執行system(“/bin/sh”)的話,也可以獲取到shell。既然思路有了,那麼接下來的問題就是如何得到system()這個函數的地址以及”/bin/sh”這個字元串的地址。

如果關掉了ASLR的話,system()函數在記憶體中的地址是不會變化的,並且libc.so中也包含”/bin/sh”這個字元串,並且這個字元串的地址也是固定的。那麼接下來我們就來找一下這個函數的地址。這時候我們可以使用gdb進行調試。然後通過print和find命令來查找system和”/bin/sh”字元串的地址。

我們首先在main函數上下一個斷點,然後執行程式,這樣的話程式會載入libc.so到記憶體中,然後我們就可以通過”print system”這個命令來獲取system函數在記憶體中的位置,隨後我們可以通過” print __libc_start_main”這個命令來獲取libc.so在記憶體中的起始位置,接下來我們可以通過find命令來查找”/bin/sh”這個字元串。這樣我們就得到了system的地址0xb7e5f460以及"/bin/sh"的地址0xb7f81ff8。下麵我們開始寫exp:

要註意的是system()後面跟的是執行完system函數後要返回地址,接下來才是”/bin/sh”字元串的地址。因為我們執行完後也不打算乾別的什麼事,所以我們就隨便寫了一個0xdeadbeef作為返回地址。下麵我們測試一下exp:

OK。測試成功。

 

四、ROP– Bypass DEP and ASLR 通過ROP繞過DEP和ASLR防護

接下來我們打開ASLR保護。

現在我們再回頭測試一下level2的exp,發現已經不好用了。

如果你通過sudo cat /proc/[pid]/maps或者ldd查看,你會發現level2的libc.so地址每次都是變化的。

那麼如何解決地址隨機化的問題呢?思路是:我們需要先泄漏出libc.so某些函數在記憶體中的地址,然後再利用泄漏出的函數地址根據偏移量計算出system()函數和/bin/sh字元串在記憶體中的地址,然後再執行我們的ret2libc的shellcode。既然棧,libc,heap的地址都是隨機的。我們怎麼才能泄露出libc.so的地址呢?方法還是有的,因為程式本身在記憶體中的地址並不是隨機的,如圖所示:

 

Linux記憶體隨機化分佈圖

所以我們只要把返回值設置到程式本身就可執行我們期望的指令了。首先我們利用objdump來查看可以利用的plt函數和函數對應的got表:

我們發現除了程式本身的實現的函數之外,我們還可以使用read@plt()和write@plt()函數。但因為程式本身並沒有調用system()函數,所以我們並不能直接調用system()來獲取shell。但其實我們有write@plt()函數就夠了,因為我們可以通過write@plt ()函數把write()函數在記憶體中的地址也就是write.got給列印出來。既然write()函數實現是在libc.so當中,那我們調用的write@plt()函數為什麼也能實現write()功能呢? 這是因為linux採用了延時綁定技術,當我們調用write@plit()的時候,系統會將真正的write()函數地址link到got表的write.got中,然後write@plit()會根據write.got 跳轉到真正的write()函數上去。(如果還是搞不清楚的話,推薦閱讀《程式員的自我修養 - 鏈接、裝載與庫》這本書)

因為system()函數和write()在libc.so中的offset(相對地址)是不變的,所以如果我們得到了write()的地址並且擁有目標伺服器上的libc.so就可以計算出system()在記憶體中的地址了。然後我們再將pc指針return回vulnerable_function()函數,就可以進行ret2libc溢出攻擊,並且這一次我們知道了system()在記憶體中的地址,就可以調用system()函數來獲取我們的shell了。

使用ldd命令可以查看目標程式調用的so庫。隨後我們把libc.so拷貝到當前目錄,因為我們的exp需要這個so文件來計算相對地址:

最後exp如下:

接著我們使用socat把level2綁定到10003埠:

最後執行我們的exp:

 

五、小結

本章簡單介紹了ROP攻擊的基本原理,由於篇幅原因,我們會在隨後的文章中會介紹更多的攻擊技巧:如何利用工具尋找gadgets,如何在不知道對方libc.so版本的情況下計算offset;如何繞過Stack Protector等。歡迎大家到時繼續學習。另外本文提到的所有源代碼和工具都可以從我的github下載:https://github.com/zhengmin1989/ROP_STEP_BY_STEP

 

六、參考文獻

  1. The geometry of innocent flesh on the bone: return-into-libc without function calls (on the x86)
  2. picoCTF 2013: https://github.com/picoCTF/2013-Problems
  3. Smashing The Stack For Fun And Profit: http://phrack.org/issues/49/14.html
  4. 程式員的自我修養
  5. ROP輕鬆談

 

作者:蒸米@阿裡聚安全,更多技術文章,請訪問阿裡聚安全博客


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 一. 準備工作 1. 下載ubuntu鏡像文件:ubuntu-12.04.3-desktop-amd64.iso(4G及以上記憶體建議64位),註意這個amd並不是指amd晶元。 2. 下載硬碟分區工具:Acronis Disk Director 11 3. 下載系統引導軟體:EasyBCD2.2 二 ...
  • 我會用幾篇博客總結一下在Linux中進程之間通信的幾種方法,我會把這個開頭的摘要部分在這個系列的每篇博客中都打出來 進程之間通信的方式 管道 消息隊列 信號 信號量 共用存儲區 套接字(socket) 進程間通信(四)—共用存儲區傳送門:http://www.cnblogs.com/lenomire ...
  • 藉助於開源百度雲客戶端`bypy`實現將網站備份的數據上傳至網盤存儲。 ...
  • 添加網卡之後,網卡無法被正確的識別和使用排錯方法 查看/etc/udev/rules.d/70-persistent-net.rules的內容,該文件中可以查看到新添加的網卡的MAC地址 修改/etc/sysconfig/network-scripts/ifcfg-eth0的網卡的MAC地址為正確的 ...
  • 最近因為工作的需要看了看powershell相關的知識,個人總結了一點有關於powershell遠程連接需要做的步驟,希望對別人有所幫助。 使用powershell遠程連接,需要進行 設備的配置: 1. 開啟ps遠程管理:enable-psremoting -force (如果出現提示: 按 y 鍵 ...
  • Windows 7 與 Vmware Ubuntu 15.10_64 共用文件夾 ...
  • FreeBSD用戶手冊學習筆記 freeBSD安裝:http://my.oschina.net/lsgx/blog/540980 第一章 介紹 1.1 FreeBSD歷史和簡介 FreeBSD 自身的源代碼是完全公開的,所以可以對系統進行最大程度的定製。 FreeBSD 項目的目標是無附加條件地提供 ...
  • 安裝openssh-serversudo apt-get install openssh-server 查看server是否啟動: ps -ef |grep ssh 如果看到/usr/sbin/sshd -D,說明服務已經啟動,否則服務尚未啟動,那麼需要啟動server: /etc/init.d/s ...
一周排行
    -Advertisement-
    Play Games
  • 示例項目結構 在 Visual Studio 中創建一個 WinForms 應用程式後,項目結構如下所示: MyWinFormsApp/ │ ├───Properties/ │ └───Settings.settings │ ├───bin/ │ ├───Debug/ │ └───Release/ ...
  • [STAThread] 特性用於需要與 COM 組件交互的應用程式,尤其是依賴單線程模型(如 Windows Forms 應用程式)的組件。在 STA 模式下,線程擁有自己的消息迴圈,這對於處理用戶界面和某些 COM 組件是必要的。 [STAThread] static void Main(stri ...
  • 在WinForm中使用全局異常捕獲處理 在WinForm應用程式中,全局異常捕獲是確保程式穩定性的關鍵。通過在Program類的Main方法中設置全局異常處理,可以有效地捕獲並處理未預見的異常,從而避免程式崩潰。 註冊全局異常事件 [STAThread] static void Main() { / ...
  • 前言 給大家推薦一款開源的 Winform 控制項庫,可以幫助我們開發更加美觀、漂亮的 WinForm 界面。 項目介紹 SunnyUI.NET 是一個基於 .NET Framework 4.0+、.NET 6、.NET 7 和 .NET 8 的 WinForm 開源控制項庫,同時也提供了工具類庫、擴展 ...
  • 說明 該文章是屬於OverallAuth2.0系列文章,每周更新一篇該系列文章(從0到1完成系統開發)。 該系統文章,我會儘量說的非常詳細,做到不管新手、老手都能看懂。 說明:OverallAuth2.0 是一個簡單、易懂、功能強大的許可權+可視化流程管理系統。 有興趣的朋友,請關註我吧(*^▽^*) ...
  • 一、下載安裝 1.下載git 必須先下載並安裝git,再TortoiseGit下載安裝 git安裝參考教程:https://blog.csdn.net/mukes/article/details/115693833 2.TortoiseGit下載與安裝 TortoiseGit,Git客戶端,32/6 ...
  • 前言 在項目開發過程中,理解數據結構和演算法如同掌握蓋房子的秘訣。演算法不僅能幫助我們編寫高效、優質的代碼,還能解決項目中遇到的各種難題。 給大家推薦一個支持C#的開源免費、新手友好的數據結構與演算法入門教程:Hello演算法。 項目介紹 《Hello Algo》是一本開源免費、新手友好的數據結構與演算法入門 ...
  • 1.生成單個Proto.bat內容 @rem Copyright 2016, Google Inc. @rem All rights reserved. @rem @rem Redistribution and use in source and binary forms, with or with ...
  • 一:背景 1. 講故事 前段時間有位朋友找到我,說他的窗體程式在客戶這邊出現了卡死,讓我幫忙看下怎麼回事?dump也生成了,既然有dump了那就上 windbg 分析吧。 二:WinDbg 分析 1. 為什麼會卡死 窗體程式的卡死,入口門檻很低,後續往下分析就不一定了,不管怎麼說先用 !clrsta ...
  • 前言 人工智慧時代,人臉識別技術已成為安全驗證、身份識別和用戶交互的關鍵工具。 給大家推薦一款.NET 開源提供了強大的人臉識別 API,工具不僅易於集成,還具備高效處理能力。 本文將介紹一款如何利用這些API,為我們的項目添加智能識別的亮點。 項目介紹 GitHub 上擁有 1.2k 星標的 C# ...