前言
近期在 YouTube 频道《脑洞乌托邦》观看了 2 个视频,分别是:
在上文中所列出的 2 支视频讲述的内容与本文主题相关联,也是促使我编写这篇文章的原因所在。如果您不了解 Ash Vlogs 事件始末,建议您先花一些时间观看上文所列出的视频内容。
如果您在中国大陆地区,视频将通过 Bilibili 播放。
分析
本文假定您已了解 Ash Vlogs 事件始末。在下文中,我将对 Ash Vlogs 事件频道《i_know_where_she_is》首页和最后一支视频中所出现的二维码进行深入分析。
二维码
通过访问 Ash Vlogs 事件频道《i_know_where_she_is》的首页,我们可以清晰看到在首页横幅和最后一支被上传的视频的预览缩略图中都存在一张二维码:
解码
为便于进一步深入分析,我们需要采用相对于直接使用移动设备扫码方式更加安全且易于跟踪的方式对这张二维码进行解码,这里我通过 Python 交互式 Shell 使用 Zbar 库实现:
>>> from PIL import Image
>>> from pyzbar import pyzbar
>>> pyzbar.decode(Image.open('./1.png'))
其中,文件 1.png
是位于当前 Python 交互式 Shell 工作目录下与上文图中所示一致的二维码图像。上文 Python 交互式 Shell 运行结果:
[Decoded(data=b'https://tinyurl.com/y6yzvt3p', type='QRCODE', rect=Rect(left=6, top=6, width=189, height=190), polygon=[Point(x=6, y=6), Point(x=6, y=196), Point(x=195, y=196), Point(x=195, y=7)])]
当然,另一种选择是使用互联网上提供在线二维码解码工具的网站。
解码后的链接
在上文中我们将二维码解码后得到一串被编码在二维码中的 URL 链接,根据经验这似乎是一个短链接。当我们访问短链接时,短链接服务提供商会将我们重定向到真正的 URL 链接。接着尝试从短链接中剥离出域名
tinyurl.com
并访问它,以验证我的猜测:
从上图中不难看出这的确是一家短链接服务提供商,被编码到二维码中的 URL 链接也的确时一个短链接。
获得真正的链接
短链接在表面上会隐藏真正的 URL 链接,在下文中我将通过 Microsoft Windows PowerShell 尝试请求它以得到短链接服务提供商所重定向的真正 URL 链接:
(Invoke-WebRequest -Method HEAD "https://tinyurl.com/y6yzvt3p" -MaximumRedirection 0 -ErrorAction Ignore).Headers.Location
以上 PowerShell 命令运行后输出:
https://drive.google.com/drive/folders/15dyKeivBi1vRdybkcRYgkokxlyNy6GCC?usp=sharing
当然,另一种选择是使用 Web 浏览器直接访问短链接,通常情况下浏览器将自动跳转到真正的 URL 链接。
可执行文件
在上文中,我们通过解码二维码获得一个短链接,又通过 PowerShell 请求短链接获得真正的 URL 链接。从 URL 链接中可以得知这是一个 Google Drive 分享链接:
从上图中我们可以看到,一位名为 i_know_where_she_is
的 Google 用户在 Google Drive 分享了一个名为 c2f
的文件夹,其中包含一个名为 click.zip
的压缩包文件。
接着,我们从 Google Drive 上下载名为 click.zip
的压缩包文件,然后将其解压缩:
基本信息
解压缩后得到 2 个文件,其静态扫描基本信息如下:
文件名 | 类型 | 数字签名 | SHA1 |
---|---|---|---|
click.exe | PE32+ executable (console) x86-64, for MS Windows | 没有 | 3B32300D21B015A0555133362CC6BA921A9C4BF4 |
drip.ico | MS Windows icon resource - 1 icon, 150x150, 32 bits/pixel | 没有 | B5C4A0E361185D8010CD11809C1FAF6F45766AB2 |
Tips: 静态扫描有助于确定目的文件的真实类型是否与其扩展名(后缀)相关,但通常情况下可能无法正确识别有意的文件类型伪装(例如包括但不限于:伪装文件头和结构、文件尾部追加数据等...)。
安全性
值得注意的是:经过分析,我在上文所列出的 2 个文件中没有找到任何恶意代码,均为干净文件。因此您可以放心地在您的 PC 设备上下载并运行它,不会给您的设备带来威胁。
您需要确信所下载并运行的文件与上文所列出的文件 SHA1 校验结果一致,否则安全性是未知的。
动态分析
通过对压缩包内文件的静态扫描分析得知,文件 click.exe
是一个 64
位二进制可执行文件(PE)。那该可执行文件运行生命周期内究竟进行了那些工作呢?别着急...现在我们将可执行文件 click.exe
放置在动态分析环境中运行,以分析其工作行为:
上图所示为可执行文件 click.exe
运行后截屏,从图中我们可以看到可执行文件 click.exe
依赖于命令提示符主机进程,因此推测该可执行文件zi子系统为 WINDOWS_CUI
。
...
从上图中我们可以看到,可执行文件 click.exe
实现了释放器功能,运行后在当前用户临时目录下释放了许多依赖文件。从其释放的文件中可以发现包括但不限于 CPython
解释器所提供的 Python 标准库二进制可执行文件(*.pyd
)、CPython 3.6 解释器核心二进制可执行文件(python36.dll
)和
Visual C 运行时库二进制可执行文件等。
通过上述行为,我们可以推测出可执行文件 click.exe
本质是一个 Python 源文件包装器(外壳),其将一个或多个 Python 源文件编译并包装为一个二进制可执行文件。
从上图中我们可以看到,可执行文件 click.exe
完成依赖文件释放工作后为自身创建了一个子进程,从子进程的行为不难看出其正在充当真正 Python 代码的加载引导和解释器角色。
除此之外,我们可以看到可执行文件 click.exe
试图与 Google Public DNS(IPv4: 8.8.8.8
)的 53 端口建立 TCP/IP
连接,猜测其正在试图检测当前运行环境是否具有良好的互联网连接。
由于外联网安全性等因素,这边企业内部的 Workspace 流量防火墙不允许工作网络内联网设备直接外联到公网 DNS 服务器进行 DNS 查询操作,因此与 Google Public DNS 建立 TCP/IP
连接的数据包会被丢弃,最终表现为连接超时。相信这也是可执行文件 click.exe
消息框提示“connect to internet in 10 Seconds or the
game ends”的原因所在。
当我们在动态分析环境中交互式点按“OK”按钮时,可执行文件 click.exe
会再次通过相同的方式检查互联网连接的有效性。如果依旧无法与 Google Public DNS 成功建立
TCP/IP 连接,将清理先前释放的临时文件并通过创建新的命令提示符进程并执行命令以删除自身:
从上图中命令提示符进程的命令行我们可以看到,其尝试执行 Ping 操作到本机 3 次并通过重定向 Stdout
流的形式将结果写入当前工作目录的 num
文件中,然后删除自身二进制可执行文件。Ping 每次操作之间会默认间隔 1 秒,我猜测这是为了使可执行文件 click.exe
有时间清理释放的临时文件,以免自我删除因进程实例未退出(本质上是该二进制可执行文件被其进程实例映射)而失败。
静态分析
在上文的动态分析中我们得知,可执行文件 click.exe
本质上是一个 Python 源文件包装器(外壳),其功能使用 Python 语言编写并依赖于 CPython 3.6
解释器实现。目前知名的将 Python 源文件编译为字节码并包装成 Windows 可执行文件的开源解决方案有:
- bbFreeze
- pyinstaller
- py2exe
- cx_Freeze
在下文中,我们将通过对二进制可执行文件 click.exe
进行静态分析,以试图确定它是否使用了以上任意一种开源解决方案。同时结合静态分析,也将帮助我们更全面地了解该可执行文件
click.exe
的功能实现。
Tips: 为便于分析时理解,我会将已分析确定实现功能的函数重命名为便于人类阅读的名称,也会为相关代码片段编写注释。这些重命名后的函数符号并非来自于可执行文件
click.exe
的调试符号表,不能替代其函数的真实符号名,仅供参考。
上图所示为可执行文件 click.exe
入口函数,该函数在 C 运行库完成初始化后被调用。入口函数的逻辑较为简单,其将当前自身进程实例的命令行参数由宽字符转换为 UTF-8
编码的多字节数据后跳转到另一个负责进行初始化的函数,我们暂且将其称之为 __bootloader_main
。
从上文中我们得知,可执行文件 click.exe
的功能是使用 Python 编写的,因此此处转换命令行参数编码的用意猜测是为了将其传递给 CPython 3.6
解释器,以便于真正实现功能的 Python 代码正常获取命令行。
如上图所示,可执行文件 click.exe
的 __bootloader_main
函数主要尝试分配一块用于存储 Archive
状态信息(元数据)的内存;然后尝试获取当前进程实例中名为 _MEIPASS2
的环境变量值,无论上一步是否成功取得有效值均尝试从当前进程实例删除一个名为
_MEIPASS2
的环境变量;最后尝试调用另一个函数从当前可执行文件获取与 Archive 相关的元数据,暂且将其称之为
__initialize_archive
。上述逻辑任何一步失败均会导致程序退出整个流程。
那 Archive 究竟是什么呢?大家可能已经猜到,Archive 中存储了真正实现功能的 Python 字节码、CPython 3.6 解释器和依赖库。
我们通过解析可执行文件 click.exe
的区段信息可以看到其尾部拥有额外的扩展数据:
将尾部的扩展数据段转储后可以发现文件大小高达 48.7 MB,这便是 Archive。
如上图所示,函数 __initialize_archive
主要实现了从自身可执行文件的扩展数据段中解析读取 Archive 元数据到内存的功能,其元数据包括但不限于 Archive
实际长度、文件列表(TOC)、文件列表长度(TOCLEN)、Python 解释器版本和 Python 解释器动态链接库名称等。接着,我们回到函数 __bootloader_main
调用 __initialize_archive
后的逻辑:
所以环境变量 MEIPASS2
为什么存储 Archive 临时文件释放目录呢?别着急...我们接着往下看:
还记得在上文中提及可执行文件 click.exe
会创建自身的子进程吗?如上图所示,如果之前成功获取到环境变量 MEIPASS2
的值,则认为当前进程是子进程,进而调用一个函数以进行加载并初始化 CPython 3.6 解释器等相关工作;否则,当前进程是父进程,进而调用另一个函数创建自身的子进程。
这里大家可能会有疑惑,环境变量 MEIPASS2
是在父进程设置的,仅局限于父进程实例本身,为何子进程有可能成功获取呢?这是因为子进程实例在创建时会继承父进程的环境变量和值。
上图所示为函数 __bootstrap
所实现的逻辑,其主要功能是加载 CPython 3.6 解释器的动态链接库 python36.dll
到当前进程空间;然后创建一个 Python VM 实例并初始化一些参数;接着从之前加载的 Archive 元数据中找出 TOC 并遍历每条记录,将记录对应的数据段从可执行文件的附加数据段中读入,并使用
zlib
库解压后通过 Python 标准库 marshal
将其反序列化为 Python 模块对象后在 Python VM 中执行,大致流程如下:
以上便是函数 __bootstrap
的运行逻辑,让我们再来看看函数 __create_child_process
:
综上所述,我们可以得知可执行文件 click.exe
的启退流程如下:
当然,以上启退流程示意图中不包括 click.exe
所执行的 Python 代码的启动和退出逻辑。所以可执行文件 click.exe
是否使用了开源解决方案将 Python 源代码编译并包装为 Windows 可执行文件(PE)呢?使用了哪一种呢?答案在 Bootloader 流程。
从上文已有的静态分析结果中我们可以看到在 Bootloader 流程中,函数 __bootloader_main
在内部调用函数
__initialize_archive
实现对 Archive 元数据的解析操作,而背后真正实现解析功能的是函数 __open_archive
。
值得注意的是,在函数 __open_archive
内部通过调用函数 __get_cookie_for_archive
实现在当前可执行文件的附加数据段中定位
Archive 元数据偏移量并将其读入到事先已分配的缓冲区。
函数 __get_cookie_for_archive
在内部通过一段固定的特征码(Magic)来定位 Archive 元数据:
Archive Magic: 4D 45 49 0C 0B 0A 0B 0E
该特征码(Magic)属于知名开源解决方案 pyinstaller,这意味着可执行文件 click.exe
是使用 pyinstaller 将 Python 源代码编译为字节码并包装为
Windows 可执行文件(PE)的。
而接下来我们需要做的是,根据上文中静态分析所得出的 pyinstaller 包装方式和数据结构从可执行文件 click.exe
中提取真正实现其功能逻辑的 Python
源文件的字节码文件(*.pyc
)并进一步静态分析:
如上图所示,可执行文件 click.exe
的 Python 功能逻辑(在下文简称为“主模块”)开始时向当前运行环境导入了一些依赖的标准库模块和外部模块,这些模块分别是:
名称 | 来源 | 备注 |
---|---|---|
socket | 标准库 | 通常情况下用于网络通信 |
time | 标准库 | ... |
sys | 标准库 | ... |
threading | 标准库 | 通常情况下用于创建和管理超线程 |
tkinter | 标准库 | Python GUI 软件包 |
subprocess | 标准库 | 通常情况下用于创建和管理子进程 |
readchar | 外部依赖 | 通常情况下用于读取单个字符 |
cv2 | 外部依赖 | OpenCV 软件包,通常用于计算机视觉处理 |
从上文中可以看到其从标准库模块 tkinter
中导入了子模块
messagebox
,相信大家从模块名中不难看出该模块主要的功能是实现绘制一个可交互的消息对话框。
在向当前运行环境导入所依赖的模块后,主模块创建(定义)了多个函数和变量。为了不打乱静态分析顺序,我们直接跳转到主模块所执行的功能代码处:
首先主模块实例化类 tk.Tk
,并将其实例赋值给变量 root
,现在变量 root
代表主模块所创建的主对话框窗口;接着调用其实例方法 withdraw
将主窗口绘制推迟;然后其实例方法 iconbitmap
将可执行文件
click.exe
进程实例工作目录下的图标文件 drip.ico
设置为主对话框窗口的图标。
如上图所示,主模块试图调用其函数 internetCheck
来检查当前运行环境是否可以正确连接到互联网,如果成功则继续执行,否则弹出消息框恐吓用户连接到互联网。在恐吓用户的消息框被关闭后主模块将自行挂起 10 秒钟,之后再次调用函数
internetCheck
检查互联网连接,如果成功则继续运行,否则退出自身。
还记得我们在动态分析时所遇到的消息框“connect to internet in 10 Seconds or the game ends”吗?没错,就是位于上文所提及的逻辑。让我们进入主模块的函数
internetCheck
一探究竟:
从主模块函数 internetCheck
的运行逻辑和主模块对其的调用逻辑可以看出,其主要尝试与远程主机 8.8.8.8
的端口
53
(也即 Google Public DNS)建立 TCP/IP 连接以检查互联网连接是否可用。
接着我们回到主模块调用其函数 internetCheck
之后,从上图我们可以看到其又分别调用了主模块函数
storagePerm
、cameraPerm
、micPerm
和
locPerm
。这几个函数的运行逻辑较为简单,主要就是弹出一系列访问当前存储介质、摄像头、麦克风和物理位置等信息的恐吓消息框。如果用户同意则弹出消息框
good choice
,否则调用主模块函数 exitt
退出自身进程(该函数在下文会进行分析)。当然,值得注意的是主模块的确会访问使用当前设备已连接的摄像头设备。让我们继续往下看:
让我们一起看一下主模块函数 wrongAttempt
:
如上图所示,主模块会输出一些问题并从控制台(Stream:
Stdin
)接受用户输入,然后将每一个问题的用户输入内容赋值给一个变量。之后在循环中检查用户输入是否与预设的一个或多个输入相匹配,如果匹配则跳出循环继续运行,否则调用主模块函数
wrongAttempt
后重复这一过程。
主模块函数 wrongAttempt
主要实现了将全局变量 Attempts
的值减 1,然后弹出消息框警告用户还剩余几次机会。如果全局变量
Attempts
的值为 0,则调用主模块函数 exitt
退出自身进程。
接着要求用户输入一个密钥,如果密钥与预设值匹配则继续运行,否则退出自身进程:
如果密钥与预设值匹配,则循环连续打印 400 次 good drip.
:
然后便是重复询问前 3 个问题。与之前不同的是,第三个问题将需要正确回答 4 次:
值得注意的是,主模块此处并未验证用户输入的答案内容是否与问题预期答案一致,也即输入的字符个数相等即可通过验证并继续运行。
然后主模块会打印输出一个 true
。由于篇幅长度问题且接下来提问逻辑几乎一致,我将直接列出与问题对应的预设答案供大家参考:
问题 | 预设答案 |
---|---|
k2 | y%9hL_*q@eAWS5sd |
would you like to know | yes 或 y |
would you like to see | yes 或 y |
k3 | xG%rDL4_$2$WHhex |
is it okay to want to die | yes 或 y |
is it okay to see death | yes 或 y |
is it okay to kill | yes 或 y |
k4 | 4dCp!u=A7Mw3v?KN |
在问题 is it okay to kill
正确匹配预设答案后,主模块将尝试开启本机已连接的摄像头并通过预览对话框显示从摄像头捕获的图像:
如上图所示,主模块在当前进程实例创建了一个超线程并将线程起始函数设置为主模块函数 show_webcam
,然后打印输出 smile.
。让我们看看函数
show_webcam
做了哪些工作:
可见主模块函数 show_webcam
主要实现了通过 OpenCV 软件包打开当前设备的摄像头并每间隔 1 毫秒捕获一次画面,然后将捕获的画面显示在预览对话框内。
嗯...小乌视频中当时位于预览对话框内的表情非常适合表达许多不明真相的吃瓜群众当时的心情。
法律信息:图像来自 《直播视频中隐藏的线索 背后的组织究竟是谁??Ash Vlogs事件始末(下)》 视频,已获得内容创作者和所有者许可,详情请转到 许可和法律信息 页面。
言归正传,紧接着主模块将要求用户输入第四阶段的密钥,预设密钥值我已在上文中列出。如果密钥与预设值相匹配,主模块将按顺序打印如下内容:
- good drip.
- bye for now
- i_k_w_s_i
最后弹出一个消息对话框,其内容为
email true_drip@protonmail.com. subject line: dirty hands. your mailing address in the body. we will only send it to the first.
,然后调用主模块函数
exitt
退出自身进程。下面我们一起来看一下在上文多次出现的 exitt
函数做了些什么:
还记得我们在动态分析时所提到的可执行文件 click.exe
在退出时会创建一个命令提示符子进程来删除自身位于硬盘上的可执行文件的行为吗?没错,该行为在主模块函数
exitt
中实现。
最后
以上便是对 Ash Vlogs 事件频道《i_know_where_she_is》首页和最后一支视频中所出现的二维码的全部分析内容。如果您已观看过文章开头所提及的 2 支视频,相信您已了解 Ash Vlogs 所发生的事情并不是真的。
威胁定义
值得强调的是,在本文中所涉及的可执行文件 click.exe
非常干净,其中没有任何恶意代码,因此您可以放心地在自己的设备上运行它。严格意义上来说,该可执行文件应该被定义为玩笑程序。玩笑程序不是恶意软件,不会对设备产生任何实际威胁和破坏性。玩笑程序通常试图伪装成恶意软件,因此可能会对使用者造成混乱而产生有害影响,防病毒软件默认情况下不会检出并拦截它。
请注意,您仍需检查您所运行的可执行文件是否与本文所列出的可执行文件的 SHA1 校验值一致,否则其安全性是未知的。如果您不确定您运行的可执行文件 click.exe
是否安全,可以发送邮件至 king@xiaoyy.org,我将在工作空闲时间手动快速筛查这些文件的安全性。
纠错
这篇分析文章是在工作和生活闲余时间编写的,并非一气呵成,因此文中可能存在一些错误或描述不清晰的地方。如果您认为本文内容存在错误点,不要犹豫,直接发送电子邮件到 king@xiaoyy.org 并附上您的观点,我将定期检查这些邮件并核实、纠正。
本文由 小云云 创作,采用 知识共享署名4.0 国际许可协议进行许可
本站文章除注明转载/出处外,均为本站原创或翻译,转载前请务必署名
最后编辑时间为: May
13, 2020 at 05:42 pm