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檔案
2.再用NASM生成shellcode.bin -o 為輸出BIN檔案
3.接著使用本書偉大的fon909做的程式可以讀取BIN檔,再轉為OPCODE型態。
// file name fonReadBin
// 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";
}
程式操作如下圖:
#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是否跟我們預期的一樣。
大功告成~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

留言
張貼留言