BUAACTF2023 Writeup题解 by Joooook

发布时间 2023-04-27 13:13:08作者: Joooook

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!}'