一道360 crackme的詳細分析

該crackme主要實現都在so中,用ida加載libqihoo.so,出現如下錯誤java

 

第一個錯誤說明是節區頭部表格的表項大小錯誤,第二個錯誤是指節區頭部表格的大小或偏移值錯誤。無論它,點擊「Yes」繼續加載。找到JNI_OnLoad函數,發現該函數已經加密:c++

咱們知道so 文件加載時首先會查看 .init 或 .init_array 段是否存在,若是存在那麼就先運行這段的內容,若是不存在的話那麼就檢查是否存在JNI_OnLoad,存在則執行。因此JNI_OnLoad的解密可能在 .init 或 .init_array 段中。
由於 .init 或者 .init_array 在 IDA 動態調試的時候是不會顯示出來的,因此須要靜態分析出這兩段的偏移量而後動態調試的時候計算出絕對位置,而後在 make code(快捷鍵:c),這樣才能夠看到該段內的代碼內容。
查看 .init_array 段的地址有兩種辦法:
(1).可使用 IDA 加載 .so 文件,按ctrl+s快捷鍵查看 「Segments」 視圖,這裏會列出不一樣類型的代碼段信息,以下圖所示。算法

(2).可使用二進制工具 readelf 來查看 .so 文件的結構,在 OS X 上面可使用 greadelf 代替。api

從上述(1)能夠看出,並無顯示該so的.init 或者 .init_array段,因此咱們須要對該so進行修復。
由於節區頭部表的偏移值e_shoff爲0x2118c(在0x20h處),因此根據ELF文件的結構,從該偏移值開始到文件結尾的數據爲整個節區頭部表。因爲節區頭部表項的大小(e_shentsize)固定爲0x28,因此咱們能夠由:(0x214fb - 0x2118c + 1)/0x28 = 0x16 得出真正的節區頭部表項數目(e_shnum)爲0x16。
下面再來看e_shstrndx字段,咱們從ELF文件中能夠明顯地看出,字符串表節區爲最後一個節區,因此它的索引值應當爲0x15。app

根據上述規則將對應數值修改好後保存文件,而後再次加載修復後的so文件,已經不會報錯了,按ctrl+s快捷鍵也能夠查看到.init_array段信息了:函數

IDA 定位到.init_array段,能夠看到.init_array段會執行__gnu_armfini_26函數工具

該函數含義大量花指令,爲了便於分析,咱們在該函數下斷點,對其進行動態調試。經過動態調試,咱們會發現該so中花指令與真實指令的關係以下圖所示:加密

__gnu_armfini_26函數清除花指令後的彙編代碼以下:spa

BL __gnu_armfini_30
MOV R4, R0
MOV R0, #0x28 ; '('
BL sysconf ; 獲取系統的cpu個數和可用的cpu個數
ADD R1, R4, #0x8A00
BIC R0, R4, #0xFF0
MOV R2, #7
ADD R1, R1, #0xEC
BIC R0, R0, #0xF
BL __gnu_arm_fini_06
MOV R1, #0x8A00
MOV R0, R4
ADD R1, R1, #0xEC
BL __gnu_armfini_29
ADD R1, R4, #0x8A00
MOV R0, R4
ADD R1, R1, #0xEC
BL sub_B6E37614

其中調用的__gnu_armfini_29函數比較可疑3d

_arm_aeabi_6去掉花指令後是

STMFD SP!, {R3-R8,R10,LR}
MOV R4, R0
MOV R5, R1    ;0000000C
MOV R8, R2
MOV R3, #0

:loc_B6EFDA74
STRB R3, [R8,R3]    ;0x00 將r8開始的地址依次填充0-0x99
ADD R3, R3, #1    ;0+1
CMP R3, #0x100
BNE loc_B6EFDA74
MOV R3, #0
MOV R6, R3
STRB R3, [R8,#0x100]    ;R8+#0x100地址處的內容置0
STRB R3, [R8,#0x101]    ;R8+#0x101地址處的內容置0
MOV R7, R8
ADD R10, R8, #0x100    ;R8:BEF9C6F4, R10:BEF9C7F4
MOV R0, R3

:loc_B6EFD914
LDRB R2, [R4,R0]    ;r2=0x96,0xE6,0x57
LDRB R3, [R7]    ;R3=0x00, R3=0xC2,0x00
ADD R0, R0, #1    ;r0=0x01, 0x02, 0x03
MOV R1, R5    ;R1=R5=0x0C
ADD R2, R2, R3    ;R2 = R2+R3=0xC6+0x00, R2 = R2+R3=0x96+0xC2=0x158, R2 = R2+R3=0xE6+0x00=0xE6
ADD R6, R2, R6    ;R6 = R2+R6=0xC6+0x00, R6 = R2+R6=0x158+0xC6=0x21E, R6 = R2+R6=0xE6+0x01E=0x104
AND R6, R6, #0xFF    ;R6 = 0xC6, 0x01E, 0x004
LDRB R2, [R8,R6]    ;r2=0x00, 0xEF, 0xE9
STRB R2, [R7],#1    ;STR R0,[R1], #8 將R0中的字數據寫入以R1爲地址的存儲器 中,並將新地址R1+8寫入R1。
STRB R3, [R8,R6]    ;r3=0x00, 0xC2, 0x00
BL __aeabi_idivmod    ;執行完後r1=r0=0x01,0x02,0x03
CMP R7, R10    ;r7=0xC2,r10=0x00 
AND R0, R1, #0xFF    ;r0:0x01
BNE loc_B6EFD914
LDMFD SP!, {R3-R8,R10,PC}

這實際上就是RC4算法的第一個步驟(參考:https://www.jianshu.com/p/fcfdcc3ff9d5):

/*
初始化狀態向量S和臨時向量T,供keyStream方法調用
*/

void initial() { 
    for(int i=0;i<256;++i){ 
        S[i]=i; 
        T[i]=K[i%keylen]; 
    } 
} 

_gnu_arm_message函數具備rc4算法的典型特徵:

_gnu_armfini_29這個函數解密從0x75EEC6DC地址開始,大小爲0x8AEC內存區域的數據,其實是對地址爲「基址0x75ED5000+0x176dc」,大小爲0x8AEC的內存區域數據進行解密。

 

解密完畢後將該so dump下來,dump的起始地址及大小能夠經過ida的Modules菜單獲取:

dump腳本:

static main(void)
{
auto fp, begin, end, ptr;
fp = fopen("d:\\dump.so", "wb");
begin = 0x75ED5000;
end = begin + 0x23000;
for ( ptr = begin; ptr < end; ptr ++ )
fputc(Byte(ptr), fp);
}

按快捷鍵「shift+F2」打開腳本編寫窗口寫入上述腳本代碼,點擊「Run"運行該代碼就能夠在D盤根目錄看到dump下來的so文件dump.so。

用IDA打開dump.so,定位到JNI_OnLoad函數,按「C」鍵,將數據轉換成代碼,按」F5「查看反編譯的僞代碼,按「Y」鍵修正變量類型,獲得以下的代碼:


咱們知道RegisterNatives的函數原型是:

jint RegisterNatives(jclass clazz, const JNINativeMethod* methods,jint nMethods)

第二個參數是JNINativeMethod結構體

typedef struct {
  const char* name;
  const char* signature;
  void* fnPtr;
} JNINativeMethod;

結構體的第三個參數是函數指針,該結構體的偏移地址爲0x22004,定位到該偏移地址處發現沒有正常把函數指針解析出來,須要進一步調試。


從新打開libqihoo.so,在 __gnu_armfini_29函數處下斷點,進行動態調試。按F9運行到這裏後,函數會進行內存區塊解密,解密完成以後,在Modules菜單中找到libqihoo.so,點開,咱們就能夠看到「verify」和「JNI_OnLoad」函數了,分別下斷點(注意,必定要在解密完以後,即執行完__gnu_arm_message函數後再下斷點),如圖所示:

再按F9就來到了JNI_OnLoad函數處

定位到JNINativeMethod結構體地址,從ida中咱們能夠看到該結構體函數指針指向verify函數:

 

繼續F9,執行到verify函數。
在verify函數中調用了__gnu_Unwind_8和__gnu_Unwind_6函數。
__gnu_Unwind_8函數的做用是將jstring轉換成爲c/c++中的char*
從java程序中傳過去的String對象在本地方法中對應的是jstring類型,jstring類型和c中的char*不一樣,因此若是你直接當作char*使用的話,就會出錯。所以在使用以前須要將jstring轉換成爲c/c++中的char*,這裏使用JNIEnv提供的方法轉換。

/* java jstring turn to c/c++ char* */
char* jstringToChar(JNIEnv* env, jstring jstr)
{ 
    char* pStr = NULL;
    jclass jstrObj = (*env)->FindClass(env, "java/lang/String");
    jstring encode = (*env)->NewStringUTF(env, "utf-8");
    jmethodID methodId = (*env)->GetMethodID(env, jstrObj, "getBytes", "(Ljava/lang/String;)[B");
    jbyteArray byteArray = (jbyteArray)(*env)->CallObjectMethod(env, jstr, methodId, encode);
    jsize strLen = (*env)->GetArrayLength(env, byteArray);
    jbyte *jBuf = (*env)->GetByteArrayElements(env, byteArray, JNI_FALSE);
    if (jBuf > 0)
    {
        pStr = (char*)malloc(strLen + 1);
        if (!pStr)
        {
            return NULL;
        }
        memcpy(pStr, jBuf, strLen);
        pStr[strLen] = 0;
    }
    env->ReleaseByteArrayElements(byteArray, jBuf, 0);
    return pStr;
}

實際上上述轉換的字符串就是用戶輸入的用戶名、郵箱以及序列號。

在__gnu_Unwind_6函數中會判斷用戶輸入的序列號長度是否爲8。

調用__gnu_Unwind_1函數,此函數又有不少花指令

再次調用_gnu_armfini_29函數,經過前面的分析咱們知道這是一個RC4加解密函數。

在這裏其實是取「用戶名+郵箱」組成的字符串的的前四個字節進行加密,如圖所示:

而後就是一個比較關鍵的函數__gnu_Unwind_4了

這個函數其實是實現了SHA1加密算法,如下爲該算法初始化緩衝區時的特徵:

__gnu_Unwind_4將」用戶名+郵箱「組成的字符串(包括前四個被RC4算法加密過的字符)進行SHA1加密後,經過__gnu_Unwind_11函數與用戶輸入的序列號進行對比,從而判斷是否破解成功。

 

 

附:其它方法
咱們從新打開一個ida,加載libqihoo.so,按快捷鍵「shift+F2」打開腳本編寫窗口編寫腳本對該內存區域進行解密。以下圖所示:

這樣獲得的就是徹底解密的so文件了。
腳本內容:

import idaapi

def rc4(data, key):
"""RC4 encryption and decryption method."""
S, j, out = list(range(256)), 0, []

for i in range(256):
j = (j + S[i] + ord(key[i % len(key)])) % 256
S[i], S[j] = S[j], S[i]

i = j = 0
for ch in data:
i = (i + 1) % 256
j = (j + S[i]) % 256
S[i], S[j] = S[j], S[i]
out.append(chr(ord(ch) ^ S[(S[i] + S[j]) % 256]))

return "".join(out)

key=idaapi.get_many_bytes(0x20434,12)
addr= 0x176dc;
data=idaapi.get_many_bytes(addr,0x8aec)
decode=rc4(data,key)
idaapi.patch_bytes(addr, decode)

 樣本下載地址:

連接:https://pan.baidu.com/s/1pMXryhP 密碼:clun

相關文章
相關標籤/搜索