SImon菌 发表于 2020-7-13 00:17

破解一个Mono For Android写的非传统APK

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

# 0x1 界面分析
首先打开软件有一个输入注册码的输入框, 随便输入一串注册码点击验证
![在这里插入图片描述](https://img-blog.csdnimg.cn/20200712214125697.png)
随后显示: 操作失败, 验证码不正确, 尝试抓包, 发现请求
![在这里插入图片描述](https://img-blog.csdnimg.cn/20200712214217496.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzQyNDM2MTc2,size_16,color_FFFFFF,t_70)
说明此软件为网络验证, 有了这些信息准备开始逆向

# 0x2 反编译
首先将apk扔进`Android Killer`(下面简称AK)里进行分析, 发现此软件并不同于普通的软件
![在这里插入图片描述](https://img-blog.csdnimg.cn/2020071221440851.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzQyNDM2MTc2,size_16,color_FFFFFF,t_70)
发现其**只有一个Activity**, 且都以`md5`命名, 怀疑加壳, 使用查壳工具进行验证
![在这里插入图片描述](https://img-blog.csdnimg.cn/20200712214532327.png)

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


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

![在这里插入图片描述](https://img-blog.csdnimg.cn/20200712215014512.png)

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

网上查询得知, 所有的C#代码都藏在`libmonodroid_bundle_app.so`内, 且是个千层饼结构, 尝试使用`binwalk`检查, 发现大量压缩包
![在这里插入图片描述](https://img-blog.csdnimg.cn/20200712215808702.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzQyNDM2MTc2,size_16,color_FFFFFF,t_70)

不过一个一个解压是要死的, 于是上GitHub寻找工具, 找到(https://github.com/tjg1/mono_unbundle)这款工具
使用命令`mono_unbundle libmonodroid_bundle_app.so dlls/`解包, 获得dll文件一堆
![在这里插入图片描述](https://img-blog.csdnimg.cn/20200712220100682.png)
根据`mono for android`结构, 主逻辑在`<appname>.dll`内, 使用`dnSpy`进行反编译

查找其页面控制, 根据Android开发习惯, 重点寻找`ViewModel`, 于是在`AutoR.ViewModel`内寻找到重点函数`OnCDKCommandExecuted()`
![在这里插入图片描述](https://img-blog.csdnimg.cn/20200712220554511.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzQyNDM2MTc2,size_16,color_FFFFFF,t_70)

分析其代码, 可以梳理出其逻辑大概是这样的
1. 将注册码发送出去, 返回数据分为三段, 分别为 `登录成功|剩余时间|CDKToken`
2. 然后CDKToken拿去进行验证, 拼接字符串`CDKToken|时间戳`, 然后与`123C7E5E875FBF0EEE2583F8AF3DDFF9`进行循环与非运算
![在这里插入图片描述](https://img-blog.csdnimg.cn/20200712234610884.png)
![在这里插入图片描述](https://img-blog.csdnimg.cn/20200712234644413.png)
3. 将加密的数据发送给服务器, 服务器进行某种运算后返回一个Base64
![在这里插入图片描述](https://img-blog.csdnimg.cn/20200712234843781.png)
4. 客户端对Base64与时间戳进行运算, 最终能变成字符串PASS, 说明验证成功



# 0x3 破解
这里有两种思路
1. 直接修改C#代码并回编译
2. 通过代{过}{滤}理拦截验证函数, 使其返回正常

由于选择第一种后我不会将dll打包回so文件, 我这里选择第二种方法
首先通过`Fiddler`进行拦截, 返回固定数据, 伪造第一个数据包
![在这里插入图片描述](https://img-blog.csdnimg.cn/20200712222002326.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzQyNDM2MTc2,size_16,color_FFFFFF,t_70)
可以发现软件发出了第二个数据包, 数据为`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 ^= key_byte
    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 ^= ok_bytes
    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 ^= ok_bytes
    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 ^= key_byte
    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 ^= ok_bytes
    return base64.b64encode(bytes(final_data)).decode()


app.run()
```
然后通过`Fiddler`的重定向功能进行测试
![在这里插入图片描述](https://img-blog.csdnimg.cn/20200712234107439.png)
验证结果
![在这里插入图片描述](https://img-blog.csdnimg.cn/20200712234143462.png)
![在这里插入图片描述](https://img-blog.csdnimg.cn/20200712234156457.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzQyNDM2MTc2,size_16,color_FFFFFF,t_70)
![在这里插入图片描述](https://img-blog.csdnimg.cn/20200712235348413.png)
这个软件就此就破解完成了
**如果有人知道如何把反编译的dll打包会so文件就好了, 我查了一天资料都没查到, Mono for Android用的人果然还是太少了吗**

xixicoco 发表于 2020-7-14 14:33

牛一个,顶你

baisepikaq 发表于 2020-7-14 15:31

破解方法真是千千万,以后路还很长!

goodgengle 发表于 2020-7-22 14:45

特别厉害,顶你一个。

11649827 发表于 2020-9-13 13:43

大佬有什么办法回编译吗?

平凡的无序 发表于 2020-9-13 14:08

牛的批爆,大佬就是大佬
页: [1]
查看完整版本: 破解一个Mono For Android写的非传统APK