前言:

希望大家多多复现,把这些题目都学会,不要辜负了学长的一片苦心
出题和写wp真的很费时间…….

大家加油!

C++是最棒的语言

考点:c++,异或

签到题,考点就是一个简单的签到,但是用c++写的,看起来有点头疼,其实也没什么啦,因为就一个异或9

img

至于其他的部分都是一些初始化和定义的部分,不需要看,也没有内置的函数,

可以直接拿赛博厨子秒了

img

当然自己随便整一个python脚本也是可以的!

exp

1
2
3
4
5
6
7
string = "XEG\\rJ\"\"V8zV}a:Vk:z}Vehgn|hnlV8gV]alV^9{8mt"
result = ""

for char in string:
result += chr(ord(char) ^ 9)

print(result)

flag:QLNU{C++_1s_th3_b3st_language_1n_The_W0r1d}

Debug_me

考点:远程动态调试,rc4

本题是两个做法,动态调试法和直接逆向法

出题的时候就是想检测一下各位动调能力,所以只要成功动调起来了就送flag,这也是我的本意,至于rc4解法我还是卖了一个小坑的

动态调试法

断点就在最后小就行了,哪里都无所谓(前提是程序在调试的情况下会经过那个地方)

img

网上搜索一下IDA远程动调的步骤,远程开启linux_server64,本地选择remote linux debugger,选择然后直接启动就好了,这个程序在启动的时候会进行判断,判断当前是否是调试模式,调试模式的话就会直接给你flag,正常运行的话就是提示输入flag,直接秒啦!

img

动调具体细节看https://jiangwenwen066.github.io/2023/02/06/%E5%88%9D%E6%AC%A1%E5%8A%A8%E6%80%81%E8%B0%83%E8%AF%95/ 或者看四节草的一个视频

分析函数解决rc4法

确实是一个rc4,但是大多数人可能不会直接得到结果,因为我对秘钥进行了一点小小的操作……(黑心出题人

img

定位到了加密函数,点进去看看,是定义了一个v2,一般来说这个v2就是key了,而且是一句我想给大家的话:调试是一个解决问题很好的方式!然后下面就是rc4_crypt函数了,参数分别是a1和v2,a1在刚刚的main函数可以看到,就是我们的输入,v2大概就是秘钥了

img

进去之后就得到了rc4的加密函数了,不用怀疑,没有魔改,就是正常的rc4,这个getkey就是生成秘钥盒了,可以进去看看秘钥确定一下秘钥是什么,这里传入的参数就是前面的那一句话:Debugging_Is_A_Great_Method_For_Problem_Solving

img

这里的坑就来了!a2的参数就是:Debugging_Is_A_Great_Method_For_Problem_Solving 是一个很长的字符串,但是在生成秘钥盒的时候却只索引了前9位,也就是Debugging,所以说,rc4的秘钥只有Debugging这一部分,然后就可以直接拿密文去赛博厨子解密了

img

也附上exp:

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
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
#include<stdio.h>

/*
程序实现时,需要注意的是,状态向量数组S和临时向量数组T
的类型应设为unsigned char,而不是char。因为在一些机器
下,将char默认做为signed char看待,在算法中计算下标i,j的
时候,会涉及char转int,如果是signed的char,那么将char的8
位拷贝到int的低8位后,还会根据char的符号为,在int的高位补
0或1。由于密钥是随机产生的,如果遇到密钥的某个字节的高
位为1的话,那么计算得到的数组下标为负数,就会越界。
*/
typedef struct _RC4INFO
{
unsigned char s_box[256];
unsigned char t_box[256];
}RC4_INFO,*PRC4_INFO; //定义 RC4 中要用到的 S-Box 和临时向量 T,封装在结构体中并给正常别名和指针别名。


/*
初始化函数 需要传入key 以及 keylen
主要有几个步骤
1.初始化Sbox
2.将key填充到Tbox中
3.组合sbox[i] 与 tbox[i] 然后进行交换
*/
void rc4_init(PRC4_INFO prc4,unsigned char key[],unsigned int keylen)
{
int i=0;
int j=0;
unsigned char tmp;
if(prc4==NULL)
{
return;
}

/*
初始化存储0-255字节的Sbox(其实就是一个数组)
填充key到256个字节数组中称为Tbox(你输入的key不满256个字节则初始化到256个字节)
*/
for(i=0;i<256;i++)
{
prc4->s_box[i] = i;
prc4->t_box[i] = key[i % keylen]; //如果密钥的长度是256字节,就直接把密钥的值赋给k,否则,轮转地将密钥的每个字节赋给k
}

//交换s[i]与s[j] i 从0开始一直到255下标结束. j是 s[i]与T[i]组合得出的下标
for(i=0;i<256;i++)
{
j=(j+prc4->s_box[i]+prc4->t_box[i])%256;
//开始交换
tmp=prc4->s_box[i];
prc4->s_box[i]=prc4->s_box[j];
prc4->s_box[j]=tmp;
}
}

/*
RC4加密其实就是遍历数据,将数据与sbox进行异或加密,而在此之前还需要交换一次sbox的数据
交换完之后 再把s[i] + s[j]的组合当做下标再去异或.
*/
void rc4_crypt(unsigned char data[], unsigned char key[])
{
int dn = 0; //data[n]的意思
int i = 0;
int j = 0; //i j分别用于交换sbox[i] 和 sbox[j]
int t = 0; //t = s[i] + s[j]
unsigned char tmp;

RC4_INFO rc4; //这里定义前面的结构题
rc4_init(&rc4, key, 9); //在加密函数中调用初始化函数,就省去了其它代码中出现的要保存初始化 sBox 的现象了.

for (dn = 0; dn < 32; dn++)
{
//i确保S-box的每个元素都得到处理,j保证S-box的搅乱是随机的。
i = (i + 1) % 256;
j = (j + rc4.s_box[i]) % 256;

//交换 s_box[i] 和 s_box[j]
tmp = rc4.s_box[i];
rc4.s_box[i] = rc4.s_box[j];
rc4.s_box[j] = tmp;

//交换完之后 再把s[i] + s[j]的组合当做下标再去异或.
t = (rc4.s_box[i] + rc4.s_box[j]) % 256;
data[dn] ^= rc4.s_box[t];
}
}

void EntryBuffer(unsigned char data[])
{
unsigned char key[] = "Debugging";
rc4_crypt(data, key);
}



int main()
{
char Hell[32] = {234,241,75,250,204,221,84,93,110,224,75,184,118,168,138,57,28,12,255,0,26,196,157,131,86,239,204,55,204,187,151,150};
int i;
EntryBuffer((unsigned char*)Hell); //加第一次调用就是加密
printf("解密后的flag是:%32s\n\n", Hell);
return 0;
}

flag:QLNU{JusT_D3bu6_Me_1S_0o0O0Oo0K}

进击的小鸟(超详细版)

考点:花指令,关键函数定位,伪随机数,异或,pdb文件导入

游戏题,出的难度还可以,有很多种做法。

很多同学说游戏无法打开,报错说确实什么什么dll,这种报错要么就去网上找这样的dll然后放到文件夹里或者系统文件里,但是最好的方法是可以跟着我的文章配置下vs的c语言环境,这个是大家早晚都要配置,毕竟devc和vscode都不如vs好用,而且下载vs的时候可以顺便下载一些dll。解决了百分之九十的dll缺失问题。

重生之妈妈我要打CTF–简单的环境配置 | WenWenJiang’s Blog

本题由于函数较多,所以本出题人好心的把pdb文件也一起给你们了。 pdb文件就是存储着程序的一些符号表,通俗的说就是你们反编译exe的时候如果载入了pdb文件那么就可以看到我写代码的时候定义的函数名,而不是loc40086这样的(为了更方便你们找到getflag关键函数) 载入方法: 在ida反编译的时候出现下面这个弹窗的时候选择yes,然后找到题目文件夹选择给你们的pdb文件就行了。

img

接下来是本题目的两个解法,分别是ce修改器解法,和ida分析逆向法。

ida分析逆向法(希望你们学会

这个解法算是常规解法,ida反编译程序

img

main函数写的很简单,分别是开始,游戏,和结束三部分代码,

我写的关键的getflag函数在end里面

img

函数传入的参数是分数score

跟进进去这个函数,就发现出现了下面的这个情况:

img

这就说明ida在反编译出现了异常,当然,这里就是一个简单的花指令。

按tab键去看一下汇编部分,去一下花指令

花指令去除:

img

是一个很经典的jz,jnz无条件跳转

img

在花指令处按u键,将代码变成十六进制数据,可以看到原来的call爆红的地方变成了db 0E8H 即定义一个数据为0XE8 这里就简单讲一下花指令的原理,我们用户端定义了一个0xE8一个无用的数据,对整个函数运行,没有任何影响。但是0xE8这个数据如果要翻译成汇编代码的话,就是一个call指令,即跳转执行函数。所以在不影响程序运行的情况下,骗过了ida的反编译,让他以为这里是一个call指令(其实就是定义了一个junkcode,所谓的脏字节)

img

解决方法也很简单,右击nop掉这个0xE8就行,然后就变成了0x90(或者用keypatch把E8改成90也是一样的)因为90这个数据转化成指令的话就是空的意思,没啥用,所以改过来让ida知道这里不是call指令就行了

img

然后找到这个函数最上面的起点,点击函数的起点,来一套U C P连招,重新编译一下nop之后的代码(也就是还原之后的源原码)

img

这里解释一下什么是UCP

U:刚刚解释过了。就是把汇编代码全部变回数据的形式

C:就是U的反操作,把数据变回汇编代码

P:类似于f5的功能,就是把刚变回来的汇编代码重新编译成伪c代码(这里按f5也是一样的功能 这三个都是ida的快捷键,类似的还是N,H,R等等,大家可以了解一下 下面就是编译好的代码,可以直接f5看一下编译好了

img

代码分析:

img

我将代码分成了三个区域,分别在下面说明

1: 这块就是定义v7这个数组,用于后面做比较的

2: 这部分是一个srand()函数,和③部分的函数rand()配套使用 srand(1704038400)这个函数的意义就是把1704038400这个数字作为随机数的种子

3:

这部分就是根据②的srand()函数生成的种子来生成一个数组,长度是35位

在逆向的时候由于我们也知道种子,所以可以直接自己生成自己去生成数组v4,结果和这里的结果是一样的

4:

加密部分,其实就是两个数组异或。

解密开始:

首先先把用srand()函数生成的数组给生成出来

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#include <stdio.h>
#include <stdlib.h>
#include <time.h>

int main() {
unsigned int seed = 1704038400;
int array[35];
int i;
srand(seed);
for (i = 0; i < 35; i++) {
array[i] = rand() % 255;
}
for (i = 0; i < 35; i++) {
printf("%d ", array[i]);
}
printf("\n");

return 0;
}
//218 215 125 172 171 129 84 200 181 148 173 10 106 234 64 82 211 149 119 54 112 41 176 26 110 212 248 56 156 84 79 192 52 125 13

至于这里为什么要%256,因为ascll码范围是0~255 得到了数组后,再加上一个异或就可以了

exp:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#include <stdio.h>
#include <stdlib.h>
#include <time.h>

int main() {
unsigned int seed = 1704038400;
unsigned char flag[35] = { 224,137,18,198,208,76,140,220,96,234,4,156,106,182,51,101,223,127,97,150,33,190,70,206,56,200,175,100,35,24,87,183,178,70,196 };
int array[35];
int i;
srand(seed);
for (i = 0; i < 35; i++) {
array[i] = rand() % 256;
flag[i]^=array[i];
}
for (i = 0; i < 35; i++) {
printf("%c", flag[i]);
}
printf("\n");

return 0;
}

flag:QLNU{The_f1ag 1s_@_littl3_bit_l0ng}

ce修改器破解法

这也是所有人的解题方法,下次可能不可能让你们这么轻松地ce修改了

可以直接用ce修改器去定位存储分数的地址,然后直接修改就可以了

ce打开这个游戏的进程(注意,不是exe的进程而且游戏的进程

img

先开局来一把零分,搜索一下为0的数值

img

然后拿到1分后点击再次扫码,找到这期间变化过的数值,还是很多,那就继续二分

img

差不多了,两个地方,一个是分数,一个是最高分,都双金修改数值50就可以了,注意关掉激活就行了(不然下次玩的时候得到的分数会覆盖修改之后的数值

img

然后就得到了flag

flag:QLNU{The_f1ag 1s_@_littl3_bit_l0ng}

keypatch、动调方法

这个方法是在ida分析的基础上,已经了解了代码的大致流程的情况下进行

可以看下重点的getflag函数:

img

出题人不算是特别阴间,拿一分就加密一分flag,而是事先就把flag给解密好,然后看得到了几份就给几位flag,所以直接下断点在解密flag的地方就可以直接看到完整的flag了,v7即是flag

img

或者也可以在这里下断点:第一个断点是在输出flag之前,用于修改a1的数值,第二个是输出结束之后,看结果的

img

a1就是getflag函数传入的参数,即分数,k是循环变量,我们只需要把k>=a1的条件给删掉(死循环)或者在这里用keypatch修改一下a1的数值就行了

img

改一下数,别太大,最好别99,容易崩溃。改成50就行了

然后一直按f8flag就一个个出来了 (后来我崩溃太多次,没截图了,这么做是可以的)

Drink a cup of tea

考点:tea加密

就是一个简单的tea加密,而且没有任何魔改,而且写的非常美观

img

v4[0]~v3[7]是密文,v7是key

img

多么经典的tea加密,甚至没有一点魔改

exp:

在re大纲的一篇文章的代码,具体的tea加密,xtea,xxtea可以看re大纲的文章

另外自己也有详细分析过一次xxtea魔改(真的很详细,GDOU的题目)

https://jiangwenwen066.github.io/2023/02/09/gdou_tea/

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
40
41
42
43
44
45
46
47
48
49
50
51
52
53
#include <stdio.h>
#include <stdint.h>

void encrypt (uint32_t *v,uint32_t *k ){
uint32_t v0=v[0],v1=v[1],sum=0,i;
uint32_t delta=0x9e3779b9;
uint32_t k0=k[0],k1=k[1],k2=k[2],k3=k[3];
for(i=0;i<32;i++){
sum+=delta;
v0+=((v1<<4)+k0)^(v1+sum)^((v1>>5)+k1);
v1+=((v0<<4)+k2)^(v0+sum)^((v0>>5)+k3);
}
v[0]=v0;v[1]=v1;
}
void decrypt (uint32_t *v,uint32_t *k){
uint32_t v0=v[0],v1=v[1],sum=0xC6EF3720,i; //这里的sum是0x9e3779b9*32后截取32位的结果
uint32_t delta=0x9e3779b9;
uint32_t k0=k[0],k1=k[1],k2=k[2],k3=k[3];
for (i=0;i<32;i++){
v1-=((v0<<4)+k2)^(v0+sum)^((v0>>5)+k3);
v0-=((v1<<4)+k0)^(v1+sum)^((v1>>5)+k1);
sum-=delta;
}
v[0]=v0;v[1]=v1;
}
int main()
{
int i;
uint32_t v[32]={0x3252ed85,0x43e280f6,0xf56e39f5,0x3698a3a8,0xc4777109,0x1421affd,0x6134c4b7,0x248722fc},k[4]={0x51,0x4C,0x4E,0x55};


for(i=0;i<8;i+=2)

{
decrypt(v+i,k);
}

printf("\n解密密之后的数据是:\n") ;

for(i=0;i<8;i++)

{
uint32_t value = v[i];
char str[5];
for (int j = 0; j < 4; j++)
{
str[3-j] = (char)(value & 0xFF);
value >>= 8;
}
str[4] = '\0';
printf("%s", str);
}
}

flag:QLNU{JuSt_JuNk_A_CuP_0F_T3A_tEa}