吾爱破解 - 52pojie.cn

 找回密码
 注册[Register]

QQ登录

只需一步,快速开始

查看: 21656|回复: 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] 纯文本查看 复制代码
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, 2024-11-24 21:37

Powered by Discuz!

Copyright © 2001-2020, Tencent Cloud.

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