動態規劃實現O(n)爆破任意加密
0x00 前言
這個框架是當你手上有一個加密,但是不知道他的實現的時候(比方說要通過逆向來獲得),已知密文,爆破出明文。
0x01 前提
其實這個有點標題黨,因為並不能爆破任意加密,像AES、DES這樣的分塊加密就不行。。。而像魔改base64、魔改rc4這種加密就隨便爆破了,所以條件是需要有從左到右的對應的關係,順序不能被打亂。
當然分塊加密後續也會支援,這個以後再說吧。。。
0x02 例項
接下來我會用一道CTF題目的例項,這是一個魔改base64+魔改rc4的加密的題目,但是不用去逆向他,直接用框架爆破。
EXE變成DLL
這個是我學PE的時候就想到的,應該有其他人早就玩過類似的東西了。但是我在網上找卻沒找到過類似的思路,這裡就寫一下吧。。。
首先EXE和DLL都是PE檔案格式,所以當我們逆向一個EXE的時候,需要分析他其中一個函式。這個時候可能就會需要call這個函式以便詳細分析,但跨程式遠端call明顯就比較麻煩,所以我就想到反正都是PE檔案格式,只要把characteristic那個bit改成1就是DLL了,那我們不就可以LoadLibrary把這個EXE當做一個DLL載入到自己的記憶體空間中了嗎?
然後雖然並沒有匯出表,所以並不能通過GetProcAddress獲取到我們要分析的函式的地址,但是LoadLibrary返回的值就是模組的基址,這個時候加上函式的RVA(通過IDA逆向分析獲得),就可以直接call它了!
具體操作如圖所示:
首先是把入口點改為0,不然LoadLibrary會直接呼叫他的主函式,這不是我們所期望的。還有就是在特徵值(characteristic)裡面把DLL勾選了,不然重定位會出問題,我不知道為什麼,有大神知道的話可以解答一下。。然後把檔案字尾改成dll(這個好像改不改都行),就可以當dll使了。
程式逆向分析
這個CrackMe是讓你輸入,然後把加密結果寫到檔案裡,題目要求寫入檔案的內容為"Itl9qnxD/IJhoarL"。程式main函式如下。
int __cdecl main(int argc, const char **argv, const char **envp)
{
char *v3; // eax
const char *v4; // eax
FILE *v5; // eax
FILE *v6; // esi
const void *v7; // ecx
void *v9; // [esp+Ch] [ebp-260h]
int v10; // [esp+10h] [ebp-25Ch]
int v11; // [esp+14h] [ebp-258h]
void *v12; // [esp+18h] [ebp-254h]
int v13; // [esp+1Ch] [ebp-250h]
int v14; // [esp+20h] [ebp-24Ch]
void *Str; // [esp+24h] [ebp-248h]
size_t Size; // [esp+34h] [ebp-238h]
unsigned int v17; // [esp+38h] [ebp-234h]
char *Filename; // [esp+3Ch] [ebp-230h]
int v19; // [esp+4Ch] [ebp-220h]
unsigned int v20; // [esp+50h] [ebp-21Ch]
char Buffer; // [esp+54h] [ebp-218h]
char Dst; // [esp+55h] [ebp-217h]
int v23; // [esp+268h] [ebp-4h]
Buffer = 0;
memset(&Dst, 0, 0x1FFu);
v3 = sub_401900(std::cout);
std::basic_ostream>::operator<<(v3, std::endl);
//輸出廢話,這個我們到時候會通過hook成空函式取消掉,不然輸出會讓爆破很慢
gets(&Buffer);
//這個到時候會hook成我們自己的函式,並把當前所遍歷的input放進他的緩衝區,待會會有詳細解釋
v12 = 0;
v13 = 0;
v14 = 0;
sub_401B50(&Buffer, (int)(&Buffer + strlen(&Buffer)));
v23 = 0;
v9 = 0;
v10 = 0;
v11 = 0;
sub_401000((int)&v9, (int)&v12);
LOBYTE(v23) = 1;
sub_401B20((int)&Str, (int)&v9);
LOBYTE(v23) = 2;
sub_401260(&Filename);
v4 = (const char *)&Filename;
if ( v20 >= 0x10 )
v4 = Filename;
//上面就是各種加密了,我們不用去逆向他
v5 = fopen(v4, "wb+");
v6 = v5;
if ( v5 )
{
v7 = &Str;
if ( v17 >= 0x10 )
v7 = Str;
fwrite(v7, Size, 1u, v5);
fclose(v6);
}
//這些檔案操作也都會被hook掉,除了fwrite其他都會變成空函式,fwrite會呼叫某個函式做一些操作,待會詳細講
if ( v20 >= 0x10 )
operator delete(Filename);
v20 = 15;
v19 = 0;
LOBYTE(Filename) = 0;
if ( v17 >= 0x10 )
operator delete(Str);
v17 = 15;
Size = 0;
LOBYTE(Str) = 0;
if ( v9 )
operator delete(v9);
if ( v12 )
operator delete(v12);
system("pause");//這個也會hook成空函式
return 0;
}
所以就是吃一手hook,我這裡基本上用的是IAT hook,有一個e8的call我是直接用的改引數值hook。
IAT hook就是要拿到IAT的RVA,這個簡單,IDA雙擊那個函式,就是IAT地址,去掉400000就是RVA了,如圖所示:
框架使用
現在來看看怎麼去用這個框架:
#include
#include
#include
#include
#include "BruteForce.h"
#include "WindowsHookFramework.h"
typedef int(*main_t)();
#define GETS_IAT 0x40DC
#define MAIN_DISPL 0x13E0
#define FWRITE_IAT 0x4060
#define SYSTEM_IAT 0x4068
#define FOPEN_IAT 0x406C
#define FCLOSE_IAT 0x4064
#define COUT_IAT 0x4038
#define OUTPUT_STR_E8ARG 0x1445
#define KEY "Itl9qnxD/IJhoarL"
#define INPUT_LEN 12
main_t hismain;
class CrackCtf : public BruteForce
{
public:
CrackCtf(size_t inputLen, const bfbyte* answer, size_t answerLen)
:BruteForce(inputLen, answer, answerLen) {}
~CrackCtf();
private:
virtual void doEncode() override
{//繼承重寫doEncode函式,他必須要通過getInput獲取到當前輸入,然後把這個輸入加密,再把加密結果作為引數呼叫testEncodeResult
//this function must call getInput to get the input, encode it,
//and call testEncodeResult with the result of encoding
hismain();//呼叫他的主函式,這個時候主函式已經被各種hook了
}
public:
static size_t __cdecl myfwrite(char *str, size_t Size, size_t Count, FILE *File);
};
CrackCtf::~CrackCtf() {}
CrackCtf crack(INPUT_LEN, (const bfbyte*)KEY, strlen(KEY));
//本應該是單例模式,但是CTF比賽的時間不可能給你寫單例
//第一個引數是所需要破解的最大長度,這個我是自己試出來的(看多長的輸入才能跟要求的答案一樣長並且沒有等於號(因為這是魔改base64)。。。
//後面兩個引數是密文和密文的長度
size_t CrackCtf::myfwrite(char * str, size_t Size, size_t Count, FILE * File)
{
//printf("%s\n", str);
if (crack.testEncodeResult((bfbyte*)str))
{//呼叫testEncodeResult,str就是加密結果
//如果返回true,說明爆破出了正確答案,這個時候彈出當前輸入
bfbyte buf[INPUT_LEN + 1];
memset(buf, 0, INPUT_LEN + 1);
crack.getInput(buf, INPUT_LEN + 1);
MessageBoxA(NULL, (LPCSTR)buf, "flag", MB_OK);
}
return 0;
}
char* __cdecl mygets(char* buffer)
{
crack.getInput((bfbyte*)buffer);
//get當前的Input,並且塞到緩衝區裡面
return buffer;
}
DECLARE_EMPTY_FUNC(emptyfunc, 0)
DECLARE_EMPTY_FUNC(emptyfunc2, 4)
int main()
{
HMODULE pBase = LoadLibraryA("Encrypt messages.dll");
//Encrypt messages is an exe crack me in one CTF competition
//change the characteristic to dll, and set the entry address to 0, it can become a dll
hookIAT(pBase, GETS_IAT, (func_p_t)&mygets);
hookIAT(pBase, FWRITE_IAT, (func_p_t)&CrackCtf::myfwrite);
hookIAT(pBase, SYSTEM_IAT, (func_p_t)&emptyfunc);
hookIAT(pBase, FOPEN_IAT, (func_p_t)&emptyfunc);
hookIAT(pBase, FCLOSE_IAT, (func_p_t)&emptyfunc);
hookIAT(pBase, COUT_IAT, (func_p_t)&emptyfunc2);
hookE8Call(pBase, OUTPUT_STR_E8ARG, (func_p_t)&emptyfunc);
//hook IAT 和 e8 call
hismain = (main_t)((PBYTE)pBase + MAIN_DISPL);
//算出Main函式地址
crack.startCrack();
//呼叫startCrack,開始爆破
return 0;
}
基本秒破,如圖:
0x03 後言
嗯。。基本上是這樣,目前是通過繼承和純虛擬函式實現的,以後可能會改成模板。。。現在暫不支援分塊加密,以後可能會支援,也有可能會支援多執行緒,這個以後再說吧。。。
原始碼我放在github上了。。https://github.com/Mem2019/BruteForceCrackEncoding
程式碼基本上是用C with class寫的。。沒怎麼用C++的特性,考慮到是爆破,就沒有用C++的那套理論。。。怕慢。。。程式碼寫的渣,大神輕噴。。。
0x04 更新:已支援分塊加密爆破
容我瞎扯嘮叨兩句
我本來以為,支援分塊加密會需要比較複雜的演算法,可能需要加很多新程式碼,我之前想到的演算法也的確如此。但是今天突然抖了個機靈,想到一個又簡單又有效的演算法,只改了幾行,就支援了分塊加密爆破。。。主要就是當progress大於塊大小的倍數時再更新,跟之前演算法區別不大,思想基本一樣。。。改完之後測試通過了搞得我自己都不敢相信日常寫fragile的垃圾程式碼的我能夠改幾行就加一個新功能。。。哈哈。。。
寫了個簡單的AES爆破demo
嗯,大概就是不用AES的解密演算法,不知道祕鑰(但是祕鑰理論上能被逆出來)的情況下爆破AES。。。其實這個又標題黨了。。我這個分塊加密其實就是一塊一塊地爆破而不是之前那樣一位元組一位元組地爆破。。而AES一塊就是128個bit,如果爆破起來真的很慢。。。(以後可能會用執行緒池實現多執行緒加速)
所以我這個又實現了一個自定義字符集的功能,之前會從把從''\x00'到'\xff'都爆破一次,現在可以自定義字符集。。比方說我知道原文只有'\x41'和'\x00',我就可以在引數裡面指定,然後爆破的時候只去遍歷這兩個字元。。這樣勉強能爆破128位分塊的AES。。。
程式碼如下:
#include "BruteForce.h"
#include "aes.h"
#include
#include
uint8_t key[] = {
0x00, 0x01, 0x02, 0x03,
0x04, 0x05, 0x06, 0x07,
0x08, 0x09, 0x0a, 0x0b,
0x0c, 0x0d, 0x0e, 0x0f,
0x10, 0x11, 0x12, 0x13,
0x14, 0x15, 0x16, 0x17,
0x18, 0x19, 0x1a, 0x1b,
0x1c, 0x1d, 0x1e, 0x1f };
uint8_t res[] =
{ 0x35,0x27,0xbd,0x7d,0x7f,0xfe,0x7e,0xdb,0xd2,0x7f,0xf9,0xc9,0x11,0x88,0x43,0x2a,
0xcf,0xf1,0xf9,0x47,0xec,0xd4,0xf6,0x0e,0xf6,0xa6,0x2a,0x3c,0x3f,0x1c,0xba,0xe3 };
void printHex(bfbyte* input, size_t inputLen)
{
for (size_t i = 0; i < inputLen; i++)
{
printf("%02x ", input[i]);
}
printf("\n");
}
class CrackAes : public BruteForce
{
public:
CrackAes(size_t inputLen, const bfbyte* answer, size_t answerLen)
:BruteForce(inputLen, answer, answerLen, AES_BLOCK_SIZE, (bfbyte*)"A", 2)
{
this->outputBufLen = answerLen - answerLen % AES_BLOCK_SIZE + AES_BLOCK_SIZE;
this->inputBufLen = inputLen;
}
~CrackAes() {};
private:
size_t outputBufLen;
size_t inputBufLen;
virtual void doEncode() override
{//繼承重寫doEncode函式,他必須要通過getInput獲取到當前輸入,然後把這個輸入加密,再把加密結果作為引數呼叫testEncodeResult
//this function must call getInput to get the input, encode it,
//and call testEncodeResult with the result of encoding
uint8_t* input = new uint8_t[inputBufLen];
uint8_t* output = new uint8_t[outputBufLen];
size_t inputLen = getInput(input);
if (rand() % 4096 == 0)
{
printHex(input, inputLen);
}
if (!aesEncrypt(input, inputLen, output, outputBufLen, key))
{
#ifdef _DEBUG
throw 0;
#endif // DEBUG
}
if (this->testEncodeResult(output))
{
printHex(input, inputLen);
system("pause");
}
delete[] output;
delete[] input;
}
};
int main()
{
CrackAes aes(19, res, AES_BLOCK_SIZE * 2);
aes.startCrack();
/*
output:
41 00 00 00 41 41 41 41 00 00 41 41
41 41 00 41 41 00 41 00 41 41 00 00 00
41 41 41 00 41 41 00 41 41 41 00 00 41 41
41 00 00 00 41 41 41 00 00 41 00 00 00 00
00 00 00 41 00 41 00 41 00 41 41 41 00 00
00 41 41 41 00 41 00 41 41 00 41 41 00 00 41
41 41 00 00 00 00 00 00 41 00 41 00 41 00 41
41 41 41 00 00 00 41 41 00 41 00 00 00 41 41
00 41 00 00 41 00 00 41 00 41 00 00 41 00 00
41 00 41 00 41 41 00 00 41 41 00 00 41 00 00
00 41 00 00 00 00 00 41 00 41 00 00 00 41 00
00 41 41 00 41 00 41 41 41 00 41 00 00 41 00
41 00 41 00 41 41 41 00 00 00 41 41 00 41 00
41 00 00 41 41 41 41 41 00 00 41 41 41 41 00
00 41 41 41 00 41 00 00 41 00 41 41 00 41 41 00 00 41
請按任意鍵繼續. . .
00 41 41 41 00 41 00 00 41 00 41 41 00 41 41 00 00 41 00
請按任意鍵繼續. . .
*/
return 0;
}
本文由看雪論壇 holing 原創 轉載請註明來自看雪社群
相關文章
- 【動態規劃(一)】動態規劃基礎2017-07-22動態規劃
- 使用動態規劃 實現字元級Diff & Patch2018-02-13動態規劃字元
- 動態規劃2024-05-08動態規劃
- PHP使用動態規劃實現最優紅包組合2021-07-18PHP動態規劃
- Python動態規劃實現虛擬機器部署2021-08-01Python動態規劃虛擬機
- 【動態規劃】字串最小編輯距離Java實現2016-03-23動態規劃字串Java
- 動態規劃分析2021-08-20動態規劃
- 動態規劃(DP)2022-03-22動態規劃
- 動態規劃初步2024-06-09動態規劃
- 模板 - 動態規劃2024-10-02動態規劃
- 動態規劃法2024-10-09動態規劃
- Python 實現 動態規劃 /斐波那契數列2018-10-29Python動態規劃
- 詳解動態規劃最長公共子序列--JavaScript實現2018-05-28動態規劃JavaScript
- 爬臺階問題(遞迴和動態規劃實現)2017-10-06遞迴動態規劃
- 詳解動態規劃01揹包問題--JavaScript實現2018-05-19動態規劃JavaScript
- 詳解動態規劃01揹包問題–JavaScript實現2018-05-19動態規劃JavaScript
- 演算法系列-動態規劃(1):初識動態規劃2020-12-01演算法動態規劃
- 淺談動態規劃2019-06-28動態規劃
- 有關動態規劃2022-04-24動態規劃
- 動態規劃小結2018-12-11動態規劃
- 動態規劃初級2020-11-21動態規劃
- 動態規劃講義2014-11-30動態規劃
- 好題——動態規劃2024-05-01動態規劃
- 3.動態規劃2024-10-22動態規劃
- 動態規劃-----線性2024-04-02動態規劃
- 動態規劃專題2024-04-07動態規劃
- 區間動態規劃2024-07-23動態規劃
- 動態規劃題單2024-09-04動態規劃
- 雙序列動態規劃2024-08-18動態規劃
- 動態規劃 總結2024-08-18動態規劃
- 動態規劃方法論2024-08-20動態規劃
- 動態規劃位置hdu 4540 威威貓系列故事——打地鼠(動態規劃)2013-05-26動態規劃
- 【動態規劃】0-1揹包問題原理和實現2019-02-23動態規劃
- python-動態規劃的遞迴、非遞迴實現2019-02-16Python動態規劃遞迴
- 【演算法】找零錢-動態規劃實現過程解析2017-09-29演算法動態規劃
- 動態規劃之數的劃分2018-04-29動態規劃
- 禮物的最大價值(一維動態規劃&二維動態規劃)2020-06-24動態規劃
- 動態規劃演算法原理與實踐2021-06-27動態規劃演算法