Android APK 逆向,添加自定义功能

4,541 阅读7分钟

Android APK 逆向(macOS开发环境)

反编译

如果只做代码查看的话,可以使用dex2jar和jd-gui查看

dex2jar下载地址:

sourceforge.net/projects/de…

jd-gui下载地址:

java-decompiler.github.io/

使用方法:

首先将.apk文件修改成.zip文件并解压,解压文件如图

这也就是一个android工程编译出来的目录,每个文件是什么作用,不了解的可以自己去查资料了解一下,这里不做细说。

然后把下载好的dex2jar.zip解压,打开命令行进入 d2j-dex2jar.bat 文件所在目录,输入命令:

d2j-dex2jar.bat [你的dex文件路径]

能看到生成了一个.jar后缀的文jar如图

这个jar包可以通过jd-gui查看,不过先要安装jd-gui。同dex2jar的安装,下载之后解压即可。

双击打开.app程序,在文件目录中找到刚才上一步生成的jar包导入进来就可以查看反编译的代码了,如图(有些代码是混淆过的,没有mapping文件的话不能复原,只能看一下代码架构和没有混淆的代码)

如果打算做smali代码打桩的话,可以使用apktool反编译并修改

首先安装apktool,打开终端通过brew安装,打开终端执行命令安装apktool

brew install apktool

安装好以后执行

apktool d apk文件路径 -o 输出路径

得到反编译后的工程,目录如图所示:

这样我们就拿到smali文件了,可以根绝自己的需求去修改其中的代码了。

修改

由于修改是打桩是插入的,所以首先要确定代码的插入位置,例如我逆向的是一个叫GBWhatsApp的应用,要做的是把点击登录页的一个按钮,跳转到我自定义的界面。

确定登录按钮所在界面

通过命令

adb shell dumpsys activity top | grep ACTIVITY

获取当前的activity信息如图:

所以通过包名就知道当前的页面就在<com.gbwhatsapp/.registration.EULA>路径 找到工程中对应的smali文件

如果打算修改按钮跳转页面的话,只要找到对应的跳转页面,将我们自己的页面替换进去就可以了。 上一步找到了按钮所在页面,那么跳转到目标页面,查看是哪个activity。

所以通过包名就知道当前的页面就在<com.gbwhatsapp/.registration.RegisterPhone>路径

可以直接用android studio直接打开来查看smali代码,其中一个onCreate()方法,也就是说这个EULA类是Activity的子类,代码如下

建议先去简单看一下smali语法再看代码,磨刀不误砍柴工。

以下是EULA的onCreate()方法中的一段代码,其中我们能看到:

//Lcom/gbwhatsapp/registration/RegisterPhone类型的对象被赋值给v0; const-class v0, Lcom/gbwhatsapp/registration/RegisterPhone;

//v0用来初始化 Landroid/content/Intent对象v3;
new-instance v3, Landroid/content/Intent;

invoke-direct {v3, p0, v0}, Landroid/content/Intent;->(Landroid/content/Context;Ljava/lang/Class;)V

//v3作为路由跳转到RegisterPhone invoke-virtual {p0, v3},Landroid/content/Context;>startActivity(Landroid/content/Intent;)V

    const-class v0, Lcom/gbwhatsapp/registration/RegisterPhone;


    new-instance v3, Landroid/content/Intent;

    invoke-direct {v3, p0, v0}, Landroid/content/Intent;-><init>(Landroid/content/Context;Ljava/lang/Class;)V

    const-string v0, "com.gbwhatsapp.registration.RegisterPhone.resetstate"

    invoke-virtual {v3, v0, v4}, Landroid/content/Intent;->putExtra(Ljava/lang/String;Z)Landroid/content/Intent;

    invoke-virtual {p0}, Landroid/app/Activity;->getIntent()Landroid/content/Intent;

    move-result-object v2

    const-string v1, "com.gbwhatsapp.registration.RegisterPhone.phone_number"

    invoke-virtual {v2, v1}, Landroid/content/Intent;->hasExtra(Ljava/lang/String;)Z

    move-result v0

    if-eqz v0, :cond_5

    invoke-virtual {v2, v1}, Landroid/content/Intent;->getStringExtra(Ljava/lang/String;)Ljava/lang/String;

    move-result-object v0

    invoke-virtual {v3, v1, v0}, Landroid/content/Intent;->putExtra(Ljava/lang/String;Ljava/lang/String;)Landroid/content/Intent;

    :cond_5
    const-string v1, "com.gbwhatsapp.registration.RegisterPhone.country_code"

    invoke-virtual {v2, v1}, Landroid/content/Intent;->hasExtra(Ljava/lang/String;)Z

    move-result v0

    if-eqz v0, :cond_6

    invoke-virtual {v2, v1}, Landroid/content/Intent;->getStringExtra(Ljava/lang/String;)Ljava/lang/String;

    move-result-object v0

    invoke-virtual {v3, v1, v0}, Landroid/content/Intent;->putExtra(Ljava/lang/String;Ljava/lang/String;)Landroid/content/Intent;

    :cond_6
    const-string v1, "com.gbwhatsapp.registration.RegisterPhone.clear_phone_number"

    invoke-virtual {v2, v1}, Landroid/content/Intent;->hasExtra(Ljava/lang/String;)Z

    move-result v0

    if-eqz v0, :cond_7

    invoke-virtual {v2, v1, v4}, Landroid/content/Intent;->getBooleanExtra(Ljava/lang/String;Z)Z

    move-result v0

    invoke-virtual {v3, v1, v0}, Landroid/content/Intent;->putExtra(Ljava/lang/String;Z)Landroid/content/Intent;

    :cond_7
    invoke-virtual {p0, v3}, Landroid/content/Context;->startActivity(Landroid/content/Intent;)V


    invoke-virtual {p0}, Landroid/app/Activity;->finish()V

