天天看點

第二代Android殼源碼-Android第二代加強(support 4.4-8.1)

轉:https://bbs.pediy.com/thread-225303.htm

介紹

代碼放在github上:https://github.com/woxihuannisja/Bangcle

第二代加強使用的是記憶體動态加載Dex,也就是不落地加載,可以将Dex加密放在Apk中,在記憶體中實作解密

相容性

測試可以支援Andorid 4.4-8.1版本,目前還不能支援重寫了Application類 的Apk

原理

Dalvik下的動态加載方法使用的是我這篇文章的方案 https://bbs.pediy.com/thread-215078.htm,

Art 下提供了2種方案,

方案一是 call libart下的OpenMemory函數,如何将java的mCookie和c層的cookie聯系起來是一個難點,

方案二是 使用elf Hook來實作 ,由于在Nougat+上dlsym openMemory失敗,還有dex_location和dex_cache_location的路徑檢查,使用方案一有些問題。

最後

Android二代加強盡管脫殼不是很難,但是這種技術還是一些加強廠商的基礎,很多在這基礎上添加保護so,以及類抽取等等功能。

關鍵代碼:

main.java

package com.fengyue.bangcle;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.text.SimpleDateFormat;
import java.util.Iterator;

import org.dom4j.Attribute;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.Element;
import org.dom4j.io.OutputFormat;
import org.dom4j.io.SAXReader;
import org.dom4j.io.XMLWriter;

/**
 * 
 * @author Administrator
 *
 */
public class Main {
	private static final String DEX_APP_NAME = "com.storm.fengyue.StubApplication";
	private static  final String AES_KEY="1234567812345678";
	private static final SimpleDateFormat sdf = new SimpleDateFormat(
			"yyyyMMddHHmmss");
	private static Config config;
	private static String soName="libdexload";
	/**
	 * @param args
	 * @throws IOException 
	 */
	public static void main(String[] args) {
		System.out.println("----------Bangcle Automation----------------");
		System.out.println(System.getProperty("user.dir"));
		String cmd = args[0];
		if (!"b".equals(cmd)) {
			System.out.println("usage:java -jar Bangcle.jar b apkName");
			return;
		}
		
		String apkName = args[1];
//		String apkName="msgnow-release.apk";
//		String apkName="unpack_permmgr.apk";
//		String apkName="com.aispeech.weiyu_2.apk";
		String apkPath=getWorkPath()+File.separator+apkName;
		
		
		// 反編譯目錄
		String workPath=getWorkPath();
		String toolsPath=workPath+File.separator+"tools";
		int pos=apkName.lastIndexOf(".");
		String decompiledDirName=apkName.substring(0,pos);
		System.out.println("apkPath:" + apkPath+ " decompiledDirName:"+decompiledDirName);
		
		
		// 删除反編譯目錄
		File outputFolder = new File(workPath + File.separator+"output");
		if(!outputFolder.exists()){
			outputFolder.mkdir();
			System.out.println("建立生成目錄:"+outputFolder.getAbsolutePath());
		}
		File decompiledFile = new File(outputFolder.getAbsolutePath() +File.separator+ decompiledDirName);
		String decompiledPath=decompiledFile.getAbsolutePath();
		if (decompiledFile.exists()) {
			FileUtil.delete(decompiledFile);
			System.out.println("已删除" + decompiledFile.getAbsolutePath());
		}
		// 建立反編譯目錄
		boolean decompiled = false;
		try {
			long startTime = System.currentTimeMillis();
			System.out.println("正在反編譯" + apkPath);

			// 確定apktool.jar放在工作目錄下
			SystemCommand.execute("java -jar tools/apktool.jar d " + apkPath+" -o "+decompiledFile.getAbsolutePath()+" -s -f");
			
			System.out.println("反編譯結束,生成目錄" + decompiledFile.getAbsolutePath());
			
			decompiled = true;
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		
		//extract classes.dex
		String assetDir=decompiledPath+File.separator+"assets";
		File file_asset=new File(assetDir);
		if(!file_asset.exists()){
			file_asset.mkdirs();
			
		}
		

		String rawdex=decompiledPath+File.separator+"classes.dex";
		
		//encrypt raw dex
		byte[] data=FileUtil.getFileByte(rawdex);
		byte[] encrypt_data=AESUtil.encrypt(data, AES_KEY);
		System.out.println("AES encrypt classes.dex finished");
		FileUtil.byteToFile(encrypt_data, assetDir, "jiami.dat");
		
		System.out.println("copy jiami.dat to assets dir finished");
		//delete orig raw dex
		FileUtil.delete(new File(rawdex));
		
		try {
			//将libdexload.so 複制到 assets目錄下
			FileUtil.copyFile(toolsPath+File.separator+soName+"_arm.so",assetDir+File.separator+soName+"_arm.so" );
			FileUtil.copyFile(toolsPath+File.separator+soName+"_a64.so",assetDir+File.separator+soName+"_a64.so" );
		} catch (IOException e2) {
			// TODO Auto-generated catch block
			e2.printStackTrace();
		}

		try {
			FileUtil.copyDir(toolsPath+File.separator+"smali", decompiledPath+File.separator+"smali");
		} catch (IOException e1) {
			// TODO Auto-generated catch block
			e1.printStackTrace();
		}
		
		if (decompiled) {
			if (alterAndroidMainifest(decompiledFile.getAbsolutePath())) {

				try {
					String apkNewName=decompiledDirName+"_unsigned.apk";
					String apkNewSignName=decompiledDirName+"_signed.apk";
					String outputPath=workPath+File.separator+"output"+File.separator+apkNewName;
					String outputSignPath=workPath+File.separator+"output"+File.separator+apkNewSignName;
					SystemCommand.execute("java -jar tools/apktool.jar b "
							+ decompiledPath+ " -o "+outputPath);
					System.out.println("重編譯完成");
					System.out.println("正在簽名Apk");
					signApk_x509(outputPath,outputSignPath);
					System.out.println("重簽名完成");
					System.out.println("加強Apk目錄:"+outputSignPath);
			
				} catch (IOException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				} catch (InterruptedException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				} catch (Exception e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
			}
		} else {
			System.out.println("反編譯失敗");
		}
	}

	private static void signApk_x509(String unsignedApkPath, String signedApkPath){
		try {
			String command="java -jar tools/signapk.jar tools/testkey.x509.pem tools/testkey.pk8"+" "+unsignedApkPath+" "+signedApkPath;
			
			SystemCommand.execute(command);
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
				
	}


	

	/**
	 * 執行此方法確定,jarsigner的路徑被添加到系統環境變量中
	 * 
	 * @param unsignedApkPath
	 *            未簽名的apk的路徑
	 * @param signedApkPath
	 *            生成的簽名apk的路徑
	 * @throws IOException
	 * @throws InterruptedException
	 */
	private static void signApk(String unsignedApkPath, String signedApkPath)
			throws IOException, InterruptedException {

		String signCommand = "jarsigner -verbose -keystore "
				+ getConfig().signaturePath
				+ " "
				+ "-storepass "
				+ getConfig().storePwd
				+ " -keypass "
				+ getConfig().aliasPwd
				+ " "
				+ "-sigfile CERT -digestalg SHA1 -sigalg MD5withRSA -signedjar "
				+ signedApkPath + " " + unsignedApkPath + " "
				+ getConfig().alias;
		//System.out.println("cmd:" + signCommand);
		;
		SystemCommand.execute(signCommand);
		System.out.println("簽名後的apk路徑:" + signedApkPath);
	}


	/**
	 * 修改AndroidMinifest.xml中的Application Class為脫殼的Application Class名
	 * 在Application标簽中增加原Application Class名
	 * 
	 * @param workPath
	 */
	private static boolean alterAndroidMainifest(String workPath) {
		// TODO Auto-generated method stub
		String manifestFileName = "AndroidManifest.xml";
		File manifestFile = new File(workPath + "\\" + manifestFileName);
		if (!manifestFile.exists()) {
			System.err.println("找不到" + manifestFile.getAbsolutePath());
			return false;
		}
		SAXReader reader = new SAXReader();
		reader.setEncoding("UTF-8");
		try {
			Document document = reader.read(manifestFile);
			Element root = document.getRootElement();

			System.out.println("目前包名:" + root.attribute("package").getText());
			Element applicationEle = root.element("application");
			System.out.println("周遊application标簽的屬性:");
			Iterator<Attribute> attrIterator = applicationEle
					.attributeIterator();
			String APP_NAME = null;
			boolean find_application=false;
			while (attrIterator.hasNext()) {
				Attribute attr = attrIterator.next();
				System.out.println(attr.getNamespacePrefix() + ":"
						+ attr.getName() + " = " + attr.getValue());
				//尋找android:name标簽
				if ("android".equals(attr.getNamespacePrefix())
						&& "name".equals(attr.getName())) {
					APP_NAME = attr.getValue();
					attr.setValue(DEX_APP_NAME);
					find_application=true;
					System.out.println("orig application name:" + APP_NAME);
					System.out.println("new application name:" + attr.getValue());
					break;
				}
			}
			//如果apk沒有原始的application
			if(!find_application){
				System.out.println("no orig application");
				applicationEle.addAttribute("android:name",DEX_APP_NAME);
			}
			//儲存原始的application
			else{
				Element mataDataEle = applicationEle.addElement("meta-data");
				mataDataEle.addAttribute("android:name", "APP_NAME");
				mataDataEle.addAttribute("android:value", APP_NAME);
			}
			

			manifestFile.delete();
			
			//進行中文字元的亂碼
			//參考:https://blog.csdn.net/zhengdesheng19930211/article/details/64443572
//			java.io.Writer wr=new java.io.OutputStreamWriter(new java.io.FileOutputStream(manifestFile.getAbsolutePath()),"UTF-8");
//			document.write(wr);
//			wr.close();
			
			//下列方式不能進行中文字元 ,雖然這裡設定了編碼 但是儲存的檔案還是ANSI格式
//			OutputFormat format = OutputFormat.createPrettyPrint();
//			format.setEncoding("UTF-8");// 設定編碼
//			Writer filewriter = new FileWriter(manifestFile.getAbsolutePath());
//			System.out.println("manifest path:"+manifestFile.getAbsolutePath());
//			XMLWriter xmlwriter = new XMLWriter(filewriter, format);
//			xmlwriter.write(document);
//			xmlwriter.close();
//			System.out.println("修改Manifest成功");
			
			//進行中文亂碼
			//參考:https://blog.csdn.net/zl594389970/article/details/53353813
			System.out.println("使用方式3寫入xml");
			OutputFormat format = OutputFormat.createPrettyPrint();
			format.setEncoding("UTF-8");// 設定編碼
			OutputStream out =  new FileOutputStream(manifestFile.getAbsolutePath());
			System.out.println("manifest path:"+manifestFile.getAbsolutePath());
			XMLWriter xmlwriter = new XMLWriter(out, format);
			xmlwriter.write(document);
			xmlwriter.close();
			
			
			System.out.println("修改Manifest成功");
			return true;
		} catch (DocumentException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}

		return false;
	}

	private static Config getConfig() {
		if (config != null) {
			return config;
		}

		File signerConfigFile = new File(getWorkPath() + "\\" + "config.xml");
		if (!signerConfigFile.exists()) {
			System.err.println("找不到" + signerConfigFile.getAbsolutePath());
			return null;
		}
		// 讀取XML
		SAXReader reader = new SAXReader();
		try {
			Document document = reader.read(signerConfigFile);
			Element root = document.getRootElement();
			Element signaturePathEle = root.element("signature-path");
			String signaturePath = signaturePathEle.getText();

			Element storePwdEle = root.element("store-pwd");
			String storePwd = storePwdEle.getText();

			Element aliasEle = root.element("alias");
			String alias = aliasEle.getText();

			Element aliasPwdEle = root.element("alias-pwd");
			String aliasPwd = aliasPwdEle.getText();

			System.out.println("signature-path:" + signaturePath
					+ " store-pwd:" + storePwd + " alias:" + alias
					+ " aliasPwd:" + aliasPwd );
			config = new Config();
			config.signaturePath = signaturePath;
			config.storePwd = storePwd;
			config.alias = alias;
			config.aliasPwd = aliasPwd;
		} catch (DocumentException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}

		return config;
	}

	private static String getWorkPath() {
		return System.getProperty("user.dir");
	}

	static class Config {
		public String signaturePath;
		public String storePwd;
		public String alias;
		public String aliasPwd;
		public String winRARPath;
	}
}
           
第二代Android殼源碼-Android第二代加強(support 4.4-8.1)