短短10个小时的比赛,题目更多的还是基础,算是温故而知新了。GoVM是第一次做到,总体看来还是跟普通VM有着共通点的。拿了三个一血,ak了re,跟去年那个菜鸟比起来也算是有所进步了。
Web
GameV4.0
F12,翻一翻
Base64 + URLdecode
Misc
仔细找找
灰度化图片,手工辨认……
vnctf{34aE@w}
问卷
None
Reverse
BabyMaze
pycdc
/ uncompyle6
都试了试,还是撸字节码
import marshal, dis fp = open(r"BabyMaze.pyc", 'rb') fp.seek(16) co = marshal.load(fp) dis.dis(co)
主要函数maze
def maze(): x = 1 y = 1 step = input() for i in range(len(step)): if step[i] == 'w': x -= 1 if step[i] == 's': x += 1 if step[i] == 'a': y -= 1 if step[i] == 'd': y += 1 print(x, y) if _map[x][y] != 0: if x == 29 and y == 29: return True else: return False
写个脚本拿到_map
dcode = open("opcode.txt", "r").read() _map = [] tmp = [] for line in dcode.splitlines(): if "LOAD_CONST" in line: a = line.split("LOAD_CONST ")[1].split(" (")[1].split(")")[0] tmp.append(a) elif "BUILD_LIST" in line: _map.append(tmp) tmp = [] print(_map)
迷宫
- 大小:
31 * 31
- 起点:5
(1, 1)
- 终点:7
(29, 29)
抄作业,dfs
route_stack = [[1, 1]] route_history = [[1, 1]] source = [···] def up(location): if location[1] == 0: return False else: new_location = [location[0], location[1] - 1] if new_location in route_history: return False elif source[new_location[0]][new_location[1]] == 1: return False else: route_stack.append(new_location) route_history.append(new_location) return True def down(location): if location[1] == 31: return False else: new_location = [location[0], location[1] + 1] if new_location in route_history: return False elif source[new_location[0]][new_location[1]] == 1: return False else: route_stack.append(new_location) route_history.append(new_location) return True def left(location): if location[0] == 0: return False else: new_location = [location[0] - 1, location[1]] if new_location in route_history: return False elif source[new_location[0]][new_location[1]] == 1: return False else: route_stack.append(new_location) route_history.append(new_location) return True def right(location): if location[0] == 31: return False else: new_location = [location[0] + 1, location[1]] if new_location in route_history: return False elif source[new_location[0]][new_location[1]] == 1: return False else: route_stack.append(new_location) route_history.append(new_location) return True lo = [1, 1] while route_stack[-1] != [29, 29]: if up(lo): lo = route_stack[-1] continue if down(lo): lo = route_stack[-1] continue if left(lo): lo = route_stack[-1] continue if right(lo): lo = route_stack[-1] continue route_stack.pop() lo = route_stack[-1] print(route_stack) track = "" for i in range(1, len(route_stack)): [_x, _y] = route_stack[i-1] [nx, ny] = route_stack[i] if nx - _x == 1: track += "s" elif nx - _x == -1: track += "w" elif ny - _y == 1: track += "d" elif ny - _y == -1: track += "a" print(track)
md5带上前后缀
cm狗
golang vm题
分析出大致的VM结构
struct func { void *call; vm *ptr; }; struct REG { _DWORD R[21]; }; struct vm { REG reg; _DWORD Memory[1000]; _DWORD rip; _DWORD index; void *data; func *func[100]; _QWORD isExit; };
导入后,vm的初始化过程清晰
执行过程
以及vm的指令
dump出字节码,写个decompiler(有点小问题,不过不影响笼统分析)
from string import printable _m = list(printable.encode()) _m.remove(10) _m.remove(13) _hex = lambda _d: "0x"+hex(_d)[2:].zfill(2) if _d > 15 else _d _chr = lambda _v: hex(_v) if _v not in _m else "'{ch}'".format(ch=chr(_v)) maps = { 0x1: "nop", 0x2: "mov R[{num}], {right} ; {ch}", 0x3: "mov R[{num}], R[{right}]", 0x4: "mov R[{num}], Memory[{right}]", 0x5: "mov Memory[num] R[{right}]", 0x6: "push R[{num}]", 0x7: "pop R[{num}]", 0x8: "add R[{num}], R[{right}]", 0x9: "dec R[{num}], R[{right}]", 0xa: "div R[{num}], R[{right}]", 0xb: "mul R[{num}], R[{right}]", 0xc: "xor R[{num}], R[{right}]", 0xd: "jmp R[{num}]", 0xe: "cmp R[{num}] R[{right}], je R[19]", 0xf: "cmp R[{num}] R[{right}], jne R[19]", 0x10: "cmp R[{num}] R[{right}], jb R[19]", 0x11: "cmp R[{num}] R[{right}], ja R[19]", 0x62: "getchar R[{num}]", 0x63: "putchar R[{num}]", 0x64: "vm quit" } data = [···] for i in range(0, len(data), 3): funcItem, arg1, arg2 = data[i], data[i+1], data[i+2] # print(funcItem+1, arg1, arg2) print(hex(i // 3).rjust(5, " ")+":", maps[funcItem+1].format(num=arg1, right=_hex(arg2), ch=_chr(arg2)))
简单分析pseudo asm
- 输入长度为43
- 转换为11组双字
- TEA
#include <stdio.h> #include <stdint.h> void decrypt (uint32_t* v, uint32_t* k) { uint32_t v0=v[0], v1=v[1], sum=0xC6EF3720, i; /* set up */ uint32_t delta=0x9e3779b9; /* a key schedule constant */ uint32_t k0=k[0], k1=k[1], k2=k[2], k3=k[3]; /* cache key */ for (i=0; i<32; i++) { /* basic cycle start */ v1 -= ((v0<<4) + k2) ^ (v0 + sum) ^ ((v0>>5) + k3); v0 -= ((v1<<4) + k0) ^ (v1 + sum) ^ ((v1>>5) + k1); sum -= delta; } /* end cycle */ v[0]=v0; v[1]=v1; } int main() { uint32_t v[12] = {0xe8d1d5df, 0xf5e3c114, 0x228ec216, 0x89d45a61, 0x655b8f69, 0x2484a07a, 0xd9e5e7f8, 0x3a441532, 0x91ab7e88, 0x69fc64bc, 0x7d3765, 0x00}; uint32_t k[4] = {0x95c4c, 0x871d, 0x1a7b7, 0x12c7c7}; for (int i = 0; i < 5; ++i) { decrypt(v+i*2, k); } printf("%s", (char*)v); }
cm1
Android题
一大堆障眼法,看到释放核心dex
jadx报错,改用gda
糊了一个解密
obyteArray = bytearray(open(r"ooo", "rb").read()) bBytes = b"vn2022" for i in range(len(obyteArray)): vi5 = i % 1024 obyteArray[i] = ((obyteArray[i] ^ bBytes[(vi5 % len(bBytes))]) & 0x00ff) open(r"classes.dex", "wb").write(obyteArray)
核心是xxtea
python简单处理下
from struct import unpack a = [68, 39, -92, 108, -82, -18, 72, -55, 74, -56, 38, 11, 60, 84, 97, -40, 87, 71, 99, -82, 120, 104, 47, -71, -58, -57, 0, 33, 42, 38, -44, -39, -60, 113, -2, 92, -75, 118, -77, 50, -121, 43, 32, -106] bytes = bytes(i % 256 for i in a) print(bytes, len(bytes)) print(unpack("<11L",bytes)) print(unpack("<4L", b"H4pPY_VNCTF!!OvO"))
扔给c跑一遍
#include <stdio.h> #include <stdint.h> #include <windows.h> #define DELTA 0x9e3779b9 #define MX (((z>>5^y<<2) + (y>>3^z<<4)) ^ ((sum^y) + (key[(p&3)^e] ^ z))) void btea(uint32_t *v, int n, uint32_t const key[4]) { uint32_t y, z, sum; unsigned p, rounds, e; if (n > 1) /* Coding Part */ { rounds = 6 + 52/n; sum = 0; z = v[n-1]; do { sum += DELTA; e = (sum >> 2) & 3; for (p=0; p<n-1; p++) { y = v[p+1]; z = v[p] += MX; } y = v[0]; z = v[n-1] += MX; } while (--rounds); } else if (n < -1) /* Decoding Part */ { n = -n; rounds = 6 + 52/n; sum = rounds*DELTA; y = v[0]; do { e = (sum >> 2) & 3; for (p=n-1; p>0; p--) { z = v[p-1]; y = v[p] -= MX; } z = v[n-1]; y = v[0] -= MX; sum -= DELTA; } while (--rounds); } } int main() { uint32_t key[4] = {1349530696, 1314283353, 558257219, 1333153569}; uint32_t v[12] = {1822697284, 3377000110, 187091018, 3630257212, 2925741911, 3106891896, 553699270, 3654559274, 1560179140, 850622133, 2518690695, 0}; btea(v, -11, key); printf("%s", (char*)v); return 0; }
时空飞行
蜜汁文字阅读题(bushi)
首先是check日期,看起来像极了sm4的set_key过程
然而sm4CalciRK
这里没有使用sbox,大概是照着sm4魔改的?
check了最后4个双字,从输出看起来日期长度是8
爆破一手
#include <stdio.h> #ifndef GET_ULONG_BE #define GET_ULONG_BE(n,b,i) \ { \ (n) = ( (unsigned long) (b)[(i) ] << 24 ) \ | ( (unsigned long) (b)[(i) + 1] << 16 ) \ | ( (unsigned long) (b)[(i) + 2] << 8 ) \ | ( (unsigned long) (b)[(i) + 3] ); \ } #endif #ifndef PUT_ULONG_BE #define PUT_ULONG_BE(n,b,i) \ { \ (b)[(i) ] = (unsigned char) ( (n) >> 24 ); \ (b)[(i) + 1] = (unsigned char) ( (n) >> 16 ); \ (b)[(i) + 2] = (unsigned char) ( (n) >> 8 ); \ (b)[(i) + 3] = (unsigned char) ( (n) ); \ } #endif #define SHL(x,n) (((x) & 0xFFFFFFFF) << n) #define ROTL(x,n) (SHL((x),n) | ((x) >> (32 - n))) static const unsigned long FK[4] = {0xa3b1bac6, 0x56aa3350, 0x677d9197, 0xb27022dc}; static const unsigned long CK[32] = { 0x00070e15, 0x1c232a31, 0x383f464d, 0x545b6269, 0x70777e85, 0x8c939aa1, 0xa8afb6bd, 0xc4cbd2d9, 0xe0e7eef5, 0xfc030a11, 0x181f262d, 0x343b4249, 0x50575e65, 0x6c737a81, 0x888f969d, 0xa4abb2b9, 0xc0c7ced5, 0xdce3eaf1, 0xf8ff060d, 0x141b2229, 0x30373e45, 0x4c535a61, 0x686f767d, 0x848b9299, 0xa0a7aeb5, 0xbcc3cad1, 0xd8dfe6ed, 0xf4fb0209, 0x10171e25, 0x2c333a41, 0x484f565d, 0x646b7279 }; static unsigned long sm4CalciRK(unsigned long ka) { return ka ^ (ROTL(ka, 13)) ^ (ROTL(ka, 23)); } static void sm4_setkey( unsigned long SK[32], unsigned char key[16] ) { unsigned long MK[4]; unsigned long k[36]; unsigned long i = 0; GET_ULONG_BE( MK[0], key, 0 ); GET_ULONG_BE( MK[1], key, 4 ); GET_ULONG_BE( MK[2], key, 8 ); GET_ULONG_BE( MK[3], key, 12 ); k[0] = MK[0] ^ FK[0]; k[1] = MK[1] ^ FK[1]; k[2] = MK[2] ^ FK[2]; k[3] = MK[3] ^ FK[3]; for (; i < 32; i++) { k[i + 4] = k[i] ^ (sm4CalciRK(k[i + 1] ^ k[i + 2] ^ k[i + 3] ^ CK[i])); SK[i] = k[i + 4]; } } int main() { unsigned long checkSK[4] = {0xFD07C452, 0xEC90A488, 0x68D33CD1, 0x96F64587}; for (int i = 0; i < 100000000; ++i) { unsigned long SK2[32] = {0}; unsigned char keys[16] = {0}; sprintf((char*)keys,"%08d", i); sm4_setkey(SK2, keys); int find = 1; for (int j = 0; j < 4; ++j) { if (*(SK2+28+j) != checkSK[j]) { find = 0; break; } } if (find) { printf("%s", keys); break; } } return 0; }
拿到日期20211205
而后是对flag进行check
流程很复杂,直接用z3跑了(
这里要注意比对数据会被伪随机异或,输入正确日期后dump即可
还原代码的时候要注意双字转字节过程中的大端小端和偏移。
糊了一份脚本出来
# -*- coding:utf-8 -*- """ @Author: Mas0n @File: vnctf2022_4.py @Time: 2022-02-12 15:04 @Desc: It's all about getting better. """ import struct def _PUT_ULONG_BE(_v): DEST = [0] * 4 DEST[3] = ((_v >> 24) & 0xff) DEST[2] = ((_v >> 16) & 0xff) DEST[1] = ((_v >> 8) & 0xff) DEST[0] = ((_v >> 0) & 0xff) return DEST def PUT_ULONG_BE(_v): DEST = [0] * 4 DEST[0] = ((_v >> 24) & 0xff) DEST[1] = ((_v >> 16) & 0xff) DEST[2] = ((_v >> 8) & 0xff) DEST[3] = ((_v >> 0) & 0xff) return DEST def MODIFY_PUT_ULONG_BE(_v, offset): _v3 = _v.copy() DEST = [0] * 4 _v4 = offset for i in range(4): DEST[i] = _v3[_v4] _v4 += 1 _v4 %= 4 return DEST def GET_ULONG_BE(_v): return _v[3] | (_v[2] << 8) | ((_v[1] << 16) | (_v[0] << 24)) xorArr = [0x01000000, 0x02000000, 0x04000000, 0x08000000, 0x10000000, 0x20000000, 0x40000000, 0x80000000, 0x1B000000, 0x36000000] def FUNC1(_v, _idx): _a1 = PUT_ULONG_BE(_v) v3 = MODIFY_PUT_ULONG_BE(_a1, 1) v4 = GET_ULONG_BE(v3) return v4 ^ xorArr[_idx] from z3 import * a1 = [BitVec(f"flag[{i}]", 32) for i in range(66)] sol = Solver() v3 = 0 v5 = 6 while v5 < 66: if v5 % 6 != 0: a1[v5] = a1[v5 - 6] ^ a1[v5 - 1] else: v2 = a1[v5 - 6] a1[v5] = v2 ^ FUNC1(a1[v5 - 1], v3) v3 += 1 v5 += 1 v4 = [] for i in range(6): v4.extend(_PUT_ULONG_BE(a1[60 + i])) for i in range(1, 24): v4[i - 1] ^= (v4[i - 1] % 0x12 + v4[i] + 5) ^ 0x41 cmpd = [0x00000025, 0x00000015, 0x000000DF, 0x000000A2, 0x000000C0, 0x00000093, 0x000000AD, 0x00000014, 0x00000046, 0x000000C5, 0x0000000F, 0x0000002E, 0x0000009A, 0x000000EB, 0x00000030, 0x000000F8, 0x00000020, 0x000000E9, 0x000000CB, 0x00000088, 0x000000C6, 0x000000BE, 0x0000008D, 0x000000E3] for i in range(24): sol.add(v4[i] == cmpd[i]) stop = PUT_ULONG_BE(a1[0]) sol.add(stop[0] == ord("V")) sol.add(stop[1] == ord("N")) sol.add(stop[2] == ord("C")) sol.add(stop[3] == ord("T")) assert sol.check() == sat mol = sol.model() print(struct.pack(">6L", *[mol.eval(i).as_long() for i in a1[:6]]))
发表回复