第一次接触到安卓反编译是在我念初中的时候,那个时候会反编译修改一些东西,但是没有原生开发技术的支持,想想这么早就接触到了反编译,到目前还这么菜,最近想起了捡一捡
首先分享自己反编译植入布局的一些小经验
- 不要动resources.arsc(反编译/回编译都容易失败,容易打乱其它文件的地址)
- 不要植入findViewById这类语句
- 使用tag绑定布局
- 尽量使用自定义View
- 将所有的findViewById换成findViewByTag
- 尽量不要植入一整个xml文件(植入容易改变大量xml的地址)
- 在java中任何的R.id/R.layout最后编译成class/smali中都变成了0x**的16进制地址
该篇内容需要具备以下知识:
- 安卓原生开发基础
- 安卓原生自定义View
- 内容观察者
- 使用Handler更新View
- 简单的线程相关
使用到的软件
- MT管理器(使用它的文本修改功能)
- Apktool反编译工具(这里我用自己的MToolkit,还有国外的同名Apktool)
- AIDE(可用android studio等替代)
我们在植入任何的View之前,先确保这些View能被正确的显示出来,也就是说,我们需要跑出一个demo。这篇我们植入布局进手机的状态栏
AIDE部分
1.新建一个空项目
2.点击右上角把这个简单的app跑起来
3.新建一个java文件,随便编写一个自定义View
package com.nos;
import android.annotation.*;
import android.content.*;
import android.database.*;
import android.net.*;
import android.os.*;
import android.provider.Settings.*;
import android.text.*;
import android.util.*;
import android.view.*;
import android.widget.*;
import java.io.*;
import android.provider.Settings.System;
public class StatusWeather extends TextView
{
static final Uri WEATHER_URI = Uri.parse("content://weather/weather");
private final Context mContext;
@SuppressLint({"HandlerLeak"})
private Handler mHandler;
private final ContentObserver mWeatherObserver;
private WeatherRunnable mWeatherRunnable;
public StatusWeather(Context context)
{
this(context, null);
}
public StatusWeather(Context context, AttributeSet attributeSet)
{
this(context, attributeSet, -1);
}
public StatusWeather(Context context, AttributeSet attributeSet, int defStyle)
{
super(context, attributeSet, defStyle);
this.mHandler = new WeatherHandler(this);
this.mWeatherObserver = new StatusWeatherObserver(this, this.mHandler);
this.mContext = context;
this.mWeatherRunnable = new WeatherRunnable(this, mHandler);
setVisibility(this.GONE);
this.mContext.getContentResolver().registerContentObserver(WEATHER_URI, true, this.mWeatherObserver);
this.mContext.getContentResolver().registerContentObserver(System.getUriFor("your key"), true, this.mWeatherObserver);
this.setOnClickListener(new View.OnClickListener(){
@Override
public void onClick(View p1)
{
Runtime runtime = Runtime.getRuntime();
try
{
runtime.exec("input keyevent 4");
}
catch (IOException ignored)
{
}
Intent intent = new Intent();
intent.setClassName("com.miui.weather2", "com.miui.weather2.ActivityWeatherMain");
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED);
mContext.startActivity(intent);
}
});
updateWeatherInfo();
}
public void updateWeatherInfo()
{
this.mHandler.removeCallbacks(this.mWeatherRunnable);
this.mHandler.postDelayed(this.mWeatherRunnable, 200);
}
@Override
protected void onDetachedFromWindow()
{
super.onDetachedFromWindow();
if (this.mWeatherObserver != null)
{
this.mContext.getContentResolver().unregisterContentObserver(this.mWeatherObserver);
}
}
}
class WeatherHandler extends Handler
{
final StatusWeather a;
WeatherHandler(StatusWeather statusBarWeather)
{
this.a = statusBarWeather;
}
@Override
public void handleMessage(Message message)
{
String str = (String) message.obj;
this.a.setText(TextUtils.isEmpty(str) ? "点击获取\n天气数据" : str);
this.a.setVisibility(message.what != 0 ? 0 : 8);
}
}
class StatusWeatherObserver extends ContentObserver
{
final StatusWeather mStatusWeather;
public StatusWeatherObserver(StatusWeather view, Handler handler)
{
super(handler);
this.mStatusWeather = view;
}
@Override
public void onChange(boolean z)
{
mStatusWeather.updateWeatherInfo();
}
}
class WeatherRunnable implements Runnable
{
final StatusWeather this$0;
final Handler mHandler;
public WeatherRunnable(StatusWeather weatherView, Handler handler)
{
this.this$0 = weatherView;
this.mHandler = handler;
}
@Override
public void run()
{
Object obj = "";
try
{
Cursor query = this$0.getContext().getContentResolver().query(StatusWeather.WEATHER_URI, null, null, null, null);
if (query != null)
{
if (query.moveToFirst())
{
String string = query.getString(query.getColumnIndexOrThrow("city_name"));
String string2 = query.getString(query.getColumnIndexOrThrow("description"));
String string3 = query.getString(query.getColumnIndexOrThrow("temperature"));
StringBuilder stringBuilder = new StringBuilder();
stringBuilder.append(string);
stringBuilder.append("/");
stringBuilder.append(string2);
stringBuilder.append(" ");
stringBuilder.append(string3);
obj = stringBuilder.toString();
}
query.close();
}
}
catch (IllegalArgumentException e)
{
obj = "没有获取到天气信息";
}
catch (Throwable th)
{
Message obtainMessage = mHandler.obtainMessage();
obtainMessage.what = 100;
obtainMessage.obj = obj;
mHandler.sendMessage(obtainMessage);
}
Message obtainMessage2 = mHandler.obtainMessage();
int bb=System.getInt(this.this$0.getContext().getContentResolver(),"your key", 0);
// TODO: Implement this method
obtainMessage2.what = bb;
obtainMessage2.obj = obj;
mHandler.sendMessage(obtainMessage2);
}
}
StatusWeather则是我们自定义的TextView,他用来获取本地的天气信息,内容观察者是为了其它的应用能够控制这个控件的显示与隐藏
添加进xml中
没有获取到天气,不过无事,可能是因为app的权限不是系统级别的,这篇也是讲个方法
植入部分
1.复制出你的状态栏apk
如下gif
2.反编译状态栏
由于我的工具箱还没有具备文本编写功能,所以我们用MT管理器,我这里需要植入这个TextView到我的下拉栏
3.在下拉栏xml中添加代码
这个xml的路径为res/layout/quick_status_bar_expanded_header.xml
<?xml version="1.0" encoding="utf-8"?>
<com.android.systemui.qs.QuickStatusBarHeader android:layout_gravity="@integer/notification_panel_layout_gravity" android:id="@id/header" android:background="@android:color/transparent" android:clickable="false" android:clipChildren="false" android:clipToPadding="false" android:layout_width="fill_parent" android:layout_height="@dimen/notch_expanded_header_height" android:baselineAligned="false" android:elevation="4.0dip"
xmlns:android="http://schemas.android.com/apk/res/android">
<com.android.systemui.statusbar.HeaderView android:gravity="bottom" android:layout_gravity="start|bottom|center" android:id="@id/header_content" android:focusable="true" android:focusableInTouchMode="true" android:layout_width="fill_parent" android:layout_height="fill_parent" android:layout_marginBottom="@dimen/expanded_notification_header_bottom" android:alpha="@dimen/qs_status_bar_header_alpha" android:layout_marginStart="@dimen/expanded_notification_header_start">
<com.android.systemui.statusbar.policy.Clock android:textAppearance="@style/TextAppearance.StatusBar.Expanded.Weather" android:gravity="center_vertical" android:id="@id/date_time" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_above="@id/carrier_layout" android:layout_alignTop="@id/system_icon_area" android:layout_alignParentBottom="true" android:layout_marginStart="@dimen/expanded_notification_weather_temperature_right" />
<LinearLayout android:id="@id/carrier_layout" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignParentBottom="true" android:layout_marginStart="@dimen/expanded_notification_weather_temperature_right">
<com.android.keyguard.CarrierText android:textAppearance="@style/TextAppearance.StatusBar.Expanded.Weather" android:ellipsize="marquee" android:gravity="center_vertical" android:layout_gravity="center_vertical" android:id="@id/carrier" android:visibility="gone" android:layout_width="wrap_content" android:layout_height="wrap_content" android:maxEms="9" android:singleLine="true" android:marqueeRepeatLimit="1" />
</LinearLayout>
<LinearLayout android:id="@id/carrier_land_layout" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignTop="@id/system_icon_area" android:layout_alignParentBottom="true" android:layout_marginStart="@dimen/notch_expanded_header_carrier_margin" android:layout_toEndOf="@id/date_time">
<com.android.keyguard.CarrierText android:textAppearance="@style/TextAppearance.StatusBar.Expanded.Weather" android:ellipsize="marquee" android:gravity="center_vertical" android:layout_gravity="center_vertical" android:id="@id/carrier_land" android:visibility="gone" android:layout_width="wrap_content" android:layout_height="wrap_content" android:maxEms="9" android:singleLine="true" android:marqueeRepeatLimit="1" />
</LinearLayout>
<com.android.keyguard.AlphaOptimizedLinearLayout android:gravity="end" android:orientation="horizontal" android:id="@id/system_icon_area" android:focusable="true" android:focusableInTouchMode="true" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignParentBottom="true" android:layout_toEndOf="@id/date_time" android:layout_alignParentEnd="true">
<com.nos.Temperature android:textSize="12.0sp" android:textStyle="bold" android:textColor="#ffffffff" android:gravity="end|center" android:layout_gravity="end|center" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginEnd="@dimen/notch_settings_button_margin" />
<include layout="@layout/system_icons" />
</com.android.keyguard.AlphaOptimizedLinearLayout>
<com.android.systemui.statusbar.policy.Clock android:textAppearance="@style/TextAppearance.StatusBar.Expanded.Clock.Notch" android:id="@id/big_time" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_above="@id/date_time" android:layout_alignParentStart="true" />
<LinearLayout android:orientation="horizontal" android:focusable="true" android:focusableInTouchMode="true" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_above="@id/system_icon_area" android:layout_alignParentEnd="true">
<com.nos.StatusShortcut android:layout_width="wrap_content" android:layout_height="fill_parent" android:layout_marginBottom="32.0px" android:layout_marginStart="@dimen/notch_settings_button_margin" android:layout_marginEnd="0.0dip" />
<ImageView android:id="@id/notification_shade_shortcut" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginBottom="@dimen/notch_settings_button_margin" android:layout_marginStart="@dimen/notch_settings_button_margin" />
</LinearLayout>
<LinearLayout android:orientation="horizontal" android:focusable="true" android:focusableInTouchMode="true" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginTop="30.0dip" android:layout_above="@id/system_icon_area" android:layout_alignParentTop="true" android:layout_alignParentEnd="true">
<com.nos.Charge android:textSize="12.0dip" android:textColor="#ffffffff" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginStart="@dimen/notch_settings_button_margin" />
<com.nos.StatusWeather android:textSize="12.0dip" android:textColor="#ffffffff" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginStart="@dimen/notch_settings_button_margin" />
</LinearLayout>
</com.android.systemui.statusbar.HeaderView>
</com.android.systemui.qs.QuickStatusBarHeader>
可以发现这里植入了多个view了,不过都是差不多的
4.回编译
回编译耗时比较久,点击文件夹后面那个按钮即可看到弹窗
5.替换xml回原状态栏
可以观察到上面回编译已经生成新的apk了,这个apk是不能用的:
- 没有签名
- 地址被打乱了
- 换回去99%概率fc
我们需要将其中变换的部分提取出来 也就是
MiuiSystemUI_new.apk/res/layout/quick_status_bar_expanded_header.xml
按照原路径替换回我们原始的状态栏apk
没有完,这个替换回去后它是找不到这个自定义view的,自定义view在我们的aide项目中呢,所以我们
6.提取AIDE项目的dex
在你自己项目的路径下,有build的产物,apk/class/dex都有
7.重命名classes.dex为classes2.dex
这步无细节操作,设为标题是怕大家忽略了
8.将classes2.dex添加进状态栏apk
最后效果
总结
植入布局的流程(只适用于无混淆无加固的app)
- 需要得到自己java对应的dex
- 反编译需要植入的app
- 添加布局到对应的xml
- 回编译app并提取出对应的xml
- 添加回编译后的xml到原apk
- 添加自己的dex到原apk
安卓原生动态添加布局用得好的话,植入addView对应的安卓字节码去添加一些View也是可行的