转载自风之轻语

隐写术

概述

STEGA即隐写术,将信息隐藏在多种载体中,如:视频、硬盘和图像,将需要隐藏的信息通过特殊的方式嵌入到载体中,而又不损害载体原来信息的表达。旨在保护需要隐藏的信息不被他人识别。信息隐蔽技术有:

  1. 隐写术
  2. 数字水印
  3. 隐蔽信道
  4. 阀下信道
  5. 匿名信道

文件格式及文件头

图片类

文件类型 后缀 文件头 文件尾 标志
JPEG .jpg/.jpeg FFD8FF FFD9 JFIF
PNG .png 89504E47 AE426082 PNG IEND IHDR
GIF .gif 47494638 003B GIT9a
TIFF .tif/.tiff 49492A00 4D4D2A00 - II MM

音频类

文件类型 后缀 文件头 文件尾 标志
WAVE .wav 52494646 - RIFF

压缩文件类

文件类型 后缀 文件头 文件尾 标志
ZIP Archive .zip 504B0304 - PK
RAR Archive .rar 52617221 - RAR!
7Z Archive .7z 377ABCAF - 7z

文本文档类

文件类型 后缀 文件头 文件尾 标志
XML .xml 3C3F786D6C - -
HTML .html 68746D6C3E - -
Adobe PDF .pdf 255044462D312E - %PDF %%EOF

图片隐写

  • 一、附加式的图片隐写
  • 二、基于文件结构的图片隐写
  • 三、基于LSB原理的图片隐写
  • 四、基于DCT域的JPG图片隐写
  • 五、数字水印的隐写
  • 六、图片容差的隐写

附加式图片隐写

拿到图片后打开,没有发现任何异常,猜测flag在图片数据中,使用kali自带的strings工具搜索字符串

可以看到flag出来了,或者使用winhex等可以查看文件数据的都可以拿到flag


文件的数据格式,是由文件头及具体的数据内容决定的,所以我们可以将两种格式的文件数据拼接,这样就达到了使用一个文件,存放n种文件的效果

图种:
一种采用特殊方式将图片文件(如jpg格式)与rar文件结合起来的文件。该文件一般保存为jpg格式,可以正常显示图片,当有人获取该图片后,可以修改文件的后缀名,将图片改为rar压缩文件,并得到其中的数据。
图种这是一种以图片文件为载体,通常为jpg格式的图片,然后将zip等压缩包文件附加在图片文件后面。因为操作系统识别的过程中是,从文件头标志,到文件的结束标志位,当系统识别到图片的结束标志位后,默认是不再继续识别的,所以我们在通常情况下只能看到它是只是一张图片。

这样一张图片,同样的,我们也无法通过图片表面去发现,这里说一下如何判断图片中是否隐藏有其他文件

隐藏文件检测
  • 通过文件大小判断:正常的图片大小不会很大,如果发现文件大小几M几十M,甚至几百M,基本可以判断其中有隐藏文件
  • 通过工具判断:可以使用binwalk一键检测或者winhex手动查看文件
隐藏文件分离

使用kali自带的binwalk工具检测分离

binwalk 文件名 #按照文件头检测文件

binwalk 文件名 -e #将检测到的文件提取出来

使用winhex手动分离

使用winhex打开,按照文件头查找,将非图片数据的内容选中,右键编辑,复制选块到新文件,保存为zip文件

基于文件结构的隐写

