REVERSE-COMPETITION-VNCTF-2024
- 前言
- TBXO
- baby_c2
- yun
- obfuse
- ko
前言
ko的随机数算法没看出来,可惜~
这里给自己打个广告:东南网安研二在读,求实习,求内推,求老板们多看看我QAQ
TBXO
通过字符串定位到main函数汇编视图
存在混淆,最后retn的返回地址不太会算
调试发现就是几个块在栈上埋下返回地址,通过retn跳转,最后的retn还是会回到main函数
继续向下调试发现是一个魔改的TEA,多异或了0x33
密文和密钥已知,直接解密即可
#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
发现又套了一层powershell,同样是先解码base64再通过iex运行
通过替换iex为Write-Output打印出解码结果
先解码base64,然后启动相应的进程
通过cyberchef解码并保存为exe文件
ida打开该exe文件,调整跳转条件并去除花指令
发现两段SMC,都是异或v3,v3是一个时间相关的值,不知道具体是什么
继续分析发现这两个数组都被解释为了函数指针
一般情况下,函数开头的"push ebp"指令(机器码为0x55)是比较固定的,于是可知v3=0x55^0x8C=0xD9
手动还原代码,分析可知是标准的RC4加密算法
查找交叉引用来到,分析可知RC4密钥为文件名
在流量包中找到对应的TCP流
最后直接解密即可
yun
安卓逆向,jadx-gui打开
java层就是简单的长度和格式校验
ida打开libyun.so,搜索"java",只有一个静态注册的方法,但很明显方法名不对
分析JNI_OnLoad,发现通过RegisterNatives动态注册了flag0_o方法
method_name和method_signature是在init段动态修改的
同样通过arr_xor动态修改的变量还有Vnvn,而校验函数会用到这个变量
分析method_funcptr,inp传入enc,inp_enc与密文比较
分析enc,记录inp和Vnvn的索引,然后对两组索引依次进行2*2的矩阵乘法
最后又是一个查表和一个base64编码
需要注意的是,密文在JNI_OnLoad也通过arr_xor被动态修改
于是最终的解密脚本即为
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这种模式的花指令
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的魔改点
构造输入为"abcdefghijklmnopqrstuvwxyz123456"
经过不断调试,发现在aes_encrypt函数中存在两步运算xor和aes
在进入aes前,输入的前16字节与一段固定的数据异或,也就是CBC模式的初始向量iv
通过异或前后的数据得到这段iv为
0x16, 0x16, 0x1a, 0x1a, 0x1e, 0x1e, 0x1a, 0x1a, 0x16, 0x16, 0x2a, 0x2a, 0x2e, 0x2e, 0x2a, 0x2a
随后进入aes,aes大概魔改了三个地方
第一个魔改点在aes_add_round_key,通过调试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
第三个魔改点在aes_mix_columns,最后多异或了0x64
对应的解密过程即应调整为
解密密文前16字节得到flag前16个字符为"VNCTF{th1s_t@ste"
然后调整输入为"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~~?}"
最后验证成功
ko
一堆数据需要异或
这里笔者是先找好数据范围,把数据全都dump出来,把异或运算复制出来,计算完再把结果写回去
通过字符串提示找到标准的rc4_init
魔改的rc4_encode
魔改点在于异或运算时对sbox的索引增加了一个随机数
这个随机数由标准的MT19937算法生成,随机数种子为0xFA11010D
同样地,通过字符串提示找到密文
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联系笔者
在此先谢过各位老板