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長度即可。