易语言教程_易语言源码_易语言写挂_易语言论坛_看流星社区

 找回密码
 注册
零基础辅助入门教学 原创 高清 专业课程售后(每日解答)
零基础辅助入门教学 原创 高清 专业课程售后(每日解答)
零基础辅助入门教学 原创 高清 专业课程售后(每日解答)
零基础辅助入门教学 原创 高清 专业课程售后(每日解答)
零基础辅助入门教学 原创 高清 专业课程售后(每日解答)
零基础辅助入门教学 原创 高清 专业课程售后(每日解答)
赞助广告位 请点击这里联系站长 QQ20209081
赞助广告位 请点击这里联系站长 QQ20209081
赞助广告位 请点击这里联系站长 QQ20209081
查看: 493|回复: 0

安卓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();
        }

    }

}

您需要登录后才可以回帖 登录 | 注册

本版积分规则

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

GMT+8, 2018-6-22 00:02 易语言论坛 易语言导航

Powered by 看流星社区 X3.2

©2011-2016 最好的辅助编程技术论坛

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