«

安卓逆向 - 使用ApkTool进行逆向

ljierui 发布于 阅读:47 技术杂谈


ApkTool简介

APKTool 是一种常用的逆向工程工具,专门用于处理 Android APK 文件(Android应用安装包)。它的主要功能是将 APK 文件进行反编译(Decompile)和重新打包(Recompile),以便开发者或研究人员分析和修改 Android 应用程序。

ApkTool工作原理

  1. 解包过程:

    • APKTool 使用 aapt(Android Asset Packaging Tool)解析 APK 的资源和 XML 文件。
    • 将 APK 文件的内容分解为:
    • AndroidManifest.xml:包含应用的基本配置信息。
    • res/:应用的界面资源(如图片、布局文件)。
    • smali/:反编译后的代码。
  2. 重新打包过程:

    • APKTool 会将修改后的资源和代码重新组合为 APK 文件,保持原始结构。

ApkTool常用命令

apktool --version (查看版本)
apktool d app.apk -o output_folder (反编译APK :d表示解包)
apktool b output_folder -o new_app.apk (重新打包apk: b 表示打包)
apktool if app.apk (仅解包资源文件: 用于解包需要特定资源框架的应用)
apktool d app.apk -p custom_framework_path (指定系统框架: 对于依赖特定系统框架的应用,必须先安装相关框架文件)
apktool d app.apk --force (强制解包: 如果同名目录已经存在,默认情况下 APKTool 会提示错误,使用此选项可以覆盖输出)
apktool b output_folder --ignore-res (忽略错误继续打包: 用于资源文件可能损坏但希望完成打包的场景)
apktool b output_folder --copy-original (添加自定义配置: 用于保留未修改的资源文件或代码部分)
apktool b output_folder --use-aapt2 (自定义压缩选项: 适用于需要支持新版 Android 特性的场景)
apktool clean (清除缓存)

ApkTool解包后目录解析

实战

编写app,功能是需要邀请码才能进行注册,代码参考《Android 软件安全权威指南》
要点:要编译release的,不然代码不全

对apk进行解包

查看解包后的文件

随便输入注册信息,提示 Registration failed!

分析smail语法,在MainActivity$1.smali 中

.class Lcom/example/crame0201/MainActivity$1;
.super Ljava/lang/Object;
.source "MainActivity.java"

# interfaces
.implements Landroid/view/View$OnClickListener;

# annotations
.annotation system Ldalvik/annotation/EnclosingMethod;
    value = Lcom/example/crame0201/MainActivity;->onCreate(Landroid/os/Bundle;)V
.end annotation

.annotation system Ldalvik/annotation/InnerClass;
    accessFlags = 0x0
    name = null
.end annotation

# instance fields
.field final synthetic this$0:Lcom/example/crame0201/MainActivity;

# direct methods
.method constructor <init>(Lcom/example/crame0201/MainActivity;)V
    .locals 0
    .annotation system Ldalvik/annotation/MethodParameters;
        accessFlags = {
            0x8010
        }
        names = {
            null
        }
    .end annotation

    .line 70
    iput-object p1, p0, Lcom/example/crame0201/MainActivity$1;->this$0:Lcom/example/crame0201/MainActivity;

    invoke-direct {p0}, Ljava/lang/Object;-><init>()V

    return-void
.end method

