阅读 648

Android的反编译(布局植入篇)

第一次接触到安卓反编译是在我念初中的时候,那个时候会反编译修改一些东西,但是没有原生开发技术的支持,想想这么早就接触到了反编译,到目前还这么菜,最近想起了捡一捡

首先分享自己反编译植入布局的一些小经验

  • 不要动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(thisthis.mHandler);
		this.mContext = context;
		this.mWeatherRunnable = new WeatherRunnable(this, mHandler);
		setVisibility(this.GONE);
		this.mContext.getContentResolver().registerContentObserver(WEATHER_URI, truethis.mWeatherObserver);
		this.mContext.getContentResolver().registerContentObserver(System.getUriFor("your key"), truethis.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, nullnullnullnull);
			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中

run起来康康

没有获取到天气,不过无事,可能是因为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都有

我们只需要它的classes.dex

7.重命名classes.dex为classes2.dex

这步无细节操作,设为标题是怕大家忽略了

8.将classes2.dex添加进状态栏apk

是不是觉得熟悉,这里也是用到了Mutldex的做法 当然我们还可以将AIDE工程run出来的apk反编译,得到它的smali,将smali添加进状态栏apk反编译后的smali,随后回编译状态栏也就顺便把我们植入的smali一起回编译进了一个dex,提取出来,覆盖回原状态栏

最后效果

总结

植入布局的流程(只适用于无混淆无加固的app)

  • 需要得到自己java对应的dex
  • 反编译需要植入的app
  • 添加布局到对应的xml
  • 回编译app并提取出对应的xml
  • 添加回编译后的xml到原apk
  • 添加自己的dex到原apk

安卓原生动态添加布局用得好的话,植入addView对应的安卓字节码去添加一些View也是可行的

关注下面的标签,发现更多相似文章
评论