在黑客攻击-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位][目标寄存器],[源寄存器](可选)。
- 移位操作。
- 返回操作。
- 常量操作。
- 调用操作。
- 判断操作。
- 属性操作。
- 其他指令。
实战领跑娱乐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
构造方法
类的初始化方法
:在jvm第一次加载class文件时调用,包括静态变量初始化语句和静态块的执行。 :在实例创建出来的时候调用,包括调用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逆向相关内容,欢迎关注我的微信公众号: 无情剑客。