12

[原创]某app广告太多,看不下去了,逆一波

 2 years ago
source link: https://bbs.pediy.com/thread-268116.htm
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

​ 本篇只供学习,本人未用作商业用途。他人若未经允许,也不得使用以及参考本篇中提到的源码以及附件进行谋利,违者必究

本篇目的:

​ 由于工作原因,很长时间没搞破解了。几个月前只是简单对某镖游戏dump了il2cpp与meta-data,昨天试玩了一下,竟然要更新,fuck! fuck! fuck!

​ 然后随便在小米应用商店下载了个小游戏,广告太多,就以去广告为目标 练一下手吧。

游戏主页的最上方有一个广告栏:

点击礼物盒后启动了一个新的页面:

目标一:去除红色箭头所示的广告栏

目标二:礼物盒点击后去除广告

本篇用到的逆向工具

dex文件查看工具

Jeb(windows)

mac可参考这里

apktool2.5

java -jar ~/env/apktool_2.5.0.jar d app-debug.apk
java -jar ~/env/apktool_2.5.0.jar d -r app-debug.apk //Do not decode resources. 不反编译资源文件
java -jar ~/env/apktool_2.5.0.jar d -s app-debug.apk //Do not decode sources. 不反编译源码文件

Baksmali-2.5.2/smali-2.5.2

java -jar ~/env/baksmali-2.5.2.jar d classes4.dex //反编译dex文件,输出目录默认位out
java -jar ~/env/smali-2.5.2.jar a out //将out目录下面的smali源文件编译为dex文件,输出默认为out.dex

keytool

该cmd是为了生成keystore文件,重签名apk时需要。keytool命令在java安装目录/bin下面

keytool -genkey -alias mine.keystore -keyalg RSA -validity 30000 -keystore mine.keystore

jarsigner

该cmd是为了将apk进行重签名,重签名需要使用keystore文件。jarsigner命令也在java安装目录/bin下面

jarsigner -verbose -keystore mine.keystore -signedjar new.apk old.apk mine.keystore
-keystore:keystore 的名称
new.apk:签名后的apk
old.apk:签名前的apk

adb shell dumpsys activity activities top

打印手机上当前的未销毁的activity列表

adb shell dumpsys window windows

打印手机上当前的window,默认Z-order

游戏主页的广告栏分析与去除

将app启动,显示游戏主页,查看activity

​ dumpsys看看当前的广告栏属于哪个activity

activity为com.outfit7.mytalkingtomfree/com.jinke.Main

用jeb打开,Main--activity中看看没有相关的contentView之类的。

分析后,发现Main的父类中含有可疑的布局代码

上图中onCreate中调用了initContentLayout函数

private void initContentLayout() {
WeakReference v0 = this.banneContainerReference;
if(v0 == null || v0.get() == null) {
this.banneContainerReference = new WeakReference(LayoutInflater.from(this.activityWeakReference.get()).inflate(layout.adjustable_banner_layout, this.findViewById(0x1020002), true));
Log.d(MaoActivity.TAG, "setAdjustableBannerXY create view");
}
}

这个函数中有一个Log.d,我们可通过查看程序运行日志确认该函数中的if语句是否执行了。

我这边在app冷启动时开始抓log,确实有这个日志

06-16 17:44:45.360 27784 27784 D com.outfit7.jinke.MaoActivity: setAdjustableBannerXY create view

或者大家也可以通过打断点调试,确认这里是否执行了。

代码拆分说明:

LayoutInflater.from(this.activityWeakReference.get()) 获取当前显示的activity,然后基于该activity对应的上下文创建一个LayoutInflater
inflate(layout.adjustable_banner_layout, this.findViewById(0x1020002), true));

LayoutInflater.inflate函数,代码加载布局,其函数声明为:

public View inflate(@LayoutRes int resource, @Nullable ViewGroup root, boolean attachToRoot)

所以布局文件为layout.adjustable_banner_layout,这个布局的名字看起来就像是广告位。

看看广告位布局文件layout.adjustable_banner_layout

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout android:orientation="vertical" android:id="@id/mao_adjustable_container_root" android:layout_width="fill_parent" android:layout_height="fill_parent"
xmlns:android="http://schemas.android.com/apk/res/android">
<FrameLayout android:id="@id/mao_adjustable_ad" android:layout_width="wrap_content" android:layout_height="wrap_content" />
</RelativeLayout>