对于一个正常的PNG图片来讲,其文件头总是由固定的字节来表示的,以16进制表示即位 89 50 4E 47 0D 0A 1A 0A,这一部分称作文件头。
标准的PNG文件结构应包括:

  • PNG文件标志

  • PNG数据块

  • PNG图片是有两种数据块的,一个是叫关键数据块,另一种是辅助数据块。正常的关键数据块,定义了4种标准数据块,每个PNG文件都必须包含它们。
    它们分别是长度,数据块类型码,数据块数据,循环冗余检测即CRC。
    我们这里重点先了解一下,png图片文件头数据块以及png图片IDAT块,这次的隐写也是以这两个地方为基础的。
    png图片文件头数据块
    即IHDR,这是PNG图片的第一个数据块,一张PNG图片仅有一个IHDR数据块,它包含了哪些信息呢?IHDR中,包括了图片的宽,高,图像深度,颜色类型,压缩方法等等。

    https://public-cdn-oss.mosoteach.cn/mssvc/editor/00dcc5a41d9f6ae3663a4dcc76140361/2024/6/1d72a1fd7d07cb86f3f7c6a007d8398f.png如图中蓝色的部分即IHDR数据块。

    IDAT 数据块

    它存储实际的数据,在数据流中可包含多个连续顺序的图像数据块。这是一个可以存在多个数据块类型的数据块。它的作用就是存储着图像真正的数据。
    因为它是可以存在多个的,所以即使我们写入一个多余的IDAT也不会多大影响肉眼对图片的观察

高度被修改的隐写
png图片结构详解

开头的0-7八个字节为png的文件头:89 50 4E 47 0D 0A 1A 0A (固定格式)

8-11四个字节:00 00 00 0D 表示头部数据块的长度为13(固定格式)

12-15四个字节:49 48 44 52 表示文件头数据块的标示(固定格式)

16-19四个字节:00 00 03 84表示图片的宽(不固定)

20-23四个字节:00 00 00 96表示图片的高(不固定)

24-28五个字节:08 02 00 00 00表示Bit depth(图像深度)、ColorType(颜色类型)、 Compression method(压缩方法)、 Filter method(滤波器方法)、Interlace method(隔行扫描方法)这五个字节不固定,均为可变数据

29-32四个字节:76 EC 1E 40为图片的crc校验值由从第12个字节到第28个字节的十七位字节进行crc计算得到

解题

首先校验CRC的值是否正确,也就是查看图片有没有被修改过

使用在线校验网站:CRC校验,输入第12到第28共十七位字节,参数模型选择CRC32,点击计算,得到图片CRC校验值

若校验值与图片的CRC值不相等,则说明该图片crc校验有问题。一般是由于高度或者宽度所引起的,这里需要用到脚本对其宽高进行爆破。

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

crcbp = open("heigth.png", "rb").read() #打开图片
crc32frombp = int(crcbp[29:33].hex(),16) #读取图片中的CRC校验值
print(crc32frombp)

for i in range(4000): #宽度1-4000进行枚举
for j in range(4000): #高度1-4000进行枚举
data = crcbp[12:16] + \
struct.pack('>i', i)+struct.pack('>i', j)+crcbp[24:29]
crc32 = binascii.crc32(data) & 0xffffffff
# print(crc32)
if(crc32 == crc32frombp): #计算当图片大小为i:j时的CRC校验值,与图片中的CRC比较,当相同,则图片大小已经确定
print(i, j)
print('hex:', hex(i), hex(j))
exit(0)
隐写信息以IDAT块加入图片

我们可以用 pngcheck -v [文件名] 去查看PNG文件数据块信息,然后利用 python zlib 解压多余IDAT块的内容,此时注意剔除长度、数据块类型及末尾的CRC校验值。

1
2
3
4
5
import zlib
import binascii
IDAT = " ".decode('hex') #双引号中填IDAT数据
result = binascii.hexlify(zlib.decompress(IDAT))
print(result)

LSB隐写

LSB,最低有效位,英文是Least Significant Bit 。我们知道图像像素一般是由RGB三原色(即红绿蓝)组成的,每一种颜色占用8位,0x00~0xFF,即一共有256种颜色,一共包含了256的3次方的颜色,颜色太多,而人的肉眼能区分的只有其中一小部分,这导致了当我们修改RGB颜色分量种最低的二进制位的时候,我们的肉眼是区分不出来的。
Stegosolve介绍
CTF中,最常用来检测LSB隐写痕迹的工具是Stegsolve,这是一款可以对图片进行多种操作的工具,包括对图片进行xor,sub等操作,对图片不同通道进行查看等功能。

