上次在其他论坛找了几个入门级别的Android CrackMe,这个是第二个,后面还会继续分析剩下的几个。
运行截图:

首先使用apktool将apk进行反编译,看了下程序清单文件,程序启动后显示运行EX05_01类

到反编译好的smali文件中,打开EX05_01.smali

为了学习和熟悉smali语法,我把EX05_01.smali 和 它的一个匿名类EX05_01$1.smali从头到尾都做了注释,注释如下:
EX05_01.smali的注释
//当前类
.class public Lirdc/ex05_01/EX05_01;
//当前类的父类
.super Landroid/app/Activity;
//当前类所在的源码文件
.source "EX05_01.java"
//当前类的普通数据成员
# instance fields
//有一个私有数据成员,名为mEditText01,类型为EditText对象引用
.field private mEditText01:Landroid/widget/EditText;
//有一个私有数据成员,名为mTextView01,类型为TextView对象引用
.field private mTextView01:Landroid/widget/TextView;
//当前类的直接方法
# direct methods
//无参构造
.method public constructor <init>()V
//该方法寄存器变量个数0
.locals 0
//该方法代码开始
.prologue
.line 10
//调用父类构造
invoke-direct {p0}, Landroid/app/Activity;-><init>()V
return-void
.end method
//当前类的静态方法,看样子像是编译器自动生成的
//为了保证匿名类或内部类访问外部累的私有成员
//一个参数,EX05_01类的对象引用,返回值为TextView对象引用
//返回值为TextView对象引用
.method static synthetic access$0(Lirdc/ex05_01/EX05_01;)Landroid/widget/TextView;
//该方法寄存器变量个数1
.locals 1
//该方法代码开始
.prologue
.line 12
//获取普通数据成员到v0, p0为this指针(参数传递进来的)
//数据成员名为mTextView01,类型为TextView对象引用
iget-object v0, p0, Lirdc/ex05_01/EX05_01;->mTextView01:Landroid/widget/TextView;
//返回获取的对象引用
return-object v0
.end method
//当前类的普通方法
# virtual methods
//有一个公有方法,名为onCreate,有一个参数,类型为Bundle对象引用,无返回值
.method public onCreate(Landroid/os/Bundle;)V
//该方法寄存器变量个数为2
.locals 2
//该方法第一个参数p1变量名为savedInstanceState,类型为Bundle对象引用
.param p1, "savedInstanceState" # Landroid/os/Bundle;
//该方法代码开始
.prologue
.line 18
//调用父类Activity的onCreate方法,参数为Bundle对象引用,p0为this指针,无返回值
invoke-super {p0, p1}, Landroid/app/Activity;->onCreate(Landroid/os/Bundle;)V
.line 19
//寄存器赋值 v0 = 0x7f030000
const/high16 v0, 0x7f030000
//调用EX05_01类的setContentView方法,参数为int,无返回值,p0为this指针
invoke-virtual {p0, v0}, Lirdc/ex05_01/EX05_01;->setContentView(I)V
.line 20
//寄存器赋值 v0 = 0x7f050000
const/high16 v0, 0x7f050000
//调用EX05_01类的findViewById方法,参数为int,返回值为View对象引用
invoke-virtual {p0, v0}, Lirdc/ex05_01/EX05_01;->findViewById(I)Landroid/view/View;
//获取返回值到v0
move-result-object v0
//类型转换成TextView对象引用
//v0 = (TextView)v0
check-cast v0, Landroid/widget/TextView;
//设置普通数据成员,名为mTextView01,类型为TextView对象引用
//mTextView01 = v0
iput-object v0, p0, Lirdc/ex05_01/EX05_01;->mTextView01:Landroid/widget/TextView;
.line 21
//寄存器赋值 v0 = 0x7f050002
const v0, 0x7f050002
//调用EX05_01类的findViewById方法,参数为int,返回值为View对象引用
invoke-virtual {p0, v0}, Lirdc/ex05_01/EX05_01;->findViewById(I)Landroid/view/View;
//获取返回值到v0
move-result-object v0
//类型转换成EditText对象引用
//v0 = (EditText)v0
check-cast v0, Landroid/widget/EditText;
//设置普通数据成员,名为mEditText01,类型为EditText对象引用
//mEditText01 = v0
iput-object v0, p0, Lirdc/ex05_01/EX05_01;->mEditText01:Landroid/widget/EditText;
.line 22
//获取普通数据成员,名为mEditText01,类型为EditText对象引用
//v0 = mEditText01
iget-object v0, p0, Lirdc/ex05_01/EX05_01;->mEditText01:Landroid/widget/EditText;
//new 匿名类
new-instance v1, Lirdc/ex05_01/EX05_01$1;
//匿名类构造,参数为外部类(EX05_01)this指针,无返回值
//编译器自动生成的,为了保证匿名类可以访问外部类的私有成员
invoke-direct {v1, p0}, Lirdc/ex05_01/EX05_01$1;-><init>(Lirdc/ex05_01/EX05_01;)V
//调用EditText类的setOnKeyListener方法,参数为View$OnKeyListener对象引用,无返回值
//v0为this指针,v1为参数
//v0 = mEditText01
invoke-virtual {v0, v1}, Landroid/widget/EditText;->setOnKeyListener(Landroid/view/View$OnKeyListener;)V
.line 44
//函数退出,无返回值
return-void
.end method
EX05_01$1.smali的注释
//当前类
.class Lirdc/ex05_01/EX05_01$1;
//当前类的父类
.super Ljava/lang/Object;
//当前类所在的源码文件
.source "EX05_01.java"
//接口注释
# interfaces
.implements Landroid/view/View$OnKeyListener;
//注释
# annotations
.annotation system Ldalvik/annotation/EnclosingMethod;
value = Lirdc/ex05_01/EX05_01;->onCreate(Landroid/os/Bundle;)V
.end annotation
//匿名类 name = null
.annotation system Ldalvik/annotation/InnerClass;
accessFlags = 0x0
name = null
.end annotation
//编译器自动添加了一个数据成员为外部类的this指针
//外部类 ==> EX05_01
# instance fields
.field final synthetic this$0:Lirdc/ex05_01/EX05_01;
//该类的直接方法(构造或私有方法)
# direct methods
//编译器自动生成的构造函数
.method constructor <init>(Lirdc/ex05_01/EX05_01;)V
.locals 0
.prologue
.line 1
//保存外部类的this指针
iput-object p1, p0, Lirdc/ex05_01/EX05_01$1;->this$0:Lirdc/ex05_01/EX05_01;
.line 22
//调用父类构造
invoke-direct {p0}, Ljava/lang/Object;-><init>()V
return-void
.end method
//当前类的普通成员方法
# virtual methods
//有一个公有的成员方法,名为onKey,返回值为boolean
//该方法第一个参数为View的对象引用
//第二个参数为int
//第三个参数为KeyEvent的对象引用
.method public onKey(Landroid/view/View;ILandroid/view/KeyEvent;)Z
//该方法寄存器个数4
.locals 4
//该方法第一个参数p1的变量名为arg0,类型为View的对象引用
.param p1, "arg0" # Landroid/view/View;
//该方法第二个参数p2的变量名为arg1,类型为int
.param p2, "arg1" # I
//该方法第三个参数p3的变量名为arg3,类型为KeyEvent的对象引用
.param p3, "arg2" # Landroid/view/KeyEvent;
//该方法代码开始
.prologue
//寄存器赋值 v3 = 1
const/4 v3, 0x1
.line 28
//寄存器赋值 v0 = "gogo"
const-string v0, "gogo"
.line 30
//寄存器v0的变量名为str,类型为String对象引用
.local v0, "str":Ljava/lang/String;
//寄存器赋值v1 = "11"
const-string v1, "11"
//调用String类的equals成员方法,参数为Object对象引用,返回值为boolean
//v0 == this指针
//v1 == 参数
invoke-virtual {v0, v1}, Ljava/lang/String;->equals(Ljava/lang/Object;)Z
//获取返回值到寄存器v1
move-result v1
[COLOR="Red"]
//条件判断,当v1 == 0时,跳转到cond_0标号处
if-eqz v1, :cond_0
//此处改成 if-nez v1, :cond_0 即可成功
[/COLOR]
.line 31
//获取外部类的this指针保存到v1寄存器
iget-object v1, p0, Lirdc/ex05_01/EX05_01$1;->this$0:Lirdc/ex05_01/EX05_01;
# getter for: Lirdc/ex05_01/EX05_01;->mTextView01:Landroid/widget/TextView;
//调用EX05_01类的access$0静态方法,参数为EX05_01对象引用,返回值为TextView对象引用
//v1 == 参数,静态方法无需this指针
invoke-static {v1}, Lirdc/ex05_01/EX05_01;->access$0(Lirdc/ex05_01/EX05_01;)Landroid/widget/TextView;
//获取返回值到v1寄存器
move-result-object v1
//调用TextView类的setText方法,参数为CharSequence对象引用,无返回值
//v1 == this指针
//v0 == 参数
invoke-virtual {v1, v0}, Landroid/widget/TextView;->setText(Ljava/lang/CharSequence;)V
.line 32
//获取外部类的this指针到寄存器v1
iget-object v1, p0, Lirdc/ex05_01/EX05_01$1;->this$0:Lirdc/ex05_01/EX05_01;
//寄存器赋值v2 = "right++"
const-string v2, "right++"
//调用Toast的makeText静态方法,返回值为Toast对象引用
//第一个参数为Context对象引用 v1
//第二个参数为CharSequence对象引用 v2
//第三个参数为int v3
invoke-static {v1, v2, v3}, Landroid/widget/Toast;->makeText(Landroid/content/Context;Ljava/lang/CharSequence;I)Landroid/widget/Toast;
//获取返回值到寄存器v1
move-result-object v1
//调用Toast类的show方法,无返回值
//v1 == this指针
invoke-virtual {v1}, Landroid/widget/Toast;->show()V
.line 40
//标号,用于goto到此标号
:goto_0
//获取外部类this指针到寄存器v1
iget-object v1, p0, Lirdc/ex05_01/EX05_01$1;->this$0:Lirdc/ex05_01/EX05_01;
# getter for: Lirdc/ex05_01/EX05_01;->mTextView01:Landroid/widget/TextView;
//调用EX05_01类的access$0静态方法,参数为EX05_01对象引用,返回值为TextView对象引用
//v1 == 参数,静态方法无需this指针
invoke-static {v1}, Lirdc/ex05_01/EX05_01;->access$0(Lirdc/ex05_01/EX05_01;)Landroid/widget/TextView;
//获取返回值到v1寄存器
move-result-object v1
//寄存器赋值 v2 = 7
const/4 v2, 0x7
//调用Linkify类的addLinks静态方法,返回值为boolean
//第一个参数为TextView对象引用
//第二个参数为int
invoke-static {v1, v2}, Landroid/text/util/Linkify;->addLinks(Landroid/widget/TextView;I)Z
.line 41
//寄存器赋值v1 = 0
const/4 v1, 0x0
//函数退出,返回值为v1
return v1
.line 35
//标号,用于goto到此标号
:cond_0
//获取外部类this指针到寄存器v1
iget-object v1, p0, Lirdc/ex05_01/EX05_01$1;->this$0:Lirdc/ex05_01/EX05_01;
# getter for: Lirdc/ex05_01/EX05_01;->mTextView01:Landroid/widget/TextView;
//调用EX05_01类的access$0静态方法,参数为EX05_01对象引用,返回值为TextView对象引用
//v1 == 参数,静态方法无需this指针
invoke-static {v1}, Lirdc/ex05_01/EX05_01;->access$0(Lirdc/ex05_01/EX05_01;)Landroid/widget/TextView;
//获取返回值到v1寄存器
move-result-object v1
//调用TextView类的setText方法,参数为CharSequence对象引用,无返回值
//v1 == this指针
//v0 == 参数
invoke-virtual {v1, v0}, Landroid/widget/TextView;->setText(Ljava/lang/CharSequence;)V
.line 36
//获取外部类this指针到寄存器v1
iget-object v1, p0, Lirdc/ex05_01/EX05_01$1;->this$0:Lirdc/ex05_01/EX05_01;
//寄存器赋值 v2 = "error--"
const-string v2, "error--"
//调用Toast的makeText静态方法,返回值为Toast对象引用
//第一个参数为Context对象引用 v1
//第二个参数为CharSequence对象引用 v2
//第三个参数为int v3
invoke-static {v1, v2, v3}, Landroid/widget/Toast;->makeText(Landroid/content/Context;Ljava/lang/CharSequence;I)Landroid/widget/Toast;
//获取返回值到v1寄存器
move-result-object v1
//调用Toast类的show方法,无返回值
//v1 == this指针
invoke-virtual {v1}, Landroid/widget/Toast;->show()V
//无条件跳转到goto_0标号处
goto :goto_0
.end method
通过对smali代码的阅读,模拟还原了下这个CrackMe的算法C代码:
#include <string.h>
#include <stdio.h>
int main(int argc, char* argv[])
{
if (strcmp("11", "gogo") == 0)
{
puts("ok");
}
else
{
puts("error");
}
return 0;
}
通过这个CrackMe的算法可以看到,不管输入任何值,最终都是错误,程序中使用的是两个固定的字符串做比较,所以只能通过修改smali中的条件判断达到爆破的目的(修改位置已在注释中加亮)。
附一张注册成功截图:
由于刚开始学习,如果有写的不对的地方,希望大家可以告诉我一下,谢谢。附件中打包了apk和注释的.smali文件。
CrackMe.apk.rar
smali.rar
[培训]科锐逆向工程师培训第53期2025年7月8日开班!