DASCTF_2020_六月赛 write up

发布于 2020-06-28  1311 次阅读


Reverse

T0p_Gear

程序加壳了,用upx脱壳一下
程序有三个check

第一个是明文匹配
第二个是从文件中解密字符串
第三个是将输入进行加密,和指定字符串进行比较

flag = ""

for c in [0x35, 0x61, 0x36, 0x62, 0x62, 0x32, 0x39, 0x63][::-1]:
    flag += chr(c)
flag += '-'

flag += 'a6c30091'
flag += '-'

for idx, c in enumerate([ord(t) for t in list('00l>>=<:'[::-1] + 'mm?kj<l:'[::-1])]):
    temp = (c + 10) ^ (idx + 3)
    if temp == 70:
        flag += 'F'
    else:
        flag += chr(c ^ 8)

print(flag)

easy_maze

一个迷宫,很简单

jkkjjhjjkjjkkkuukukkuuhhhuukkkk

Magia

裸解方程题,让输入满足以下等式即可

 for ( i = 31; v8 < i; --i )
  {
    if ( (input[i] ^ input[v8]) != *(&v62 + v8) )
    {
      printf_0("No_need_to_scare");
      return 0;
    }
    if ( (input[i] & input[v8]) != *(&v46 + v8) )
    {
      printf_0("No_need_to_scare");
      return 0;
    }
    if ( (input[v8] & 0xF) != *(&v14 + v8) || (input[i] & 0xF) != *(&v14 + i) )
    {
      printf_0("No_need_to_scare");
      return 0;
    }
    ++v8;
  }

用 z3 建立 BitVec 变量(位运算只能用bitvec),然后添加所有等式

原题目hints提示说flag中含有下划线,所以我在方程中多加了几个特判,否则解出来的flag有问题,会解出一些不可见字符

from z3 import *

v46 = [0x4c, 0x65, 0x60, 0x72, 0x64, 0x49, 0x70, 0x63, 0x6c, 0x45, 0x53, 0x61, 0x4e, 0x64, 0x48, 0x61]
v62 = [0x33, 0x0, 0x15, 0x9, 0xb, 0x36, 0x6, 0xc, 0x2, 0x3a, 0x2c, 0x8, 0x31, 0xb, 0x37, 0xc]
v14 = [0xe, 0x5, 0x0, 0xb, 0xd, 0x9, 0x2, 0x3, 0xc, 0x5, 0xf, 0x1, 0xe, 0x4, 0xf, 0xd, 0x1, 0x8,
       0xf, 0xf, 0x9, 0x3, 0xf, 0xe, 0xf, 0x4, 0xf, 0x6, 0x2, 0x5, 0x5, 0xd]

ans = [BitVec(f'v{i}', 8) for i in range(32)]
s = Solver()

s.add(ans[0] == ord('N'))
s.add(ans[1] == ord('e'))
s.add(ans[2] == ord('p'))
s.add(ans[3] == ord('{'))
s.add(ans[31] == ord('}'))

for i in range(32):
    s.add(Or(ans[i] == ord('{'), ans[i] == ord('}'), ans[i] == ord('_'),
             And(ans[i] >= ord('a'), ans[i] <= ord('z')), ans[i] == ord('N')))

i = 31
v8 = 0
while v8 < i:
    s.add(ans[i] ^ ans[v8] == v62[v8])
    s.add(ans[i] & ans[v8] == v46[v8])
    s.add(ans[v8] & 0xf == v14[v8])
    s.add(ans[i] & 0xf == v14[i])

    v8 += 1
    i -= 1

if s.check() == sat:
    print("good!")
    m = s.model()

    for idx, each in enumerate(ans):
        #print(each, end=",")
        print(chr(m[each].as_long()), end="")

else:
    print("boom!")

接下来需要一点心灵感应,输入神秘字符串之后程序会再跑一段解密代码,解密出真正的flag,动态调试才能看到

8b272473500a451286ab225413f1debd

521

c++ 逆向,需要有一些动态调试技巧

主逻辑:

file

其中 encrypt_0 中能找到这个函数:

__int64 __fastcall encrypt(__int64 a1, unsigned int a2, __int64 a3, unsigned int a4)
{
  __int64 result; // rax
  unsigned int v5; // eax
  unsigned int v6; // [rsp+1Ch] [rbp-Ch]
  int v7; // [rsp+20h] [rbp-8h]
  unsigned int i; // [rsp+24h] [rbp-4h]

  sub_55C13086819A(a3, a4);
  v6 = 0;
  v7 = 0;
  for ( i = 0; ; ++i )
  {
    result = i;
    if ( a2 <= i )
      break;
    v6 = (unsigned __int8)(((unsigned int)((signed int)(v6 + 1) >> 31) >> 24) + v6 + 1)
       - ((unsigned int)((signed int)(v6 + 1) >> 31) >> 24);
    v5 = (unsigned int)((v7 + byte_55C130A6A260[v6]) >> 31) >> 24;
    v7 = (unsigned __int8)(v5 + v7 + byte_55C130A6A260[v6]) - v5;
    swap((char *)&byte_55C130A6A260[v6], (char *)&byte_55C130A6A260[v7]);
    *(_BYTE *)((signed int)i + a1) ^= byte_55C130A6A260[(unsigned __int8)(byte_55C130A6A260[v6] + byte_55C130A6A260[v7])];
  }
  return result;
}

a1 就是刚刚输入的字符串,可以看到,程序只是对它进行了异或操作,其中加密时利用到了一个固定字符串

file

这个操作就利用固定密钥对称加密字符串,因此我们只需要把输入换成密码,就能得到flag了,利用idapython patch一下即可:

#  在字符串加密之前运行
#  将 addr 换成你之前输入的字符串的地址
addr = 0x000055C1325964C0
enc = [128, 89, 35, 53, 34, 115, 141, 26, 81, 93, 48, 232, 87, 38, 246, 7, 198, 146, 94, 220, 131, 31, 118, 146, 37, 15, 101, 251, 46, 77, 107, 69, 3, 135, 233, 159, 34]

for i in range(37):
    patch_byte(addr+i, enc[i])

pycharm

参考资料:
http://chumen77.xyz/2020/06/27/DASCTF%E5%AE%89%E6%81%92%E6%9C%88%E8%B5%9B(6th)/#pyCharm-pyc%E6%96%87%E4%BB%B6%E6%81%A2%E5%A4%8D
https://www.52pojie.cn/thread-912103-1-1.html
https://www.cnblogs.com/blili/p/11799483.html (pyc文件结构分析,重点关注code_block的位置)

这道题给了一个pyc,能够正常运行,但是使用 uncompyle6 反编译却出错了:
file

这时候有两种方法:修复pyc,或者直接翻译pyc。

我们通过以下的命令分别读取 pyc 的文件头、常量表、变量名表、字节码表:

Python 2.7.12 (default, Apr 15 2020, 17:07:12) 
[GCC 5.4.0 20160609] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> import dis,marshal
>>> f = open('a.pyc')
>>> f.read(4)
'\x03\xf3\r\n'
>>> f.read(4)
'jv\xe7^'
>>> code = marshal.load(f)
>>> code.co_consts
(-1, None, 'YamaNalaZaTacaxaZaDahajaYamaIa0aNaDaUa3aYajaUawaNaWaNajaMajaUawaNWI3M2NhMGM=', 'Are u ready?', 0, 32, 'a', '', 'great!waht u input is the flag u wanna get.', 'pity!')
>>> code.co_varnames
()
>>> code.co_names
('base64', 'a', 'raw_input', 'flag', 'b64encode', 'c', 'list', 'd', 'range', 'i', 'join', 'ohh')
>>> code.co_code
"q\x03\x00q\x00\x06d\xffd\x00\x00d\x01\x00l\x00\x00Z\x00\x00d\x02\x00Z\x01\x00e\x02\x00d\x03\x00\x83\x01\x00Z\x03\x00e\x00\x00j\x04\x00e\x03\x00\x83\x01\x00Z\x05\x00e\x06\x00e\x05\x00\x83\x01\x00Z\x07\x00x'\x00e\x08\x00d\x04\x00d\x05\x00\x83\x02\x00D]\x16\x00Z\t\x00e\x07\x00e\t\x00c\x02\x00\x19d\x06\x007\x03<qI\x00Wd\x07\x00j\n\x00e\x07\x00\x83\x01\x00Z\x0b\x00e\x0b\x00e\x01\x00k\x02\x00r\x86\x00d\x08\x00GHn\x05\x00d\t\x00GHd\x01\x00S"
>>> dis.dis(code.co_code)
          0 JUMP_ABSOLUTE       3
    >>    3 JUMP_ABSOLUTE    1536
          6 LOAD_CONST      25855 (25855)
          9 STOP_CODE      
         10 STOP_CODE      
         11 LOAD_CONST          1 (1)
         14 IMPORT_NAME         0 (0)
         17 STORE_NAME          0 (0)
         20 LOAD_CONST          2 (2)
         23 STORE_NAME          1 (1)
         26 LOAD_NAME           2 (2)
         29 LOAD_CONST          3 (3)
         32 CALL_FUNCTION       1
         35 STORE_NAME          3 (3)
         38 LOAD_NAME           0 (0)
         41 LOAD_ATTR           4 (4)
         44 LOAD_NAME           3 (3)
         47 CALL_FUNCTION       1
         50 STORE_NAME          5 (5)
         53 LOAD_NAME           6 (6)
         56 LOAD_NAME           5 (5)
         59 CALL_FUNCTION       1
         62 STORE_NAME          7 (7)
         65 SETUP_LOOP         39 (to 107)
         68 LOAD_NAME           8 (8)
         71 LOAD_CONST          4 (4)
         74 LOAD_CONST          5 (5)
         77 CALL_FUNCTION       2
         80 GET_ITER       
         81 FOR_ITER           22 (to 106)
         84 STORE_NAME          9 (9)
         87 LOAD_NAME           7 (7)
         90 LOAD_NAME           9 (9)
         93 DUP_TOPX            2
         96 BINARY_SUBSCR  
         97 LOAD_CONST          6 (6)
        100 INPLACE_ADD    
        101 ROT_THREE      
        102 STORE_SUBSCR   
        103 JUMP_ABSOLUTE      73
    >>  106 POP_BLOCK      
    >>  107 LOAD_CONST          7 (7)
        110 LOAD_ATTR          10 (10)
        113 LOAD_NAME           7 (7)
        116 CALL_FUNCTION       1
        119 STORE_NAME         11 (11)
        122 LOAD_NAME          11 (11)
        125 LOAD_NAME           1 (1)
        128 COMPARE_OP          2 (==)
        131 POP_JUMP_IF_FALSE   134
    >>  134 LOAD_CONST          8 (8)
        137 PRINT_ITEM     
        138 PRINT_NEWLINE  
        139 JUMP_FORWARD        5 (to 147)
        142 LOAD_CONST          9 (9)
        145 PRINT_ITEM     
        146 PRINT_NEWLINE  
    >>  147 LOAD_CONST          1 (1)
        150 RETURN_VALUE   
>>> 

可以发现,前五条语句明显出现了问题:

          0 JUMP_ABSOLUTE       3
    >>    3 JUMP_ABSOLUTE    1536
          6 LOAD_CONST      25855 (25855)
          9 STOP_CODE      
         10 STOP_CODE      
         11 LOAD_CONST          1 (1)

程序不可能一下子jmp到1536,所以这个地方明显是混淆代码,程序能够执行而 decompyle6 却不能反编译,只要我们读懂这个地方的逻辑并且修改字节码,就能顺利反编译。

0x64 操作为LOAD_CONST,用法举例:LOAD_CONST 1 HEX: 640100
0x71 操作为JUMP_ABSOLUTE,用法举例:JUMP_ABSOLUTE 14 HEX: 710e00
0x65 操作为LOAD_NAME,用法举例:LOAD_NAME 1 HEX: 650100
...

file

python的字节码都是以 opcode + arg 的形式呈现的。

根据上方的字节码对照表我们能够猜出整个程序逻辑是从 0x1e 开始的。 '/x97/x00/x00/x00' 则是字节码的长度。

0x26 至 0x28,本来程序本意是要从表中读取常量(Load_Const 0),但程序因为混淆代码的关系翻译有误。我们只需要去掉这之前的代码即可。

