【天翼杯安卓題二】 愛加密脫殼實戰

來源:互聯網
上載者:User

標籤: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.........

我乾的傻事
  • 代碼迴圈條件忘記寫了,導致越界,一開啟應用就報錯。

  • 檔案開啟失敗,貌似是許可權問題,我直接暴力把 /data/local/tmp 改成 777

總結

分析安卓底層代碼的錯誤,要關注 logcat 日誌,找到出問題的代碼點,然後把庫的帶符號版本放到ida中分析
分析bug, 要看代碼的關鍵邏輯, 判斷條件等。

最後

要多實踐,如有問題請在下面評論。

【天翼杯安卓題二】 愛加密脫殼實戰

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.