[CISCN2018]sm题解

简介

这道题比较老了,但是质量还是不错的,本来应该是跟SM加密方式有关的,但是脚本能直接读懂,那就不必再去查了。

题解

分析函数

在这里插入图片描述首先分析一下题目的代码,从运行的顺序来逐个分析函数

直接调用的函数是run()
在这里插入图片描述
这个函数内部最关键的是这个key,这是AES加密的密钥,有了key,才能从ef中的密文还原出flag。

再看key的来源:
在这里插入图片描述
虽然有一大串式子,但是都是在以固定的方式变换形式,真正影响key的只有choose这个变量。

顺势就看看choose怎么求:
在这里插入图片描述
仔细看这段代码,大概意思是,从前往后遍历choose的512个比特位,如果值为一则异或ps中的对应数。

我们知道异或是可逆的,如果我们知道r都异或了ps中的哪些数是不是就知道bchoose的哪些位是1了?那么不就知道choose是啥了。

但是单纯来想,已知一个512个数的数表和在其中挑选若干个进行异或的结果,这个算法要跑出来都异或了哪些数,这计算量得到2^512(天文数字中的天文数字),所以必定是有一些特殊结构简化了计算。因而解出choose的关键就在这个生成ps的**gen512num()**这个函数有什么特殊结构了。

在这里插入图片描述
分析该函数:主要操作是生成512个相似结构的512位数字,每个数都由一个长度不定的前缀和随后的0填充组成。对异或来讲,只有这个前缀是有意义的

虽然是通过随机数生成的,但事实上这个前缀的长度是可以反算出来的,不妨试一下

脚本如下:

f = open("ps","r")
line = f.readlines()
s=[]
for i in range(len(line)):
    line[i] = int(line[i].strip('\n'))
    t=line[i]
    len=0
    while(t%2==0):
        len+=1
        t=t>>1
    s.append(512-len)
#s.sort() 一旦排序了就看出其中玄机了
print(s)

仔细观察s的结果,不难发现s正好是1-512的无重复数组,但是注意,我们可以排序了之后观察,但是不能在后面使用排序过后的s,因为解出choose是要知道ps中哪几行的数被异或过,那么这些数原来的顺序也是必要的信息,不能擅自修改。

还原choose

至此,前缀长度是1-512的不重复数组这个特殊结构,让我们反推choose的算法难度大幅降低(但是这很不现实,随机生成的长度这么巧的也太难了)

这是因为比如最后一个值为1的位,导致它值为1的可能,只有前缀长为它对应位置的那个数(因为前缀的结尾必为1)

异或这个数把这个数造成的影响消除,这样从后往前找可以再发现一个位仅受ps中的一个数的影响的情况,依次类推,就可以还原出 r 到底异或了哪些数,找到都是异或了哪些数,根据 r 的产生的方式,我们就能知道choose的哪些位是1。

脚本如下:

f1=open("r","r")

k=int(f1.read())
m=list("0"*512) #bchoose

for i in range(511,-1,-1):
    if((k>>(511-i))%2==1):
        k=k^line[s.index(i+1)]
        m[s.index(i+1)]= "1"
    else:
        m[s.index(i+1)]="0"
choose=int("0b"+"".join(m),2)
#choose=11400599473028310480620591074112690318799501642425666449519888152497765475409346201248744734864375690112798434541063767944755958258558428437088372812844657

解密

由于我们已经还原出了choose,那么key只需要按照题目给的方法生成就可以了,随后先进行base64解码,再进行AES解密,就得到最后的结果啦。

key=long_to_bytes(int(hashlib.md5(long_to_bytes(choose)).hexdigest(),16))
aes_obj = AES.new(key, AES.MODE_ECB)
f2=open("ef","rb")
ef=f2.read()
ef=base64.b64decode(ef)
result=aes_obj.decrypt(ef)
print(result)

FLAG:flag{shemir_alotof_in_wctf_fun!}

  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
06-01
这道题是一道典型的费用限制最短路题目,可以使用 Dijkstra 算法或者 SPFA 算法来解决。 具体思路如下: 1. 首先,我们需要读入输入数据。输入数据中包含了道路的数量、起点和终点,以及每条道路的起点、终点、长度和限制费用。 2. 接着,我们需要使用邻接表或邻接矩阵来存储图的信息。对于每条道路,我们可以将其起点和终点作为一个有向边的起点和终点,长度作为边权,限制费用作为边权的上界。 3. 然后,我们可以使用 Dijkstra 算法或 SPFA 算法求解从起点到终点的最短路径。在这个过程中,我们需要记录到每个点的最小费用和最小长度,以及更新每条边的最小费用和最小长度。 4. 最后,我们输出从起点到终点的最短路径长度即可。 需要注意的是,在使用 Dijkstra 算法或 SPFA 算法时,需要对每个点的最小费用和最小长度进行松弛操作。具体来说,当我们从一个点 u 经过一条边 (u,v) 到达另一个点 v 时,如果新的费用和长度比原来的小,则需要更新到达 v 的最小费用和最小长度,并将 v 加入到优先队列(Dijkstra 算法)或队列(SPFA 算法)中。 此外,还需要注意处理边权为 0 或负数的情况,以及处理无法到达终点的情况。 代码实现可以参考以下样例代码: ```c++ #include <cstdio> #include <cstring> #include <queue> #include <vector> using namespace std; const int MAXN = 1005, MAXM = 20005, INF = 0x3f3f3f3f; int n, m, s, t, cnt; int head[MAXN], dis[MAXN], vis[MAXN]; struct Edge { int v, w, c, nxt; } e[MAXM]; void addEdge(int u, int v, int w, int c) { e[++cnt].v = v, e[cnt].w = w, e[cnt].c = c, e[cnt].nxt = head[u], head[u] = cnt; } void dijkstra() { priority_queue<pair<int, int>, vector<pair<int, int>>, greater<pair<int, int>>> q; memset(dis, 0x3f, sizeof(dis)); memset(vis, 0, sizeof(vis)); dis[s] = 0; q.push(make_pair(0, s)); while (!q.empty()) { int u = q.top().second; q.pop(); if (vis[u]) continue; vis[u] = 1; for (int i = head[u]; i != -1; i = e[i].nxt) { int v = e[i].v, w = e[i].w, c = e[i].c; if (dis[u] + w < dis[v] && c >= dis[u] + w) { dis[v] = dis[u] + w; q.push(make_pair(dis[v], v)); } } } } int main() { memset(head, -1, sizeof(head)); scanf("%d %d %d %d", &n, &m, &s, &t); for (int i = 1; i <= m; i++) { int u, v, w, c; scanf("%d %d %d %d", &u, &v, &w, &c); addEdge(u, v, w, c); addEdge(v, u, w, c); } dijkstra(); if (dis[t] == INF) printf("-1\n"); else printf("%d\n", dis[t]); return 0; } ```

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值