xml中整个view的layout_height为fill_parent,这个高度值可能太大了,看起来应该不是主页上方广告的布局。

通过调试查找可疑layout, 在MaoActivity的onResume处打个端点

直接查看当前Activity的DecorView,查看其children,发现0,0 1080,206这个boundary就是广告显示的位置

在jeb中看看com.miui.zeus.mimo.sdk.ad.banner.b{75aee83 VFE...C.. ......ID 0,0-1080,206}这玩意

b继承了FrameLayout

public class b extends FrameLayout {
public class com.miui.zeus.mimo.sdk.ad.banner.b$1 implements View$OnClickListener {
public com.miui.zeus.mimo.sdk.ad.banner.b$1(b arg1) {
this.a = arg1;
super();
}
public void onClick(View arg2) {
b v2 = this.a;
a v0 = v2.e;
if(v0 != null) {
v0.a(b.a(v2));
}
}
}
...
...
...
}

继续查看这个类中有关初始化的函数,发现有一个函数调用了setOnClickListener。

//com.miui.zeus.mimo.sdk.ad.banner.b
private void a(View arg3) {
if(this.d()) {
this.b = arg3.findViewById(l.c("mimo_banner_view_summary"));
this.f = arg3.findViewById(l.c("mimo_banner_border"));
Glide.with(this.i).load(Integer.valueOf(l.b("mimo_banner_border"))).into(this.f);
this.k = arg3.findViewById(l.c("mimo_banner_view_flipper"));
}
else {
this.a = arg3.findViewById(l.c("mimo_banner_view_image"));
}
this.c = arg3.findViewById(l.c("mimo_banner_view_ad_mark"));
this.d = arg3.findViewById(l.c("mimo_banner_view_close"));
this.h = new d();
this.d.setOnClickListener(new View$OnClickListener() {
public void onClick(View arg1) {
a v1 = this.a.e;
if(v1 != null) {
v1.b();
}
}
});
((FrameLayout)this).setOnClickListener(this.g);
}

看看void a(View arg3)这个函数有哪些地方调用了

jeb中查看交叉引用非常方便,选中函数a,然后按下快捷键x,发现只有一个地方调用了a

//com.miui.zeus.mimo.sdk.ad.banner.b
public void a(c arg3) {
this.m = arg3;
int v0 = com.miui.zeus.mimo.sdk.utils.a.a(arg3.Y());
this.j = v0;
if(v0 == 0) {
this.j = l.a("mimo_banner_view_layout");
}
this.a(LayoutInflater.from(this.i).inflate(this.j, ((ViewGroup)this)));
String v3 = arg3.M();
if(TextUtils.isEmpty(((CharSequence)v3))) {
this.b();
return;
}
this.a(v3);
}

这个函数中使用了LayoutInflator。 YES!!!!!无疑了,这个调用链肯定初始化了主页上方的广告位。

继续查找a(c arg3)的调用者

//com.miui.zeus.mimo.sdk.ad.banner
public void a(com.miui.zeus.mimo.sdk.server.api.c arg3, ViewGroup arg4, BannerInteractionListener arg5) {
this.l = System.currentTimeMillis();
String v0 = "BannerUIController";
j.a(v0, "showBanner");
this.e = arg3;
this.h = arg5;
if(arg3 == null) {
this.a(com.miui.zeus.mimo.sdk.utils.error.a.e.au, com.miui.zeus.mimo.sdk.utils.error.a.e.av);
j.b(v0, "Empty splash ad info view arguments");
return;
}
this.f = arg4;
this.c.post(new Runnable(arg3) {
public void run() {
try {
c.a(this.b, new b(c.a(this.b)));
c.b(this.b).a(this.b);
c.b(this.b).a(this.a);
}
catch(Exception v0) {
j.b("BannerUIController", "Failed to create view", ((Throwable)v0));
this.b.a();
}
}
});
}

这个函数中的字符很给力,showBanner, Failed to create view等

a(com.miui.zeus.mimo.sdk.server.api.c arg3, ViewGroup arg4, BannerInteractionListener arg5)的调用者

//com.miui.zeus.mimo.sdk.ad.banner
public void a(ViewGroup arg3, BannerInteractionListener arg4) {
this.f = arg3;
b v3 = new b(this, arg4);
this.q = v3;
this.g.a(this.d, this.f, ((BannerInteractionListener)v3));
}

