標籤:ade virt ref 修改檔案 www. make for [] index
前言
這個apk使用愛加密加密,加密時間是2017.6月。這個題其實就是個脫殼題,脫完立馬見flag。(出題人也太懶了)
題目連結:https://gitee.com/hac425/blog_data/blob/master/app02.apk
殼介紹
愛加密的殼16年年底就已經開始通過 hook dvmResolveClass
,在調用具體方法時解密方法指令,然後將 DexFile結構體中的對應方法的 md->insns
指向 解密後的方法指令資料區,然後進入 真正的dvmResolveClass
中執行指令,執行完後在重新加密指令,這樣就可以防止 dexhunter
等工具在記憶體中 dump dex
檔案。
流程圖
圖片來源
脫殼
由上面可以知道,在dvmResolveClass
函數執行的時候,代碼是已經還原好了的。這時我們去dump
相應的指令就是正確的指令。於是修改 dvmResolveClass
的代碼,dump
方法的資料。
修改 dvmResolveClass
函數:
/* add dump .....*/ char key_str[20] = "jiajiatest"; int fd=open("/data/local/tmp/resolve_class_config",O_RDONLY,0666); if(fd!=-1){ int len = read(fd,key_str,19); key_str[len-1] = ‘\x00‘; key_str[len] = ‘\x00‘; close(fd); } ALOGI("The key_str ---> %s----referrer->descriptor--->%s--", key_str, referrer->descriptor); if(strstr(referrer->descriptor, key_str)){ char task_name[] = "task_name"; char *logbuf = new char[1024]; char path[50] = {0}; sprintf(path, "/data/local/tmp/%s_dump_%d", key_str, getpid()); FILE *fpw = fopen(path, "awb+"); for(int i=0; i < referrer->directMethodCount; i++){ Method* md = &referrer->directMethods[i]; const char* mName_d = md->name; const u2 insSize_d = md->insSize; const u2* insns_d = md->insns; const u2 methodldx_d = md->methodIndex; u4 insns_d_size = dvmGetMethodInsnsSize(md);// ALOGI("hacklh_md---->%p, i-->%d, directMethodCount-->%d", md, i,referrer->directMethodCount); sprintf(logbuf,"-------------- (KL)resolving [class=%s, method=%s, methodIndex=%u, insSize=%u, insns_d=%x, codeSize=%d] in pid: %d(name: %s)",referrer->descriptor,mName_d,methodldx_d,insSize_d,(u4)insns_d, insns_d_size,getpid() , task_name); LOGD("%s",logbuf); if(fpw != NULL){ fwrite(logbuf,1,strlen(logbuf),fpw); fflush(fpw); fwrite((u1*)insns_d,1,insns_d_size*2, fpw); fflush(fpw); }else{ LOGD("——(KL)open %s fail!", path); } } for(int i=0; i < referrer->virtualMethodCount; i++){ Method* mv = &referrer->virtualMethods[i]; const char* mName_v = mv->name; const u2 insSize_v = mv->insSize; const u2* insns_v = mv->insns; const u2 methodIdx_v = mv->methodIndex; u4 insns_v_size = dvmGetMethodInsnsSize(mv); sprintf(logbuf,"-------------- (KL)resolving [class=%s, method=%s, methodIndex=%u, insSize=%u, insns_d=%x, codeSize=%d] in pid: %d(name: %s)",referrer->descriptor,mName_v,methodIdx_v,insSize_v,(u4)insns_v, insns_v_size,getpid() , task_name); LOGD("%s",logbuf); if(fpw != NULL){ fwrite(logbuf,1,strlen(logbuf),fpw); fflush(fpw); fwrite((u1*)insns_v,1,insns_v_size*2, fpw); fflush(fpw); }else{ LOGD("%s","——(KL)open file fail!"); } } if(fpw != NULL){ fclose(fpw); } delete logbuf;/* add end .....*/
dump之後我們需要把指令patch到dex對應位置上去,patch的方式有很多種,我選擇使用ida指令碼對他進行patch。我覺得ida就是一個各種檔案格式的loader,我們可以在ida中修改檔案的內容,然後可以讓ida把修改應用到檔案中,以完成patch。 因此在IDA中patch代碼十分的方便,而且也很方便的查看patch後的結果。patch代碼的流程是:
讀取dump的方法指令--->定位相應方法指令資料區在ida中的位置---->patch
代碼如下:
#! /usr/bin/python# -*- coding: utf8 -*-# 該指令碼用於在ida中使用dump下來的method指令對 dex 進行Patchimport refrom dex_parser import dex#儲存 存放dump資料的字典data_array = []#用來避免多次patchpatched = []file_data = ""def parse_meta_data(data=""): # print data ret = {} tokens = re.findall("\[class=(.*?),.*?method=(.*?),.*?codeSize=(.*?)\]",data) # print tokens ret[‘class_name‘] = tokens[0][0][1:].replace(‘/‘,‘.‘).replace(‘;‘,‘‘) ret[‘method‘] = tokens[0][1] ret[‘code_size‘] = int(tokens[0][2]) * 2 #dex檔案格式定義,總大小為 codeSize*2 # print ret return ret#注釋,用於給ida執行# def patch_byte(a, b):# print hex(b),def patch(dest, src, size): print "dest::{}, src::{}, size::{}".format(dest, src, size) for i in range(size): patch_byte(dest + i, int(file_data[ src + i].encode(‘hex‘), 16)) print "\n"def parse_dump_data(filename): global file_data with open(filename, "rb") as fp: file_data = fp.read() #使用Regex把說明dump資料的中繼資料載入到記憶體 all_item = re.findall("-------------- \(KL\)resolving(.*?) in pid:.*?\(name: task_name\)", file_data) offset = 0 for meta_data in all_item: try: #使用字典組織資料 #{‘class_name‘: ‘com.example.jiajiatest.MainActivity‘, ‘code_size‘: 306, ‘method‘: ‘add‘, ‘data_offset‘: 7175} ret = parse_meta_data(meta_data) data_addr = file_data.find(‘(name: task_name)‘, offset) + 17 ret[‘data_offset‘] = data_addr data_array.append(ret) offset = data_addr except Exception as e: raise e return data_arraydef get_method_addr(method_data, signature_str): for md_name in method_data: if signature_str in md_name: return method_data[md_name] return -1def patch_dex(dump_data_file, dex_file): dump_data = parse_dump_data(dump_data_file) dex_obj = dex.dex_parser(dex_file) method_data = dex_obj.get_class_data() for item in dump_data: signature_str = "{}::{}".format(item[‘class_name‘], item[‘method‘]) if signature_str not in patched: #擷取要patch的目標地址 addr = get_method_addr(method_data, signature_str) if addr == -1: print "{} can‘t get insns addr".format(signature_str) continue #do patch print "patch " + signature_str, patch(addr,item[‘data_offset‘],item[‘code_size‘]) patched.append(signature_str) # print patched # for i in patched: # print iimport pprintpatch_dex("F:\code_workplace\ida_script\jiajiatest_dump_20406","F:\code_workplace\ida_script\classes.dex" )if __name__ == ‘__main__‘: print "comming main" # parse_dump_data("F:\code_workplace\ida_script\jiajiatest_dump_20406") # patch_dex("F:\code_workplace\ida_script\jiajiatest_dump_20406","F:\code_workplace\ida_script\classes.dex" ) # dex_obj = dex.dex_parser("F:\code_workplace\ida_script\classes.dex") # class_data = dex_obj.get_class_data() # pprint.pprint(class_data) # parse_meta_data("-------------- (KL)resolving [class=Lcom/example/jiajiatest/HttpRunner;, method=makeImgHttpGET, methodIndex=13, insSize=2, insns_d=6daf04d8, codeSize=270] in pid: 20406(name: task_name)")
patch前後對比:
patch前
patch後
這時已經可以看到程式的主體邏輯了。然後查看字串就可以拿到flag.........
我乾的傻事
總結
分析安卓底層代碼的錯誤,要關注 logcat
日誌,找到出問題的代碼點,然後把庫的帶符號版本放到ida中分析
分析bug, 要看代碼的關鍵邏輯, 判斷條件等。
最後
要多實踐,如有問題請在下面評論。
【天翼杯安卓題二】 愛加密脫殼實戰