REVERSE-COMPETITION-VNCTF-2024

REVERSE-COMPETITION-VNCTF-2024

前言

ko的随机数算法没看出来,可惜~
这里给自己打个广告:东南网安研二在读,求实习,求内推,求老板们多看看我QAQ
blog

TBXO

通过字符串定位到main函数汇编视图
存在混淆,最后retn的返回地址不太会算
调试发现就是几个块在栈上埋下返回地址,通过retn跳转,最后的retn还是会回到main函数
TBXO-main
继续向下调试发现是一个魔改的TEA,多异或了0x33
TBXO-tea
密文和密钥已知,直接解密即可

#include <stdio.h>
#include <stdint.h>

//加密函数
void encrypt(unsigned int num_rounds, uint32_t *v, uint32_t *k)
{
    uint32_t v0 = v[0], v1 = v[1], sum = 0, i;
    uint32_t delta = 0x61C88647;
    uint32_t k0 = k[0], k1 = k[1], k2 = k[2], k3 = k[3];
    for (i = 0; i < num_rounds; i++)
    {
        sum -= delta;
        v0 += ((v1 << 4) + k0) ^ (v1 + sum) ^ ((v1 >> 5) + k1) ^ 0x33;
        v1 += ((v0 << 4) + k2) ^ (v0 + sum) ^ ((v0 >> 5) + k3) ^ 0x33;
    }
    v[0] = v0;
    v[1] = v1;
    printf("sum==0x%x\n", sum);
}

//解密函数
void decrypt(unsigned int num_rounds, uint32_t *v, uint32_t *k)
{
    uint32_t v0 = v[0], v1 = v[1], i;
    uint32_t delta = 0x61C88647, sum = 0xc6ef3720;
    uint32_t k0 = k[0], k1 = k[1], k2 = k[2], k3 = k[3];
    for (i = 0; i < num_rounds; i++)
    {
        v1 -= ((v0 << 4) + k2) ^ (v0 + sum) ^ ((v0 >> 5) + k3) ^ 0x33;
        v0 -= ((v1 << 4) + k0) ^ (v1 + sum) ^ ((v1 >> 5) + k1) ^ 0x33;
        sum += delta;
    }
    v[0] = v0;
    v[1] = v1;
    printf("sum==0x%x\n", sum);
}

//打印数据 hex_or_chr: 1-hex 0-chr
void dump_data(uint32_t *v, int n, bool hex_or_chr)
{
    if (hex_or_chr)
    {
        for (int i = 0; i < n; i++)
        {
            printf("0x%x,", v[i]);
        }
    }
    else
    {
        for (int i = 0; i < n; i++)
        {
            for (int j = 0; j < sizeof(uint32_t) / sizeof(uint8_t); j++)
            {
                printf("%c", (v[i] >> (j * 8)) & 0xFF);
            }
        }
    }
    printf("\n");
    return;
}

int main()
{
    // v为要加解密的数据
    uint32_t v[] = {0x31363010, 0xad938623, 0x8492d4c5, 0x7567e366, 0xc786696b, 0xa0092e31, 0xdb695733, 0xdd13a893, 0x88d8a53e, 0x7e845437};
    // k为加解密密钥,4个32位无符号整数,密钥长度为128位
    uint32_t k[4] = {0x67626463, 0x696D616E, 0x79645F65, 0x6B696C69};
    // num_rounds,建议取值为32
    unsigned int r = 32;

    int n = sizeof(v) / sizeof(uint32_t);
    /*
    printf("加密前明文数据:");
    dump_data(v, n, 1);

    for (int i = 0; i < n / 2; i++)
    {
        encrypt(r, &v[i * 2], k);
    }
    */
    printf("加密后密文数据:");
    dump_data(v, n, 1);

    for (int i = 0; i < n / 2; i++)
    {
        decrypt(r, &v[i * 2], k);
    }

    printf("解密后明文数据:");
    dump_data(v, n, 1);

    printf("解密后明文字符:");
    dump_data(v, n, 0);

    return 0;
}

// VNCTF{Box_obfuscation_and_you_ar_socool}

baby_c2