a(ViewGroup arg3, BannerInteractionListener arg4)的调用者

//com.miui.zeus.mimo.sdk.BannerAd
public void showAd(ViewGroup arg3, BannerInteractionListener arg4) {
if(arg3 == null) {
j.b("BannerAd", "showAd failed, container can not be null");
}
this.mAdImpl.a(arg3, arg4);
}

showAd(ViewGroup arg3, BannerInteractionListener arg4)的调用者

public void onBannerAdLoadSuccess() {
String v0 = "MiMoAdBannerAdapter";
MLog.w(v0, "load banner ad success ,start render");
if(MiMoAdBannerAdapter.access$100(this.a) != null) {
MLog.w(v0, "start show banner ad");
MiMoAdBannerAdapter.access$100(this.a).showAd(MiMoAdBannerAdapter.access$200(this.a), MiMoAdBannerAdapter.access$300(this.a));
}
else {
MLog.w(v0, "load banner ad success,but banner ad is null");
this.a.notifyLoadError(new MMAdError(-2000));
this.a.trackDspLoad(String.valueOf(-2000), null);
}
}

​ 上述调用栈关系也可以使用动态调试的方式,一步到位。我没有使用调试的方法,因为这个广告在我的手机上出现的概率太低,想调试一次太难了,大家理解一下哈。

基于上述分析,去掉主页上方广告位的思路:

源头去除: onBannerAdLoadSuccess函数中,showAd调用移除。缺点:可能影响了app原本逻辑

尾部去除:设置view的visibility。优点:基本不影响app原本逻辑

昨天调试的时候保存了广告栏显示的代码源头。大家可以自行分析一下,看是否跟我找到的一样。

//com.xiaomi.ad.mediation.mimo.MiMoAdBannerAdapter
public void loadAndShow(AdInternalConfig arg1, AdLoadAndShowListener arg2, AdLoadAndShowInteractionListener arg3) {
super.loadAndShow(arg1, arg2, arg3);
AndroidUtils.runOnMainThread(this.mMainHandler, new MiMoAdBannerAdapter$a(this, arg1));
}
public class MiMoAdBannerAdapter$a implements Runnable {
public MiMoAdBannerAdapter$a(MiMoAdBannerAdapter arg1, AdInternalConfig arg2) {
this.b = arg1;
this.a = arg2;
super();
}
public void run() {
this.b.loadBannerAd(this.a);
}
}

直接将run函数return,实测有效

java -jar ~/env/baksmali-2.5.2.jar d classes2.dex
cd out/com/miui/zeus/mimo/sdk/ad/banner
vim a\$3.smali
//去掉invoke-virtual。相当于run()函数什么都没有做

还有一种方案:模拟广告位close的方法,对app逻辑无任何影响,大家自行尝试

点击礼物盒跳转至广告activity,如何disable该功能

老办法,先用dumpsys看看activity是哪个

admin@C02D7132MD6R bin % adb shell dumpsys activity activities
ACTIVITY MANAGER ACTIVITIES (dumpsys activity activities)
Display #0 (activities from top to bottom):
Stack #1:
...
...
* Hist #1: ActivityRecord{1f34e49 u0 com.outfit7.mytalkingtomfree/com.miui.zeus.mimo.sdk.ad.reward.RewardVideoAdActivity t3}
packageName=com.outfit7.mytalkingtomfree processName=com.outfit7.mytalkingtomfree
launchedFromUid=10139 launchedFromPackage=com.outfit7.mytalkingtomfree userId=0
app=ProcessRecord{382ce36 4930:com.outfit7.mytalkingtomfree/u0a139}
Intent { cmp=com.outfit7.mytalkingtomfree/com.miui.zeus.mimo.sdk.ad.reward.RewardVideoAdActivity (has extras) }
...
...
resizeMode=RESIZE_MODE_RESIZEABLE
* Hist #0: ActivityRecord{64ced92 u0 com.outfit7.mytalkingtomfree/com.jinke.Main t3}
packageName=com.outfit7.mytalkingtomfree processName=com.outfit7.mytalkingtomfree
launchedFromUid=10139 launchedFromPackage=com.outfit7.mytalkingtomfree userId=0
app=ProcessRecord{382ce36 4930:com.outfit7.mytalkingtomfree/u0a139}
Intent { flg=0x10000000 cmp=com.outfit7.mytalkingtomfree/com.jinke.Main (has extras) }

