日前,1024安全实验室参加了2022智能网联汽车漏洞挖掘赛线上赛,在大家的共同努力下肝了两天一夜取得了不错的成绩,团队内部进行题目复盘时将里面有几道比较有意思的跟大家分享一下。
1 签到题2
看本题的解题思路之前,我们先看一下UART协议帧格式。
1.1 UART协议
UART(Universal Asynchronous Receiver Transmitter )即通用异步收发器,是一种通用的串行、异步通信总线,该总线有两条数据线,可以实现全双工的发送和接收。在嵌入式系统中常用于主机与辅助设备之间的通信,帧格式如下:
其中各位的含义如下:
-
起始位:发送1位逻辑0(低电平),开始传输数据。
-
数据位:可以是5~8位的数据,先发低位,再发高位,一般常见的就是8位(1个字节),其他的如7位的ASCII码。
-
校验位:奇偶校验,可有可无。
-
停止位:停止位是数据传输结束的标志,可以是1/1.5/2位的逻辑1(高电平)。
-
空闲位:空闲时数据线为高电平状态,代表无数据传输。
1.2 解题思路
打开附件,发现内容与逻辑分析仪导出内容格式很像,部分内容如下:
$comment
Acquisition with 3/8 channels at 1 GHz
$end
$timescale 1 ns $end
$scope module ThanOSX $end
$var wire 1 ! 0 $end
$var wire 1 " 1 $end
$var wire 1 # 2 $end
$upscope $end
$enddefinitions $end
#0 0! 0" 0#
#1131723500 1!
#1131749500 0!
#1131758000 1!
#1131766500 0!
#1131775500 1!˛
#1131784000 0!
#1131792500 1!
#1131801500 0!
#1131811500 1!
#1131846000 0!
#1131854500 1!
#1131863500 0!
#1131880500 1!
#1131889500 0!
#1131899500 1!
#1131908000 0!
#1131916500 1!
#1131951500 0!
#1131968500 1!
这里以上位机软件PulseView为例,具体使用详见下述解题过程,将附件后缀改为sr,然后导附件,首先可以发现一个问题,就是波形空闲位一直是低电平,先继续往下分析。
选择UART协议来解析波形。
发现没有解析出内容,上面提到空闲位一直是低电平,可以猜测是不是信号进行了反转,因此将ThanOSX.0通道进行反转。
选择二进制解码视图显示。
解析后的内容为如下:
去除重复字符串得到flag。
2 我给车接了个外设打游戏
2.1 USB-HID名词解释
USB HID类是USB设备的一个标准设备类,包括的设备非常多。HID类设备定义它属于人机交互操作的设备,用于控制计算机操作的一些方面,如USB鼠标、USB键盘、USB游戏操纵杆等。但HID设备类不一定要有人机接口,只要符合HID类别规范的设备都是HID设备。
2.2 USB HID设备有关的描述符
USB HID类是USB设备的一个标准设备类,包括的设备非常多。HID类设备定义它属于人机交互操作的设备,用于控制计算机操作的一些方面,如USB鼠标、USB键盘、USB游戏操纵杆等。但HID设备类不一定要有人机接口,只要符合HID类别规范的设备都是HID设备。
bInterfaceProtocol的取值(十进制) 含义
0 NONE
1 键盘
2 鼠标
3-255 保留
本案例中的流量中包含了键盘和鼠标的标识
2.3 键盘流量和鼠标流量
键盘数据包的数据长度为8个字节,击键信息集中在第3个字节,每次key stroke都会产生一个keyboard event usb packet。
官网文档有相关对应关系:
https://www.usb.org/sites/default/files/documents/hut1_12v2.pdf
鼠标数据包的数据长度为4个字节,第一个字节代表按键,当取0x00时,代表没有按键、为0x01时,代表按左键,为0x02时,代表当前按键为右键。第二个字节可以看成是一个signed byte类型,其最高位为符号位,当这个值为正时,代表鼠标水平右移多少像素,为负时,代表水平左移多少像素。第三个字节与第二字节类似,代表垂直上下移动的偏移。
其中第一个字节代表按键,当取0x00时,代表没有按键、为0x01时,代表按左键,为0x02时,代表当前按键为右键。
第二个字节可以看成是一个signed byte类型,其最高位为符号位,当这个值为正时,代表鼠标水平右移多少像素,为负时,代表水平左移多少像素。
第三个字节与第二字节类似,代表垂直上下移动的偏移。
2.4 流量提取
Wireshark中捕获的USB流量集中在Leftover Capture Data模块,我们可以使用tshark工具来进行提取。
tshark -r usb.pcap -T fields -e usb.capdata > usb.txt
分析流量为8个字节为键盘流量。
2.5 小键盘的字符
将第3个字节的击键信息进行数据识别,跟上文中的官方文档发现输入的数据主要为小键盘的字符。
网上很多公开的转换脚本没有小键盘的,修改了下脚本可以识别。
344441234444123444412343412344441243333123444412334441233444123444123444412344441244433123344412344441244443124443312344441244433124433312344441244443123444412443331244433123334412344441244444124443312333441244433123333312344441243333124443312443331244433123444412344441244443124<DEL>344441244433124443312344441244433124333312444331244433123444412443331244433124333312444331244433123444412344441234444124<DEL>344441244433123333312444331233334123444412433331234444124333312344441234444124443312333441234444124333312444331233334123344412344
发现里面存在两个DEL键,删除前面两个4后得到:
3444412344441234444123434123444412433331234444123344412334441234441234444123444412444331233444123444412444431244433123444412444331244333123444412444431234444124433312444331233344123444412444441244433123334412444331233333123444412433331244433124433312444331234444123444412444431234444124443312444331234444124443312433331244433124443312344441244333124443312433331244433124443312344441234444123444412344441244433123333312444331233334123444412433331234444124333312344441234444124443312333441234444124333312444331233334123344412344
经过分析发现类似摩斯电码进行转换得到
-..../-..../-..../-.-./-..../.----/-..../--.../--.../-.../-..../-..../...--/--.../-..../....-/...--/-..../...--/..---/-..../....-/-..../..---/...--/---../-..../...../...--/---../...--/-----/-..../.----/...--/..---/...--/-..../-..../....-/-..../...--/...--/-..../...--/.----/...--/...--/-..../..---/...--/.----/...--/...--/-..../-..../-..../-..../...--/-----/...--/----./-..../.----/-..../.----/-..../-..../...--/---../-..../.----/...--/----./--.../-..
附优化后的提取脚本
#!/usr/bin/env python
import sys
import os
DataFileName = "usb.dat"
presses = []
normalKeys = {"04": "a", "05": "b", "06": "c", "07": "d", "08": "e", "09": "f", "0a": "g", "0b": "h", "0c": "i",
"0d": "j", "0e": "k", "0f": "l", "10": "m", "11": "n", "12": "o", "13": "p", "14": "q", "15": "r",
"16": "s", "17": "t", "18": "u", "19": "v", "1a": "w", "1b": "x", "1c": "y", "1d": "z", "1e": "1",
"1f": "2", "20": "3", "21": "4", "22": "5", "23": "6", "24": "7", "25": "8", "26": "9", "27": "0",
"28": "<RET>", "29": "<ESC>", "2a": "<DEL>", "2b": "t", "2c": "<SPACE>", "2d": "-", "2e": "=", "2f": "[",
"30": "]", "31": "\", "32": "<NON>", "33": ";", "34": "'", "35": "<GA>", "36": ",", "37": ".", "38": "/",
"39": "<CAP>", "3a": "<F1>", "3b": "<F2>", "3c": "<F3>", "3d": "<F4>", "3e": "<F5>", "3f": "<F6>",
"40": "<F7>", "41": "<F8>", "42": "<F9>", "43": "<F10>", "44": "<F11>", "45": "<F12>","54": "/","55": "*","56": "-","57": "+","59": "1","5a": "2","5b": "3","5c": "4","5d": "5","5e": "6","5f": "7","60": "8","61": "9","62": "0","63": ".",}
shiftKeys = {"04": "A", "05": "B", "06": "C", "07": "D", "08": "E", "09": "F", "0a": "G", "0b": "H", "0c": "I",
"0d": "J", "0e": "K", "0f": "L", "10": "M", "11": "N", "12": "O", "13": "P", "14": "Q", "15": "R",
"16": "S", "17": "T", "18": "U", "19": "V", "1a": "W", "1b": "X", "1c": "Y", "1d": "Z", "1e": "!",
"1f": "@", "20": "#", "21": "$", "22": "%", "23": "^", "24": "&", "25": "*", "26": "(", "27": ")",
"28": "<RET>", "29": "<ESC>", "2a": "<DEL>", "2b": "t", "2c": "<SPACE>", "2d": "_", "2e": "+", "2f": "{",
"30": "}", "31": "|", "32": "<NON>", "33": """, "34": ":", "35": "<GA>", "36": "<", "37": ">", "38": "?",
"39": "<CAP>", "3a": "<F1>", "3b": "<F2>", "3c": "<F3>", "3d": "<F4>", "3e": "<F5>", "3f": "<F6>",
"40": "<F7>", "41": "<F8>", "42": "<F9>", "43": "<F10>", "44": "<F11>", "45": "<F12>"}
def main():
# check argv
if len(sys.argv) != 2:
print("Usage : ")
print(" python UsbKeyboardHacker.py data.pcap")
print("Tips : ")
print(" To use this python script , you must install the tshark first.")
print(" You can use `sudo apt-get install tshark` to install it")
print("Author : ")
print(" WangYihang <[email protected]>")
print(" If you have any questions , please contact me by email.")
print(" Thank you for using.")
exit(1)
# get argv
pcapFilePath = sys.argv[1]
# get data of pcap
os.system("tshark -r %s -T fields -e usb.capdata 'usb.data_len == 8' > %s" % (pcapFilePath, DataFileName))
# read data
with open(DataFileName, "r") as f:
for line in f:
presses.append(line[0:-1])
# handle
result = ""
for press in presses:
if press == '':
continue
if ':' in press:
Bytes = press.split(":")
else:
Bytes = [press[i:i + 2] for i in range(0, len(press), 2)]
if Bytes[0] == "00":
if Bytes[2] != "00" and normalKeys.get(Bytes[2]):
result += normalKeys[Bytes[2]]
elif int(Bytes[0], 16) & 0b10 or int(Bytes[0], 16) & 0b100000: # shift key is pressed.
if Bytes[2] != "00" and normalKeys.get(Bytes[2]):
result += shiftKeys[Bytes[2]]
else:
print("[-] Unknow Key : %s" % (Bytes[0]))
print("[+] Found : %s" % (result))
# clean the temp data
os.system("rm ./%s" % (DataFileName))
if __name__ == "__main__":
main()
3 扩展维吉尼亚
打开题目后发现分别是一个图片和密文
打开图片可以看到这类似于维吉尼亚加密
但是出题者对维吉尼亚进行了扩展,原来的维吉尼亚只有字母,现在出题者将码表扩展为字母+数字+特殊符号。
对于一般的维吉尼亚密码,我们可以将不同密钥的凯撒密码应用于连续的字母上,如果密钥是”PUB”,则第一个字母使用密钥为16的凯撒密码(P是第16个字母)进行加密,第二个字母用另一个密钥进行加密,第三个字母用再一个密钥进行加密。当我们到达第四个字母时,它使用与字母1相同的密码进行加密。因此,如果我们收集字母1,4,7,10,…,我们应该得到一个字符序列,所有这些字符都是使用相同密钥的凯撒密码进行加密。字符2,5,8,11,…和3,6,9,12,…的序列也将使用其自己的凯撒密码进行加密。当然,确切的顺序将取决于密码的周期,即密钥长度。为了确定维吉尼亚密码的周期,我们首先假定密钥长度为2。我们从密文中提取两个序列1,3,5,7,…和2,4,6,8,…。使用我们刚举的例子,我们得到以下结果(注意:IC是使用整个序列计算的,而不止是显示的部分)
我们可以假设密钥的长度为2~15,求出所有子序列重合指数的平均值,如下表所示:
现在我们可以大胆假设密钥长度为7,也就是说我们有7个凯撒密码要破解。我们使用卡方统计来找到正确的密钥:
提取密文中所有使用第一个密钥加密的密文(也就是第1,8,15,…个字母),得到第一个序列VURZJUGRGGUGVGJQKEOAGUGKKQVWQP,这是使用同一个密钥加密的凯撒密文。
如果想破解这个密钥,卡方统计在这个页面有详细的描述。从本质上讲,我们尝试使用25种可能的密钥解密该序列,然后将解密后的文本频率分布与英文的频率分布进行比较,正确的密钥有着较低卡方统计量。
通过分析,我们得到了该密钥的第一个字母’C’(A = 0,B = 1,C = 2,…)。以此类推,分析剩下的六个密钥我们会得到序列:2,8,0,7,4,17,18,这意味着密钥是:CIAHERS,但很遗憾,答案错误。
这恰恰说明了除非有长密文,否则该方法未必可靠。正确的密钥是CIPHERS
而该题目由于对维吉尼亚密码进行了扩展,我们虽然也可以对其进行卡方统计,按理来说字符的概率统计应该为0,但是由于密文较短,所以此处不按照这种方法来操作。
我们可以使用一种新的方法:
因为我们不知道密钥长度,所以我们需要假设密钥长度为3、密钥长度为4….密钥长度为20。为了测试密钥的准确性,可以使用四元组统计的方法来评估解密文本的分数。例如,我们当前的密钥为CHIPAAA,我们只搜索了7位密钥中的前四位,我们再评估分数的时候也只评估前四位的分数,即:
我们仅会计算阴影部分的四元组统计分数,可以降低其他未正确解密区域对总体评分的影响。
我们不会搜索所有的密钥(大概是26N次方个密钥 N是密钥长度),这显然不太现实,我们在破解密钥的时候将各位密钥独立开来破解,这样可以将搜索密钥的数量降低为26*N。
完整的脚本如下:
import re
ciphertext = '{snm6 vs3s tz6d"io v2 1m6yg}} v8m4j6 te15nz{v 3ilfj 1m6yg}} v8m4j vmw t 0gy3fwp nq1pm!wq8 }kwx lhtqg!6 jv3u 2vzy7t"'
# ciphertext = re.sub(r'[^A-Z]', '', ciphertext.upper())
print('loading....')
qgram = ngram_score('quadgrams.txt')
tgram = ngram_score('trigrams.txt')
N = 100
Alphabet = 'abcdefghijklmnopqrstuvwxyz0123456789,.!{}"'
Alphabet_base = 'abcdefghijklmnopqrstuvwxyz'
class OptativeStore(object):
def __init__(self, store_length=1000):
self.store = []
self.store_length = store_length
def append(self, item):
self.store.append(item)
self.store.sort(reverse=True)
self.store = self.store[:self.store_length]
def __getitem__(self, K):
return self.store[K]
def __len__(self):
return len(self.store)
def ProcessTriGram(KLEN):
oTable = OptativeStore(N)
for i in permutations(Alphabet_base, 3):
partkey = ''.join(i)
fullkey = partkey + 'A' * (KLEN - 3)
#plaintext = Vigenere(fullkey).decipher(ciphertext)
plaintext = decode(fullkey,Alphabet,ciphertext)
score = 0
for j in range(0, len(plaintext), KLEN):
score += tgram.score(plaintext[j:j + 3])
oTable.append((score, partkey, plaintext[:30]))
return oTable
def SolveVirginia(KLEN):
assert(KLEN > 2)
best = ProcessTriGram(KLEN)
nextbest = OptativeStore(N)
for i in range(0, KLEN - 3):
# print(i)
for j in range(N):
for k in Alphabet_base:
partkey = best[j][1] + k
# print(KLEN,len(partkey))
fullkey = partkey + ('A' * (KLEN - len(partkey)))
# print(fullkey)
#plaintext = Vigenere(fullkey).decipher(ciphertext)
plaintext = decode(fullkey,Alphabet,ciphertext)
score = 0
for l in range(0, len(ciphertext), KLEN):
score += qgram.score(plaintext[l:l + KLEN])
nextbest.append((score, partkey, plaintext[:30]))
best = nextbest
nextbest = OptativeStore(N)
return best
def main():
for i in range(3, 20):
bestkey = SolveVirginia(i)[0][1]
#plaintext = Vigenere(bestkey).decipher(ciphertext)
plaintext = decode(bestkey,Alphabet,ciphertext)
bestscore = qgram.score(plaintext)
print("Vigenere key length is %2d : %4d %20s %s" %
(i, bestscore, bestkey, plaintext))
if __name__ == '__main__':
main()
可以解出来正确的密码为welcometocvvd
使用此密钥解密flag为
4 蓝牙钥匙的春天
4.1 解题过程
这是一道蓝牙分析题,因为最近在研究蓝牙相关内容,所以看到有蓝牙相关的赛题就首先看了一下这道题,也拿了个一血,在这里先说一下这道题的解法,顺便也扩展一些蓝牙相关的知识。
下载下来是一个蓝牙流量包,随便翻翻发现存在 SMP 协议,全称是 Secure Manager Protocol,是蓝牙用来定义配对和密钥分发的。
配对后的流量是被加密的,但是有个工具 crackle 是可以解密这种数据包的,这个工具解密蓝牙流量有三个前提,这也在官方的 FAQ 中提到了:
https://github.com/mikeryan/crackle/blob/master/FAQ.md
首先要有完整的配对过程流量,要使用链路层加密(有些开发者会自己实现加密),且只适合于传统配对(legacy pairing)
安装 crackle
git clone https://github.com/mikeryan/crackle.git
cd crackle
make
make install
如果提示下图则需要安装相关依赖
sudo apt-get install libpcap-dev
安装完成后我们使用 crackle 解密发现如下提示,它不支持 BLUETOOTH_LE_LL
但是在它的 issues 中发现有人讨论过这个问题
用这个人的 crackle 再解一遍发现已经可以识别
加个参数 -o 可以保存出解密的数据包
crackle -i uploads_2022_04_11_h5kAcZEg_ble.pcapng -o de.pcapng
保存出来的数据包用 wireshark 打开后发现了一些值
把这些值提取出来之后发现一共有四组不同的内容,对他们进行两次十六进制转字符串后尝试拼接,得到了 flag
4.2 蓝牙配对协议介绍
配对主要的目的就是防止中间人攻击(MITM)与被动监听,其中防止被动监听有了采用 ECDH(椭圆曲线密钥交换体制)的 LE Secure Connections 已经可以防范类似我们上面解密蓝牙流量的方法了。
防止中间人攻击采用的防范手段是引入人为的操作,比如我们在配对过程中需要输入 PIN 码,只有双方输入相同的 PIN 码才能配对成功。
但是不同的设备输入输出能力是不同的,不能要求一个没有键盘的设备在配对的时候还要输入 PIN 码吧,所以在配对的第一步便是双方交换配对特征,这一步会交换双方的配对能力与认证要求。
具体解释一下其中的字段含义
IO Capability 表示设备有哪些输入输出能力(是否能用键盘或者显示信息啥的)在本题中主设备的 IO 能力是既能输入又能显示,而从设备只能显示,IO 能力字段对应的值如下
OOB,是否支持通过带外通道的方式进行认证,就是说设备是否可以通过其他方式(比如 NFC、红外)进行认证,因为对于蓝牙攻击者来说这种方式是不可见的,所以安全些。
AuthReq 字段不同的位有不同的含义(主要看加粗)的分别是:
Keypress Flag:仅在 Passkey Entry 协议中使用,当设置为 1 时使用摁键配对
Secure Connection Flag:如果设备支持 LESC 安全连接配对就设置为 1,否则为 0,根据这个字段来选择是使用安全连接( LE Secure Connections)还是遗留配对(LE legacy pairing)
MITM:如果设备请求 MITM 保护中间人身份验证,就设置为 1,否则为 0
Bonding Flags:表示设备请求绑定的类型
4.3 关于配对方式的选择
先判断 OOB,如果双方 OOB 均为 1 的话,优先使用 OOB,任何一个没有 OOB 就根据 MITM 判断,如果两个都没有要求 MITM 的话就 JUST WORK 了,如果是任何一个有 MITM 的话就看 IO 能力
对于 IO 能力对应的是否可靠,蓝牙协议里面有张图可以参考,但是被分成两页了(在 Core Specification 5.3 的 1573 页)
5 Big升级包
5.1 概述
这道题目在解题过程中比较滑稽,解题流程看似是很繁琐的66层套娃式解压缩包,尝试写出快速层层解包的脚本,这帮助我们拿到该题目的一血,有趣的是从30余k大小的压缩包中最终解出的flag文件足足有1GB大小,好奇心驱使下扩展了高压缩文件的实现原理。
5.2 解题步骤
big升级包题目是一个名为Flag.zip的压缩文件;题目未给出较明显的提示,于是尝试暴力破解;密码仅有四位密码构成,很容易解出密码为cvvd。
显然爆破并非是该题目的考察点,于是在解开压缩包后,有意思的部分出现了:
在名为666666的文件夹中出现了6个新的压缩包:
使用unzip 命令尝试解压缩其中的任何一个压缩包均会解出七个新的压缩包文件,但其压缩包序号会向下递减一层:
观察到这一套娃现象,不难想到一口气解开总共66层代码应该可以得到flag或是得到下一步线索。
但是手动解压缩66次数据包未免显得过于繁琐;
于是观察到每当解出当前其中的一个压缩包,以66-3.zip为例会解出65-0.zip到65-6.zip七个新的压缩包,于是思路制定为将文件名称的前两位设置为变量,拼凑出解压后下一层中新的文件名称,再依次循环将这新一层的压缩文件解出,于是写了如下脚本:
执行脚本后几秒钟时间解出压缩包的最后一层,得到flag文件,文件类型为data,有1GB大小,查看会疯狂滚动满屏的666:
运行下述命令过滤高亮显示,可以容易的看到flag:
strings flag | grep --color=auto flag
虽然顺利解出flag但是留下了满屏的压缩包文件:
因此考虑同步删除文件的方案优化脚本,以仅保留flag文件:
思路为将每一层解压过的文件进行删除,由于每次解压一层,但新一层的7个压缩包名称首位会递减1,以66-0.zip为例,解压后成为65-0.zip到65-6.zip七个压缩包,完成解压后即可删除66-0.zip到66-6.zip七个压缩包即可,因此可以将压缩包名称首位部分的“66”用变量表示、代表七个压缩表的编号“0-6”也使用变量表示,拼凑出删除的文件名称,脚本如下:
import os
import sys
import time
i=64
file_name = ""
un_name = ""
command = ""
command0 = ""
def unzip():
global i
while i > 60:
file_name = str(i) + "-0.zip"
command = "unzip " + file_name
ret0 = os.popen(command).read()
print(ret0)
time.sleep(0.05)
j = 0
while j < 7:
un_name = str(i) + "-" + str(j) + ".zip"
command0 = "rm " + un_name
os.popen(command0)
print(command0)
j = j+1
i = i-1
if __name__ == '__main__':
unzip()
5.3 高压缩文件的实现原理
文件以0和1的形式存储,高比例压缩的核心原理在于运用适当的规律简化文件结构中数字的排列方式。
例如:
10000000000 可以简化为数学中的10^9
11110001110000 可以简化为四个1三个0三个1四个0的排列方式。
在本文题目当中,最后的压缩包文件大小为1个G,但不难看出,里面除了那行flag均是666666……..组成的data文件,显然,可以使用有穷N多个数字6来表示,以此实现高比率压缩;
扩展:
(1)字典压缩
字典算法是最为简单的压缩算法之一。它是把文本中出现频率比较多的单词或词汇组合做成一个对应的字典列表,并用特殊代码来表示这个单词或词汇。
例如:有字典列表:00=Chinese01=People02=China源文本:I am a Chinese people,I am from China 压缩后的编码为:I am a 00 01,I am from 02。压缩编码后的长度显著缩小,这样的编码在SLG游戏等专有名词比较多的游戏中比较容易出现;
(2)固定位长算法
这种算法是把文本用需要的最少的位来进行压缩编码。比 如八个十六进制数:1,2,3,4,5,6,7,8。转换为二进制为:00000001,00000010,00000011,00000100, 00000101,00000110,00000111,00001000。每个数只用到了低4位,而高4位没有用到(全为0),因此对低4位进行压缩编 码后得到:0001,0010,0011,0100,0101,0110,0111,1000。然后补充为字节得到:00010010, 00110100,01010110,01111000。所以原来的八个十六进制数缩短了一半,得到4个十六进制数:12,34,56,78。
(3)运行长度编码 (RLE) 数据压缩算法
RLE是一种简单的无损数据压缩形式,它在具有相同值连续多次出现的序列上运行。它将序列编码为仅存储单个值及其计数。
例如,考虑在纯白色背景上包含纯黑色文本的屏幕。空白处会有很多长的白色像素,文本中有很多短的黑色像素。
WWWWWWWWWWWWBWWWWWWWWWWWWBBBWWWWWWWWWWWWWWWWWWWWWWWWBWWWWWWWWWWWWWW
将游程编码 (RLE) 数据压缩算法应用于上述假设的扫描线,可以将其渲染为12W1B12W3B24W1B14W. 这可以解释为十二W’s、一B、十二W’s、三等的序列B’s。
这个算法是对字符串运行线性扫描,并且对于每个不同的字符,在输出字符串中附加该字符及其连续出现。
在本文所述题目中,很可能采用的也是这个算法对大量的数字6进行游程编码,本文试给出一种实现该题目游程编码的Python脚本:
def encode(s):
encoding = ""
i = 0
while i < len(s):
count = 1
while i + 1 < len(s) and s[i] == s[i + 1]:
count = count + 1
i = i + 1
encoding += str(count) + s[i]
i = i + 1
return encoding
if __name__ == '__main__':
s = '666666666666666666666666'
print(encode(s))
1024安全团队
1024安全团队是网络安全爱好者组建的团队,希望通过此平台更好的进行前沿的技术交流与分享,现研究方向包含但不限于车联网安全、物联网安全、硬件安全、无线电安全等,我们正在招募新的成员,期待更多志同道合的朋友加入我们,一起进行技术交流学习。
原文始发于微信公众号(1024安全团队):2022智能网联汽车漏洞挖掘赛 WP by 1024SEC