附件为一段powershell和一个流量包
powershell先解码base64再通过iex运行
将iex替换为Write-Output并运行powershell
baby_c2-base64
发现又套了一层powershell,同样是先解码base64再通过iex运行
通过替换iex为Write-Output打印出解码结果
baby_c2-process
先解码base64,然后启动相应的进程
通过cyberchef解码并保存为exe文件
baby_c2-exe
ida打开该exe文件,调整跳转条件并去除花指令
baby_c2-decode
发现两段SMC,都是异或v3,v3是一个时间相关的值,不知道具体是什么
baby_c2-smc
继续分析发现这两个数组都被解释为了函数指针
一般情况下,函数开头的"push ebp"指令(机器码为0x55)是比较固定的,于是可知v3=0x55^0x8C=0xD9
手动还原代码,分析可知是标准的RC4加密算法
baby_c2-rc4
baby_c2-rc4
查找交叉引用来到,分析可知RC4密钥为文件名
baby_c2-main
在流量包中找到对应的TCP流
baby_c2-tcp
最后直接解密即可
baby_c2-flag

yun

安卓逆向,jadx-gui打开
java层就是简单的长度和格式校验
yun-java
ida打开libyun.so,搜索"java",只有一个静态注册的方法,但很明显方法名不对
yun-so
分析JNI_OnLoad,发现通过RegisterNatives动态注册了flag0_o方法
yun-so
yun-so
yun-so
method_name和method_signature是在init段动态修改的
yun-so
同样通过arr_xor动态修改的变量还有Vnvn,而校验函数会用到这个变量
yun-so
分析method_funcptr,inp传入enc,inp_enc与密文比较
yun-so
分析enc,记录inp和Vnvn的索引,然后对两组索引依次进行2*2的矩阵乘法
yun-so
最后又是一个查表和一个base64编码
yun-so
需要注意的是,密文在JNI_OnLoad也通过arr_xor被动态修改
yun-so
于是最终的解密脚本即为

import base64
import claripy

tab = "abcdefghijklmnopqrstuvwxyz1234567890-"

cipher = [0x62, 0x6a, 0x4a, 0x79, 0x64, 0x58, 0x59, 0x34, 0x4c, 0x58, 0x42, 0x36, 0x63, 0x6e, 0x49, 0x79, 0x5a, 0x6a, 0x4a, 0x78, 0x61, 0x6d, 0x30,
          0x79, 0x64, 0x47, 0x4e, 0x76, 0x4d, 0x33, 0x52, 0x71, 0x4f, 0x47, 0x51, 0x30, 0x65, 0x54, 0x42, 0x70, 0x64, 0x48, 0x70, 0x6f, 0x4d, 0x58, 0x55, 0x74]
cipher = base64.b64decode(bytes(cipher)).decode()
cipher_idx = []
for c in cipher:
    cipher_idx.append(tab.find(c))

key = "s0ry"
key_idx = []
for c in key:
    key_idx.append(tab.find(c))

inp_idx = []
for idx in range(0, len(cipher_idx), 2):
    inp = [claripy.BVS(f"inp_{i}", 16) for i in range(2)]
    s = claripy.Solver()
    s.add((inp[0]*key_idx[0]+inp[1]*key_idx[2]) % len(tab) == cipher_idx[idx])
    s.add((inp[0]*key_idx[1]+inp[1]*key_idx[3]) %
          len(tab) == cipher_idx[idx+1])
    s.add(inp[0] >= 0)
    s.add(inp[0] < len(tab))
    s.add(inp[1] >= 0)
    s.add(inp[1] < len(tab))
    if s.check_satisfiability() == "SAT":
        for i in s.batch_eval(inp, 1):
            for ii in i:
                inp_idx.append(ii)

flag = ""
for idx in inp_idx:
    flag += tab[idx]
print(flag)

# 75531c14-8825-44ed-a9ec-74d47d5cb76b

obfuse

简单去一下jb/jnb这种模式的花指令
obfuse-main

from idaapi import *
from idautils import *
start = 0x0000000000401A10
finish = 0x0000000000407F41
while start <= finish:
    ins1 = get_bytes(start, 6)
    ins2 = get_bytes(start+6, 6)
    if ins1.hex().startswith("0f") and ins2.hex().startswith("0f"):
        addr1 = start+6+ins1[2]
        addr2 = start+6+6+ins2[2]
        if addr1 == addr2:
            patch_bytes(start, b"\x90"*(addr1-start))
        start += 12
    else:
        start += 1

边调试边猜,大致确定如下的校验逻辑
输入经过AES加密后与已知的密文比较,密钥已知,需要找到AES的魔改点
obfuse-main
构造输入为"abcdefghijklmnopqrstuvwxyz123456"
经过不断调试,发现在aes_encrypt函数中存在两步运算xor和aes
obfuse-aes
在进入aes前,输入的前16字节与一段固定的数据异或,也就是CBC模式的初始向量iv
通过异或前后的数据得到这段iv为