RewardVideoAdActivity与主页(Main)不一个activity,暴力拦截startActivity可行?

先挂断点查看startActivity调用栈, 我断在了instrumentation->execSartActivity方法

很可惜,尝试了头部去除以及尾部去除,重打包运行会有两个问题:

1.activity确实被拦截掉了,但是页面会卡在一个view中,显示加载中...

2.按返回键尝试将卡view的界面关掉,礼物盒没有正常开启奖励,肯定是影响了游戏逻辑。

奖励没拿到。。。。毛用都没有

继续硬头皮找关键点吧

广告activity显示有一个特点,close按钮隐藏后显示

广告加载完之后,右上角有一个X按钮,会显示出来。未加载完的时候隐藏。所以就在Activity中找找有没有相关的close或者onClick关键字

jeb中直接找到RewardVideoAdActivity, 确实搜索到了close与onclick关键字

//com.miui.zeus.mimo.sdk.ad.reward.RewardVideoAdActivity
private void k() {
int v0 = com.miui.zeus.mimo.sdk.utils.a.c(this.i.Y());
if(v0 == 0) {
v0 = l.a("mimo_reward_view_end_page_landscape");
}
this.h = LayoutInflater.from(((Context)this)).inflate(v0, this.g, true);
String v0_1 = this.i.M();
String v1 = this.i.L();
Bitmap v0_2 = BitmapFactory.decodeFile(v0_1, this.o);
Bitmap v1_1 = BitmapFactory.decodeFile(v1, this.o);
this.h.findViewById(l.c("mimo_reward_flv_video")).setImageBitmap(v0_2);
this.h.findViewById(l.c("mimo_reward_icon")).setImageBitmap(v1_1);
this.h.findViewById(l.c("mimo_reward_title")).setText(this.i.h());
this.h.findViewById(l.c("mimo_reward_summary")).setText(this.i.g());
this.h.findViewById(l.c("mimo_reward_dsp")).setText(this.i.i());
View v0_3 = this.h.findViewById(l.c("mimo_reward_jump_btn"));
((TextView)v0_3).setText(this.i.O());
b v1_2 = new b();
this.y = v1_2;
((com.miui.zeus.mimo.sdk.anim.a)v1_2).b(v0_3).a(1200).a(-1).b(1).a(new AccelerateDecelerateInterpolator()).c();
this.h.findViewById(l.c("mimo_reward_close_img")).setOnClickListener(((View$OnClickListener)this));
this.g.setOnClickListener(((View$OnClickListener)this));
}

void k()这个函数中使用了LayoutInflater,“mimo_reward_close_img”, setOnClickListener(this)。太明显了,毫无压力。

再考虑一个问题,何时广告播放完显示X按钮呢

直接搜关键字setVisibility,该函数会将一个view显示出来

//com.miui.zeus.mimo.sdk.ad.reward.RewardVideoAdActivity
private void p() {
int v0;
if(this.i.c()) {
this.n();
if(!TextUtils.isEmpty(this.i.u())) {
v0 = 1;
}
else {
goto label_11;
}
}
else {
label_11:
v0 = 0;
}
if(v0 != 0) {
this.m();
}
else {
this.f.setVisibility(8);
this.k.setVisibility(8);
this.g.setVisibility(0);
b v0_1 = this.y;
if(v0_1 != null) {
((com.miui.zeus.mimo.sdk.anim.a)v0_1).c();
}
ViewFlipper v0_2 = this.u;
if(v0_2 != null) {
v0_2.stopFlipping();
}
com.miui.zeus.mimo.sdk.utils.analytics.b.a(this.i.m(), this.i, "END_PAGE_VIEW", "load_success", 0, "");
}
}

搜到了,就在RewardVideoAdActivity代码中,代码还是那么的明显,毫无压力。

this.g.setVisibility(0),这里的0是一个flag, 就是将view显示出来。

在RewardVideoAdActivity.p()函数打断点,调试一把,看看调用栈

源头是通过onCompletion调过来的, com.miui.zeus.mimo.sdk.video.TextureVideoView$3实现了MediaPlayer.OnCompletionListener接口

