Shellcode編程小技巧


工作需要,需要注入其他程序監控一些東西,檢測到的數據通過WM_COPY 消息發送給顯示窗體。(大體是這樣的還沒定稿)

##1 選擇一個框架 ##

tombkeeper/Shellcode_Template_in_C

mattifestation/PIC_Bindshell

框架選擇上,我選擇了第一個,婦科聖手tomkeeper提供的框架,對比發現這個比較簡單。

##2 搭建框架 ##

我使用的是vs10,(vs15我試了下各種錯誤 ,可能是自己笨吧,用管了vs10了)

- 新建個解決方案(名字看自己喜好)

- 解決方案上新添個空的 win32console 工程

- 再在工程上新建個 xxxx.c的文件,把tomkeeper工程中的shellcode.c的內容復制過來

- 編譯試試吧

- 其他的2個工具文件代碼,分別是生成字符串用的工具(str2intarr.c) 和 生成函數hash宏定義的 gethash.c (方法同上)

- 我自己定義了個字符串生成的增強版(如下,我這個是cpp文件,和上面的有所不同),根據 第2個參數 <a/w> 將字符串轉為多字節字符串和寬字節字符串。

#include <Windows.h>

int _tmain(int argc, char* argv[])
{
    if (argc < 2)
    {
        printf("Use %s <a/w> <string> \n",argv[0]);
    }else
    {
        printf("DWORD str[] = {\n\t");

        if (argv[1][0] == 'a')
        {
            int strLen = strlen(argv[2]);
            strLen = (strLen /4 +1)*4;
            int n = strLen /4 ;
            DWORD* pArry = (DWORD*)malloc(strLen);
            ZeroMemory(pArry,strLen);
            memcpy(pArry,argv[2],strlen(argv[2]));
            
            for (int i=0;i<n;i++ )
            {
                
                printf("0x%08x",*(pArry+i));
                if (i != n-1)
                {
                    printf(", ");
                }
                if (i && i%9 ==8)
                {
                    printf("\n\t");
                }
            }
        
        }else
        {
            int strLen = strlen(argv[2]);
            int nAlloc = strLen*2+4;
            DWORD* pWchar = (DWORD*)malloc(nAlloc);
            ZeroMemory(pWchar,nAlloc);
            MultiByteToWideChar(GetACP(),NULL,argv[2],strLen,(WCHAR*)pWchar,strLen);
            int n = (strLen/2+1);
            for (int i=0;i< n;i++)
            {
                printf("0x%08x",*(pWchar+i));
                if (i != n-1)
                {
                    printf(", ");
                }
                if (i && i%9 ==8)
                {
                    printf("\n\t");
                }
            }

        }
        printf("\n};");
        //getchar();
    }
    return 0;
}

##3 編譯選項的設置  ##

編譯選項的設置直接關系到shellcode大小的准確計算,代碼的排列順序,以及代碼的精簡程度,還有 GS。。。 是否被添加。

- 設置為release模式,我就不多說了,大家都會

- 去掉Gs 選項,如果不去掉shellcode()中會有gs檢測函數的插入,這是我們不想要的。

- opt, 調試時我選擇 disable, 發布時我使用 minsize (體積小些)。disable代碼順序是一樣的就是多了不少int3 (cc)。

- linker 下的debuging :generate Debug info 我設置為YES。便於調試,對shellcode無影響

 

 

##4 測試shellcode的正確性 ##

tomkeeper的工程能將shellcode以c數組的形式輸出出來,我增加了個函數可以將 shellcode dump到2進制文件中。

- 利用c數組, char shellcode[] = {0xb8,....}; __asm{ call shellcode} ,這樣的話要注意將 工程的NX關閉(數據執行保護DEP) ,不會的自行補腦

- 第2種將 dump出的文件讀入到申請的可讀可寫可執行的內存中,然后 __asm { call lpMem} .(我用的這種辦法,不用每次進行復制黏貼,編譯,執行)

