前阵子刚从总公司撸了一台Google亲儿子Nexus 6,Root后并刷了Xposed,刚想爽一把,结果其中一个APP打开后就这幅德行了。
没办法,既然不让我爽,我自己亲自动手撸。分析APK
最基本的分析可以用Android Studio,我们将安装包拷贝到Android Studio打开的工程目录下,粗略查看下
dex文件中竟然没有com.huimai365
的包,而且lib下面的so文件竟然占比达到了50%多。心里顿时凉了半截,我擦,这是加固了呀! 不行,作为一线程序猿,怎么能轻言放弃!
脱壳
经过百般思考(google),我终于找到了一个傻瓜式脱壳工具dumpDex,没想到还挺好用的
我们“点击脱壳”后,在/data/data/com.huimai365/dump
文件夹下发现了很多dex文件,感觉离成功又进了一步! 接下来就是体力活了,我们把dex全部pull下来,逐个用dex2jar工具将dex转为jar。为此我写了一个批量操作的脚本,“xxx”目录存放的是所有的dex文件
dir=../xxx/
for file in $dir/*; do
./d2j-dex2jar.sh $file
done
复制代码
然后用JD-GUI寻找我们需要的包名com.huimai365
,幸运的是我终于终于找到了,不幸的是特么代码混淆。小伙子可以呀!
分析代码
我们先用脚指头分析下,这种检测Xposed基本在Application
的onCreate
方法中去执行,然后进行弹窗。好,那我们就寻找下他们自己实现的Application
类。
onCreate
中的方法 我发现有个
ar.a();
的方法,点进去一看,果然!(这里只是展示部分代码)这里不仅检测Xposed,竟然还想关闭Xposed,沃日!
public class ar
{
public static void a()
{
try
{
Field localField = ClassLoader.getSystemClassLoader().loadClass("de.robv.android.xposed.XposedBridge").getDeclaredField("disableHooks");
localField.setAccessible(true);
localField.set(null, Boolean.valueOf(true));
return;
}
catch (Throwable localThrowable) {}
}
public static boolean a(Context paramContext)
{
return (b(paramContext)) || (c(paramContext)) || (b()) || (c());
}
private static boolean b(Context paramContext)
{
paramContext = paramContext.getPackageManager().getInstalledApplications(128);
if (paramContext == null) {
return false;
}
paramContext = paramContext.iterator();
boolean bool = false;
if (paramContext.hasNext())
{
ApplicationInfo localApplicationInfo = (ApplicationInfo)paramContext.next();
if (localApplicationInfo.packageName.equals("de.robv.android.xposed.installer"))
{
ac.d("HookDetection", "Xposed found on the system.");
bool = true;
}
if (!localApplicationInfo.packageName.equals("com.saurik.substrate")) {
break label92;
}
ac.d("HookDetection", "Substrate found on the system.");
bool = true;
}
label92:
for (;;)
{
break;
return bool;
}
}
private static boolean c()
{
try
{
Object localObject = ClassLoader.getSystemClassLoader().loadClass("de.robv.android.xposed.XposedHelpers").newInstance();
if (localObject != null) {
if ((!a(localObject, "fieldCache")) && (!a(localObject, "methodCache")))
{
boolean bool = a(localObject, "constructorCache");
if (!bool) {}
}
else
{
return true;
}
}
}
catch (Throwable localThrowable) {}
return false;
}
}
复制代码
Hook方法
从上面的分析看,我们只要Hook ar
中的a()
和a(Context paramContext)
方法就行了。那么就动手吧!
public class XposedHookInit implements IXposedHookLoadPackage {
private static final String TAG = "XposedHookInit";
@Override
public void handleLoadPackage(XC_LoadPackage.LoadPackageParam lpparam) throws Throwable {
if ("com.huimai365".equals(lpparam.packageName)) {
Log.e(TAG, "Find 优品惠 " + lpparam.packageName);
hookCheckoutXposed(lpparam.classLoader);
}
}
private void hookCheckoutXposed(ClassLoader classLoader) {
XposedHelpers.findAndHookMethod("com.huimai365.util.ar", classLoader, "a", new XC_MethodReplacement() {
@Override
protected Object replaceHookedMethod(MethodHookParam param) throws Throwable {
Log.e(TAG, "Replace close xposed");
return null;
}
});
XposedHelpers.findAndHookMethod("com.huimai365.util.ar", classLoader, "a", Context.class, new XC_MethodReplacement() {
@Override
protected Object replaceHookedMethod(MethodHookParam param) throws Throwable {
Log.e(TAG, "Replace find xposed");
return false;
}
});
}
}
复制代码
可是事情并没有想象的那么简单,报错了
2019-01-17 12:46:17.038 5888-5888/? E/Xposed: de.robv.android.xposed.XposedHelpers$ClassNotFoundError: java.lang.ClassNotFoundException: com.huimai365.util.ar
at de.robv.android.xposed.XposedHelpers.findClass(XposedHelpers.java:71)
at de.robv.android.xposed.XposedHelpers.findAndHookMethod(XposedHelpers.java:260)
at com.example.xposeddemo.XposedHookInit.hookCheckoutXposed(XposedHookInit.java:35)
at com.example.xposeddemo.XposedHookInit.handleLoadPackage(XposedHookInit.java:20)
at de.robv.android.xposed.IXposedHookLoadPackage$Wrapper.handleLoadPackage(IXposedHookLoadPackage.java:34)
at de.robv.android.xposed.callbacks.XC_LoadPackage.call(XC_LoadPackage.java:61)
at de.robv.android.xposed.callbacks.XCallback.callAll(XCallback.java:106)
at de.robv.android.xposed.XposedInit$2.beforeHookedMethod(XposedInit.java:134)
at de.robv.android.xposed.XposedBridge.handleHookedMethod(XposedBridge.java:340)
at android.app.ActivityThread.handleBindApplication()
at android.app.ActivityThread.-wrap2(ActivityThread.java)
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1546)
at android.os.Handler.dispatchMessage(Handler.java:102)
at android.os.Looper.loop(Looper.java:154)
at android.app.ActivityThread.main(ActivityThread.java:6121)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:889)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:779)
at de.robv.android.xposed.XposedBridge.main(XposedBridge.java:107)
Caused by: java.lang.ClassNotFoundException: com.huimai365.util.ar
at java.lang.Class.classForName(Native Method)
at java.lang.Class.forName(Class.java:400)
at external.org.apache.commons.lang3.ClassUtils.getClass(ClassUtils.java:823)
复制代码
分析问题
为什么明明有这个类,却ClassNotFoundError呢?去了解下加固原理,才知道原来APP加壳之后,更改了classloader,因此我们用原本的classloader是会报类无法被找到的异常的。该APP使用的是腾讯的乐固,我们看到AndroidManifest.xml
里面的application已经被替换为com.tencent.StubShell.TxAppEntry
TxAppEntry
这个类,就在没有加壳的
classes.dex
文件中。
protected void attachBaseContext(Context paramContext)
{
super.attachBaseContext(paramContext);
SystemClassLoaderInjector.fixAndroid(paramContext, this);
if (!b(this)) {
return;
}
d(paramContext);
a(this);
}
复制代码
原来就在这里更改了classloader,那我们获取更改后classloader,然后再Hook不就行了?!OK,OK,OK,我们换个姿势再来一次
public class XposedHookInit implements IXposedHookLoadPackage {
private static final String TAG = "XposedHookInit";
@Override
public void handleLoadPackage(XC_LoadPackage.LoadPackageParam lpparam) throws Throwable {
if ("com.huimai365".equals(lpparam.packageName)) {
Log.e(TAG, "Find 优品惠 " + lpparam.packageName);
XposedHelpers.findAndHookMethod("com.tencent.StubShell.TxAppEntry", lpparam.classLoader,
"attachBaseContext", Context.class, new XC_MethodHook() {
@Override
protected void afterHookedMethod(MethodHookParam param) throws Throwable {
super.afterHookedMethod(param);
//获取到Context对象,通过这个对象来获取classloader
Context context = (Context) param.args[0];
//获取classloader,之后hook加固后的就使用这个classloader
ClassLoader realClassLoader = context.getClassLoader();
//下面就是将classloader修改成壳的classloader就可以成功的hook了
hookCheckoutXposed(realClassLoader);
}
});
}
}
private void hookCheckoutXposed(ClassLoader classLoader) {
XposedHelpers.findAndHookMethod("com.huimai365.util.ar", classLoader, "a", new XC_MethodReplacement() {
@Override
protected Object replaceHookedMethod(MethodHookParam param) throws Throwable {
Log.e(TAG, "Replace close xposed");
return null;
}
});
XposedHelpers.findAndHookMethod("com.huimai365.util.ar", classLoader, "a", Context.class, new XC_MethodReplacement() {
@Override
protected Object replaceHookedMethod(MethodHookParam param) throws Throwable {
Log.e(TAG, "Replace find xposed");
return false;
}
});
}
}
复制代码
结局
啊,整个世界清静了。随着身体的一阵哆嗦,这个APP变得索然无味……总结
其实本人在这里也只是将各种工具整合起来,大部分的知识都是从网上获取的。希望能有个抛砖引玉的作用,让各位读者能有一点收获。再者,真心膜拜这些造工具的大牛,看来我还有很长的路要走呀!
其实这也是我第一次写技术文章,我特意放在了掘金上,因为这是我最喜欢的一个国内平台(没有之一),希望掘金越来越好,勿忘初心!
参考资料
抱歉,Xposed真的可以为所欲为——5.我自己刷的Xposed凭什么不给我用
dumpDex-Android脱壳
Android逆向之路---脱壳360加固