BUAACTF2023 Writeup by Joooook
目录
可以从队名猜一下博主是哪里人(no offline attack)
Misc
Which Element
给了Element.pcapng,从TCP流里面找到一个流,包含了四个文件
根据提示,Google到https://www.dcode.fr/hexahue-cipher
按照passwd得到密码3.1415
打开flag.zip得到如下三个文件
观察flag1和flag2的大小,猜是盲水印https://github.com/chishaxie/BlindWaterMark#blindwatermark,一开始用python3的脚本跑,跑出来是一坨
之后看到了一篇文章
https://www.cnblogs.com/Flat-White/p/13517001.html
才知道python2和python3跑出来的结果不一样,之后加上参数—oldseed跑一下就出来了
*hint文件结尾有一个提示,盲水印解不出来的时候盯着水银计都要盯麻了,很后来才想到应该是谐音"水印"?
chatgpt
穷举验证。比较懒脚本写的十分丑陋
import hashlib
def sha(head):
h = hashlib.sha256()
h.update((head + tail).encode())
str = h.hexdigest()
if str == result:
print(head)
return head
return 0
hashstr="sha256(XXXX+srwxZ1nUFH2Cl8qu) == 798421dfdb693f3aeac37918815512086b393ebdc002e6193ae6b06783e346f5"
chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + "abcdefghijklmnopqrstuvwxyz" + "0123456789" # A-Z a-z 0-9
tail = hashstr.split('+')[1][:28] # 原字符串的尾部
result = hashstr.split('==')[1].strip() # hash值
head=0
for ch1 in chars:
if head != 0:
break
for ch2 in chars:
if head != 0:
break
for ch3 in chars:
if head != 0:
break
for ch4 in chars:
head=sha(ch1 + ch2 + ch3 + ch4)
if head!=0:
break
进入之后一步步先答题。
这里就随便举几个问题,有几个问题我一直没找到正确答案,反正够了五题就行
Do you know the city where the CISCN-CTF finals will be held this year? Answer in English with the first letter capital
answer > Hefei
Do you know the city where we played CISCN-CTF finals last year? Answer in English with the first letter capital
answer > Beijing
Do you know the final place we've got in the *CTF 2022?
answer > 11
Do you know the amount of the prize for the team who get 1st place in the CTF we played last month in Hangzhou?
answer > 150000
出题人住在六楼,穷举出来了= =准备进行offline attack(x
之后会得到第一段flag
BUAACTF{C0Ngr@~tuLati0N5_Y0u_R~
解/pss,一开始一直想着石头剪刀布的选择环节有没有什么神奇的漏洞,后来出题人提醒了一下,石头剪刀布这块是不公平的,才想到去赌钱这块研究,稍微试了一下发现负数可以,-10000000000输完就变正的了
解/hard-pss,也是解了好久,输入字母什么的都"No hack",之后再问了下出题人,提示了一下数据类型,Google了python数据类型发现把复数给忘了,-10000000000i,结束
flag为
BUAACTF{C0Ngr@~tuLati0N5_Y0u_R~4~r3a1_MA5Ter_of_0ur_s1llY_CH@tgpT__5_L0vvvvv3_fr0m_M1sc!}
zhuzhu
打开有四个文件
根据enc1.m脚本,gift4you用matlab导入数据,直接imshow(Img);即可
zhuzhu's revenge
这次的gift里面没有了Img数据,按照脚本逆向就行。先逆向enc2
from Crypto.Cipher import AES
from Crypto.Util.Padding import pad,unpad
from Crypto.Util.number import long_to_bytes
import os
with open('enc.png', 'rb') as f:
data = f.read()
key = long_to_bytes(0x839fff08948936e7d78c5105dde1e95a)
print(AES.block_size)
print(key)
cipher = AES.new(key, AES.MODE_CBC,iv=b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00')
decrypted_data = unpad(cipher.decrypt(data),AES.block_size)
with open('wm.png', 'wb') as f:
f.write(decrypted_data)
由于不知道iv,按照CBC的解密规则,最后的16字节是解不出来的,不过没关系,png文件的前16字节是固定的,把16字节改一下就能得到wm.png
之后matlab逆向enc1.m就行
[U,S,V]=svd(LL);
IW=imread('wm.png');
IW=im2double(IW);
[LL2,LH,HL,HH]=dwt2(IW,'haar');
S2=inv(U)*LL2*inv(V);
Sw=1.945525*(S2-S);
Sigma = diag(Sw);
A_reconstructed = Uw * diag(Sigma) * Vw';
imshow(A_reconstructed);
这里关于svd的逆向是问chatgpt出来的。(实在线性代数没学好
得到了
*这里感谢一下马老师,因为我的matlab没有小波变换包,他的有,所以属于人肉调试器了。
Screenshot
给了这样一张图片,社交平台显然是小蓝鸟了。
根据推文和信息页导到github,一开始属实没头绪,在博客也找了半天,后来@zzzzzzc提示了一下,在历史修改里找,根据图片给的日期是4-19,主要看前三个
在屏幕截图里还有一个image 接口网站,在代码里很常出现,合理猜测是这个网站,所以直接搜loli,一个个找,可以在某个commit里面找到这一段https://smms.app/image/KA3Cm4z7hWDncyk
carzymaze
爆破验证
给了750x750矩阵,要求移动只能右,右上,右下移动,要达到target,而且时间限制只有1s,显然全部用回溯算法不可行,所以一开始想看看,target大概在什么范围里,所以先求最大和最小。
一开始是用递归算法,要求某点最大值即求该点前三点的各自最大值
def maxpoint(x,y):#x是列数,y是行数
if x == 0:
return mat[x][y]
if y == 0:
ls = [0, maxpoint(x-1,y), maxpoint(x-1,y + 1)]
elif y == 749:
ls = [maxpoint(x-1,y - 1), maxpoint(x-1,y), 0]
else:
ls = [maxpoint(x-1,y - 1), maxpoint(x-1,y), maxpoint(x-1,y + 1)]
return mat[x][y]+max(ls)
求出最大最小范围之后,发现target值很接近最大路径点的值,所以考虑倒回去几行,也就是前N列求取最大点,后750-N列以第N列作为起点,用回溯算法求后750-N列,看看能不能正好对上target。
而且上面的递归算法会发现有很大的冗余,很多点的的max都算了好几次,所以改用动态规划,保证每个点的最大路径都只算了一次。
for i in range(1,N):
for j in range(0, 750):
if j == 0:
ls= [mat[i-1][j], mat[i-1][j+1]]
elif j == 749:
ls= [mat[i-1][j], mat[i - 1][j-1]]
else:
ls= [mat[i-1][j - 1], mat[i-1][j], mat[i-1][j+1]]
mat[i][j] += max(ls)
回溯算法如下
def dfs(mat,x,y,target,now,route):#x代表列数,y代表行数,now代表现在值的大小
if x==749:
if now==target:
return route
return 0
for i in range(3):
if 0<=y-1+i<750:
res=dfs(mat,x+1,y-1+i,target,now+mat[x+1][y-1+i],route+[y-1+i])
if res!=0:
return res
return 0
测试当N=744的时候,就能跑出来了,测试的时候运气好,一次就出了,没考虑成功率的事情,后来和出题人和Z神@zeroc聊了一下才发现不是百分百出的,基本上7次左右能出1次,可以试试把N调小,保证在1s的时间限制里面提高成功率。
最终脚本如下
import hashlib
import string
import time
import nclib
import numpy
def dfs(mat,x,y,target,now,route):
if x==749:
if now==target:
return route
return 0
for i in range(3):
if 0<=y-1+i<750:
res=dfs(mat,x+1,y-1+i,target,now+mat[x+1][y-1+i],route+[y-1+i])
if res!=0:
return res
return 0
def sha(head):
h = hashlib.sha256()
h.update((head + tail).encode())
str = h.hexdigest()
if str == result:
print(head)
return head
return 0
"""
以下是过验证
"""
TO=2
obj=nclib.Netcat('10.212.27.23',25553)
rc=obj.recv_line(timeout=TO).decode()
chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + "abcdefghijklmnopqrstuvwxyz" + "0123456789" # A-Z a-z 0-9
tail = rc.split('+')[2][:16] # 原字符串的尾部
result = rc.split('==')[1].strip() # hash值
head=0
rc=obj.recv_line(timeout=TO).decode()
print(rc)
for ch1 in chars:
if head != 0:
break
for ch2 in chars:
if head != 0:
break
for ch3 in chars:
if head != 0:
break
for ch4 in chars:
head=sha(ch1 + ch2 + ch3 + ch4)
if head!=0:
break
obj.send(head.encode()+b'\n')
rc=obj.recv_all(timeout=TO)
print(rc)
"""
以下是创建连接
"""
print('start')
start=time.time()#计时
obj.send_line(b'2')
rc = obj.recv_line()
target=int(rc[rc.decode().find('is')+3:rc.decode().find('.')].decode())
print(target)
mat=[]
"""
以下是接收数据部分
"""
for i in range(750):
rc = obj.recv_line().decode()
mat.append(list(map(int, rc.split())))
mat=numpy.transpose(mat).tolist()
v=[]#验证部分,可以删掉
for i in range(750):
tmp=[]
for j in range(750):
tmp.append(mat[i][j])
v.append(tmp)
"""
以下是求解前半段部分
"""
for i in range(1,744):
for j in range(0, 750):
if j == 0:
ls= [mat[i-1][j], mat[i-1][j+1]]
elif j == 749:
ls= [mat[i-1][j], mat[i - 1][j-1]]
else:
ls= [mat[i-1][j - 1], mat[i-1][j], mat[i-1][j+1]]
mat[i][j] += max(ls)
"""
以下是求解后半段路径部分
"""
secondroute=0
midpoint=0
for i in range(750):
secondroute=dfs(mat,743,i,target,mat[743][i],[])
if secondroute:
print(secondroute)
midpoint=i
break
"""
以下是反求前半段路径部分
"""
route=[]
index = midpoint
route.append(index)
for i in range(742, -1, -1):
if index == 0:
ls = [0, mat[i][index], mat[i][index + 1]]
index = index - 1 + ls.index(max(ls))
route.append(index)
elif index == 749:
ls = [mat[i][index - 1], mat[i][index], 0]
index = index - 1 + ls.index(max(ls))
route.append(index)
else:
ls=[mat[i][index-1],mat[i][index], mat[i][index +1]]
index = index-1 + ls.index(max(ls))
route.append(index)
route.reverse()
sum=0#验证部分
for i,j in enumerate(route+secondroute):
sum+=v[i][j]
assert sum==target
path = list(map(str, route+secondroute))
print(time.time()-start)
obj.send_line(' '.join(path).encode())
print(obj.recv_all().decode())
print(obj.recv_all().decode())
*一开始觉得反求路径比较花时间,所有采用直接记录路径,后来测试了一下,每个点的路径都记录还不如反求来得快= =
MC
给了两个文件
system.raw是内存镜像文件,用volatility工具来分析,这里给了很详细的介绍https://blog.csdn.net/m0_68012373/article/details/127419463,github项目https://github.com/volatilityfoundation
volatility有针对python3的版本volatility3,但是觉得没有python2的版本好用,由于现在kali都是自带python3,所以安装python2的库就有点麻烦,需要先安装pip2,不然安装的都是python3的库,这里有详细介绍安装pip2:https://blog.csdn.net/huayimy/article/details/128338899,要注意一下pip2装完在/home/username/.local/bin下,需要进入文件夹才能用。
用volatility扫一下文件,hint.txt和devcpp.txt比较可疑,还看到一个VeraCrypt.exe,这里我忘记这是个硬盘挂载程序,我还以为是自己写的加密程序,dump也dump不下来,卡了半天。
扫一下进程,没看到什么太可疑的,有cmd、notepad、mspaint可以再查查
notepad内容,和devcpp.txt内容一样的
cmd没扫到什么
mspaint将进程dump下来之后后缀改成.data然后用gimp打开
width=1920的时候会找到这个,反转之后和hint.txt的内容一样
总结一下,总共拿到的信息有
devcpp.txt
Administrator:admin:4cb9c8a8048fd02294477fcb1a41191a::
sjh123:sjh:a1562f9392c4b70d4ad3bf9d116fa71f::c2bd324d229b098b8175954812a8d8a2
ch3v4l:caiji:8a24367a1f46c141048752f2d5bbd14b::022da9d18852ffb0ea9a0b8020046733
hint.txt
LP49.13_m7sdq0#J
用VeraCrypt挂载Visual Studio,密码就是LP49.13_m7sdq0#J
得到压缩文件DontLookInThisFile.zip,也需要密码
用https://www.somd5.com/解密之前得到的密码hash
Administrator:admin:4cb9c8a8048fd02294477fcb1a41191a(changeme)
sjh123:sjh:a1562f9392c4b70d4ad3bf9d116fa71f(songjinghang123)
ch3v4l:caiji:8a24367a1f46c141048752f2d5bbd14b(P@ssw0rd!)
用P@ssw0rd!解密DontLookInThisFile.zip
得到traffic.pcapng
这里提示文件可能损坏,但是没关系,还可以继续分析,进去之后结合提示发现是向两个UDP端口轮流转发
怀疑是把文件分成了两部分分别发向两个端口,将两部分文件取下来观察
发现了非常熟悉的504B0403,发现文件是以4字节为单位,轮流发给12345和12346端口,同时发给12345端口的字节是逆序,写脚本结合后又得到了一个zip
file1=open('5',mode='rb')
file2=open('6',mode='rb')
data1=file1.read()
data2=file2.read()
data=b''
for i in range(len(data1)//4):
for j in range(4):
data += data1[i * 4+(3-j):i * 4+(4-j)]
for j in range(4):
data += data2[i* 4+j:i*4+j+1]
file3=open('7.zip',mode='wb')
file3.write(data)
显然flag在里面,可惜又要密码,这里的密码找了好一段时间。
还记得之前的pcapng文件损坏提示吗,用二进制打开pcapng文件,发现最下面有一大段数据
将下面的数据提取出来,一开始一直在Google evoM,esuoM,YALED,突然才看出来这是反的,分别是Move,Mouse,DELAY,把所有数据都反过来就是
google了一下很像是鼠标轨迹记录,找记录鼠标的软件,发现Jitbit Macro Recorder非常像要找到软件。
成功导入数据,打开画板,见证奇迹的时刻,鼠标自己动起来把密码写出来了
密码是083b18030440da0b785d2beb1c5bc4e3
用这个密码解压刚刚有flag.png的压缩包,本来以为已经到头了,结果还有一层
这个编码方式感觉是在某处见过但是想不起来,直接google搜图,发现这个是MaxiCode:https://de.wikipedia.org/wiki/MaxiCode,上网找在线解码,直接扔进去是不行的,因为MaxiCode中间还有个同心圆,
随便什么内容生成一张,之后用ps把同心圆放好位置得到最终的图片
https://www.dynamsoft.com/barcode-reader/barcode-types/maxicode/扔进去解码
Crypto
Block Cipher
加密脚本如下
import operator
import random
import re
from functools import reduce
from secret import flag
def pad(s):
padding_length = (8 - len(s)) % 8
return s + chr(padding_length) * padding_length
def xor(a, b):
assert len(a) == len(b)
return bytes(map(operator.xor, a, b))
def encrypt(s):
iv = bytes(random.randint(0, 255) for _ in range(8))
key = bytes(random.randint(0, 255) for _ in range(8))
parts = list(map(str.encode, map(pad, re.findall(r'.{1,8}', s))))
results = []
for index, part in enumerate(parts):
results.append(reduce(xor, [part, iv if index == 0 else results[-1], key]))
return iv, key, results
iv, key, parts = encrypt(flag)
print(f"iv = {iv}")
print(f"key = {key}")
print(f"parts = {parts}")
"""
iv = b'\xba=y\xa3\xc6)\xcf\xf7'
key = b'}6E\xeb(\x91\x08\xa0'
parts = [b'\x85^}\t\xad\xec\x81,', b'\xba\x04W\xa1\xee"\xea\xc5', b'\xb7ZW\x18\x99\x82\xd6:', b'\x99\x03}\x9c\xde|\xb1\xc5', b'\xa1Tk.\x8b\xee\xbaf']
"""
很简单的异或分组加密
if __name__ == '__main__':
results=[]
parts.reverse()
for index, part in enumerate(parts):
results.append(reduce(xor, [part, iv if index == len(parts)-1 else parts[index+1], key]))
results.reverse()
print(''.join(list(map(bytes.decode,results))))
Math
challenge1
$$
leak=2p+k(q-1)
$$
暴力k即可
challenge2
$$
ed = 1 +k\varphi(n),leak = d+p+q\(e-k)\varphi(n) = e(n-leak+1)+1
$$
爆破k找到求出phi
challenge3
$$
leak = p^q \mod n + q^p \mod n\
leak = p+q
$$
import gmpy2
from Crypto.Util.number import *
from sympy import *
def c1():
#leak = (n + p) % (q-1)
e = 65537
c = 112603855258130214477579025724347852348654764350616283814228948696384649423509219700593580927436981131009253539258926656199553162798353424478456071312399854016855769730451167219095385224786982617931318336289395411790738746409786626741975659380781585117554886308860499453339049544449101737359477485341756095685
n = 156554201159196482703626229456180336082601822691986590744780894650795965611900555514134060137082990473725887830010623065818450185823730822009287525836754951877076875771488298693374835052451569073414980813577454117535462658662205409616157954907990248587384015512293862003341283487632375295475440935688094002617
leak = 3118625448174043604744529027319786605170955560813558072118277982181159822887603847530258597519488281509287189431662221215460717933909531417536796929018210
p,q = symbols("p q")
eq = [p*q-n,2*(p-q+1)-leak]
result = list(nonlinsolve(eq, [p, q]))
p,q =int(result[1][0]),int(result[1][1])
phi =(p-1)*(q-1)
d = gmpy2.invert(e,phi)
m = pow(c,d,n)
flag1 = long_to_bytes(m)
print(flag1)
return flag1[:14]
def c2():
#leak = d + p + q
e = 65537
c = 93978686378293828086191790122529919325716419332933686245770487684123528049983735994708527178252488091788860014057317894385773677231842636035334795883708882974841442069339023354766426009980023194357039539192634379156228433248998982297141837057318199779573613425738350307108121215320184191104501916977179892819
n = 113314208967522806109377775391344774798246647245609527530250842162998260734597302913497036905918762468707747032503484781341144969937434755205814353751104297147933894782213419095409180897649121217538219106453769844032495640631837946695496195968385913896925163019434775707488581307013382934251169925612458912701
leak = 92227188008859636551571172271384433717034261725865660741125015972908284264524691925307008950647919630793901680894279288364099880089609034246812387558747800115003457695305687069115350786189814893940014083795308121668729848809030525554558177868795576913946290045467615215700823448216007469394406486122762340799
for k in range(1,65537):
if (e*(n-leak+1)+1)%(e-k)==0:
phi = (e*(n-leak+1)+1)//(e-k)
d = gmpy2.invert(e, phi)
m = pow(c,d,n)
flag2 = long_to_bytes(m)
print(flag2)
return flag2[:14]
def c3():
#leak = (pow(p, q, n) + pow(q, p, n)) % n
e = 65537
c = 57267353578218922590866753594427040157686249506833227002343780474377875165575891892094097781872920937467859618146840876344931100376140310307914367629844308420600760564342032844568436492313668901310573464694813393787808054157639041836407430313117254669011492042858846089516806744148324079065759777653798065470
n = 72559362705546768966148503007933634888094434921837745715507408562619698919824091078235194244259688366819227638823888641073251820477424150670604138979273724058577742415825092468613527614237094273848230891355347434776123637086037409586867384122532873818945115846995272181542565199089210266795364142054521432969
leak = 17238032591075070541888189318082997841936669281409415876040204774279914799918723435203750385624612322410269658410156575528112867456120069790169980041169270
p,q = symbols("p q")
eq = [p*q-n,p+q-leak]
result = list(nonlinsolve(eq, [p, q]))
p,q =int(result[1][0]),int(result[1][1])
phi =(p-1)*(q-1)
d = gmpy2.invert(e,phi)
m = pow(c,d,n)
flag3 = long_to_bytes(m)
print(flag3)
return flag3[:14]
if __name__ == '__main__':
print(c1()+c2()+c3())
#b'flag{512_b1t3_R5A_15_n0_l0n63r_53cur3!!!!}'
KeyExchange
加密脚本如下
from Crypto.Util.number import *
from Crypto.Cipher import AES
from Crypto.Util.Padding import pad
from hashlib import sha256
from random import randint
from libnum import invmod, n2s
from secret import flag
def LCG(a, s, b, p):
return (a * s + b) % p
def LCG_n(a, s, b, p, n):
for _ in range(n):
s = LCG(a, s, b, p)
return s
p = getPrime(1024)
a = getPrime(512)
b = getPrime(512)
s = a * b
assert s < p
sk_a = randint(1, p)
sk_b = randint(1, p)
pk_a = LCG_n(a, s, b, p, sk_a)
pk_b = LCG_n(a, s, b, p, sk_b)
key_a = LCG_n(a, pk_b, b, p, sk_a)
key_b = LCG_n(a, pk_a, b, p, sk_b)
assert key_a == key_b
print("p: ", p)
print("s: ", s)
print("pka: ", pk_a)
print("pkb: ", pk_b)
leak = a ** 5 - b ** 3
print("leak: ", leak)
key = sha256(str(key_a + s).encode()).digest()[:16]
iv = bytes(n2s(randint(0, 2 ** 128)))
cipher = AES.new(key, AES.MODE_CBC, iv=iv)
c = cipher.encrypt(pad(flag, 16))
print("iv: ", iv.hex())
print("c: ", c)
"""
p: 110045941581246566163852236169863726493322518614291259738650243772537777220447239564993293840178002270803358414801874876416861655681706091960526864981702297311975108477231430850046449802619712390151394990272883738498246287828012579651055043742174921894177086628278972268977919519134723993840866617728971125501
s: 103083435269546339253719292770367969157635923310796947624819807524514689714230038488629636887588073083408362510426148038690685619752778205013088349018307009939108678198113606930036410404549808586987764543060403391315169316818289657581541763263327670573813529568272160960214773113382466107788108424376942305179
pk_a: 104249886397359747249273092453488313866919664956033370839273165940023672119280749446507600277914299719461300855987124305389892009846065290488788845690592952592538823773157304003987554741970547804984472643400001801640946933980112156093413786073659705669257814748840719625590155866880068232234070029543099326008
pk_b: 73943176009325878591909652784371325868093784459904155452171082544674415915957790552228923092972996591011414909913381038800791214834557764389213534028507829053436031472854955231610581309098048200856194242071844838535785588222371659431886892907007805510914868507217421041100993332508303713741693778352086616092
leak: 29040777564893836055576935754958349303880224434489606637225123765874859084299305072404829973337935904266301029232783978449958360875461566254630838251549934932706914467564998782070045758886430608783726619661530870388938391442492043731894115397400015449950051951769344001061013880264004181568996494392695873947102851715933737547680797285706746028093745905399081868517548754953070086517995947839613006470389399208171194194504365074914947024889949570680226668773867669172700292722732348438746947857754535156207414199788546164281701388891687436649862876851080136461979528906440369488791240780833837652254029174364644864043838626162283607643013213942931346399529512644954169730170267869381208558883655869400666658812252861841021468606579935712846111089021383151894750921351386
iv: e759b23de290a6d4ea11dab4b0c306f3
c: b'\xa8\x83\x8b,\x14\x1ad\xfb\xfd\x12\xc6\xc1=\xa0+k\x05\xc7\xc9\xed\xde\xca>\x04\xe1;\xcc\xc1\xb6\x9a+\xe66d\xcf\x9b\xfftp\x81\xab\xbf(\x02\xcd\x0b\xa4\xc0Lpg\x83F\x82l\xad2\xdcI\x1ab\x08\x88\n'
"""
leak是$a5-b3$,使用LCG算法,一开始在嗯凑,死活凑不出来这个五次方和三次方,后来利用s=ab直接拿sage解
s=103083435269546339253719292770367969157635923310796947624819807524514689714230038488629636887588073083408362510426148038690685619752778205013088349018307009939108678198113606930036410404549808586987764543060403391315169316818289657581541763263327670573813529568272160960214773113382466107788108424376942305179
leak=29040777564893836055576935754958349303880224434489606637225123765874859084299305072404829973337935904266301029232783978449958360875461566254630838251549934932706914467564998782070045758886430608783726619661530870388938391442492043731894115397400015449950051951769344001061013880264004181568996494392695873947102851715933737547680797285706746028093745905399081868517548754953070086517995947839613006470389399208171194194504365074914947024889949570680226668773867669172700292722732348438746947857754535156207414199788546164281701388891687436649862876851080136461979528906440369488791240780833837652254029174364644864043838626162283607643013213942931346399529512644954169730170267869381208558883655869400666658812252861841021468606579935712846111089021383151894750921351386
p=110045941581246566163852236169863726493322518614291259738650243772537777220447239564993293840178002270803358414801874876416861655681706091960526864981702297311975108477231430850046449802619712390151394990272883738498246287828012579651055043742174921894177086628278972268977919519134723993840866617728971125501
R.<x>=Zmod(p)[]
f=x^8-s^3-leak*x^3
f.roots()
[(79142966538854891253816238634366173066278287664013536240916671084997778589773793140001544363310316918632829086943283144627774718204753994292504213434137874672696179958842612700390778571855461447956745271534414513519955909525348876621942536354689971704197321157287422646981017075975603899282894413072163005281,
1),
(7809111936301730050325531123556941524419043237553061253494514954511014517669231983792610985652302681729865258538952534506348431869920157739027505606648803,
1)]
解出ab就很好解了,出题人的博客里给出了很详细的证明https://blog.csdn.net/weixin_51122085/article/details/126231484(exchange题),写的很好,膜膜大佬
脚本如下
from Crypto.Cipher import AES
from Crypto.Util.Padding import unpad
from Crypto.Util.number import long_to_bytes
from hashlib import sha256
import gmpy2
pk_a=104249886397359747249273092453488313866919664956033370839273165940023672119280749446507600277914299719461300855987124305389892009846065290488788845690592952592538823773157304003987554741970547804984472643400001801640946933980112156093413786073659705669257814748840719625590155866880068232234070029543099326008
pk_b=73943176009325878591909652784371325868093784459904155452171082544674415915957790552228923092972996591011414909913381038800791214834557764389213534028507829053436031472854955231610581309098048200856194242071844838535785588222371659431886892907007805510914868507217421041100993332508303713741693778352086616092
leak=29040777564893836055576935754958349303880224434489606637225123765874859084299305072404829973337935904266301029232783978449958360875461566254630838251549934932706914467564998782070045758886430608783726619661530870388938391442492043731894115397400015449950051951769344001061013880264004181568996494392695873947102851715933737547680797285706746028093745905399081868517548754953070086517995947839613006470389399208171194194504365074914947024889949570680226668773867669172700292722732348438746947857754535156207414199788546164281701388891687436649862876851080136461979528906440369488791240780833837652254029174364644864043838626162283607643013213942931346399529512644954169730170267869381208558883655869400666658812252861841021468606579935712846111089021383151894750921351386
iv=b'\xe7\x59\xb2\x3d\xe2\x90\xa6\xd4\xea\x11\xda\xb4\xb0\xc3\x06\xf3'
c=b'\xa8\x83\x8b,\x14\x1ad\xfb\xfd\x12\xc6\xc1=\xa0+k\x05\xc7\xc9\xed\xde\xca>\x04\xe1;\xcc\xc1\xb6\x9a+\xe66d\xcf\x9b\xfftp\x81\xab\xbf(\x02\xcd\x0b\xa4\xc0Lpg\x83F\x82l\xad2\xdcI\x1ab\x08\x88\n'
s=103083435269546339253719292770367969157635923310796947624819807524514689714230038488629636887588073083408362510426148038690685619752778205013088349018307009939108678198113606930036410404549808586987764543060403391315169316818289657581541763263327670573813529568272160960214773113382466107788108424376942305179
leak=29040777564893836055576935754958349303880224434489606637225123765874859084299305072404829973337935904266301029232783978449958360875461566254630838251549934932706914467564998782070045758886430608783726619661530870388938391442492043731894115397400015449950051951769344001061013880264004181568996494392695873947102851715933737547680797285706746028093745905399081868517548754953070086517995947839613006470389399208171194194504365074914947024889949570680226668773867669172700292722732348438746947857754535156207414199788546164281701388891687436649862876851080136461979528906440369488791240780833837652254029174364644864043838626162283607643013213942931346399529512644954169730170267869381208558883655869400666658812252861841021468606579935712846111089021383151894750921351386
p=110045941581246566163852236169863726493322518614291259738650243772537777220447239564993293840178002270803358414801874876416861655681706091960526864981702297311975108477231430850046449802619712390151394990272883738498246287828012579651055043742174921894177086628278972268977919519134723993840866617728971125501
a=7809111936301730050325531123556941524419043237553061253494514954511014517669231983792610985652302681729865258538952534506348431869920157739027505606648803
s=103083435269546339253719292770367969157635923310796947624819807524514689714230038488629636887588073083408362510426148038690685619752778205013088349018307009939108678198113606930036410404549808586987764543060403391315169316818289657581541763263327670573813529568272160960214773113382466107788108424376942305179
b=s//a
ap=(gmpy2.invert((a-1)*s+b,p)*((a-1)*pk_b+b) )%p
K=(ap *pk_a+gmpy2.invert(a-1,p)*(ap-1)*b)%p
key = sha256(str(K + s).encode()).digest()[:16]
cipher = AES.new(key, AES.MODE_CBC, iv=iv)
m = cipher.decrypt(c)
print(m)
Web
mota
记得Hgame出过一个mota,记忆很深,当时以为要调用出测试的程序才能出flag,没想到是在资源里找
在Events.js里面
atob就是base64
import base64
print("BUAACTF{HT",end='')
s="NV9tb3RhXzFz"
print(base64.decodebytes(s.encode()).decode(),end='')
l=[95, 115, 48, 95, 102, 117, 110, 33, 125]
for i in l:
print(chr(i),end='')
Reverse
oneQuiz's revenge*
java逆向好
用GDA直接打开apk文件,在资源里找了半天也没找到什么字符串,既然没有字符串就来找逻辑。
显然是一个走迷宫,安装一下apk
照着走就对了,只是最后一步向右走的时候只用走一步就出了
flag为VaaaVaVVaaaaaVVVVAAJJAAVVVAVVaaaaaJa
Snake
CE修改器就够了,直接改分
Minesweep
看似是自己扫,实则是自动扫。主要逻辑在encrypt和Game里
点开Game里面有个Sweep,再进去,会发现即使输入了x和y,之后也会赋值成0,所以扫雷都是自动的。
本质上就是一个个扫过,每个点都要加上get_num的值,用python还原一下矩阵。
mine=[['a','a','b','b','a','a','b','0'],
['a','b','b','a','b','a','a','0'],
['a','b','a','b','a','b','b','0'],
['a','a','a','a','b','b','b','0'],
['a','a','b','a','b','a','b','0'],
['a','a','a','b','b','a','b','0'],
['a','b','b','b','a','a','b','0'],
['0','0','0','0','0','0','0','0']]
def swep():
x = 0
y = 0
while True:
if y == 6:
x += 1
y = 0
else:
y += 1
if x > 6:
break
ret = get_num(x, y)
mine[x][y] = chr(ret+ord(mine[x][y]))
return 0
def get_num(x, y):
count = 0
if x-1 >= 0:
if y-1 >= 0:
if mine[x-1][y-1] == 'a':
count = 1
if mine[x-1][y] == 'a':
count += 1
if mine[x-1][y+1] == 'a':
count += 1
if y - 1 >= 0:
if mine[x][y-1] == 'a':
count += 1
if mine[x][y+1] == 'a':
count += 1
if y - 1 >= 0:
if mine[x+1][y-1] == 'a':
count += 1
if mine[x+1][y] == 'a':
count += 1
if mine[x+1][y+1] == 'a':
count += 1
return count
if __name__ == '__main__':
swep()
for i in mine:
print(''.join(i))
输出矩阵为
{"accddcd",
"cedcdca",
"cfdebcc",
"dddbdcc",
"ddebdbc",
"ccadecc",
"adcdbac"};
接下来看encrypt的逻辑
就是简单的和矩阵异或,而且就前6个进行了加密,拿C暴力一下就出来了
obfu
看晕了,慢慢调,试了很多都不能很好的反混淆。Chrome打开F12在Source里+New Snippets就可以开始调了。
经过一会下断点调试,基本确定在了这个switch这里
switch里面是按照0,3,1,2,4,而且试了一下这个顺序和flag没关系,是固定的,那么就一个个看
0是一个array,先放着
case 3里面出现了一个btoa,说明和base64有关,基本上可以猜出是将字符串转base64
这个地方调用了一个函数,调用的方式是_0x3519a3[_ 0x254fd3(0x174)],先看里面这层_0x254fd3(0x174),调试器告诉我们结果是
然后外面一层,发现这是个函数组,里面的函数逻辑都很简单。
aklXq就是调用函数。之后继续分析,全部都是类似的操作灵活使用调试器,会发现case3就是map(charcodeat,split(btoa(flag))),就是将输入的flag转base64转ascii。
休息一下,瞪眼调实在是太累了,不知道有没有什么好的反混淆,反正我是google了一堆都不太行
下一个case1,慢慢调可以看出是个循环,循环里面是个异或
这里调用了jxrsD,就是个减法
所以就是一个循环
for i in range(1,32):
data[i]^=data[i-1]
继续看case2
就是一个循环比较,基本上到这里就可以出了,调试台里面有正确的flag加密结果
脚本
import base64
ls=[81, 61, 107, 41, 120, 45, 99, 54, 100, 10, 126, 53, 123, 51, 106, 90, 57, 11, 69, 60, 113, 41, 107, 91, 3, 49, 1, 49, 80, 42, 100, 2, 96, 52, 122, 28, 69, 118, 63, 15, 106, 4, 111, 7, 97, 48,
13, 48]
s=chr(81)
for i in range(1,len(ls)):
s+=chr(ls[i]^ls[i-1])
print(base64.decodebytes(s.encode()))
#b'BUAACTF{J4v4scr1pt_m4k3_m3_cr4zy!}'