Hackergame2021 部分WriteUp

yyyのWriteUp


一点碎碎念

这是我的第一次Hackergame/CTF比赛,感觉很有意思。前几天心态有点崩,好在调整过来了。大家还是要把Hackergame当做game。最开心的是认识了好多大佬,抱大腿.jpg

对自己的吐槽:EasyRSA差点做出来,扩展欧几里得写错弃疗了;RAID0卡在软件没有激活码不让保存;和各种小测撞;各种体调不良,饮食睡眠不佳;晚上学校断电没法做,我又是大夜猫子……奇奇怪怪挡路的东西一直不少,归根结底还是太菜了啦。
总之非常感谢@TonyCrane大佬、GodSpeed大佬及群内各成员的鼓励支持帮助(膜不算)。
希望我能早日学会binary和math。


签到

这题二分一下page参数即可。
鼠标连点器


进制十六——参上

使用Hex Editor Neo直接新建文件抄写即可。当然OCR识别也行吧。


去吧!追寻自由的电波

下载音频后可以听出是一段人声,但是语速极快。于是使用Adobe Audition CC打开,效果->伸缩与变调放慢速度,适当调节音高就能开始听写了。可以发现这是NATO Phonetic Alphabet

A ALPHA
B BRAVO
C CHARLIE
D DELTA
E ECHO
F FOXTROT
G GOLF
H HOTEL
I INDIA
J JULIET
K KILO
L LIMA
M MIKE
N NOVEMBER
O OSCAR
P PAPA
Q QUEBEC
R ROMEO
S SIERRA
T TANGO
U UNIFORM
V VICTOR
W WHISKEY
X X-RAY
Y YANKEE
Z ZULU


猫咪问答 Pro Max

General× 杂技√

1. 经典WebArchive 第一行就有答案:20150504

2. https://lug.ustc.edu.cn/wiki/intro/

此处资料显示是4次,但并非最新,我后来手动尝试才得知是5

3. FTP服务器 Obviously,答案是Development Team of Library 后来得知网站首页news头图就有,我还找了好久

4. 谷歌关键词SIGBOVIK2021 Newcomb-Benford直接就能找到原论文http://sigbovik.org/2021/proceedings.pdf 直接Ctrl+F找到文章,发现有14个Figures,排除第一个在Background里的Figure后得到答案为13

5. 谷歌关键词protocol report 找到https://www.rfc-editor.org/rfc/rfc8962.html

6.Reporting Offenses
Send all your reports of possible violations and all tips about wrongdoing to /dev/null. The Protocol Police are listening and will take care of it.

还挺幽默,一开始还真以为有police在listening


卖瓜

20不是3的倍数,乍看似乎不可能用6和9加和得到。但随便试了试,发现9斤的瓜很多很多时会溢出为-9223372036854775808,据此判断为int64溢出,判断应当在此处加以利用。
我们需要让这个数字稍微溢出一点,不能溢出太多。也就是略大于floor(9223372036854775808/9),使得溢出为-9223372036854775808以外的数字,并且让该数字到20的距离能被3整除。之后直接加回20就行(简单小学(?)算数,屡有即将做出来时加过头超过20的惨剧hhh)
灵感来源:CTF 两道web整数溢出题目(猫咪银行和ltshop)


透明的文件

根据题面和文件会发现这是终端的颜色代码,网络搜索终端颜色代码格式后将所有[前补上一个\e,然后echo -e "{内容}"就行啦。记得执行前把终端先用字符填满,不然可能画不完整。


旅行照片

简简单单开个盒
旅游,海滩,汉字,说明这是国内一个海边旅游景点。
蓝色KFC?这可不常见,应该有不少人打卡了吧。
百度搜索蓝色KFC,第一项就是某红书的秦皇岛蒂凡尼蓝秦皇岛网红打卡|国内唯一蒂芙尼蓝色肯德基
百度地图定位发现这家店是某海底世界分店,直接得到电话号码。
https://www.earthol.org/上通过街景发现三个汉字“海|豚|馆”

对照地图,视线和海岸线大约成45°,推测应当在东南方向。进而发现影子西斜,应当在下午/傍晚
通过绘制各个水平线找出灭点可以推测楼层(知乎有些文章有详细说明)。我的方法是找到对面楼“最矩形”变形最少的窗户判断为同一楼层,然后数,发现是13层左右(经尝试发现是14层)


FLAG助力大红包

既然是和ip有关,第一时间想到的就是X-Forwarded-For,果然成功了。因为每个/8ip段(也就是例如255.0.0.0-255.255.255.255)都只能算一次,我们使用BurpSuite的Intruder,将Post数据中的ip和X-Forwarded-For的ip首段打上标记,选择Battering Ram模式(让两处参数一致),构造0~255的字典,自动化访问0.114.114.1141.114.114.1142.114.114.114……255.114.114.114达成刚好256个助力获得flag。由于每次间隔2s,2s*256=512s,小于题目时长限制10min=600s所以是可行的。


Amnesia-轻度失忆

直接putchar()逐个打印绕过即可。


图之上的信息

GraphQL的利用。访问/graphql?query={}发现存在利用可能。查阅资料后得知可以利用内省注入。没有UI界面我直接地址栏输入。换行替换为%0A后,访问/graphql?query={__schema%0A{types%0A{name}}}得到:

{"data":{"__schema":{"types":[{"name":"Query"},{"name":"GNote"},{"name":"Int"},{"name":"String"},{"name":"GUser"},{"name":"Boolean"},{"name":"__Schema"},{"name":"__Type"},{"name":"__TypeKind"},{"name":"__Field"},{"name":"__InputValue"},{"name":"__EnumValue"},{"name":"__Directive"},{"name":"__DirectiveLocation"}]}}}

发现有个GUser类型。接下来访问/graphql?query={__type(name:"GUser"){name%0Afields{name%0Atype{name,kind}}}}爆出字段名privateEmail,然后直接/graphql?query={user(id:1){id,privateEmail}}得到flag.


赛博厨房

  • Level0:直接写
  • Level1:复制粘贴发现有长度限制 goto优化行数
    然后不会了。

阵列恢复大师-RAID5

直接丢进RAID Reconstructor 5里面跑得到镜像文件。Windows上并没法直接读,于是丢进Diskinternals Linux Reader里读文件导出。执行getflag.py却提示Did you recover my data correctly?,疑惑之下换WSL(Kali Linux)执行就成功了。
吐槽:RAID0的XFS需要注册码没法搞。WSL也mount不上,看来还是虚拟机靠谱。


助记词-第一顿大餐

代码审计后发现目的是延迟尽可能高。用BurpSuite改包在POST数据里复制出来很多行注记词提交就有了flag1。就像这样:

[
    "necessary science growth addition",
    "necessary science growth addition",
    (重复好多好多次)
    "necessary science growth addition"
]

如果不行CLEAR再试试。

小坑:token要跟上才能拿到flag,不过有时自动获取的token无效,使得flag只显示为true,因为加号没有URLEncode转义,不知道是不是bug.


马赛克

模拟题。大概思路是先扫一遍,把能确定的先确定下来。之后再dfs剩下的块(不需要全部求出,毕竟这题的二维码纠错拉满了)。
代码很丑对吧QAQ(当时不熟悉ndarray操作,全部当做list做一遍)

from PIL import Image
import math
import numpy as np
# import scipy
import matplotlib.pyplot as plt

SIZE = 627
MSBLOCK = 23
QRBLOCK = 11
X, Y = 103, 137
def ImageToMatrix(filename):
    # 读取图片
    im = Image.open(filename)
    # 显示图片
#     im.show()  
    width,height = im.size
    im = im.convert("L") 
    data = im.getdata()
    data = np.matrix(data,dtype='float')/255.0
    new_data = np.reshape(data,(width,height))
    return new_data
#     new_im = Image.fromarray(new_data)
#     # 显示图片
#     new_im.show()
def MatrixToImage(data):
    data = data*255
    new_im = Image.fromarray(data.astype(np.uint8))
    return new_im
    
pre_arr =  [[0 for col in range(SIZE)] for row in range(SIZE)]
res_arr =  [[1 for col in range(SIZE//QRBLOCK)] for row in range(SIZE//QRBLOCK)]
kimeta =  [[0 for col in range(SIZE//QRBLOCK)] for row in range(SIZE//QRBLOCK)]#钦定了
EACH = int(math.ceil(MSBLOCK/QRBLOCK)) #EACH=3
filename = 'pixelated_qrcode.bmp'
data = ImageToMatrix(filename)
np.set_printoptions(threshold=1145141919810)
for i in range(0,SIZE):
    for j in range(0,SIZE):
            pre_arr[i][j]=data[i].tolist()[0][j]
            if i%QRBLOCK==0 and j%QRBLOCK==0:
                res_arr[i//QRBLOCK][j//QRBLOCK]=int(data.tolist()[i][j])
# print(pre_arr)
def  check(i,j,now):
    if i<=9 or i>=51 or j<=12 or j>=54:
        if not (pre_arr[i*QRBLOCK+3][j*QRBLOCK+3] == now):#随便写的offset
            return False
    if (kimeta[i][j] or vis[i][j]) and res_arr[i][j]!=now:
        return False
    return True


def getRange(i,j):
    return i*QRBLOCK,j*QRBLOCK,(i+1)*QRBLOCK-1,(j+1)*QRBLOCK-1 #x1,y1,x2,y2

vis =  [[0 for col in range(SIZE//QRBLOCK)] for row in range(SIZE//QRBLOCK)]
def calcDelta(Mi,Mj,fillN,comp,LUR,LUC):
    fn = fillN
    for xx in range(LUR,LUR+EACH) :
        for yy in range(LUC,LUC+EACH):
            if not check(xx,yy,fn&1):
                return 114514
            fn = fn>>1
    avr = 0
    qx1,qy1,qx2,qy2 = X+Mi*MSBLOCK,Y+Mj*MSBLOCK,X+(Mi+1)*MSBLOCK-1,Y+(Mj+1)*MSBLOCK-1
    for x in range(LUR,LUR+EACH) :
        for y in range(LUC,LUC+EACH):
            if (fillN&1):
                x1,y1,x2,y2 = getRange(x,y)
                inx1,iny1,inx2,iny2 = max(x1,qx1),max(y1,qy1),min(x2,qx2),min(y2,qy2)
                avr = avr + (iny2-iny1+1)*(inx2-inx1+1)
            fillN = fillN >> 1
    newres = avr/(MSBLOCK*MSBLOCK)
    if (newres < comp-0.1):
        return 1919810
    return abs(math.floor((avr/(MSBLOCK*MSBLOCK))*255)/255-comp)

def putRec(i,j):#放置识别码块
    kimeta[i][j]=1
    res_arr[i][j]=0

    kimeta[i][j+1]=1
    res_arr[i][j+1]=1
    kimeta[i][j-1]=1
    res_arr[i][j-1]=1
    kimeta[i+1][j]=1
    res_arr[i+1][j]=1
    kimeta[i-1][j]=1
    res_arr[i-1][j]=1
    kimeta[i+1][j+1]=1
    res_arr[i+1][j+1]=1
    kimeta[i-1][j-1]=1
    res_arr[i-1][j-1]=1
    kimeta[i+1][j-1]=1
    res_arr[i+1][j-1]=1
    kimeta[i-1][j+1]=1
    res_arr[i-1][j+1]=1

    for x in range(i-2,i+3):
        for y in range(j-2,j+3):
            if abs(x-i)==2 or abs(y-j)==2:
                kimeta[x][y]=1
                res_arr[x][y]=0

putRec(28,28)
putRec(28,50)
putRec(50,50)
putRec(50,28)

for i in range(9,52):
    for j in range(12,55):
        if i==9 or i==51 or j==12 or j==54:
            kimeta[i][j]=1
            res_arr[i][j]=pre_arr[i*QRBLOCK+3][j*QRBLOCK+3]

new_arr =  [[1 for col in range(SIZE)] for row in range(SIZE)]
fked =  [[0 for col in range(20)] for row in range(20)]

cntt = 0
savecnt=0
def dfs(i,j):#从马赛克块的i行j列开始
    global cntt,savecnt
    cntt = cntt+1
    LUR , LUC= (X+i*MSBLOCK)//QRBLOCK , (Y+j*MSBLOCK)//QRBLOCK
    #落在哪个二维码方块上
    if (cntt==200000):#保存中途结果
        for i in range(0,SIZE):
            for j in range(0,SIZE):
                    new_arr[i][j]=res_arr[i//QRBLOCK][j//QRBLOCK]
        MatrixToImage(np.array(new_arr)).save('mid%s.bmp'%savecnt)
        savecnt = savecnt + 1
        cntt = 0
    if i<0 or i>=20 or j<0 or j>=20 or fked[i][j]:
        return False
    fked[i][j]=1
    
    nowMin , nowSol = 114514191981.0 , (1<<(EACH*EACH))-1
    tmpp =  [[0 for col in range(3)] for row in range(3)]
    for x in range(LUR,LUR+EACH) :
        for y in range(LUC,LUC+EACH):
            tmpp[x-LUR][y-LUC]=vis[x][y]
    for filN in range(0,1<<(EACH*EACH)):#枚举每个马赛克块影响到的3*3=9个QR块
        tmp = calcDelta(i, j, filN, pre_arr[X+i*MSBLOCK+2][Y+j*MSBLOCK+2],LUR,LUC)#同样是乱写的offset+2
        if tmp < 0.00000001:
            nowSol = filN
            for x in range(LUR,LUR+EACH) :
                for y in range(LUC,LUC+EACH):
                    vis[x][y] = 1
                    res_arr[x][y]=nowSol&1
                    nowSol = nowSol>>1
            if ((i+1<20 and (not fked[i+1][j]) and dfs(i+1,j)) or (j+1<20 and (not fked[i][j+1]) and dfs(i,j+1)) or (i-1>=0 and (not fked[i-1][j]) and dfs(i-1,j)) or (j-1>=0 and (not fked[i][j-1]) and dfs(i,j-1))):
                fked[i][j]=0
                for x in range(LUR,LUR+EACH) :
                    for y in range(LUC,LUC+EACH):
                        vis[x][y] = tmpp[x-LUR][y-LUC]
                return True
            
    fked[i][j]=0
    for x in range(LUR,LUR+EACH) :
        for y in range(LUC,LUC+EACH):
            vis[x][y] = tmpp[x-LUR][y-LUC]
    return False
    

#预先扫描,把能确定的、没有多解的先填上
for i in range(0,20):
    for j in range(0,20):
        LUR , LUC= (X+i*MSBLOCK)//QRBLOCK , (Y+j*MSBLOCK)//QRBLOCK
        succnt = 0
        for filN in range(0,1<<(EACH*EACH)):
                tmp = calcDelta(i, j, filN, pre_arr[X+i*MSBLOCK+2][Y+j*MSBLOCK+2],LUR,LUC)
                if tmp < 0.00000001:
                    succnt = succnt + 1
                    nowSol = filN
        if succnt == 1:
            for x in range(LUR,LUR+EACH) :
                for y in range(LUC,LUC+EACH):
                    kimeta[x][y] = 1
                    res_arr[x][y]=nowSol&1
                    nowSol = nowSol>>1
dfs(5,5)

for i in range(0,SIZE):
    for j in range(0,SIZE):
        new_arr[i][j]=res_arr[i//QRBLOCK][j//QRBLOCK]
MatrixToImage(np.array(new_arr)).save('resu.bmp')

最终效果:QRCode


minecRaft

web× reverse√
打开网页查看js,找到flag.js,在jsnice.org反混淆后自己手动再替换下几个迷人眼的常量,之后进行代码审计,会发现这是个TEA加密。如何发现是TEA呢?我搜了好久,后来有人告诉我只需要搜常量就行(还需要学习一个人生经验)。
我们需要找到一个字符串s,使得s.encrypt("{那串数字密钥}")=== "6f......0c"
把密文切片避免转换后数字过大,在题目页面下F12,进入Console里执行:

window.btoa(LongToStr4(Base16ToLong('6fbde674'))+LongToStr4(Base16ToLong('819a59bf'))+LongToStr4(Base16ToLong('a1209256'))+LongToStr4(Base16ToLong('5b4ca2a7'))+LongToStr4(Base16ToLong('a11dc670'))+LongToStr4(Base16ToLong('c678681d'))+LongToStr4(Base16ToLong('af4afb67'))+LongToStr4(Base16ToLong('04b82f0c')))

得到密文dOa9b79ZmoFWkiChp6JMW3DGHaEdaHjGZ/tKrwwvuAQ=
谷歌TEA decryption online,进入https://www.mefancy.com/obfuscation/encryption-generator.php把上面那串数字密钥(13...)还有密文丢进去就出flag了。


p😭q

老外大奥流泪.gif 早知道,还是原道.jpg
先把gif的帧提取出来方便处理:

import os
import sys
from PIL import Image

def extractFrames(inGif, outFolder):
    frame = Image.open(inGif)
    nframes = 0
    while frame:
        frame.save('./dest/%s.png' % (nframes))
        nframes += 1
        try:
            frame.seek(nframes)
        except EOFError:
            break;
    return True

if __name__ == '__main__':
    image = os.path.abspath(sys.argv[1])
    dest = os.path.join(os.path.dirname(image), "dest")
    if not os.path.exists(dest):
        os.mkdir(dest)
    extractFrames(image, dest)

然后原题给啥库就用啥库,逆回去:

from array2gif import write_gif  # version: 1.0.4
import librosa  # version: 0.8.1
import numpy as np  # version: 1.19.5
import soundfile as sf
from PIL import Image
import matplotlib.pyplot as plt

def ImageToMatrix(filename):
    im = Image.open(filename)
    height,width = im.size
    im = im.convert('L')
    data = im.getdata()
    data = np.matrix(data,dtype='float')/255.0
    new_data = np.reshape(data,(width,height))
    return new_data
np.set_printoptions(threshold=1145141919810)
num_freqs = 32
quantize = 2
min_db = -60
max_db = 30
fft_window_size = 2048
frame_step_size = 512
window_function_type = 'hann'
red_pixel = [255, 0, 0]
white_pixel = [255, 255, 255]
sample_rate = 22050
lis =  [[0.0 for col in range(587)] for row in range(32)]
imgg = [[0.0 for col in range(130)] for row in range(92)]
for ii in range (0,587):
    filename = './dest/%s.png'%ii
    print(filename)
    data = ImageToMatrix(filename)
    for i in range(0,92):
        for j in range(0,130):
            imgg[i][j]=data.tolist()[i][j]
    for xpos in range(0,32):
        for scan in range(0,92):
            if imgg[scan][xpos*4+2]<1.0:  
                lis[xpos][ii]=float((91-scan)-60)
                break

spectrogram =  np.array(lis)
audio_signal = librosa.feature.inverse.mel_to_audio(librosa.db_to_power(spectrogram), sr=sample_rate, n_fft=fft_window_size*2, hop_length=frame_step_size, window=window_function_type)
sf.write('newnew.wav', audio_signal, sample_rate) 

最后在Adobe Audition CC里随便处理处理,勉强能听了,开始努力听写:
Theflagis f,l,a,g ......

posted @ 2021-10-30 16:11  yyy2015c01  阅读(504)  评论(0编辑  收藏  举报

在寒风中颤抖之时、听到了歌声—— 在傍晚的校园里,在无人在的食堂里,在孤寂的校舍窗边。
三年前被冰封的那首歌曲。 被热情突然迫动,点缀着纯粹的思念,欺瞒的歌曲逐渐溶化在夜里。
那时,三人一起的冬天已经远去, 一个人和另一个人的季节却在循环往复。 后续,就在这样的晚秋。 那个时候似要撕裂的羁绊的丑陋伤痕尚未干却, 但是伴随着有什么将要改变的预感中,开始了。
寂寞的两首旋律,互相吸引而再伤害彼此, 因此,新的旋律被召集来了。
不久,新的冬天即将到来。 不能和那个人在一起、而另一个人也已不在的冬天。
“白色相簿”什么的,已经无所谓了。 因为已经不再有歌,值得去唱了。
传达不了的恋情,已经不需要了。 因为已经不再有人,值得去爱了。