[原創]看雪CTF2017第五題 獨行孤客CrackMe的writeup
0x00. 先看驅動
驅動不大,才20多個函式。
從入口開始分析。
1. 建立裝置
.text:000107D5 68 58 13 01 00 push offset aDeviceVmxdrv ; "\\device\\vmxdrv" .text:000107DA 8D 45 F4 lea eax, [ebp+DestinationString] .text:000107DD 33 FF xor edi, edi .text:000107DF 50 push eax ; DestinationString .text:000107E0 89 7D FC mov [ebp+DeviceObject], edi .text:000107E3 FF D6 call esi ; RtlInitUnicodeString
用來與應用層通訊
2. IRP_MJ_FUNCTION
主要有三個,read/write/ioctl。
.text:00010870 C7 46 44 A8 05 01 00 mov dword ptr [esi+44h], offset f_DrvRead_105A8 .text:00010877 C7 46 48 1C 06 01 00 mov dword ptr [esi+48h], offset f_DrvWrite_1061C .text:0001087E C7 46 70 1A 07 01 00 mov dword ptr [esi+70h], offset f_DrvControl_1071A
先看f_DrvWrite_1061C,透過irp獲取到上層傳入的資料,然後透過104b6獲取某個輸出存入全域性變數g_READCC(根據read的分析,可以知道長度為4的4位元組陣列)。
.text:00010669 57 push edi ; size_t .text:0001066A FF 75 0C push [ebp+Irp] ; void * .text:0001066D 53 push ebx ; void * .text:0001066E E8 11 0C 00 00 call memcpy .text:00010673 83 C4 18 add esp, 18h .text:00010676 83 3D D8 14 01 00 00 cmp dword ptr is_clean_port, 0 .text:0001067D 74 15 jz short loc_10694 .text:0001067F 68 C8 14 01 00 push offset g_READCC ; int .text:00010684 53 push ebx ; void * .text:00010685 E8 2C FE FF FF call f_GetMd5_104B6 .text:0001068A C7 05 DC 14 01 00 01 00 00 00 mov is_write, 1
進入104b6內部,key是個16位元組陣列,初始化0。然後將上面傳下的資料複製到key中,長度需要小於16。然後將key進行一下變換。 key[0] ++(反除錯標誌為1,後面再說),其他key[i] += i
.text:000104F5 56 push esi ; size_t //長度 .text:000104F6 51 push ecx ; void * //上層輸入 .text:000104F7 8D 45 EC lea eax, [ebp+key] .text:000104FA 50 push eax ; void * .text:000104FB E8 84 0D 00 00 call memcpy... text:00010505 39 05 D8 14 01 00 cmp dword ptr is_clean_port, eax //判斷標誌是否為0,不為0,key[0] ++ .text:0001050B 74 03 jz short loc_10510 .text:0001050D FE 45 EC inc [ebp+key] .text:00010510.text:00010510 loc_10510: ; CODE XREF: f_GetMd5_104B6+55j .text:00010510 3B F0 cmp esi, eax .text:00010512 7E 09 jle short loc_1051D .text:00010514.text:00010514 loc_10514: ; CODE XREF: f_GetMd5_104B6+65j .text:00010514 00 44 05 EC add [ebp+eax+key], al //key[i] += i .text:00010518 40 inc eax .text:00010519 3B C6 cmp eax, esi .text:0001051B 7C F7 jl short loc_10514
接著透過下面三個函式對key進行計算,輸出結果
f_Md5_Init_108B2((MD5OBJ *)&v5); f_Md5_j_11124((MD5OBJ *)&v5, key, strlen(key));f_Md5_hexdigest((int)&v5, md5);
進入108b2一看就猜測是md5計算,f_Md5_hexdigest將計算結果(32位元組字元)儲存到md 5欄位中輸出,設定計算標誌。也就是大致確認write是計算md5,然後儲存到g_READCC
MD5OBJ *__stdcall f_Md5_Init_108B2(MD5OBJ *a1) { MD5OBJ *result; // eax@1 result = a1; a1->len8 = 0; a1->unk_4 = 0; a1->s1 = 0x67452301; a1->s2 = 0xEFCDAB89; a1->s3 = 0x98BADCFE; a1->s4 = 0x10325476; return result; }
接著看f_DrvRead_105A8,看剛才的計算標誌是否為0,為0就初始化g_READCC一段值(不知道作者意圖,迷惑cracker?),如果計算標誌是1,就直接返回計算的結果,然後該值返回到使用者空間。也就是如果透過write計算了md5,這裡就是獲取md5計算結果。
//.text:000105AD if ( !is_write ) { i = 3; do { g_READCC[i] = 3 * i - 'd'; ++i; } while ( i < 16 ); g_READCC[0] = 0xCBu; g_READCC[1] = 0xAAu; g_READCC[2] = 0xDEu; g_READCC[3] = 0xB0u; } //返回資料 *(_DWORD *)&MasterIrp->Type = *(_DWORD *)g_READCC; v4 = (int)&MasterIrp->MdlAddress; *(_DWORD *)v4 = *(_DWORD *)&g_READCC[4]; v4 += 4; *(_DWORD *)v4 = *(_DWORD *)&g_READCC[8]; *(_DWORD *)(v4 + 4) = *(_DWORD *)&g_READCC[12];
最後看f_DrvControl_1071A,支援多個命令號,但只有222004h有用。設定反除錯標誌為1,然後進入10486看看
.text:00010734 2D 04 20 22 00 sub eax, 222004h .text:00010739 8B 4E 0C mov ecx, [esi+0Ch] .text:0001073C 74 2C jz short loc_1076A ... .text:0001076A loc_1076A: ; CODE XREF: f_DrvControl_1071A+22j .text:0001076A C7 05 D8 14 01 00 01 00 00 00 mov dword ptr is_clean_port, 1 .text:00010774 FF 15 80 13 01 00 call ds:IoGetCurrentProcess .text:0001077A A3 E0 14 01 00 mov eproc, eax .text:0001077F E8 02 FD FF FF call f_ClearDebugPort_10486
列舉程式找到當前程式的eprocess(其實沒必要列舉把),置eprocess->DebugPort = NULL,讓應用層偵錯程式失效,達到反跳試效果。
result = IoGetCurrentProcess(); v1 = result; while ( result != (PEPROCESS)eproc ) { result = (PEPROCESS)(*((_DWORD *)result + 0x22) - 0x88);// eproc->ActiveProcessLinks.Flink if ( result == v1 ) return result; } *((_DWORD *)result + 0x2F) = 0; // eproc->DebugPort = 0
這裡猜想一下,如果破解者透過應用層patch,不傳送222004h命令來解除反跳試的話,那麼這裡的反跳試標誌就是0,然後在write中計算md5時,對key[0]就不會做++操作,那麼上層就會獲取到一個錯誤的值,從而影響破解。
3. k掉驅動反除錯
首先想到的是將驅動檔案patch,也就是DebugPort置零的指令nop掉
.text:000104A9 83 A0 BC 00 00 00 00 and dword ptr [eax+0BCh], 0
透過reshacker將驅動資源匯出來,然後hex編輯工具修改104A9的內容(檔案記憶體對齊一樣)為7個NOP,然後再將patch驅動檔案匯入到exe中。
會提示驅動載入失敗,可能有校驗,不再細跟。
沒辦法,為了讓od能夠除錯,我寫了個簡單驅動,在本驅動載入時,將104A進行patch,透過反跳試。
0x01. 再看CrackMe
既然知道有驅動了,先找找釋放和載入驅動的程式碼,透過 FindResourceA和CreateService即可定位(不再詳述),注意到的是,驅動載入成功會設定一個標誌,用於後面驗證的判斷
v5 = f_CreaetSrv_401AA0(ServiceName, &Buffer);// vmxdrv v1->is_drv_run = v5;
然後再找和驅動通訊的程式碼,透過DeviceIoControl找到呼叫222004命令好的程式碼。透過建立一個執行緒,迴圈呼叫該介面來清零DebugPort
while ( 1 ) { v0 = CreateFileA(FileName, 0xC0000000, 0, 0, 3u, 0x80u, 0); if ( v0 == (HANDLE)-1 ) break; DeviceIoControl(v0, 0x222004u, 0, 0, &OutBuffer, 0x100u, &BytesReturned, 0); CloseHandle(v0); Sleep(0xBB8u); }
按理說這裡可以patch掉來去掉反跳試,但就會出現我前面分析提到的問題。
透過WriteFile找到呼叫read/write的位置,也就是計算md5和獲取md5的位置。
.text:00401D50 ; HANDLE __thiscall f_CalcKeyMd5_401D50(void *this, char *key, size_t len)... .text:00401E4E push ebx ; lpOverlapped .text:00401E4F push eax ; lpNumberOfBytesWritten .text:00401E50 lea ecx, [esp+344h+Buffer] //使用者輸入的key相關資料 .text:00401E54 push esi ; nNumberOfBytesToWrite .text:00401E55 push ecx ; lpBuffer .text:00401E56 push edi ; hFile .text:00401E57 call ds:WriteFile //計算md5 .text:00401E5D test eax, eax .text:00401E5F jz short loc_401ED4 .text:00401E61 lea edx, [esp+33Ch+NumberOfBytesRead] .text:00401E65 push ebx ; lpOverlapped .text:00401E66 push edx ; lpNumberOfBytesRead .text:00401E67 lea eax, [esp+344h+keymd5] .text:00401E6E push 10h ; nNumberOfBytesToRead .text:00401E70 push eax ; lpBuffer .text:00401E71 push edi ; hFile .text:00401E72 call ds:ReadFile //讀取md5
f_CalcKeyMd5_401D50回溯一層就是輸入key回車的響應函式。 這裡先透過UpdateData(1)獲取輸入資料,然後複製到區域性變數
f_UpdateData_41A4F7(1); f_CString_copy_417D43((CString *)&key, (LPCSTR *)&v1->key);//使用者輸入的
然後輸入進行小寫和反轉變換
f_CString_lwr_4182FA((CString *)&key); //小寫 f_Cstring_rev_41830C((CString *)&key); // 反轉
判斷輸入長度是否為6,不是退出,清除輸入,並透過IsDebuggerPresent檢查是否在除錯(OD直接過),是除錯也退出,清理出輸入。
if ( *(_DWORD *)(key - 8) != 6 || IsDebuggerPresent() ) { CString::operator=((CString *)&v1->unk_6c, byte_431398); CString::operator=((CString *)&v1->key, byte_431398); f_UpdateData_41A4F7(0); }
滿足長度要求,再看驅動是否載入,再呼叫f_CalcKeyMd5_401D50計算md5. 也就是呼叫驅動獲取md5,記為KeyMd51.
//.text:004017DE if ( v1->is_drv_run ) { keymd5str = *(_DWORD *)(key - 8); v3 = sub_418263(&key, 0); f_CalcKeyMd5_401D50(v1, (char *)v3, keymd5str); }
接著下面兩個函式,先呼叫f_GetStrMd5_401920(應用層的Md5,透過除錯可以很快確認,內部也有md5特徵)計算KeyMd51的Md5,記為KeyMd52,然後呼叫sub_415A78擷取KeyMd52從第3為開始的10字元,記為KeyMd53。
f_GetStrMd5_401920((char)v4, (CString *)keymd5str);// 00943950 37 63 37 36 36 65 32 61 31 63 61 30 35 37 63 37 7c766e2a1ca057c7 // 00943960 62 30 65 39 31 66 39 33 35 65 64 61 61 64 37 33 b0e91f935edaad73 // // // sub_415A78((LPCSTR *)&keymd5str_obj, (int)&v9, 2, 0xAu);// 擷取2開始長度0xA的值 // 00943900 37 36 36 65 32 61 31 63 61 30 00 38 39 30 33 38 766e2a1ca0.89038 // 00943910 33 39 32 36 39 32 65 38 32 64 36 33 62 31 37 64 392692e82d63b17d //
最後KeyMd53與888aeda4ab比較,成功提示Success^^!
if ( _mbsicmp(keymd5str_obj, a888aeda4ab) ) // 888aeda4ab { CString::operator=((CString *)&v1->unk_6c, byte_431398); CString::operator=((CString *)&v1->key, byte_431398); f_UpdateData_41A4F7(0); } else { f_ShowSuccess_402030(v1);//成功提示 }
總結演算法:
KEY1 = rev(lwr(key)),key長度6,將輸入轉小寫,逆序反除錯成功時KEY1[0]+=1, 其他KEY1[i]+=i;KEY2 = DrvMd5(KEY1),驅動MD5計算KEY3 = Md5(KEY2), 應用層Md5計算KEY4 = KEY3[2:12],取第3位開始的10個字元KEY4 == '888aeda4ab'0x11. 求解
由於MD5hash無法逆運算,只能爆破了,剛開始忘了題目key只能是數字和字母,結果我跑了全字元,跑了1天多....沒出來,卡hi是懷疑自己
後來改成了數字字母,終於得到答案 su1987
爆破程式碼如下:
char Seed[/*68*/36] = { 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', }; #define SEED_SIZE 36 typedef struct _THREAD_PARAM { int i1; int i2; int i3; int i2_1; int i2_2; }TPP, *PTPP; int g_ThreadCnt = 0; int g_start = 0; long g_count = 0; void write_file(char* sz) { HANDLE hFile = CreateFileA("1.log", GENERIC_WRITE|GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL); if(hFile) { SetFilePointer(hFile, 0, 0, FILE_END); DWORD dw = 0; WriteFile(hFile, sz, strlen(sz), &dw, NULL); CloseHandle(hFile); hFile = NULL; } } bool crack1(PTPP p){ int i1 = p->i1; int i2 = p->i2; char sss[20] = {0}; for(int i3=0; i3<SEED_SIZE; i3++) { for(int i4=0; i4<SEED_SIZE; i4++) { for(int i5=0; i5<SEED_SIZE; i5++) { for(int i6=0; i6<SEED_SIZE; i6++) { char sza[7] = {Seed[i1], Seed[i2], Seed[i3], Seed[i4], Seed[i5], Seed[i5]}; g_count ++; char sz[7] = {0}; //反轉 sz[0] = Seed[i6]+1; sz[1] = Seed[i5]+1; sz[2] = Seed[i4]+2; sz[3] = Seed[i3]+3; sz[4] = Seed[i2]+4; sz[5] = Seed[i1]+5; FileMD5 fm; char* p = (char*)fm.md5(sz, 6); p = (char*)fm.md5(p, 32); strncpy(sss, p+2, 10); if(!stricmp(sss, "888aeda4ab")) { char info[1024] = {0}; sprintf(info, "%c%c%c%c%c%c, => %s,%s\n", Seed[i1], Seed[i2], Seed[i3], Seed[i4], Seed[i5], Seed[i6], sz, sss ); write_file(info); int spell = GetTickCount() - g_start; printf("spell time : %d s", spell/1000); system("pause"); return true; } } } //system("cls"); printf("count: %ld\n", g_count); } } return false; } void crack3(PTPP p){ int i1 = p->i1; int i2_1 = p->i2_1; int i2_2 = p->i2_2; delete[] p; TPP p1 = {0}; p1.i1 = i1; for(int i=i2_1; i<i2_2; i++) { p1.i2 = i; if(crack1(&p1)) { return; } } } void crack2(int i1, int i2_1, int i2_2) { PTPP p = new TPP;//{0}; if(p == NULL) { printf("!!!!!!!!!!!!沒neicun!!"); return; } memset(p, 0, sizeof(TPP)); p->i1 = i1; p->i2_1 = i2_1; p->i2_2 = i2_2; HANDLE h = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)crack3, (PVOID)p, 0, NULL); if(h == NULL) { printf("CreateTHREAD error [%d]\n", g_ThreadCnt); } else { g_Handles[g_ThreadCnt++] = h; } } void crack() { for(int i1=0; i1<SEED_SIZE; i1++) { int i2 = 0;#define STEP_SIZE 2 for(i2 = 0; i2<SEED_SIZE-STEP_SIZE; i2+=STEP_SIZE) { crack2(i1, i2, i2+STEP_SIZE); } crack2(i1, i2, SEED_SIZE); } } int _tmain(int argc, _TCHAR* argv[]) { int start = GetTickCount(); g_start = GetTickCount(); crack(); WaitForMultipleObjects(g_ThreadCnt, g_Handles, TRUE, INFINITE); int spell = GetTickCount() - start; printf("spell time : %d s, thread-count: %d\n", spell, g_ThreadCnt); getchar(); return 0; }
最後結果
su1986, => 79;4yx,888aeda4ab
由於演算法開始有轉小寫,所以其時答案中所有字母都可以是大小寫選擇,答案不唯一。
相關文章
- [原創]看雪CTF2017第二題lelfeiCM的writeup2019-02-25TF2
- [原創]看雪CTF2017第六題 Ericky-apk詳細writeup(從一個安卓新手的角度)2019-02-25TF2APK安卓
- 第五季極客大挑戰writeup2020-08-19
- [原創]破解-分析Crackme演算法2009-06-13演算法
- 看雪.WiFi萬能鑰匙 CTF 2017第五題 點評及解題思路2017-06-28WiFi
- 看雪·深信服 2021 KCTF 春季賽 | 第五題設計思路及解析2021-05-17
- 看雪·眾安 2021 KCTF 秋季賽 | 第五題設計思路及解析2021-11-29
- 看雪.騰訊TSRC 2017 CTF 秋季賽 第五題點評及解析思路2017-11-03
- 看雪.紐盾 KCTF 2019 Q2 | 第五題點評及解題思路2019-07-01
- 看雪CTF.TSRC 2018 團隊賽 第五題 『交響曲』 解題思路2018-12-23
- 『看雪眾測』這次的眾測物件是看雪!2018-03-21物件
- 看雪安卓容器2019-01-14安卓
- 【原創】視訊+文字:詳解VBA解決數獨問題2020-11-28
- [投票] 看雪2018年度最佳原創技術文章落誰家?你說了算!2019-01-01
- 看雪安全網站2015-11-15網站
- 看雪論壇版主座談 | 看雪2017安全開發者峰會2017-12-09
- 看雪版主、十年黑客“玩命”:我不作死,我只玩命|宅客故事2017-08-09黑客
- 【極客大挑戰2023】- Re -點選就送的逆向題 WriteUp2024-10-04
- 看雪專案外包服務2017-04-07
- [下載]《看雪精華20週年紀念版》釋出!(含看雪論壇精華20)2020-02-24
- 看雪安卓研修班,安卓逆向2020-12-21安卓
- 《看雪招聘使用者協議》2021-11-26協議
- 看雪學院《註冊協議》2016-11-28協議
- 看雪論壇 Markdown 使用指南2017-10-31
- 推進創客工程教育運用的實踐原則2022-07-13
- 看雪.萬能鑰匙 CTF 2017第一題 WannaLOL 解題思路2017-06-29
- 四劍客第五關2024-03-14
- 原創一看便知、Maven建立web專案2016-12-01MavenWeb
- 《看雪專欄使用者協議》2021-11-26協議
- 看雪 安全技術沙龍 第1期2016-11-23
- 每週精選+原創題2024-06-03
- 從獨立開發者視角看F2P遊戲的發行2013-09-10遊戲
- 敏捷宣言的第五項原則2008-08-26敏捷
- oracle統計表的所有行數(原創)2010-07-07Oracle
- 演算法分析:看雪CTF2019的一道逆向題目2022-03-24演算法TF2
- 武漢科銳,助力2019 第三屆看雪安全開發者峰會,引航創新!2019-05-14
- 看雪CTF.TSRC 2018 團隊賽 第七題 『魔法森林』 解題思路2018-12-23
- 看雪CTF.TSRC 2018 團隊賽 第九題『諜戰』 解題思路2018-12-19