• 依旧很菜, 做题没有师傅们快

Signin

用qt和python写的, 用PyInstaller打包, 可以用pyinstxtractor解包出来, 在解包出来的文件夹下有个main, 其实是个pyc文件, 更名为main.pyc

然后由于PyInstaller生成文件时会去掉头部信息, 所以在反汇编之前需要补充头部信息。方法就是找到struct文件, 复制进main.pyc即可

image-20200706170442620

image-20200706171727892

接着可以用decomplie3uncomplie6反汇编, 得到main.py

可以看到调用tmp.dll里面的enc函数

image-20200706172825138

而这个tmp.dll是程序运行时产生的, 运行后消失。

加密逻辑很简单, CRC64 + 异或加密 + base64

image-20200706174235067

解密脚本如下:

import base64
import struct
name = list(map(ord, "SCTFer"))
enc = base64.b64decode(b'PLHCu+fujfZmMOMLGHCyWWOq5H5HDN2R5nHnlV30Q0EA')

flag = b""
for i in range(len(enc)):
flag += bytes([enc[i] ^ name[i % len(name)]])

def Dec(f):
for i in range(64):
if(f % 2 == 0):
f //= 2
else:
f ^= 0xB0004B7679FA26B3
f = f + 0xffffffffffffffff + 1
f //= 2
return f

decode_flag = []
for i in range(4):
decode_flag.append(int.from_bytes(flag[i * 8 : i * 8 + 8], "little"))

flag = []
for i in range(4):
flag.append((Dec(decode_flag[i])))

s = b""
for i in range(len(flag)):
s += struct.pack(">Q", flag[i])[::-1]


print(s)

解得:

b'SCTF{We1c0m3_To_Sctf_2020_re_!!}'

get_up

有两处代码自修改。

第一处要求输入一个 md5 1万次等于32c1d123c193aecc4280a5d7925a2504的词, 用在线工具查一下就知道单词是sycsyc, 然后根据输入解密代码

第二处输入flag, 长度要求是30, 然后根据前五个解密代码, 当然前五个肯定是SCTF{

最后会进入一个RC4的函数, 直接解密就可以了(所以发现前面做的都没用)

import sys, os, hashlib, time, base64
from Crypto.Cipher import ARC4

def mydeRC4(data, key):
rc41 = ARC4.new(key)
decrypted = rc41.decrypt(data)
return decrypted

a = [0x80, 85, 126, 45, 209, 9, 37, 171, 60, 86, 149, 196, 54, 19, 237, 114, 36, 147, 178, 200, 69, 236, 22, 107, 103, 29, 249, 163, 150, 217]
flag=b''
for i in range(len(a)):
flag += bytes([a[i]])
print(mydeRC4(flag, 'syclover'))

flag_detector

有个最新的upx, 官方工具upx -d即可

从ida中可以知道是用golang写的, gin是一个golang的web框架。访问127.0.0.1:8000即可这里不知道比赛的时候咋了, 第一个函数居然从main_main_fun1开始看, 傻了。赛后在main_main中找到路由表, 且为多级路由。

image-20200707232728163

整理如下:

/v1/login ; 初始化config
/v2/user ; 初始化 asdf, 必须要有config
/v2/flag ; 初始化 hjkl, 必须要有config
/v3/check ; 校验flag

然后就可以开始调试了。校验flag的关键流程在machine函数里面, 实现了一个VM, asdf为VM指令, hjkl是flag的值

而这个VM卡住我的地方在这里, 即case 10和 case 11, 到后来才知道这里是保存当前的状态或者还原当前状态, 类似于call

image-20200710201033197

opcode 如下:

-1 10 1 10 4 10 5 2 1 20 10 3 11 -2
-1 18 11 -2
-1 2 1 22 8 1 7 6 1 2 1 12 3 -11 1 2 1 13 20 11 -2
-1 29 0 11 -2
-1 10 2 21 8 22 7 5 2 2 20 10 3 1 2 1 8 23 7 9 24 20 10 6 2 1 12 3 -13 11 -2
-1
2 1 26 2 73 20 10 7
2 2 26 2 89 20 10 7
2 3 26 2 70 20 10 7
2 4 26 2 84 20 10 7
2 5 26 2 -111 20 10 7
2 6 26 2 116 20 10 7
2 7 26 2 103 20 10 7
2 8 26 2 124 20 10 7
2 9 26 2 121 20 10 7
2 10 26 2 102 20 10 7
2 11 26 2 99 20 10 7
2 12 26 2 42 20 10 7
2 13 26 2 124 20 10 7
2 14 26 2 77 20 10 7
2 15 26 2 121 20 10 7
2 16 26 2 123 20 10 7
2 17 26 2 43 20 10 7
2 18 26 2 43 20 10 7
2 19 26 2 77 20 10 7
2 20 26 2 43 20 10 7
2 21 26 2 43 20 10 7
2 22 26 2 111 20 10 7 11
-2
-1 21 22 2 122 17 23 11 -2
-1 10 8 27 22 21 13 8 4 7 5 2 2 20 10 3 11 -2
-1 21 2 108 17 20 11 -2 -2

指令处理起来, 不复杂。简单运算后, 逐字节比较。

enc = [73, 89, 70, 84, -111, 116, 103, 124, 121, 102, 99, 42, 124, 77, 121, 123, 43, 43, 77, 43, 43, 111]
flag = ''
for i in range(len(enc)):
flag += chr(122 ^ ((108 ^ enc[i]) + 4))
print(flag)

flag:SCTF{functi0n_ca11_11}

easy_re

TLS回调函数

根据字符串找到的main函数中是假的逻辑, 但是里面有回调函数, 去看了下回调函数, 发现有个crc32算值以后来异或data段中的数据, 而且有两处, 经过调试可以知道异或的值是0x610x78

image-20200709205751491

image-20200710191214579

把这两处异或的值patch成0, 再用idap ython脚本patch整段代码

image-20200710192159800

image-20200710192224424

idapy_patch.py:

import ida_bytes
addr = 0x0417418
length = 34048
xor_num = 0x61
buf = map(ord, ida_bytes.get_bytes(addr, length))
buf = map(lambda x: x ^ xor_num, buf)
ida_bytes.patch_bytes(addr, str(bytearray(buf)))

length = 0x10A00 - 0x8500
xor_num = 0x78
addr = addr + 0x8500
buf = map(ord, ida_bytes.get_bytes(addr, length))
buf = map(lambda x: x ^ xor_num, buf)
ida_bytes.patch_bytes(addr, str(bytearray(buf)))

异或以后可以看到一个熟悉的字符串, 猜测可能是个程序, 再用ida python dump出这份dll

image-20200709210032769

主逻辑

下内存断点, 经过调试可以知道主逻辑在0x0409FF0, 里面调用了dll中的encode函数进行判断

image-20200710194131500

题目中还有类似下面的花指令, 直接patch_nop掉就可以了

image-20200710102525686

patch_nop:

def patch_nop(begin, end):
while(end>begin):
PatchByte(begin, 0x90)
begin=begin+1

beginpusha地址, endpopa的下一条指令

这个函数还会导致堆栈不平衡, 依旧patch成nop, 接着就可以F5

image-20200710194512721

分析

根据动态调试, encode函数首先用SCTF2020的hex后字符串做为密钥, 对输入AES_ECB加密, 然后加密后得到的中间部分21个字节被分成三组, 每组七个字节先异或运算然后做移位运算, 算法相同但是异或的值不同, 最后所有字节异或0x55。

AES Sbox

image-20200710110655424

加密中间21字节

image-20200710112035085

所有字节异或0x55

image-20200710110034964

解密脚本(借鉴了队内的一个师傅的脚本):

from hashlib import md5
from Crypto.Cipher import AES

cmp_data = [0 for i in range(32)]
cmp_data[0] = 142
cmp_data[1] = 56
cmp_data[2] = 81
cmp_data[3] = 115
cmp_data[4] = -90
cmp_data[5] = -103
cmp_data[6] = 42
cmp_data[7] = -16
cmp_data[8] = -38
cmp_data[9] = -43
cmp_data[10] = 106
cmp_data[11] = -111
cmp_data[12] = -23
cmp_data[13] = 78
cmp_data[14] = -104
cmp_data[15] = -50
cmp_data[16] = 42
cmp_data[17] = -73
cmp_data[18] = 61
cmp_data[19] = 64
cmp_data[20] = -15
cmp_data[21] = -27
cmp_data[22] = 29
cmp_data[23] = -85
cmp_data[24] = -17
cmp_data[25] = -18
cmp_data[26] = -80
cmp_data[27] = -42
cmp_data[28] = 20
cmp_data[29] = 11
cmp_data[30] = 42
cmp_data[31] = -107


if __name__ == "__main__":
for i in range(len(cmp_data)):
cmp_data[i] = (cmp_data[i]) & 0xff
cmp_data[i] ^= 0x55
if i >= 7 and i <= 13:
cmp_data[i] = cmp_data[i]//0x10 + (cmp_data[i] % 0x10)*0x10
cmp_data[i] ^= 0xEF
if i >= 14 and i <= 20:
cmp_data[i] = ((cmp_data[i] & 0xcc) >> 2) | (
(cmp_data[i] << 2) & 0xcc)
cmp_data[i] ^= 0xBE
if i >= 21 and i <= 27:
cmp_data[i] = ((cmp_data[i] & 0xAA) >> 1) | (
(cmp_data[i] << 1) & 0xAA)
cmp_data[i] ^= 0xAD
print(cmp_data)
flag = b''
for i in range(len(cmp_data)):
flag += bytes([cmp_data[i]])
key = b"SCTF2020".hex()
print(key)
aes = AES.new(key, AES.MODE_ECB)
print(aes.decrypt(flag))

得到flag:

SCTF{y0u_found_the_true_secret}

参考WP

星盟安全ctf战队——SCTF–WriteUp