0x16, 0x16, 0x1a, 0x1a, 0x1e, 0x1e, 0x1a, 0x1a, 0x16, 0x16, 0x2a, 0x2a, 0x2e, 0x2e, 0x2a, 0x2a

随后进入aes,aes大概魔改了三个地方
obfuse-aes
第一个魔改点在aes_add_round_key,通过调试aes源码,比较数据可知魔改相当于如下效果
obfuse-aes
即在每轮标准的aes_add_round_key后又异或一段固定的数据,该段固定数据可计算得到为

uint8_t xor_arr[] = {0x0, 0x1, 0x2, 0x3, 0x1, 0x0, 0x3, 0x2, 0x2, 0x3, 0x0, 0x1, 0x3, 0x2, 0x1, 0x0};

第二个魔改点在aes_sub_bytes,最后多异或了0xC8
obfuse-aes
第三个魔改点在aes_mix_columns,最后多异或了0x64
obfuse-aes
对应的解密过程即应调整为
obfuse-aes
obfuse-aes
obfuse-aes
解密密文前16字节得到flag前16个字符为"VNCTF{th1s_t@ste"
obfuse-aes
然后调整输入为"VNCTF{th1s_t@steqrstuvwxyz123456"
计算得到输入的后16字节在进入aes前需要异或的初始向量iv为

0xa3,0x81,0x39,0x1a,0xac,0xce,0x11,0xfe,0x12,0xc8,0x7b,0xa9,0x5e,0xc8,0xd,0x5b

于是可解出flag后16个字符为"s_go0d_r1ght~~?}"
obfuse-aes
最后验证成功
obfuse-aes

ko

一堆数据需要异或
ko-xor
这里笔者是先找好数据范围,把数据全都dump出来,把异或运算复制出来,计算完再把结果写回去
ko-strings
通过字符串提示找到标准的rc4_init
ko-rc4
魔改的rc4_encode
ko-rc4
魔改点在于异或运算时对sbox的索引增加了一个随机数
ko-rc4
这个随机数由标准的MT19937算法生成,随机数种子为0xFA11010D
ko-rc4
同样地,通过字符串提示找到密文
ko-rc4
rc4密钥应该在运行过程中被直接打印出来
ko-rc4
于是可先计算得到42个随机数

def _int32(x):
    return int(0xFFFFFFFF & x)


class MT19937:
    def __init__(self, seed):
        self.mt = [0] * 624
        self.mt[0] = seed
        self.mti = 0
        for i in range(1, 624):
            self.mt[i] = _int32(
                1812433253 * (self.mt[i - 1] ^ self.mt[i - 1] >> 30) + i)

    def extract_number(self):
        if self.mti == 0:
            self.twist()
        y = self.mt[self.mti]
        y = y ^ y >> 11
        y = y ^ y << 7 & 2636928640
        y = y ^ y << 15 & 4022730752
        y = y ^ y >> 18
        self.mti = (self.mti + 1) % 624
        return _int32(y)

    def twist(self):
        for i in range(0, 624):
            y = _int32((self.mt[i] & 0x80000000) +
                       (self.mt[(i + 1) % 624] & 0x7fffffff))
            self.mt[i] = (y >> 1) ^ self.mt[(i + 397) % 624]

            if y % 2 != 0:
                self.mt[i] = self.mt[i] ^ 0x9908b0df


mt = MT19937(0xFA11010D)
rand_num = []
for i in range(42):
    rand_num.append(mt.extract_number())
print(rand_num)
# [4194389780, 1470670990, 3026560136, 833079161, 4131466323, 759157580, 4009079223, 2614551579, 513472238, 2993535632, 3748155928, 1410131130, 299129314, 1493771472, 794099837, 1833596110, 2003735283, 2301239837, 2210620102, 1212227044, 286209931, 3671727524, 2298672460, 4293907163, 1382287885, 2958570228, 2560383921, 2598710070, 1618082056, 461162807, 2533884627, 631807209, 1331614827, 3197521366, 586344870, 3568429591, 2977811948, 661755625, 1170402972, 1364250142, 3774156842, 380335765]

然后再进行rc4解密,即可得到flag

#include <stdio.h>

/*
RC4初始化函数
*/
void rc4_init(unsigned char *s, unsigned char *key, unsigned long Len_k)
{
    unsigned int i = 0, j = 0;
    char k[256] = {0};
    unsigned char tmp = 0;
    for (i = 0; i < 256; i++)
    {
        s[i] = i;
        k[i] = key[i % Len_k];
    }
    for (i = 0; i < 256; i++)
    {
        j = (j + s[i] + k[i]) % 256;
        tmp = s[i];
        s[i] = s[j];
        s[j] = tmp;
    }
}

