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

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

0x0 前言

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

0x1 界面分析

首先打开软件有一个输入注册码的输入框, 随便输入一串注册码点击验证

随后显示: 操作失败, 验证码不正确, 尝试抓包, 发现请求

说明此软件为网络验证, 有了这些信息准备开始逆向

0x2 反编译

首先将apk扔进 Android Killer (下面简称AK)里进行分析, 发现此软件并不同于普通的软件

1708874295433

发现其只有一个Activity, 且都以 md5 命名, 怀疑加壳, 使用查壳工具进行验证

1708874303688

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

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

1708874313307

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

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

1708874321286

不过一个一个解压是要死的, 于是上GitHub寻找工具, 找到 mono_unbundle 这款工具

使用命令 mono_unbundle libmonodroid_bundle_app.so dlls/ 解包, 获得dll文件一堆

1708874330106

根据 mono for android 结构, 主逻辑在 <appname>.dll 内, 使用 dnSpy 进行反编译

查找其页面控制, 根据Android开发习惯, 重点寻找 ViewModel , 于是在 AutoR.ViewModel 内寻找到重点函数 OnCDKCommandExecuted()

1708874339087

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

  1. 将注册码发送出去, 返回数据分为三段, 分别为 登录成功|剩余时间|CDKToken

  2. 然后CDKToken拿去进行验证, 拼接字符串 CDKToken|时间戳 , 然后与 123C7E5E875FBF0EEE2583F8AF3DDFF9 进行循环异或运算

    1708874354185
    1708874371281
  3. 将加密的数据发送给服务器, 服务器进行某种运算后返回一个Base64

    1708874377878
  4. 客户端对Base64与时间戳进行运算, 最终能变成字符串PASS, 说明验证成功

0x3 破解

这里有两种思路

  1. 直接修改C#代码并回编译

  2. 通过代理拦截验证函数, 使其返回正常

由于选择第一种后我不会将dll打包回so文件, 我这里选择第二种方法 首先通过 Fiddler 进行拦截, 返回固定数据, 伪造第一个数据包

1708874387552

可以发现软件发出了第二个数据包, 数据为 NTA0MDMyOzo1NjY6OjIzMTIzfzIxMDc2NTQ7OjM= , base64解码后为 504032;:566::231232107654;:3

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

python
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() 模拟软件发送验证, 随后编写代理服务器

python
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 的重定向功能进行测试

1708874404790

验证结果

1708874412304
1708874419277
1708874425964

这个软件就此就破解完成了


Android逆向笔记(二) -- 破解AutoR的注册码验证
https://simonkimi.githubio.io/2020/07/13/Android逆向笔记-二-破解AutoR的注册码验证/
作者
simonkimi
发布于
2020年7月13日
许可协议