《黑客攻防-系統實戰》--shellcode


shellcode

  shellcode 是一組可注入的指令,可以在被攻擊得到程序內運行,因為shellcode要直接操作寄存器和程序函數,所以通常用匯編語言編寫並被翻譯為十六進制操作碼,因此不能用高級語言編寫shellcode, 即使細微的差別有可能導致shellcode無法准確執行,這些導致編寫shellcode難度的原因

  shellcode最初的作用是漏洞利用程序的特殊部分,破解漏洞就是預先把shellcode注入到緩沖區,然后欺騙目標程序執行它

 

理解系統調用

  寫shellcode的目的就是想讓目標程序以不同於設計者預期的方式運行,而操縱程序的方式之一就是強制它產生系統調用,通過系統調用可以獲取一些特權操作,訪問系統內核,

  調用系統調用方法:1)使用C庫包裝(libc) 2)使用匯編指令(把適當的參數加載到寄存器,然后調用軟中斷)執行系統調用

 

系統調用的過程

  linux環境程序通過int 0x80軟中斷來執行系統調用,程序執行int  0x80時,CPU切換到內核模式並執行相應的系統調用,使用fastcall約定, 提高寄存器的使用率

__fastcall調用的主要特點就是快,因為它是通過寄存器來傳送參數的(實際上,它用ECX和EDX傳送前兩個雙字(DWORD)或更小的參數,
      剩下的參數仍舊自右向左壓棧傳送,被調用的函數在返回前清理傳送參數的內存棧),在函數名修飾約定方面,它和前兩者均不同。
      __fastcall方式的函數采用寄存器傳遞參數,VC將函數編譯后會在函數名前面加上”@”前綴,在函數名后加上”@”和參數的字節數。 __cdecl (The C
default calling convention)即C調用約定按從右至左的順序壓參數入棧,由調用者把參數彈出棧。對於傳送參數的內存棧是由調用者來維護的。
      另外,在函數名修飾約定方面也有所不同。 _cdecl是C和C++程序的缺省調用方式。每一個調用它的函數都包含清空堆棧的代碼,所以產生的可執行文件大小
      會比調用_stdcall函數的大 __stdcall是Pascal方式清理C方式壓棧,通常用於Win32 Api中,函數采用從右到左的壓棧方式,自己在退出時清空堆棧。VC將函數編譯后會在函數名前面加上下划線前綴,
      在函數名后加上”@”和參數的字節數。
int f(void *p) –>> _f@4(在外部匯編語言里可以用這個名字引用這個函數) 參考: https://blog.csdn.net/sunriver2000/article/details/84913380

調用過程:

  1)系統調用編號載入EAX

  2)把系統調用參數壓入棧中(注意:最多支持6個參數,分別保存在:EBX,ECX,EDX,ESI,EDI,EBP,  超過六個,通過第一個指定數據結構傳遞)

  3)執行int 0x80指令

  4)CPU切換到內核模式

  5)執行系統調用

舉個例子:

  

1 #include <stdio.h>
2 
3 int main()
4 {
5     exit(0);
6     return 0;
7 }

exit() -> _exit()

反匯編看一下:

 

 

 call   *%gs:0x10     ;系統調用從這里進入,會調用到int80

mov 0x4(%esp), %ebx; 退出把系統調用參數加載到ebx, 系統調用之前壓棧

編寫shellcode

注意:

  shellcode大小:由於較小的shellcode 可以注入更多的緩沖區,可以用來攻擊更多的程序,所以要使得shellcode盡量保持簡單,緊湊

  當攻擊問題程序的時候,需要把shellcode復制到緩存區,另外還要加上調用shellcode的指令, 所以必須要小

分析程序:

  前面exit系統調用分析主要完成三個動作:

  1)把0放到EBX

  2)把1放到EAX

  3)執行int  0x80 指令來產生系統調用

1. hello.c

 1 section .data                           ;section declaration
 2 msg     db      "Hello, world!",0xA     ;our dear string
 3 len     equ     $ - msg                 ;length of our dear string
 4 section .text                           ;section declaration
 5                        ;we must export the entry point to the ELF linker or
 6    global _start       ;loader. They conventionally recognize _start as their
 7                        ;entry point. Use ld -e foo to override the default.
 8 _start:
 9 ;write our string to stdout
