Mas0n
to be reverse engineer🐧
翻车鱼

VNCTF2022

短短10个小时的比赛,题目更多的还是基础,算是温故而知新了。GoVM是第一次做到,总体看来还是跟普通VM有着共通点的。拿了三个一血,ak了re,跟去年那个菜鸟比起来也算是有所进步了。

Web

GameV4.0

F12,翻一翻

https://cdn.shi1011.cn/2022/02/dcbc4e255900ed699ddb4577768087f4.png?imageMogr2/format/webp/interlace/0/quality/90|watermark/2/text/wqlNYXMwbg/font/bXN5aGJkLnR0Zg/fontsize/14/fill/IzMzMzMzMw/dissolve/80/gravity/southeast/dx/5/dy/5

Base64 + URLdecode

https://cdn.shi1011.cn/2022/02/21b109fd487943cc47c0988c8f8227bd.png?imageMogr2/format/webp/interlace/0/quality/90|watermark/2/text/wqlNYXMwbg/font/bXN5aGJkLnR0Zg/fontsize/14/fill/IzMzMzMzMw/dissolve/80/gravity/southeast/dx/5/dy/5

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的初始化过程清晰

https://cdn.shi1011.cn/2022/02/b19653d2ba123400b627591042aa7672.png?imageMogr2/format/webp/interlace/0/quality/90|watermark/2/text/wqlNYXMwbg/font/bXN5aGJkLnR0Zg/fontsize/14/fill/IzMzMzMzMw/dissolve/80/gravity/southeast/dx/5/dy/5

执行过程

https://cdn.shi1011.cn/2022/02/9f229e3936b5ac40f8997ca0dcda3286.png?imageMogr2/format/webp/interlace/0/quality/90|watermark/2/text/wqlNYXMwbg/font/bXN5aGJkLnR0Zg/fontsize/14/fill/IzMzMzMzMw/dissolve/80/gravity/southeast/dx/5/dy/5

以及vm的指令

https://cdn.shi1011.cn/2022/02/b96e6160b6eb3ee6c810bc438fc1d39e.png?imageMogr2/format/webp/interlace/0/quality/90|watermark/2/text/wqlNYXMwbg/font/bXN5aGJkLnR0Zg/fontsize/14/fill/IzMzMzMzMw/dissolve/80/gravity/southeast/dx/5/dy/5

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

https://cdn.shi1011.cn/2022/02/520738301557834707c1c42c149f1afc.png?imageMogr2/format/webp/interlace/0/quality/90|watermark/2/text/wqlNYXMwbg/font/bXN5aGJkLnR0Zg/fontsize/14/fill/IzMzMzMzMw/dissolve/80/gravity/southeast/dx/5/dy/5

jadx报错,改用gda

https://cdn.shi1011.cn/2022/02/34a631dbb0601eb3cba8835700b8c986.png?imageMogr2/format/webp/interlace/0/quality/90|watermark/2/text/wqlNYXMwbg/font/bXN5aGJkLnR0Zg/fontsize/14/fill/IzMzMzMzMw/dissolve/80/gravity/southeast/dx/5/dy/5

糊了一个解密

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

https://cdn.shi1011.cn/2022/02/831a716fd2372fb65b8e72cd3f852b57.png?imageMogr2/format/webp/interlace/0/quality/90|watermark/2/text/wqlNYXMwbg/font/bXN5aGJkLnR0Zg/fontsize/14/fill/IzMzMzMzMw/dissolve/80/gravity/southeast/dx/5/dy/5

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过程

https://cdn.shi1011.cn/2022/02/262e5705a77ce1a150f4f9e0ce777444.png?imageMogr2/format/webp/interlace/0/quality/90|watermark/2/text/wqlNYXMwbg/font/bXN5aGJkLnR0Zg/fontsize/14/fill/IzMzMzMzMw/dissolve/80/gravity/southeast/dx/5/dy/5

然而sm4CalciRK这里没有使用sbox,大概是照着sm4魔改的?

https://cdn.shi1011.cn/2022/02/068920d65e682777e24b3b27224d942d.png?imageMogr2/format/webp/interlace/0/quality/90|watermark/2/text/wqlNYXMwbg/font/bXN5aGJkLnR0Zg/fontsize/14/fill/IzMzMzMzMw/dissolve/80/gravity/southeast/dx/5/dy/5

check了最后4个双字,从输出看起来日期长度是8

https://cdn.shi1011.cn/2022/02/29ecdf97bf1f1f2169995cf0ebc8c59e.png?imageMogr2/format/webp/interlace/0/quality/90|watermark/2/text/wqlNYXMwbg/font/bXN5aGJkLnR0Zg/fontsize/14/fill/IzMzMzMzMw/dissolve/80/gravity/southeast/dx/5/dy/5

爆破一手

#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

https://cdn.shi1011.cn/2022/02/c49d2490490d01fe460bb62df79aa029.png?imageMogr2/format/webp/interlace/0/quality/90|watermark/2/text/wqlNYXMwbg/font/bXN5aGJkLnR0Zg/fontsize/14/fill/IzMzMzMzMw/dissolve/80/gravity/southeast/dx/5/dy/5

流程很复杂,直接用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]]))

本文链接:https://blog.shi1011.cn/ctf/2162
本文采用 CC BY-NC-SA 4.0 Unported 协议进行许可

Mas0n

文章作者

发表回复

textsms
account_circle
email

  • t0hka

    这篇里的z3解时空旅行这题和你之前的那篇z3文章讲解结合起来学到了不少😆

    3年前 回复

翻车鱼

VNCTF2022
短短10个小时的比赛,题目更多的还是基础,算是温故而知新了。GoVM是第一次做到,总体看来还是跟普通VM有着共通点的。拿了三个一血,ak了re,跟去年那个菜鸟比起来也算是有所进步了。 …
扫描二维码继续阅读
2022-02-13