喵喵机折腾记

也发点存货吧。这个是去年7月份做的一些工作,现在应该有些已经失效了。

去年暑假发现了喵喵机这个东西,女票买了一个送给我。作为一个折腾小能手,搞了点喵喵机相关的东西。 所有东西见: https://github.com/ihciah/miaomiaoji-tool

喵喵机是个蓝牙热敏打印机,可以直接利用它的 App 走蓝牙连接并打印。之前在微博看到了 DIYGOD 折腾了一个远程推送打印,也想搞一个类似的东西,可是我并不想一直手动戳着手机。考虑到手头有一个树莓派3,带蓝牙功能,于是决定拆了喵喵机的 App ,拿到通信协议,然后在树莓派上起控制脚本。

App 逆向

这部分本来懒得拆的,只想拿到蓝牙日志猜一猜的,找了个安卓手机搞,果然没猜出来==

那就拆 Apk 吧。下了最新的 Apk 和好几个旧版本,发现都有 360 加固,就很烦。后来折腾了一波虚拟机脱壳,就是在系统加载 dex 的时候把 dex dump 出来。蛮通用的做法,毕竟现有加固就和多年前的 PC 应用加壳一样,加载时动态解密代码放内存,只要找到 OEP 然后 dump 即可。在安卓里,加载 dex 时 dump 就可以拿到加固的内容。

当然,除了 dex ,这款 App 还有一点点的 Native 代码,拖 IDA 里可以拿到一个 key。这个稍后再讲。

蓝牙通信

我们先 Focus 蓝牙通信部分。在看了这么多垃圾代码(自带天然人工混淆)后,逆出了通信协议:

1
2
3
4
5
6
7
(1 byte) 2
(1 byte) 控制命令
(1 byte) 当前 payload 包序列号
(2 byte) payload 长度
(n byte) payload
(4 byte) payload 的 CRC32 校验码
(1 byte) 3

当 payload 过长时,拆成长度不大于 2016 的多个包,依次发送。

其中, CRC32 校验码的初始值为 crcKey = 0x35769521。控制命令包括 PRT_PRINT_DATA 等,数字常量,1.0.2 版本 Apk 中有 26 个, 2.0.3 版本中增加到 47 个,向下兼容。

机器返回的消息同样遵循上述协议,当最后一个包的 command 字段为 \x00 的时候表示信息发送完毕。

这时候我们只需要使用 Python 等语言复现一遍协议即可。

这里我实现了一个图片打印接口,然后在微信公众号后台接收图片,发送至树莓派自动进行打印。

微信端 喵喵机端(microUSB纯粹充电用)

另附几个折腾样例:

不同深度的打印测试页 失败样例

服务端通信

请求包:

1
2
3
4
5
6
7
8
9
10
11
12
13
POST /api/ap/Login HTTP/1.1
ts: 1502784192
sign: b71a3f80dab036e3110532590972961e
Accept-Encoding: gzip, deflate
Connection: close
Accept: **/*//*
userId: 1758717881
language: zh_CN
User-Agent: paperang_mm_android
Content-Type: application/x-www-form-urlencoded
Content-Length: 442
Host: ifs.mm.paperang.cn
msg=xxxxxxx

其中:

  • tstime.time() 取整
  • sign 为字符串 userId=xxx&msg=xxx&ts=xxx&signKey=ab69a9d9-94a5-4111-9554-00af2917732f 的小写 MD5
  • msg 在签名字符串中为一串json:
    {"ip":"","language":"CN","remark":"{\"brand\":\"samsung\",\"device\":\"SMA3000\",\" os\":\"and\",\"release\":\"5.0.2\",\"baseObjId\":0}","userName":"ihciah@gmail.com","userPassWord":"md5_of_password","type":1,"
  • 消息正文中的 msg 为原始 msg (即上文提到的 json 串,下记作msg_original )经加密后组成的 json 串(需 urllib.quote ,但 ios 版app抓包后发现似乎不quote也行?)
  • 原始消息:
    {"swType":"mmj_p1_fw"}
    奇怪的AES加密:
    QpJ42KMTg1f9msKsG5Xz3JY5nGL0UVQWZtAcFRG5dA8=
    组成json串:
    {"parameter":"QpJ42KMTg1f9msKsG5Xz3JY5nGL0UVQWZtAcFRG5dA8\\u003d\\n","baseObjId":0}
    转码:
    %7B%22parameter%22%3A%22QpJ42KMTg1f9msKsG5Xz3JY5nGL0UVQWZtAcFRG5dA8%5Cu003d%5Cn%22%2C%22baseObjId%22%3A0%7D
  • AES 加密算法采用 AES_ECB ,逆向 libalf_h_sdkcore.so 可以得到key: f3e15c3a845d48dc ( classes.dex 文件中也包含该密钥),输出时使
    b64encode ,特殊的是还需要将所有 + 替换为 -

响应包为 {"data":"xxxxx"}json 串,其中data 的值也遵循上面提到的奇怪的AES ,进行逆操作即可得到未加密的 json 响应。

1
2
3
4
5
6
from Crypto.Cipher import AES
import base64
def decrypt(msg):
msg = base64.b64decode(msg.replace("-", "+"))
c = AES.new("f3e15c3a845d48dc", AES.MODE_ECB)
return c.decrypt(msg)

服务端

手机 App 加载图片等资源通过阿里云 OSS SDK 加载,很奇怪的是 Burp 中未出现。应该是没有走系统 HTTP 代理,于是还是上 wireshark 。抓包得到这些请求都
是使用了 BUCKETID
mb-mm 内的文件,该 BUCKET 为私有 BUCKET ,需要 OSSAccessKeyIdSignature
按理说这个签名应该在服务器端做,但是尝试在 iOS上下载字体,从服务器上却没有返回任何签名,那么可以断定签名是在 App 内部做的。查看 App 的相关代码
和阿里云 OSS 的 Android SDK ,可以通过 OSSPlainTextAKSKCredentialProvider 设置 AccessKeyIdAccessKeySecret ,于是得到:

1
2
AccessKeyId: LTAIrPvoTOwid4QD
AccessKeySecret: 5xxJHjKmgMEFaqXhb3VZ2QrkcFRWde

之前抓包并解密后得到最新固件名: mmj_p1_fw_v127.bin ,打算尝试从OSS下载;现在直接下载到所有历史版本的APK和固件了2333333。

也就是说,如果我们生成了符合签名的固件并替换上去,可以让所有喵喵机在更新固件之后变砖=。= 或者加进去一些后门代码== 并且,该 OSS 内还有所有用户远程打印的图片,如果你仔细探索,会发现一些奇♂怪的图(捂脸)。

另外,在测试过程中也发现了一些注入漏洞。如:

1
2
POST /AjaxSub/GetSubscriptionCategory
typeId=2-1

我们可以直接得到表名:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
Database: mm_subscription_db
t_examineimagecache - 4829
t_material - 30459
t_materialcollectionconfig - 550402
t_materialgroup - 717
t_materialgroupconfig - 23593
t_materialoperationrecords - 3866621
t_message - 0
t_subcontentoperationrecords - 214243
t_subscription - 507
t_subscriptioncategory - 10
t_subscriptioncheck - 901
t_subscriptioncontent - 5642
t_subscriptionmaterial - 115
t_subscriptionrecord - 505050
t_subscriptionuserconfig - 396411
t_subscriptionusergroup - 6

硬件

一番尝试后并没有分析出固件格式或者是入口点,但是在观察hex数据时发现 YC1021 字样,搜索发现这个是蓝牙芯片名称。
第二天拆了机器,找到了芯片制造商 Nuvoton ,芯片型号 NUC123LD4BN0 ,使用的处理芯片是 STM32F071CBU6Cortex M0 架构。
IDA按照ARM6M 格式载入,发现似乎还能看,搜索立即数0x180 ,该数字十进制表示为384 ,即每行的像素数。

1
SRAM_BASE = 0x20000000

固件修改后可以通过两种方式上传,一种是 Nuvoton 所支持的 ISP ,被固件本身实现为蓝牙上传后调用接口更新;另一种是 USB 上传。考虑到改错的话蓝牙方式更新固件后可能会GG,所以先确保USB上传固件方式可以工作。
于是找到官网,然而又打出GG,需要编程器刷入。。。

本打算修改固件,让机器接受灰度图,然后能将吐出去的纸吃回去,多次打印来实现有深浅区别的打印。然而凉了呢。有 dalao 愿意改的话求戳。

另: 某小哥一年前曾因逆向喵喵机戳过我,现在…emmm去了这家公司。。感觉也是很神奇。