<menu id="guoca"></menu>
<nav id="guoca"></nav><xmp id="guoca">
  • <xmp id="guoca">
  • <nav id="guoca"><code id="guoca"></code></nav>
  • <nav id="guoca"><code id="guoca"></code></nav>

    棧溢出漏洞利用(繞過ASLR)

    VSole2021-10-09 16:38:28

    原文:https://sploitfun.wordpress.com/2015/05/08/bypassing-aslr-part-iii

    • 原文內容比較精煉,只闡述關鍵思路,沒有具體到一些細節;
    • 原文提供的利用腳本,很多數值需要根據實際環境修改,才能成功執行;
    • 原文文字內容可以直接通過鏈接看到,但其中的圖片,是鏈接到google的,如果訪問不到,可以對照附件中的pdf文件一起看。

    另外需要說明一下,原文只是一個系列(https://sploitfun.wordpress.com/2015/06/26/linux-x86-exploit-development-tutorial-series/)中的一篇文章:

    學習這篇之前,最好先把前面的都搞明白,本文的細節,只針對Part III using GOT overwrite and GOT dereference這篇。

    漏洞程序

    // vuln.c#include <stdio.h>#include <string.h>#include <stdlib.h> int main (int argc, char **argv) {    char buf[256];    int i;    seteuid(getuid());    if(argc < 2) {        puts("Need an argument\n");        exit(-1);    }    strcpy(buf, argv[1]);    printf("%s\nLen:%d\n", buf, (int)strlen(buf));    return 0;}
    

    準備工作:

    # 編譯vuln.c,并修改vuln可執行文件屬性  sudo gcc -fno-stack-protector -o vuln vuln.c  sudo chown root vuln  sudo chgrp root vuln  sudo chmod +s vuln# 打開ASLR  sudo sh -c 'echo 2 > /proc/sys/kernel/randomize_va_space'
    

    程序說明:

    行9:setuid(getuid())

    由于以上步驟,通過chmod +s vuln,給vuln可執行文件添加了粘滯位,這就使普通用戶(比如:jmpcall)執行vuln,在剛進入main()函數時,擁有vuln所屬用戶的權限(即root權限),setuid(getuid())就是將權限改回執行者本身的權限,其實就是給利用增加難度,期望的是,即使被攻擊者拿到shell,也只是個普通的shell,不具有root權限(就等同于沒給vuln添加粘滯位)。

    行14:strcpy(buf, argv[1])

    將命令行參數內容,拷貝到buf[256],沒有限制拷貝長度,從而存在棧溢出漏洞。

    問題分解

    面對一個復雜的問題時,通常需要自上而下、以大化小、分而治之,直接上圖:

    換個方式來說明最終目標的話,就是要讓vuln執行這樣一段代碼:

    char *p = "/bin/sh";char **argv = { p, NULL }; setuid(0);execve(p, argv, NULL);
    

    并且僅僅是通過構造輸入數據達到這個目的,而不是修改vlun.c源碼,重新編譯出新的vlun可執行文件。

    為了方便描述,先假設可以構造出如下堆棧布局:

    這顯然可以使vuln按照上述的期望進行執行,但問題是我們無法向棧內構造這樣的數據,原因如下:

    • 棧中的數據,全都來自于命令行參數,然后由vuln進程strcpy()進去的,所以當中不可能夾雜著'\0'字符;
    • 由于開啟了ASLR,棧的地址和libc.so映射到vuln進程空間的位置,是隨機的,相應的,棧中的"/bin/sh"字符串地址、argv[]數組地址,以及setuid()、execve()函數地址,無法根據上次啟動的信息,事先計算出來。

    不過,這些問題都可以化解:

    • 對于0值,可以通過輸入數據,構造strcpy()執行邏輯(向棧中構造strcpy()函數地址及其所需的參數,后續簡稱"strcpy()構造邏輯"),從有0的地方復制;
    • 對于"/bin/sh"、argv[]數組內容,可以通過strcpy()構造邏輯,將它們寫到一個固定的地方,后續就可以將這些固定的地址值,作為setuid()、execve()參數。關于這個固定地方的選擇,只要保證每次啟動vuln程序,這塊進程空間總是可寫,修改它的內容也不會導致程序掛掉即可,.bss、.data段加載所占的一塊進程空間,就符合這個條件,并且每次加載的位置不受ASLR影響:

    • 對于setuid(),由于vuln.c本身調用過它,編譯器會在vuln可執行文件中生成setuid@PLT這個"中間跳轉函數",通過它也能執行setuid()函數,并且反編譯vuln看到的setuid@PLT地址,就是它運行時的地址。PLT的原理,我之前發過一個帖子詳細介紹過:32位elf格式中的10種重定位類型;
    • 對于execve(),可以通過GOT overwrite的方法執行,它依據的原理是,vuln進程執行到攻擊者構造的邏輯時,GOT[getuid]這個內存單元,存儲的一定已經是getuid()函數的加載地址,另外,execve與getuid加載地址的偏移,和它們在libc.so動態庫文件中的偏移,是一樣的,這樣,通過輸入數據構造邏輯,將這個偏移加到GOT[getuid],并觸發getuid@PLT執行,即可執行execve()。GOT的原理,32位elf格式中的10種重定位類型同樣介紹的很清楚。
    # 查看execve-getuid偏移ldd vulnobjdump -S /lib/i386-linux-gnu/libc.so.6 | grep "getuid"objdump -S /lib/i386-linux-gnu/libc.so.6 | grep "execve"
    

    上述這些,明顯還沒有將問題徹底分解,它們都依賴"通過輸入數據構造的邏輯",比如,具體如何實現:GOT[getuid]+=(execve-getuid)?

    由于gcc編譯時沒有加"-z execstack"選項,即棧所在的內存區間,沒有可執行權限,無法使用往棧里構造shellcode的方法,這種情況下可以使用ROP的方法,從代碼中尋找一些以"ret"指令結束的代碼片段,拼湊出需要的完整邏輯,還是直接上圖:

    目標①:GOT[getuid] += (execve-getuid)

    這個目標需要一條add指令,將execve-getuid(可以根據libc.so事先計算),加到GOT[getuid]所在的內存單元,作者從而找到gadget:"addl %eax, 0x5D5B04C4(%ebx); ret;",將目標轉換成②③;

    目標②:ebx = GOT[getuid]-0x5d5b04c4

    GOT[getuid]運行時地址,根據vuln可執行文件就可以得到:

    objdump -R vuln
    

    從而可以事先計算出ebx的靜態值:ebx = 0xaaa99b40,因此,作者找一個gadget: "popl %ebx; ret;",這樣,通過往棧里構造0xaaa99b40和這個gadget的地址即可達到目的;

    目標③:eax = execve-getuid

    如果能找到gadget: "popl %eax; ...; ret;",再通過往棧里構造execve-getuid(可以根據libc.so事先計算)和這個gadget的地址即可,也就不需要后續的目標④⑤⑥⑦了,可惜的是沒能找到,所以作者就找到圖中的代碼片段,也能實現向eax寄存器賦值的目的,但是"mov 0x34(%esp),%eax"到"ret"指令之間,有call和jne指令,為了跳轉到的代碼片段,別再次修改eax,又分化出2個新目標:目標④保證call指令調用到一個不影響eax值的函數,目標⑤保證jne指令不執行跳轉,因為當前這個代碼片段后續的指令,是不影響eax值的;

    目標④:-0xe0(%ebx,%esi,4) = 0x0804861c

    作者發現,如果上述的call指令,調用_fini函數,可以達到不修改eax的目的,也就是要讓-0xe0(%ebx,%esi,4)這個地址值(注意它前面有*號),存儲的是_fini函數地址0x0804861c:

    objdump -sj .dynamic vuln | grep '1c860408'
    

    通過這樣執行objdump命令,可以發現0x8049f3c這個地址處,存的是0x0804861c,從而又將目標轉換成:-0xe0(%ebx,%esi,4) = 0x0804861c,這可以由目標⑦實現。另外,_fini函數中又會執行call指令,不過call的目標地址是確定的,由目標⑥保證再次call到的函數不修改eax即可;

    目標⑤:edi = esi+1

    目標⑥:(0x804a028) = 0x01

    作者發現,_fini再次call到的函數中,又有條jne指令,如果這條jne指令執行跳轉的話,就可以保證不修改eax,所以找到gadget:"movb $0x00000001, 0x0804A028; addl $0x04, %esp; popl %ebx; popl %ebp; ret;",保證jne跳轉;

    目標⑦:esi = 0x01020101, ebx = 0x8049f3c -(0x01020101*0x4) + 0xe0 = 0x3fc9c18

    通過往棧里構造0x01020101和gadget:"popl %ebx; ret;",往棧里構造0x3fc9c18和gadget:"popl %esi; popl %edi; popl %ebp; ret;",可以滿足:-0xe0(%ebx,%esi,4) = 0x0804861c(ebx可以選其它值,只要保證它本身,以及滿足目標等式的esi值不包含'\0'字節即可)。另外,選擇gadget:"popl %esi; popl %edi; popl %ebp; ret;",是為了一舉兩得,因為它里面包含了"popl %edi"指令,可以順便讓目標⑤的條件得到滿足。

    堆棧布局結果

    不管是寫一段程序,還是學習一段程序,先有一個概括性的圖,往往能事半功倍,所以我給棧的布局,畫了一張高清大圖:

    根據上述的問題分解過程,再去理解為什么要這樣布局,難度就不大了,接下來可以順著這個布局,看一下具體的執行過程:

    gadget7

    指向代碼片段"movb $0x00000001, 0x0804A028; addl $0x04, %esp; popl %ebx; popl %ebp; ret;",并且gadget7所在位置,是main()函數返回地址的存儲位置,這是通過寫溢出覆蓋的,那么,main()函數返回時,就會執行這段指令。使用這個代碼片段,其實是有點“迫不得已”的,真正需要的其實只是"movb $0x00000001, 0x0804A028; ret;",但是沒能在vuln中直接找到。

    gadget7執行后:

    *(0x0804A028) = 0x00000001
    

    另外,"addl $0x04, %esp; popl %ebx; popl %ebp;"使esp向上移動了4*3個字節,所以利用腳本在構造輸入數據時,需要在gadget7后面安排4*3字節的dummy數據,保證gadget6距離gadget7 4*3字節,進而保證gadget7中的"ret"指令,能繼續執行到gadget6,后續還有很多小的代碼片段,都是這樣連續在一起執行的,從而完成一個完整的邏輯,這也正是ROP的原理,以及gadget為什么要以"ret"指令結束的原因。

    gadget6

    指向代碼片段"popl %esi; popl %edi; popl %ebp; ret;",執行gadget7的"ret"指令時,esp指向圖中存儲gadget6的位置,而gadget7的"ret"指令,實際上是將gadget6 pop到eip寄存器,所以執行gadget6時,esp指向的是圖中存儲esi的位置,因此,gadget6執行后:

    esi = 0x01020101edi = 0x01020102
    

    和gadget7同樣的道理,由于沒能在vuln中找到"popl %esi; popl %edi; ret;",所以“迫不得已”使用了"popl %esi; popl %edi; popl %ebp; ret;",為了能繼續執行到gadget5,填充了4個字節的dummy。

    gadget5

    就不再啰嗦了,執行結果:

    ebx = 0x3fc9c18
    

    gadget4

    執行結果:

    eax = 0xfffff530
    

    指向代碼片段"mov 0x34(%esp),%eax; ...; ret;",問題分解階段,已經說明過,需要gadget4~7,是由于沒能找到類似"popl %eax; ...; ret;"這樣的gadget。這里需要注意的是,gadget4不是用pop指令給eax賦值,而是將距離esp上方0x34字節處的值mov給eax,所以利用腳本也必須遵循這一點安排0xfffff530的位置,另外,gadget4指向的是一個函數的內部,相當于跳過了函數頭部對esp的減操作,因此gadget4后續的pop指令,會使esp上移4*11字節,所以要在gadget2之前,需要填充4*11字節的dummy數據。

    gadget2

    gadget4~7,相當于迂回實現了gadget3的作用,與gadget2一起,服務于gadget1,gadget2執行結果:

    ebx = 0xaaa99b40
    

    但是,由于上方正好遇到服務于gadget4的0xfffff530,所以特地讓gadget2指向代碼片段"popl %ebx; popl %ebp; ret;",保證esp能夠跳過0xfffff530的存儲位置,到達gadget1的存儲位置。

    gadget1

    指向代碼片段"addl %eax, 0x5D5B04C4(%ebx); ret;",此時,eax = execve-getuid,0x5D5B04C4(%ebx)=&GOT[getuid],從而達到將GOT[getuid]修改為execve()加載地址的目的。

    不過,到這個時候,離最終目標,還有另外一半路程:觸發setuid(0)和execve()函數的執行。

    根據上半程的"經驗"+棧的詳細布局圖,不難看出,后續邏輯,是找了另外一塊地盤,按照上圖布局,構造了一個遷移棧(兩個布局中的①~⑨標號,是一一對應的"邏輯-結果"),保證"/bin/sh"地址、{ "/bin/sh"地址, NULL }數組地址,都是固定的,從而繞過ASLR保護機制。

    最后說明2點:

    為了遷移到新棧幀,溢出棧的末尾布局如下:

    pr_addr        # leave; ret;cust_base_esp  # 遷移棧地址lr_addr        # popl %ebp; ret;
    

    leave指令等于:

    movl %ebp, %esp;  # 將esp遷到cust_base_esppopl %ebp;        # esp上移4字節,指向setuid()地址存儲位置
    

    "/bin/sh"的復制

    vuln可執行文件中,無法找到一個現成的"/bin/sh"字符串,提供給利用腳本往遷移棧復制,所以只能一個碎片一個碎片的復制,比如先復制個"/bin",再向緊接著的位置復制"/sh",作者提供的利用腳本,是一個字符一個字符復制的。

    我想說明的是怎么去找這些字符,比如我的環境存在這樣的情況:

    "/\00"找不到,但單獨的'/'可以找到,而strcpy()要遇到'\0'字符才會停止復制,所以盡量用能盡快遇到'\0'的那個:

    另外,如果只能找到"h",找不到"h\x00",要額外再復制個'\0',保證"/bin/sh"以0結束(復制getuid@PLT、setuid@PLT地址的過程,同理)。

    利用腳本執行結果演示

    邏輯函數esi
    本作品采用《CC 協議》,轉載必須注明作者和本文鏈接
    32位,調用了很多Win32 api,本次計時器的破解突破口就在這。這些操作本質是通過外設向操作系統發送了一些消息,操作系統通過窗口句柄把消息發送給對應窗口過程函數進行處理進而把處理結果通過窗口再次呈現給用戶。將txt文件還原到文件夾中,程序在43FCE8順序執行。patch文件以后,關閉網絡連接發現仍然可以正常啟動程序,至此成功繞過程序需要聯網的要求。timekey破解開發者給出的限制是Timekey.txt里面的內容需要定期更新,大概是要每一個月更新一次。
    Windows API調用詳解
    2022-04-06 16:07:31
    retn是返回指令,表示已經執行完成。也就是說由eax的值來決定內核API的調用。mov edx,xxx所有通過ntdll調用的函數,給edx所傳遞的值都是一樣的,這里都是0x7FFE0300。
    ?微信小程序逆向分析
    2023-07-03 09:00:42
    WeChatAppEx.exe 版本:2.0.6609.4以融智云考學生端為例。網上已經有關于微信小程序解密的非常優秀的文章,本著學習的目的便不參考相關內容。筆者水平實在有限,如發現紕漏,還請讀者不吝賜教。拓展的,我們分別觀察RSI與RDI指向的內存區域。事實上,這一部分解密過程與微信圖片解密相同。目的是用int8類型的值a3填滿rbp至8個字節,進行8個字節分組異或。至此,尾部部分解密告一段落。
    關于堆噴堆噴射(Heap Spraying)是一種計算機安全攻擊技術,它旨在在進程的堆中創建多個包含惡意負載的內存塊。這種技術允許攻擊者避免需要知道負載確切的內存地址,因為通過廣泛地“噴射”堆,攻擊者可以提高惡意負載被成功執行的機會。
    之前我說Codemeter的反調試很猛,我收回。Scylla Hide調至VMP檔,運行后將爆出的幾個錯誤全部Pass to the Application && Do not suspend or log即可。根據前面的分析,我們知道了Codemeter私有協議中最終要的兩個函數就是encrypt_telegram和decrypt_telegram。服務器接受到客戶端的請求那就必定調用decrypt_telegram解密,向客戶端回復數據必定通過encrypt_telegram解密。因此我們第一步就需要確定這兩個函數。先對decrypt_telegram下斷,跑起來看看誰調用她。查看decrypt_package和encrypt_package的交叉引用,有一個函數同時引用這兩個函數
    剛好404之前有一篇博客寫過這個漏洞,我在它的參考資料里面找到了github上面提供的poc生成代碼,以及j00sean在twitter上面發的6行代碼,以這兩者為基礎對該漏洞進行分析。
    另外需要說明一下,原文只是一個系列(https://sploitfun.wordpress.com/2015/06/26/linux-x86-exploit-development-tutorial-series/)中的一篇文章:
    STATEMENT聲明由于傳播、利用此文所提供的信息而造成的任何直接或者間接的后果及損失,均由使用者本人負責,雷神眾測及文章作者不為此承擔任何責任。雷神眾測擁有對此文章的修改和解釋權。
    假如想在x86平臺運行arm程序,稱arm為source ISA, 而x86為target ISA, 在虛擬化的角度來說arm就是Guest, x86為Host。這種問題被稱為Code-Discovery Problem。每個體系結構對應的helper函數在target/xxx/helper.h頭文件中定義。
    因為這是一個很老的漏洞,網上能搜到的很多分析文章都是基于《漏洞戰爭》這本書完成的,并且其中的大多數只是在進行書中內容的復述。在閱讀書中內容的過程中,作者提到使用TrueType Font Analyzer對ttf文件進行解析時出錯,由此判斷問題出現在glyf表中。
    VSole
    網絡安全專家
      亚洲 欧美 自拍 唯美 另类