StarCTF2021

自己的程序分析能力还要加强啊!!!!chacha20的解密不知道问题出在哪里??? qemu-riscv64也没能配置好环境,调试不了程序,所以 Favourite Architecture flag0 只解了一半,希望有大佬能指导指导咋搞啊???

Stram

这是一个rust编写的elf文件

image-20220407133426419

程序流程分析

找到main函数,进入第一个函数之中

image-20220408122043365

读取输入数据

打开flag文件,读取其中的数据

image-20220408122229158

image-20220408122233905

对输入的加密过程

这里的加密过程调用了rustcrypto_api

init_chacha: 利用输入生成随机数种子

image-20220408122809423

refill_wide:流密钥加密的过程

image-20220408122923674

因为密钥的生成和输入显然是有关系的,这样的流密钥加密我们并不能够直接解密,所以使用爆破的方法

加密结果的输出

加密的结果会被写入到一个名为output的文件之中

image-20220408123238791

所以该程序的输入,我们可以通过写入flag文件输入;该程序输出的获取,我们可以通过读取ouput文件得知

python和程序的交互(subprocess模块)

subprocess模块

  • subprocess.Popen 类:创建并返回一个子进程,并在这个进程中执行指定的程序。

  • ```
    subprocess.Popen(args, bufsize=0, executable=None, stdin=None, stdout=None, stderr=None, preexec_fn=None, close_fds=False, shell=False, cwd=None, env=None, universal_newlines=False, startupinfo=None, creationflags=0)

    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

    * p.wait() : 等待子进程 p 终止,返回 p.returncode 属性(wait() 立即阻塞父进程,直到子进程结束!)

    * p.communicate(input=None): 和子进程 p 交流,将参数 *input* (字符串)中的数据发送到子进程的 stdin,同时从子进程的 stdout 和 stderr 读取数据,直到EOF。(只能通过**管道**和**子进程通信**,也就是说,只有调用 **Popen()** 创建子进程的时候参数 **stdin=subprocess.PIPE**,才能通过 **p.communicate(input)** 向子进程的 stdin **发送数据**;只有参数 **stout 和 stderr** 也都为 **subprocess.PIPE** ,才能通过**p.communicate()** 从子进程**接收数据**,否则接收到的二元组中,对应的位置是None。)

    * subprocess.PIPE: 调用本模块提供的若干函数时,作为 std* 参数的值,为标准流文件打开一个管道。

    例:

    ![image-20220407175812946](/images/starCTF/image-20220407175812946.png)

    ### 脚本

    脚本的执行流程:

    * 由加密的字符的顺序来爆破每一个元素,以防前面的加密会对后面的加密过程产生影响(index数组的遍历)
    * 向flag文件之中写入数据(初始化的数组,并且将对应的爆破对象的数据修改)
    * 运行程序
    * 读取output之中文件的数据
    * 将读取的数据之中对应的数据和目标的数据对比,如果相同,则爆破成功该元素,跳出循环
    * 再一次循环

    脚本中需要书写的函数:

    * 向flag文件之中写入数据
    * 从ouput文件之中读取数据,并且和目标的数据进行对比
    * 将数组转换成str形式的数据并返回
    * 运行该程序

    对比的数据(从提供的output文件之中得到)

    ```data
    0x9A,0x7A,0x42,0x92,0x3C,0xE3,0x1B,0xA4,0xB8,0x72,0x96,0x2F,0x01,0x98,0x5E,0x29,0x22,0xF5,0x9A,0x14,0x0D,0x4C,0x18,0x2A,0x38,0x41,0xD4,0xF3,0xA9,0xE8,0x60,0xEE,0xDA,0x03,0xDC,0x1C,0xA1,0x86,0x12,0xFE,0x18,0x42,0x89,0x80,0x91,0xDC

读取文件的顺序:

1
4,11,18,25,32,39,0,7,14,21,28,35,42,3,10,17,24,31,38,45,6,13,20,27,34,41,2,9,16,23,30,37,44,5,12,19,26,33,40,1,8,15,22,29,36,43

因为要运行程序,所以需要先配置环境rust环境(我是在ubuntu里面配置的环境)

这里爆破一定要使用递归的方法(因为相同的计算结果可能有多种输入都满足,用递归的方法排除不满足的结果)

爆破脚本:

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
import subprocess

index = [4,11,18,25,32,39,0,7,14,21,28,35,42,3,10,17,24,31,38,45,6,13,20,27,34,41,2,9,16,23,30,37,44,5,12,19,26,33,40,1,8,15,22,29,36,43]
rightOut = [0x9A,0x7A,0x42,0x92,0x3C,0xE3,0x1B,0xA4,0xB8,0x72,0x96,0x2F,0x01,0x98,0x5E,0x29,0x22,0xF5,0x9A,0x14,0x0D,0x4C,0x18,0x2A,0x38,0x41,0xD4,0xF3,0xA9,0xE8,0x60,0xEE,0xDA,0x03,0xDC,0x1C,0xA1,0x86,0x12,0xFE,0x18,0x42,0x89,0x80,0x91,0xDC]
flag = [0x21]*46

def implemnt():
ox = subprocess.Popen('./a',stdout=subprocess.PIPE,shell=True)
out,erro = ox.communicate()
status = ox.wait()

def writeFlag(flag):
f = open('flag','wb')
f.write(str.encode(flag))
f.close

def checkOutput(theIndex):
f = open('output','rb')
judge = f.read()
f.close
# print(judge[theIndex],end=" : ")
# print(rightOut[theIndex])
if len(judge) != 46:
return False
if judge[theIndex] == rightOut[theIndex]:
return True
return False

def ArrayToStr(arr):
tem = ''
for i in range(len(arr)):
tem+=chr(arr[i])
return tem


def dfs(i):
if i>=46:
print(ArrayToStr(flag))
return 0
for j in range(33,126,1):
flag[index[i]] = j
flagStr = ArrayToStr(flag)
writeFlag(flagStr)
implemnt()
if checkOutput(index[i]):
print(ArrayToStr(flag))
dfs(i+1)
else:
continue

dfs(0)
print("suc")
print()
print(ArrayToStr(flag))

flag:*ctf{EbXZCOD56vEHNSofFvRHG7XtgFJXcUXUGnaaaaaa}

image-20220408000131371

wherekey

知识点:静态编译文件的符号表恢复、sage的线性方程求解

导入签名文件

这是一个静态编译的elf文件

image-20220408132906464

ldd命令是Linux查看动态链接库的命令,执行该命令我们可以查看到,所以这个是静态编译

image-20220408133153683

所以需要恢复一下库函数的符号

FLIRT:IDA之中所提供的,库文件快速识别与鉴定技术,能通过一个静态链接库的签名文件,来快速识别被去符号的程序中的函数,从而为函数找到符号

将sig文件导入到ida之中,网上有大佬把各个平台各个版本的sig都放到github上面,我们只需要下载下来导入就可以了(但是如果有特殊需要可以按照上面提供的文章制作SIG文件)

相关sig文件的下载地址:

https://github.com/Maktm/FLIRTDB

https://github.com/push0ebp/sig-database

大致的挑选sig的版本,可以通过用file命令和strings命令去找版本信息,从而挑选sig,但是这样也很难准确找到,使用下面脚本找

使用iscan的脚本可以匹配测试出最佳的sig:https://github.com/maroueneboubakri/lscan

上面这个脚本之中指定的-S 参数要是一个只有SIG文件的文件夹

我找了一个Ubuntu20.版本的SIG文件(签名文件),导入之后识别了500多个函数

程序逻辑分析

查看字符串,通过Please enter key 定位到一段代码,这个地方有花指令(去花指令)

main函数

这个地方是jmp jnz型的花指令,nop掉jmp和jnz那个地方的花指令就可以(逐个进行jmp,因为该指令字节之中可能会有效的字节)

image-20220408194407936

又通过 /dev/tty 定位到一段代码(也就是主函数之中,字符串上面的那个函数)

image-20220408192144886

  • bsd_signal() 函数为使用 BSD 形式的 signal() 函数编写的程序提供了部分兼容的接口。(所以这里有信号的传送)
  • _libc_open64()函数读取文件,获得输入(可能就是输入的额flag)

bsd_signal()的函数联系到一个函数:(这个地方能够返回一些值)

image-20220408193332857

再在主函数之中向下翻看函数,找到第二个有效函数sub_402072();

在这个函数的开头我们发现了读取文件的操作的函数,并且这里读取的内容和之前 “/dev/tty ”输入数据相关联

image-20220408194929090

image-20220408195007066

在最开始接受信号的函数之中,通过下面两个数据,跟进到内存之中,发现了一个字符串

image-20220408200209847

image-20220408200307920

通过字符串找到对应的函数

image-20220408200333547

这个函数本身内部只有简单的操作,我交叉引用了一下,找到了下面这个函数

image-20220408200503270

再向上xref 找到一个含有_libc_recvfrom()的函数,dword_4C8520关联到socket接口(接受输入的数据)

image-20220408201150205

image-20220408201758219

image-20220408201806836

  • recvfrom()函数:经socket接收数据
  • sys_socket():创建插口

所以上面的逻辑是,程序执行socket产生,tty文件中的数据被recvfrom()函数接收(5个数),接收之后进入到这个加密函数之中,最后对比的结果是:0x4C5150位置的数据(我们在主函数之中最后unk_4C5130数据能够定位到这段数据的周围)后面的分析也可以知道输入的字符串的长度应该是25字节

1
2
3
4
5
0x38, 0x6D, 0x4B, 0x4B, 0xB9, 
0x8A, 0xF9, 0x8A, 0xBB, 0x5C,
0x8A, 0x9A, 0xBA, 0x6B, 0xD2,
0xC6, 0xBB, 0x05, 0x90, 0x56,
0x93, 0xE6, 0x12, 0xBD, 0x4F

线性方程求解

分析加密的过程:

初始化数组:

image-20220408205815225

每轮初始化的结果依次是

1
2
3
4
5
"faori"
"lruee"
"ae__n"
"g_sfd"
"{yur}"

输入的元素和上面初始化的结果(每轮和一组字符串)进行逐byte的相乘之后相加则得到最后的结果(要模257)

image-20220408210401423

所以这个地方就是线性方程组求解问题(一共有五组线性方程组,每组之中五个变量,5个方程,每个方程对应在模257的运算之中得到相应的结果)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
[[102,108,97,103,123,],
[97,114,101,95,121,],
[111,117,95,115,117,],
[114,101,95,102,114,],
[105,101,110,100,125,]]

5组方程分别的结果:
[[0x38, 0x6D, 0x4B, 0x4B, 0xB9],
[0x8A, 0xF9, 0x8A, 0xBB, 0x5C],
[0x8A, 0x9A, 0xBA, 0x6B, 0xD2],
[0xC6, 0xBB, 0x05, 0x90, 0x56],
[0x93, 0xE6, 0x12, 0xBD, 0x4F]]

下面的矩阵乘以上面矩阵的逆矩阵

使用Sage解有限域的矩阵的运算

Sage的使用:

将每轮加密使用的密钥矩阵声明M 在有限域之中(作为右乘元素)

image-20220409094704039

将加密的结果声明成二维数组,其中的每组元素就是一个线性方程的解

image-20220409094808398

第一组解:[72,97,50,51,95]

image-20220409094916865

第二组解:[102 ,48,110, 95 ,57]

image-20220409094950173

第三组解:[110,100 ,95 ,71 ,48]

image-20220409095206154

第四组解:[111,100 ,45 ,49,117]

image-20220409095311499

第五组解:[ 99,107 ,45 ,79 ,72]

image-20220409095353846

整体:

1
2
3
4
5
6
tuple(M.solve_right(Matrix(GF(257),(arrayFlag[i] for i in range(5))).transpose()).transpose())
((72, 97, 50, 51, 95),
(102, 48, 110, 95, 57),
(110, 100, 95, 71, 48),
(111, 100, 45, 49, 117),
(99, 107, 45, 79, 72))

image-20220409101502916

转为字符串

1
2
3
4
obj = [[72,97,50,51,95],[102 ,48,110, 95 ,57],[110,100 ,95 ,71 ,48],[111,100 ,45 ,49,117],[ 99,107 ,45 ,79 ,72]]
for i in range(len(obj)):
for j in range(5):
print(chr(obj[i][j]),end="")

Ha23_f0n_9nd_G0od-1uck-OH

*ctf{Ha23_f0n_9nd_G0od-1uck-OH}

Favourite Architecture flag0

这道题使用的 qemu-riscv64 框架 RISC-V的反编译在ida之中不能完成,要使用Ghidra这款软件反编译

运行该程序,加载了Input the flag 这个字符串,我们能够通过字符串定位到代码之中的位置

image-20220409122603408

定位关键代码

在Ghidra之中查看字符串窗口,找到关键的字符串(有特别含义的)

image-20220409124436585

通过上面的三个字符串定位到相应的代码位置

定位到相对应的代码位置(这三个字符串都在同一函数之中)

image-20220409124726333

在GHidra之中按Ctrl+E反汇编,但是这里不能直接反汇编(这个地方的反编译失败和gp在最开始的设置的数值有关)

重置gp

看反编译代码,这里的指针调用使用了gp

image-20220409143211328

gp在程序运行的时候就会进行初始化

image-20220409143310349

这里初始化成的是0x6f178,不正确,需要修改gp的值

gp:

  • gp寄存器在启动代码中加载为__global_pointer$的地址,并且之后不能被改变。
  • 通过gp指针,访问其值±2KB,即4KB范围内的全局变量,可以节约一条指令。4KB区域可以位于寻址内存中任意位置,但是为了使优化更有效率,最好覆盖最频繁使用的RAM区域。

根据gp寄存器的作用,我将gp寄存器的值修改为需要反编译的代码附近

重置gp的步骤:

  • Ctrl+A选中所有的反汇编的代码
  • Ctrl+R打开寄存器的修改界面
  • 选择gp寄存器
  • 将值修改为hex的00010400这个地址

反编译关键代码

再回到目标的代码处,Ctr+E 就能够对该段代码进行反汇编

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
undefined8 UndefinedFunction_00010400(void)

{
ulonglong uVar1;
longlong lVar2;
undefined8 uVar3;
undefined auStack488 [192];
undefined auStack296 [256];
ulonglong uStack40;
longlong lStack32;
int iStack20;

FUN_00017d74(uRam000000000000fcb0,0);
FUN_00017d74(uRam000000000000fca8,0);
FUN_00017d74(uRam000000000000fca0,0);
FUN_0001605a("Input the flag: ");
FUN_00016a5a(auStack296);
uVar1 = FUN_000204e4(auStack296);
if (uVar1 == ((longlong)(iRam000000000000fc64 + iRam000000000000fc60) & 0xffffffffU)) {
lStack32 = FUN_00020386(auStack296 + ((longlong)iRam000000000000fc60 & 0xffffffff));
FUN_0001118a(auStack488,"tzgkwukglbslrmfjsrwimtwyyrkejqzo","oaeqjfhclrqk",0x80);
FUN_000111ea(auStack488,auStack296,iRam000000000000fc60);
lVar2 = FUN_00020e2a(auStack296,&DAT_0006d000,iRam000000000000fc60);
if (lVar2 == 0) {
uStack40 = FUN_000204e4(lStack32);
iStack20 = 0;
while ((ulonglong)(longlong)iStack20 < uStack40 >> 3) {
FUN_000102ae(iStack20 * 8 + lStack32,&DAT_0006d060);
lVar2 = FUN_00020e2a(iStack20 * 8 + lStack32,(longlong)(iStack20 * 8) + 0x6d030,8);
if (lVar2 != 0) goto LAB_0001057a;
iStack20 = iStack20 + 1;
}
FUN_00016bc8("You are right :D");
uVar3 = 0;
goto LAB_00010588;
}
}
LAB_0001057a:
FUN_00016bc8("You are wrong ._.");
uVar3 = 1;
LAB_00010588:
gp = UndefinedFunction_00010400;
return uVar3;
}

chacha20加密

第一个加密的过程:将输入进行了异或运算,异或运算的对象上一个函数初始化的结果

image-20220409145605305

加密之后对比的数据:

1
88 e7 03 b4 36 cd 97 ab 5a a5 a6 0b df ce 08 3b 9d 90 32 3c 4e 15 14 bd 8d 38 38 b0 ee 2a bc 4b f9 aa 24 26 76 a3 a5 75 5e 00 00 00 00 00 00 00
1
[0x88,0xe7,0x03,0xb4,0x36,0xcd,0x97,0xab,0x5a,0xa5,0xa6,0x0b,0xdf,0xce,0x08,0x3b,0x9d,0x90,0x32,0x3c,0x4e,0x15,0x14,0xbd,0x8d,0x38,0x38,0xb0,0xee,0x2a,0xbc,0x4b,0xf9,0xaa,0x24,0x26,0x76,0xa3,0xa5,0x75,0x5e]

进入这个加密函数之中,进入里面引用了字符串的函数之中(FUN_000106ce(param_1,param_2,param_3))

这个函数之中是初始化密钥,经过搜索字符串“expand 32-byte k”,这个地方使用的可能是Salsa20的流密码 => chacha20

image-20220409165149123

image-20220409165113356

image-20220409164221139

image-20220409180108114

前半部分的解密是chahca20 密钥是32字节的 tzgkwukglbslrmfjsrwimtwyyrkejqzo 随机数是16字节的 oaeqjfhclrqk

flag{have_you_tried_ghidra9.2_decompiler_

但是这里的流密码加密中间应该是做了修改的,我不知道具体是在哪里,猜测有可能是最后的异或的对象做了修改(下面两个就是chacha20的密钥和随机数)

image-20220409175857337

tea加密

第二个加密(在while循环里面) 这个地方是tea加密

image-20220409150540764

tea加密:

1
2
3
4
5
6
7
8
9
10
11
void encrypt (uint32_t v[2], const uint32_t k[4]) {
uint32_t v0=v[0], v1=v[1], sum=0, i; /* set up */
uint32_t delta=0x9E3779B9; /* a key schedule constant */
uint32_t k0=k[0], k1=k[1], k2=k[2], k3=k[3]; /* cache key */
for (i=0; i<32; i++) { /* basic cycle start */
sum += delta;
v0 += ((v1<<4) + k0) ^ (v1 + sum) ^ ((v1>>5) + k1);
v1 += ((v0<<4) + k2) ^ (v0 + sum) ^ ((v0>>5) + k3);
} /* end cycle */
v[0]=v0; v[1]=v1;
}

变形:进行的是16轮加密 -0x61c88647 所以解密代码之中的 减delta -> 加delta 其中的sum = delta << 4(16轮)进行解密

密钥:

1
0x1368A0BB,0x190ACE1E,0x35D8A357,0x26BF2C61

加密的数据:

1
2
3
4
5
6
0xC45087F9,0x703F2B2,
0x6974F43C,0xEDB4BB59,
0xFF0B02A,0x8520F2,
0xFDCD23DD,0x35024875,
0xF1D7B6D3,0x74F21BE1,
0xCB2DBF12,0xA4B453F6

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
#include <stdio.h>
#include <stdint.h>
void decrypt (uint32_t* v, uint32_t* k) {
uint32_t v0=v[0], v1=v[1], i; /* set up */
int32_t delta=-0x61c88647 , sum=0xC6EF3720; /* a key schedule constant */
uint32_t k0=k[0], k1=k[1], k2=k[2], k3=k[3]; /* cache key */
sum = delta * 16;
for (i=0; i<16; i++) { /* basic cycle start */
v1 -= ((v0<<4) + k2) ^ (v0 + sum) ^ ((v0>>5) + k3);
v0 -= ((v1<<4) + k0) ^ (v1 + sum) ^ ((v1>>5) + k1);
sum -= delta;
} /* end cycle */
v[0]=v0; v[1]=v1;
}

int main()
{
uint32_t v1[3]={0xC45087F9,0x703F2B2,0x0},k[4]={0x1368A0BB,0x190ACE1E,0x35D8A357,0x26BF2C61};
uint32_t v2[3]={0x6974F43C,0xEDB4BB59,0x0};
uint32_t v3[3]={0xFF0B02A,0x8520F2,0x0};
uint32_t v4[3]={0xFDCD23DD,0x35024875,0x0};
uint32_t v5[3]={0xF1D7B6D3,0x74F21BE1,0x0};
uint32_t v6[3]={0xCB2DBF12,0xA4B453F6,0x0};
decrypt(v1, k);
decrypt(v2, k);
decrypt(v3, k);
decrypt(v4, k);
decrypt(v5, k);
decrypt(v6, k);
printf("解密后的数据:%s%s%s%s%s%s\n",(char*)v1,(char*)v2,(char*)v3,(char *)v4,(char *)v5,(char *)v6); // 注意如何将一串数组以字符串的形式输出
return 0;
}
解密后的数据:if_you_have_hexriscv_plz_share_it_with_me_thx:P}

这就是后半部分的flag

所以整个flag是 flag{have_you_tried_ghidra9.2_decompiler_if_you_have_hexriscv_plz_share_it_with_me_thx:P}

chacha20加密的分析过程(未解)-自己的分析过程

下面是我的解密的思路,但是不知道到底是哪里的数据出问题了,并不能解得答案?????

初始化 ,利用下面的特征我推测出来这个地方是进行chacha20加密的方法,密钥是tzgkwukglbslrmfjsrwimtwyyrkejqzo(32字节)

随机数 oaeqjfhclrq(12字节)

image-20220410084430925

image-20220410084449792

image-20220410084912851

image-20220410084516526

初始化之后进入加密函数之中

image-20220410084546592

我猜测下面标注的这个函数是利用初始化的表生成密钥流的过程

image-20220410084607632

image-20220410084639998

生成的密钥流用下面的方法和输入的字符进行异或运算

image-20220410084700362

最后生成的密文是

1
2
3
[0x88,0xe7,0x03,0xb4,0x36,0xcd,0x97,0xab,0x5a,0xa5,0xa6,0x0b,0xdf,0xce,0x08,0x3b,0x9d,0x90,0x32,0x3c,0x4e,0x15,0x14,0xbd,0x8d,0x38,0x38,0xb0,0xee,0x2a,0xbc,0x4b,0xf9,0xaa,0x24,0x26,0x76,0xa3,0xa5,0x75,0x5e]
hex表示:
88e703b436cd97ab5aa5a60bdfce083b9d90323c4e1514bd8d3838b0ee2abc4bf9aa242676a3a5755e

但是这里并不能解密,中间的加密步骤相比于常规的chacha20加密有可能被改了,我猜测是不是最后一步异或的过程,常规的流密钥加密

单次运算的结果

image-20220410104954137

Java的Chacha20的解密脚本:

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
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
package Coco2;

import java.util.Arrays;

public class ChaCha20 {


public static void main(String[] args) {
// TODO Auto-generated method stub
// byte[] key, byte[] nonce, int counter
byte[] key = {116,122,103,107,119,117,107,103,108,98,115,108,114,109,102,106,115,114,119,105,109,116,119,121,121,114,107,101,106,113,122,111};
byte[] nonce = {111,97,101,113,106,102,104,99,108,114,113,107};
byte[] dst = new byte[0x29];
byte[] src = {(byte) 0x88,(byte) 0xe7,0x03,(byte) 0xb4,0x36,(byte) 0xcd,(byte) 0x97,(byte) 0xab,0x5a,(byte) 0xa5,(byte) 0xa6,0x0b,(byte) 0xdf,(byte) 0xce,0x08,0x3b,(byte) 0x9d,(byte) 0x90,0x32,0x3c,0x4e,0x15,0x14,(byte) 0xbd,(byte) 0x8d,0x38,0x38,(byte) 0xb0,(byte) 0xee,0x2a,(byte) 0xbc,0x4b,(byte) 0xf9,(byte) 0xaa,0x24,0x26,0x76,(byte) 0xa3,(byte) 0xa5,0x75,0x5e};
int counter = 0;
int len = src.length;
System.out.println("加密的对象是:");
System.out.println(Arrays.toString(src));
try {
ChaCha20 cha = new ChaCha20(key,nonce,counter);
cha.encrypt(dst, src, len);
System.out.println("加密或者解密之后的结果:");
System.out.println(Arrays.toString(dst));
} catch (WrongKeySizeException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (WrongNonceSizeException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}

}
/*
* Key size in byte
*/
public static final int KEY_SIZE = 32;

/*
* Nonce size in byte (reference implementation)
*/
public static final int NONCE_SIZE_REF = 8;

/*
* Nonce size in byte (IETF draft)
*/
public static final int NONCE_SIZE_IETF = 12;

private int[] matrix = new int[16];

// 初始化表的时候使用
protected static int littleEndianToInt(byte[] bs, int i) {
return (bs[i] & 0xff) | ((bs[i + 1] & 0xff) << 8) | ((bs[i + 2] & 0xff) << 16) | ((bs[i + 3] & 0xff) << 24);
}
// 得到密钥之后使用
protected static void intToLittleEndian(int n, byte[] bs, int off) {
bs[ off] = (byte)(n );
bs[++off] = (byte)(n >>> 8);
bs[++off] = (byte)(n >>> 16);
bs[++off] = (byte)(n >>> 24);
}

protected static int ROTATE(int v, int c) {
return (v << c) | (v >>> (32 - c));
}
// 0 4 8 12
protected static void quarterRound(int[] x, int a, int b, int c, int d) {
x[a] += x[b];
x[d] = ROTATE(x[d] ^ x[a], 16);
x[c] += x[d];
x[b] = ROTATE(x[b] ^ x[c], 12);
x[a] += x[b];
x[d] = ROTATE(x[d] ^ x[a], 8);
x[c] += x[d];
x[b] = ROTATE(x[b] ^ x[c], 7);
}
// 密钥流的生成过程中使用
public class WrongNonceSizeException extends Exception {
private static final long serialVersionUID = 2687731889587117531L;
}

public class WrongKeySizeException extends Exception {
private static final long serialVersionUID = -290509589749955895L;
}

// 初始化表的时候
public ChaCha20(byte[] key, byte[] nonce, int counter)
throws WrongKeySizeException, WrongNonceSizeException {

if (key.length != KEY_SIZE) {
throw new WrongKeySizeException();
}

this.matrix[ 0] = 0x61707865;
this.matrix[ 1] = 0x3320646e;
this.matrix[ 2] = 0x79622d32;
this.matrix[ 3] = 0x6b206574;
this.matrix[ 4] = littleEndianToInt(key, 0);
this.matrix[ 5] = littleEndianToInt(key, 4);
this.matrix[ 6] = littleEndianToInt(key, 8);
this.matrix[ 7] = littleEndianToInt(key, 12);
this.matrix[ 8] = littleEndianToInt(key, 16);
this.matrix[ 9] = littleEndianToInt(key, 20);
this.matrix[10] = littleEndianToInt(key, 24);
this.matrix[11] = littleEndianToInt(key, 28);

if (nonce.length == NONCE_SIZE_REF) { // reference implementation
this.matrix[12] = 0;
this.matrix[13] = 0;
this.matrix[14] = littleEndianToInt(nonce, 0);
this.matrix[15] = littleEndianToInt(nonce, 4);

} else if (nonce.length == NONCE_SIZE_IETF) {
this.matrix[12] = counter;
this.matrix[13] = littleEndianToInt(nonce, 0);
this.matrix[14] = littleEndianToInt(nonce, 4);
this.matrix[15] = littleEndianToInt(nonce, 8);
} else {
throw new WrongNonceSizeException();
}
}
// 密钥流的生成,利用生成的密钥流进行异或运算加密
public void encrypt(byte[] dst, byte[] src, int len) {
int[] x = new int[16];
byte[] output = new byte[64];
int i, dpos = 0, spos = 0;

while (len > 0) {
for (i = 16; i-- > 0; ) x[i] = this.matrix[i];
for (i = 20; i > 0; i -= 2) {
quarterRound(x, 0, 4, 8, 12);
quarterRound(x, 1, 5, 9, 13);
quarterRound(x, 2, 6, 10, 14);
quarterRound(x, 3, 7, 11, 15);
quarterRound(x, 0, 5, 10, 15);
quarterRound(x, 1, 6, 11, 12);
quarterRound(x, 2, 7, 8, 13);
quarterRound(x, 3, 4, 9, 14);
}
for (i = 16; i-- > 0; ) x[i] += this.matrix[i];
for (i = 16; i-- > 0; ) intToLittleEndian(x[i], output, 4 * i);
// TODO: (1) check block count 32-bit vs 64-bit; (2) java int is signed!
this.matrix[12] += 1;
if (this.matrix[12] == 0) {
this.matrix[13] += 1;
}
System.out.println("生成的密钥是:");
System.out.println(Arrays.toString(output));
if (len <= 64) {
for (i = len; i-- > 0; ) {
dst[i + dpos] = (byte) (src[i + spos] ^ output[i]);
}
return;
}
for (i = 64; i-- > 0; ) {
dst[i + dpos] = (byte) (src[i + spos] ^ output[i]);
}
len -= 64;
spos += 64;
dpos += 64;
}
}
// 解密就是加密
public void decrypt(byte[] dst, byte[] src, int len) {
encrypt(dst, src, len);
}

}
加密的对象是:
[-120, -25, 3, -76, 54, -51, -105, -85, 90, -91, -90, 11, -33, -50, 8, 59, -99, -112, 50, 60, 78, 21, 20, -67, -115, 56, 56, -80, -18, 42, -68, 75, -7, -86, 36, 38, 118, -93, -91, 117, 94]
生成的密钥是:
[-97, 114, -91, 36, -54, -100, 77, -116, 39, -85, -55, -56, 78, -44, 66, 29, 106, -73, -56, -39, 41, -111, 17, -107, 97, -54, -18, 8, 91, -50, -29, 118, 11, 26, -100, -89, -64, -8, 10, -41, 86, 92, 22, -56, 18, -55, -116, -105, -66, -53, 80, 8, 8, -2, -2, -100, 90, 115, -106, 58, 7, 122, -103, 32]
加密或者解密之后的结果:
[23, -107, -90, -112, -4, 81, -38, 39, 125, 14, 111, -61, -111, 26, 74, 38, -9, 39, -6, -27, 103, -124, 5, 40, -20, -14, -42, -72, -75, -28, 95, 61, -14, -80, -72, -127, -74, 91, -81, -94, 8]

用c语言编写的得到密钥流的脚本:

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
#include <stdio.h>
#include <stdint.h>

//#define KEY_SIZE 32
//#define NONCE_SIZE_REF 8
//#define NONCE_SIZE_IETF 12

uint32_t matrix[16];
uint32_t key[8] = {0x6b677a74,0x676b7577,0x6c73626c,0x6a666d72,0x69777273,0x7977746d,0x656b7279,0x6f7a716a};
uint32_t nonce[3] = {0x7165616f,0x6368666a,0x6b71726c};
int counter=0;
uint32_t ROTATE(uint32_t v, int c)
{
return (v << c) | (v >> (32 - c));
}
void quarterRound(uint32_t* x, int a, int b, int c, int d)
{
x[a] += x[b];
x[d] = ROTATE(x[d] ^ x[a], 16);
x[c] += x[d];
x[b] = ROTATE(x[b] ^ x[c], 12);
x[a] += x[b];
x[d] = ROTATE(x[d] ^ x[a], 8);
x[c] += x[d];
x[b] = ROTATE(x[b] ^ x[c], 7);
}
// uint8_t* key, uint8_t* nonce, int counter
int ChaCha20()
{
matrix[ 0] = 0x61707865;
matrix[ 1] = 0x3320646e;
matrix[ 2] = 0x79622d32;
matrix[ 3] = 0x6b206574;
matrix[ 4] = key[0];
matrix[ 5] = key[1];
matrix[ 6] = key[2];
matrix[ 7] = key[3];
matrix[ 8] = key[4];
matrix[ 9] = key[5];
matrix[10] = key[6];
matrix[11] = key[7];

matrix[12] = counter;
matrix[13] = nonce[0];
matrix[14] = nonce[1];
matrix[15] = nonce[2];

}

// uint8_t* dst, uint8_t src, int len
void encrypt()
{
uint32_t x[16];
int i, dpos = 0, spos = 0;

for (i = 16; i-- > 0; ) x[i] = matrix[i];
// for (i=0;i<16;i++) printf("%d ",x[i]);
for (i = 0; i < 10; i ++)
{
quarterRound(x, 0, 4, 8, 12);
quarterRound(x, 1, 5, 9, 13);
quarterRound(x, 2, 6, 10, 14);
quarterRound(x, 3, 7, 11, 15);
quarterRound(x, 0, 5, 10, 15);
quarterRound(x, 1, 6, 11, 12);
quarterRound(x, 2, 7, 8, 13);
quarterRound(x, 3, 4, 9, 14);
}
for (i = 0; i< 16;i++ ) x[i] += matrix[i];
for (i=0;i<16;i++) printf("0x%x,",x[i]);
}

int main()
{
ChaCha20();
encrypt();
return 0;
}
得到的密钥流是:
0x24a5729f,0x8c4d9cca,0xc8c9ab27,0x1d42d44e,0xd9c8b76a,0x95119129,0x8eeca61,0x76e3ce5b,0xa79c1a0b,0xd70af8c0,0xc8165c56,0x978cc912,0x850cbbe,0x9cfefe08,0x3a96735a,0x20997a07

我对比了源码和我的解密脚本,以及密钥流的生成过程,我觉得是一样的,到底是哪里处问题了,望指正。

RCT-Harmony

就是为了练习一下使用Ghidra

这道题是RISC-V题目

image-20220410155031767

使用Ghidra打开该程序

通过查看字符串的表,我们找到特殊的字符串

image-20220410160020112

加密代码:

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
void UndefinedFunction_8000095c(void)

{
int iStack84;
undefined4 uStack72;
undefined4 uStack68;
undefined4 uStack64;
undefined4 uStack60;
undefined4 uStack56;
undefined2 uStack52;
undefined uStack50;
undefined uStack49;
undefined4 uStack48;
undefined4 uStack44;
undefined4 uStack40;
undefined4 uStack36;
undefined4 uStack32;
undefined4 uStack28;
undefined2 uStack24;

do {
FUN_80000832("Welcome to RCTF 2021...\n\r");
uStack72 = 0x4d524148;
uStack68 = 0x44594e4f;
uStack64 = 0x4d414552;
uStack60 = 0x4f505449;
uStack56 = 0x42495353;
uStack52 = 0x454c;
uStack50 = 0;
uStack48 = 0x44434241;
uStack44 = 0x48474645;
uStack40 = 0x4c4b4a49;
uStack36 = 0x504f4e4d;
uStack32 = 0x54535251;
uStack28 = 0x58575655;
uStack24 = 0x5a59;
iStack84 = 0;
while (iStack84 < 0x16) {
if (*(char *)((int)&uStack72 + iStack84) + 3 < 0x5b) {
*(char *)((int)&uStack72 + iStack84) = *(char *)((int)&uStack72 + iStack84) + '\x03';
}
else {
// 注意这个地方的起点是 &uStack49
*(undefined *)((int)&uStack72 + iStack84) =
(&uStack49)[(*(char *)((int)&uStack72 + iStack84) + -0x57) % 0x1a];
}
iStack84 = iStack84 + 1;
}
FUN_80000832("The result of encryption: %s\n\r",&uStack72);
FUN_800059a2(1000);
} while( true );
}

通过该字符串定位到对应的位置,Ctrl+E反编译

1
2
3
4
5
6
7
8
9
10
11
12
13
src1= [0x48,0x41,0x52,0x4d,0x4f,0x4e,0x59,0x44,0x52,0x45,0x41,0x4d,0x49,0x54,0x50,0x4f,0x53,0x53,0x49,0x42,0x4c,0x45]
src2= [0x0,0x41,0x42,0x43,0x44,0x45,0x46,0x47,0x48,0x49,0x4a,0x4b,0x4c,0x4d,0x4e,0x4f,0x50,0x51,0x52,0x53,0x54,0x55,0x56,0x57,0x58,0x59,0x5a]
print(len(src1))
print(len(src2))
print("RCTF{",end="")
for i in range(len(src1)):
if src1[i]+3 < 0x5b:
src1[i] = src1[i] +3
else:
src1[i] = src2[(src1[i]-0x57) % 0x1a]
print(chr(src1[i]),end="")
print("}")
RCTF{KDUPRQBGUHDPLWSRVVLEOH}

ChineseGame

先导入一个签名文件,将函数符号化,这个程序是用c++写的,所以找签名文件的时候找c++的签名文件会更好

数组之中的元素数据

image-20220410233525377

函数逻辑分析

进入到v9的赋值函数之中

image-20220411194148967

这里赋值了10个数,用1表示[101,200]的数 用0表示[0,100)的数

所以初始化的结果是1011111111

主体的逻辑:

通过输入0和1来判断如何给v9中的元素重新赋值image-20220411215259390

当输入的元素是0时:

将对应v9的值重新赋值为一个小于100的数

能赋值的条件:

  1. 是v9[9]
  2. 该值本生就小于100
  3. 当该值大于100,它后面的那位大于100,它再后面的每一位都小于100

image-20220411215515316

当输入的元素是1时:

将对应v9的值重新赋值为一个大于100的数

能赋值的条件:

  1. 是v9[9]
  2. 该值本生就大于100
  3. 当该值大于100,它后面的那位大于100,它再后面的每一位都小于100

image-20220411220002871

最后判断的条件:

v9中的所有数都是小于100时就成功了

image-20220411220039939

这里将大于100的状态定义为1 小于100的状态定义为0

所以就是将1011111111 =》 0000000000

脚本

我起初以为要自己来走,后来看别人的wp发现,这个给的数组就是正确的走的方式,为了保证每一次走的位置有意义(相应的v9的状态能发生变化),我们要判断这个点当前的状态,然后输出使其达到取反的方式(0or1)

脚本:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
src = [0x01,0x03,0x01,0x02,0x01,0x05,0x01,0x02,0x01,0x03,0x01,0x02,0x01,0x04,0x01,0x02,0x01,0x03,0x01,0x02,0x01,0x07,0x01,0x02,0x01,0x03,0x01,0x02,0x01,0x04,0x01,0x02,0x01,0x03,0x01,0x02,0x01,0x05,0x01,0x02,0x01,0x03,0x01,0x02,0x01,0x04,0x01,0x02,0x01,0x03,0x01,0x02,0x01,0x06,0x01,0x02,0x01,0x03,0x01,0x02,0x01,0x04,0x01,0x02,0x01,0x03,0x01,0x02,0x01,0x05,0x01,0x02,0x01,0x03,0x01,0x02,0x01,0x04,0x01,0x02,0x01,0x03,0x01,0x02,0x01,0x09,0x01,0x02,0x01,0x03,0x01,0x02,0x01,0x04,0x01,0x02,0x01,0x03,0x01,0x02,0x01,0x05,0x01,0x02,0x01,0x03,0x01,0x02,0x01,0x04,0x01,0x02,0x01,0x03,0x01,0x02,0x01,0x06,0x01,0x02,0x01,0x03,0x01,0x02,0x01,0x04,0x01,0x02,0x01,0x03,0x01,0x02,0x01,0x05,0x01,0x02,0x01,0x03,0x01,0x02,0x01,0x04,0x01,0x02,0x01,0x03,0x01,0x02,0x01,0x07,0x01,0x02,0x01,0x03,0x01,0x02,0x01,0x04,0x01,0x02,0x01,0x03,0x01,0x02,0x01,0x05,0x01,0x02,0x01,0x03,0x01,0x02,0x01,0x04,0x01,0x02,0x01,0x03,0x01,0x02,0x01,0x06,0x01,0x02,0x01,0x03,0x01,0x02,0x01,0x04,0x01,0x02,0x01,0x03,0x01,0x02,0x01,0x05,0x01,0x02,0x01,0x03,0x01,0x02,0x01,0x04,0x01,0x02,0x01,0x03,0x01,0x02,0x01,0x08,0x01,0x02,0x01,0x03,0x01,0x02,0x01,0x04,0x01,0x02,0x01,0x03,0x01,0x02,0x01,0x05,0x01,0x02,0x01,0x03,0x01,0x02,0x01,0x04,0x01,0x02,0x01,0x03,0x01,0x02,0x01,0x06,0x01,0x02,0x01,0x03,0x01,0x02,0x01,0x04,0x01,0x02,0x01,0x03,0x01,0x02,0x01,0x05,0x01,0x02,0x01,0x03,0x01,0x02,0x01,0x04,0x01,0x02,0x01,0x03,0x01,0x02,0x01,0x07,0x01,0x02,0x01,0x03,0x01,0x02,0x01,0x04,0x01,0x02,0x01,0x03,0x01,0x02,0x01,0x05,0x01,0x02,0x01,0x03,0x01,0x02,0x01,0x04,0x01,0x02,0x01,0x03,0x01,0x02,0x01,0x06,0x01,0x02,0x01,0x03,0x01,0x02,0x01,0x04,0x01,0x02,0x01,0x03,0x01,0x02,0x01,0x05,0x01,0x02,0x01,0x03,0x01,0x02,0x01,0x04,0x01,0x02,0x01,0x03,0x01,0x02,0x01,0x0a,0x01,0x02,0x01,0x03,0x01,0x02,0x01,0x04,0x01,0x02,0x01,0x03,0x01,0x02,0x01,0x05,0x01,0x02,0x01,0x03,0x01,0x02,0x01,0x04,0x01,0x02,0x01,0x03,0x01,0x02,0x01,0x06,0x01,0x02,0x01,0x03,0x01,0x02,0x01,0x04,0x01,0x02,0x01,0x03,0x01,0x02,0x01,0x05,0x01,0x02,0x01,0x03,0x01,0x02,0x01,0x04,0x01,0x02,0x01,0x03,0x01,0x02,0x01,0x07,0x01,0x02,0x01,0x03,0x01,0x02,0x01,0x04,0x01,0x02,0x01,0x03,0x01,0x02,0x01,0x05,0x01,0x02,0x01,0x03,0x01,0x02,0x01,0x04,0x01,0x02,0x01,0x03,0x01,0x02,0x01,0x06,0x01,0x02,0x01,0x03,0x01,0x02,0x01,0x04,0x01,0x02,0x01,0x03,0x01,0x02,0x01,0x05,0x01,0x02,0x01,0x03,0x01,0x02,0x01,0x04,0x01,0x02,0x01,0x03,0x01,0x02,0x01,0x08,0x01,0x02,0x01,0x03,0x01,0x02,0x01,0x04,0x01,0x02,0x01,0x03,0x01,0x02,0x01,0x05,0x01,0x02,0x01,0x03,0x01,0x02,0x01,0x04,0x01,0x02,0x01,0x03,0x01,0x02,0x01,0x06,0x01,0x02,0x01,0x03,0x01,0x02,0x01,0x04,0x01,0x02,0x01,0x03,0x01,0x02,0x01,0x05,0x01,0x02,0x01,0x03,0x01,0x02,0x01,0x04,0x01,0x02,0x01,0x03,0x01,0x02,0x01,0x07,0x01,0x02,0x01,0x03,0x01,0x02,0x01,0x04,0x01,0x02,0x01,0x03,0x01,0x02,0x01,0x05,0x01,0x02,0x01,0x03,0x01,0x02,0x01,0x04,0x01,0x02,0x01,0x03,0x01,0x02,0x01,0x06,0x01,0x02,0x01,0x03,0x01,0x02,0x01,0x04,0x01,0x02,0x01,0x03,0x01,0x02,0x01,0x05,0x01,0x02,0x01,0x03,0x01,0x02,0x01,0x04,0x01,0x02,0x01,0x03,0x01,0x02,0x01,0x09,0x01,0x02,0x01,0x03,0x01,0x02,0x01,0x04,0x01,0x02,0x01,0x03,0x01,0x02,0x01,0x05,0x01,0x02,0x01,0x03,0x01,0x02,0x01,0x04,0x01,0x02,0x01,0x03,0x01,0x02,0x01,0x06,0x01,0x02,0x01,0x03,0x01,0x02,0x01,0x04,0x01,0x02,0x01,0x03,0x01,0x02,0x01,0x05,0x01,0x02,0x01,0x03,0x01,0x02,0x01,0x04,0x01,0x02,0x01,0x03,0x01,0x02,0x01,0x07,0x01,0x02,0x01,0x03,0x01,0x02,0x01,0x04,0x01,0x02,0x01,0x03,0x01,0x02,0x01,0x05,0x01,0x02,0x01,0x03,0x01,0x02,0x01,0x04,0x01,0x02,0x01,0x03,0x01,0x02,0x01,0x06,0x01,0x02,0x01,0x03,0x01,0x02,0x01,0x04,0x01,0x02,0x01,0x03,0x01,0x02,0x01,0x05,0x01,0x02,0x01,0x03,0x01,0x02,0x01,0x04,0x01,0x02,0x01,0x03,0x01,0x02,0x01,0x08,0x01,0x02,0x01,0x03,0x01,0x02,0x01,0x04,0x01,0x02,0x01,0x03,0x01,0x02,0x01,0x05,0x01,0x02,0x01,0x03,0x01,0x02,0x01,0x04,0x01,0x02,0x01,0x03,0x01,0x02,0x01,0x06,0x01,0x02,0x01,0x03,0x01,0x02,0x01,0x04,0x01,0x02,0x01,0x03,0x01,0x02,0x01,0x05,0x01,0x02,0x01,0x03,0x01,0x02,0x01,0x04,0x01,0x02,0x01,0x03,0x01,0x02,0x01,0x07,0x01,0x02,0x01,0x03,0x01,0x02,0x01,0x04,0x01,0x02,0x01,0x03,0x01,0x02,0x01,0x05,0x01,0x02,0x01,0x03,0x01,0x02,0x01,0x04,0x01,0x02,0x01,0x03,0x01,0x02,0x01,0x06,0x01,0x02,0x01,0x03,0x01,0x02,0x01,0x04,0x01,0x02,0x01,0x03,0x01,0x02,0x01,0x05,0x01,0x02,0x01,0x03,0x01,0x02,0x01,0x04,0x01,0x02,0x01,0x03,0x01,0x02,0x01]
status = [1]*10 # 当状态为1的时候是值大于100时 这时候需要选择0改变状态
status[1] = 0 # 当状态为0时时值小于100时 这时候需要选择1
# 根据使用表中的数据定位到的状态来输出值
for i in range(len(src)):
if status[10-src[i]] == 1:
status[10-src[i]] = 0
print("0",end="")
else:
status[10-src[i]] = 1
print("1",end="")
print("最后的状态是:")
print(status)
0010001101100011001000110110011100100111011000110010001101100111001000110110001100100111011001110010011101100011001001110110011100100011011000110010011101100111001001110110001100100011011001110010001101100011001000110110011100100111011000110010011101100111001000110110001100100011011001110010011101100011001000110110011100100011011000110010001101100111001001110110001100100111011001110010001101100011001001110110011100100111011000110010001101100111001000110110001100100111011001110010011101100011001001110110011100100011011000110010001101100111001001110110001100100011011001110010001101100011001000110110011100100111011000110010011101100111001000110110001100100111011001110010011101100011001000110110011100100011011000110010001101100111001001110110001100100111011001110010001101100011001000110110011100100111011000110010001101100111001000110110001100100
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0]

因为复现的时候环境关了,但是通过上面最后status都是0,达到了我们的最后目的可以知道这个路线是正确的

网上最后得到的flag是:*CTF{4ncient_G4me_Fr0m_4ncient_Ch1na!}