在黑客攻击-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位] [目标寄存器],[源寄存器](可选)
。
-
移位操作。
在这里插入图片描述
-
返回操作。
在这里插入图片描述
-
常量操作。
在这里插入图片描述
-
调用操作。
在这里插入图片描述
-
判断操作。
在这里插入图片描述
-
属性操作。
在这里插入图片描述
- 其他指令。[图片上传失败...(image-fd08fc-1603018206703)]
实战领跑娱乐APK
通过apktool进行解包。解压后的目录结构如下:
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>
。
-
<clinit>
:在jvm第一次加载class文件时调用,包括静态变量初始化语句和静态块的执行。 -
<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文件。这个文件是动态链接库,后续会详细介绍。
成员方法
看这些名字就能直到,名字被混淆了。这个只能通过阅读代码来看出对应的函数到底做了什么。
.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逆向相关内容,欢迎关注我的微信公众号: 无情剑客。
网友评论