优秀的编程知识分享平台

网站首页 > 技术文章 正文

Android逆向之Smali汇编(android逆向工程)

nanyue 2025-02-03 14:21:56 技术文章 3 ℃

在黑客攻击-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中的类型说明Vvoid 只能用于返回类型ZbooleanBbyteSshortCcharIintJlongFfloatDdouble

对象

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为:

寄存器声明和使用

在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进行解包。解压后的目录结构如下:

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

AndroidManifest

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

                                                                                    

由此可知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

构造方法

类的初始化方法 , 实例的初始化方法

  1. :在jvm第一次加载class文件时调用,包括静态变量初始化语句和静态块的执行。
  2. :在实例创建出来的时候调用,包括调用new操作符;调用Class或java.lang.reflect.Constructor对象的newInstance()方法;调用任何现有对象的clone()方法;通过java.io.ObjectInputStream类的getObject()方法反序列化。
# direct methods.method static constructor ()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 ()V    .locals 1    # 用于调用private修饰的方法,或者构造方法    invoke-direct {p0}, Lcom/fish/main/BaseGameActivity;->()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文件。这个文件是动态链接库,后续会详细介绍。

成员方法

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

.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;->(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;->(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;->()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逆向相关内容,欢迎关注我的微信公众号: 无情剑客。

Tags:

最近发表
标签列表