由上面的分析可知,只要把下面的代码

const-class v0, Lcom/gbwhatsapp/registration/RegisterPhone;

替换成自定义的页面代码即可。

仿照被逆向工程的包路径新建一个工程:

如何将.java文件转化成.smali文件呢?Android Studio中安装jave2smali插件,一键转化java代码到smali代码。

插件功能使用:打开java文件——Build--Compile to Samli。

例如我的代码

转换前

public class HookActivity extends Activity {
    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_hook);
    }
}

转换后

# direct methods
.method public constructor <init>()V
    .registers 1

    .prologue
    .line 17
    invoke-direct {p0}, Landroid/app/Activity;-><init>()V

    return-void
.end method


# virtual methods
.method protected onCreate(Landroid/os/Bundle;)V
    .registers 3
    .param p1, "savedInstanceState"    # Landroid/os/Bundle;
        .annotation build Landroidx/annotation/Nullable;
        .end annotation
    .end param

    .prologue
    .line 20
    invoke-super {p0, p1}, Landroid/app/Activity;->onCreate(Landroid/os/Bundle;)V

    .line 21
    const v0, 0x7f0b001c

    invoke-virtual {p0, v0}, Lcom/gbwhatsapp/HookActivity;->setContentView(I)V

    .line 22
    return-void
.end method

merge自定义工程到逆向工程中

也就是把你需要打桩注入的代码按照对应包-文件-代码位置等,拷贝到原逆向工程中。

  • 1.将HookActivity.smali文件拷贝到com/gbwhatsapp目录下;将HookActivity中所需的布局文件拷贝到res/layout目录下。路径如下:
<GBWA.8.70/smali/com/gbwhatsapp/HookActivity.smali>
<GBWA.8.70/res/layout/activity_hook.xml >
  • 2.将HookActivity注册到manifest文件中,代码如下:
<activity android:name="com.gbwhatsapp.HookActivity"/>
  • 3.将layout资源文件的ID添加到public.xml文件中:(res/value/public.xml用于将固定资源ID分配给Android资源,不同type有不同的取值范围,添加资源的时候需要避坑)找到最下面的一个layoutid,将layoutid+1分配给我们新添加的layout文件。

原工程最大layoutID为

<public type="layout" name="activity_voicenotesounds" id="0x7f0d03ef" />

添加新的layoutID,name是上文拷贝进来的资源文件名,id是最大layoutID+1

<public type="layout" name="activity_hook" id="0x7f0d03f0" />
  • 4.在EULA文件中把原来的页面替换成HookActivity. 在上一步的EULA.smali文件中找到的smali代码
const-class v0, Lcom/gbwhatsapp/registration/RegisterPhone;

修改成我们merge进来的代码

const-class v0, Lcom/gbwhatsapp/HookActivity;

merge代码完成。

重打包签名安装

重新打包

apktool支持将反编译的工程再编译出apk文件,当把所有修改完成以后,执行命令

apktool b 【你的逆向工程根路径】
例如我的工程:apktool b /Users/xxx/GBWA.8.70/

执行过程如图:

编译结束后,【工程名】/dist/ 的目录下会生成一个apk如图:

这样重新打包就完成了,你修改的代码已经在生成的apk文件中了。

签名

系统安装apk的时候会对apk文件作签名校验,如果没有进行重新签名的话,会提示安装失败:

 Failure [INSTALL_PARSE_FAILED_NO_CERTIFICATES: Failed to collect certificates from /data/app/vmdl58739005.tmp/base.apk: Attempt to get length of null array]

签名的话需要先生成一个.keystore密钥库文件,可以通过jdk生成

在终端执行命令查看jdk目录:

/usr/libexec/java_home -v

结果如图:

进入到java home 目录下。执行命令生成.keystore密钥库文件

keytool -genkey -v -keystore my_local_keystore.keystore -alias my_local_keystore -keyalg RSA -validity 20000 -keystore 【生成文件存放路径】

生成过程中需要填入一些信息,信息可以随便填入,并不影响密钥文件的生成,如图:

生成结束后可以执行命令查看my_local_keystore.keystore信息:

keytool -list -v -keystore /Users/dongchengpu/decompile/my_local_keystore.keystore 

执行结果如图:

这样就完成了密钥库文件的生成。

同样还是在java_homem目录下,执行命令进行签名

jarsigner -keystore 【keystore文件路径】【待签名apk文件路径】 【keystore文件名】

执行过程中需要输入密钥库口令,也就是你生成keystore文件时输入的密码,执行结束如图:

这样就完成签名了!最后就剩下安装查看效果了!

执行命令安装apk:

adb install 【你的apk文件路径】

效果展示

应用名不一样是因为我顺手修改了应用名的字符串

修改前的效果

修改后的效果

备注

修改功能比较简单,主要是之前没有接触过逆向,本文记录一下逆向的整体流程,哪里有错误欢迎大佬们指正批评互相交流!