在黑客攻击-apk破解(1) 对apk的破解流程进行了完整的介绍。本篇文章基于解包之后的内容介绍smali汇编。

汇编语言

说到汇编语言,给人的感觉是很高端,很深奥,甚至于很神秘。其实这东西就是另一种形式的语言,一种非常底层的低级语言,大多数的编译型语言在编译过程中都要经历汇编的过程。比如C语言在Windows下会转化为x86/x64汇编,在Linux下会转化为ATT汇编,在Android系统上会转化为arm汇编等,很显然,汇编语言不具备跨平台的性质,在不同平台和系统中的汇编语言是不一样的。就是这些汇编语言的执行效率,直接影响cpu的性能,米国对中国芯片的技术封锁就在这里。不知开创鸿蒙的鸿蒙系统使用的是什么形式的汇编语言。

Java语言首先会通过编译器把源代码转化成Java二进制代码,并将这种虚拟的机器语言保存在文件中。之后,Java虚拟机的解释器将执行这些代码。大多数Java虚拟机为了提高性能,会在执行过程中通过编译器将一部分Java二进制代码直接转化为机器代码使用,执行过程中进行搞得机器语言转化称为动态编译或JIT编译,转化后得到的机器语言将被载入内存,由硬件执行,无需使用解释器。因此Java是一种解释、编译型的语言。

smali汇编

介绍

Dalvik 虚拟机(Dalvik VM)是 Google 专门为 Android 平台设计的一套虚拟机。区别于标准 Java 虚拟机 JVM 的 class 文件格式,Dalvik VM 拥有专属的 DEX 可执行文件格式和指令集代码。smali 和 baksmali 则是针对 DEX 执行文件格式的汇编器和反汇编器,反汇编后 DEX 文件会产生.smali 后缀的代码文件,smali 代码拥有特定的格式与语法,smali 语言是对 Dalvik 虚拟机字节码的一种解释。目前ART虚拟机相较于dalvik虚拟机算是一种升级,没有本质的区别。

smali语法

数据类型
基本类型

类型关键字

对应Java中的类型说明

V

void 只能用于返回类型

Z

boolean

B

byte

S

short

C

char

I

int

J

long

F

float

D

double

对象

Object类型,即引用类型的对象,在引用时,使用L开头,后面紧接着的是完整的包名,比如:java.lang.String对应的Smali语法则是Ljava/lang/String

数组

