天天看點

新160個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請看上期

  1. 016-fly_crkme3
               

算法難度:⭐⭐⭐

爆破難度:⭐

資訊收集

運作情況:

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

查殼與脫殼:

UPX殼,直接ESP定律脫殼即可

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

調試分析

Delphi程式,截圖不友善注釋,之後用IDR直接複制代碼到everEdit裡寫注釋了:

找到校驗按鈕,分析校驗函數sub_00444B30:

首先是判斷使用者是否有輸入,無輸入則彈窗,有輸入則跳轉到00444B78:

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

接下來校驗輸入的資料,輸入的内容必須是0x30~0x39之間,也就是純數字:

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

接下來校驗字元串長度:這個cm允許的輸入是9,10,11字元,對于每種輸入都有單獨的計算,這裡以輸入長度為9位為例:輸入格式是xx-xxx-xx

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

接下來進行了一個取數字的操作:進行了7段,總之就是把字元串中間的-去掉,把數字拼接在一起

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

取完數字之後,轉換成Int類型儲存起來:

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

接下來又進行了7段運算,運算出結果累加起來:

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

這裡調用了一個00444B20的函數,功能類似C的pow函數,對一個數(eax)求n(edx)次方,這裡Delphi函數調用約定是fastcall:

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

累加完成之後會進行對比:累加的值和輸入的數字是否一樣

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

相同則跳轉到成功提示上:

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

然後再往下就是10位元組長度和11位元組長度的運算對比了,方法類似,都是分别計算一個次方,然後和原數比較,相同則成

算法分析

注冊碼生成算法:

#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;
}
           

}

總結

難得一次性分析了這麼長的反彙編,很多可以寫成函數來友善調用的地方都直接内聯了,可能是為了提高效率,這樣一來就出現了大量重複代碼段,分析花了挺多時間

  1. 017-Cabeca
               

算法難度:⭐⭐

爆破難度:⭐⭐

資訊收集

運作情況:

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

查殼與脫殼:

無殼,Delphi程式,那個年代很流行Delphi啊

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

查字元串:

存在一些提示字元

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

調試分析

依然是拖IDR,複制出來在編輯器裡寫注釋

視窗裡有兩個事件,一個是Name編輯框鍵入的時候觸發的,一個是點選Try按鈕觸發的

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

直接看按鈕的驗證邏輯:經過函數初始化部分之後,判斷了兩個數字,不為0則向下進行,然後一系列擷取編輯框的值,判空

這一段主要是判斷三個編輯框是否有輸入,無輸入就罵你傻子,然後清空編輯框

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

接下來判斷序列号,第一個數字和序列号1對比,第二個數字和序列号2對比,如果都對比上了,則提示成功

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

後面就是驗證失敗的邏輯了,到這裡問題來了,這兩個數字是哪來的?剛剛看到界面還有個鍵入事件,去看看這個函數:

首先校驗鍵入的值的合法性,是否小于0x80,也就是是否是ascii字元,不是就跳轉,是就往下走

對字元減去8作為索引,從數組中取一個值,以這個值作為新的索引去跳轉表中去跳轉執行

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

看看跳轉表跳轉位址的功能:就是操作這兩個值,然後傳回

這兩個數字是在鍵入Name的時候生成的

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

暴力破解

爆破思路就是去改校驗時候的那幾個跳轉,比如判空跳轉,對比跳轉等

算法分析

寫注冊機邏輯也很清晰了,對于每一個Name的字元,都進行一個指定的操作,就是對兩個數字進行add,但是要錄入很多數字,麻煩,這裡直接手算一個輸入:

Name = s
           

Serial1 = 2224

Serial2 = 1

結果:

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

總結

新160個CM裡第一次見到的新姿勢:通過鍵入事件在輸入時自動生成校驗碼

  1. 018-crackme_0006
               

算法難度:⭐⭐⭐⭐

爆破難度:⭐

資訊收集

運作情況:

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

查殼與脫殼:

彙編寫的程式,無殼

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

查字元串:

存在提示字元串:

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

調試分析

這個程式計算比較複雜,這裡通過x86dbg+IDA結合進行分析

找到驗證邏輯

直接從oep開始分析:

彙編寫的程式,這裡是一個視窗過程,參數裡這個是過程函數,處理視窗消息的函數

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

一般自己建立視窗寫視窗過程函數都是類似這樣的:參數uMsg是消息号,根據消息來進行不同的操作

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;
           

}

分析思路很簡單,找到Check的那個分支,下斷點向下分析
           

首先這是消息号0x111的分支,0x111是WM_COMMAND消息,對于這個消息,它的參數是指令号,也就是反彙編裡的這個arg_8,然後根據指令号進行下一層的跳轉

這裡需要跳轉,跳轉之後就是在判斷指令号,指令号在eax裡,這裡如果eax=0x3f0,則是about按鈕的事件,check按鈕事件位于指令号0x3ee

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

是以直接在0x4011ca下一行0x4011d0下斷點即可,從這裡往下就是驗證邏輯了

第一段運算
           

首先是第一段運算,基于機器特征的校驗碼:

通過自寫函數去擷取卷序列号,分别擷取C槽和D盤的,然後進行一段浮點運算,中間經曆的這些自寫的位移函數在此處無意義

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

這裡來看一下這些自寫函數:首先是擷取卷序列号的:通過Win32 API擷取資訊,直接傳回

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

