8种机械键盘轴体对比

本人程序员,要买一个写代码的键盘,请问红轴和茶轴怎么选?

这篇文章介绍一下自己学习dex加固的过程。虽然市面上比这篇文章介绍的技术要先进的多了,但还是从基础抓起吧。而且这种最简单的加固实践起来也遇到好多坑,一开始看reference里的文章真是云里雾里,所以记录一下以免以后忘了思想。

代码见github。

原理

我们在加固的过程中需要三个对象:需要加密的Apk(源Apk)

壳程序Apk(负责解密Apk工作)

加密工具(将源Apk进行加密和壳Dex合并成新的Dex)解释一下,熟悉android的朋友们都知道。运行android程序后会有一个class.dex文件。它的格式我就不多说了。我们本身有一个source Apk,是我们需要进行加密的Apk。然后我们有一个解壳apk,它负责进行解密,还有一个进行加密的java程序,它负责对apk进行加密。

加密java程序工作过程:也就是上面那张图。其实这里有种A要B,B要A的循环过程,所以比较难以理解。现在假设我们已经有了解壳apk的class.dex文件了。那么我们把source apk进行加密后,附在解壳dex的后面,这样就形成新的dex了。

解壳apk工作过程:我们先假设解壳apk的class.dex已经是加固过的dex了,解壳Apk就从这个dex去读取加过密的source apk,然后进行解密,再进行运行。所以,我们最终真正运行的是解壳apk。

理一下全部过程:

①编写source apk

②编写解壳apk,假设自己的class.dex已经是加固过的dex。

③编写加密java程序过程,把解壳dex和source apk进行整合,得到新的class.dex文件。

④关键步骤,幸亏看了reference的文章才知道。跪谢大神们。应该把解壳apk变成.zip文件,再替换里面的class.dex,再重打包重签名。一开始傻逼的我以为替换掉后在eclipse再运行就可,然而这会重新生成class.dex,因此总是找不到source apk。

过程

截图来自于Reference<1>。

source apk

加密java程序

这里脱壳程序的dex文件,就是解壳dex,作者改了个名而已。

class.dex是运行java程序后自动生成的文件。

public static void (String[] args){
try {
File payloadSrcFile = new File("force/ForceApkObj.apk"); //需要加壳的程序
System.out.println("apk size:"+payloadSrcFile.length());
File unShellDexFile = new File("force/ForceApkObj.dex");//解客dex
byte[] payloadArray = encrpt(readFileBytes(payloadSrcFile));//以二进制形式读出apk,并进行加密处理//对源Apk进行加密操作
byte[] unShellDexArray = readFileBytes(unShellDexFile);//以二进制形式读出dex
int payloadLen = payloadArray.length;
int unShellDexLen = unShellDexArray.length;
int totalLen = payloadLen + unShellDexLen +4;//多出4字节是存放长度的。
byte[] newdex = new byte[totalLen]; // 申请了新的长度
//添加解壳代码
System.arraycopy(unShellDexArray, 0, newdex, 0, unShellDexLen);//先拷贝dex内容
//添加加密后的解壳数据
System.arraycopy(payloadArray, 0, newdex, unShellDexLen, payloadLen);//再在dex内容后面拷贝apk的内容
//添加解壳数据长度
System.arraycopy(intToByte(payloadLen), 0, newdex, totalLen-4, 4);//最后4为长度
//修改DEX file size文件头
fixFileSizeHeader(newdex);
//修改DEX SHA1 文件头
fixSHA1Header(newdex);
//修改DEX CheckSum文件头
fixCheckSumHeader(newdex);
String str = "force/classes.dex";
File file = new File(str);
if (!file.exists()) {
file.createNewFile();
}
FileOutputStream localFileOutputStream = new FileOutputStream(str);
localFileOutputStream.write(newdex);
localFileOutputStream.flush();
localFileOutputStream.close();
} catch (Exception e) {
e.printStackTrace();
}
}

上面的程序很好的体现了下图:

最后多了四个字节用来存放source dex长度。

public static byte[] intToByte(int number) {
byte[] b = new byte[4];
for (int i = 3; i >= 0; i--) {
b[i] = (byte) (number % 256);
number >>= 8;
}
return b;
}

这个是将int转化为byte[4]数组,因为int是32位,每八位换成一个byte。

更改头部

更改filesize。

private static void fixFileSizeHeader(byte[] dexBytes){
//新文件长度
byte[] newfs = intToByte(dexBytes.length);
System.out.println(Integer.toHexString(dexBytes.length));
byte[] refs = new byte[4];
//高位在前,低位在前掉个个--->因为小端格式
for (int i = 0; i < 4; i++) {
refs[i] = newfs[newfs.length - 1 - i];
System.out.println(Integer.toHexString(newfs[i]));
}
System.arraycopy(refs, 0, dexBytes, 32, 4);//修改(32-35)
}

解壳apk

