首页
论坛
专栏
课程

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

2019-4-10 17:45 1282

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

2019-4-10 17:45
1282

目录

概述

自己以前学习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

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

  1. 判断用户名是否为空和长度是否为0
  2. 判断注册码是否为空和长度为0
  3. 求用户名的MD5值
  4. 将MD5转为宽字节字符串
  5. 将MD5的每隔1个取出作为注册码

解题思路

1. 爆破

第一种爆破:改判断的地方,比如长度,字符串等。
第二种爆破:将checkSn的返回值改为1 也就是true
都比较简单 这里不再赘述,有兴趣的可以自己试一试

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

插入log

图片描述

 

图片描述

插入toast

图片描述
图片描述

3. xposed hook

基本步骤

  1. 手机或者模拟器端安装框架
  2. android studio 导入xposed模块
  3. 配置xml清单
  4. 编写代码
  5. 设置入口类

代码

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))

图片描述



[推荐]看雪企服平台,提供安全分析、定制项目开发、APP等级保护、渗透测试等安全服务!

上传的附件:
上一主题 下一主题
最新回复 (10)
youxiaxy 2019-4-10 19:23
2
0
我居然看懂了
joker陈 2019-4-11 11:32
3
0
确实好文章,支持下。
如斯咩咩咩 2019-4-11 12:54
4
0
妙啊。
cmputer 2019-4-11 13:07
5
0
通俗易懂,帮助新手,手把手教学
fireflyoso 2019-4-11 14:41
6
0
emmm很好的东西,插个旗旗方便以后再回头看
邓dg 2019-4-11 15:11
7
0
楼主牛逼
道道道wtu 2019-4-11 16:31
8
0
楼主威武,我是小白,楼主可方便进一步向你请教
道道道wtu 2019-4-11 16:31
9
0
楼主威武,我是小白,楼主可方便进一步向你请教
Editor 3天前
10
0
感谢分享~
小刀 2天前
11
0
通俗易懂,帮助新手
游客
登录 | 注册 方可回帖
返回