buffer_overflow 第3章 筆記6 拼出我們第二個shellcode。

本節將是最後一步了!!!!

在第3章 筆記3 抓取kernel32.dll基址中寫了個自動抓取kernel32.dll的組合語言:

把他改寫成這樣:
// KERNEL32_BASE 為標籤,我們的程式要從這個標籤開始執行。

--------------------------------------------------------------------------------------------------------------------------
KERNEL32_BASE:
     
     xor eax,eax                                  //eax=0  大概是不想用EAX暫存器來指向TEB 所以把EAX變0
     mov ebx,[fs:eax+0x30]               //ebx = &(_PEB)
     mov ebx,[ebx+0x0c]                   //ebx = PEB->Ldr
     add ebx,0x1c                               //ebx = Ldr.InitializtionOrderModuleList
     
KERNEL32_BASE_NEXT_MODULE:
                          
     mov ebx,[ebx]                            //ebx = ebx-> Flink 跳過首先的鏈結串列 到下一個元素
     mov ecx,[ebx+0x08]                  //ecx= 某個DLL 的基底
     mov edx,[ebx+0x20]                  //edx = 該DLL名稱的指標
     cmp [edx+0x18],al                     //比較字串第25個位元是否為0(NULL字元)

     jne KERNEL32_BASE_NEXT_MODULE                              // 不為0就跳LOOP



--------------------------------------------------------------------------------------------------------------------------



到此我們已經找到kernel32.dll的位置了,我們想要在kernel32.dll找到LoadLibraryA函數,找到後加載msvcrt.dll,之後,也必須在msvcrt.dll找到 printf 跟 exit。

所以接下來要寫從dll基底自動抓取想要的函數的組語。


其中,假設在找到kernel32.dll基址後存放在ecx,我們需要把先前計算好的LoadLibaryA雜湊值
0x8e4e0eec給推入堆疊,接著也把ecx推入堆疊,執行call址令會把call的下一行位置也推入堆疊。
--------------------------------------------------------------------------------------------------------------------------
push 0xec0e4e8e                 

push ecx  

call    FIND_FUNCTION
....
...



--------------------------------------------------------------------------------------------------------------------------

FIND_FUNCTION:
    pushad                                               // 將所有暫存器推入堆疊,堆疊加 0x20
    mov ebp,[esp+0x24]                         //將 DLL 基底位址存回 ebp 
    mov eax,[ebp+0x3c]                         // 找到 NT Headers 的相對位址
    mov edx,[ebp+eax+0x78]                 // 找到 Export Directory 的相對位址
    add edx,ebp                                       // 將 Export Directory 的絕對位址存在 edx
    mov ecx,[edx+0x18]                         // 將NumberOfNames的值 存在 ecx
    mov ebx,[edx+0x20]                         // 找到「Names 陣列」的相對位址
    add ebx,ebp                                       // 將「Names 陣列」的絕對位址存在 ebx

FIND_FUNCTION_LOOP:                 //外層迴圈起點,會從「Names 陣列」的最後一個元素往                                                                                                                                                      //前迭代

    jecxz FIND_FUNCTION_END       //如果 ecx == 0 則迴圈結束
    dec ecx                                              // ecx = ecx - 1
    mov esi,[ebx + ecx*4]                 //找到 Names[ecx],就是「Names 陣列」索引值為 ecx 的元素
    add esi,ebp             // 將該元素內容轉化成絕對位址存放於 esi,esi 現在指向一個函式名稱字                                                                                                                                                            //串

                                                                                                                                                          
COMPUTE_HASH:                               //準備計算函式名稱的雜湊值
    xor edi,edi                                           // edi = 0,edi 將會存放雜湊值
    xor eax,eax                                          // eax = 0,eax 將會存放函式名稱中的每一個字元
    cld                                                       // direction_flag = 0

COMPUTE_HASH_LOOP:                  //內層迴圈起點
    lodsb                                         //將 esi 所指向的字串,載入 1 個字元到 eax,並且 esi 位址加 1
    test al,al                                              // eax 是否等於字串的結尾 NULL 字元
    jz COMPUTE_HASH_END              // 如果是,內層迴圈結束,跳到 COMPUTE_HASH_END
    ror edi,0xd                      // 如果否,edi 向右旋轉 13 個位元,等效於 edi=edi>>13|edi<<(32-13)
    add edi,eax                                         // edi = edi + eax
    jmp COMPUTE_HASH_LOOP       // 回到內層迴圈起點

COMPUTE_HASH_END:                   //內層迴圈結束
    cmp edi,[esp+0x28]                          // 比較雜湊值是否符合,若否,跳到下一個元素繼續比
    jnz FIND_FUNCTION_LOOP        //跳到外層迴圈起點
    mov ebx,[edx+0x24]                        // 雜湊值符合,找到「Ordinals 陣列」
    add ebx,ebp                                      // 將「Ordinals 陣列」絕對位址存放於 ebx
    mov cx,[ebx+ecx*2]                        // cx = Ordinals[ecx],cx 是 ecx 的最後 2 個位元組
    mov ebx,[edx+0x1c]                        // 找到「Functions 陣列」
    add ebx,ebp                                      // 將「Functions 陣列」的絕對位址存放於 ebx
    mov eax,[ebx+ecx*4]                      // eax = Functions[ecx],此即為欲尋找的函式的相對位址
    add eax,ebp                                      // 將函式的絕對位址存放在 eax
    mov [esp+0x1c],eax                        //[esp+0x1c] = eax

FIND_FUNCTION_END:
    popad                                               //復原所有的暫存器,eax = [esp+0x1c]
    ret                                                    // 回到當初 call FIND_FUNCTION 的下一行指令位址


--------------------------------------------------------------------------------------------------------------------------

其中在執行 pushas 會把堆疊推高0x20個位元組,會按照eax ,ecx,edx,ebx,esp,ebp,esi,edi依序推入,所以此時堆疊的狀態應該為:





完整的拼圖:






[Section .text]
[BITS 32]
global _start
_start:
     jmp KERNEL32_BASE            //以上是為了讓程式從 標籤KERNEL32_BASE開始
                                          



FIND_FUNCTION:                                                      //FIND_FUNCTION函數
     pushad
     
     mov ebp,[esp+0x24]
     mov eax,[ebp+0x3c]
     
     mov edx,[ebp+eax+0x78]
     add edx,ebp
     
     mov ecx,[edx+0x18]
     
     mov ebx,[edx+0x20]
     add ebx,ebp

FIND_FUNCTION_LOOP:
     jecxz FIND_FUNCTION_END
     dec ecx
     mov esi,[ebx+ecx*4]
     add esi,ebp
     
COMPUTE_HASH:
     xor edi,edi
     xor eax,eax
     cld
     
COMPUTE_HASH_LOOP:
     lodsb
     test al,al
     jz COMPUTE_HASH_END
     ror edi,0xd
     add edi,eax
     jmp COMPUTE_HASH_LOOP
     
COMPUTE_HASH_END:
                 
     cmp edi,[esp+0x28]
     jnz FIND_FUNCTION_LOOP
     
     mov ebx,[edx+0x24]
     add ebx,ebp
     
     mov cx,[ebx+ecx*2]
     
     mov ebx,[edx+0x1c]
     add ebx,ebp
     
     mov eax,[ebx+ecx*4]
     add eax,ebp
     
     mov [esp+0x1c],eax

FIND_FUNCTION_END:
     popad
     ret


KERNEL32_BASE:                                                   //程序從這開始
     
     xor eax,eax
     mov ebx,[fs:eax+0x30]
     mov ebx,[ebx+0x0c]
     add ebx,0x1c
     
KERNEL32_BASE_NEXT_MODULE:
                          
     mov ebx,[ebx]
     mov ecx,[ebx+0x08]
     mov edx,[ebx+0x20]
     cmp [edx+0x18],al
     jne KERNEL32_BASE_NEXT_MODULE             
                                                                                               //找到DLL基底
     push 0xec0e4e8e                                                              //推入LoadLibraryA的雜湊值
     push ecx
     call FIND_FUNCTION                                                   //呼叫FIND_FUNCTION回來後eax是
                                                                                                           //LoadLibarayA的函數位址

    //要呼叫 LoadLibraryA("msvcrt.dll"),預備字串 "msvcrt.dll"
     push 0x00006ec6c                                                            
     push 0x642e7472
     push 0x6376736d
     push esp
     call eax                                                                //呼叫 LoadLibraryA("msvcrt.dll"),調                                                                           //用 LoadLibraryA ,返回值自動放進eax,eax是msvcrt基址         

                                                                                   
     push 0xd5a73c1e                      //printf雜湊值  1e3ca7d5
     push eax                                   // 推入 msvcrt.dll基底 
     call FIND_FUNCTION           //回來後 eax為printf的位址
     mov ecx,eax                             //把eax放在ecx
     
     mov DWORD [esp+0x04],0xcd481e74    //改變 [esp+0x04]的數值,變成exit的雜湊值
     call FIND_FUNCTION           //回來後 eax 就是exit的位址
     mov edx,eax                             //將exit位址放入edx
     

    ; 要呼叫 printf("Hello, World!\n") 和 exit(0)
    ; 先把字串參數 "Hello, World!\n" 推入堆疊
     push 0x00000A21
     push 0x646C726F
     push 0x57202C6F
     push 0x6C6C6548
     mov esi,esp              //字串參數在esi
     xor eax,eax              // eax=0 
     push eax                  //推入exit的參數
     push edx                  //推入exit的位址
     push esi                   //推入字串參數
     call ecx                    //呼叫printf
     pop edx                    //清除字串參數
     pop edx                    //把exit的位址給edx
     call edx                    //呼叫exit



有了完整的shellcode 之後,我們要把她轉換為OPCODE的型態。

可以用很多工具來轉換,前面我們提到用mona轉,這次我們用NASM組譯器,把我們的shellcode 生成一個副檔為bin的檔案。

1.先把我們的shellcode存成名為shellcode2.asm的檔案

2.再用NASM生成shellcode.bin   -o 為輸出BIN檔案 



3.接著使用本書偉大的fon909做的程式可以讀取BIN檔,再轉為OPCODE型態。

// file name fonReadBin

#include <iostream>
#include <fstream>
#include <vector>
#include <algorithm>
#include <iomanip>
#include <string>
using namespace std;

typedef std::vector<unsigned char> BinaryArray;

void usage();
bool read_binary(ifstream&, BinaryArray&);
unsigned output_hex(BinaryArray const &, unsigned const);

int main(int argc, char **argv) {
    if(argc < 2) {
        usage();
        return -1;
    }

    ifstream fin(argv[1], ios_base::binary);
    if(!fin) {
        cerr << "failed to open file \"" << argv[1]
             << "\".\n";
        return -1;
    }

    BinaryArray array;
    if(!read_binary(fin, array)) {
        cerr << "failed to parsed file \"" << argv[1]
             << "\".\n";
        return -1;
    }

    unsigned count_per_line = 16;
    if(argc >= 3) count_per_line = atoi(argv[2]);
    cout << "//Reading \"" << argv[1] << "\"\n"
         << "//Size: " << array.size() << " bytes\n"
         << "//Count per line: " << count_per_line
         << "\n";
    unsigned null_count = output_hex(array, count_per_line);
    cout << "//NULL count: " << null_count << '\n';
}

unsigned output_hex(BinaryArray const &carr, unsigned const cpl) {
    unsigned null = 0;
    cout << "char code[] = \n\"";
    for(size_t i = 1; i <= carr.size(); ++i) {
        cout << "\\x" << hex << setw(2)
             << setfill('0') << (unsigned)(carr[i-1]);
        if(!(i % cpl)) {
            cout << "\"\n";
            if(i < carr.size()) cout << '\"';
        }
        if(!(carr[i-1])) ++null;     
    }

    if(carr.size() % cpl) cout << '\"';
    cout << ";\n";
    return null;
}

bool read_binary(ifstream& fin, BinaryArray& arr) {
    try {
        unsigned file_length;

        fin.seekg(0, ios::end);
        file_length = fin.tellg();
        fin.seekg(0, ios::beg);

        arr.resize(file_length);
        char *mem_buf = new char [file_length];
        fin.read(mem_buf, file_length);
        copy(mem_buf, mem_buf+file_length, arr.begin());
        delete [] mem_buf;
    } catch(...) {return false;}
    return true;
}

void usage() {
    cout << "Usage: fonReadbin <asm_bin_file> [count_per_line=16]\n"
         << "Read binary data from the file and output the hex string for C/C++\n"
         << "Version 1.0\n"
         << "Email: fon909@outlook.com\n"
         << "Example: ./fonReadBin shellcode.asm 32";
}


程式操作如下圖:






4.
把他的輸出右鍵全選,ctrl+c 複製下來後


5.
既然VC++ 載入的是MSVCR100.dll裡面的pirntf 跟exit

我們來測試看看把這個shellcode給VC++編譯器編譯,是否依然能夠正常使用msvcrt,dll的printf
跟exit

//file name Test_shellcode2VC.cpp






編譯成功。

其中:
1.
因為 shellcode 是一個全域變數,會被VC編譯器設定為不可執行,反觀 Dev-C++ 不會有這樣的問題,所以 Dev-C++ 裡的 shellcode 可以被執行,我們在 VC 的程式裡面先使用 Windows API 函式 VirtualProtect() 將 shellcode 所在的記憶體位址設定為可執行,另外再對 shellcode 作型別轉換的時候,必須先將轉換成 void* 指標,才能再轉換成 FUNCPTR 型別。

2.
執行時畫面一閃而過,因為shellcode會先執行exit()動作離開程式,所以就算最後加上system("pause") 也沒用,所以改用CMD 執行,可以觀察其中 shellcode是否跟我們預期的一樣。





大功告成~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~





                                   ç›¸é—œåœ–片
















留言

這個網誌中的熱門文章

buffer_overflow 第1章 筆記 介紹

Buffer_overflow 第2章 筆記1 函數堆疊的故事