# virtual methods
.method public onClick(Landroid/view/View;)V
    .locals 2

    .line 73
    iget-object p1, p0, Lcom/example/crame0201/MainActivity$1;->this$0:Lcom/example/crame0201/MainActivity;

    invoke-static {p1}, Lcom/example/crame0201/MainActivity;->-$$Nest$fgetedit_userName(Lcom/example/crame0201/MainActivity;)Landroid/widget/EditText;

    move-result-object v0

    invoke-virtual {v0}, Landroid/widget/EditText;->getText()Landroid/text/Editable;

    move-result-object v0

    invoke-virtual {v0}, Ljava/lang/Object;->toString()Ljava/lang/String;

    move-result-object v0

    invoke-virtual {v0}, Ljava/lang/String;->trim()Ljava/lang/String;

    move-result-object v0

    iget-object v1, p0, Lcom/example/crame0201/MainActivity$1;->this$0:Lcom/example/crame0201/MainActivity;

    invoke-static {v1}, Lcom/example/crame0201/MainActivity;->-$$Nest$fgetedit_sn(Lcom/example/crame0201/MainActivity;)Landroid/widget/EditText;

    move-result-object v1

    .line 74
    invoke-virtual {v1}, Landroid/widget/EditText;->getText()Landroid/text/Editable;

    move-result-object v1

    invoke-virtual {v1}, Ljava/lang/Object;->toString()Ljava/lang/String;

    move-result-object v1

    invoke-virtual {v1}, Ljava/lang/String;->trim()Ljava/lang/String;

    move-result-object v1

    .line 73
    invoke-static {p1, v0, v1}, Lcom/example/crame0201/MainActivity;->-$$Nest$mcheckSN(Lcom/example/crame0201/MainActivity;Ljava/lang/String;Ljava/lang/String;)Z

    move-result p1

    const/4 v0, 0x0

    if-eqz p1, :cond_0

    .line 75
    iget-object p1, p0, Lcom/example/crame0201/MainActivity$1;->this$0:Lcom/example/crame0201/MainActivity;

    sget v1, Lcom/example/crame0201/R$string;->unsuccessed:I

    invoke-static {p1, v1, v0}, Landroid/widget/Toast;->makeText(Landroid/content/Context;II)Landroid/widget/Toast;

    move-result-object p1

    .line 76
    invoke-virtual {p1}, Landroid/widget/Toast;->show()V

    goto :goto_0

    .line 78
    :cond_0
    iget-object p1, p0, Lcom/example/crame0201/MainActivity$1;->this$0:Lcom/example/crame0201/MainActivity;

    sget v1, Lcom/example/crame0201/R$string;->successed:I

    invoke-static {p1, v1, v0}, Landroid/widget/Toast;->makeText(Landroid/content/Context;II)Landroid/widget/Toast;

    move-result-object p1

    .line 79
    invoke-virtual {p1}, Landroid/widget/Toast;->show()V

    .line 80
    iget-object p1, p0, Lcom/example/crame0201/MainActivity$1;->this$0:Lcom/example/crame0201/MainActivity;

    invoke-static {p1}, Lcom/example/crame0201/MainActivity;->-$$Nest$fgetbtn_register(Lcom/example/crame0201/MainActivity;)Landroid/widget/Button;

    move-result-object p1

    invoke-virtual {p1, v0}, Landroid/widget/Button;->setEnabled(Z)V

    .line 81
    iget-object p1, p0, Lcom/example/crame0201/MainActivity$1;->this$0:Lcom/example/crame0201/MainActivity;

    sget v0, Lcom/example/crame0201/R$string;->register:I

    invoke-virtual {p1, v0}, Lcom/example/crame0201/MainActivity;->setTitle(I)V

    :goto_0
    return-void
.end method

核心的验证逻辑在下面的这段代码中
1、作用:调用 checkSN 方法。
2、参数说明:
p1:可能是当前 Activity 的实例。
v0:第一个字符串(通常是用户名,edit_userName 的值)。
v1:第二个字符串(通常是注册码,edit_sn 的值)。
返回值:该方法返回一个布尔值(true 或 false),结果被存储在寄存器 p1 中。

invoke-static {p1, v0, v1}, Lcom/example/crame0201/MainActivity;->-$$Nest$mcheckSN(Lcom/example/crame0201/MainActivity;Ljava/lang/String;Ljava/lang/String;)Z

move-result p1

if-nez p1, :cond_0

所以修改的就是 if-nez p1, :cond_0 这个逻辑,让它为false

if-eqz p1, :cond_0

重新打包apk,并使用编写apk时生成的as签名加上
java -jar apktool_2.10.0.jar b app-release (打包)
apksigner sign --ks keys_stores.jks --out a.apk app-release.apk (加上签名,要输入签名时填入的密码)

成功注册

Android逆向