0x01 前言
演示APK: 自写的一个简单CrackMe
测试平台: Win7 64
反编译工具: APK改之理3.5
懵懵懂懂的第一次接触apk调试,有太多不懂与自我理解的偏差,还望大家指正
为了不对上千行的代码产生恐惧,决定新人还是自写小代码进行分析
先看下AndroidManifest.xml,因为是自写,代码不多
<?xml version="1.0" encoding="utf-8" standalone="no"?><manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.a3st.demo">
<application android:allowBackup="true" android:icon="@mipmap/ic_launcher" android:label="@string/app_name" android:roundIcon="@mipmap/ic_launcher_round" android:supportsRtl="true" android:theme="@style/AppTheme">
<activity android:name="com.a3st.demo.MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
<meta-data android:name="android.support.VERSION" android:value="26.1.0"/>
<meta-data android:name="android.arch.lifecycle.VERSION" android:value="27.0.0-SNAPSHOT"/>
</application>
</manifest>
从中可以看出:
- 只有一个主活动.MainActivity
- 包名为com.a3st.demo
先贴上此文章需分析的MainActivity的smali源码
.class public Lcom/a3st/demo/MainActivity;
.super Landroid/support/v7/app/AppCompatActivity;
.source "MainActivity.java"
# interfaces
.implements Landroid/view/View$OnClickListener;
# instance fields
.field private btnRegister:Landroid/widget/Button;
.field private editPassword:Landroid/widget/EditText;
.field private editUsername:Landroid/widget/EditText;
# direct methods
.method public constructor <init>()V
.locals 0
.prologue
.line 12
invoke-direct {p0}, Landroid/support/v7/app/AppCompatActivity;-><init>()V
return-void
.end method
.method private static reg(Ljava/lang/String;)Ljava/lang/String;
.locals 10
.param p0, "_username" # Ljava/lang/String;
.prologue
.line 31
invoke-virtual {p0}, Ljava/lang/String;->toCharArray()[C
move-result-object v4
.line 32
.local v4, "username":[C
array-length v1, v4
.line 33
.local v1, "len":I
const-wide/16 v2, 0x0
.line 34
.local v2, "result":J
const/4 v0, 0x0
.local v0, "i":I
:goto_0
if-ge v0, v1, :cond_1
.line 35
aget-char v5, v4, v0
const/16 v6, 0x61
if-lt v5, v6, :cond_0
.line 36
aget-char v5, v4, v0
add-int/lit8 v5, v5, -0x20
int-to-char v5, v5
aput-char v5, v4, v0
.line 38
:cond_0
aget-char v5, v4, v0
int-to-long v6, v5
add-long/2addr v2, v6
.line 34
add-int/lit8 v0, v0, 0x1
goto :goto_0
.line 40
:cond_1
const-wide/16 v6, 0x2
mul-long/2addr v6, v2
const-wide/16 v8, -0x1
and-long v2, v6, v8
.line 41
invoke-static {v2, v3}, Ljava/lang/Long;->toHexString(J)Ljava/lang/String;
move-result-object v5
invoke-virtual {v5}, Ljava/lang/String;->toUpperCase()Ljava/lang/String;
move-result-object v5
return-object v5
.end method
# virtual methods
.method public onClick(Landroid/view/View;)V
.locals 7
.param p1, "v" # Landroid/view/View;
.prologue
const/4 v6, 0x0
.line 46
invoke-virtual {p1}, Landroid/view/View;->getId()I
move-result v4
packed-switch v4, :pswitch_data_0
.line 84
:goto_0
return-void
.line 48
:pswitch_0
iget-object v4, p0, Lcom/a3st/demo/MainActivity;->editUsername:Landroid/widget/EditText;
invoke-virtual {v4}, Landroid/widget/EditText;->getText()Landroid/text/Editable;
move-result-object v4
invoke-virtual {v4}, Ljava/lang/Object;->toString()Ljava/lang/String;
move-result-object v3
.line 49
.local v3, "username":Ljava/lang/String;
iget-object v4, p0, Lcom/a3st/demo/MainActivity;->editPassword:Landroid/widget/EditText;
invoke-virtual {v4}, Landroid/widget/EditText;->getText()Landroid/text/Editable;
move-result-object v4
invoke-virtual {v4}, Ljava/lang/Object;->toString()Ljava/lang/String;
move-result-object v1
.line 50
.local v1, "password":Ljava/lang/String;
invoke-virtual {v3}, Ljava/lang/String;->length()I
move-result v4
const/4 v5, 0x4
if-lt v4, v5, :cond_0
invoke-virtual {v3}, Ljava/lang/String;->length()I
move-result v4
const/16 v5, 0xc
if-le v4, v5, :cond_1
.line 51
:cond_0
const-string v4, "\u7528\u6237\u540d\u4e0d\u80fd\u4e3a\u7a7a \u6216 \u8d85\u8fc7\u6700\u5927\u957f\u5ea6"
invoke-static {p0, v4, v6}, Landroid/widget/Toast;->makeText(Landroid/content/Context;Ljava/lang/CharSequence;I)Landroid/widget/Toast;
move-result-object v4
invoke-virtual {v4}, Landroid/widget/Toast;->show()V
goto :goto_0
.line 54
:cond_1
invoke-static {v3}, Lcom/a3st/demo/MainActivity;->reg(Ljava/lang/String;)Ljava/lang/String;
move-result-object v2
.line 55
.local v2, "result":Ljava/lang/String;
invoke-virtual {v2, v1}, Ljava/lang/String;->equals(Ljava/lang/Object;)Z
move-result v4
if-eqz v4, :cond_2
.line 56
new-instance v0, Landroid/app/AlertDialog$Builder;
invoke-direct {v0, p0}, Landroid/app/AlertDialog$Builder;-><init>(Landroid/content/Context;)V
.line 57
.local v0, "msg":Landroid/app/AlertDialog$Builder;
const-string v4, "\u4fe1\u606f\u6846:"
invoke-virtual {v0, v4}, Landroid/app/AlertDialog$Builder;->setTitle(Ljava/lang/CharSequence;)Landroid/app/AlertDialog$Builder;
.line 58
const-string v4, "\u6ce8\u518c\u6210\u529f!"
invoke-virtual {v0, v4}, Landroid/app/AlertDialog$Builder;->setMessage(Ljava/lang/CharSequence;)Landroid/app/AlertDialog$Builder;
.line 59
invoke-virtual {v0, v6}, Landroid/app/AlertDialog$Builder;->setCancelable(Z)Landroid/app/AlertDialog$Builder;
.line 60
const-string v4, "OK"
new-instance v5, Lcom/a3st/demo/MainActivity$1;
invoke-direct {v5, p0}, Lcom/a3st/demo/MainActivity$1;-><init>(Lcom/a3st/demo/MainActivity;)V
invoke-virtual {v0, v4, v5}, Landroid/app/AlertDialog$Builder;->setPositiveButton(Ljava/lang/CharSequence;Landroid/content/DialogInterface$OnClickListener;)Landroid/app/AlertDialog$Builder;
.line 66
invoke-virtual {v0}, Landroid/app/AlertDialog$Builder;->show()Landroid/app/AlertDialog;
goto :goto_0
.line 68
.end local v0 # "msg":Landroid/app/AlertDialog$Builder;
:cond_2
new-instance v0, Landroid/app/AlertDialog$Builder;
invoke-direct {v0, p0}, Landroid/app/AlertDialog$Builder;-><init>(Landroid/content/Context;)V
.line 69
.restart local v0 # "msg":Landroid/app/AlertDialog$Builder;
const-string v4, "\u4fe1\u606f\u6846:"
invoke-virtual {v0, v4}, Landroid/app/AlertDialog$Builder;->setTitle(Ljava/lang/CharSequence;)Landroid/app/AlertDialog$Builder;
.line 70
const-string v4, "\u6ce8\u518c\u5931\u8d25!"
invoke-virtual {v0, v4}, Landroid/app/AlertDialog$Builder;->setMessage(Ljava/lang/CharSequence;)Landroid/app/AlertDialog$Builder;
.line 71
invoke-virtual {v0, v6}, Landroid/app/AlertDialog$Builder;->setCancelable(Z)Landroid/app/AlertDialog$Builder;
.line 72
const-string v4, "OK"
new-instance v5, Lcom/a3st/demo/MainActivity$2;
invoke-direct {v5, p0}, Lcom/a3st/demo/MainActivity$2;-><init>(Lcom/a3st/demo/MainActivity;)V
invoke-virtual {v0, v4, v5}, Landroid/app/AlertDialog$Builder;->setPositiveButton(Ljava/lang/CharSequence;Landroid/content/DialogInterface$OnClickListener;)Landroid/app/AlertDialog$Builder;
.line 78
invoke-virtual {v0}, Landroid/app/AlertDialog$Builder;->show()Landroid/app/AlertDialog;
goto :goto_0
.line 46
:pswitch_data_0
.packed-switch 0x7f070021
:pswitch_0
.end packed-switch
.end method
.method protected onCreate(Landroid/os/Bundle;)V
.locals 1
.param p1, "savedInstanceState" # Landroid/os/Bundle;
.prologue
.line 20
invoke-super {p0, p1}, Landroid/support/v7/app/AppCompatActivity;->onCreate(Landroid/os/Bundle;)V
.line 21
const v0, 0x7f09001b
invoke-virtual {p0, v0}, Lcom/a3st/demo/MainActivity;->setContentView(I)V
.line 23
const v0, 0x7f070021
invoke-virtual {p0, v0}, Lcom/a3st/demo/MainActivity;->findViewById(I)Landroid/view/View;
move-result-object v0
check-cast v0, Landroid/widget/Button;
iput-object v0, p0, Lcom/a3st/demo/MainActivity;->btnRegister:Landroid/widget/Button;
.line 24
iget-object v0, p0, Lcom/a3st/demo/MainActivity;->btnRegister:Landroid/widget/Button;
invoke-virtual {v0, p0}, Landroid/widget/Button;->setOnClickListener(Landroid/view/View$OnClickListener;)V
.line 26
const v0, 0x7f07002f
invoke-virtual {p0, v0}, Lcom/a3st/demo/MainActivity;->findViewById(I)Landroid/view/View;
move-result-object v0
check-cast v0, Landroid/widget/EditText;
iput-object v0, p0, Lcom/a3st/demo/MainActivity;->editUsername:Landroid/widget/EditText;
.line 27
const v0, 0x7f07002d
invoke-virtual {p0, v0}, Lcom/a3st/demo/MainActivity;->findViewById(I)Landroid/view/View;
move-result-object v0
check-cast v0, Landroid/widget/EditText;
iput-object v0, p0, Lcom/a3st/demo/MainActivity;->editPassword:Landroid/widget/EditText;
.line 28
return-void
.end method
0x02 包名与类
先来看看smali源码的前几行
// 类名为public MainActivity,包名为com.a3st.demo
.class public Lcom/a3st/demo/MainActivity;
// 此类的父类为AppCompatActivity
.super Landroid/support/v7/app/AppCompatActivity;
// 文件名称
.source "MainActivity.java"
// 此类调用的所有接口: View.OnClickListener
// #号在smali中表示注释
# interfaces
.implements Landroid/view/View$OnClickListener;
// 此类的成员属性有三个(Java标准说法是字段,这里注明一下)
# instance fields
.field private btnRegister:Landroid/widget/Button;
.field private editPassword:Landroid/widget/EditText;
.field private editUsername:Landroid/widget/EditText;
整理得到:
package com.a3st.demo;
public MainActivity extends AppCompatActivity implements View.OnClickListener{
private Button btnRegister;
private EditText editPassword;
private EditText editUsername;
}
0x03 构造函数
下来就是方法了, 看下这段:
// 注释direct methods表示下列的方法都在本类中,直译就是直接方法
# direct methods
// 可能是构造函数。本人没写构造函数,这个是编译时自动生成的,所以我也不好解释init是啥
.method public constructor <init>()V // @1
// 局部变量个数0
.locals 0
// 代码块开始
.prologue
// 下面代码在Java源码中的行数: 第12行
.line 12
// 调用函数: AppCompatActivity()
invoke-direct {p0}, Landroid/support/v7/app/AppCompatActivity;-><init>()V // @2
// 返回 void
return-void
.end method
@1: .method public constructor <init>()V
.method 表明这是一个方法
public 访问权限为公共
constructor 函数名称
constructor<init> 看到一些文章把init当成函数名称
() 表明此函数无参,有参的话,括号内显示的是参数的数据类型
V 表示返回值。 V表示void
@2: invoke-direct {p0}, Landroid/support/v7/app/AppCompatActivity;-><init>()V
invoke-direct 调用直接函数
{p0} 对应Landroid/support/v7/app/AppCompatActivity
从上面得出源代码: AppCompatActivity()
整理下:
从行号与Java源码中的对比 + 其他文章来看,本人猜测为:
该类的不带参数缺省的构造方法
public {
// 可能是因为extends AppCompatActivity,会自动调用父类的构造函数
AppCompatActivity()
}
0x04 Android入口函数
注意: 以后的smali代码,如果之前已解释过,后面将不会再做解释,除非特殊说明
// protected void onCreate(Bundle savedInstanceState)
.method protected onCreate(Landroid/os/Bundle;)V
// 局部变量总数: 1。 它的变量名默认为v0
.locals 1
// 参数变量名为p1, 参数名称为savedInstanceState
// 参数名称后有个类据类型,但别忘了前面有个#,说明只是个注释
.param p1, "savedInstanceState" # Landroid/os/Bundle;
.prologue
.line 20
// 调用父类的onCreate
invoke-super {p0, p1}, Landroid/support/v7/app/AppCompatActivity;->onCreate(Landroid/os/Bundle;)V
.line 21
// 给v0赋值0x7f09ff1b
const v0, 0x7f09001b // @3
// 调用方法
invoke-virtual {p0, v0}, Lcom/a3st/demo/MainActivity;->setContentView(I)V
.line 23
const v0, 0x7f070021
invoke-virtual {p0, v0}, Lcom/a3st/demo/MainActivity;->findViewById(I)Landroid/view/View;
// 把上条指令的返回值(对象)赋值给v0
move-result-object v0
// 把v0的数据类型强制转换成Button
check-cast v0, Landroid/widget/Button;
// 把v0的值赋值给p0.btnRegister,也就是this.btnRegister
iput-object v0, p0, Lcom/a3st/demo/MainActivity;->btnRegister:Landroid/widget/Button;
.line 24
// 把this.btnRegister的值赋值给v0
iget-object v0, p0, Lcom/a3st/demo/MainActivity;->btnRegister:Landroid/widget/Button;
// btnRegister.setOnClickListener(this)
invoke-virtual {v0, p0}, Landroid/widget/Button;->setOnClickListener(Landroid/view/View$OnClickListener;)V
.line 26
const v0, 0x7f07002f
invoke-virtual {p0, v0}, Lcom/a3st/demo/MainActivity;->findViewById(I)Landroid/view/View;
move-result-object v0
check-cast v0, Landroid/widget/EditText;
iput-object v0, p0, Lcom/a3st/demo/MainActivity;->editUsername:Landroid/widget/EditText;
.line 27
const v0, 0x7f07002d
invoke-virtual {p0, v0}, Lcom/a3st/demo/MainActivity;->findViewById(I)Landroid/view/View;
move-result-object v0
check-cast v0, Landroid/widget/EditText;
iput-object v0, p0, Lcom/a3st/demo/MainActivity;->editPassword:Landroid/widget/EditText;
.line 28
return-void
.end method
@3 const v0, 0x7f09001b
0x7f09001b是一个资源id,当创建资源文件,创建控件id等时会自动生成
当编译成apk后,通过反编译发现这些资源id存放在:
项目/app/src/main/res/values/public.xml
或 项目/app/src/main/java 目录下的R$layout.smali文件中如内容: <public type="layout" name="layout_main" id="0x7f09001b" />
可以看出是layout.main.xml的资源id
整理下:
让我感到头晕的是:
invoke-virtual{p0, v0}
invoke-virtual{v0, p0}
个人理解, 举例:
invoke-virtual {p0, v0}, Lcom/a3st/demo/MainActivity;->setContentView(I)V我把大括号后的Lcom/a3st/demo/MainActivity;->setContentView(I)V分成三段:
- Lcom/a3st/demo/MainActivity 方法所在的类
- setContentView(I) 方法名称及参数
- V 方法的返回类型
大括号的第一个参数p0表示实例对象,同时可以从第一段中进行验证
如第一个参数为p0,而第一段为本类类名,则100%为本类的实例对象this
如第一个参数为其它寄存器,而第一段也不是本类类名,则100%为其它类的实例对象从大括号的第二个参数开始分别表示第二段中的各个参数,这里只有一个int型参数
得出源码: p0.setContentView(v0);
@Override // 此@Override在接下来分析onCreate函数时会讲到
protected void onCreate(Bundle savedInstanceState) {
int v0;
super.onCreate(savedInstanceState);
setContentView(0x7f09001b); // 布局id
// 小提示: 上面空了一行,从smali中看到上一行是line21,而此行代码在line23...少了个22,表示空行
this.btnRegister = (Button)findViewById(0x7f070021);
this.btnRegister.setOnClickListener(this);
// 下面的代码差不多,不全写了
/*
另外说下,这些还原的代码并不是从源码中直接抄来的,而是从smali中分析得来
如: this.btnRegister = (Button)findViewById(0x7f070021);
在源码中是: btnRegister = findViewById(R.id.btn_register);
源码中并没有进行强制转换,是编译时自动转成Button的
*/
}
0x05 按钮事件
// virtual methods表示下面的所有方法都为重载方法
# virtual methods
.method public onClick(Landroid/view/View;)V
.locals 7
.param p1, "v" # Landroid/view/View;
.prologue
const/4 v6, 0x0
.line 46
// 如0x04节所说,第一个参数为实例对象,所以代码为v.getId();
invoke-virtual {p1}, Landroid/view/View;->getId()I
move-result v4
// switch开始
// :pswitch_data_0 这是一个标签,用于表示从此开到标签处为所有case的区域 ,而标签后为case分支流程表
// 此指令意思: 在case分支流程表中查看是否有v4的值,如有则进入case代码块,没有则退出或进入default代码块
packed-switch v4, :pswitch_data_0
// 如果defaule代码块中有代码的话,在这里会显示
// default代码块写在此处
.line 84
// 所有case分支的出口
:goto_0
return-void
.line 48
// 从下面的case分支流程表看出,这里是case register按钮id的代码块
:pswitch_0
iget-object v4, p0, Lcom/a3st/demo/MainActivity;->editUsername:Landroid/widget/EditText;
invoke-virtual {v4}, Landroid/widget/EditText;->getText()Landroid/text/Editable;
move-result-object v4
invoke-virtual {v4}, Ljava/lang/Object;->toString()Ljava/lang/String;
move-result-object v3
.line 49
// 表明变量作用域。local到end local之间
// 并明确v3的变量名称为String username
// 也就是String username = v3,但它不是出现在第49行,而是在上一行(48行)代码中,这是smali中需注意的一点
.local v3, "username":Ljava/lang/String;
iget-object v4, p0, Lcom/a3st/demo/MainActivity;->editPassword:Landroid/widget/EditText;
invoke-virtual {v4}, Landroid/widget/EditText;->getText()Landroid/text/Editable;
move-result-object v4
invoke-virtual {v4}, Ljava/lang/Object;->toString()Ljava/lang/String;
move-result-object v1
.line 50
.local v1, "password":Ljava/lang/String;
invoke-virtual {v3}, Ljava/lang/String;->length()I
move-result v4
const/4 v5, 0x4
// 如果v4<v5的话,则跳到cond_0标签处
if-lt v4, v5, :cond_0
invoke-virtual {v3}, Ljava/lang/String;->length()I
move-result v4
const/16 v5, 0xc
// 如果v4<=v5的话,则跳到cond_1标签处
if-le v4, v5, :cond_1
.line 51
// 从utf8编码的字符串中可以看出这是失败信息
:cond_0
const-string v4, "\u7528\u6237\u540d\u4e0d\u80fd\u4e3a\u7a7a \u6216 \u8d85\u8fc7\u6700\u5927\u957f\u5ea6"
invoke-static {p0, v4, v6}, Landroid/widget/Toast;->makeText(Landroid/content/Context;Ljava/lang/CharSequence;I)Landroid/widget/Toast;
move-result-object v4
invoke-virtual {v4}, Landroid/widget/Toast;->show()V
// 跳到所有case分支的出口处
goto :goto_0
.line 54
:cond_1
// 因为是invoke-static,大括号内的参数对应方法中的参数
// 得出源码为: MainActivity.reg(username);
invoke-static {v3}, Lcom/a3st/demo/MainActivity;->reg(Ljava/lang/String;)Ljava/lang/String;
move-result-object v2
.line 55
.local v2, "result":Ljava/lang/String;
invoke-virtual {v2, v1}, Ljava/lang/String;->equals(Ljava/lang/Object;)Z
move-result v4
// v4等于0时,则跳到cond_2标签处
if-eqz v4, :cond_2
// 从utf8编码的字符串看出这里为注册成功
.line 56
new-instance v0, Landroid/app/AlertDialog$Builder;
invoke-direct {v0, p0}, Landroid/app/AlertDialog$Builder;-><init>(Landroid/content/Context;)V
.line 57
.local v0, "msg":Landroid/app/AlertDialog$Builder;
const-string v4, "\u4fe1\u606f\u6846:"
invoke-virtual {v0, v4}, Landroid/app/AlertDialog$Builder;->setTitle(Ljava/lang/CharSequence;)Landroid/app/AlertDialog$Builder;
.line 58
const-string v4, "\u6ce8\u518c\u6210\u529f!"
invoke-virtual {v0, v4}, Landroid/app/AlertDialog$Builder;->setMessage(Ljava/lang/CharSequence;)Landroid/app/AlertDialog$Builder;
.line 59
invoke-virtual {v0, v6}, Landroid/app/AlertDialog$Builder;->setCancelable(Z)Landroid/app/AlertDialog$Builder;
.line 60
const-string v4, "OK"
new-instance v5, Lcom/a3st/demo/MainActivity$1;
invoke-direct {v5, p0}, Lcom/a3st/demo/MainActivity$1;-><init>(Lcom/a3st/demo/MainActivity;)V
invoke-virtual {v0, v4, v5}, Landroid/app/AlertDialog$Builder;->setPositiveButton(Ljava/lang/CharSequence;Landroid/content/DialogInterface$OnClickListener;)Landroid/app/AlertDialog$Builder;
.line 66
invoke-virtual {v0}, Landroid/app/AlertDialog$Builder;->show()Landroid/app/AlertDialog;
goto :goto_0
.line 68
.end local v0 # "msg":Landroid/app/AlertDialog$Builder;
:cond_2
new-instance v0, Landroid/app/AlertDialog$Builder;
invoke-direct {v0, p0}, Landroid/app/AlertDialog$Builder;-><init>(Landroid/content/Context;)V
.line 69
.restart local v0 # "msg":Landroid/app/AlertDialog$Builder;
const-string v4, "\u4fe1\u606f\u6846:"
invoke-virtual {v0, v4}, Landroid/app/AlertDialog$Builder;->setTitle(Ljava/lang/CharSequence;)Landroid/app/AlertDialog$Builder;
.line 70
const-string v4, "\u6ce8\u518c\u5931\u8d25!"
invoke-virtual {v0, v4}, Landroid/app/AlertDialog$Builder;->setMessage(Ljava/lang/CharSequence;)Landroid/app/AlertDialog$Builder;
.line 71
invoke-virtual {v0, v6}, Landroid/app/AlertDialog$Builder;->setCancelable(Z)Landroid/app/AlertDialog$Builder;
.line 72
const-string v4, "OK"
new-instance v5, Lcom/a3st/demo/MainActivity$2;
invoke-direct {v5, p0}, Lcom/a3st/demo/MainActivity$2;-><init>(Lcom/a3st/demo/MainActivity;)V
invoke-virtual {v0, v4, v5}, Landroid/app/AlertDialog$Builder;->setPositiveButton(Ljava/lang/CharSequence;Landroid/content/DialogInterface$OnClickListener;)Landroid/app/AlertDialog$Builder;
.line 78
invoke-virtual {v0}, Landroid/app/AlertDialog$Builder;->show()Landroid/app/AlertDialog;
goto :goto_0
.line 46
:pswitch_data_0
// case分支流程表
// 0x7f070021 表示递增的初始值
.packed-switch 0x7f070021
// pswitch_0指的是0x7f070021(递增的初始值)
// 0x7f070021是一个按钮id
:pswitch_0
.end packed-switch
.end method
@4 switch说明
switch格式:
// 在case分支流程表中查看是否有v4的值,如有则进入case代码块,没有则退出或进入default代码块
packed-switch v4, :pswitch_data_0
// defaule代码块
......
// 所有case分支出口
:goto_0
return-void
//各个case代码块
......
// case分支流程表
:pswitch_data_0
// 因为是packed-switch,所以有个递增的初始值(0x7F070021)
.packed-switch 0x7f070021
// 各个case代码块位置
:pswitch_0
:pswitch_1
:pswitch_2
// switch结束
.end packed-switch在smali中,有packed-switch和sparse-switch二种switch语法(其它的没见到过)
packed-switch 表示紧凑的switch,它的分支流程表以一个递增初始值开始,每个case分支位置渐进递增。常见的就是case 1, case 2, case 3...
sparse-switch 表示稀疏的switch,它的格式为(简单写下,与packed-switch一样,只是名字变了):
sparse-switch v1, :sswitch_data_0
default: code
....
// 所有case分支出口
:goto_0
return-void
// 各个case代码块
:sswitch_0
...
// case分支流程表
.sparse-switch
0x5ec5cc94 -> :sswitch_0
0x5ec5cc96 -> :sswitch_1
0x5ec5cc98 -> :sswitch_2
0x5ec5cc99 -> :sswitch_3
0x5ec5ccb0 -> :sswitch_4
.end sparse-switch可以看出,在分支流程表中,没有了递增初始值
与之取代的是各个case位置都指定了值,可以看出这此值并不是有序的,所以不能用packed-switch
这里就只逆个switch出来吧,其它与前面几节差不多,重点还是讲switch语句
@Override
public void onClick(View v) {
switch(v.getId()) {
case 0x7f070021:
// 代码
String username = editUsername.getText().toString();
......
break;
}
}
0x06 算法函数
此apk的关键函数
// private static String reg(_username)
.method private static reg(Ljava/lang/String;)Ljava/lang/String;
.locals 10
.param p0, "_username" # Ljava/lang/String;
.prologue
.line 31
// char[] username = _username.toCharArray();
// 返回值[C表示char[]
invoke-virtual {p0}, Ljava/lang/String;->toCharArray()[C
move-result-object v4
.line 32
.local v4, "username":[C
// 取数组长度赋值给v1
// 看到第33行中的第一条指令现在大家都知道怎么逆出源码了吧:
// int len = username.length;
array-length v1, v4
.line 33
.local v1, "len":I
// 把0在放到v2中,如超出放到v3中
// winde可能就是把0扩展long型,这时v2,v3都为0
// long result = 0
const-wide/16 v2, 0x0
.line 34
.local v2, "result":J
// i = 0
const/4 v0, 0x0
.local v0, "i":I
// 循环开始
:goto_0
// 当 i>= len 时,跳出循环
if-ge v0, v1, :cond_1
.line 35
// 读取数组元素: v5 = username[i]
aget-char v5, v4, v0
const/16 v6, 0x61
// 如果 v5<'a'',则跳到cond_0标签处
if-lt v5, v6, :cond_0
.line 36
aget-char v5, v4, v0
// v5 -= 0x20
add-int/lit8 v5, v5, -0x20
// 将整形转换成字符型
int-to-char v5, v5
// 把结果保存到username[i]中
aput-char v5, v4, v0
.line 38
:cond_0
aget-char v5, v4, v0
// 转换成long型
int-to-long v6, v5
// result += username[i]
add-long/2addr v2, v6
.line 34
// i++
add-int/lit8 v0, v0, 0x1
// 继续循环
goto :goto_0
.line 40
:cond_1
const-wide/16 v6, 0x2
// v6 *= result
mul-long/2addr v6, v2
const-wide/16 v8, -0x1
// result = v6 & 0xFFFFFFFF
and-long v2, v6, v8
.line 41
// 注意,这里不是二个参数,而是一个,因为是long型
// 前面(const-wide/16 v2, 0x0)时有详细说过
// Long.toHexString(result);
invoke-static {v2, v3}, Ljava/lang/Long;->toHexString(J)Ljava/lang/String;
move-result-object v5
// v5.toUpperCase();
invoke-virtual {v5}, Ljava/lang/String;->toUpperCase()Ljava/lang/String;
move-result-object v5
// 返回一个字符串
return-object v5
.end method
整理下:
此节我是琢行分析的,逆出后与源码对比下,完美
private static String reg(_username) {
char[] username = _username.toCharArray(); // .line31
int len = usernema.length; // .line32
long result = 0; // .line33
for(int i = 0; i < len; i++) { // .line34
if(username[i] >= 'a') { // .line35
username[i] -= 0x20; // .line36
} // .line37,相当于一个空行
result += username[i]; // .line38
} // .line39
result = (2 * result) & 0xFFFFFFFF; // .line40
return Long.toHexString(result).toUpperCase(); // .line41
}
0x07 修改函数
既然使用CrackMe来进行演示,当然要有个CrackMe的样子
修改方法有二种:
- 更改跳转语句的流程
- 修改关键函数reg的返回值
本人用第二种方法进行修改:
在0x05中节可能看到取password与reg函数的返回值进行比较
本人就把0x06节中的第.line41行所有代码删除,并修改返回值为字符串123:
.line 41
const string v5, "\u0031\u0032\u0033"
return-object v5
对修改后的smali进行编译,修改后的结果如下
图1
网友评论