很抱歉本次比赛给大家带来了不好的体验,本次比赛的本意是让大家学到更多的知识,所以大家就当做是学习资源,去复现这些题吧(不懂的地方可以私聊我)

签到

抱歉抱歉,给各位磕了,第一版附件难了,看第二版就行了!
(其实就是加了一点点知识)

旧附件

img

无壳,32位

img

定位到关键的代码函数

1
2
for ( i = 0; i < strlen(Str); ++i )
Str[i] ^= (unsigned __int8)i ^ 2

这个str[i]^=i^2可以看成str[i]=str[i]^i^2

C语言基础的语法了属于是

然后密文在哪??额这波纯属是我套娃了,密文在一个定义的函数里面,函数嵌套了16层(闲得无聊弄得)

可以选择点击16次找到定义密文的函数,也可以shift+f12拿到密文

img

img

至于这个加密怎么逆呢,就……再异或一遍就行了

这就是异或的特征了,写脚本的时候这么写就行了,这里附上简单的python脚本:

1
2
3
4
5
6
7
8
9
10
flag = "SONT}Paii;eVZ?S\\#}eRBqKDVUMFGfXn_"
flag_list = list(flag)
new_flag_list = []

for i in range(len(flag_list)):
flag_list[i] = ord(flag_list[i]) ^ i ^ 2
new_flag_list.append(chr(flag_list[i]))

new_flag = ''.join(new_flag_list)
print(new_flag)

当然由于大家最近在学c,我也顺便附上c的代码。其实本质上这些代码都差不多

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#include <stdio.h>
#include<string.h>
int main() {
char flag[] = "SONT}Paii;eVZ?S\\#}eRBqKDVUMFGfXn_";
char new_flag[strlen(flag)+1]; // +1 for null terminator

for (int i = 0; i < strlen(flag); i++) {
flag[i] = flag[i] ^ i ^ 2;
new_flag[i] = flag[i];
}
new_flag[strlen(flag)] = '\0';

printf("%s\n", new_flag);
return 0;
}

当然,GPT的话,就直接秒了……ε=(´ο`*)))唉

flag:QLNU{Welc0m_T0_Q1nuCTf_QLNU_YyDs}
上面的修改附件之后的题解,以下是新附件的wp:

新附件

image-20231126170255077

改完附件之后题目简单了很多,之前是嵌套了很多层的函数嵌套然后就改成了一层,找到v5 = (const char *)&unk_41B000;点进去就是密文
image-20231126170918161

可以shitf+e提取字符串,提取输出,然后看后面的加密是一个异或2,所以直接上脚本就秒了!

可以选择赛博厨子
image-20231126171240502

至于为什么是2u,2是数据,u是代表这个2是十进制的2,所以用u来表示

原始人!!起洞!!!!

这个题是拿的newstar week2 的原题,个人觉得这个题是对于新手入门挺好的,所以就拿来用了,并且稍微改了改!希望出题人可以不要介意呜呜

安卓逆向题,安卓逆向的话,逆的是java代码,其实本质上和c差不多

反编译apk的工具很多,jeb,jadx,androidkiller都行,我个人喜欢jadx

先用模拟器打开一下看看吧!

img

原神的登录界面,各位可以去反编译找到登录的代码然后找到账号和密码

main函数一般是在com文件夹下,然后找找就找到了

img

代码看起来可能确实是吃力,但是,英文字母和简单的if语句应该能看的到

img

这里是判断,username是否等于genshinimpact

如果不是的话,就会有一个.show()的语句,意思大概是展示一个窗口,这里猜测就是账号错误的窗口提示吧

如果是的话才会继续走下面的函数,也就是所谓的加密密码的函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
if (!username.equals("genshinimpact")) {

Toast.makeText(MainActivity.this, (int) bin.mt.plus.TranslationData.R.string.wrong1, 0).show();

}

int[] encode_table = {125, 239, 101, 151, 77, 163, 163, 110, 58, 230, 186, 206, 84, 84, 189, 193, 30, 63, 104, 178, 130, 211, 164, 94, 75, 16, 32, 33, 193, 160, 120, 47, 30, 127, 157, 66, 163, 181, 177, 47, 0, 236, 106, 107, 144, 231, 162, 16, 36, 34, 91, 9, 188, 81, 5, 241, 235, 3, 54, 150, 40, 119, 202, 150};

String retval = whatwhatme.encodee(username, encode_table);

String result2 = whatme.encode(password.getBytes(), retval);

if (!result2.equals("VVwPUWsYcXEPW0MpN35gW0FwW1RxandgJTE8")) {

Toast.makeText(MainActivity.this, (int) bin.mt.plus.TranslationData.R.string.wrong2, 0).show();

return;

首先下面就出现了一个encode_table,并且后面有数据,可以大概猜测一下

1
int[] encode_table = {125, 239, 101, 151, 77, 163, 163, 110, 58, 230, 186, 206, 84, 84, 189, 193, 30, 63, 104, 178, 130, 211, 164, 94, 75, 16, 32, 33, 193, 160, 120, 47, 30, 127, 157, 66, 163, 181, 177, 47, 0, 236, 106, 107, 144, 231, 162, 16, 36, 34, 91, 9, 188, 81, 5, 241, 235, 3, 54, 150, 40, 119, 202, 150};

img

下面定义了一个retval的数值,然后调用了whatwahtme.encode()函数,并且导进去了两个参数,一个和是username,另一个就是刚才的encode_table,双击点进去看看whatwhatme是一个啥玩意!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
public class whatwhatme {
public static String encodee(String keyStr, int[] data) {
byte[] key = keyStr.getBytes();
int[] s = new int[256];
int[] k = new int[256];
int j = 0;
for (int i = 0; i < 256; i++) {
s[i] = i;
k[i] = key[i % key.length];
}
for (int i2 = 0; i2 < 256; i2++) {
j = (s[i2] + j + k[i2]) & 255;
int temp = s[i2];
s[i2] = s[j];
s[j] = temp;
}
StringBuilder result = new StringBuilder();
int j2 = 0;
int i3 = 0;
for (int i4 : data) {
i3 = (i3 + 1) & 255;
j2 = (s[i3] + j2) & 255;
int temp2 = s[i3];
s[i3] = s[j2];
s[j2] = temp2;
int rnd = s[(s[i3] + s[j2]) & 255];
result.append((char) (i4 ^ rnd));
}
return result.toString();
}
}

如果问gpt了的话或者做过类似的题话,可以看出来这里是一个rc4的加密,并且加密的秘钥是刚刚的用户名,然后加密的密文是刚刚的encode_yable

rc4加密虽然过程很复杂,就是什么什么盒子又是什么盒子,只要没有动算法,那就可以把他当成黑盒子,然后就可以当成异或就行了,他也是可逆的

网上随便搜个python的脚本就行然后加密得到了我们加密(解密)之后的数据:

BADCFEHGJILKNMPORQTSVUXWZYbadcfehgjilknmporqts.uxwzy1032547698/+

img

这玩意说实话一眼顶针,很明显了就是base64的码表了,盲猜后面的就是base换表加密了!

这里就贴上一个rc4的解密脚本:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
def decode(key_str, data):
key = key_str.encode()
s = list(range(256))
k = [key[i % len(key)] for i in range(256)]
j = 0
for i in range(256):
j = (s[i] + j + k[i]) & 255
s[i], s[j] = s[j], s[i]
result = bytearray()
j2 = 0
i3 = 0
for i in range(len(data)):
i3 = (i3 + 1) & 255
j2 = (s[i3] + j2) & 255
s[i3], s[j2] = s[j2], s[i3]
rnd = s[(s[i3] + s[j2]) & 255]
result.append(data[i] ^ rnd)
return result.decode()
key = "genshinimpact"
encoded_data = [125, 239, 101, 151, 77, 163, 163, 110, 58, 230, 186, 206, 84, 84, 189, 193, 30, 63, 104, 178, 130, 211, 164, 94, 75, 16, 32, 33, 193, 160, 120, 47, 30, 127, 157, 66, 163, 181, 177, 47, 0, 236, 106, 107, 144, 231, 162, 16, 36, 34, 91, 9, 188, 81, 5, 241, 235, 3, 54, 150, 40, 119, 202, 150]
decoded_data = decode(key, encoded_data)
print("re4解密之后的码表:", decoded_data)

虽然能猜到是base64,但是继续往下看吧:

img

这里定义了一个result2,并且走的 是whatme.encode()的加密方式

并且也是调用了两个参数,一个是刚刚输入的密码也就是password,并且后面有getBytes()方法,就是把密码变成比特形的数据传进去

跟进一下whatme.encode

img

这是标准的base64算法,跟刚才猜的一样,然后数据应该就是下面的那个if的判断了,直接拿那个密文和刚刚rc4得到的码表去赛博厨子也能直接秒了!

img

这边也顺便给上全部的脚本:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
import base64
import string

def decode(key_str, data):
key = key_str.encode()
s = list(range(256))
k = [key[i % len(key)] for i in range(256)]
j = 0
for i in range(256):
j = (s[i] + j + k[i]) & 255
s[i], s[j] = s[j], s[i]
result = bytearray()
j2 = 0
i3 = 0
for i in range(len(data)):
i3 = (i3 + 1) & 255
j2 = (s[i3] + j2) & 255
s[i3], s[j2] = s[j2], s[i3]
rnd = s[(s[i3] + s[j2]) & 255]
result.append(data[i] ^ rnd)
return result.decode()

key = "genshinimpact"
encoded_data = [125, 239, 101, 151, 77, 163, 163, 110, 58, 230, 186, 206, 84, 84, 189, 193, 30, 63, 104, 178, 130, 211, 164, 94, 75, 16, 32, 33, 193, 160, 120, 47, 30, 127, 157, 66, 163, 181, 177, 47, 0, 236, 106, 107, 144, 231, 162, 16, 36, 34, 91, 9, 188, 81, 5, 241, 235, 3, 54, 150, 40, 119, 202, 150]

decoded_data = decode(key, encoded_data)
print("re4解密之后的码表:", decoded_data)
string = "VVwPUWsYcXEPW0MpN35gW0FwW1RxandgJTE8"#base64密文
tableBase64 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"#base64原来的码表
tableNew = decoded_data
flag = base64.b64decode(string.translate(str.maketrans(tableNew, tableBase64)))
print("解密之后的flag:" ,end="")
print( flag )

'''
一些base64库的函数介绍:
maketrans():用于创建字符映射的转换表,对于接受两个参数的最简单的调用方式,第一个参数是字符串,表示需要转换的字符,第二个参数也是字符串表示转换的目标;
translate():法根据参数table给出的表(包含 256 个字符)转换字符串的字符, 要过滤掉的字符放到 del 参数中;
decode():以encoding指定的编码格式解码字符串。'''

flag:QLNU{YuaN_Sh3n!_Q1_D0ng!!!}

另外其实whatwhatme和whatme这俩函数上面也有提示是什么加密,细心的人捏应该已经看到了

彩蛋:拿到正确的flag输入为密码时,会真正的原神启动!!!!

澳门博彩模拟器

这个题可能是在你们的环境上有点问题,可能会报毒,而且好像部分python版本太高的电脑用uncompyle6反编译不了!然后pycdc可能搜到的东西也比较少,所以就爆0了
python打包的exe

查壳

img

pyinstaller,这个可不算是什么壳,这个是真的打包工具,是python写的py文件,然后使用pyinstaller打包出来的exe程序

具体的讲解可以学习一下雪月三十佬的博客:

exe文件转py文件 exe -> pyc -> py 详细步骤(例题)

他用的是uncompyle6,但是随着现在时代的进步,uncompyle6只能支持python 3.8.6之前的版本了,后面的版本就编译不了了(出的题时候用的3.10,编译了好久好久都寄)

现在推荐用pycdc,但是吧,pycdc有点难配置(指要用ide和cmake加载程序)大佬可以自行搜素配置

首先是exe -> py,这一步就有pyinstxtractor就行

python pyinstxtractor game.exe

img

然后就转到了pyc,之后可以用uncompyle6

img

pip install uncompyle6 配置环境

uncompyle6 game.pyc 得到源码,当然为了方便观看,就加上 ->来生成一个1.py

img

打开之后,发现汉字是乱码,并且用pycharm打开的话提示编码问题

img

那就将编码设置为GBK吧,然后就可以看到完整的源码,找到几个比较关键的地方

首先是一个enc的数值:

enc = ‘48514D5A4C5455364141416A466855544B67456D46523874487773644B5473505067347A436877304743674A4541494B6643556D4A7967704B6E6439’

然后往下看看哪里调用了这个enc的数据:

img

这里有一个购买密文的地方,调用了enc,然后传入了一个encodeeee.cccccccc()函数里面

但是在这个game里面并没有找到我们要的encodeeee.cccccccc()这个函数,说明是调用了外部的函数

再看一下文件头:

有一个import

img

Time 和 random是python自带的内置函数,但是那个encodeeee这个名字,一看就知道是我们自己定义的,所以可以找找有没有encodeeee.pyc这个文件,并且给他编译回py看代码

有的有的!编译一下

img

反编译后打开就看到了这个cccccccc函数,参数应该就是那个enc

img

这个函数是干啥的嘞,23级数电应该学到了BCD8421码吧,我就不多解释了,这里就是一个BCD码转化成字符串的代码,当然也可以拿赛博厨子嗦:

img

得到了被加密之后的密文,这里再给一个python的代码实现相应的功能 (不用会写,会用就行)

1
2
3
4
5
6
7
8
9
def bcd_to_string(bcd_string):
output_string = ""
for i in range(0, len(bcd_string), 2):
bcd_code = int(bcd_string[i:i+2], 16) # 将2位的十六进制字符串转换为整数
output_string += chr(bcd_code)
return output_string
enc = "48514D5A4C5455364141416A466855544B67456D46523874487773644B5473505067347A436877304743674A4541494B6643556D4A7967704B6E6439"
secret = bcd_to_string(enc)#整体的bcd_to_string()函数就是在把题目中的enc(8421BCD码)转化成字符串的形式,得到我们的密文
print("我们得到的密文是:"+secret)

img

然后回到game.py继续看

我记得是玩游戏的时候是可以选择验证flag还是玩游戏获得密文来着,到现在我们就完成了获得密文的工作

验证flag的话,看一下

img

还是调用了encodeeee里面的check_flag()的方法

img

浅看一下:

大体的流程就是,首先把enc经过bec转字符串函数,得到我们的密文,刚刚也得到了:HQMZLTU6AAAjFhUTKgEmFR8tHwsdKTsPPg4zChw0GCgJEAIKfCUmJygpKnd9

然后是一个异或:

1
2
for i in range(len(inputs) - 1):
inputs[i] = inputs[i] ^ inputs[i + 1] ^ i

这个呢就是一个自身异或之后再和i异或,逆的话要从后往前进行,并且异或一下i就行

再然后就是一个base64了

逆向的过程就是先解base64–>然后异或–>拿到flag

脚本奉上:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import base64
def bcd_to_string(bcd_string):
output_string = ""
for i in range(0, len(bcd_string), 2):
bcd_code = int(bcd_string[i:i+2], 16) # 将2位的十六进制字符串转换为整数
output_string += chr(bcd_code)
return output_string


enc = "48514D5A4C5455364141416A466855544B67456D46523874487773644B5473505067347A436877304743674A4541494B6643556D4A7967704B6E6439"
secret = bcd_to_string(enc)#整体的bcd_to_string()函数就是在把题目中的enc(8421BCD码)转化成字符串的形式,得到我们的密文
print("我们得到的密文是:"+secret)

secret = secret.encode('utf-8')
secret = bytearray(base64.b64decode(secret))#调用内部的base64的解密函数进行解密
# 将字节数据转换为字符串
secret = secret.decode('utf-8')#两个.decode('utf-8')只是修改文件的定义格式,方便后面的操作

for i in range(len(secret) - 2, -1, -1):#从倒数第二位开始进行逆向的异或,疑惑到-1,并且每次都i-1(往前走)
secret = secret[:i] + chr(ord(secret[i]) ^ i ^ ord(secret[i + 1])) + secret[i + 1:]#疑惑加密,只不过是三个一起,问题不大

print("解密之后的flag:"+secret)

flag:QLNU{Just_@_GamE_PlaypLay_HappY_hApPy!!!!!!!}

base64??快秒!!

本来题更难,在原有的基础上,把逻辑弄得稍微复杂了一点,然后就改回了”简略版”

img

然后下面是校赛版本:

img

本次可以拿这个反编译的代码和正常的base64加密后的代码对比一下,其实仔细观察就发现是对于base64的加密源码进行了一些”稍微”的改动

img

这是常规的base64编码,在后面有前三个位移是,2,4,6并且没有异或符号,但是反观本题的base64,它不仅仅是0,6,4,2的位移,而且还在后面加了一个异或符号

虽然看上去是修改了base64的加密流程,其实base64还是base64,只是重新分配了顺序

所以逆向的大体思路可以是先四个为一组倒过来排列,然后再异或各自的数字就可以了

解密脚本如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import base64

secret = "G~QW]r\TpmCQK?5WNH\[nTnLnHROqPif}?Rgn3CS~Phg`T7ZG?ROn3CS1PSO"
enc = []
for i in range(0, len(secret), 4):
for j in range(3, -1, -1):
enc.append(secret[i+j])

key = [2, 4, 6, 8]
data = [ord(char) for char in enc]
flag = []
for i in range(len(data)):
data[i] = data[i] ^ key[i % 4]
flag.append(chr(data[i]))

decoded_flag = base64.b64decode("".join(flag)).decode()
print(decoded_flag)