public class com.miui.zeus.mimo.sdk.video.TextureVideoView$3 implements MediaPlayer$OnCompletionListener {
public com.miui.zeus.mimo.sdk.video.TextureVideoView$3(TextureVideoView arg1) {
this.a = arg1;
super();
}
public void onCompletion(MediaPlayer arg2) {
TextureVideoView.c(this.a, 5);
TextureVideoView.d(this.a, 5);
if(TextureVideoView.e(this.a) != null) {
TextureVideoView.e(this.a).hide();
}
if(TextureVideoView.h(this.a) != null) {
TextureVideoView.h(this.a).onCompletion(TextureVideoView.d(this.a));
}
}
}

原来广告是通过MediaPlayer播放的,然后播放是否完成 是 通过MediaPlayer的回掉接口掉过来的。

模拟MediaPlayer播放完成,模拟点击close按钮,实现插件

首先要拿到MediaPlayer实例

手有点疼了。。。。

主要是通过jeb,查看交叉引用关系

MediaPlayer是TextureVideoView的成员

//com/miui/zeus/mimo/sdk/video/TextureVideoView
public Map m;
public int n;
public int o;
public Surface p;
public MediaPlayer q;

TextureVideoView是VideoAdView的成员

//com.miui.zeus.mimo.sdk.video.VideoAdView
public TextureVideoView a;
public ImageView b;
public FrameLayout c;
public boolean d;
public static final String e = "VideoAdView";

VideoAdView是RewardVideoActivity的成员