然後是左移函數:從參數擷取值,然後左移指令進行運算,通過eax傳回

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

循環左移函數:使用循環位移運算指令實作的

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

然後Add就是兩數相加,沒啥好看的,最後這個浮點運算很關鍵:将擷取到的兩個卷序列号分别類型轉換成浮點型,通過浮點運算,分别計算兩個數的平方,然後開根号,最後轉換回十進制:

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

第二段運算

第二段運算是基于使用者名的運算,首先判斷了使用者名長度,必須大于4位元組,然後調用了一個自寫函數對使用者名計算了一個結果,然後進行位移操作,與或操作之後得到一個新的值,這個值與第一段運算的結果有關,最後儲存起來第二段計算的結果

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

看看這個自寫函數:循環周遊每一個位元組,然後累乘起來,使用cld擴充指令,溢出32位的部分會儲存到edx裡,這裡會把高32和低32位結果加起來

![圖檔 72.png](https://s2.51cto.com/images/20220909/1662735656238152.png?x-oss-process=image/watermark,size_14,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_30,g_se,x_10,y_10,shadow_20,type_ZmFuZ3poZW5naGVpdGk=) 
           

第三段運算

第三段運算使用到了第二段運算計算的結果,對這個結果除以10取餘數,用餘數作為索引依次從固定字元串裡取值,每次取值之後,再對剛剛那個結果除以4求商,并把商儲存起來下次循環的時候用,這裡的取餘數和求商依然是自寫函數,功能簡單就不展開描述了

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

最後計算出來一個字元串就是真的序列号了

校驗

最後就是拿第三段運算計算出來的字元串和使用者輸入比對,比對一樣了就是成功

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

暴力破解

驗證流程圖大概如下,紅線标的是要走的路,在驗證邏輯裡,對于需要跳轉的,就改jmp,對于不需要跳轉的就清空改nop,對于這種簡單的邏輯這麼操作比較無腦可行hhhh

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

注冊機編寫

#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;
// 第一段運算
volumeSerialNumber_C = GetVolumeSerialNumber("C:\\");
volumeSerialNumber_D = GetVolumeSerialNumber("D:\\");
tmp = floatdeal(volumeSerialNumber_C, volumeSerialNumber_D);
// 第二段運算
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;
// 第三段運算
DWORD i = 0;
do
{
    serial[i++] = arr[tmp2 % 0x10];
    tmp2 /= 4;

} while (tmp2);
std::cout << "序列号:";
std::cout << serial << std::endl;
system("pause");
           

}

結果

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

總結

這是目前為止分析160個CM裡遇到最複雜的校驗算法了,分析了好久,這個程式主要有兩個難點:

第一個難點在于程式直接啟動了視窗過程,是以需要找到驗證邏輯出現的地方才能開始下斷點,分析程式執行流程即可去跟蹤消息号即可

第二個難點在于使用了8個自寫函數,要了解驗證過程,需要知道自寫函數做了什麼事情,其中有的函數使用了浮點數運算,這一塊不熟悉估計要查一會文檔了

做完之後再回頭看,嘛,也不過如此

參考資料

– [1] (2條消息) GetVolumeInformationA擷取磁盤卷标、檔案系統,_上善若水pjf的部落格-CSDN部落格_getvolumeinformationa

– [2] 彙編語言SHL(左移)指令:将操作數邏輯左移一位 (biancheng.net)

– [3] 彙編語言ROL(循環左移)指令:将操作數所有位都向左移 (biancheng.net)

– [4] (2條消息) C/C++實作循環左移,循環右移_子木呀的部落格-CSDN部落格_c++數組循環左移

  1. 019-Acid_Byte.3
               

算法難度:⭐

爆破難度:⭐

資訊收集

運作情況:

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

查殼與脫殼:

有UPX殼:無腦ESP定律即可

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

調試分析

IDR分析,複制到編輯器裡寫注釋,寫死Name和Serial,沒啥好說的

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

效果:

新160個CrackMe分析-第2組:11-20(下)
  1. 020-cosh.3
        算法難度:⭐⭐
    
        爆破難度:⭐
               

資訊收集

運作情況:

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

查殼與脫殼:

無殼,MFC程式

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

字元串:

提示字元串

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

調試分析

靜态分析從字元串入手或者從函數調用的交叉引用入手比較友善,可以查MessageBox函數的調用,也可以查提示資訊字元串的交叉引用,這裡從後者入手(友善),這個字元串大機率會出現在校驗函數裡

首先進行兩個操作,判斷編輯框輸入的長度,使用者名和序列号得是大于5,然後儲存到局部變量裡CString

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

接下來儲存Name和Serial,分别對這兩個值進行了一個運算:

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

再往下就是strcmp了:兩個計算後的結果相同,則跳轉到成功分支

![圖檔 86.png](https://s2.51cto.com/images/20220909/1662735840718072.png?x-oss-process=image/watermark,size_14,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_30,g_se,x_10,y_10,shadow_20,type_ZmFuZ3poZW5naGVpdGk=)
           
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;
           
效果:





![圖檔 87.png](https://s2.51cto.com/images/20220909/1662735849544183.png?x-oss-process=image/watermark,size_14,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_30,g_se,x_10,y_10,shadow_20,type_ZmFuZ3poZW5naGVpdGk=)