我们使用Stegosolve工具来进行解题,在工具中打开文件,点击箭头切换通道

发现图片分别在红0绿0蓝0通道中发现异常

接着我们查看该通道的信息

勾选上红绿蓝的异常通道后,点击预览,发现是个png格式的数据,点击保存导出为png,扫描导出的二维码图片发现flag

JPG及其他图片隐写

JPG隐写

jpg方面的隐写,有F5隐写,Outguess算法隐写,盲水印攻击等隐写

可以使用Stegdetect工具解题

Stegdetect的指令介绍

1
2
3
4
5
6
7
8
9
10
11
12
-q 仅显示可能包含隐藏内容的图像。
-n 启用检查JPEG文件头功能,以降低误报率。如果启用,所有带有批注区域的文件将被视为没有被嵌入信息。如果JPEG文件的JFIF标识符中的版本号不是1.1,则禁用OutGuess检测。
-s 修改检测算法的敏感度,该值的默认值为1。检测结果的匹配度与检测算法的敏感度成正比,算法敏感度的值越大,检测出的可疑文件包含敏感信息的可能性越大。
-d 打印带行号的调试信息。
-t 设置要检测哪些隐写工具(默认检测jopi),可设置的选项如下:
j 检测图像中的信息是否是用jsteg嵌入的。
o 检测图像中的信息是否是用outguess嵌入的。
p 检测图像中的信息是否是用jphide嵌入的。
i 检测图像中的信息是否是用invisible secrets嵌入的。
-V 显示软件版本号。
如果检测结果显示该文件可能包含隐藏信息,那么Stegdetect会在检测结果后面使用1~3颗星来标识
隐藏信息存在的可能性大小,3颗星表示隐藏信息存在的可能性最大。
GIF隐写

gif图的隐写,因为gif是动态图,所以可以像视频一样,在其中一帧中隐藏信息,或其中的帧按规律进行信息隐藏

可以使用Stegosolve查看每一帧或者导出

文档隐写

word文档

  1. 隐藏文字:word中可以将输入的文字隐藏,可以通过”文件”-“选项”-“显示”,将隐藏文字勾选,这样就会显示隐藏文字
  2. 与背景色相同:与背景色相同的文字可以选中,但是无法查看,只需要全选,然后更改文字颜色就可以了,或者Ctrl+F搜索关键字
  3. 嵌入图片:word中插入的图片分为嵌入式和非嵌入式,区别在嵌入式会跟着文本的位置产生移动,即有回车后,图片下移。但非嵌入的不会跟着文本走,即有回车后,图片保持原位置不动。可以将word文档后缀改为zip解压查看,或者使用binwalk、foremost等工具分离查看
  4. xml文件隐藏:word文档可以转为xml文件,只需要把后缀改为zip解压,解压后的文件中,可能藏有flag信息

Excel表格

excel文件和word隐藏的方式差不多,需要注意的是,在Excel中,一个工作簿可以有多个工作表,不同的表里可能会有不同的信息

NTFS数据流隐写

NTFS这里不过多介绍,只需要清楚,它的创建和打开方式是宿主:寄宿文件

比如,这里有一个txt文件,打开之后里面的内容为空,但是占用空间为22M,这很不正常

001.jpg

其实是我用NTFS数据流的方式放了一本小说在里面

如何查看

这么多文件,我怎么知道它是不是可疑的文件,这里就要查看一下有哪些文件是有NTFS数据流的,可以使用以下命令dir /r

如果有数据流文件的话,会在文件的后面显示**:NTFS:$DATA**,第一个冒号后面,表示的是数据流的文件名,第二个冒号后面表示是数据流

使用NtfsStreamsEditor工具可以搜索NTFS数据流文件

如何创建
  1. 使用命令创建:我们可以使用echo或者type等命令将数据写入到其中
1
echo 你好 >hello.txt:world	#将“你好”这句话写入hello.txt中的world数据流
  1. 使用工具创建:使用NtfsStreamsEditor工具中的“编辑”来写入
