Android逆向笔记(二) -- 破解AutoR的注册码验证

0x0 前言

此软件为练习用Crackme.apk 仅有验证逻辑
此软件不同于普通的Android软件, 是由Mono for Android编写的, 主逻辑的语言为C#, 在尝试破解这款软件的时候花费了我大量功夫, 特此记录

0x1 界面分析

首先打开软件有一个输入注册码的输入框, 随便输入一串注册码点击验证
在这里插入图片描述
随后显示: 操作失败, 验证码不正确, 尝试抓包, 发现请求
在这里插入图片描述
说明此软件为网络验证, 有了这些信息准备开始逆向

0x2 反编译

首先将apk扔进Android Killer(下面简称AK)里进行分析, 发现此软件并不同于普通的软件
在这里插入图片描述
发现其只有一个Activity, 且都以md5命名, 怀疑加壳, 使用查壳工具进行验证
在这里插入图片描述

发现并没有加壳, 只能手动进行分析, 只有一个Activity肯定不正常, 首先检查assetslib查看是否有线索

lib文件里发现猫腻, 有几个libmono开头的.so文件, 还有libmonodroid_bundle_app.so, 经过百度, 此Android程序并非传统的Android程序, 而是Mono for Android, 使用C#写的.

在这里插入图片描述

检查MainActivity代码, 发现大量native代码, 说明Java层全是代理方法, 没有主逻辑, 现在应该寻找C#代码藏在哪里

网上查询得知, 所有的C#代码都藏在libmonodroid_bundle_app.so内, 且是个千层饼结构, 尝试使用binwalk检查, 发现大量压缩包
在这里插入图片描述

不过一个一个解压是要死的, 于是上GitHub寻找工具, 找到mono_unbundle这款工具
使用命令mono_unbundle libmonodroid_bundle_app.so dlls/解包, 获得dll文件一堆
在这里插入图片描述
根据mono for android结构, 主逻辑在<appname>.dll内, 使用dnSpy进行反编译

查找其页面控制, 根据Android开发习惯, 重点寻找ViewModel, 于是在AutoR.ViewModel内寻找到重点函数OnCDKCommandExecuted()
在这里插入图片描述

分析其代码, 可以梳理出其逻辑大概是这样的

  1. 将注册码发送出去, 返回数据分为三段, 分别为 登录成功|剩余时间|CDKToken
  2. 然后CDKToken拿去进行验证, 拼接字符串CDKToken|时间戳, 然后与123C7E5E875FBF0EEE2583F8AF3DDFF9进行循环与非运算
    在这里插入图片描述
    在这里插入图片描述
  3. 将加密的数据发送给服务器, 服务器进行某种运算后返回一个Base64
    在这里插入图片描述
  4. 客户端对Base64与时间戳进行运算, 最终能变成字符串PASS, 说明验证成功

0x3 破解

这里有两种思路

  1. 直接修改C#代码并回编译
  2. 通过代理拦截验证函数, 使其返回正常

由于选择第一种后我不会将dll打包回so文件, 我这里选择第二种方法
首先通过Fiddler进行拦截, 返回固定数据, 伪造第一个数据包
在这里插入图片描述
可以发现软件发出了第二个数据包, 数据为NTA0MDMyOzo1NjY6OjIzMTIzfzIxMDc2NTQ7OjM=, base64解码后为504032;:566::231232107654;:3

返回的数据包经过一定的运算后, 最终变成PASS, 尝试进行反向运算

import base64


def login4(data_in: str):
    input_bytes = list(base64.b64decode(data_in))
    key_byte = list("123C7E5E875FBF0EEE2583F8AF3DDFF9".encode())
    for i in range(len(input_bytes) - 1, -1, -1):
        for j in range(len(key_byte) - 1, -1, -1):
            input_bytes[i] ^= key_byte[j]
    ok, token = bytes(input_bytes).decode().split("|")
    print(ok, token)
    ok_bytes = list(ok.encode())
    final_data = list("PASS".encode())
    for i in range(len(final_data) - 1, -1, -1):
        for j in range(len(ok) - 1, -1, -1):
            final_data[i] ^= ok_bytes[j]
    return base64.b64encode(bytes(final_data)).decode()


def verify():
    ok = "637301896559910210"
    array2 = list(base64.b64decode(login4("NTA0MDMyOzo1NjY6OjIzMTIzfzIxMDc2NTQ7OjM=")))
    ok_bytes = list(ok.encode())

    for i in range(len(array2)):
        for j in range(len(ok_bytes)):
            array2[i] ^= ok_bytes[j]
    return bytes(array2).decode()


if __name__ == "__main__":
    print(verify())

其中login4()是接受软件请求验证的数据, verify()模拟软件发送验证, 随后编写代理服务器

from flask import Flask, request
import base64

app = Flask(__name__)


@app.route('/api/cd694e62ba74089c8df7aefb324c7910')
def hello_world():
    data = request.args.get("login4")
    if data:
        return login4(data)
    else:
        return "登录成功|999999|1234567890"


def login4(data_in: str):
    input_bytes = list(base64.b64decode(data_in))
    key_byte = list("123C7E5E875FBF0EEE2583F8AF3DDFF9".encode())
    for i in range(len(input_bytes) - 1, -1, -1):
        for j in range(len(key_byte) - 1, -1, -1):
            input_bytes[i] ^= key_byte[j]
    ok, token = bytes(input_bytes).decode().split("|")
    print(ok, token)
    ok_bytes = list(ok.encode())
    final_data = list("PASS".encode())
    for i in range(len(final_data) - 1, -1, -1):
        for j in range(len(ok) - 1, -1, -1):
            final_data[i] ^= ok_bytes[j]
    return base64.b64encode(bytes(final_data)).decode()


app.run()

然后通过Fiddler的重定向功能进行测试
在这里插入图片描述
验证结果
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
这个软件就此就破解完成了

  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值