[CNSS Recruit 2021] Reverse部分赛题复现

成电新生赛,这个我应该复现得动吧。。

0x07 meta_game

首先exeinfo查一下,发现是32位程序

运行程序, 提示you can see the password by debuger.,并且第一步得猜一个数字
ida打开,动态调试,在第十三行设置断点,可以在调试的时候看到十二行显示了v2的值,直接输入即可
然后来到下一个函数,发现有语句 if (i == 2000) break,一直需要循环2000次才可进入下一步
打开汇编代码,发现

此处的7D0h即十进制的2000,则此句即为判断i是否为2000,猜测下一句代码的jz为跳到下一个函数,那么直接将jz修改为jnz,运行后发现该函数通过
来到下一步,发现勇士直接GG,查看伪代码

考虑第一个语句rand() % 100 == 1为真才可进入后续,直接查看汇编代码

直接把jnz改为jz即可
再查看后续代码,发现必须让if(IsDebuggerPresent())不能为真
继续查看汇编,再把最后一个jz改成jnz
最后动态调试中运行

得到flag:
cnss{0f3b82c6c7f1808e3e464ebe338c71e0}

0x08 encoder

扔进ida,点开前两个加密函数,看到有数组叫aAbcdefghijklmn


ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/显然是base64的索引表,但是这里对这个数组进行了操作,那就是base64换表了
首先要找到他把索引表换成啥了

char v2[65];
// unsigned char v2[65]
char aAbcdefghijklmn[65] = {"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"};
#define HIDWORD(x)	(*((unsigned int*)&(x)+1))
#define _BYTE unsigned char
char aJlg5q7iz0ni9it[65] = {"jLG/5Q7iZ0NI9ituYKtwZLN0Z8UNxwiIWLit"};
inline void sub_401690(char *a1, char *a2)
{
	char *result; // eax
	char v3; // [esp+Fh] [ebp-1h]

	v3 = *a1;
	*a1 = *a2;
	result = a2;
	*a2 = v3;
}
inline void sub_4016B6()
{
	char *result; // eax
	LL v1_1; // rax
	int v4; // [esp+5Ch] [ebp-Ch]

	result = (char *)memset(v2, 0, sizeof(v2));
	for (rg int i = 0; i <= 63; ++i)
	{
		v1_1 = dword_406420[i + 1];
		v4 = (((HIDWORD(v1_1) >> 26) + (_BYTE)v1_1) & 0x3F) - (HIDWORD(v1_1) >> 26);
		if ( v2[v4] )
		{
			while ( v2[v4] )
				v4 = (v4 + 1) % 64;
		}
		sub_401690(&aAbcdefghijklmn[i], &aAbcdefghijklmn[v4]);
		result = &v2[v4];
		v2[v4] = 1;
	}
}
inline _BYTE sub_401898(char *flag) // sub_401898(flag) = aJlg5q7iz0ni9it
{
	signed int v3; // [esp+20h] [ebp-18h]
	int v4; // [esp+24h] [ebp-14h]
	int v5; // [esp+28h] [ebp-10h]
	int v6; // [esp+2Ch] [ebp-Ch]

//	v3 = strlen(flag);
//	if (v3 % 3) v6 = 4 * (v3 / 3 + 1);
//	else v6 = 4 * (v3 / 3);
	v3 = 27, v6 = 36;
	v2[v6] = 0;
	v5 = 0;
	v4 = 0;
	while ( v5 < v6 - 2 )
	{
		v2[v5] = aAbcdefghijklmn[(unsigned __int8)flag[v4] >> 2];
		v2[v5 + 1] = aAbcdefghijklmn[(16 * flag[v4]) & 0x30 | ((unsigned __int8)flag[v4 + 1] >> 4)];
		v2[v5 + 2] = aAbcdefghijklmn[(4 * flag[v4 + 1]) & 0x3C | ((unsigned __int8)flag[v4 + 2] >> 6)];
		v2[v5 + 3] = aAbcdefghijklmn[flag[v4 + 2] & 0x3F];
		v4 += 3;
		v5 += 4;
	}
	if ( v3 % 3 == 1 )
	{
		v2[v5 - 2] = 61;
		v2[v5 - 1] = 61;
	}
	else if ( v3 % 3 == 2 )
	{
		v2[v5 - 1] = 61;
	}
}
char flag[233];
inline void decod()
{
	#define dec aJlg5q7iz0ni9it

}
int main()
{
	sub_4015C0();
	sub_4016B6(); // adjust aAbcdefghijklmn
	decod();
	__endl, cout << aAbcdefghijklmn;
	// qAFHSNKTBUglYrbVc9MXWiaZjm345xkROp6Ewhouz2JDv7dfyCI/08LQPG+nste1
	return 0;
}

(又臭又长,懒得删注释什么的了)
关键在于换表的base64不能简单的用原始的解密脚本替换索引表实现解密,这里贴一份好用的base64换表解密脚本

"""
Customized base64 algorithm
You can set you own indexing string using the config() method.
Usage:
    b = CusBase64()
    b.encode('binary\x00string')  # Output: YmluYXJ5AHN0cmluZw==
    b.decode('YmluYXJ5AHN0cmluZw==') # Output: binary\x00string
    b.config('aABCDEFGHIJKLMNOPQRSTUVWXYZbcdefghijklmnopqrstuvwxyz0123456789+/')
    b.decode('c2UsYi1kYWM0cnUjdFlvbiAjb21wbFU0YP==') # Output: self-destruction complete
"""
class CusBase64(object):
    DEFAULT = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"

    def __init__(self):
        self.idx_str = CusBase64.DEFAULT

    def encode(self, str):
        """
        Encode string using the customized indexing string.
        - args:
            str: String to be encoded
        """
        # Get the binary string
        binary = ''.join([format(ord(c),'0>8b') for c in str])
        # Add additional zero
        binary = self.padding(binary)
        # Get the index in indexing string
        idxs = [int(binary[6*i:6*i+6], 2) for i in range(len(binary)//6)]

        result = ''.join([self.idx_str[i] for i in idxs])
        # add '='
        if len(str)%3 != 0:
            result = result + (3-len(str)%3)*'='

        print("%r" % result)


    def decode(self, str):
        """
        Decode string using the customized indexing string.
        - args:
            str: String to be decoded
        """
        if len(str) == 0:
            return

        # remove '='
        while str[-1]=='=':
            str = str[:-1]
        try:
            # Get the binary string
            binary = ''.join([format(self.idx_str.index(c), '0>6b') for c in str])

            # Remove additional zero
            binary = self.remove(binary)

            result = ''.join([chr(int(binary[8*i:8*i+8], 2)) for i in range(len(binary)//8)])
        except ValueError:
            result = "Please check again!"

        print("%r" % result)

    def remove(self, binary):
        """
        Remove additional zero while decoding string.
        - args:
            binary: Binary format of the index.
        - returns:
            Binary string without additional zero.
        """
        if len(binary)%8 == 0:
            return binary
        else:
            return binary[:-(len(binary)%8)]

    def padding(self, binary):
        """
        Add additional zero while encoding string.
        - args:
            binary: Binary format of the string.
        - returns:
            Binary string with additional zero.
        """
        if len(binary)%6 == 0:
            return binary
        n = 6 - len(binary)%6
        binary = binary + n * '0'
        return binary


    def config(self, str):
        """
        Set customized indexing string.
        """
        self.idx_str = str
        print("New indexing string is %r" % self.idx_str)

得到flag:
cnss{U_ArE_g0od_at_REvErSe}

0x09 CSS大师第一步

首先观察主体代码,发现用flag更改了coord内元素对应的值,最后让x对应的值为1就算胜利

然后看root里面的代码,发现他有一些元素初值为1,有一些初值为0,还有一些是由其他的元素计算而出。
这里计算近包括全为乘法或者全为加法,那么假设这个数是1,如果他由加法而来,那么加法中有且仅有一个元素为1;如果他由乘法而来,那么乘法中所有元素均应为1。
找到关于x的计算,

考虑从这里倒着走,输出“一些数得改成有且仅有一个为1”以及“一些数中得改成至少有一个为1”,然后手算拿到flag

#include <algorithm>
#include <iostream>
#include <cstring>
#include <cstdio>
#include <cmath>
#include <queue>
#include <stack>
#include <set>
#include <map>
using namespace std;
#define rg register
#define LL long long
#define __space putchar(' ')
#define __endl putchar ('\n')
template <typename qwq> inline void read(qwq &x)
{
	x = 0;
	rg int f = 1;
	rg char c = getchar();
	while (c < '0' || c > '9')
	{
		if (c == '-') f = -1;
		c = getchar();
	}
	while (c >= '0' && c <= '9')
	{
		x = (x << 1) + (x << 3) + (c ^ 48);
		c = getchar();
	}
	x *= f;
}
template <typename qaq> inline void print(qaq x)
{
	if (x < 0)
	{
		putchar('-');
		x = -x;
	}
	if (x > 9) print(x / 10);
	putchar(x % 10 + '0');
}
string list[171] = {"--2eead", "--68b16", "--bd92b", "--8eb64", "--fec8a", "--f6579", "--12447", "--65353", "--1bbf5", "--42d17", "--86526", "--00c17", "--d961b", "--c7e32", "--bcdac", "--3bf8d", "--7ce1b", "--4d92b", "--eef0f", "--b488f", "--33fa7", "--2bbe3", "--5bb71", "--3384e", "--5e2ce", "--bdbac", "--d75a1", "--0624d", "--1c582", "--2b916", "--28633", "--d5cb1", "--81e26", "--d7225", "--1d2b3", "--4f67a", "--084e9", "--ae4af", "--e9d11", "--05d82", "--4daa1", "--54887", "--fe35a", "--12e13", "--a8471", "--ec4c9", "--c9228", "--d4655", "--03f15", "--22cba", "--9c47e", "--68930", "--191cc", "--6d330", "--6b658", "--0e125", "--8bfeb", "--62771", "--c4575", "--beba0", "--bb2bb", "--c0c00", "--48322", "--8cbdf", "--71216", "--95a2a", "--6748b", "--5ed01", "--db963", "--8ed74", "--e0019", "--7f4b5", "--6b4a8", "--3bf7a", "--ad937", "--96afc", "--4c8b7", "--d660c", "--f5e87", "--63e08", "--afd50", "--b9e3e", "--e1d99", "--161ea", "--bc078", "--dfc3b", "--45812", "--75c62", "--0cd13", "--60d47", "--6b99d", "--acbb8", "--9f815", "--46fd1", "--ff63e", "--e8a89", "--98720", "--0fe50", "--4ad76", "--81a61", "--f3ea9", "--49059", "--14e69", "--f0ffe", "--7bb49", "--29580", "--99e12", "--ec297", "--00241", "--6c431", "--78082", "--3b5b5", "--6b426", "--2d350", "--90d05", "--70006", "--2fd1b", "--9823e", "--e68b7", "--d0d96", "--14b57", "--ebd5f", "--a4f2b", "--097e7", "--d4401", "--e9c7d", "--1e967", "--286e7", "--1290c", "--7f952", "--d6061", "--2607e", "--25631", "--df315", "--111ff", "--dd697", "--f6822", "--9b112", "--f1471", "--ff9fc", "--714de", "--b28be", "--33348", "--9ee6e", "--f81ec", "--a8c08", "--0eb94", "--7cf78", "--3366b", "--a43bf", "--7a377", "--28b62", "--69fe1", "--ee762", "--cb980", "--091c0", "--bad61", "--428f4", "--ddee7", "--642a2", "--b379c", "--b2a13", "--62a0f", "--b32a6", "--5bb37", "--04466", "--4d61b", "--91408", "--40a02", "--03300", "--8ada7"};
int cnt, idx;
map<string, int> mp_1;
struct dat
{
	int type, tot;
	string element[23];
}data[23333];
inline bool find(string now)
{
	if (data[mp_1[now]].type == 0)
	{
		cout << now << " ";
		return false;
	}
	if (data[mp_1[now]].type == 2); // +
	{
		rg bool flag = false;
		for (rg int i = 1; i <= data[mp_1[now]].tot; ++i)
		{
			if (data[mp_1[data[mp_1[now]].element[i]]].type == 0) continue;
			if (data[mp_1[data[mp_1[now]].element[i]]].type == 1 || find(data[mp_1[now]].element[i]))
			{
				flag = true;
				break;
			}
		}
	}
}
int main()
{
	freopen("read.txt", "r", stdin);
	while (1)
	{
		rg string now, nowls; 
		if (cnt == 1002) break;
		getline(cin, now);
		nowls = "";
		for (rg int i = 0; i < 7; ++i) nowls += now[i];
		mp_1[nowls] = ++idx; // hash: list -> index
		if (now[9] == '0') data[idx].type = 0;
		if (now[9] == '1') data[idx].type = 1;
		if (now[9] == 'c') // calc
		{
			rg int len = now.size();
			for (rg int i = 10; i < len; ++i)
			{
				if (now[i] == '-' && now[i + 1] == '-') // find a element
				{
					rg string tmp = "";
					for (rg int j = i; j <= i + 6; ++j) tmp += now[j];
					data[idx].element[++data[idx].tot] = tmp;
				}
				if (now[i] == '+') data[idx].type = 2;
				if (now[i] == '*') data[idx].type = 3;
			}
		}
		++cnt;
	}
	// calc(var(--dd1bd)*var(--28e99)*var(--5588f));
	find("--dd1bd"), __endl;
	find("--28e99"), __endl;
	find("--5588f"), __endl;
	return 0;
}

CNSS{8718346957215865968}

0x0? 生瓜蛋子

扔进ida

支持正义花指令,刚好不是很会,就写详细点吧

阅读汇编,一个一个call调用函数跳过去看,发现loc_401619有点东西

把数据块按c转化为代码

这里大概是比较和最后判断正误的区域,发现loc_401725还没看过,跳转一下

经过尝试后(汇编水平不是很够..)发现loc_401725中jz/jnz和loc_401734的jz是花指令,应该是这些语句尝试跳转到一个无法识别的地址,导致反汇编无法继续进行
把他们nop掉,然后再按几下c把剩下的机器码搞成

按P然后f5终于搞定

sub都是很简单的运算函数,Str2可以猜测是我们输入的数据
v3 v4 v5由i得出,但v8是由Str的数据操作而得到的
但由模运算发现v8是0~255的数据,那么考虑直接枚举每个v8对应的Str2

int __cdecl sub_4018E6(int a1)
{
	if ( a1 )
		return a1 * sub_4018E6(a1 - 1);
	else
		return 2;
}
int __cdecl sub_40190D(int a1);
int __cdecl sub_40193A(int a1)
{
	if ( a1 )
		return sub_40190D(a1 - 1) + 3;
	else
		return 3;
}
int __cdecl sub_40190D(int a1)
{
	if ( a1 > 0 )
		return sub_40193A(a1 - 1) + a1 + 1;
	else
		return 1;
}
char str2[233];
int main()
{
	freopen("flag.out", "w", stdout);
	int v1; // ebx
	int v2; // ebx
	int v3; // ebx
	int v4; // ebx
	int v5; // eax
	char Str1[33]; // [esp+17h] [ebp-31h] BYREF
	int v8; // [esp+38h] [ebp-10h]
	int i; // [esp+3Ch] [ebp-Ch]

	Str1[0] = -40;
	Str1[1] = -34;
	Str1[2] = -23;
	Str1[3] = 87;
	Str1[4] = 26;
	Str1[5] = 43;
	Str1[6] = 99;
	Str1[7] = -81;
	Str1[8] = -83;
	Str1[9] = -53;
	Str1[10] = -20;
	Str1[11] = 108;
	Str1[12] = -22;
	Str1[13] = 58;
	Str1[14] = 72;
	Str1[15] = 59;
	Str1[16] = 42;
	Str1[17] = -104;
	Str1[18] = 122;
	Str1[19] = -95;
	Str1[20] = 43;
	Str1[21] = -122;
	Str1[22] = -125;
	Str1[23] = 85;
	Str1[24] = -118;
	Str1[25] = -34;
	Str1[26] = 44;
	Str1[27] = 60;
	Str1[28] = 37;
	Str1[29] = -111;
	Str1[30] = -65;
	Str1[31] = -13;
	Str1[32] = 0;
	
//	v1 = sub_4018E6(Str2[6]);
//	v2 = sub_40190D(Str2[18]) + v1;
//	v8 = (v2 + sub_40193A(Str2[26])) % 255;
	for ( i = 0; i <= 31; ++i )
	{
		v3 = sub_4018E6(i);
		v4 = sub_40190D(i) * v3;
		v5 = sub_40193A(i);
		Str1[i] ^= ((v4 + v5) % 0xFFFF);
	}
	for (v8 = 0; v8 <= 255; ++v8)
	{
		for (i = 0; i <= 31; ++i) putchar(Str1[i] ^ v8);
		__endl;
	}
}

直接搜索cnss可以得到答案:
cnss{We1Come_To_ReVErze_w0rld!!}

0x0A Baby Exception

摸进加密函数,一眼AES

现在就考虑一下调试拿到data和key就成

ida里面有一堆int 3 和mov eax 0CCCCCh,全nop掉就搞定int 3了
(后面发现Ollydbg好像可以一键绕过所有int 3...)

动调后拿到data = \x96\x7f\x37\x7c\x26\x30\x03\xeb\x61\x6d\xa3\xda\x0c\x77\x3e\x7c\xdf\x18\x5d\x4e\xd9\xbe\x0a\x5c\x02\x36\x87\x37\xb4\x2f\xb1\x9f
key = \x4D\x4C\x57\x4E\x46\x7C\x19\x0A\x4D\x4C\x57\x4E\x46\x7C\x7B\x67

网上扒一个exp跑一跑

from Crypto.Cipher import AES
from binascii import b2a_hex, a2b_hex

def add_to_16(text):
    if len(text.encode('utf-8')) % 16:
        add = 16 - (len(text.encode('utf-8')) % 16)
    else:
        add = 0
    text = text + ('\0' * add)
    return text.encode('utf-8')

def encrypt(text):
    key = '\x4D\x4C\x57\x4E\x46\x7C\x19\x0A\x4D\x4C\x57\x4E\x46\x7C\x7B\x67'.encode('utf-8')
    mode = AES.MODE_ECB
    text = add_to_16(text)
    cryptos = AES.new(key, mode)
    cipher_text = cryptos.encrypt(text)
    return cipher_text

def decrypt(text):
    key = '\x4D\x4C\x57\x4E\x46\x7C\x19\x0A\x4D\x4C\x57\x4E\x46\x7C\x7B\x67'.encode('utf-8')
    mode = AES.MODE_ECB
    cryptor = AES.new(key, mode)
    plain_text = cryptor.decrypt(a2b_hex(text))
    return bytes.decode(plain_text).rstrip('\0')

data = b'\x96\x7f\x37\x7c\x26\x30\x03\xeb\x61\x6d\xa3\xda\x0c\x77\x3e\x7c\xdf\x18\x5d\x4e\xd9\xbe\x0a\x5c\x02\x36\x87\x37\xb4\x2f\xb1\x9f'
data = b2a_hex(data)
ans = decrypt(data)
print(ans)

(如果你安Crypto库后还是没法跑这个脚本,建议你在site-packages里面把crypto文件夹改成Crypto)

得到flag:
cnss{s3h_v3h_b0th_ez_r1ght?__:)}

[Boss] Trivual Game

tbd..

posted @ 2021-11-11 14:15  iPlayForSG  阅读(209)  评论(0编辑  收藏  举报