新160個(gè)CrackMe分析-第2組:11-20(下)

作者:selph
目錄:
??011-wocy.11
??012-ACG-crcme12
??013--Acid_burn3
??014-Splish4
??015-BradSoblesky.15
??016-fly_crkme36
??017-Cabeca7
??018-crackme_00068
??019-Acid_Byte.39
??020-cosh.310
011-015請(qǐng)看上期
1.??????016-fly_crkme3
算法難度:???
爆破難度:?
信息收集
運(yùn)行情況:
??????????
查殼與脫殼:
UPX殼,直接ESP定律脫殼即可
?????????

?
調(diào)試分析
Delphi程序,截圖不方便注釋,之后用IDR直接復(fù)制代碼到everEdit里寫(xiě)注釋了:
找到校驗(yàn)按鈕,分析校驗(yàn)函數(shù)sub_00444B30:
首先是判斷用戶是否有輸入,無(wú)輸入則彈窗,有輸入則跳轉(zhuǎn)到00444B78:

接下來(lái)校驗(yàn)輸入的數(shù)據(jù),輸入的內(nèi)容必須是0x30~0x39之間,也就是純數(shù)字:

??????????
接下來(lái)校驗(yàn)字符串長(zhǎng)度:這個(gè)cm允許的輸入是9,10,11字符,對(duì)于每種輸入都有單獨(dú)的計(jì)算,這里以輸入長(zhǎng)度為9位為例:輸入格式是xx-xxx-xx

接下來(lái)進(jìn)行了一個(gè)取數(shù)字的操作:進(jìn)行了7段,總之就是把字符串中間的-去掉,把數(shù)字拼接在一起

??????????
取完數(shù)字之后,轉(zhuǎn)換成Int類(lèi)型保存起來(lái):

??????????
接下來(lái)又進(jìn)行了7段運(yùn)算,運(yùn)算出結(jié)果累加起來(lái):

??????????
這里調(diào)用了一個(gè)00444B20的函數(shù),功能類(lèi)似C的pow函數(shù),對(duì)一個(gè)數(shù)(eax)求n(edx)次方,這里Delphi函數(shù)調(diào)用約定是fastcall:

??????????
累加完成之后會(huì)進(jìn)行對(duì)比:累加的值和輸入的數(shù)字是否一樣

??????????
相同則跳轉(zhuǎn)到成功提示上:

??????????
然后再往下就是10字節(jié)長(zhǎng)度和11字節(jié)長(zhǎng)度的運(yùn)算對(duì)比了,方法類(lèi)似,都是分別計(jì)算一個(gè)次方,然后和原數(shù)比較,相同則成
算法分析
注冊(cè)碼生成算法:
?????#define _CRT_SECURE_NO_WARNINGS
#include
#include
int main()
{
??? for (int i = 1000000; i < 9999999; i++)
??? {
??????? int sum = 0;
??????? char tmp[8] = { 0 };
??????? _itoa(i, tmp, 10);
??????? for (int j = 0; j < 7; j++) sum += pow(tmp[j]-'0',7);
??????? if (i == sum)std::cout << i << std::endl;
??? }
}
總結(jié)
難得一次性分析了這么長(zhǎng)的反匯編,很多可以寫(xiě)成函數(shù)來(lái)方便調(diào)用的地方都直接內(nèi)聯(lián)了,可能是為了提高效率,這樣一來(lái)就出現(xiàn)了大量重復(fù)代碼段,分析花了挺多時(shí)間
2.??????017-Cabeca
算法難度:??
爆破難度:??
信息收集
運(yùn)行情況:

??????????
查殼與脫殼:
無(wú)殼,Delphi程序,那個(gè)年代很流行Delphi啊
??

????????
查字符串:
存在一些提示字符

??????????
調(diào)試分析
依然是拖IDR,復(fù)制出來(lái)在編輯器里寫(xiě)注釋
窗口里有兩個(gè)事件,一個(gè)是Name編輯框鍵入的時(shí)候觸發(fā)的,一個(gè)是點(diǎn)擊Try按鈕觸發(fā)的
??????????

直接看按鈕的驗(yàn)證邏輯:經(jīng)過(guò)函數(shù)初始化部分之后,判斷了兩個(gè)數(shù)字,不為0則向下進(jìn)行,然后一系列獲取編輯框的值,判空
這一段主要是判斷三個(gè)編輯框是否有輸入,無(wú)輸入就罵你傻子,然后清空編輯框
??????

????
接下來(lái)判斷序列號(hào),第一個(gè)數(shù)字和序列號(hào)1對(duì)比,第二個(gè)數(shù)字和序列號(hào)2對(duì)比,如果都對(duì)比上了,則提示成功
??????????