10        mov     eax,4   ;system call number (sys_write)
11        mov     ebx,1   ;first argument: file handle (stdout)
12        mov     ecx,msg ;second argument: pointer to message to write
13        mov     edx,len ;third argument: message length
14        int     0x80    ;call kernel
15 ;and exit
16        mov     eax,1 ;system call number (sys_exit) 17  xor ebx,ebx ;first syscall argument: exit code 18        int     0x80    ;call kernel

2. objdump -d hello

 1 foobar:     fiobjdump -d foobarle format elf32-i386
 2 
 3 
 4 Disassembly of section .text:
 5 
 6 08049000 <.text>:
 7  8049000:    b8 04 00 00 00           mov    $0x4,%eax
 8  8049005:    bb 01 00 00 00           mov    $0x1,%ebx
 9  804900a:    b9 00 a0 04 08           mov    $0x804a000,%ecx
10  804900f:    ba 0e 00 00 00           mov    $0xe,%edx
11  8049014:    cd 80                    int    $0x80
12  8049016:    b8 01 00 00 00           mov    $0x1,%eax
13  804901b:    31 db                    xor    %ebx,%ebx
14  804901d:    cd 80                    int    $0x80

 

3. 把操作碼寫到字符串數組中執行

char shellcode[] = "\xb8\x04\x00\x00\x00"

        "\xbb\x01\x00\x00\x00"

        "\xb9\x00\xa0\x04\x08"

        "\xba\x0e\x00\x00\x00"

        "\xcd\x80"

        "\xb8\x01\x00\x00\x00"

        "\x31\xdb"

        "\xcd\x80"

 

gcc -o wack wack.c -m32 -z execstack

 

跳板技術(轉https://zhuanlan.zhihu.com/p/88459547

程序每次運行后在內存中的指令地址都是變化的,所以shellcode入口地址也是動態的,所以為了能夠動態找到shellcode的位置,引入了跳板技術。

如圖所示,左邊表示存儲返回地址的棧幀填充為shellcode入口地址,這種方式下次運行時入口地址將發生變化導致失敗,右邊表示跳板技術后,通過esp來定位shellcode,這種方式可保證下次運行exp依然有效。

 

 

跳板技術是用來動態跳轉shellcode的,shellcode代碼需要從函數返回后esp的棧頂位置開始,然后函數返回到JMP ESP指令處,指令執行后跳到esp位置進入shellcode入口。

注: 對於不同的返回指令的不同,函數返回后esp的指向也有所不同。一般執行ret指令后esp+4,此時shellcode放在存放返回地址的棧幀的下一位置。若是ret N指令,執行后esp+4+N,則shellcode需要放在計算出的對應位置處才行。JMP ESP指令的地址要已知,在xp中JMP ESP可以通過加載kernel32.dll、user32.dll、mfc32.dll等這些經常被加載到內存中的庫中尋找,一般地址都是確定的。利用C實現查找代碼如下:

 1 # include <stdio.h>
 2 #include <windows.h>
 3  
 4 main()
 5 {
 6     HINSTANCE hLib;
 7     hLib = LoadLibrary("user32.dll");
 8     if(!hLib)
 9     {
10         printf("Load dll error!\n");
11         exit(0);
12     }
13  
14     byte* ptr = (byte*) hLib;
15     int address;
16     int position;
17     bool done_flag = false;
18  
19     for(position=0; !done_flag; position++)
20     {
21         try
22         {
23             if(ptr[position] == 0xFF && ptr[position+1] == 0xE4)
24             {
25              // jmp esp 的機器碼 為 0xFFE4
26                 address = (int)ptr + position;
27                 printf("Find OPcode at 0x%08lX\n", address);
28             }
29         }
30         catch(...)
31         {
32             address = (int)ptr + position;
33             printf("End of 0x%08lX\n", address);
34             done_flag = true;
35         }
36     }
37 }

 

抬高棧頂保護shellcode 

如果shellcode放在返回地址棧幀之前,那么在函數返回后棧頂位置會在shellcode下方,雖然出棧后的數據不被清空,但是卻會受入棧操作的影響,所以shellcode中若存在push操作,很有可能破壞shellcode結構:

 

所以要在shellcode開頭適當先抬高棧頂讓shellcode在棧頂下方,這樣push就不會影響shellcode。

抬高棧頂可以用sub esp, N,N大於shellcode長度即可。

 


免責聲明!

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



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