如何打开
  1. 使用记事本打开:正常使用记事本打开肯定是看不到的,我们需要使用命令行来打开
1
notepad 宿主:寄宿文件
  1. 使用NtfsStreamsEditor打开

零宽字符隐写

零宽度字符是一些不可见的,不可打印的字符。它们存在于页面中主要用于调整字符的显示格式。

常见的零宽度字符及它们的unicode码和原本用途:

零宽度空格符 (zero-width space) U+200B : 用于较长单词的换行分隔
零宽度非断空格符 (zero-width no-break space) U+FEFF : 用于阻止特定位置的换行分隔
零宽度连字符 (zero-width joiner) U+200D : 用于阿拉伯文与印度语系等文字中,使不会发生连字的字符间产生连字效果
零宽度断字符 (zero-width non-joiner) U+200C : 用于阿拉伯文,德文,印度语系等文字中,阻止会发生连字的字符间的连字效果
左至右符 (left-to-right mark) U+200E : 用于在混合文字方向的多种语言文本中(例:混合左至右书写的英语与右至左书写的希伯来语),规定排版文字书写方向为左至右
右至左符 (right-to-left mark) U+200F : 用于在混合文字方向的多种语言文本中,规定排版文字书写方向为右至左

怎么实现的怎么实现的?

首先,输入需要被加密的内容将被转换为其二进制形式,然后该二进制将被转换为一系列表示每个二进制数字的零宽度字符。然后可以将零宽度的字符串不可见地插入正常文本中。如果将文本粘贴在其他地方,则可以提取零宽度的字符串,然后反向进行操作以找出被加密的内容!

具体加密过程

  1. 将需加密的内容转换为二进制
    只是将每个字符转换为其等效的二进制
1
2
3
4
5
const zeroPad = num => ‘00000000’.slice(String(num).length) + num;
const textToBinary = username => (
username.split('').map(char =>
zeroPad(char.charCodeAt(0).toString(2))).join(' ')
);
  1. 将二进制转换为0宽字符
    它将遍历二进制字符串,并将每个1转换为0宽度空间,将每个0转换为零宽非连接符。转换字母后,我们将插入0宽连接符,然后再下一个。
1
2
3
4
5
6
7
8
9
10
11
const binaryToZeroWidth = binary => (
binary.split('').map((binaryNum) => {
const num = parseInt(binaryNum, 10);
if (num === 1) {
return '​'; // zero-width space
} else if (num === 0) {
return '‌'; // zero-width non-joiner
}
return '‍'; // zero-width joiner
}).join('') // zero-width no-break space
);
  1. 插入正常文本中

需要注意的是加密与解密所使用的字典必须一致,也就是说,在哪儿进行加密的,就要在哪儿解密。

Base64隐写

前言

在了解base64隐写之前,我们先来了解一下base64编码。

base64编码是现在网络上最常见的用于传输8Bit字节码的编码方式之一。在最初有的网络并不支持所有的字节,比如某些系统只支持可见字符的传送,比如图片二进制流的每个字节不可能全部是可见字符,所以就传送不了。所以由于不能传送ASCII的控制字符,它的用途就受到了很大的限制。

而Base64旨在通过一种方法,把所有的字符都用64个特定的可打印字符表示来实现传送。选定的64个字符如下:

索引 对应字符 索引 对应字符 索引 对应字符 索引 对应字符
0 A 17 R 34 i 51 z
1 B 18 S 35 j 52 0
2 C 19 T 36 k 53 1
3 D 20 U 37 l 54 2
4 E 21 V 38 m 55 3
5 F 22 W 39 n 56 4
6 G 23 X 40 o 57 5
7 H 24 Y 41 p 58 6
8 I 25 Z 42 q 59 7
9 J 26 a 43 r 60 8
10 K 27 b 44 s 61 9
11 L 28 c 45 t 62 +
12 M 29 d 46 u 63 /
13 N 30 e 47 v
14 O 31 f 48 w
15 P 32 g 49 x
16 Q 33 h 50 y
加密过程

