春秋杯 2024夏季

BEDTEA

[题目制作过程]:

首先是通过矩阵相乘的方法来获取斐波那契数列来作为后面tea的每一轮的key,tea的轮数常数和左移右移都被魔改了,加了PEB的反调试,这个调试的作用是key从第几位取,如果正常运行则是从第三个开始取,考察师傅们面对tea的魔改的能力,然后将加密后的数据用先序遍历的顺序构建树,然后用后序遍历来取值,这里其实就是一个逆序,如果没走出来就会在里面一直套娃,最后根据时间戳,也是反调试,来异或一个常数。

如果题目想要继续上升难度可以考虑用rust重写,采用中序遍历,或者使用tls来实现反调试

[题目writeup]:

根据字符串可以很快定位到关键函数

image-20240624193322486

调试发现这些是没有逻辑的垃圾值,只是为了混淆而出

image-20240624193352428

这个地方是一个时间戳检测,通过两次时间戳的不同来获取运行时间然后可以用来做反调试

只要不在这两个地方之间下断点就好了,或者手动patch一下

image-20240624193409434

image-20240624193428388

这里也是通过一个PEB来实现反调试,调试的话会赋值为1,不调试则是1

image-20240624193435893

可以通过修改eax寄存器的值来达到绕过

image-20240624193733279

继续跟进下面的逻辑,很明显的tea特征但是细心可以发现是魔改了左移右移常数和轮数

image-20240624193750900

上面有常数的赋值

image-20240624193759947

其实继续调试到下面,会发现这里的key后面会被再次赋值

所以这个并不是真正的key,真正的key可以在调试的时候拿到,轮数也可以调试拿到

这里原来通过key可以看出3,5,8,13,21,34这其实是斐波那契数列

(源码中也可以看见这里是通过初始矩阵然后矩阵乘法来获取斐波那契数列

但是考虑到可能有点些许脑洞于是就成了现在这样,调试的时候可以拿key)

image-20240624194023823

后面两个tea函数是相同的,只是key是不同的,耐心调试就可以拿到

此处不一一赘述

这两个函数是关键,跟进一下

image-20240624154823937

有能力的师傅可以逆向看出来这就是一个通过迭代实现二叉树的前序遍历构造(跟进就会无限套娃),

image-20240624194104551

下面一个函数就是二叉数的后序遍历

把值拿出来

image-20240624194216729

其实这里想的是可以通过前后的输入输出来看这两段干的是一个翻转的功能,即把加密后的结果进行翻转

然后对最后的值进行异或,数据类型被优化了,其实就是异或0x33

image-20240624194237119

最后v11和v12是我们的加密的结果和密文

image-20240624194331901

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



//0x76, 0x71, 0x9d, 0xe7, 0x70, 0x77, 0x3f, 0xa3, 0x2, 0xf1, 
// 0x8d, 0xc9, 0x2, 0xc6, 0xa2, 0x4b, 0xba, 0x19, 0x56, 0x5, 0xf2, 
// 0x89, 0x5e, 0xe0
// 异或0x33得到:
//  0x4542AED4  0X43440C90, 
//     0x31C2BEFA 0X31F59178, 
//     0x892A6536  0XC1BA6DD3
//解密函数
void decrypt(uint32_t* v, uint32_t* k) {
    uint32_t v0 = v[0], v1 = v[1], sum = 0x987E55D0, i;
    uint32_t delta = 0x9e3449b8;
    uint32_t k0 = k[0], k1 = k[1], k2 = k[2], k3 = k[3];
    for (i = 0; i<22; i++) {
        v1 -= ((v0 << 5) + k2) ^ (v0 + sum) ^ ((v0 >> 4) + k3);
        v0 -= ((v1 << 5) + k0) ^ (v1 + sum) ^ ((v1 >> 4) + k1);
        sum -= delta;
    }
    v[0] = v0; v[1] = v1;
}

int main()
{
    // v为要加解密的数据,两个32位无符号整数
    uint32_t v[6] = { 0XC1BA6DD3,0X892A6536,0X31F59178,0X31C2BEFA,0X43440C90,0X4542AED4};
    // k为加解密密钥,4个32位无符号整数,密钥长度为128位,斐波那契数列

    uint32_t k[4] = { 3,5,8,13 };
    uint32_t k1[4] = { 21,34,55,89 };
    uint32_t k2[4]={144,233,377,610};

    decrypt(v, k);
    decrypt(v+2, k1);
    decrypt(v+4, k2);
    puts((char*)v);
    printf("\n");
    return 0;
}
//flag{y0u_reallyl1ke_te@}

HardSignin

[题目制作过程]:

  1. UPX加壳(修改区段名加难度)

  2. 4个TLS函数,存在花指令和反调试

    TLS中执行SMC解密main,伪随机数base64换表和生成key

  3. 在main函数,输入flag依次调用base64,rc4,xtea对flag加密

[题目writeup]:

基本解题思路如下:

  1. 修复区段名,upx脱壳

  2. TLS函数,去花指令,伪随机数生成rc4和xtea的key

  3. SMC,恢复main函数

  4. 换表base64+rc4+xtea

    脚本或者动调拿base表和key

脱UPX壳

直接脱壳会失败,010editor打开,修复区段名,将VMP改为UPX即可一键脱壳

TLS函数

花指令

TLS函数中存在花指令,手动修复后看到真实逻辑

其中TLS3的花指令将0x4011e8-0x4011f5这段全部nop即可

每个TLS函数都存在反调试操作,均为常规反调试API,全部patch即可

TLS0-SMC

TLS0反调试直接patch跳转地址绕过exit分支

执行了SMC操作解密main函数代码,并设置随机数种子为0x114514

TLS1-base换表

直接patch跳转地址绕过exit分支

生成伪随机数进行base64换表操作,设置随机数种子为0x1919810

TLS2-生成key

直接patch跳转地址绕过exit分支

TLS2生成rc4和xtea的key

s

TLS3

TLS3只执行了反调试,检测到调试器时会强制终止进程

设置ZwSetInformationThread的第二个参数为1即可

main函数

idapython修复或者动调看main函数均可

idapython脚本

import ida_bytes
addr=0x401890
for i in range(170):
	data=ida_bytes.get_byte(addr)
	data^=0x66
	ida_bytes.patch_byte(addr,data)
	addr+=1

解密后,逻辑清晰,输入flag后对flag进行base64+rc4+xtea加密

checkflag

base64

rc4

xtea

getflag

密文静态直接复制,base64表和key需要动调或者脚本生成

密文

unsigned char encflag[64]={  0x59,0x1b,0xfd,0xb4,0x6b,0xb8,0xbe,0xd9,0xb3,0xd3,0x77,0xd6,0xf0,0x65,0x5f,0x18,0xa0,0x9d,0x3a,0x53,0x6d,0x4a,0x7b,0x26,0x74,0x3a,0x9c,0x4e,0x20,0x43,0x19,0xd8,0x72,0xed,0x95,0xb5,0x9c,0x5,0x22,0x56,0xcb,0x7a,0x11,0x91,0x9f,0x7a,0xbc,0xc,0x4a,0x69,0x6d,0xce,0x3d,0xb4,0xab,0x29,0x61,0xfa,0x62,0x32,0xb4,0xec,0x4c,0xb6}

base64表

unsigned char base64_table[64]="4yZRiNP8LoK/GSA5ElWkUjXtJCz7bMYcuFfpm6+hV0rxeHIdwv32QOTnqg1BDsa9"

rc4和xtea的key

unsigned char[16] rc4_key={0x76,0x89,0x33,0x49,0x19,0x13,0xc3,0xc7,0xad,0xd8,0xe4,0x68,0xfc,0x48,0x4,0xbc};
unsigned char[16] xtea_key={0xdd,0x5b,0xaa,0xc,0x24,0x69,0x84,0xd6,0xb8,0x1e,0x4,0x51,0x6,0xab,0x2a,0x8b}

一键getflag脚本

#include<stdio.h>
#include<Windows.h>
#include<stdio.h>
#include<stdlib.h>
#include<stdint.h>
unsigned char sbox[256];
void swap(unsigned char* a, unsigned char* b)
{
	unsigned char tmp = *a;
	*a = *b;
	*b = tmp;
}
void init_sbox(unsigned char key[], int keyLen) {
	for (int i = 0; i < 256; i++)//赋值
		sbox[i] = i;
	unsigned char Ttable[256] = { 0 };
	for (int i = 0; i < 256; i++)
		Ttable[i] = key[i % keyLen];//根据初始化t表
	for (int j = 0, i = 0; i < 256; i++)
	{
		j = (j + sbox[i] + Ttable[i]) % 256;	//打乱s盒
		swap(&sbox[i], &sbox[j]);
	}
}
void RC4_enc_dec(unsigned char data[], int dataLen, unsigned char key[], int keyLen) {
	int k = 0, i = 0, j = 0, t = 0;
	init_sbox(key, keyLen);
	for (int h = 0; h < dataLen; h++)
	{
		i = (i + 1) % 256;
		j = (j + sbox[i]) % 256;
		swap(&sbox[i], &sbox[j]);
		t = (sbox[i] + sbox[j]) % 256;
		k = sbox[t];	//求密钥流,并对明文加密
		data[h] ^= k;
	}
}
//查找下标所在位置
unsigned char findPos(const unsigned char* base64_map, unsigned char chr)
{
	for (int i = 0; i < strlen((const char*)base64_map); i++)
	{
		if (base64_map[i] == chr)
			return i;
	}
}
unsigned char* base64_decode(unsigned char* base64_table,const unsigned char* buf, unsigned int buflen)
{
	
	unsigned int len, mod = 0;
	unsigned char* res;
	if (buf[buflen - 1] == '=')
	{
		if (buf[buflen - 2] == '=')
		{
			mod = 1;
			len = buflen / 4 * 3 - 2;
		}
		else
		{
			mod = 2;
			len = buflen / 4 * 3 - 1;
		}
	}
	else
		len = buflen / 4 * 3;
	res = (unsigned char*)malloc(sizeof(unsigned char) * len);
	unsigned char tmp[4] = { 0 };

	for (int i = 0, j = 0; j < len - mod; j += 3, i += 4)
	{
		tmp[0] = findPos(base64_table, buf[i]);		//code[]每一个字符对应base64表中的位置,用位置值反推原始数据值
		tmp[1] = findPos(base64_table, buf[i + 1]);
		tmp[2] = findPos(base64_table, buf[i + 2]);
		tmp[3] = findPos(base64_table, buf[i + 3]);
		res[j] = tmp[0] << 2 | tmp[1] >> 4;		//取出第一个字符对应base64表的十进制数的前6位与第二个字符对应base64表的十进制数的后2位进行组合  
		res[j + 1] = tmp[1] << 4 | tmp[2] >> 2;	//取出第二个字符对应base64表的十进制数的后4位与第三个字符对应bas464表的十进制数的后4位进行组合  
		res[j + 2] = tmp[2] << 6 | tmp[3];	   //取出第三个字符对应base64表的十进制数的后2位与第4个字符进行组合 
	}

	switch (mod)
	{
	case 0:break;
	case 1:
	{
		tmp[0] = findPos(base64_table, buf[buflen - 4]);
		tmp[1] = findPos(base64_table, buf[buflen - 3]);
		res[len - 1] = tmp[0] << 2 | tmp[1] >> 4;
		break;
	}
	case 2: {
		tmp[0] = findPos(base64_table, buf[buflen - 4]);
		tmp[1] = findPos(base64_table, buf[buflen - 3]);
		tmp[2] = findPos(base64_table, buf[buflen - 2]);
		res[len - 2] = tmp[0] << 2 | tmp[1] >> 4;
		res[len - 1] = tmp[1] << 4 | tmp[2] >> 2;
		break;
	}
	}
	return res;
}
void xtea_decipher(unsigned int num_rounds, uint32_t v[], uint32_t const key[4]) {
	for (int j = 0; j < 16; j += 2) {
		uint32_t v0 = v[j], v1 = v[j+1], delta = 0x9E3779B9, sum = delta * num_rounds;
		for (int i = 0; i < num_rounds; i++) {
			v1 -= (((v0 << 4) ^ (v0 >> 5)) + v0) ^ (sum + key[(sum >> 11) & 3]);
			sum -= delta;
			v0 -= (((v1 << 4) ^ (v1 >> 5)) + v1) ^ (sum + key[sum & 3]);
		}
		v[j] = v0; v[j+1] = v1;
	}
}
void changeBase64Table(unsigned char* base64_table) {
	int randPosA = 0, randPosB = 0;
	for (int i = 0; i < 100; i++) {
		randPosA = rand() % 64;
		randPosB = rand() % 64;
		swap(&base64_table[randPosA], &base64_table[randPosB]);
	}
}

void generateKey(unsigned char* rc4_key,unsigned char* xtea_key,int len) {
	for (int i = 0; i < len; i++) {
		rc4_key[i] = rand() % 0xff;
		xtea_key[i] = rand() % 0xff;
	}
}

int main() {
	//初始化
	unsigned char base64_table[64] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
	unsigned char encflag[64]={  0x59,0x1b,0xfd,0xb4,0x6b,0xb8,0xbe,0xd9,0xb3,0xd3,0x77,0xd6,0xf0,0x65,0x5f,0x18,0xa0,0x9d,0x3a,0x53,0x6d,0x4a,0x7b,0x26,0x74,0x3a,0x9c,0x4e,0x20,0x43,0x19,0xd8,0x72,0xed,0x95,0xb5,0x9c,0x5,0x22,0x56,0xcb,0x7a,0x11,0x91,0x9f,0x7a,0xbc,0xc,0x4a,0x69,0x6d,0xce,0x3d,0xb4,0xab,0x29,0x61,0xfa,0x62,0x32,0xb4,0xec,0x4c,0xb6};
	int encflag_len = 64,flag_len=64/4*3, xtea_rounds = 100,rc4_key_len=16;
	unsigned char rc4_key[16];
	unsigned int xtea_key[4] = { 0 };
	//伪随机数换表和生成key
	srand(0x114514);
	changeBase64Table(base64_table);//换表
	srand(0x1919810);
	generateKey(rc4_key, (unsigned char*)xtea_key, 16);//key均为16字节
	//解密
	xtea_decipher(xtea_rounds, encflag, xtea_key);
	RC4_enc_dec(encflag, encflag_len, rc4_key, rc4_key_len);
	unsigned char* decflag=base64_decode(base64_table,encflag, encflag_len);
	printf("flag is:\n");
	for (int i = 0; i < flag_len; i++) {
		printf("%c", decflag[i]);
	}
	printf("\n");
	system("pause");
	return 0;
}

snake

[题目制作过程]:

通过设计贪吃蛇小游戏 将flag设计在当分数达到一定上限的时候(设置的分数非常大,常规方法很难实现)会出现,通过RC4加密的方法将flag存储,需要使用python的解包和反编译工具,RC4在最后的时候异或了一个常数,解包的时候

[题目writeup]:

python打包的exe首先解包,首先你得需要pyinstxtractor.py文件,上github直接下了

image-20240626223749783

然后在解包出来的文件夹中找到对应exe名字的snack文件uncompyle6 snack.pyc > snack.py,打开反编译之后的snack.py文件,你就会发现有问题

image-20240624165847320

010查看snack.pyc之后发现Magic头被擦除,这边需要去解包出来后的base_library压缩包中找,从base_library中随便找一个pyc用010打开,把魔术头拼到snack中去就行

image-20240624170322224

然后找个在线网站反编译一下或者直接decompyle3 snake.pyc也行,源码

# Visit https://www.lddgo.net/string/pyc-compile-decompile for more information
# Version : Python 3.8

import pygame
import random
import key

def initialize(key):
    key_length = len(key)
    S = list(range(256))
    j = 0
    for i in range(256):
        j = (j + S[i] + key[i % key_length]) % 256
        S[i] = S[j]
        S[j] = S[i]
    return S


def generate_key_stream(S, length):
    i = 0
    j = 0
    key_stream = []
    for _ in range(length):
        i = (i + 1) % 256
        j = (j + S[i]) % 256
        S[i] = S[j]
        S[j] = S[i]
        key_stream.append(S[(S[i] + S[j]) % 256])
    return key_stream


def decrypt(data, key):
    S = initialize(key)
    key_stream = generate_key_stream(S, len(data))
    decrypted_data = None((lambda .0 = None: [ i ^ data[i] ^ key_stream[i] for i in .0 ])(range(len(data))))
    return decrypted_data

pygame.init()
WINDOW_WIDTH = 800
WINDOW_HEIGHT = 600
SNAKE_SIZE = 20
SNAKE_SPEED = 20
WHITE = (255, 255, 255)
BLACK = (0, 0, 0)
RED = (255, 0, 0)
window = pygame.display.set_mode((WINDOW_WIDTH, WINDOW_HEIGHT))
pygame.display.set_caption('贪吃蛇')
font = pygame.font.Font(None, 36)
snake = [
    (200, 200),
    (210, 200),
    (220, 200)]
snake_direction = (SNAKE_SPEED, 0)
food = ((random.randint(0, WINDOW_WIDTH - SNAKE_SIZE) // SNAKE_SIZE) * SNAKE_SIZE, (random.randint(0, WINDOW_HEIGHT - SNAKE_SIZE) // SNAKE_SIZE) * SNAKE_SIZE)
key_bytes = bytes((lambda .0: [ ord(char) for char in .0 ])(key.xor_key))
data = [
    101,
    97,
    39,
    125,
    218,
    172,
    205,
    3,
    235,
    195,
    72,
    125,
    89,
    130,
    103,
    213,
    120,
    227,
    193,
    67,
    174,
    71,
    162,
    248,
    244,
    12,
    238,
    92,
    160,
    203,
    185,
    155]
decrypted_data = decrypt(bytes(data), key_bytes)
running = True
if running:
    window.fill(BLACK)
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            running = False
        elif event.type == pygame.KEYDOWN or event.key == pygame.K_UP:
            snake_direction = (0, -SNAKE_SPEED)
        elif event.key == pygame.K_DOWN:
            snake_direction = (0, SNAKE_SPEED)
        elif event.key == pygame.K_LEFT:
            snake_direction = (-SNAKE_SPEED, 0)
        elif event.key == pygame.K_RIGHT:
            snake_direction = (SNAKE_SPEED, 0)
            continue
            snake_head = (snake[0][0] + snake_direction[0], snake[0][1] + snake_direction[1])
            snake.insert(0, snake_head)
            snake.pop()
            if snake[0] == food:
                food = ((random.randint(0, WINDOW_WIDTH - SNAKE_SIZE) // SNAKE_SIZE) * SNAKE_SIZE, (random.randint(0, WINDOW_HEIGHT - SNAKE_SIZE) // SNAKE_SIZE) * SNAKE_SIZE)
                snake.append(snake[-1])
    if snake[0][0] < 0 and snake[0][0] >= WINDOW_WIDTH and snake[0][1] < 0 and snake[0][1] >= WINDOW_HEIGHT or snake[0] in snake[1:]:
        running = False
    for segment in snake:
        pygame.draw.rect(window, WHITE, (segment[0], segment[1], SNAKE_SIZE, SNAKE_SIZE))
    pygame.draw.rect(window, RED, (food[0], food[1], SNAKE_SIZE, SNAKE_SIZE))
    score_text = font.render(f'''Score: {len(snake)}''', True, WHITE)
    speed_text = font.render(f'''Speed: {SNAKE_SPEED}''', True, WHITE)
    window.blit(score_text, (10, 10))
    window.blit(speed_text, (10, 40))
    score = len(snake)
    if score >= 9999:
        flag_text = font.render('Flag: ' + decrypted_data.decode(), True, WHITE)
        window.blit(flag_text, (10, 70))
    pygame.display.update()
    pygame.time.Clock().tick(10)
    continue
pygame.quit()

key从下图exe解包之后的PYZ文件夹中找

image-20240624171412051

找个在线的

image-20240626224213833

这里是魔改了RC4 魔改逻辑就是多加了一个循环下标 i 的异或

import pygame
import random

key='V3rY_v3Ry_Ez'

def initialize(key):
    key_length = len(key)
    S = list(range(256))
    j = 0
    for i in range(256):
        j = (j + S[i] + key[i % key_length]) % 256
        S[i], S[j] = S[j], S[i]
    return S

def generate_key_stream(S, length):
    i = 0
    j = 0
    key_stream = []
    for _ in range(length):
        i = (i + 1) % 256
        j = (j + S[i]) % 256
        S[i], S[j] = S[j], S[i]
        key_stream.append(S[(S[i] + S[j]) % 256])
    return key_stream

def decrypt(data, key):
    S = initialize(key)
    key_stream = generate_key_stream(S, len(data))
    decrypted_data = bytes([i^data[i] ^ key_stream[i] for i in range(len(data))])
    return decrypted_data

pygame.init()




key_bytes = bytes([ord(char) for char in key])

data = [0x65,0x61,0x27,0x7D,0xDA,0xAC,
        0xCD,0x03,0xEB,0xC3,0x48,0x7D,
        0x59,0x82,0x67,0xD5,0x78,0xE3,
        0xC1,0x43,0xAE,0x47,0xA2,0xF8,
        0xF4,0x0C,0xEE,0x5C,0xA0,0xCB,
        0xB9,0x9B]

print(decrypt(bytes(data), key_bytes))


posted @ 2024-07-07 21:58  BediveRe_RE  阅读(50)  评论(0编辑  收藏  举报