记一次带壳子的hook体验
前言
学完正己老师的安卓逆向(七)后,我们知道xposed的hook可以对加壳应用生效。正好NeatReader的安卓端加了360壳,我们就以它为例应用一下对加壳应用的hook。
NeatReader在安卓端是免费软件,除云存储外没有使用限制。因此本次hook仅实现开启本地会员,使底部的“会员”一栏消失。
准备工作
首先下载32位的NeatReader,将它安装到刷入了fart的手机上脱壳,将所得的dex删掉类重复的(一般也就是大小相同的),并用mt/np做dex修复,然后全部打包到一个zip中,这个zip文件可以直接拖入jadx分析,如下图所示:
现在将app安装到pixel2xl上, 这个手机已经root并解锁bl,刷入了Magisk和 lsposed:
安装好原版app后,可以正常启动,接下来就可以着手分析代码开启本地会员了:
分析
分析流程与破解无加固的app大同小异,首先搜索会员,看到明显的提示:
这一行所在的方法如下:
private void v3(boolean z7) {
com.gzhi.neatreader.r2.utils.l lVar = com.gzhi.neatreader.r2.utils.l.f9986a;
lVar.e("账号切换", "updateBottomNavigationTab");
if (p2()) {
lVar.e("账号切换", "已登录");
if (this.H.i() == 1) {
lVar.e("账号切换", "VIP_USER 隐藏促销按钮");
k3();
return;
} else if (q2()) {
lVar.e("账号切换", "FREE_USER 显示促销按钮");
if (z7) {
return;
}
if (this.f9896x.getMenu().size() == 5) {
if (r2()) {
return;
}
p3(com.gzhi.neatreader.r2.utils.g.f9982a.d(this.H.s().b()));
return;
} else if (this.f9896x.getMenu().size() == 4) {
if (this.H.s() == null) {
this.f9896x.getMenu().add(0, R.id.im_premium, 4, getString(2131755099));
} else {
this.f9896x.getMenu().add(0, R.id.im_premium, 4, getString(2131755100));
p3(true);
}
e(4);
return;
} else {
return;
}
} else {
return;
}
}
lVar.e("账号切换", "未登录, 显示会员按钮");
if (z7) {
return;
}
k3();
this.f9896x.getMenu().add(0, R.id.im_premium, 4, getString(2131755099)).setIcon(R.drawable.ic_main_premium);
e(4);
}
分析可以得知p2()
用于判断账号是否登录;this.H.i()
表示当前账号身份,会员为1。
因此我们需要令 p2() 返回true,令 H.i() 返回1。
hook的代码实现
我们先创建一个NoActivity的项目:
在项目的gradle脚本中增加一行,使后续所有放入 libs的名字带 -api的jar包都能参与编译,且不会被打包:
compileOnly fileTree(include: '*-api.jar', dir: 'libs')
将教程中的 XposedBridge-89.jar 改名为 XposedBridge-89-api.jar 放入 app\libs
目录下,重新用gradle sync同步项目即可完成导包。
然后我们在AndroidManifest中添加元数据:
<!-- 是否是xposed模块,xposed根据这个来判断是否是模块 -->
<meta-data
android:name="xposedmodule"
android:value="true" />
<!-- 模块描述,显示在xposed模块列表那里第二行 -->
<meta-data
android:name="xposeddescription"
android:value="给NeatReader开个本地会员" />
<!-- 最低xposed版本号(lib文件名可知) -->
<meta-data
android:name="xposedminversion"
android:value="89" />
接下来我们新建一个Hook类MyHook,这里相比教程略微做一点变化,我们改为通过反射调用方法
来注入hook,因为把所有的hook方法都挂在回调函数里不利于阅读。
public class MyHook implements IXposedHookLoadPackage {
@Override
public void handleLoadPackage(XC_LoadPackage.LoadPackageParam loadPackageParam) throws Throwable {
if ("com.gzhi.neatreader.r2.main".equals(loadPackageParam.packageName)) {
// 在attach方法中,可以取得真正的类加载器,而attach本身的加载器是壳代{过}{滤}理的加载器
XposedHelpers.findAndHookMethod(Application.class, "attach", Context.class, new XC_MethodHook() {
protected void afterHookedMethod(MethodHookParam param) throws Throwable {
Context context = (Context) param.args[0];
// 这是真正的类加载器
ClassLoader classLoader = context.getClassLoader();
// 取得当前类下的所有方法
Method[] methods = MyHook.class.getDeclaredMethods();
// 用反射调用所有以 mod_ 开头, 且只有一个 ClassLoader 作为参数的方法
for (Method method : methods) {
if (method.getName().startsWith("mod_") && method.getParameterTypes().length == 1 && method.getParameterTypes()[0] == ClassLoader.class) {
method.setAccessible(true);
method.invoke(MyHook.this, classLoader);
}
}
}
});
}
}
}
然后在asset目录下新建xposed_init文件,将类的全限定名粘贴进去:
接下来只要在MyHook类中新增符合要求的方法,并将jadx复制的xposed片段粘贴进去就行了:
如图,这个方法将自己设为已登录状态:
private void mod_ILoggedIn(ClassLoader classLoader){
XposedHelpers.findAndHookMethod("com.gzhi.neatreader.r2.ui.MainActivity", classLoader, "p2", new XC_MethodHook() {
@Override
protected void afterHookedMethod(MethodHookParam param) throws Throwable {
super.afterHookedMethod(param);
param.setResult(true);
}
});
}
同理,这个方法将自己设为会员状态:
private void mod_IamVIP(ClassLoader classLoader){
XposedHelpers.findAndHookMethod("com.gzhi.neatreader.r2.utils.SharedPreferenceHelper", classLoader, "i", new XC_MethodHook() {
@Override
protected void afterHookedMethod(MethodHookParam param) throws Throwable {
super.afterHookedMethod(param);
param.setResult(1);
}
});
}
编译安装,然后到管理界面激活模块:
激活后再强行停止并重启NeatReader,发现会员一栏已经消失了: