Signin
用qt和python写的, 用PyInstaller打包, 可以用pyinstxtractor解包出来, 在解包出来的文件夹下有个main, 其实是个pyc文件, 更名为main.pyc
。
然后由于PyInstaller生成文件时会去掉头部信息, 所以在反汇编之前需要补充头部信息。方法就是找到struct文件, 复制进main.pyc
即可
接着可以用decomplie3或uncomplie6
反汇编, 得到main.py
可以看到调用tmp.dll
里面的enc
函数
而这个tmp.dll
是程序运行时产生的, 运行后消失。
加密逻辑很简单, CRC64 + 异或加密 + base64
解密脚本如下:
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中找到路由表, 且为多级路由。
整理如下:
/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
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
段中的数据, 而且有两处, 经过调试可以知道异或的值是0x61
和0x78
把这两处异或的值patch成0, 再用idap ython脚本patch整段代码
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
主逻辑
下内存断点, 经过调试可以知道主逻辑在0x0409FF0
, 里面调用了dll中的encode函数进行判断
题目中还有类似下面的花指令, 直接patch_nop掉就可以了
patch_nop:
def patch_nop(begin, end): while(end>begin): PatchByte(begin, 0x90) begin=begin+1
|
begin
是pusha
地址, end
是popa
的下一条指令
这个函数还会导致堆栈不平衡, 依旧patch成nop, 接着就可以F5
分析
根据动态调试, encode函数首先用SCTF2020的hex后字符串做为密钥, 对输入AES_ECB加密, 然后加密后得到的中间部分21个字节被分成三组, 每组七个字节先异或运算然后做移位运算, 算法相同但是异或的值不同, 最后所有字节异或0x55。
AES Sbox
加密中间21字节
所有字节异或0x55
解密脚本(借鉴了队内的一个师傅的脚本):
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