FILE* file = 0;
    int iRead = 0;


    HANDLE hFile =CreateFile("c:\\temp\\c.sc",GENERIC_WRITE|GENERIC_READ,0,NULL,OPEN_EXISTING,FILE_ATTRIBUTE_NORMAL,NULL);

    fprintf(stdout,"test shellcode ........... \n");
    if (hFile != INVALID_HANDLE_VALUE)
    {
        DWORD dwFileSizeHigh = 0;
        DWORD dwFileSizeLow = GetFileSize(hFile,&dwFileSizeHigh);
        fprintf(stdout,"file size is 0x%x \n",dwFileSizeLow);
        BYTE* lpMem = (BYTE*)VirtualAlloc(NULL,dwFileSizeLow+4,MEM_COMMIT|MEM_RESERVE,PAGE_EXECUTE_READWRITE);
        ZeroMemory(lpMem,dwFileSizeLow+4);
        ReadFile(hFile,lpMem,dwFileSizeLow,&dwFileSizeHigh,NULL);
        printf("should read %d ,readed %d byte \n",dwFileSizeLow,dwFileSizeHigh);
        CloseHandle(hFile);
        //getchar();
        _asm{
            call lpMem
        }
        
    }else
    {
        fprintf(stdout,"open file error [0x%x] \n",GetLastError());
    }

我先把shellcode dump到 c:\\temp\\c.sc 中,在用這個代碼去測試它。

 

##5 小技巧 (排錯、編程 經驗) ##

- 不要用 *1.02 這種浮點算法,會死在這種命令上,那個2B2040絕對會出錯,所以盡量別用了。

000300B3 DF6D D8 FILD QWORD PTR SS:[EBP-28]
000300B6 DC0D 40202B00 FMUL QWORD PTR DS:[2B2040]
000300BC D97D D6 FSTCW WORD PTR SS:[EBP-2A]

- 如何在shellcode后面追加上 配置信息便於測試呢

shellcode會帶着些配置信息很長見的。 一般這些都是后期用工具合並上去的。

對於我們調試每次都去追加修改太麻煩了。所以要用到 #define NAKED __declspec(naked) 和 __asm{ __emit 0xbb }

__emit 可以使數據以代碼的形式寫入到代碼段,也就是shellcode中。記得和 DWORD str[] = {0xabcdef,0xbaddef..}這種技術分開。這樣就實現了任意數據的存入

NAKED 進制優化:當我們使用minsize選項的時候,這些代碼可能就被優化掉,我試過了,所以才用到了NAKED,注意函數必須有 參數,聲明不用實現用。

最后:在永遠不會調用到的地方調用下,要不編譯器一定會給你省略掉

void  StoreConfigData(DWORD dwVoid);