RefInvoke.java是封装反射类工具Android程序由不同的组件构成,系统在有需要的时候启动程序组件。因此解壳程序必须在Android系统启动组件之前运行,完成对解壳数据的解壳及APK文件的动态加载,否则会使程序出现加载类失败的异常。Android开发者都知道Applicaiton做为整个应用的上下文,会被系统第一时间调用,这也是应用开发者程序代码的第一执行点。因此通过对AndroidMainfest.xml的application的配置可以实现解壳代码第一时间运行。

我们需要找到一个时机,就是在脱壳程序还没有运行起来的时候,来加载源程序的Apk,执行他的onCreate方法,那么这个时机不能太晚,不然的话,就是运行脱壳程序,而不是源程序了。查看源码我们知道。Application中有一个方法:attachBaseContext这个方法,他在Application的onCreate方法执行前就会执行了,那么我们的工作就需要在这里进行。

当在AndroidMainfest.xml文件配置为解壳代码的Application时。源程序原有的Applicaiton将被替换,为了不影响源程序代码逻辑,我们需要在解壳代码运行完成后,替换回源程序原有的Application对象。我们通过在AndroidMainfest.xml文件中配置原有Applicaiton类信息来达到我们 的目的。解壳程序要在运行完毕后通过创建配置的Application对象,并通过反射修改回原Application。1

在attachBaseContext中,解壳apk

//这是context 赋值
protected void attachBaseContext(Context base){
super.attachBaseContext(base);
try {
//创建两个文件夹payload_odex,payload_lib 私有的,可写的文件目录
File odex = this.getDir("payload_odex", MODE_PRIVATE);
File libs = this.getDir("payload_lib", MODE_PRIVATE);
odexPath = odex.getAbsolutePath();
libPath = libs.getAbsolutePath();
apkFileName = odex.getAbsolutePath() + "/payload.apk";
File dexFile = new File(apkFileName);
Log.i("demo", "apk size:"+dexFile.length());
if (!dexFile.exists())
{
dexFile.createNewFile(); //在payload_odex文件夹内,创建payload.apk
// 读取程序classes.dex文件
byte[] dexdata = this.readDexFileFromApk();
// 分离出解壳后的apk文件已用于动态加载
this.splitPayLoadFromDex(dexdata);
}
// 配置动态加载环境
Object currentActivityThread = RefInvoke.invokeStaticMethod(
"android.app.ActivityThread", "currentActivityThread",
new Class[] {}, new Object[] {});//获取主线程对象 
String packageName = this.getPackageName();//当前apk的包名
//下面两句不是太理解
ArrayMap mPackages = (ArrayMap) RefInvoke.getFieldOjbect(
"android.app.ActivityThread", currentActivityThread,
"mPackages");
WeakReference wr = (WeakReference) mPackages.get(packageName);
//创建被加壳apk的DexClassLoader对象 加载apk内的类和本地代码(c/c++代码)
DexClassLoader dLoader = new DexClassLoader(apkFileName, odexPath,
libPath, (ClassLoader) RefInvoke.getFieldOjbect(
"android.app.LoadedApk", wr.get(), "mClassLoader"));
//base.getClassLoader(); 是不是就等同于 (ClassLoader) RefInvoke.getFieldOjbect()? 有空验证下//?
//把当前进程的DexClassLoader 设置成了被加壳apk的DexClassLoader ----有点c++中进程环境的意思~~
RefInvoke.setFieldOjbect("android.app.LoadedApk", "mClassLoader",
wr.get(), dLoader);
Log.i("demo","classloader:"+dLoader);
try{
Object actObj = dLoader.loadClass("com.example.forceapkobj.MainActivity");
Log.i("demo", "actObj:"+actObj);
}catch(Exception e){
Log.i("demo", "activity:"+Log.getStackTraceString(e));
}
} catch (Exception e) {
Log.i("demo", "error:"+Log.getStackTraceString(e));
e.printStackTrace();
}
}

通过反射置换android.app.ActivityThread 中的mClassLoader为加载解密出APK的DexClassLoader,该DexClassLoader一方面加载了源程序、另一方面以原mClassLoader为父节点,这就保证了即加载了源程序又没有放弃原先加载的资源与系统代码。

ZipInputStream 类

ZipInputStream是InputStream的子类,通过此类可以方便地读取ZIP格式的压缩文件,使用ZipInputStream可以像ZipFile一样取得ZIP压缩文件中的每一个ZipEntry。

package org.lxh.demo12.zipdemo;
import java.io.File;
import java.io.FileInputStream;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;
public class ZipInputStreamDemo01 {
public static void main(String[] args) throws Exception { //所有异常抛出
File zipFile = new File("d:" + File.separator + "mldn.zip");
ZipInputStream input = null; // 定义压缩输入流
input = new ZipInputStream(new FileInputStream(zipFile)); // 实例化压缩输入流
ZipEntry entry = input.getNextEntry(); // 得到一个压缩实体
System.out.println("压缩实体名称:" + entry.getName()) ;
// 输出实体名称
input.close();
// 关闭压缩输入流
}
}