綁定帳號登入

Android 台灣中文網

打印 上一主題 下一主題

[資料] Android so檔案脫殼思路(java版)附源碼

[複製連結] 查看: 1990|回覆: 0|好評: 0
跳轉到指定樓層
樓主
fam1001 | 收聽TA | 只看該作者 回帖獎勵 |倒序瀏覽 |閱讀模式
發表於 2016-3-19 10:35

馬上加入Android 台灣中文網,立即免費下載應用遊戲。

您需要 登錄 才可以下載或查看,沒有帳號?註冊

x
本文針對Android so本加密的情況,且解密算法在.init_array段的函數中,目標是去掉加密的算法,然後對dump出來的so進行修復,然後再放回到apk包中,使之能正常的跑起來。脫殼思路如下:
(1)準備工作:使用IDA調試apk,使之斷點再so的JNI_Onload函數的第一行,一般來說都是「PUSH    {R4-R7,LR}」類似的一行。
這時,通過ctrl+s找到so的起始地址和結束地址,然後將起始地址和結束地址之間的資料拷貝出來,保存成一個so。
這裡假定原so名稱為:liba.so,從記憶體中拷貝出來的so名稱為:liba_dump.so
(2)  修復工作一般包含如下方面:
2.1 將Segment(程式段)頭拷貝到liba_dump.so中,這個操作主要是為了在IDA能進行靜態解析。
2.2 將Section(節)頭拷貝到liba_dump.so中,這個操作主要是為了在IDA能進行靜態解析。
2.3 因為ProgramSegment一般位於so的頭部一端(也就是頂部),而Section頭一般位於so的尾部一端(也就是底部)

所以,需要判斷下尾部是否發生了偏移,如果Section頭資料的起始地址(offset)正好處於偏移的資料中,則需要重新計算偏移後的地址(一般
就是+0x1000),然後再拷貝到liba_dump.so中


2.4 更新在映射到記憶體過程中,不變的資料。
2.5 將在.init_array段中出現的,在.rel.dyn的重定位項設定為0,然後將.init_array段全部資料置0
2.6 將修復後的資料寫入到新的檔案中,比如:liba_repair.so, 該so可以直接放入到原apk中,程式可以正常的跑起來。