NAKED void  StoreConfigData(DWORD dwVoid)
{ 

__asm{
__emit 0xff 
__emit 0xfe 
__emit 0xff

.}

 我這里專門寫了個小工具將bin文件轉為上面的形式

// bin2emit.cpp : Defines the entry point for the console application.
//

#include "stdafx.h"
#include <Windows.h>

int _tmain(int argc, _TCHAR* argv[])
{
    if (argc < 2)
    {
        printf("Use %s <path of bin> \n",argv[0]);
    }else
    {
        printf("path of bin: %s \n",argv[1]);
        HANDLE hFile = CreateFile(argv[1],GENERIC_WRITE|GENERIC_READ,0,NULL,OPEN_EXISTING,FILE_ATTRIBUTE_NORMAL,NULL);
        if (hFile == INVALID_HANDLE_VALUE)
        {
            printf("open '%s' error \n",argv[1]);
            return 0;
        }
        DWORD dwFileSizeLow,dwFileSizeHigh;
        dwFileSizeLow = GetFileSize(hFile,&dwFileSizeHigh);

        LPVOID lpBuffer = HeapAlloc(GetProcessHeap(),0x08,dwFileSizeLow);

        if (!ReadFile(hFile,lpBuffer,dwFileSizeLow,&dwFileSizeHigh,0))
        {
            CloseHandle(hFile);
            printf("read file '%s' error \n",argv[1]);
            HeapFree(GetProcessHeap(),HEAP_NO_SERIALIZE,lpBuffer);
            return 0;
        }
        CloseHandle(hFile);
        printf("\n\n\n __asm{\n\t");
        for ( int i=0;i < dwFileSizeLow;i++)
        {
            printf("__emit 0x%02x \n\t", *(((LPBYTE)lpBuffer)+i) );
        }
        printf("\n}");
        
    }
    return 0;
}

 

- 函數重定位

我們的shellcode都是經過 call聯系出來的,在調用的時候編譯器已經算好了偏移,所以不存在重定位的問題,但是,向createthread這種將傳入函數地址作為參數的函數就會出先這種問題;

解決的辦法就是要動態計算地址。

//記得-8 
DWORD GetEip()
{
    __asm{
        call get_eip
get_eip:
        pop eax
    }
}
void WINAPI AdjustFunction(struct ADJUSTFUNCTIONS* pFuns)
{
    DWORD dwGetEip;
    
    dwGetEip = GetEip(); 
    dwGetEip -= 8;    //計算得到 GetEip()函數在內存中的地址
    
    pFuns->lpThreadProc = (DWORD)dwGetEip+( (DWORD)ThreadProc- (DWORD)GetEip ); // 以GetEip()函數做參考動態計算出其他 函數的地址
}
// 調用的時候這樣寫
hThread = modules.kernel32.CreateThread(NULL,0,adjustFuns.
lpThreadProc,&param,0,NULL);

 

- 函數動態獲取

開發中發現通過函數hash獲取時會出現和GetProcAddress返回地址不同的問題。出現以上問題有3種可能:

1.hash碰撞了,沒辦法那只能找替換函數了,例如CreateFile 函數,可以看看ntdll重的zw 、nt、rtl、有沒有名稱相似的函數,我們知道很多函數都是層層封裝的,不怕找不到的。再說tk教主的hash計算時帶着i也就是長度進行了計算,大大減小碰撞幾率。

2.函數名搞錯了,注意 xxxA xxxB xxxx 有些函數有A和W版本,你可要看清楚自己要的是哪個,但有些函數沒有A和W區分。還有很多函數是通過lib鏈接的,根本導不出函數如 —beginthreadex,所以還是用 createthread代替吧 。

3. 還有些函數導出地址不是最終真正的函數地址,而是像ntdll.xxxxx的這種形式的內存地址,這樣就還要再次去計算才能得到,而GetProcAddress這個函數應該進行了2次轉換。例如:HeapAlloc是kernel32導出的,通過hash找到的是ntdll!RtlAllocateHeap字符串的地址(大約是這個樣子,反正是個ntdll開通的),所以還是直接去ntdll下找 RtlAllocateHeap 它吧,所以 找函數盡量去ntdll中招吧。

http://www.onlinedown.net/soft/59825.htm

http://www.pc6.com/softview/SoftView_16910.html  dllviewer 可以看看dll導出函數表

 - 初始化局部變量 

struct MODULES modules = {0x00};

這種初始化還是不要有的好,我跟蹤發現這樣會讓執行流程變亂。

還是用自己寫的memset吧。

ps:shellcode真省地方,寫了這么多代碼還沒2K呢。 

# 自我實現一些函數 #

有些函數沒必要動態獲取,太麻煩了。很多函數很簡單,用代碼就能實現

- GetProcessHeap  (測試環境 win7 x64 \win8 x64 \win10 x64)

 

HANDLE WINAPI GetProcessHeap()
{
    __asm{

        mov eax,fs:[0x30]
        mov eax,[eax+0x18]
    }
}

- memcpy

void WINAPI zMemcpy(LPVOID lpDest,LPVOID lpSrc,DWORD dwSize)
{
    if (!lpDest || !lpSrc ||!dwSize)
    {
        _asm int 3
    }
    __asm{
        mov ecx,dwSize
        mov edi,lpDest
        mov esi,lpSrc
        rep movsb
    }
}

 為了兼容x64shellcode開發,這里將內嵌的匯編都去掉了。

最近發現在大數據除法和取余時,vs會嵌入alldiv、allrem等函數,這將直接造成,shellcode跑飛

 


免責聲明!

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



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