2020 DASCTF 6th Re all wp

这次比赛re不难,做的很爽,misc/smb因为卡了一波半角全角然后就没拿到一血orz..本来要写misc wp的发现群里都写完了,那就先来一波re吧。

本人其实是misc师傅...基本什么都看一眼,跟专业逆向肯定是天地之差...wp写的需要改进的地方希望大佬多指点。

RE

521

逆的时候看到异或了,然后就蒙只异或,通过构造部分相同的输入发现那部分输出也是相同的,没把加密部分搅到一块去,大概率是明文异或了。

exp:

def xor(x,y):
    result = ""
    for i in range(len(x)):
        result += chr(ord(x[i])^ord(y[i]))
    return result


enc = ""
enc += p64(0x6c8e072a35235980)
enc += p64(0x7df45d5aee345023)
enc += p64(0xe0766f80db58e7b1)
enc += p64(0x301b4c2e821f0d50)
enc += p64(0x000000229b9e8271)

dec = "Nep{0123456789ABCDEFFEDCBA9876543210}"

flag = "8059233522738D1A515D30E85726F607C6925EDC831F7692250F65FB2E4D6B450387E99F2200".decode("hex")
cipher = xor(enc[:37],dec)
print xor(cipher,flag)

DDoll

感觉这个题开了挺多混淆,至少ida下面看的乱乱的,还好是win下的逆向,直接拉到od里准备搜一波字符串。

刚开始进去搜字符串还没有,多跟几步基本就有了。

输入部分和success与fail输出都有了,直接去判断flag正确与否的地方找一下有没有什么特别的。

je就是关键跳了,上面最近的call是用来判断的。在ida里查看一下发现就是纯比较,然后就看call 0x891122时的栈来确定传参的两个地址。之后传一个比较长的字符串,再通过传参把加密后的字符串取出来,与原字符串xor一下就能把xor的key给套出来,直接xor目标字符串就OK了。

PS: ida里的rebase还是挺好用的。od里的地址有时候跟ida里的不一样,直接rebase。

ida edit->segments->rebase program,之后打开od的memory map(alt+m),找pe文件头,ida的base地址直接设成文件头的地址两边地址就能一块用了。

exp:

enc = [0xC6,0xA5,0x04,0x53,0x33,0xC3,0xC8,0x9E,0x2F,0x8F,0x44,0xE0,0x9D,0x24,0x2F,0x28,0xE4,0xDC,0xDB,0x34,0x78,0xB8,0x4C,0x38]
flag=[0xC6,0xA5,0x04,0x53,0x17,0xF6,0xCF,0xAA,0x21,0xAF,0x5C,0xE0,0x9F,0x17,0x2F,0x74,0x88,0xA6,0x8B,0x72,0x3F,0xE5,0x1B,0x75,0xD1]

def xor(x,y):
    result = ""
    for i in range(len(x)):
        result += chr(ord(x[i])^ord(y[i]))
    return result

dec = "Nep{fuckyouall1234567890}"
cipher = xor("".join([chr(i) for i in enc]),dec)
print xor(cipher,"".join([chr(i) for i in flag]))

Magia

扔到ida里在main下面看,题目给了三个数组,分别是flag[i]^flag[len-i],flag[i]&flag[len-i]和flag[i]&0xf的结果。不想手算直接扔z3。发现z3解出来flag看起来怪怪的,结合题目的小写+下划线限定一波就是正确输入了。接下来跟进0x403000,发现对flag进行了逐位处理,最后把结果写到了ebp-0x4c,数据区跟过去就看到32个字符了。

PS1: ida7.2比ida7.0要先进一点,能准确定位更多程序的main函数地址,但ida7.2 32bit没hexray...所以我为了确定main函数先拖到ida7.2 64bit,确定完main在哪再回ida7.0 32bit看hexray。

PS2: 为啥我就敢判断ebp-0x4c有东西,因为从上到下看了一圈,mov [寄存器+寄存器+数值],* 只有这个这一个地方,可以猜edx是当前计数寄存器(不管是正着数的还是倒着数的),结果就对了。

eazy_maze

送分题...ida里面给了一个100个0和O组成的数组,然后又定义了上下左右键,直接对着数组shift+e导出之后10个10个分开,然后照着地图敲就行了,初始点在(0,0)

brainbreaker

找到main之后就看到div 0了,直接去找seh链。

od: 查看->SEH链 越靠前的函数越先catch exception,所以第一个就是接下来的流程了。

接下来搜一波字符串,又发现了right和wrong..直接跟过去

老套路了,直接比较,然后上面就大概率有关键函数..

在ida里看,发现是一堆数据,对着数据按c就变成code了。

发现是switch case,上面又有奇怪的一串东西在往里面读,估计是vm,然后就下个断点验证一下。然而下断到jmp程序就直接退出了。怀疑有检测断点的机制,下断点给ExitProcess。

看最后的栈帧在这,ida跟过去一看,就是这个位置有个验证

应该是计算内存"哈希",毕竟od下断点都是直接在内存里插int 3,计算一下就露馅了。直接把jnz patch成nop就OK。剩下的断都正常断了。

分析发现这个vm有两个寄存器,然后opcode种类少,操作好看,直接分析opcode再来一波z3就完事。(我真是爱死z3了23333)

本来是要opcode+表里的东西才能对switch做选择的,没想到ida直接就把什么分支对应什么字符给整出来了,太厉害了。

exp:

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

PS1: 注意python里的符号问题,vm里寄存器操作是无符号的,python直接加减是有符号的,需要&0xff来转换成无符号。

PS2: 由于只有BitVec支持位操作,而BitVec是无符号的,请时刻注意这点。当你发现计算的结果和你预期不符时,可以试着把BitVec扩大一位来解决问题。这个题没碰到这个情况。

pyCharm

参照https://www.52pojie.cn/thread-912103-1-1.html

提一句找长度,我也不知道长度在哪,但是我len(code)之后小端编码就可直接在pyc里面搜16进制了。

这就很简单了。

T0p_Gear

题目描述都是“签 到 题”了...的确很签到。

直接开ida,发现有3个check,而且基本没什么混淆,还是直接明文strcmp的...直接断在3个strcmp答案就出来了。

其他方向参考

misc: http://www.ga1axy.top/index.php/archives/42/

re: https://badmonkey.site/archives/dasctf-2020-6.html

部分web: https://www.gem-love.com/ctf/2401.html

部分pwn: https://blog.csdn.net/weixin_44145820/article/details/106974426

文档: https://shimo.im/docs/ywRvpRW3DkgqDgqP