看流星社区

 找回密码
 注册账号
查看: 4525|回复: 2

安卓so文件脱壳思路(java版)附源代码

[复制链接]

该用户从未签到

发表于 2017-6-4 10:15:53 | 显示全部楼层 |阅读模式

本文针对安卓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代码吧,其中涉及的类都在附件中。欢迎拍砖。


package com.tudou.soshelltest;

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

import com.tudou.soshell.ELF32_Phdr;
import com.tudou.soshell.ELF32_Shdr;
import com.tudou.soshell.ELFType32;
import com.tudou.soshell.ELFUtils;


public class MGPBaseMainRepair {
     
    public static String sofile = "so/liba.so";
    public static String dumpsofile = "so/liba_dump.so";
    public static String repairsofile = "so/liba_repair.so";
     
    public static void main(String[] args){
        try {
            byte[] srcElfFileBytes = ELFUtils.readFile(sofile);     
            ELFType32 elfsrc = new ELFType32(srcElfFileBytes);
            //读取头部内容
            elfsrc.parseELFHeader();
     
            //读取程序头信息
            elfsrc.parseSegmentHeader();
            elfsrc.printSegmentHeaderList();
            elfsrc.parseDynamicSegment();
            /*
             * 解析段名称
             */
            elfsrc.parseSectionNames();
            //读取段头信息
            elfsrc.parseSectionHeader();
            elfsrc.printSectionHeaderList();
            elfsrc.parseSectionHeaderDetail();
            
            byte[] destElfFileBytes = ELFUtils.readFile(dumpsofile);        
            ELFType32 elfdest = new ELFType32(destElfFileBytes);
            //读取头部内容
            elfdest.parseELFHeader();
     
            //读取程序头信息
            elfdest.parseSegmentHeader();
            elfdest.printSegmentHeaderList();
    //      elfdest.parseDynamicSegment();
            
            /*
             * dump出来的so(待处理的so)没有Section头信息,无需解析
             */
    //      elfdest.parseSectionHeader();
    //      elfdest.printShdrList();
    //      elfdest.printShdrDetailList();
            /*
             * 修复步骤
             * 1、将Segment(程序段)头拷贝到dump.so中
             * 2、将Section(节)头拷贝到dump.so中
             * 3、因为ProgramSegment一般位于so的头部一端(也就是顶部),而Section头一般位于so的尾部一端(也就是底部)
             *      所以,需要判断下尾部是否发生了偏移,如果Section头数据的起始地址(offset)正好处于偏移的数据中,则需要重新计算偏移后的地址(一般
             *      就是+0x1000),然后再拷贝到dump.so中
             *  4、更新在映射到内存过程中,不变的数据。
             *  5、将在.init_array段中出现的,在.rel.dyn的重定位项设置为0,然后将.init_array段全部数据置0
             */
            System.out.println("\n\n********************************************************************************");
            System.out.println("************************************ 开始修复 **********************************");
            System.out.println("********************************************************************************\n\n");
            /**
             * 判断是否需要移动Section头信息
             */
            int __SECTION_START_OFFSET__ = 0;
            List<ELF32_Shdr> srcShdrList = elfsrc.secheaderList;
            if(srcShdrList != null && srcShdrList.size() > 0){
                /*
                 * 当文件偏移地址与内存加载时地址不一致时,dump出来的so文件,会按照内存中的偏移保存到文件中。
                 */
                int startoffset = 0;
                for(ELF32_Shdr shdr : srcShdrList){
                    /*
                     * 第一步:更新Section节offset,使之与addr一样。(offset是在文件内的偏移,addr是加载后内存的偏移)
                     */
                    int offset = ELFUtils.byte2Int(shdr.sh_offset);
                    int addr = ELFUtils.byte2Int(shdr.sh_addr);
    //              int size = Utils.byte2Int(shdr.sh_size);
                    if(addr > 0 && offset > 0 && Math.abs(addr - offset) == 0x1000){
                        if(startoffset == 0){
                            startoffset = ELFUtils.byte2Int(shdr.sh_offset);
                        }
                        if(__SECTION_START_OFFSET__ == 0){
                            __SECTION_START_OFFSET__ = startoffset;
                        }
                        System.arraycopy(shdr.sh_addr, 0, shdr.sh_offset, 0, 4);
                    }else if((offset > startoffset) && addr == 0){
                        offset += 0x1000;
                        byte [] offsetbytes = ELFUtils.int2Byte(offset);
                        System.arraycopy(offsetbytes, 0, shdr.sh_offset, 0, 4);
                    }
                }
                ELFUtils.log("在源包上,更新section的offset,使之与addr一样");
            }
            
            List< ELF32_Phdr> srcPhdrList = elfsrc.proheaderList;
            if(srcPhdrList != null && srcPhdrList.size() > 0){
                /*
                 * 当文件偏移地址与内存加载时地址不一致时,dump出来的so文件,会按照内存中的偏移保存到文件中。
                 */
                int startoffset = 0;
                for( ELF32_Phdr phdr : srcPhdrList){
                    /*
                     * 第二步:更新Segment节offset,使之与addr一样。(offset是在文件内的偏移,addr是加载后内存的偏移)
                     */
                    int offset = ELFUtils.byte2Int(phdr.p_offset);
                    int vaddr = ELFUtils.byte2Int(phdr.p_vaddr);
                    if(vaddr > 0 && offset > 0 && Math.abs(vaddr - offset) == 0x1000){
                        if(startoffset == 0){
                            startoffset = ELFUtils.byte2Int(phdr.p_offset);
                        }
                        System.arraycopy(phdr.p_vaddr, 0, phdr.p_offset, 0, 4);
                    }else if((offset > startoffset) && vaddr == 0){
                        offset += 0x1000;
                        byte [] offsetbytes = ELFUtils.int2Byte(offset);
                        System.arraycopy(offsetbytes, 0, phdr.p_offset, 0, 4);
                    }
                }
                ELFUtils.log("在源包上,更新segment的offset,使之与addr一样");
            }
            
            /*
             * 第四步:将源包的Section头信息更新到dump包上
             */
            ByteArrayOutputStream baos = new ByteArrayOutputStream();
            for(ELF32_Shdr shdr : srcShdrList){
                    baos.write(shdr.toByteArray());
            }
            baos.close();
            baos.flush();
            byte [] updateSectionDumpBytes = baos.toByteArray();
            elfdest.updateSectionHeader(updateSectionDumpBytes);
            System.out.println("将源包的Section头信息更新到dump包上");
            
            /*
             * 第五步:将源包的Segment头信息更新到dump包上
             */
            ByteArrayOutputStream pbaos = new ByteArrayOutputStream();
            for(ELF32_Phdr shdr : srcPhdrList){
                pbaos.write(shdr.toByteArray());
            }
            pbaos.close();
            pbaos.flush();
            byte [] updateSegmentDumpBytes = pbaos.toByteArray();
            elfdest.updateProgramHeadert(updateSegmentDumpBytes);
            System.out.println("将源包的Segment头信息更新到dump包上");

            /*
             * 第六步:更新其他Section节
             */
            elfdest.shstrtab = elfsrc.shstrtab;
            elfdest.dynamic = elfsrc.dynamic;
            elfdest.dynstr = elfsrc.dynstr;
            elfdest.comment = elfsrc.comment;
            elfdest.noteGunGoldVe = elfsrc.noteGunGoldVe;
            elfdest.armattributes = elfsrc.armattributes;
            elfdest.data = elfsrc.data;
            elfdest.got = elfsrc.got;
            
            elfdest.copySectionList(elfsrc.secheaderList);
            elfdest.copySegmentList(elfsrc.proheaderList);
            elfdest.setZeroInitArray();
            elfdest.updateSectionData();

            /*
             * 第七步:一般情况下,Program Segment头信息位于elf的顶部,Section头信息位于elf的底部
             * 从内存中dump出来时,有可能Section头信息需要移动位置
             */
            int shoff = ELFUtils.byte2Int(elfdest.elfheader.e_shoff);
            if(shoff > __SECTION_START_OFFSET__){
                System.out.println("准备移动Section头信息。。。");
                shoff += 0x1000;
                byte [] offsetbytes = ELFUtils.int2Byte(shoff);
                System.arraycopy(offsetbytes, 0, elfdest.elfheader.e_shoff, 0, 4);
                byte [] dataupdate = elfdest.elfheader.getSrcDataUpdate();
                elfdest.updateELFHeader();
                elfdest.updateSectionHeader(updateSectionDumpBytes);
                System.out.println("将源包的Section头信息更新到dump包上,第二次更新");
            }else{
                System.out.println("不需要移动Section头信息");
            }
            
            /*
             * 第八步:保存到文件
             */
            ELFUtils.saveFile(repairsofile, elfdest.getELFFileBytes());
        } catch (Exception e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }

    }

}

该用户从未签到

发表于 2020-3-26 12:56:13 | 显示全部楼层
支持楼主,支持看流星社区,以后我会经常来!

该用户从未签到

发表于 2020-5-16 20:02:26 | 显示全部楼层
激动人心,无法言表,感谢楼主,感谢看流星社区的分享!
点击按钮快速添加回复内容: 支持 高兴 激动 给力 加油 苦寻 生气 回帖 路过 感恩
您需要登录后才可以回帖 登录 | 注册账号

本版积分规则

小黑屋|手机版|Archiver|看流星社区 |网站地图

GMT+8, 2024-3-19 13:36

Powered by Kanliuxing X3.4

© 2010-2019 kanliuxing.com

快速回复 返回顶部 返回列表