即:去掉0x1e-0x25 共8个字节

字节码表长度初始值为 0x97(大端序)

0x97 - 8 = 0x8f

with open('a.pyc','r') as f:
    dt = f.read()

# 其他pyc也可用类似的方法进行去混淆
data = dt[:0x1a]+'\x8f\x00'+dt[0x1c:0x1e] + dt[0x26:]
with open('out.pyc', 'w') as ff:
    ff.write(data)

接下来就是 uncompyle6 一把梭

# uncompyle6 version 3.6.2
# Python bytecode 2.7 (62211)
# Decompiled from: Python 2.7.12 (default, Apr 15 2020, 17:07:12) 
# [GCC 5.4.0 20160609]
# Embedded file name: pyCharm.py
# Compiled at: 2020-06-15 21:23:54
import base64
a = 'YamaNalaZaTacaxaZaDahajaYamaIa0aNaDaUa3aYajaUawaNaWaNajaMajaUawaNWI3M2NhMGM='
flag = raw_input('Are u ready?')
c = base64.b64encode(flag)
d = list(c)
for i in range(0, 32):
    d[i] += 'a'

ohh = ('').join(d)
if ohh == a:
    print 'great!waht u input is the flag u wanna get.'
else:
    print 'pity!'
# okay decompiling out.pyc

程序逻辑十分简单,去掉字符串中的'a'并且base64解码即可。

brainbreaker

题目中有反调,patch一下即可

main函数不能直接f5,也需要patch一下修一下栈才能继续,不过翻译出来的伪代码有点问题。

就是一个简单的线性变换题。

本来可以z3一把梭的,不过不知道为啥解出来结果不太对,先放着,以后解决。

from z3 import *

a1 = [BitVec('flag%d' % i,9) for i in range(100)]
s = Solver()
a1.reverse()

result = [0xFA,0x2B,0x94,0xEA,0x63,0xA8,0xC4,0x13,0x84,0xE7,0xA7,0xDA,0xD4,0x2D,0xAE,0xBE]

opcode = "****--:~+******^.~+*****:*+++++^.-:///--*^.:///---^.,:~-----^>,:~-/++++++++++++^>,:~+****--^>+*******://++++++++++^:,^>-:///^:,^>+******://-^:,^>-/+://+++++++^:,^>-/----:,^>-/+:/+++++^:,^>-:++****+^:,^>-/+:~+**++^:,^>+****:*^:,^>-:++****++++^:,^>-/---:,^>+*****://+^:,^>-/+:///+^:,^>,"
ax = 0
bx = 0
fin=[0 for i in range(100)]
fin[0] = 1
for i in opcode:
    o = ord(i)
    if o == 60:
        ax -= 1
        # print "dec ax","ax = ",ax
    if o == 62:
        ax += 1
        # print "inc ax","ax = ",ax
    if o == 46:
        continue
        # print "put %c fin[{}]".format(ax)
        # print chr(fin[ax])
    if o == 44:
        fin[ax] = a1.pop()
        # print "get %2x fin[{}]".format(ax)
        # break
    if o == 43:
        # print "inc fin[{}]".format(ax)
        fin[ax] += 1
    if o == 45:
        # print "dec fin[{}]".format(ax)
        fin[ax] -= 1
    if o == 42:
        # print "double fin[{}]".format(ax)
        fin[ax] *= 2
    if o == 47:
        # print "shr fin[{}]".format(ax)
        fin[ax] >>= 1
    if o == 94:
        # print "xor fin[{}],bx".format(ax)
        fin[ax] ^= bx
    if o == 58:
        # print "mov bx,fin[{}]".format(ax)
        bx = fin[ax]
    if o == 126:
        # print "check ax == 32;mov fin[{}],0".format(ax)
        if ax == 32:
            break
        fin[ax] = 0
    fin[ax] &= 0xff
    ax &= 0xff
    bx &= 0xff

for i in range(len(result)):
    s.add(result[i] == fin[i])

print(s.check())
answer = s.model()
flag = "".join([hex(answer[i].as_long())[2:] for i in a1])
print(flag)
print(answer)

Pwn


CTFer|NOIPer|CSGO|摸鱼|菜鸡