環境:windows xp
工具:
1、OllyDBG
2、exeinfo
3、IDA
0x00 查殼
加了UPX殼,那么就要脫殼了。可以使用單步法來脫殼。
UPX殼還是比較簡單的,開頭pushad,找個popad,然后就是jmp了。
然后就可以用OD來脫殼了。
0x01 分析
先運行一下程序,看看有什么東西。
隨便輸入些東西進去,彈出了提示輸入錯誤的消息框。
OD載入,F9運行程序。然后隨便輸入點東西,彈出上面找個消息框。在OD中按F12暫停,Alt+F9運行到用戶代碼,然后點擊消息框的確定。
程序停在這里
單步執行到函數返回。
記下這個401E86,然后在IDA打開這個脫殼后的程序。等待IDA分析完后,按G跳轉,輸入這個401E86
跳轉完后就可以按F5進行分析了。
程序的流程主要如下:
一、程序根據輸入的用戶名進行處理:
(1)將輸入的內容翻轉再拼接一起,如輸入:gnubd,將得到gnubddbung。
(2)讀取HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion下的ProductID和RegisteredOwner的值,并且拼接到第(1)步的結果后面。
二、然后再進行類似md5值的計算:
用 smd5(input_username_2) 表示結果。
程序再對輸入的序列號進行運行,用 calc(input_serial) 表示結果。
由于smd5(input_username_2)的結果是4個DWORD,即smd5_result[4],所以第57行處要求用戶的輸入也是4個DWORD,所以這里對輸入的內容進行了限制,為0-9,A-F,a-f這樣。
calc(input_serial)的結果也是4個DWORD,即calc_result[4],接下來就是第68行處進行smd5_result 和 calc_result 比較,全部相等就輸出通過。
首先來分析一下這個smd5,我這里有個md5算法筆記,可以先看看普通md5的算法是怎樣的。
看完之后比較這個程序的,可以發現是第54行處存在差異。填充后的消息在計算長度的時候把0x80也算上了,這樣使得計算出來的值與普通的md5值不同,這個寫出代碼的難度不大,找個md5源碼改一下就行了。
三、接下來看一看calc函數,也就是地址sub_401B90處的函數。
int __cdecl sub_401B90(int input_0, int constValue)
{int result; // eax@1int v3; // ebx@1unsigned int first; // esi@1unsigned int second; // edi@1unsigned __int64 v6; // rax@2int v7; // esi@2int v8; // edi@2unsigned __int64 v9; // rax@2result = input_0;v3 = constValue;first = *(_DWORD *)input_0; //輸入的第一個值second = *(_DWORD *)(input_0 + 4);//輸入的第二個值if ( constValue ){do{v6 = 2 * __PAIR__(second, first); // 這里注意可能存在 first<<1 發生進位,而 second<<1 丟失第31位LODWORD(v6) = ((unsigned __int8)(2 * first) | (second >> 31)) & 4;v7 = v6 | (second >> 31);v8 = HIDWORD(v6); // second<<1 | first>>31v9 = (unsigned __int64)(unsigned int)v6 << 11;// 第3位移動到了第14位LODWORD(v9) = v7 & 0x2000 ^ v9;v9 <<= 18; // 第14位移動到32位LODWORD(v9) = v7 & 0x80000000 ^ v9;v9 *= 2i64; // 將結果移動到了高32位的最后1位first = v9 ^ v7;second = HIDWORD(v9) ^ v8;--v3;}while ( v3 );result = input_0;}*(_DWORD *)result = first;*(_DWORD *)(result + 4) = second;return result;
}
因為上面是由ida分析得到的,v6和v9這些事int64類型的變量,分析起來可能比較難,所以為了簡單理解,我將其拆分,v6的高32位為v6_h,v6低32位為v6_l,v9同理。
do{v6_l = first<<1; v6_h = second<<1 | first>>31;v6_l = first<<1 & 4 ; // 因為有個&4,所以找個second>>31可以忽略了,不影響結果v7 = v6_l | (second >> 31); // first<<1 | (second >> 31)v8 = v6_h; // second<<1 | (first >> 31)v9_l = v6_l << 11; // (first<<1 & 4)<<11v9_h = v6_l >> 21; // 0v9_l = v7 & 0x2000 ^ v9; // (first<<1 & 0x2000)^(first<<1 & 4)<<11v9_l <<= 18; // ((first<<1 & 0x2000)^(first<<1 & 4)<<11)<<18v9_h <<= 18; //0v9_l = v7 & 0x80000000 ^ v9; //(first<<1 & 0x80000000) ^((first<<1 & 0x2000)^(first<<1 & 4)<<11)<<18 v9_h = v9_l>>31; // & 0x80000000 后就只剩下最高1位了,左移1位就進入到了高32位的最低1位v9_l <<= 1; // & 0x80000000 后就只剩下最高1位了,左移1位就溢出了,變回0first = v9_l ^ v7; // 0 ^ (first<<1 | (second >> 31)) second = v9_h ^ v8;// ((first<<1 & 0x80000000) ^((first<<1 & 0x2000)^(first<<1 & 4)<<11)<<18)>>31 ^ (second<<1 | (first >> 31))--v3;}while ( v3 );
將輸入的serial設為first,那么第一輪運算的結果為first_1,second_1,根據上面可得:
first_1 = v9_l ^ v7; // 0 ^ (first<<1 | (second >> 31))second_1 = v9_h ^ v8;// ((first<<1 & 0x80000000) ^((first<<1 & 0x2000)^(first<<1 & 4)<<11)<<18)>>31 ^ (second<<1 | (first >> 31))
用first.1表示first的第1位,first.32表示first第32位,
即:first.1 = first & 1,first.3 = first & 0x80000000。
根據上面可以逆推得到:
first = first_1>>1 | ((first_1.32 ^ first_1.14 ^ first_1.3^ second_1)&1)
second = first<<31 | (second_1>>1);
這個逆推得到的函數稱為 rcalc,
所以只需將 rcalc(smd5(input_username)) 計算出來就好了。