吾爱破解 - 52pojie.cn

 找回密码
 注册[Register]

QQ登录

只需一步,快速开始

查看: 22044|回复: 33
收起左侧

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

  [复制链接]
liujinlong 发表于 2016-3-18 11:55
本文针对安卓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代码吧,其中涉及的类都在附件中。欢迎拍砖。


[Java] 纯文本查看 复制代码
001
002
003
004
005
006
007
008
009
010
011
012
013
014
015
016
017
018
019
020
021
022
023
024
025
026
027
028
029
030
031
032
033
034
035
036
037
038
039
040
041
042
043
044
045
046
047
048
049
050
051
052
053
054
055
056
057
058
059
060
061
062
063
064
065
066
067
068
069
070
071
072
073
074
075
076
077
078
079
080
081
082
083
084
085
086
087
088
089
090
091
092
093
094
095
096
097
098
099
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
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();
        }
 
    }
 
 
}

soshell.zip

34.88 KB, 下载次数: 407, 下载积分: 吾爱币 -1 CB

免费评分

参与人数 4威望 +1 吾爱币 +1 热心值 +4 收起 理由
pistons + 1 + 1 谢谢@Thanks!
huyukun + 1 回个热心啊
qtfreet00 + 1 + 1 感谢发布原创作品,吾爱破解论坛因你更精彩.
lies2014 + 1 感谢发布原创作品,吾爱破解论坛因你更精彩.

查看全部评分

发帖前要善用论坛搜索功能,那里可能会有你要找的答案或者已经有人发布过相同内容了,请勿重复发帖。

 楼主| liujinlong 发表于 2016-3-20 06:53
如果自解密在init段,还要看是否有init_array段,如有且不想保留了,可以使用上面代码,生成repair。so之后,手动将init段的函数addr都设置为0即可。
ai枫 发表于 2016-3-18 14:39
solea 发表于 2016-3-18 16:12 来自手机
lies2014 发表于 2016-3-18 17:33
感谢分享,现在的一些壳已经不能通过这种方式脱掉了,比如阿里聚
 楼主| liujinlong 发表于 2016-3-18 18:02
对的。只适合自解密程序在init_array段中的脱壳。
wxl2578 发表于 2016-3-18 18:38
自解密在init段可否?
poca 发表于 2016-3-18 20:57
前排支持一下
superykc 发表于 2016-3-19 20:54
虽然看不懂,不过还是感谢支持一下。
州哥在江湖 发表于 2016-3-25 15:54
感谢分享。
您需要登录后才可以回帖 登录 | 注册[Register]

本版积分规则

返回列表

RSS订阅|小黑屋|处罚记录|联系我们|吾爱破解 - LCG - LSG ( 京ICP备16042023号 | 京公网安备 11010502030087号 )

GMT+8, 2025-4-1 13:46

Powered by Discuz!

Copyright © 2001-2020, Tencent Cloud.

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