新手一枚,为论坛增加学习氛围。帮同学找apk的注册码,发现其中的一些东西还是很值得分享的。
新人一枚。帮同学找apk的注册码,发现其中的一些东西还是很值得分享的。
首先,运行Crackme001.apk获取信息,了解大致运行过程。
将其安装到andorid虚拟机上,点击运行,发现闪退,无法运行,估计可能有保护机制。于是只能放到手机上去运行:
输入用户名和注册码,点击注册后没有反应。
利用apktool进行反编译。命令行中输入apktool d Crackme001.apk后,得到反编译文件。
打开MainActivity.smali文件,从onCreate函数看起:
.method protected onCreate(Landroid/os/Bundle;)V
.locals 1
.parameter "savedInstanceState"
.prologue
.line 20
invoke-super {p0, p1}, Landroid/app/Activity;->onCreate(Landroid/os/Bundle;)V
.line 21
const/high16 v0, 0x7f03
invoke-virtual {p0, v0}, Lcom/syclover/crackme001/MainActivity;->setContentView(I)V
.line 24
invoke-static {}, Lcom/syclover/crackme001/MainActivity;->hasQEmuFiles()Z
move-result v0
if-eqz v0, :cond_0 #等于0则不进行killProcess,否则killProcess
.line 25
invoke-static {}, Landroid/os/Process;->myPid()I
move-result v0
invoke-static {v0}, Landroid/os/Process;->killProcess(I)V #终结进程
.line 28
:cond_0
return-void
.end method
发现其中会调用hasQEmuFiles()函数,对其返回值进行判断,若返回非0,则终结进程,这说明hasQEmuFiles()函数是保护机制中的关键。现在来分析一下hasQEmuFiles(),过程如下:
#此函数用来初始化静态类成员know_file(字符串数组)
# direct methods
.method static constructor <clinit>()V
.locals 3
.prologue
.line 30
const/4 v0, 0x3
new-array v0, v0, [Ljava/lang/String;
const/4 v1, 0x0
.line 31
const-string v2, "/system/lib/libc_malloc_debug_qemu.so" #第一个数组元素为“/system/lib/libc_malloc_debug_qemu.so”
aput-object v2, v0, v1
const/4 v1, 0x1
const-string v2, "/sys/qemu_trace" #第二个数组元素为“/sys/qemu_trace”
aput-object v2, v0, v1
const/4 v1, 0x2
.line 32
const-string v2, "/system/bin/qemu-props" #第三个数组元素"/system/bin/qemu-props"
aput-object v2, v0, v1
.line 30
sput-object v0, Lcom/syclover/crackme001/MainActivity;->known_file:[Ljava/lang/String;
.line 32
return-void
.end method
#通过检测特定的文件是否存在于文件系统中来判断是否在android虚拟机中
.method public static hasQEmuFiles()Z
.locals 7
.prologue
const/4 v2, 0x0
.line 35
sget-object v4, Lcom/syclover/crackme001/MainActivity;->known_file:[Ljava/lang/String; #获取存放文件名的静态字符串数组
array-length v5, v4 #求得数组长度,存放在v5中
move v3, v2
.local v0, pipe:Ljava/lang/String;
.local v1, qemu_files:Ljava/io/File;
:goto_0
if-lt v3, v5, :cond_0 #小于则跳转到cond_0继续执行
.line 41
:goto_1
return v2 #若有一个文件存在于文件系统,返回1,说明程序运行在android虚拟机中;若文件均不存在,返回0,说明程序运行在真机中
.line 35
:cond_0
aget-object v0, v4, v3 #读出文件名数组中的第V3个元素,放到V0中
.line 36
new-instance v1, Ljava/io/File;
.end local v1 #qemu_files:Ljava/io/File;
invoke-direct {v1, v0}, Ljava/io/File;-><init>(Ljava/lang/String;)V #利用v0中的文件名字符串初始化File对象
.line 37
.restart local v1 #qemu_files:Ljava/io/File;
invoke-virtual {v1}, Ljava/io/File;->exists()Z #查找文件系统中是否存在相应的文件,若存在,返回true,否则返回false
move-result v6
if-eqz v6, :cond_1 #不存在则跳转到cond_1
.line 38
const/4 v2, 0x1
goto :goto_1 #存在,跳转到goto_1(继而返回1)
.line 35
:cond_1
add-int/lit8 v3, v3, 0x1
goto :goto_0
.end method
这样,从hasQEmuFiles()函数里可以发现,程序通过检查运行环境的文件系统中是否存在:
1. /system/lib/libc_malloc_debug_qemu.so
2. /sys/qemu_trace
3. /system/bin/qemu-props
这三个文件来判断运行环境是否是android虚拟机,若至少存在其中一个文件,则说明运行环境是android虚拟机,而非真机。
我自己去查看了一下android虚拟机里头的文件,发现确实是有这几个文件:
我觉得这个保护机制还是挺值得借鉴的。
分析完了保护机制,再来继续找注册码。
在MainActivity.smali文件中发现了关键函数,分析如下:
# virtual methods
.method public OnMySelfClick(Landroid/view/View;)V
.locals 6
.parameter "v"
.prologue
.line 46
const v3, 0x7f080002
invoke-virtual {p0, v3}, Lcom/syclover/crackme001/MainActivity;->findViewById(I)Landroid/view/View;
move-result-object v2 #获取username的EditText对象
check-cast v2, Landroid/widget/EditText;
.line 47
.local v2, username:Landroid/widget/EditText;
const v3, 0x7f080003
invoke-virtual {p0, v3}, Lcom/syclover/crackme001/MainActivity;->findViewById(I)Landroid/view/View;
move-result-object v0 #获取password的EditText对象
check-cast v0, Landroid/widget/EditText;
.line 50
.local v0, password:Landroid/widget/EditText;
:try_start_0
invoke-virtual {v2}, Landroid/widget/EditText;->getText()Landroid/text/Editable;
move-result-object v3
invoke-interface {v3}, Landroid/text/Editable;->toString()Ljava/lang/String;
move-result-object v3 #获取username字符串放在v3
.line 51
invoke-virtual {v2}, Landroid/widget/EditText;->getText()Landroid/text/Editable;
move-result-object v4
invoke-interface {v4}, Landroid/text/Editable;->toString()Ljava/lang/String;
move-result-object v4 #获取username字符串放在v4
.line 50
invoke-static {v3, v4}, Lcom/syclover/crackme001/DES;->encode(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;
move-result-object v3 #调用DES类中的encode()进行加密,返回值存放在v3
.line 51
const/4 v4, 0x0
const/4 v5, 0x6
invoke-virtual {v3, v4, v5}, Ljava/lang/String;->substring(II)Ljava/lang/String;
move-result-object v1 #截取v3中字符串的0~6部分存放在v1
.line 54
.local v1, pwdString:Ljava/lang/String;
invoke-virtual {v0}, Landroid/widget/EditText;->getText()Landroid/text/Editable;
move-result-object v3
invoke-interface {v3}, Landroid/text/Editable;->toString()Ljava/lang/String;
move-result-object v3 #获取password字符串
.line 55
invoke-static {}, Ljava/util/Locale;->getDefault()Ljava/util/Locale;
move-result-object v4 #获取locale
invoke-virtual {v1, v4}, Ljava/lang/String;->toLowerCase(Ljava/util/Locale;)Ljava/lang/String; #toLowerCase的功能:Converts this string to lower case, using the rules of locale.
move-result-object v4 #结果存放在v4
invoke-virtual {v3, v4}, Ljava/lang/String;->equals(Ljava/lang/Object;)Z #关键,比较v3和v4是否相等
move-result v3
if-eqz v3, :cond_0 #不相等则跳转到cond_0
.line 56
const-string v3, "\u6ce8\u518c\u6210\u529f"
const/4 v4, 0x0
invoke-static {p0, v3, v4}, Landroid/widget/Toast;->makeText(Landroid/content/Context;Ljava/lang/CharSequence;I)Landroid/widget/Toast;
move-result-object v3
.line 57
invoke-virtual {v3}, Landroid/widget/Toast;->show()V #显示注册成功
:try_end_0
.catch Ljava/lang/Exception; {:try_start_0 .. :try_end_0} :catch_0
.line 63
.end local v1 #pwdString:Ljava/lang/String;
:cond_0
:goto_0
return-void
分析完成后,为了获取到注册码,我想可以利用代码注入法将正确的注册码输出来,但是在重新编译时候出了一些问题,感觉这个方法行不通。考虑之后自己决定顺着apk的思路来,写出它的注册机。
要写注册机,就得对注册码的生成过程进行还原,我想这时可以利用dex2jar将apk反编译得到java源码,看java源码来写apk定是极好的。
用jd-gui将.jar打开,找到关键部分:(感觉好简洁)
除此之外,DES类中的相关函数也是必要的:
所有的函数都在这儿了,只需要粘贴复制,修修改改,就可以写出一个生成注册码的apk来了:
测试发现,这个注册码的生成还是有缺陷的:
用户名至少得8位,而且注册码只和用户名的前八位有关联,和后面的位均无关。
求各位看雪朋友交流,共同进步!
[课程]FART 脱壳王!加量不加价!FART作者讲授!
上传的附件: