前言

本文记录一下打国外比赛或者自己做题时遇到的一些有意思的考点知识。

https://github.com/tgrddf55/any/tree/main/challenges

2023-08-07 LITCTF

  1. obf.py
eval(compile(b64decode(eval('\x74\x72\x75\x73\x74')),'<string>','exec'))

python中 compile的用法 字符串混淆方法 import as修改名称

  1. budget-mc

    题目是在远端服务器上运行的,因此这里的flag.txt在本地是无法看到的

    image-20230809170416414

题目本身是一个带有重力的二维游戏

image-20230809170559194

程序中开了一个数组,用一维数组来表示二维数组,向上走只能通过改变当前元素值为1。程序中唯一可以显示元素值的是

image-20230809171009319

看起来程序没有地方直接输出flag,我们要想获得flag只能依据这个数组和flag数组的位置关系,通过类似于pwn的方式来越界访问数组,从而获取flag。

3.regex

(正则表达式的学习) 比赛的时候没来得及做,赛后做的

题目原文

^LITCTF\{(?<=(?=.{42}(?!.)).(?=.{24}greg).(?=.{30}gex).{5})(?=.{4}(.).{19}\1)(?=.{4}(.).{18}\2)(?=.{6}(.).{2}\3)(?=.{3}(.).{11}\4)(?=.{3}(.).{3}\5)(?=.{16}(.).{4}\6)(?=.{27}(.).{4}\7)(?=.{12}(.).{4}\8)(?=.{3}(.).{8}\9)(?=.{18}(.).{2}\10)(?=.{4}(.).{20}\11)(?=.{11}(.).{2}\12)(?=.{32}(.).{0}\13)(?=.{3}(.).{24}\14)(?=.{12}(.).{9}\15)(?=.{7}(.).{2}\16)(?=.{0}(.).{12}\17)(?=.{13}(.).{5}\18)(?=.{1}(.).{0}\19)(?=.{27}(.).{3}\20)(?=.{8}(.).{17}\21)(?=.{16}(.).{6}\22)(?=.{6}(.).{6}\23)(?=.{0}(.).{1}\24)(?=.{8}(.).{11}\25)(?=.{5}(.).{16}\26)(?=.{29}(.).{1}\27)(?=.{4}(.).{9}\28)(?=.{5}(.).{24}\29)(?=.{15}(.).{10}\30).*}$

之前仅仅只是知道正则表达式这个名字,现在才具体学习了一下正则表达式的规则。

主要讲解一下(?<=(?=.{42}(?!.)).(?=.{24}greg).(?=.{30}gex).{5})这一段内容,来加深理解一下零宽断言。首先要先划分层次,

( ?<= (?=.{42}(?!.) ) . (?=.{24}greg) . (?=.{30}gex) .{5} )

我们逐层分析。最外层的零宽断言去掉后是

(?=.{42}(?!.) ) . (?=.{24}greg) . (?=.{30}gex) .{5}

这样的零宽断言是并列的形式分别是

(?=.{42}(?!.) ) . (?=.{24}greg) . (?=.{30}gex) .{5}

这几部分

第一小部分又嵌套了一个零宽断言,(?!.)意思是后面没有任何字符,那么.{42}(?!.)的意思就是四十二个字符之后没有任何字符,(?=.{42}(?!.) )就是表示有一个零宽位置(夹在两个字符中间的位置,不算字符)后面跟着42个字符,并且42个字符后没有别的字符。

第二部分是一个通配符,也就是任意字符。

第三部分(?=.{24}greg)意思就是一个零宽位置,后面跟着24个字符并且紧跟着greg这四个字母。

第四部分是一个通配符。

第五部分(?=.{30}gex) 意思就是有一个零宽位置,后面跟着30个字符并且紧跟着gex这四个字母。

第六部分.{5}表示五个通配符,也就是五个人资字符。

我们将这六部分合起来看,就是要有七个字符,这七个字符要满足一些条件,第一个字符前面的零宽位置满足这个零宽位置后面有42个任意字符(显然包括第一个字符),并且之后不再有字符,第一个字符和第二个字符之间的零宽位置要满足,这个零宽位置后面有24个任意字符,之后紧跟着greg四个字符,第二个字符和第三个字符之间的零宽位置要满足这个零宽字符后面有30个任意字符,之后紧跟着gex三个字符,之后从第三个字符到第七个字符都是任意字符。

再加上最外层的零宽断言(?<=(?=.{42}(?!.)).(?=.{24}greg).(?=.{30}gex).{5}) 意思就是有一个零宽位置,这个零宽位置的左边要有七个字符,这七个字符满足刚才说的那些条件。

这一部分最麻烦的分析完了,再把前面的加上

^LITCTF\{(?<=(?=.{42}(?!.)).(?=.{24}greg).(?=.{30}gex).{5})

^表示开始,\{表示{,那么这一段就是最开始要有LITCTF{这七个字符然后紧跟着一个零宽位置,这个位置的左边要有七个字符,这七个字符满足刚才那些条件。显然后面这个零宽断言中有的七个字符就是LITCTF{,刚刚说的那些条件中第一个字符就是L,第二个字符就是I,第三字符就是C,这些字符周围的零宽位置需要满足一堆条件。记住刚刚七个字符之后紧跟着的零宽位置,这个位置还要满足一些条件。

(?=.{4}(.).{19}\1)

后面的不列举了,和这个是一样的。

刚刚记住的那个零宽位置还要满足一些条件,(?=.{4}(.).{19}\1) \1表示前面的捕获的分组的重复,我理解的分组就是,原本可以不用加()的地方结果加了,那么这个位置就是分组,在这里指的就是(.) 注意零宽断言的小括号显然不算。这个零宽断言就是有一个零宽位置,后面紧跟着4个任意字符,然后后面又紧跟着一个字符,把这个字符记住,之后又紧跟着19个任意字符,在之后紧跟着一个字符,这个字符和刚刚记住的那个字符需要一致。也就是说这个零宽位置后面的第5个字符和后面的第25个字符要一致。之后的所有零宽断言都是类似这样。这些也是限制条件。

零宽断言之后还有一些字符 .*}$ .*表示任意个任意字符 } 表示最后有一个},$表示结束,也就是说最后要以}字符结尾。

通过刚刚的分析很容易得出答案,下面是我的脚本。

from z3 import *
x = [Int('x%s' % i) for i in range(42)]
s=Solver()
ls=[4,19,4,18,6,2,3,11,3,3,16,4,27,4,12,4,3,8,18,2,4,20,11,2,32,0,3,24,12,9,7,2,0,12,13,5,1,0,27,3,8,17,16,6,6,6,0,1,8,11,5,16,29,1,4,9,5,24,15,10]

s.add(x[0]==ord('L'))
s.add(x[1]==ord('I'))
s.add(x[2]==ord('T'))
s.add(x[3]==ord('C'))
s.add(x[4]==ord('T'))
s.add(x[5]==ord('F'))
s.add(x[6]==ord('{'))
s.add(x[41]==ord('}'))
s.add(x[25]==ord('g'))
s.add(x[26]==ord('r'))
s.add(x[27]==ord('e'))
s.add(x[28]==ord('g'))
s.add(x[32]==ord('g'))
s.add(x[33]==ord('e'))
s.add(x[34]==ord('x'))
for i in range(30):
s.add( x[ls[2*i]+7] == x[ls[2*i]+8+ls[2*i+1]])
s.check()
flag=s.model()
res=[]
for i in x:
res.append(flag[i])
print(res)
res=[76, 73, 84, 67, 84, 70, 123, 114, 114, 114, 101, 103, 101, 114, 101, 101, 114, 101, 103, 101, 114, 103, 101, 103, 101, 103, 114, 101, 103, 101, 103, 103, 103, 101, 120, 101, 120, 101, 120, 120, 120, 125]


str_flag=""
for i in range(len(res)):
str_flag+=chr(res[i])
print(str_flag)
# LITCTF{rrregereeregergegegregegggexexexxx}