后面就是驗(yàn)證失敗的邏輯了,到這里問(wèn)題來(lái)了,這兩個(gè)數(shù)字是哪來(lái)的?剛剛看到界面還有個(gè)鍵入事件,去看看這個(gè)函數(shù):
?首先校驗(yàn)鍵入的值的合法性,是否小于0x80,也就是是否是ascii字符,不是就跳轉(zhuǎn),是就往下走
對(duì)字符減去8作為索引,從數(shù)組中取一個(gè)值,以這個(gè)值作為新的索引去跳轉(zhuǎn)表中去跳轉(zhuǎn)執(zhí)行
??????????

看看跳轉(zhuǎn)表跳轉(zhuǎn)地址的功能:就是操作這兩個(gè)值,然后返回
這兩個(gè)數(shù)字是在鍵入Name的時(shí)候生成的

?
暴力破解
爆破思路就是去改校驗(yàn)時(shí)候的那幾個(gè)跳轉(zhuǎn),比如判空跳轉(zhuǎn),對(duì)比跳轉(zhuǎn)等
算法分析
寫(xiě)注冊(cè)機(jī)邏輯也很清晰了,對(duì)于每一個(gè)Name的字符,都進(jìn)行一個(gè)指定的操作,就是對(duì)兩個(gè)數(shù)字進(jìn)行add,但是要錄入很多數(shù)字,麻煩,這里直接手算一個(gè)輸入:
?????Name = s
Serial1 = 2224
Serial2 = 1
結(jié)果:
??????????
總結(jié)
新160個(gè)CM里第一次見(jiàn)到的新姿勢(shì):通過(guò)鍵入事件在輸入時(shí)自動(dòng)生成校驗(yàn)碼
3.??????018-crackme_0006
算法難度:????
爆破難度:?
信息收集
運(yùn)行情況:

??????????
查殼與脫殼:
匯編寫(xiě)的程序,無(wú)殼
?
?????????
查字符串:
存在提示字符串:
??????????
調(diào)試分析
這個(gè)程序計(jì)算比較復(fù)雜,這里通過(guò)x86dbg+IDA結(jié)合進(jìn)行分析
找到驗(yàn)證邏輯
直接從oep開(kāi)始分析:
匯編寫(xiě)的程序,這里是一個(gè)窗口過(guò)程,參數(shù)里這個(gè)是過(guò)程函數(shù),處理窗口消息的函數(shù)
??????????
?一般自己創(chuàng)建窗口寫(xiě)窗口過(guò)程函數(shù)都是類(lèi)似這樣的:參數(shù)uMsg是消息號(hào),根據(jù)消息來(lái)進(jìn)行不同的操作
?????LRESULT CALLBACK MyWndProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam){
??? switch(uMsg){
??? ????case WM_CREATE:
??????????? //content
??????????? return 0;
??????? case WM_DESTORY:
??????????? //content
??????????? return 0;
??????? default:
??????????? return DefWindowProc(hwnd,uMsg,wParam,lParam);
??? }
??? return 0;
}
???????????分析思路很簡(jiǎn)單,找到Check的那個(gè)分支,下斷點(diǎn)向下分析
首先這是消息號(hào)0x111的分支,0x111是WM_COMMAND消息,對(duì)于這個(gè)消息,它的參數(shù)是命令號(hào),也就是反匯編里的這個(gè)arg_8,然后根據(jù)命令號(hào)進(jìn)行下一層的跳轉(zhuǎn)
這里需要跳轉(zhuǎn),跳轉(zhuǎn)之后就是在判斷命令號(hào),命令號(hào)在eax里,這里如果eax=0x3f0,則是about按鈕的事件,check按鈕事件位于命令號(hào)0x3ee
?????
?????
所以直接在0x4011ca下一行0x4011d0下斷點(diǎn)即可,從這里往下就是驗(yàn)證邏輯了
????????????第一段運(yùn)算
首先是第一段運(yùn)算,基于機(jī)器特征的校驗(yàn)碼:
通過(guò)自寫(xiě)函數(shù)去獲取卷序列號(hào),分別獲取C盤(pán)和D盤(pán)的,然后進(jìn)行一段浮點(diǎn)運(yùn)算,中間經(jīng)歷的這些自寫(xiě)的位移函數(shù)在此處無(wú)意義
??????????
這里來(lái)看一下這些自寫(xiě)函數(shù):首先是獲取卷序列號(hào)的:通過(guò)Win32 API獲取信息,直接返回
??????????
然后是左移函數(shù):從參數(shù)獲取值,然后左移指令進(jìn)行運(yùn)算,通過(guò)eax返回
??????????
循環(huán)左移函數(shù):使用循環(huán)位移運(yùn)算指令實(shí)現(xiàn)的
??????????
然后Add就是兩數(shù)相加,沒(méi)啥好看的,最后這個(gè)浮點(diǎn)運(yùn)算很關(guān)鍵:將獲取到的兩個(gè)卷序列號(hào)分別類(lèi)型轉(zhuǎn)換成浮點(diǎn)型,通過(guò)浮點(diǎn)運(yùn)算,分別計(jì)算兩個(gè)數(shù)的平方,然后開(kāi)根號(hào),最后轉(zhuǎn)換回十進(jìn)制:
??????????
第二段運(yùn)算
第二段運(yùn)算是基于用戶名的運(yùn)算,首先判斷了用戶名長(zhǎng)度,必須大于4字節(jié),然后調(diào)用了一個(gè)自寫(xiě)函數(shù)對(duì)用戶名計(jì)算了一個(gè)結(jié)果,然后進(jìn)行位移操作,與或操作之后得到一個(gè)新的值,這個(gè)值與第一段運(yùn)算的結(jié)果有關(guān),最后保存起來(lái)第二段計(jì)算的結(jié)果
??????????
看看這個(gè)自寫(xiě)函數(shù):循環(huán)遍歷每一個(gè)字節(jié),然后累乘起來(lái),使用cld擴(kuò)展指令,溢出32位的部分會(huì)保存到edx里,這里會(huì)把高32和低32位結(jié)果加起來(lái)
??????
????
第三段運(yùn)算
第三段運(yùn)算使用到了第二段運(yùn)算計(jì)算的結(jié)果,對(duì)這個(gè)結(jié)果除以10取余數(shù),用余數(shù)作為索引依次從固定字符串里取值,每次取值之后,再對(duì)剛剛那個(gè)結(jié)果除以4求商,并把商保存起來(lái)下次循環(huán)的時(shí)候用,這里的取余數(shù)和求商依然是自寫(xiě)函數(shù),功能簡(jiǎn)單就不展開(kāi)描述了
??????