long long int rand_num[42] = {4194389780, 1470670990, 3026560136, 833079161, 4131466323, 759157580, 4009079223, 2614551579, 513472238, 2993535632, 3748155928, 1410131130, 299129314, 1493771472, 794099837, 1833596110, 2003735283, 2301239837, 2210620102, 1212227044, 286209931, 3671727524, 2298672460, 4293907163, 1382287885, 2958570228, 2560383921, 2598710070, 1618082056, 461162807, 2533884627, 631807209, 1331614827, 3197521366, 586344870, 3568429591, 2977811948, 661755625, 1170402972, 1364250142, 3774156842, 380335765};

/*
RC4加解密函数
unsigned char* Data     加解密的数据
unsigned long Len_D     加解密数据的长度
unsigned char* key      密钥
unsigned long Len_k     密钥长度
*/
void rc4_crypt(unsigned char *Data, unsigned long Len_D, unsigned char *key, unsigned long Len_k) //加解密
{
    unsigned char s[256];
    rc4_init(s, key, Len_k);
    int i = 0, j = 0, t = 0;
    unsigned long k = 0;
    unsigned char tmp;
    for (k = 0; k < Len_D; k++)
    {
        i = (i + 1) % 256;
        j = (j + s[i]) % 256;
        tmp = s[i];
        s[i] = s[j];
        s[j] = tmp;
        t = (s[i] + s[j] + rand_num[k]) % 256;
        Data[k] = Data[k] ^ s[t];
    }
}

int main()
{
    //字符串密钥
    /*
    unsigned char key[] = "";
    unsigned long key_len = sizeof(key) - 1;
    */
    //数组密钥
    unsigned char key[] = {98, 51, 66, 108, 98, 110, 78, 122, 97, 67, 49, 114, 90, 88, 107, 116, 100, 106, 69, 65, 65, 65, 65, 65, 66, 71, 53, 118, 98, 109, 85, 65, 65, 65, 65, 69, 98, 109, 57, 117, 90, 81, 65, 65, 65, 65, 65, 65, 65, 65, 65, 66, 65, 65, 65, 66, 108, 119, 65, 65, 65, 65, 100, 122, 99, 50, 103, 116, 99, 110, 78, 104, 65, 65, 65, 65, 65, 119, 69, 65, 65, 81, 65, 65, 65, 89, 69, 65, 119, 108, 49, 120, 68, 99, 65, 104, 98, 100, 109, 52, 57, 122, 57, 77, 69, 74, 117, 86, 107, 121, 119, 50, 106, 70, 106, 65, 65, 73, 86, 89, 116, 89, 73, 90, 50, 68, 51, 98, 75, 48, 89, 112, 75, 76, 117, 79, 89, 55, 117, 113, 116, 70, 79, 115, 106, 67, 79, 49, 73, 52, 55, 90, 76, 99, 50, 110, 71, 87, 102, 114, 118, 97, 120, 116, 67, 111, 50, 56, 78, 53, 54, 73, 101, 53, 66, 108, 118, 68, 104, 106, 107, 113, 108, 113, 78, 90, 106, 76, 119, 104, 65, 86, 67, 78, 76, 87, 110, 116, 67, 115, 88, 74, 110, 56, 120, 116, 51, 118, 104, 67, 69, 102, 57, 99, 99, 69, 105, 78, 75, 103, 74, 81, 122, 73, 118, 88, 115, 102, 57, 54, 85, 104, 50, 107, 55, 54, 99, 88, 76, 105, 54, 121, 113, 122, 118, 106, 122, 109, 78, 110, 107, 117, 113, 105, 80, 118};
    unsigned long key_len = sizeof(key);

    //加解密数据
    unsigned char data[] = {31, 209, 241, 145, 62, 90, 173, 108, 4, 37, 225, 196, 154, 90, 160, 119, 29, 107, 231, 23, 171, 16, 231, 172, 41, 73, 19, 251, 196, 135, 213, 190, 96, 215, 215, 150, 146, 236, 225, 97, 138, 176};
    //加解密
    rc4_crypt(data, sizeof(data), key, key_len);

    for (int i = 0; i < sizeof(data); i++)
    {
        printf("%c", data[i]);
    }

    printf("\n");
    return 0;
}
// VNCTF{Welcome_To_Kernel_World@RETraveler}

感谢大家读到这里
最后再表达一下笔者的求职意愿,老板们可通过站内私信或添加微信P1umH0联系笔者
在此先谢过各位老板

  • 23
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 4
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

P1umH0

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值