由于只用到了64个字符,所以使用6个二进制位(2^6 = 64)完全可以把所有的字符表示出来,于是原来的1个字节8位在base64编码中变成了1个字节6位。

换言之:把原本的3个字节变成现在的4个字节,因为(3*8 == 4*6)

所以在加密的时候首先写出原字符ASCII码对应的二进制数字,每个字符都可以得到一个8位的01串,再把该01串重新按照每6位一组划分即可得到一个新的数字,对照上图给出的表格可以得到一个新的字符。

但是这里有一个问题了:如果明文的字节数刚好是3的倍数那没有问题,按照6位一组划分肯定是刚刚好的;但是如果明文的字节数不是3的倍数,那按照6位一组划分不是就有剩余了吗?具体可见下图:

这样是没有剩余的:

而这样是有剩余的:

而base64给出的解决方法是在二进制数串后面加0,一直到二进制数串变成8和6的公倍数,然后把只有0的字节编码成”=”,如下图:

所以LoV3被编码成了TG9WMw== ,同时base64编码后面最多只可能出现2个==

解密过程

解密的过程可视作加密的逆过程

由于”=”是最后为了补齐填充的,所以解密的时候首先把”=”删去,然后写出二进制数串,然后从左往右每8位一组,剩余的不足8位丢掉,然后根据转换表获得相应字符:

以上图为例,TG9WMw==首先变成了TG9WMw,对照上图的表写出来二进制数串:

然后每8位一组,剩余不足的丢弃:

所以这里牵涉到了一个地方,由上面的过程我们可以看成,TG9WMw在解密回LoV3的时候,按每8位一组剩余的丢弃来算,最后的110000中的0000是没有用到的。所以换句话说,这剩下的4位无论是0000还是1111,都是要被丢弃的,所以这就提供了一个可以隐藏信息的地方:

TG9WMx==解密后依然是LoV3,但已经隐藏进了一个1进去,那么这就是base64隐写

那么,在平时我们如何判断有没有信息被隐藏进去了呢

最简单的就是你是否隐藏信息,解密得到的明文是不变的,那么你重新按照正确的加密流程计算一遍,如果发现结果不一样,那么就说明隐藏进了信息。

一般CTF题目中出现一大堆base64编码字符串的时候,更需要考虑base64隐写

这里附上一个base64提取隐藏信息的脚本:

1
2
3
4
5
6
7
8
9
10
11
b64chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'
with open('flag_encode.txt', 'rb') as f:
bin_str = ''
for line in f.readlines():
stegb64 = ''.join(line.split())
rowb64 = ''.join(stegb64.decode('base64').encode('base64').split())
offset = abs(b64chars.index(stegb64.replace('=','')[-1])-b64chars.index(rowb64.replace('=','')[-1]))
equalnum = stegb64.count('=')
if equalnum:
bin_str += bin(offset)[2:].zfill(equalnum * 2)
print ''.join([chr(int(bin_str[i:i + 8], 2)) for i in xrange(0, len(bin_str), 8)])

其他文件隐写

压缩包

一、zip

CTF中的压缩包隐写一般有这样几个套路

1、通过编码转换隐藏信息(common)
比如给出一堆字符或数字,仔细观察为某种进制,将其解码为十六进制,观察其文件头是压缩包或者是其他格式,修改后缀名后解压得flag

2、在文件中隐藏压缩包(图种)
在CTF压缩包隐写中最为常见,多用于在一个文件中隐藏一个压缩包

原理:以jpg格式为例,完整的JPG由FF D8开头,FF D9结束,图片浏览器会忽略FF D9之后的内容,因此可以在JPG文件之后加入其他的文件。

利用foremost,dd或者直接将其修改为压缩包后缀进行提取。
推荐使用foremost,因为foremost还可以分离其他隐藏的文件。
修改为ZIP文件虽然方法简单,但是如果隐写了多个文件时可能会失败。
以前不知道foremost的时候一直是用dd分离的,后边知道了foremost就一直用的foremost。

3、伪加密
原理:ZIP伪加密是在文件头的加密标志位进行修改,进而再次打开文件时被识别为加密压缩包。

ZIP文件主要由三个部分组成:压缩源文件数据区 + 核心目录 + 目录结束标志

压缩源文件数据区

local file header + file data + data descriptor

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
local file header:文件头用于标识该文件的开始,记录了该压缩文件的信息,
这里的文件头标识由固定值 50 4B 03 04 开头,也是 ZIP 的文件头的重要标志。

file data:文件数据记录了相应压缩文件的数据。

data descriptor:数据描述符用于标识该文件压缩结束,
该结构只有在相应的 local file header 中通用标记字段的第 3 bit设为 1 时才会出现,
紧接在压缩文件源数据后。
12345678
50 4B 03 04:这是头文件标记(0x04034b50)
14 00:解压文件所需 pkware 版本
00 00:全局方式位标记(有无加密)
08 00:压缩方式
5A 7E:最后修改文件时间
F7 46:最后修改文件日期
16 B5 80 14:CRC-32校验(1480B516)
19 00 00 00:压缩后尺寸(25)
17 00 00 00:未压缩尺寸(23)
07 00:文件名长度
00 00:扩展记录长度
1234567891011

6B65792E7478740BCECC750E71ABCE48CDC9C95728CECC2DC849AD284DAD0500 (直到核心目录文件头标识)

压缩源文件目录区

记录了压缩文件的目录信息,在这个数据区中每一条纪录对应在压缩源文件数据区中的一条数据。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
50 4B 01 02:目录中文件文件头标记(0x02014b50)  
3F 00:压缩使用的 pkware 版本
14 00:解压文件所需 pkware 版本
00 00:全局方式位标记(有无加密,这个更改这里进行伪加密,改为09 00打开就会提示有密码了)
08 00:压缩方式
5A 7E:最后修改文件时间
F7 46:最后修改文件日期

16 B5 80 14:CRC-32校验(1480B516)
19 00 00 00:压缩后尺寸(25)
17 00 00 00:未压缩尺寸(23)
07 00:文件名长度
24 00:扩展字段长度
00 00:文件注释长度
00 00:磁盘开始号
00 00:内部文件属性
20 00 00 00:外部文件属性
00 00 00 00:局部头部偏移量
6B65792E7478740A00200000000000010018006558F04A1CC5D001BDEBDD3B1CC5D001BDEBDD3B1CC5D001 (直到目录结束标识头)
12345678910111213141516171819


目录结束标识

存在于整个归档包的结尾,用于标记压缩的目录数据的结束。每个压缩文件必须有且只有一个结束标识。

1
2
3
4
5
6
7
8
9
50 4B 05 06:目录结束标记  
00 00:当前磁盘编号
00 00:目录区开始磁盘编号
01 00:本磁盘上纪录总数
01 00:目录区中纪录总数
59 00 00 00:目录区尺寸大小
3E 00 00 00:目录区对第一张磁盘的偏移量
00 00:ZIP 文件注释长度
12345678

zip伪加密

zip伪加密是在文件头的加密标志位做修改,进而再打开文件时识被别为加密压缩包。

如果把第二个加密标记位的00 00改为09 00,打开就会提示有密码:

其实改成09只是举的一个例子,只要末位是奇数,就代表加密,反之,末位是偶数代表未加密。
有时这里是01,也代表加密!不用更改!

识别真假加密

无加密
压缩源文件数据区的全局加密应当为00 00
且压缩源文件目录区的全局方式位标记应当为00 00

假加密
压缩源文件数据区的全局加密应当为00 00
且压缩源文件目录区的全局方式位标记应当为09 00

真加密
压缩源文件数据区的全局加密应当为09 00
且压缩源文件目录区的全局方式位标记应当为09 00

二、RAR

文件格式