????
最后計(jì)算出來(lái)一個(gè)字符串就是真的序列號(hào)了
校驗(yàn)
最后就是拿第三段運(yùn)算計(jì)算出來(lái)的字符串和用戶輸入比對(duì),比對(duì)一樣了就是成功
???

???????
暴力破解
驗(yàn)證流程圖大概如下,紅線標(biāo)的是要走的路,在驗(yàn)證邏輯里,對(duì)于需要跳轉(zhuǎn)的,就改jmp,對(duì)于不需要跳轉(zhuǎn)的就清空改nop,對(duì)于這種簡(jiǎn)單的邏輯這么操作比較無(wú)腦可行hhhh
?

?????????
注冊(cè)機(jī)編寫(xiě)
?????#include
#include
DWORD GetVolumeSerialNumber(const char* lpRootPathName) {
??? CHAR FileSystemNameBuffer[128];
??? DWORD FileSystemFlags;
??? DWORD VolumeSerialNumber;
??? CHAR VolumeNameBuffer[128];
??? GetVolumeInformationA(
??????? lpRootPathName,
??????? VolumeNameBuffer,
??????? 0x80u,
??????? &VolumeSerialNumber,
??????? (LPDWORD)0xFF,
??????? &FileSystemFlags,
??????? FileSystemNameBuffer,
??????? 0x80u);
??? return VolumeSerialNumber;
}
DWORD bit_move(DWORD val, int n) {
??? DWORD size = sizeof(val) * 8;
??? n = n % size;
??? return (val >> (size - n) | (val << n));//左移
}
DWORD floatdeal(DWORD a1,DWORD a2) {
??? int res = 0;
??? __asm {
??????? fwait;
??????? fninit;
??????? fild dword ptr[a1];
??????? fld st(0);
??????? fmulp st(1), st(0);
??????? fild dword ptr[a2];
??????? fld st(0);
??????? fmulp st(1), st(0);
??????? faddp st(1), st(0);
??????? fsqrt;
??????? fistp dword ptr[res];
??? }
??? return res;
??? //??? return (DWORD)sqrt((float)a1 * (float)a1 + (float)a2 * (float)a2);
}
int main()
{
??? DWORD volumeSerialNumber_C = 0;
??? DWORD volumeSerialNumber_D = 0;
??? DWORD tmp = 0;
??? DWORD tmp2 = 1;
??? LONGLONG tmp2_1 = 1;
??? char name[20] = {0};
??? char serial[20] = { 0 };
??? const char* arr = "071362de9f8ab45c";
??? std::cin >> name;
??? if (strlen(name) < 4) return 0;
??? //?第一段運(yùn)算
??? volumeSerialNumber_C = GetVolumeSerialNumber("C:\\");
??? volumeSerialNumber_D = GetVolumeSerialNumber("D:\\");
??? tmp = floatdeal(volumeSerialNumber_C, volumeSerialNumber_D);
??? //?第二段運(yùn)算
??? for (int i = 0; name[i]; i++)
??? {
??????? tmp2_1 *= name[i];
??????? tmp2 = tmp2_1 & 0x00000000FFFFFFFF;
??????? tmp2 += (tmp2_1 & 0xffffffff00000000) >> 32;
??? }
??? tmp2 = bit_move(tmp2,1);
??? tmp2 |= tmp;
??? tmp2 &= 0x0FFFFFFF;
??? //?第三段運(yùn)算
??? DWORD i = 0;
??? do
??? {
??????? serial[i++] = arr[tmp2 % 0x10];
??????? tmp2 /= 4;
??? } while (tmp2);
??? std::cout << "序列號(hào):";
??? std::cout << serial << std::endl;
??? system("pause");
}
結(jié)果