public class RewardVideoAdActivity extends Activity implements View$OnClickListener, a {
public static final String a = "key_baseadinfo";
public static final String b = "RewardVideoAdActivity";
public static final String c = "key_exposure";
public static final long d = 60000;
public EventRecordFrameLayout e;
public VideoAdView f;

结论: 可以通过拿到RewardVideoActivity实例,然后反射一步步拿到MediaPlayer实例

如何拿到RewardVideoActivity实例

这可能是个老生常谈的问题。方式可能有好几种吧, 下面是我能想到的

1: ActivityThread中保存了当前进程的activity列表

final ArrayMap<IBinder, ActivityClientRecord> mActivities = new ArrayMap<>();

2.拦截ActivityThread.H,这玩意是一个Handler,第一时间通过system_server将activity的生命周期,回掉过来。

3.通过frida或者其他hook工具,在activity->onCreate或者onResume这里下手

4.通过注入Instrumentation的方式,管控activity生命周期,可以拦截到onCreate以及onResume等

我使用了最后一种方案,用java写了点代码,最后编译为smali文件后,通过静态代码注入重打包了apk。

instrumentation注入:

public static void initInstrumentation() {
final String CLASS_ACTIVITYTHREAD = "android.app.ActivityThread";
RefUtils.MethodRef<Object> currentActivityThreadRef =
new RefUtils.MethodRef<Object>(CLASS_ACTIVITYTHREAD, true,
"currentActivityThread", new Class[0]);
Object activityThread = currentActivityThreadRef.invoke(null, new Object[0]);
RefUtils.FieldRef<Instrumentation> instrumentationFieldRef =
new RefUtils.FieldRef<Instrumentation>(CLASS_ACTIVITYTHREAD, false, "mInstrumentation");
Instrumentation appInstru = instrumentationFieldRef.get(activityThread);
HackerInstrumentation.INSTANCE().setOriginInstru(appInstru);
instrumentationFieldRef.set(activityThread, HackerInstrumentation.INSTANCE());
}

Instrumentation实体类

public class HackerInstrumentation extends Instrumentation {
private Instrumentation mOrigin = null;
public static HackerInstrumentation INSTANCE() {
return DefaultHackerInstru.get();
}
private HackerInstrumentation(){}
private static class DefaultHackerInstru {
private static HackerInstrumentation sInstancce = new HackerInstrumentation();
static HackerInstrumentation get() {
return sInstancce;
}
}
public void setOriginInstru(Instrumentation instru) {
mOrigin = instru;
}
@Override
public void callActivityOnCreate(Activity activity, Bundle icicle) {
mOrigin.callActivityOnCreate(activity, icicle);
TalkingTomHacker.afterCreateActivity(activity);
}
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
@Override
public void callActivityOnCreate(Activity activity, Bundle icicle, PersistableBundle persistentState) {
mOrigin.callActivityOnCreate(activity, icicle, persistentState);
TalkingTomHacker.afterCreateActivity(activity);
}
@Override
public void callActivityOnDestroy(Activity activity) {
mOrigin.callActivityOnDestroy(activity);
TalkingTomHacker.afterCreateActivity(activity);
}
@Override
public void callActivityOnResume(Activity activity) {
mOrigin.callActivityOnResume(activity);
TalkingTomHacker.afterResumeActivity(activity);
}
}

在callActivityOnCreate回掉中拿到activity实例,然后通过反射拿到MediaPlayer

public static void afterCreateActivity(Activity activity) {
Log.d(TAG, "afterCreateActivity " + activity);
try {
disableRewardActivityMediaPlayer(activity);
} catch (Exception e) {
e.printStackTrace();
}
}
private static void disableRewardActivityMediaPlayer(Activity activity) throws Exception {
final String REWARD_ACTIVITY_NAME = "com.miui.zeus.mimo.sdk.ad.reward.RewardVideoAdActivity";
if (!TextUtils.equals(activity.getLocalClassName(), REWARD_ACTIVITY_NAME)) {
return;
}
final String VIDEO_AD_VIEW_FIELD_TYPE = "com.miui.zeus.mimo.sdk.video.VideoAdView";
Field tmpRef = null;
Field[] fields = activity.getClass().getDeclaredFields();
for (Field field : fields) {
if (TextUtils.equals(field.getType().getName(), VIDEO_AD_VIEW_FIELD_TYPE)) {
tmpRef = field;
break;
}
}
if (tmpRef == null) {
return;
}
tmpRef.setAccessible(true);
Object videoAdView = tmpRef.get(activity);
if (videoAdView == null) {
Log.e(TAG, "videoAdView is null");
return;
}
final String TEXTURE_VIDEO_VIEW_TYPE = "com.miui.zeus.mimo.sdk.video.TextureVideoView";
tmpRef = null;
fields = videoAdView.getClass().getDeclaredFields();
for (Field field : fields) {
if (TextUtils.equals(field.getType().getName(), TEXTURE_VIDEO_VIEW_TYPE)) {
tmpRef = field;
break;
}
}
if (tmpRef == null) {
return;
}
tmpRef.setAccessible(true);
TextureView textureVideoView = (TextureView) tmpRef.get(videoAdView);
if (textureVideoView == null) {
Log.e(TAG, "textureVideoView null");
return;
}
final String MEDIA_PLAYER_TYPE = "android.media.MediaPlayer";
tmpRef = null;
fields = textureVideoView.getClass().getDeclaredFields();
for (Field field : fields) {
if (TextUtils.equals(field.getType().getName(), MEDIA_PLAYER_TYPE)) {
tmpRef = field;
break;
}
}
if (tmpRef == null) {
return;
}
TextureView.SurfaceTextureListener originListener = textureVideoView.getSurfaceTextureListener();
if (originListener == null) {
Log.e(TAG, "origin surface texure Listener null");
return;
}
setOldTextureListener(originListener);
textureVideoView.setSurfaceTextureListener(sSurfaceTextureListener);
final Field mediaPlayerRef = tmpRef;
...
...
}

模拟MediaPlayer播放完成

HandlerUtils.post(new Runnable() {
@Override
public void run() {
sModifyMediaPlayerCondition.block();
sModifyMediaPlayerCondition.close();
mediaPlayerRef.setAccessible(true);
MediaPlayer player = null;
try {
player = (MediaPlayer) mediaPlayerRef.get(textureVideoView);
} catch (IllegalAccessException e) {
e.printStackTrace();
}
if (player == null) {
Log.e(TAG, "MediaPlayer is null");
return;
}
player.pause();
try {
//模拟MediaPlayer播放完成
pretendCompleteMediaPlayer(player);
} catch (Exception e) {
e.printStackTrace();
}
player.release();
}
});

模拟点击close按钮,关闭广告activity

private static void disableRewardActivityResume(Activity activity) {
final String REWARD_ACTIVITY_NAME = "com.miui.zeus.mimo.sdk.ad.reward.RewardVideoAdActivity";
if (!TextUtils.equals(activity.getLocalClassName(), REWARD_ACTIVITY_NAME)) {
return;
}
synchronized (TalkingTomHacker.class) {
if (activity.isDestroyed() || activity.isFinishing()) {
return;
}
pretendClickCloseActivity(activity, "mimo_reward_close_img");
}
}
private static void pretendClickCloseActivity(Activity activity, String resStr) {
if (activity instanceof View.OnClickListener) {
int resId = activity.getApplicationContext().getResources().getIdentifier(resStr, "id", activity.getPackageName());
if (resId <= 0) {
Log.e(TAG, "pretendClickCloseActivity failed for res " + resStr + " not found");
return;
}
View closeLikelyView = new View(activity.getApplicationContext());
closeLikelyView.setId(resId);
((View.OnClickListener) activity).onClick(closeLikelyView);
Log.d(TAG, "pretendClickCloseActivity success");
} else {
Log.e(TAG, "pretendClickCloseActivity failed for not OnClickListener instance");
}
}

代码写完,写一个接口,方便静态代码注入

public class PluginManager {
public static void init(Context context) {
TalkingTomHacker.initInstrumentation();
}
}

嗯,没看错,接口代码就是如此简单,注入的时候,直接调用PluginManager.init就行了。

如何将插件代码注入到apk中呢?

1.反编译apk,得到smali文件,在Application的onCreate函数中

.class public Lcom/jinke/XiaomiApplication;
.super Lcom/outfit7/jinke/ChinaAdApplication;
.source "XiaomiApplication.java"
# direct methods
.method public constructor <init>()V
.registers 1
.line 10
invoke-direct {p0}, Lcom/outfit7/jinke/ChinaAdApplication;-><init>()V
return-void
.end method
# virtual methods
.method public onCreate()V
.registers 3
.line 14
invoke-super {p0}, Lcom/outfit7/jinke/ChinaAdApplication;->onCreate()V
.line 19
new-instance v0, Lcom/xiaomi/gamecenter/sdk/entry/MiAppInfo;
invoke-direct {v0}, Lcom/xiaomi/gamecenter/sdk/entry/MiAppInfo;-><init>()V
const-string v1, "2882303761517147655"
.line 20
invoke-virtual {v0, v1}, Lcom/xiaomi/gamecenter/sdk/entry/MiAppInfo;->setAppId(Ljava/lang/String;)V
const-string v1, "5811714712655"
.line 21
invoke-virtual {v0, v1}, Lcom/xiaomi/gamecenter/sdk/entry/MiAppInfo;->setAppKey(Ljava/lang/String;)V
.line 22
new-instance v1, Lcom/jinke/XiaomiApplication$1;
invoke-direct {v1, p0}, Lcom/jinke/XiaomiApplication$1;-><init>(Lcom/jinke/XiaomiApplication;)V
invoke-static {p0, v0, v1}, Lcom/xiaomi/gamecenter/sdk/MiCommplatform;->Init(Landroid/content/Context;Lcom/xiaomi/gamecenter/sdk/entry/MiAppInfo;Lcom/xiaomi/gamecenter/sdk/OnInitProcessListener;)V
invoke-static {p0}, Lcom/hack/core/PluginManager;->init(Landroid/content/Context;)V
return-void
.end method

这个smali文件中,只加入了最后一句, 目的就是初始化插件,实现Instrumentation注入

invoke-static {p0}, Lcom/hack/core/PluginManager;->init(Landroid/content/Context;)V

2.由于classes.dex文件方法数的限制,插件的smali文件无法编进classes.dex中,最后我放入到了classes4.dex可以成功

反编译/回编classes.dex

java -jar ~/env/baksmali-2.5.2.jar d classes.dex
java -jar ~/env/smali-2.5.2.jar a out

反编译/回编classes4.dex (反编译后将插件的smali放到out下面,然后再回编就行了)

java -jar ~/env/baksmali-2.5.2.jar d classes4.dex
java -jar ~/env/smali-2.5.2.jar a out

3.将上面面两步得到的dex,替换apk中的classes.dex与classes4.dex

Windows:我是通过zip工具打开的apk,然后直接将dex拖进去就行,比较方便

Mac:未尝试

4.删除apk中原本的签名文件夹(META-INF)重签名

1.广告页面会先弹出,80ms左右才会消失掉,所以视觉上还能看到广告activity

2.可能还有其他地方有广告。这个apk广告太多了。。。。不过目前来看应该好多了

带插件的apk如下:(法律原因,已删除)

原包apk:

链接: https://pan.baidu.com/s/1to64frG_x6DtqnrVAUZLfQ 密码: 7fwm

[注意] 招人!base上海,课程运营、市场多个坑位等你投递!

最后于 6天前 被whulzz编辑 ,原因:

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK