查看原文
其他

smali学习笔记之四种方法破解一个简单creakme

lcdxsun 看雪学院 2019-05-25


概述


自己以前学习Android逆向的一个笔记,虽然超级简单,但是涉及到smali汇编,java层逆向的思路以及Xposed hook的编写,log等浅显知识,适合零基础以及刚学Android的同学看。



工具


Androidkiller、Androidstudio、夜神模拟器、pycharm



分析过程


安装到模拟器查看提示信息,我们看到这里提示无效用户名或注册码:



androidkiller中反编译为smali


搜索字符串,定位关键点。


先搜索无效用户名或注册码如果搜索不到可以转为Unicode再搜索,这里直接能搜到,而且只有一处。


 

再去搜索unsuccessed


 

我们看到这个String类型的引用ID是0x7f05000b


 

我们看看哪里引用了这个ID 所以我们再搜索 0x7f05000b发现只有一个地方引用。

 

 

我们看这个引用的地方时在哪个方法中,我们发现是在OnClick方法中。

 

我们看这个类中有哪些方法:


 

我们猜测 checkSN就是关键方法。



关键代码分析


我们进到里面分析,我在里面做了详细的注释,可以看一下。由于我们是为了学习smali,所以这里就不编译成java代码了。


我们需要一条一条的看smali汇编:


.method private checkSN(Ljava/lang/String;Ljava/lang/String;)Z
.locals 10 # 用于本地的寄存器有10个
.param p1, "userName" # Ljava/lang/String;第一个参数是用户名
.param p2, "sn" # Ljava/lang/String;第二个参数是注册码

.prologue # 真正代码开始的地方
const/4 v7, 0x0 # 常量 v7=0

.line 45
if-eqz p1, :cond_0 # 判断userName是不是空

:try_start_0
invoke-virtual {p1}, Ljava/lang/String;->length()I #求userName的长度

move-result v8 #v8=长度

if-nez v8, :cond_1 #判断长度是否为0 为0则退出

.line 69
:cond_0
:goto_0
return v7 # 返回v7

.line 47
:cond_1
if-eqz p2, :cond_0 #判断sn是否为空

invoke-virtual {p2}, Ljava/lang/String;->length()I #判断sn的长度是否0

move-result v8

const/16 v9, 0x10 #常量v9=16

if-ne v8, v9, :cond_0 #sn长度不为16 则退出

.line 49
const-string v8, "MD5" #字符串常量v8="MD5"

invoke-static {v8}, Ljava/security/MessageDigest;->getInstance(Ljava/lang/String;)Ljava/security/MessageDigest;

move-result-object v1 # MessageDigest对象v1=MessageDigest.getinstance(“MD5”)

.line 50
.local v1, "digest":Ljava/security/MessageDigest;
invoke-virtual {v1}, Ljava/security/MessageDigest;->reset()V #MessageDigest对象重置

.line 51
invoke-virtual {p1}, Ljava/lang/String;->getBytes()[B # 将此String使用指定的字符集的字节序列,
# 并将结果存储到一个新的字节数组
move-result-object v8 #将p1存入v8中

invoke-virtual {v1, v8}, Ljava/security/MessageDigest;->update([B)V

.line 52
invoke-virtual {v1}, Ljava/security/MessageDigest;->digest()[B #MessageDigest对象设置为初始状态

move-result-object v0

.line 53
.local v0, "bytes":[B
const-string v8, ""

invoke-static {v0, v8}, Lcom/droider/crackme0201/MainActivity;->toHexString([BLjava/lang/String;)Ljava/lang/String;
# 转为16进制的字符串形式
move-result-object v3 #返回转换后的字符串

.line 54
.local v3, "hexstr":Ljava/lang/String;
new-instance v5, Ljava/lang/StringBuilder; #实例化一个字符串对象

invoke-direct {v5}, Ljava/lang/StringBuilder;-><init>()V #字符串对象初始化

.line 55
.local v5, "sb":Ljava/lang/StringBuilder;
const/4 v4, 0x0 #v4=0

.local v4, "i":I #v4命名为i
:goto_1
invoke-virtual {v3}, Ljava/lang/String;->length()I

move-result v8 #v8=v3.length()转化后的字符串长度

if-lt v4, v8, :cond_2 #v4<v3.length()

.line 58
invoke-virtual {v5}, Ljava/lang/StringBuilder;->toString()Ljava/lang/String;

move-result-object v6 #v6=v5转的字符串

.line 60
.local v6, "userSN":Ljava/lang/String;
const-string v8, "TAG"

invoke-static {v8, v6}, Landroid/util/Log;->e(Ljava/lang/String;Ljava/lang/String;)I #打印TAG:v6中的内容

.line 63
invoke-virtual {v6, p2}, Ljava/lang/String;->equalsIgnoreCase(Ljava/lang/String;)Z #字符串比较不考虑大小写

move-result v8

if-eqz v8, :cond_0 #v8等于0则字符串相等 不等于0退出

.line 69
const/4 v7, 0x1 #返回值 v7=1 也就是返回true

goto :goto_0

.line 56
.end local v6 # "userSN":Ljava/lang/String;
:cond_2
invoke-virtual {v3, v4}, Ljava/lang/String;->charAt(I)C

move-result v8 #v8=v3.charAt(v4) 取v3中的第v4个字符存放到v8中

invoke-virtual {v5, v8}, Ljava/lang/StringBuilder;->append(C)Ljava/lang/StringBuilder; #v5追加v8里的字符
:try_end_0
.catch Ljava/security/NoSuchAlgorithmException; {:try_start_0 .. :try_end_0} :catch_0

.line 55
add-int/lit8 v4, v4, 0x2 #v4+=2

goto :goto_1

.line 65
.end local v0 # "bytes":[B
.end local v1 # "digest":Ljava/security/MessageDigest;
.end local v3 # "hexstr":Ljava/lang/String;
.end local v4 # "i":I
.end local v5 # "sb":Ljava/lang/StringBuilder;
:catch_0
move-exception v2

.line 66
.local v2, "e":Ljava/security/NoSuchAlgorithmException;
invoke-virtual {v2}, Ljava/security/NoSuchAlgorithmException;->printStackTrace()V

goto :goto_0
.end method


我们这里补充一下其中用到的一些知识点:


  • java.security.MessageDigest:

https://www.cnblogs.com/mengfanrong/p/3896447.html


  • java中的String,StringBuilder,StringBuffer三者的区别:

https://www.cnblogs.com/su-feng/p/6659064.html

 

我们从中发现一个toHexString方法,我们进去看看里面详细的smali代码:


.method private static toHexString([BLjava/lang/String;)Ljava/lang/String;
.locals 7
.param p0, "bytes" # [B
.param p1, "separator" # Ljava/lang/String;

.prologue
.line 101
new-instance v2, Ljava/lang/StringBuilder; #创建实例

invoke-direct {v2}, Ljava/lang/StringBuilder;-><init>()V #实例初始化

.line 102
.local v2, "hexString":Ljava/lang/StringBuilder;
array-length v4, p0 #计算参数p0的长度并放入v4中

const/4 v3, 0x0 #v3=0

 :goto_0
if-lt v3, v4, :cond_0 #if(v3<v4)

.line 109
invoke-virtual {v2}, Ljava/lang/StringBuilder;->toString()Ljava/lang/String;

move-result-object v3

return-object v3 #返回v3 也就是转变后的宽字符串

.line 102
 :cond_0
aget-byte v0, p0, v3 #v3位置的p0的字节放入v0中 ,这里p0是个array,v3是index
#aget-byte vx,vy,vz Gets a byte value of a byte array into vx. The array is referenced by vy and is indexed by vz.
.line 103
.local v0, "b":B
and-int/lit16 v5, v0, 0xff #v5=v0&0xff 取一个字节

invoke-static {v5}, Ljava/lang/Integer;->toHexString(I)Ljava/lang/String; #16进制整数转为字符串

move-result-object v1 #将转化完的字符串放入v1中

.line 104
.local v1, "hex":Ljava/lang/String;
invoke-virtual {v1}, Ljava/lang/String;->length()I #求字符串的长度

move-result v5 #长度放入v5中

const/4 v6, 0x1 #v6=1

if-ne v5, v6, :cond_1 #if(v5!=v6) #如果是窄字节 则前面加0

.line 105
const/16 v5, 0x30 #v5=0x30

invoke-virtual {v2, v5}, Ljava/lang/StringBuilder;->append(C)Ljava/lang/StringBuilder; #加0

.line 107
 :cond_1 #加0后追加v1
invoke-virtual {v2, v1}, Ljava/lang/StringBuilder;->append(Ljava/lang/String;)Ljava/lang/StringBuilder;

move-result-object v5 #合成的字符串放入v5中

invoke-virtual {v5, p1}, Ljava/lang/StringBuilder;->append(Ljava/lang/String;)Ljava/lang/StringBuilder;

.line 102
add-int/lit8 v3, v3, 0x1 #v3++

goto :goto_0
.end method


我们这里梳理下登录的验证过程:

① 判断用户名是否为空和长度是否为0

② 判断注册码是否为空和长度为0

③ 求用户名的MD5值

④ 将MD5转为宽字节字符串

⑤ 将MD5的每隔1个取出作为注册码



解题思路


1. 爆破


第一种爆破:改判断的地方,比如长度,字符串等。

第二种爆破:将checkSn的返回值改为1 也就是true

都比较简单 这里不再赘述,有兴趣的可以自己试一试


2. smali注入log或者toast进行追码


插入log


 


插入toast




3. xposed hook


基本步骤

① 手机或者模拟器端安装框架

② Android studio 导入xposed模块

③ 配置xml清单

④ 编写代码

⑤ 设置入口类


代码


package com.example.sun.myapplication;

import de.robv.android.xposed.IXposedHookLoadPackage;
import de.robv.android.xposed.XC_MethodHook;
import de.robv.android.xposed.XposedHelpers;
import de.robv.android.xposed.callbacks.XC_LoadPackage;

public class Xposed implements IXposedHookLoadPackage {
@Override
public void handleLoadPackage(XC_LoadPackage.LoadPackageParam loadPackageParam) throws Throwable {
// 判断包名.这里注意是.而不是 /
if(!loadPackageParam.packageName.equals("com.droider.crackme0201"))
return;
XC_MethodHook.Unhook andHookMethod = XposedHelpers.findAndHookMethod(
"com.droider.crackme0201.MainActivity",
loadPackageParam.classLoader,
"checkSN",
String.class,
String.class,
new XC_MethodHook() {
@Override
protected void afterHookedMethod(MethodHookParam param) throws Throwable {
super.afterHookedMethod(param);
// 设置返回值为True
param.setResult(true);
}
});
}
}


4. Python注册机


import os
import binascii
import hashlib

def getMD5(str):
hash = hashlib.md5()
hash.update(bytes(str, encoding='utf-8'))
return hash.hexdigest()

def check(username):
md = getMD5(username)
md_str = ""
for index in range(0, len(md), 2):
md_str = md_str + md[index]
return md_str

if __name__ == '__main__':
username = input("请输入要注册的用户名:")
print("该用户的注册码是:%s"%check(username))





- End -





看雪IDlcdxsun          

https://bbs.pediy.com/user-768906.htm



本文由看雪论坛 Icdxsun 原创

转载请注明来自看雪社区



热门图书推荐

立即购买!



热门文章阅读

1、Linux内核fuzz技术——trinity

2、二进制加壳原理与实现

3、CVE-2017-13772分析及mips栈溢出利用总结

4、早鸟票正在热售中!看雪 2019 SDC 暑假北京见!




公众号ID:ikanxue

官方微博:看雪安全

商务合作:wsc@kanxue.com



↙点击下方“阅读原文”,查看更多干货!

    您可能也对以下帖子感兴趣

    文章有问题?点此查看未经处理的缓存