前言:
1、本篇内容选自网络,仅介绍插桩的基本概念
2、要实现插桩需要一系列的反编译和打包工具,将在另一篇文章中介绍
插桩的概念是以静态的方式修改第三方的代码,也就是从编译阶段,对源代码(中间代码)进行编译,而后重新打包,是静态的篡改;
而hook则不需要再编译阶段修改第三方的源码或中间代码,是在运行时通过反射的方式修改调用,是一种动态的篡改
插桩的概念:
插桩就是在代码中插入一段我们自定义的代码。
插桩的目的:
将程序中插入我们自定义的代码编译到可执行文件中,
该程序的运行过程中就会执行我们自定义的代码,实现我们想要增加的功能需求。
安卓插桩简介:
在安卓方面,插桩通常是指在某手机的官方原厂ROM中通过反编译的方式获得中间代码,然后通过对中间代码的修改加入自定义的功能,达到为ROM添加功能的目的。
这样做的优势是:
- 厂商原厂ROM中的功能、特性或优化基本不会丧失。
- 适配工作很简单,因为官方的ROM本身就是能正常运行的,插桩者只要保证把自己的代码正确插入即可。
缺点是:
- 通过中间代码插桩实现功能比起源代码进行修改要费力太多。
- 维护起来也相对麻烦,各机型上难以复用,需要针对性的做一些修改。
什么时候用到插桩:
源码开发是自己拥有一套完整的源代码,想要实现新增的功能需求只需直接在源码上修改。
比如小米自己生产的手机的ROM,必然是在一套完整的源代码上不断修改完善的。
而小米用来适配到其它厂商手机的patchrom项目,则是通过插桩修改的方式。
中间代码.smali 和源代码.java
比如我们看小米Nexus 5 MIUIV6的框架包
这里的文件就是反编译Nexus 5官方原厂ROM后得到的中间代码并加入MIUI自己的功能的版本。
我们随便打开里面一个不太复杂的文件,如AsyncResult.smali,你看到的内容就是所谓中间代码了。
.class public Landroid/os/AsyncResult;
.super Ljava/lang/Object;
.source "AsyncResult.java"
# instance fields
.field public exception:Ljava/lang/Throwable;
.field public result:Ljava/lang/Object;
.field public userObj:Ljava/lang/Object;
# direct methods
.method public constructor <init>(Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Throwable;)V
.locals 0
.param p1, "uo" # Ljava/lang/Object;
.param p2, "r" # Ljava/lang/Object;
.param p3, "ex" # Ljava/lang/Throwable;
.prologue
.line 63
invoke-direct {p0}, Ljava/lang/Object;-><init>()V
.line 64
iput-object p1, p0, Landroid/os/AsyncResult;->userObj:Ljava/lang/Object;
.line 65
iput-object p2, p0, Landroid/os/AsyncResult;->result:Ljava/lang/Object;
.line 66
iput-object p3, p0, Landroid/os/AsyncResult;->exception:Ljava/lang/Throwable;
.line 67
return-void
.end method
.method public static forMessage(Landroid/os/Message;)Landroid/os/AsyncResult;
.locals 3
.param p0, "m" # Landroid/os/Message;
.prologue
const/4 v2, 0x0
.line 53
new-instance v0, Landroid/os/AsyncResult;
iget-object v1, p0, Landroid/os/Message;->obj:Ljava/lang/Object;
invoke-direct {v0, v1, v2, v2}, Landroid/os/AsyncResult;-><init>(Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Throwable;)V
.line 55
.local v0, "ret":Landroid/os/AsyncResult;
iput-object v0, p0, Landroid/os/Message;->obj:Ljava/lang/Object;
.line 57
return-object v0
.end method
.method public static forMessage(Landroid/os/Message;Ljava/lang/Object;Ljava/lang/Throwable;)Landroid/os/AsyncResult;
.locals 2
.param p0, "m" # Landroid/os/Message;
.param p1, "r" # Ljava/lang/Object;
.param p2, "ex" # Ljava/lang/Throwable;
.prologue
.line 40
new-instance v0, Landroid/os/AsyncResult;
iget-object v1, p0, Landroid/os/Message;->obj:Ljava/lang/Object;
invoke-direct {v0, v1, p1, p2}, Landroid/os/AsyncResult;-><init>(Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Throwable;)V
.line 42
.local v0, "ret":Landroid/os/AsyncResult;
iput-object v0, p0, Landroid/os/Message;->obj:Ljava/lang/Object;
.line 44
return-object v0
.end method
而源代码可读性要比中间代码强多了,我们打开AOSP项目中相同文件的源码来看
/*
* Copyright (C) 2006 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.os;
import android.os.Message;
/** [url=home.php?mod=space&uid=398]@hide[/url] */
public class AsyncResult
{
/*************************** Instance Variables **************************/
// Expect either exception or result to be null
public Object userObj;
public Throwable exception;
public Object result;
/***************************** Class Methods *****************************/
/** Saves and sets m.obj */
public static AsyncResult
forMessage(Message m, Object r, Throwable ex)
{
AsyncResult ret;
ret = new AsyncResult (m.obj, r, ex);
m.obj = ret;
return ret;
}
/** Saves and sets m.obj */
public static AsyncResult
forMessage(Message m)
{
AsyncResult ret;
ret = new AsyncResult (m.obj, null, null);
m.obj = ret;
return ret;
}
/** please note, this sets m.obj to be this */
public
AsyncResult (Object uo, Object r, Throwable ex)
{
userObj = uo;
result = r;
exception = ex;
}
}
读到这里你应该会对插桩的中间代码和源代码的区别有一个基础的了解了。
patchrom开源根本不算开源代码,因为反编译中间代码的取得我们任何一个人通过反编译工具都能做到,这只是忽悠外行的东西
简单的插桩实例
接下来,我们用一个示例程序演示如果通过插桩和源代码修改的方式为一个程序增加功能。
这个示例程序很简单,打开之后只会在手机上显示一段话:
而我们要实现的就是用插桩和源码修改的两种方法实现另起一行再显示”以后多发科普,提升新手玩机水平,避免大家被这种人坑!”
如果是源代码的话,只需要很简单的在源码中加入一段”\n以后多发科普,提升新手玩机水平,避免大家被这种人坑!”
下面是修改后的源码:
package com.example.appdemo;
import android.app.Activity;
import android.os.Bundle;
import android.widget.TextView;
public class MainActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
TextView mTextView = (TextView)findViewById(R.id.helloworld);
mTextView.setText("moonlight-roms月亮打包大神就是个拿别人代码打包赚推广费,肆意胡编乱造各种没有常识的话欺骗新用户的骗子!\n以后多发科普,提升新手玩机水平,避免大家被这种人坑!");
}
}
如果要插桩,则是要反编译这个程序,然后将
.line 15
.local v0, "mTextView":Landroid/widget/TextView;
const-string v1, "moonlight-roms\u6708\u4eae\u6253\u5305\u5927\u795e\u5c31\u662f\u4e2a\u62ff\u522b\u4eba\u4ee3\u7801\u6253\u5305\u8d5a\u63a8\u5e7f\u8d39\uff0c\u8086\u610f\u80e1\u7f16\u4e71\u9020\u5404\u79cd\u6ca1\u6709\u5e38\u8bc6\u7684\u8bdd\u6b3a\u9a97\u65b0\u7528\u6237\u7684\u9a97\u5b50!"
修改为:
.line 15
.local v0, "mTextView":Landroid/widget/TextView;
const-string v1, "moonlight-roms\u6708\u4eae\u6253\u5305\u5927\u795e\u5c31\u662f\u4e2a\u62ff\u522b\u4eba\u4ee3\u7801\u6253\u5305\u8d5a\u63a8\u5e7f\u8d39\uff0c\u8086\u610f\u80e1\u7f16\u4e71\u9020\u5404\u79cd\u6ca1\u6709\u5e38\u8bc6\u7684\u8bdd\u6b3a\u9a97\u65b0\u7528\u6237\u7684\u9a97\u5b50!\n\u4ee5\u540e\u591a\u53d1\u79d1\u666e\uff0c\u63d0\u5347\u65b0\u624b\u73a9\u673a\u6c34\u5e73\uff0c\u907f\u514d\u5927\u5bb6\u88ab\u8fd9\u79cd\u4eba\u5751\uff01"