加壳工具和说明:https://bbs.pediy.com/thread-270122.htm
更多惊喜:https://bbs.pediy.com/user-854079-1.htm
加壳后的APK帖子最下放(名子越长,强度越高)
加密原理如下:
1,首先了解apk的加载过程
第一步系统找到AndroidManifest.xml中的application项目的Class(在壳中需要替换此Class)
2,加壳不对dex加密等于裸奔,再说一下安卓加载dex的方法有多种方式:
BaseDexClassLoader;
PathClassLoader;
DexClassLoader;
DexFile
以上的都可以从dex中实例出class对象,DexFile是都会使用到的,其中PathClassLoader类加载器,在有的手机中会生产dex优化后的odex文件,会提高运行速度,(华为手机很多没有这个操作,之前有人问加壳后为啥华为的手机运行不了,因为生成odex后,原dex就给删除了)
前三个加载器大同小异
4,application 是系统第一加载的类,可以从attachBaseContext和onCreate两个方法中入手,替换默认加载流程
了解以上关系后,就可以动手了:
1,替换AndroidManifest.xml中的application为自己的Class
Class
以下代码可在C中实现
attachBaseContext()方法
OnCreate()方法
上以为最原始代码(各网站均有讲解),这不是关键(但很核心),要把这些放到C++中执行才安全,解密dex后,替换APK壳的dex过程,此可以通过C++调用java来实现如:env->FindClass("dalvik/system/PathClassLoader");
如果说用C++调用java实现比较难,那对于想要逆向的人来说会更难,
重点中的重点:
先了解逆向过程:
apk->dex->smali:可修改后打包,实现破解
加强1:
由于APK发布时打包有签名文件,加壳时可把sha1和sha256的值保存,在要释放解密后的dex前,先做签名验证,不通过不对dex进行解密
加强2:
APK本身可以认为是一个ZIP包,打包后可做伪加密(自行百度)
加强3:
在对dex解密前可做root权限判断,不符合不解密dex
加强4:
有的不破解apk,直接用抓包方式获取访问数据,很多app用的是明文,导致请求数据,和返回数据直接暴露,可在解密前做代理判断,不符合要求不解密dex,或直接关闭网络访问代理(方式百度)
加强5:
HOOK检查,同上,也可在解密前先检查是否有Xposed,不符合不解密
加强6:
so做为解密过程,也是可以被单独拿来调试的,可以在JNI_OnLoad加载时创建一个线程,用于以上几步的操作验证,以及反调试验证,用一个while(1)一直做检查(中间间隔几秒),不用担心耗资源,因很多app开发者使用大量无用代码相比要少的多,当程序被销毁时,循环也会退出
以上操作可实现:root手机不能运行,装有Xposed不能运行,有网络代理的不能运行,加壳后的APK被重新打包后也不能运行(签名发生变化)
在C++解密的过程里可放烟雾弹,方法名相似,代码相似,但没用到,可以让逆向者认为找到了突破口,(我用的是java调用C++,C++再调用java,java再调用C++)
DEX加载器不仅可以加载单独的dex文件,也可以加载jar,或ZIP文件,ZIP文件中可放置多个dex,大家可自由发挥,目前好像不能放一个文件目录,官方提示可指定为目录而非一个文件
再放经验:
安卓目录中如果一个文件或文件夹的名字前是以"."开头的,这个文件或目录是为隐藏文件,大家可自由发挥,不影响加载DEX文件。
APK伪加密,有些破解工具无法正常打开,
最后外加一个拼接521字节(自行百度)和资源混淆
以上所有操作流程可用写个加密工具进行测试
有什么问题和建议可发在留言区,以上操作均以在加壳工具中实现,可在开头找到下载地址
protected void attachBaseContext(Context base) {
super
.attachBaseContext(base);
try
{
/
/
创建两个文件夹payload_odex,payload_lib 私有的,可写的文件目录
String dexPath
=
Environment.getExternalStorageDirectory()
+
File
.separator
+
"classes.dex"
;
File
odex
=
this.getDir(
"payload_odex"
, MODE_PRIVATE);
File
libs
=
this.getDir(
"payload_lib"
, MODE_PRIVATE);
String odexPath
=
odex.getAbsolutePath();
libPath
=
libs.getAbsolutePath();
String apkFileName
=
odex.getAbsolutePath()
+
"/payload.apk"
;
apkFileName
=
dexPath;
File
dexFile
=
new
File
(apkFileName);
Log.i(
"demo"
,
"apk size:"
+
dexFile.length());
/
/
配置动态加载环境
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
+
+
代码)
PathClassLoader dLoader
=
new PathClassLoader(odexPath, getClassLoader());
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
{
ApplicationInfo ai
=
this.getPackageManager()
.getApplicationInfo(this.getPackageName(),
PackageManager.GET_META_DATA);
Bundle bundle
=
ai.metaData;
String frastAct
=
null;
if
(bundle !
=
null && bundle.containsKey(
"ACTIVITY_MAIN_CLASS_NAME"
)) {
frastAct
=
bundle.getString(
"ACTIVITY_MAIN_CLASS_NAME"
);
/
/
className 是配置在xml文件中的。
}
else
{
Log.e(
"demo"
,
"have no application class name"
);
}
Object
actObj
=
dLoader.loadClass(frastAct);
Log.e(
"demo"
, actObj.toString());
}catch(Exception e){
Log.e(
"demo"
,
"activity:"
+
Log.getStackTraceString(e));
}
} catch (Exception e) {
Log.i(
"demo"
,
"error:"
+
Log.getStackTraceString(e));
e.printStackTrace();
}
}
protected void attachBaseContext(Context base) {
super
.attachBaseContext(base);
try
{
/
/
创建两个文件夹payload_odex,payload_lib 私有的,可写的文件目录
String dexPath
=
Environment.getExternalStorageDirectory()
+
File
.separator
+
"classes.dex"
;
File
odex
=
this.getDir(
"payload_odex"
, MODE_PRIVATE);
File
libs
=
this.getDir(
"payload_lib"
, MODE_PRIVATE);
String odexPath
=
odex.getAbsolutePath();
libPath
=
libs.getAbsolutePath();
String apkFileName
=
odex.getAbsolutePath()
+
"/payload.apk"
;
apkFileName
=
dexPath;
File
dexFile
=
new
File
(apkFileName);
Log.i(
"demo"
,
"apk size:"
+
dexFile.length());
/
/
配置动态加载环境
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
+
+
代码)
PathClassLoader dLoader
=
new PathClassLoader(odexPath, getClassLoader());
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
{
ApplicationInfo ai
=
this.getPackageManager()
.getApplicationInfo(this.getPackageName(),
PackageManager.GET_META_DATA);
Bundle bundle
=
ai.metaData;
String frastAct
=
null;
if
(bundle !
=
null && bundle.containsKey(
"ACTIVITY_MAIN_CLASS_NAME"
)) {
frastAct
=
bundle.getString(
"ACTIVITY_MAIN_CLASS_NAME"
);
/
/
className 是配置在xml文件中的。
}
else
{
Log.e(
"demo"
,
"have no application class name"
);
}
Object
actObj
=
dLoader.loadClass(frastAct);
Log.e(
"demo"
, actObj.toString());
}catch(Exception e){
Log.e(
"demo"
,
"activity:"
+
Log.getStackTraceString(e));
}
} catch (Exception e) {
Log.i(
"demo"
,
"error:"
+
Log.getStackTraceString(e));
e.printStackTrace();
}
}
public void onCreate() {
Log.i(
"demo"
,
"onCreate"
);
/
/
如果源应用配置有Appliction对象,则替换为源应用Applicaiton,以便不影响源程序逻辑。
String appClassName
=
null;
try
{
ApplicationInfo ai
=
this.getPackageManager()
.getApplicationInfo(this.getPackageName(),
PackageManager.GET_META_DATA);
Bundle bundle
=
ai.metaData;
if
(bundle !
=
null && bundle.containsKey(
"APPLICATION_CLASS_NAME"
)) {
appClassName
=
bundle.getString(
"APPLICATION_CLASS_NAME"
);
/
/
className 是配置在xml文件中的。
}
else
{
Log.i(
"demo"
,
"have no application class name"
);
return
;
}
} catch (NameNotFoundException e) {
Log.i(
"demo"
,
"error:"
+
Log.getStackTraceString(e));
e.printStackTrace();
}
/
/
有值的话调用该Applicaiton
Object
currentActivityThread
=
RefInvoke.invokeStaticMethod(
"android.app.ActivityThread"
,
"currentActivityThread"
,
new Class[] {}, new
Object
[] {});
Object
mBoundApplication
=
RefInvoke.getFieldOjbect(
"android.app.ActivityThread"
, currentActivityThread,
"mBoundApplication"
);
Object
loadedApkInfo
=
RefInvoke.getFieldOjbect(
"android.app.ActivityThread$AppBindData"
,
mBoundApplication,
"info"
);
/
/
把当前进程的mApplication 设置成了null
RefInvoke.setFieldOjbect(
"android.app.LoadedApk"
,
"mApplication"
,
loadedApkInfo, null);
Object
oldApplication
=
RefInvoke.getFieldOjbect(
"android.app.ActivityThread"
, currentActivityThread,
"mInitialApplication"
);
ArrayList<Application> mAllApplications
=
(ArrayList<Application>) RefInvoke
.getFieldOjbect(
"android.app.ActivityThread"
,
currentActivityThread,
"mAllApplications"
);
mAllApplications.remove(oldApplication);
/
/
删除oldApplication
ApplicationInfo appinfo_In_LoadedApk
=
(ApplicationInfo) RefInvoke
.getFieldOjbect(
"android.app.LoadedApk"
, loadedApkInfo,
"mApplicationInfo"
);
ApplicationInfo appinfo_In_AppBindData
=
(ApplicationInfo) RefInvoke
.getFieldOjbect(
"android.app.ActivityThread$AppBindData"
,
mBoundApplication,
"appInfo"
);
appinfo_In_LoadedApk.className
=
appClassName;
appinfo_In_AppBindData.className
=
appClassName;
Application app
=
(Application) RefInvoke.invokeMethod(
"android.app.LoadedApk"
,
"makeApplication"
, loadedApkInfo,
new Class[] { boolean.
class
, Instrumentation.
class
},
new
Object
[] { false, null });
/
/
执行 makeApplication(false,null)
RefInvoke.setFieldOjbect(
"android.app.ActivityThread"
,
"mInitialApplication"
, currentActivityThread, app);
ArrayMap mProviderMap
=
(ArrayMap) RefInvoke.getFieldOjbect(
"android.app.ActivityThread"
, currentActivityThread,
"mProviderMap"
);
Iterator it
=
mProviderMap.values().iterator();
while
(it.hasNext()) {
Object
providerClientRecord
=
it.
next
();
Object
localProvider
=
RefInvoke.getFieldOjbect(
"android.app.ActivityThread$ProviderClientRecord"
,
providerClientRecord,
"mLocalProvider"
);
RefInvoke.setFieldOjbect(
"android.content.ContentProvider"
,
"mContext"
, localProvider, app);
}
Log.e(
"demo"
,
"app:"
+
app);
app.onCreate();
super
.onCreate();
}
public void onCreate() {
Log.i(
"demo"
,
"onCreate"
);
/
/
如果源应用配置有Appliction对象,则替换为源应用Applicaiton,以便不影响源程序逻辑。
String appClassName
=
null;
[招生]科锐逆向工程师培训(2024年11月15日实地,远程教学同时开班, 第51期)
最后于 2022-9-30 13:39
被富到流油^-^编辑
,原因: 补充