RAR 文件主要由标记块,压缩文件头块,文件头块,结尾块组成。

其每一块大致分为以下几个字段:

RAR压缩包的文件头为:52 61 72 21 1A 07 00

其后是标记块(MARK_HEAD),还有文件头(FILE_HEAD)。

主要攻击方式

1、爆破

利用linux下的rarcrack(http://rarcrack.sourceforge.net/)

2、伪加密

RAR 文件的伪加密在文件头中的位标记字段上,用 010 Editor 可以很清楚的看见这一位,修改这一位可以造成伪加密。

3、其他如明文攻击等方法与ZIP相同。

音频

摩斯密码

播放音频文件,可以听到有规律的长短音,使用Audacity工具打开,可以看到波形图,使用0和1或者·和-记录下来

放到相关工具或在线解码网站解密

使用python脚本解密

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
import argparse
CODE = {
".-": "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",

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

".-.-.-": ".", "---...": ":", "--..--": ",", "-.-.-.": ";", "..--..": "?",
"-...-": "=", ".----.": "'", "-..-.": "/", "-.-.--": "!", "-....-": "-",
"..--.-": "_", ".-..-.": '"', "-.--.": "(", "-.--.-": ")", "...-..-": "$",
".-...": "&", ".--.-.": "@", ".-.-.": "+",
} #摩斯密码字典

#编码模块
def morse_encode(n):
#n = input("[+]要编码的明文为: ").upper()
inverted_code = {value:key for key,value in CODE.items()}
codelist = []
keylist = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789.:,;?='/!-_\"()$&@+"
#keylist = []
#for i in inverted_code.keys():
# keylist.append(i)
for i in n:
codelist.append(str(inverted_code.get(i)) + " ")
for i in codelist:
if i == 'None ':
print("Error!仅支持如下字符:\n" + ''.join(keylist))
exit()
else:
continue
print("[=]编码结果为:\n" + ''.join(codelist))

#解码模块
def morse_decode(n):
#n = input("[+]要解码的密文为: ")
codelist = []
spl = input(r"[+]分隔符" + '("/"或空格)' + "为:")
if len(spl) == 1:
if ord(spl) == 32 or ord(spl) == 47:
if spl not in n:
print("[x]请检查分隔符是否正确!")
print("[!]如果只翻译一个字符请在最后加空格,并将分隔符设为空格!")
exit()
else:
s = str(n).split(spl)
for i in s:
codelist.append(str(CODE.get(i)))
if codelist[0] == str(None):
print("[x]请检查分隔符或密文是否正确")
exit()
else:
if ord(n[-1]) == 32:
codelist.pop()
print("[=]解码结果为:" + ''.join(codelist))
else:
print("[x]请输入"' / '"或 空格!")
exit()
elif len(spl) == 0:
print("[x]请输入分隔符!")
print("[!]如果只翻译一个字符请在最后加空格,并将分隔符设为空格!")
exit()
elif len(spl) > 1:
print("[x]请输入一位分隔符!")
exit()

#cmd调用函数
def morsecode(encode, decode, text):
if encode:
if text:
n = text.upper()
print("[+]要编码的明文为:" + n)
morse_encode(n)
else:
morse_encode(input("[+]要编码的明文为: ").upper())
elif decode:
if text:
n = text
print("[+]要解码的密文为:" + n)
morse_decode(n)
else:
morse_decode(input("[+]要解码的密文为:"))

#运用argparse方法实现cmd调用
paser = argparse.ArgumentParser(description='morsecode e&d')
paser.add_argument('--encode', '-e',nargs='?', const=1, help='调用encode模块')
paser.add_argument('--decode', '-d',nargs='?', const=1, help='调用decode模块')
paser.add_argument('--text', '-t',nargs='?', help='传入明文或密文,请用双引号框选字符串')
args = paser.parse_args()

morsecode(args.encode, args.decode, args.text)
频谱图

将音频频谱放大或缩小,直到出现有效信息

001_蓝山看图王.jpg