直接上java代碼吧。歡迎拍磚。

  1. package com.tudou.soshelltest;

  2. import java.io.ByteArrayOutputStream;
  3. import java.util.List;

  4. import com.tudou.soshell.ELF32_Phdr;
  5. import com.tudou.soshell.ELF32_Shdr;
  6. import com.tudou.soshell.ELFType32;
  7. import com.tudou.soshell.ELFUtils;


  8. public class MGPBaseMainRepair {
  9.      
  10.     public static String sofile = "so/liba.so";
  11.     public static String dumpsofile = "so/liba_dump.so";
  12.     public static String repairsofile = "so/liba_repair.so";
  13.      
  14.     public static void main(String[] args){
  15.         try {
  16.             byte[] srcElfFileBytes = ELFUtils.readFile(sofile);     
  17.             ELFType32 elfsrc = new ELFType32(srcElfFileBytes);
  18.             //讀取頭部內容
  19.             elfsrc.parseELFHeader();
  20.      
  21.             //讀取程式頭訊息
  22.             elfsrc.parseSegmentHeader();
  23.             elfsrc.printSegmentHeaderList();
  24.             elfsrc.parseDynamicSegment();
  25.             /*
  26.              * 解析段名稱
  27.              */
  28.             elfsrc.parseSectionNames();
  29.             //讀取段頭訊息
  30.             elfsrc.parseSectionHeader();
  31.             elfsrc.printSectionHeaderList();
  32.             elfsrc.parseSectionHeaderDetail();
  33.             
  34.             byte[] destElfFileBytes = ELFUtils.readFile(dumpsofile);        
  35.             ELFType32 elfdest = new ELFType32(destElfFileBytes);
  36.             //讀取頭部內容
  37.             elfdest.parseELFHeader();
  38.      
  39.             //讀取程式頭訊息
  40.             elfdest.parseSegmentHeader();
  41.             elfdest.printSegmentHeaderList();
  42.     //      elfdest.parseDynamicSegment();
  43.             
  44.             /*
  45.              * dump出來的so(待處理的so)沒有Section頭訊息,無需解析
  46.              */
  47.     //      elfdest.parseSectionHeader();
  48.     //      elfdest.printShdrList();
  49.     //      elfdest.printShdrDetailList();
  50.             /*
  51.              * 修復步驟
  52.              * 1、將Segment(程式段)頭拷貝到dump.so中
  53.              * 2、將Section(節)頭拷貝到dump.so中
  54.              * 3、因為ProgramSegment一般位於so的頭部一端(也就是頂部),而Section頭一般位於so的尾部一端(也就是底部)
  55.              *      所以,需要判斷下尾部是否發生了偏移,如果Section頭資料的起始地址(offset)正好處於偏移的資料中,則需要重新計算偏移後的地址(一般
  56.              *      就是+0x1000),然後再拷貝到dump.so中
  57.              *  4、更新在映射到記憶體過程中,不變的資料。
  58.              *  5、將在.init_array段中出現的,在.rel.dyn的重定位項設定為0,然後將.init_array段全部資料置0
  59.              */
  60.             System.out.println("
  61. ********************************************************************************");
  62.             System.out.println("************************************ 開始修復 **********************************");
  63.             System.out.println("********************************************************************************
  64. ");
  65.             /**
  66.              * 判斷是否需要移動Section頭訊息
  67.              */
  68.             int __SECTION_START_OFFSET__ = 0;
  69.             List<ELF32_Shdr> srcShdrList = elfsrc.secheaderList;
  70.             if(srcShdrList != null && srcShdrList.size() > 0){
  71.                 /*
  72.                  * 當檔案偏移地址與記憶體載入時地址不一致時,dump出來的so檔案,會按照記憶體中的偏移保存到檔案中。
  73.                  */
  74.                 int startoffset = 0;
  75.                 for(ELF32_Shdr shdr : srcShdrList){
  76.                     /*
  77.                      * 第一步:更新Section節offset,使之與addr一樣。(offset是在檔案內的偏移,addr是載入後記憶體的偏移)
  78.                      */
  79.                     int offset = ELFUtils.byte2Int(shdr.sh_offset);
  80.                     int addr = ELFUtils.byte2Int(shdr.sh_addr);
  81.     //              int size = Utils.byte2Int(shdr.sh_size);
  82.                     if(addr > 0 && offset > 0 && Math.abs(addr - offset) == 0x1000){
  83.                         if(startoffset == 0){
  84.                             startoffset = ELFUtils.byte2Int(shdr.sh_offset);
  85.                         }
  86.                         if(__SECTION_START_OFFSET__ == 0){
  87.                             __SECTION_START_OFFSET__ = startoffset;
  88.                         }
  89.                         System.arraycopy(shdr.sh_addr, 0, shdr.sh_offset, 0, 4);
  90.                     }else if((offset > startoffset) && addr == 0){
  91.                         offset += 0x1000;
  92.                         byte [] offsetbytes = ELFUtils.int2Byte(offset);
  93.                         System.arraycopy(offsetbytes, 0, shdr.sh_offset, 0, 4);
  94.                     }
  95.                 }
  96.                 ELFUtils.log("在源包上,更新section的offset,使之與addr一樣");
  97.             }
  98.             
  99.             List< ELF32_Phdr> srcPhdrList = elfsrc.proheaderList;
  100.             if(srcPhdrList != null && srcPhdrList.size() > 0){
  101.                 /*
  102.                  * 當檔案偏移地址與記憶體載入時地址不一致時,dump出來的so檔案,會按照記憶體中的偏移保存到檔案中。
  103.                  */
  104.                 int startoffset = 0;
  105.                 for( ELF32_Phdr phdr : srcPhdrList){
  106.                     /*
  107.                      * 第二步:更新Segment節offset,使之與addr一樣。(offset是在檔案內的偏移,addr是載入後記憶體的偏移)
  108.                      */
  109.                     int offset = ELFUtils.byte2Int(phdr.p_offset);
  110.                     int vaddr = ELFUtils.byte2Int(phdr.p_vaddr);
  111.                     if(vaddr > 0 && offset > 0 && Math.abs(vaddr - offset) == 0x1000){
  112.                         if(startoffset == 0){
  113.                             startoffset = ELFUtils.byte2Int(phdr.p_offset);
  114.                         }
  115.                         System.arraycopy(phdr.p_vaddr, 0, phdr.p_offset, 0, 4);
  116.                     }else if((offset > startoffset) && vaddr == 0){
  117.                         offset += 0x1000;
  118.                         byte [] offsetbytes = ELFUtils.int2Byte(offset);
  119.                         System.arraycopy(offsetbytes, 0, phdr.p_offset, 0, 4);
  120.                     }
  121.                 }
  122.                 ELFUtils.log("在源包上,更新segment的offset,使之與addr一樣");
  123.             }
  124.             
  125.             /*
  126.              * 第四步:將源包的Section頭訊息更新到dump包上
  127.              */
  128.             ByteArrayOutputStream baos = new ByteArrayOutputStream();
  129.             for(ELF32_Shdr shdr : srcShdrList){
  130.                     baos.write(shdr.toByteArray());
  131.             }
  132.             baos.close();
  133.             baos.flush();
  134.             byte [] updateSectionDumpBytes = baos.toByteArray();
  135.             elfdest.updateSectionHeader(updateSectionDumpBytes);
  136.             System.out.println("將源包的Section頭訊息更新到dump包上");
  137.             
  138.             /*
  139.              * 第五步:將源包的Segment頭訊息更新到dump包上
  140.              */
  141.             ByteArrayOutputStream pbaos = new ByteArrayOutputStream();
  142.             for(ELF32_Phdr shdr : srcPhdrList){
  143.                 pbaos.write(shdr.toByteArray());
  144.             }
  145.             pbaos.close();
  146.             pbaos.flush();
  147.             byte [] updateSegmentDumpBytes = pbaos.toByteArray();
  148.             elfdest.updateProgramHeadert(updateSegmentDumpBytes);
  149.             System.out.println("將源包的Segment頭訊息更新到dump包上");

  150.             /*
  151.              * 第六步:更新其他Section節
  152.              */
  153.             elfdest.shstrtab = elfsrc.shstrtab;
  154.             elfdest.dynamic = elfsrc.dynamic;
  155.             elfdest.dynstr = elfsrc.dynstr;
  156.             elfdest.comment = elfsrc.comment;
  157.             elfdest.noteGunGoldVe = elfsrc.noteGunGoldVe;
  158.             elfdest.armattributes = elfsrc.armattributes;
  159.             elfdest.data = elfsrc.data;
  160.             elfdest.got = elfsrc.got;
  161.             
  162.             elfdest.copySectionList(elfsrc.secheaderList);
  163.             elfdest.copySegmentList(elfsrc.proheaderList);
  164.             elfdest.setZeroInitArray();
  165.             elfdest.updateSectionData();

  166.             /*
  167.              * 第七步:一般情況下,Program Segment頭訊息位於elf的頂部,Section頭訊息位於elf的底部
  168.              * 從記憶體中dump出來時,有可能Section頭訊息需要移動位置
  169.              */
  170.             int shoff = ELFUtils.byte2Int(elfdest.elfheader.e_shoff);
  171.             if(shoff > __SECTION_START_OFFSET__){
  172.                 System.out.println("準備移動Section頭訊息。。。");
  173.                 shoff += 0x1000;
  174.                 byte [] offsetbytes = ELFUtils.int2Byte(shoff);
  175.                 System.arraycopy(offsetbytes, 0, elfdest.elfheader.e_shoff, 0, 4);
  176.                 byte [] dataupdate = elfdest.elfheader.getSrcDataUpdate();
  177.                 elfdest.updateELFHeader();
  178.                 elfdest.updateSectionHeader(updateSectionDumpBytes);
  179.                 System.out.println("將源包的Section頭訊息更新到dump包上,第二次更新");
  180.             }else{
  181.                 System.out.println("不需要移動Section頭訊息");
  182.             }
  183.             
  184.             /*
  185.              * 第八步:保存到檔案
  186.              */
  187.             ELFUtils.saveFile(repairsofile, elfdest.getELFFileBytes());
  188.         } catch (Exception e) {
  189.             // TODO Auto-generated catch block
  190.             e.printStackTrace();
  191.         }

  192.     }


  193. }
複製代碼
「用Android 就來APK.TW」,快來加入粉絲吧!
Android 台灣中文網(APK.TW)

評分

參與人數 2幫助 +2 收起 理由
doubleruri + 1 很給力!
球-球 + 1 好內容,老衲來為這篇文章開開光.

查看全部評分

收藏收藏2 分享分享 分享專題
用Android 就來Android 台灣中文網(https://apk.tw)
回覆

使用道具 舉報

您需要登錄後才可以回帖 登錄 | 註冊

本版積分規則