??????????
總結(jié)
?這是目前為止分析160個(gè)CM里遇到最復(fù)雜的校驗(yàn)算法了,分析了好久,這個(gè)程序主要有兩個(gè)難點(diǎn):
第一個(gè)難點(diǎn)在于程序直接啟動(dòng)了窗口過(guò)程,所以需要找到驗(yàn)證邏輯出現(xiàn)的地方才能開(kāi)始下斷點(diǎn),分析程序執(zhí)行流程即可去跟蹤消息號(hào)即可
第二個(gè)難點(diǎn)在于使用了8個(gè)自寫(xiě)函數(shù),要了解驗(yàn)證過(guò)程,需要知道自寫(xiě)函數(shù)做了什么事情,其中有的函數(shù)使用了浮點(diǎn)數(shù)運(yùn)算,這一塊不熟悉估計(jì)要查一會(huì)文檔了
做完之后再回頭看,嘛,也不過(guò)如此
參考資料
–?[1]?(2條消息) GetVolumeInformationA獲取磁盤(pán)卷標(biāo)、文件系統(tǒng),_上善若水pjf的博客-CSDN博客_getvolumeinformationa
–?[2]?匯編語(yǔ)言SHL(左移)指令:將操作數(shù)邏輯左移一位?(biancheng.net)
–?[3]?匯編語(yǔ)言ROL(循環(huán)左移)指令:將操作數(shù)所有位都向左移?(biancheng.net)
–?[4]?(2條消息) C/C++實(shí)現(xiàn)循環(huán)左移,循環(huán)右移_子木呀的博客-CSDN博客_c++數(shù)組循環(huán)左移
4.??????019-Acid_Byte.3
算法難度:?
爆破難度:?
信息收集
運(yùn)行情況:

??????????
查殼與脫殼:
有UPX殼:無(wú)腦ESP定律即可
???

???????
調(diào)試分析
IDR分析,復(fù)制到編輯器里寫(xiě)注釋,硬編碼Name和Serial,沒(méi)啥好說(shuō)的

??????????
效果:
?

5.??????020-cosh.3
???????????算法難度:??
???????????爆破難度:?
信息收集
運(yùn)行情況:

??????????
查殼與脫殼:
無(wú)殼,MFC程序

??????????
字符串:
提示字符串

??????????
調(diào)試分析
靜態(tài)分析從字符串入手或者從函數(shù)調(diào)用的交叉引用入手比較方便,可以查MessageBox函數(shù)的調(diào)用,也可以查提示信息字符串的交叉引用,這里從后者入手(方便),這個(gè)字符串大概率會(huì)出現(xiàn)在校驗(yàn)函數(shù)里
首先進(jìn)行兩個(gè)操作,判斷編輯框輸入的長(zhǎng)度,用戶名和序列號(hào)得是大于5,然后保存到局部變量里CString

??????????
接下來(lái)保存Name和Serial,分別對(duì)這兩個(gè)值進(jìn)行了一個(gè)運(yùn)算:
??????????

再往下就是strcmp了:兩個(gè)計(jì)算后的結(jié)果相同,則跳轉(zhuǎn)到成功分支
???????

???
注冊(cè)機(jī)
?????#include
int main()
{
??? char name[20] = "selph1";
??? char serial[20] = { 0 };
??? int Len = strlen(name);
??? for (int i = 0; name[i]; i++)?? serial[i] = name[i] ^ (i + 1);
??? for (int i = 0; serial[i]; i++) serial[i] = serial[i] ^ (i + 10);
? ??std::cout << serial;
}
???????????效果:
??

????????
總結(jié)
感覺(jué)已經(jīng)看這種反匯編越來(lái)越快了