数组定义比较有意思,一维数组在类型的左边加一个方括号,比如:[I等同于Java的int[],每多一维就加一个方括号,最多可以设置255维。

方法声明及调用

官方Wiki中给出的Smali引用方法的模板如下:

Lpackage/name/ObjectName;->MethodName(III)Z

第一部分Lpackage/name/ObjectName;用于声明具体的类型,以便JVM寻找。
第二部分MethodName(III)Z,其中MethodName为具体的方法名,()中的字符,表示了参数数量和类型,即3个int型参数,Z为返回值的类型,即返回Boolean类型
由于方法的参数列表没有使用逗号这样的分隔符进行划分,所以只能从左到右,根据类型定义来区分参数个数。
如果需要调用构造方法,则MethodName为:<init>

寄存器声明和使用

在Smali中,如果需要存储变量,必须先声明足够数量的寄存器,1个寄存器可以存储32位长度的类型,比如Int,而两个寄存器可以存储64位长度类型的数据,比如Long或Double。声明可使用的寄存器数量的方式为:.registers N,N代表需要的寄存器的总个数,同时,还有一个关键字.locals,它用于声明非参数的寄存器个数(包含在registers声明的个数当中),也叫做本地寄存器,只在一个方法内有效,但不常用,一般使用registers即可。

有一个非static的Java方法如下,对应的smali需要多少个寄存器?

myMethod(int p1, float p2, boolean p3)

答案是5个,由于非static方法,需要占用一个寄存器以保存this指针。float类型需要2个寄存器来进行保存。

指令集

一般的指令格式为:[op]-[type](可选)/[位宽,默认4位] [目标寄存器],[源寄存器](可选)

  1. 移位操作。
  2. 返回操作。
  3. 常量操作。
  4. 调用操作。
  5. 判断操作。
  6. 属性操作。
  7. 其他指令。

实战领跑娱乐APK

通过apktool进行解包。解压后的目录结构如下:

实战分析smali汇编代码_java

assets存在一些静态文件,比如HTML、CSS等。lib存在动态连接库。res存在Android资源文件。smali*用来存在相应的Java代码。

AndroidManifest

对于一个apk,首先要查看以下它的AndroidManifest文件,这个文件包含APP的包名、入口、Service、Activity、权限等重要信息。
在AndroidManifest文件中,有这样一段代码:

<activity android:configChanges="keyboard|keyboardHidden|navigation|orientation|screenSize" android:label="@string/app_name" android:launchMode="singleTop" android:name="com.fish.main.MainGameActivity" android:theme="@android:style/Theme.NoTitleBar.Fullscreen">
            <intent-filter>
                <action android:name="android.intent.action.MAIN"/>
                <category android:name="android.intent.category.LAUNCHER"/>
            </intent-filter>
            <meta-data android:name="android.max_aspect" android:value="2.17"/>
        </activity>

由此可知app启动是的Activity正是MainGameActivity。本文就分析这个Activity,看看入口Activity都做了什么。

MainGameActivity

成员变量

首先声明类以及父类。然后定义成员变量。

.class public Lcom/fish/main/MainGameActivity;
.super Lcom/fish/main/BaseGameActivity;


# static fields  变量的类型是MainGameActivity 名字是m,变量是private的
.field public static m:Lcom/fish/main/MainGameActivity;

.field static n:Z

.field private static o:Ljava/lang/ref/WeakReference;
    .annotation system Ldalvik/annotation/Signature;
        value = {
            "Ljava/lang/ref/WeakReference",
            "<",
            "Lcom/fish/main/MainGameActivity;",
            ">;"
        }
    .end annotation
.end field


# instance fields 变量的类型是boolean 变量名字是p,变量是private的
.field private p:Z
构造方法

类的初始化方法<clinit>, 实例的初始化方法<init>

  1. <clinit>:在jvm第一次加载class文件时调用,包括静态变量初始化语句和静态块的执行。
  2. <init>:在实例创建出来的时候调用,包括调用new操作符;调用Class或java.lang.reflect.Constructor对象的newInstance()方法;调用任何现有对象的clone()方法;通过java.io.ObjectInputStream类的getObject()方法反序列化。
# direct methods
.method static constructor <clinit>()V
    .locals 1
	# 将常量1赋值给v0。4代表4个字节
    const/4 v0, 0x1
	# n = v0 (= 1)
    sput-boolean v0, Lcom/fish/main/MainGameActivity;->n:Z

    const-string v0, "game"
	# 用于调用静态方法System->loadlibrary("game.so")
    invoke-static {v0}, Ljava/lang/System;->loadLibrary(Ljava/lang/String;)V

    return-void
.end method

.method public constructor <init>()V
    .locals 1
	# 用于调用private修饰的方法,或者构造方法
    invoke-direct {p0}, Lcom/fish/main/BaseGameActivity;-><init>()V
	
    const/4 v0, 0x0
	# this.p = v0(=0)
    iput-boolean v0, p0, Lcom/fish/main/MainGameActivity;->p:Z

    return-void
.end method

通过分析代码可知在cinit中加载动态库game.so。如果想直到game.so文件做了什么,就需要查看game.so文件。这个文件是动态链接库,后续会详细介绍。

System.loadLibrary这个函数会在如下路径搜索libxxx.so文件:

  1. /system/lib
  2. /data/data/xxx apk package/lib
成员方法

看这些名字就能直到,名字被混淆了。这个只能通过阅读代码来看出对应的函数到底做了什么。

.method private i()V
    .locals 2

    sget v0, Landroid/os/Build$VERSION;->SDK_INT:I

    const/16 v1, 0x13
    
    # 成立,跳转到cond_0
    if-lt v0, v1, :cond_0

    invoke-virtual {p0}, Lcom/fish/main/MainGameActivity;->getWindow()Landroid/view/Window;

    move-result-object v0

    invoke-virtual {v0}, Landroid/view/Window;->getDecorView()Landroid/view/View;

    move-result-object v0

    const/16 v1, 0x1706

    invoke-virtual {v0, v1}, Landroid/view/View;->setSystemUiVisibility(I)V

    :cond_0
    return-void
.end method


# virtual methods
.method public c()V
    .locals 0

    return-void
.end method

.method public d()V
    .locals 0

    invoke-super {p0}, Lcom/fish/main/BaseGameActivity;->d()V

    return-void
.end method

.method public g()V
    .locals 1

    new-instance v0, Lcom/fish/main/MainGameActivity$1;

    invoke-direct {v0, p0}, Lcom/fish/main/MainGameActivity$1;-><init>(Lcom/fish/main/MainGameActivity;)V

    invoke-virtual {p0, v0}, Lcom/fish/main/MainGameActivity;->runOnUiThread(Ljava/lang/Runnable;)V

    invoke-super {p0}, Lcom/fish/main/BaseGameActivity;->g()V

    return-void
.end method

.method protected onActivityResult(IILandroid/content/Intent;)V
    .locals 0

    invoke-super {p0, p1, p2, p3}, Lcom/fish/main/BaseGameActivity;->onActivityResult(IILandroid/content/Intent;)V

    return-void
.end method

.method protected onCreate(Landroid/os/Bundle;)V
    .locals 2

    const/4 v1, 0x0

    sget-object v0, Lcom/fish/main/MainGameActivity;->o:Ljava/lang/ref/WeakReference;

    if-eqz v0, :cond_1

    sget-object v0, Lcom/fish/main/MainGameActivity;->o:Ljava/lang/ref/WeakReference;

    invoke-virtual {v0}, Ljava/lang/ref/WeakReference;->get()Ljava/lang/Object;

    move-result-object v0

    if-eqz v0, :cond_1

    const/4 v0, 0x1

    invoke-virtual {p0}, Lcom/fish/main/MainGameActivity;->finish()V

    invoke-virtual {p0, v1, v1}, Lcom/fish/main/MainGameActivity;->overridePendingTransition(II)V

    :goto_0
    invoke-super {p0, p1}, Lcom/fish/main/BaseGameActivity;->onCreate(Landroid/os/Bundle;)V

    if-nez v0, :cond_0

    sput-object p0, Lcom/fish/main/MainGameActivity;->m:Lcom/fish/main/MainGameActivity;

    invoke-direct {p0}, Lcom/fish/main/MainGameActivity;->i()V

    const-string v0, "onCreate"

    invoke-static {v0}, Lcom/fish/util/i;->a(Ljava/lang/Object;)V

    :cond_0
    return-void

    :cond_1
    new-instance v0, Ljava/lang/ref/WeakReference;

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

    sput-object v0, Lcom/fish/main/MainGameActivity;->o:Ljava/lang/ref/WeakReference;

    const/4 v0, 0x6

    invoke-virtual {p0, v0}, Lcom/fish/main/MainGameActivity;->setRequestedOrientation(I)V

    move v0, v1

    goto :goto_0
.end method

.method protected onDestroy()V
    .locals 1

    invoke-super {p0}, Lcom/fish/main/BaseGameActivity;->onDestroy()V

    sget-object v0, Lcom/fish/main/MainGameActivity;->o:Ljava/lang/ref/WeakReference;

    if-eqz v0, :cond_0

    sget-object v0, Lcom/fish/main/MainGameActivity;->o:Ljava/lang/ref/WeakReference;

    invoke-virtual {v0}, Ljava/lang/ref/WeakReference;->get()Ljava/lang/Object;

    move-result-object v0

    if-ne v0, p0, :cond_0

    const/4 v0, 0x0

    sput-object v0, Lcom/fish/main/MainGameActivity;->o:Ljava/lang/ref/WeakReference;

    :cond_0
    const-string v0, "onDestroy"

    invoke-static {v0}, Lcom/fish/util/i;->a(Ljava/lang/Object;)V

    return-void
.end method

.method public onKeyDown(ILandroid/view/KeyEvent;)Z
    .locals 2

    invoke-virtual {p2}, Landroid/view/KeyEvent;->getKeyCode()I

    move-result v0

    const/4 v1, 0x4

    if-ne v0, v1, :cond_1

    invoke-virtual {p0}, Lcom/fish/main/MainGameActivity;->e()Lcom/fish/controller/d;

    move-result-object v0

    invoke-virtual {v0}, Lcom/fish/controller/d;->d()Z

    move-result v0

    if-eqz v0, :cond_0

    invoke-virtual {p0}, Lcom/fish/main/MainGameActivity;->e()Lcom/fish/controller/d;

    move-result-object v0

    invoke-virtual {v0}, Lcom/fish/controller/d;->e()V

    :cond_0
    const/4 v0, 0x1

    :goto_0
    return v0

    :cond_1
    invoke-super {p0, p1, p2}, Lcom/fish/main/BaseGameActivity;->onKeyDown(ILandroid/view/KeyEvent;)Z

    move-result v0

    goto :goto_0
.end method

.method public onPause()V
    .locals 1

    invoke-super {p0}, Lcom/fish/main/BaseGameActivity;->onPause()V

    const-string v0, "onPause"

    invoke-static {v0}, Lcom/fish/util/i;->a(Ljava/lang/Object;)V

    return-void
.end method

.method public onResume()V
    .locals 1

    invoke-super {p0}, Lcom/fish/main/BaseGameActivity;->onResume()V

    const-string v0, "onResume"

    invoke-static {v0}, Lcom/fish/util/i;->a(Ljava/lang/Object;)V

    return-void
.end method

.method public onWindowFocusChanged(Z)V
    .locals 5

    sget-boolean v0, Lcom/fish/main/MainGameActivity;->n:Z

    if-eqz v0, :cond_0

    const/4 v0, 0x0

    sput-boolean v0, Lcom/fish/main/MainGameActivity;->n:Z

    invoke-virtual {p0}, Lcom/fish/main/MainGameActivity;->getWindowManager()Landroid/view/WindowManager;

    move-result-object v0

    invoke-interface {v0}, Landroid/view/WindowManager;->getDefaultDisplay()Landroid/view/Display;

    move-result-object v1

    invoke-virtual {v1}, Landroid/view/Display;->getHeight()I

    move-result v1

    int-to-float v1, v1

    invoke-interface {v0}, Landroid/view/WindowManager;->getDefaultDisplay()Landroid/view/Display;

    move-result-object v0

    invoke-virtual {v0}, Landroid/view/Display;->getWidth()I

    move-result v0

    int-to-float v0, v0

    cmpl-float v2, v1, v0

    if-lez v2, :cond_0

    const-string v2, "_N1_BUGLY_ANDROID_"

    new-instance v3, Ljava/lang/StringBuilder;

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

    const-string v4, "wrong display size("

    invoke-virtual {v3, v4}, Ljava/lang/StringBuilder;->append(Ljava/lang/String;)Ljava/lang/StringBuilder;

    move-result-object v3

    invoke-virtual {v3, v0}, Ljava/lang/StringBuilder;->append(F)Ljava/lang/StringBuilder;

    move-result-object v0

    const-string v3, ","

    invoke-virtual {v0, v3}, Ljava/lang/StringBuilder;->append(Ljava/lang/String;)Ljava/lang/StringBuilder;

    move-result-object v0

    invoke-virtual {v0, v1}, Ljava/lang/StringBuilder;->append(F)Ljava/lang/StringBuilder;

    move-result-object v0

    const-string v1, ") in Android"

    invoke-virtual {v0, v1}, Ljava/lang/StringBuilder;->append(Ljava/lang/String;)Ljava/lang/StringBuilder;

    move-result-object v0

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

    move-result-object v0

    invoke-static {v2, v0}, Lcom/tencent/bugly/crashreport/BuglyLog;->d(Ljava/lang/String;Ljava/lang/String;)V

    :cond_0
    invoke-super {p0, p1}, Lcom/fish/main/BaseGameActivity;->onWindowFocusChanged(Z)V

    if-eqz p1, :cond_1

    invoke-direct {p0}, Lcom/fish/main/MainGameActivity;->i()V

    :cond_1
    return-void
.end method

写在最后

虽然我们了解了Smali的基本语法,但一般不会直接编写Smali来进行功能开发,这样成本过高,而了解Smali的目的,是为了做Android的逆向工程,如:分析APP的原理、漏洞检测,当然,也可以对一些APP做一些小改动(最好不要做一些伤天害理、违法乱纪、损人不利己的事)。

对于网上有很多去apk广告的方法,可能根本就行不通,而是相互复制,只有了解代码究竟是怎么实现的,才能去实现自己想要实现的功能。

公众号

更多Android逆向相关内容,欢迎关注我的微信公众号: 无情剑客。

实战分析smali汇编代码_fish_02