VeryCuteLookingCat/Lockbit-Black-Loader-Analysis
GitHub: VeryCuteLookingCat/Lockbit-Black-Loader-Analysis
一份针对 LockBit Black 勒索软件加载器的深度逆向分析报告,揭示了其 Shellcode 加载、多态混淆及解密机制。
Stars: 15 | Forks: 2
# LockBit Black 分析 - Dropper 与 Shellcode
如果你想跳过直接阅读技术报告,请[点击这里](https://github.com/VeryCuteLookingCat/Lockbit-Black-Loader-Analysis?tab=readme-ov-file#Technical-Writeup)。
## 工具与环境
**静态分析**
- [Kali Linux 虚拟机](https://www.kali.org/) ([VirtualBox](https://www.virtualbox.org/))
- [Ghidra](https://github.com/NationalSecurityAgency/ghidra)
- [Detect It Easy](https://github.com/horsicq/Detect-It-Easy) (DIE)
- [Sublime Text Build 4200](https://www.sublimetext.com/)
- [PE Entropy](https://github.com/VeryCuteLookingCat/PEEntropy)
**动态分析**
- [Windows 10 Home 虚拟机](https://www.microsoft.com/en-us/software-download/windows10) ([VirtualBox](https://www.virtualbox.org/))
- [x32dbg / x64dbg](https://x64dbg.com/)
- [ScyllaHide](https://github.com/x64dbg/ScyllaHide)
## 样本来源
获取自 ANY.RUN 任务:[96df65a8-c9d4-4863-bddc-3c66617f0538](https://app.any.run/tasks/96df65a8-c9d4-4863-bddc-3c66617f0538)
# 阶段 0 – 首次接触
**文件名:**
在接触反汇编工具之前,我总是会做一次快速的健全性检查:编译器、链接器、熵值、导入表。这通常会告诉我接下来我将面临什么样的挑战。
DIE 报告显示:
- **编译器:** PureBasic
- **链接器:** Microsoft Linker
仅凭这个组合并不足以引起警觉,但熵值立刻引起了我的注意。
- `.text`:**7.71** - 这基本等同于在大喊“加了壳”
- `.rdata`:**5.32** - 这才是真正困扰我的地方
对于加壳器来说,`.text` 的高熵值是意料之中的。但 `.rdata` 的高熵值则不然。`.rdata` 的内容通常应该是枯燥乏味的。各种表、字符串、常量。看到它如此嘈杂,告诉我有东西故意隐藏在那里。

导入表看起来非常奇怪且不协调,导入函数的组合似乎并不匹配和契合。对我来说,它们看起来像是被放在那里,为了让它乍一看像一个合法的程序。但仔细阅读后,我感到十分不对劲。有太多做随机事情的导入函数了,看起来就像有人打开了 Microsoft 文档,然后滚动页面随便挑选了一些函数。

基于 DIE 提供给我的原始信息,我的推测是:该二进制文件被高度混淆,并且导入表被隐藏了。考虑到它的熵值,我认为肯定存在加密或虚拟化。
# 逆向阶段 0 - 追踪诡异之处
## 字符串(低成本,高价值)
我总是从字符串开始分析。这步操作成本很低,而且通常会暴露出意图。
我看到的大部分内容都是无用信息:键盘乱敲、垃圾数据,没有任何直接有用的东西。
接着我看到了 `LoadLibraryA`,这是开始逆向的一个绝佳起点。
这个函数通常能通过它的 XREF 很好地反映出混淆的情况。
它引我进入的第一个函数是 `0x00401224`。
接着 `LoadLibraryA` 开始讲述一个关于它如何隐藏其导入表的深度故事。
## “为什么 GetProcAddress 接收的是一个整数?”
我跟着进入的函数使用 `LoadLibraryA` 加载 `kernel32.dll`。它将句柄作为参数 1 传递给 `GetProcAddress`,但参数 2 是一个……整数数组?
没有对这个数组进行任何操作,它只是被初始化并传递了进去。我的第一直觉是它要构造一个字符串,但解码十六进制后告诉我:

按提供的顺序解码出来是乱码,但这引发了我思考为什么它会产生乱码。查看字符的索引,它们的顺序全都是乱的。
那么如果恢复顺序会怎样?我调整了组织结构并尝试再次解码,结果生成了:

Voilà!我正在查看的函数从 `kernel32.dll` 中获取了 `VirtualProtect`,并用一些全局变量调用了它。我注意到的一件事是区域标志 `0x40`,它等同于 `PAGE_EXECUTE_READWRITE`。但仅从这个函数来看,我不知道参数 1 中的指针或参数 2 中的大小是什么。我暂时将它们命名为 `sectionPointer` 和 `sectionSize`。为了不忘记这个函数,我将其命名为 `VirtualProtectRegion`。
## 某些东西正在被分配……以一种奇怪的方式
我向上回溯,XREF 显示对 `VirtualProtectRegion` 只有 1 次调用。探索这个新函数,它做的第一件事就是调用另一个未探索过的函数。
那是一个非常简单的函数,它只设置了 ExceptionList。我将其命名为 `InitSEH`(其实并不准确),然后继续往下看。

下一行代码检查 `sectionSize` 是否为 `0x93`,这就是二进制文件获取所有导入的地方。这个 if 语句非常庞大,塞满了毫无用处的导入语句,以及看起来很不正常的代码。我扫了一眼就跳过了,我不知道 `sectionSize` 的值是多少,如果我发现这个 if 语句为真,我会再回来。然后我得到了 `sectionSize` 可能是什么的答案,但并不确切。下一行代码将 `0x1134b` 加到 `sectionSize` 上,然后将一个指针移交给给了另一个指针。通常这是很正常的,但下一行代码调用了 `GlobalAlloc`,参数为 `0` 和 `sectionSize`。`0` 表示固定内存,意味着它不会被移动或偏移,大小为 `sectionSize`。我将 `sectionSize` 重命名为 `globalAllocSize`,虽然啰嗦得多,但更容易理解,也更准确。接着二进制文件将指针设置到这个节区,这个指针就是 `sectionPointer`。这把点串联了起来,这是一个可执行、可读、可写的内存区域,强烈暗示它包含了下一个 payload。为了清晰起见,我将 `sectionPointer` 更名为 `executableRegion`。

## RW 与 RWX
区分读、写和读、写、执行内存区域之间的差异感觉至关重要。区别在于功能上。如果你将 shellcode 写入可读、可写的内存段,EDR 解决方案不会太在意,因为这些 shellcode 很难有进一步的作为。然而,读、写、执行内存段会受到严密监控,这些区域允许在其中执行代码。传递到这个区域的指针会被尝试当作包含代码来运行。如果将指针传递到可读、可写的区域,会导致错误并抛出异常。
## payloadBase?
从图片中你可以看到我提到的指针都有名字。这些名字从何而来?来自接下来的几行代码。跳过垃圾代码,我看到 `executableRegion` 正在被写入。有一个循环持续进行,直到 `increment` 匹配 `globalAllocSize`,它正在将 `payloadbase + 0x1134b + increment` 写入 `executableRegion[increment]`。之所以推测命名为 `payloadBase`,是因为那必定是 payload 起始的地方,并且它偏移了 `0x1134b`。我以前见过 `0x1134b`,它曾被用来增加 `globalAllocSize`。

## 那么 Payload 到底在哪?
说实话,我不知道。但我可以使用 PE Entropy 准确猜测它在哪里,这是我专门为此构建的工具。这个工具的灵感正是来源于这次逆向分析,因为我苦苦寻找这个 payload 足足找了 3 天。我最终得到了一个更简单的解决方案,但反思并看看我错过了什么会更好。

从截图中可以看到,熵值出现了突然的飙升并保持恒定。跳转到该位置会显示出一个纯粹的数据块。这极有可能是 payload 存放的地方,位于 `0xEF00`。
那我为什么还要挣扎呢?因为没有任何东西明确指向这个区块说“你好,我是 PAYLOAD”。这个 payload 只是卡在文件的中间,正如你从截图中所看到的。二进制文件开头的代码逐渐消失,然后出现巨大的熵值飙升,达到峰值并保持不变。这就是我喜欢这个工具的原因,它以可视化的方式映射出熵值,并清楚地揭示了二进制文件的真实行为。说实话,在我花 3 天时间寻找这个 payload 的其中一天里,我滚动浏览了整个二进制文件来搜寻它。如果我早点制作这个工具,这篇文章就会提前 3 天完成。
这种隐藏 payload 的形式被称为“与节无关的 payload 走私”,它打破了所有规则,payload 存在于任何节之外,无法通过基于节的推理来发现,并且对信任 PE 结构的工具是不可见的。这击败了大量工具,如静态扫描器、特征码引擎以及静态分析人员(比如我)。这个数据块将可执行文件视为原始存储,而不是结构化的格式。这就是花费这么长时间定位 payload 的原因。
## 健全性检查:这不是 PE 加载器
我几乎可以保证这不是一个 PE 加载器。PE 加载器是一种从内存加载另一个可执行文件的恶意软件,但这个过程非常复杂。PE 加载器必须重现 `LoadLibrary` 执行的操作,因此它需要加载导入项并将 IAT 表设置为正确的指针,修补 CRT,并修正重定位。所有这些操作都需要解析 PE 头才能发生,而我在此根本没有看到这些。这些事实指向了一个结论,即这个 dropper 加载的是 shellcode,这个 shellcode 可能具有自己的导入解析功能,并自行修补所有必需的内容。
## 重点转移:解密
程序不能直接运行加密的 shellcode,它需要解密。它在下一行代码中完成了这项任务,遍历 `globalAllocSize` 的 8 字节块。这些 8 字节块作为指针传递给一个函数。这是极其明显的解密模式,对该函数的调查证明了这一点。我将这个新函数相应地命名为 `DecodeBlock`,它负责解密 `executableRegion`。它使用了 4 个密钥:
- DAT_0042e368 ( key 1 ) = 30C1F077h
- DAT_0042e36c ( key 2 ) = A597F02Ch
- DAT_0042e370 ( key 3 ) = 0EB0BA0Ah
- DAT_0042e374 ( key 4 ) = D41CC0E6h
这些密钥用来做什么?该函数的操作如下:
- 运行 32 轮
- 使用非标准的 sum( sum = `575` )
- 使用一个 delta 常量( sum += `0x61C88647` )
- 使用 Feistel 风格的混合
这种解密风格源自 **TEA/XTEA 家族**。密码学不是我的专长,所以我不会过多深入细节,但你可以在这里看到它的功能:

我还制作了一小段伪代码来解释该函数,并帮助自己在脑海中建立对它的映射:
```
void DecodeBlock(uint32_t block[2])
{
uint32_t left = block[0];
uint32_t right = block[1];
uint32_t key0 = DAT_0042e368; // 30C1F077h
uint32_t key1 = DAT_0042e36c; // A597F02Ch
uint32_t key2 = DAT_0042e370; // 0EB0BA0Ah
uint32_t key3 = DAT_0042e374; // D41CC0E6h
uint32_t sum = 575;
int rounds = 32;
do {
right -= ((left >> 5) + key3) ^ ((left << 4) + key2) ^ (left + sum);
left -= ((right >> 5) + key1) ^ ((right << 4) + key0) ^ (right + sum);
sum += 0x61c88647;
} while (--rounds);
block[0] = left;
block[1] = right;
}
```
## 绘制完整的静态分析图景
让我们回顾一下,到目前为止这个二进制文件做了什么?该二进制文件创建了一个大小为 `globalAllocSize` 的新的固定全局内存块。它将这个新区域的保护属性更改为 RWX(读、写、执行)。它将 payload 从 `payloadBase + 0x1134b` 投放进该区域。然后它使用 TEA/TEAX 变体解密器解密可执行区域。该区域通过将 `executableRegion` 作为函数调用来执行。考虑到这一点,是时候获取下一个 payload 了,为了做到这一点,我正转向
# 动态逆向 – 阶段 0
## 环境规范
全新的 Windows 10 虚拟机:
- 无网络连接
- 无 3D 加速
- 已拍摄快照
## 我不会仓促对待这部分。如果出现问题,我希望能有一个干净的回滚点。
## 运行时确认
使用 x64dbg,我首先更改了一些设置,我启用了在入口点断下功能,这样我就不会意外引爆恶意软件。然后我跳转到调用 `executableRegion` 之前的地址,即加载 msimg32.dll 的 `Library`。我让二进制文件运行,直到命中那个断点,此时我开始寻找 payload 代码。我花了一些时间搜索这些变量,但最终我直接步入了可执行内存中。

我使用 ScyllaHide 转储了内存区域并获取了下一个 payload。虽然我还不能完全确认 payload 是什么,但我已将转储文件传输到了我的 Kali 虚拟机。在 Kali 中,我再次用 DIE 检查了转储文件,它证实我成功了。
# 阶段 1 – 新的 Payload,新的问题
## 第一印象
DIE 报告显示:
- 32 位
- 无编译器签名
- 入口点 NOP
熵值仍然很高:
- 整体:**7.17**
- `.data`:**7.89**

那么这块数据砖意味着什么?缺少编译器签名意味着这个 payload 不是由任何标准编译器生成的。入口点 nop 只是意味着编写者在 shellcode 的开头放了一个 NOP 语句,很可能是解密函数的填充。而高熵值又联系回了我之前关于阶段 0 所说的内容。我预料到这个二进制文件中会有一些加密保护措施。考虑到这一点,我打算把它放进 Ghidra 并希望它能反编译。
## 入口点的疯狂
入口函数:
- 约 89 个参数
- 大部分是垃圾代码

如果你还没有做出假设,这个二进制文件确实被反编译了,并且令人震惊地包含了一个 PE 映像。我对入口点的第一反应是恐惧,这个函数看起来很可怕。略读代码时,我看到一堵墙似的一遍又一遍重复的相同语句。在函数的底部,有毫无意义的代码,这几乎让我相信我转储了错误的内存区域。深吸一口气从顶部开始看,我看到了 4 个函数调用。点击第一个,我皱了皱眉并想“也许我真的需要重新转储”,该函数是空的,只有一个返回语句。在放弃之前,我检查了第二个函数调用,差点从椅子上跳起来。这是一个看起来并不像语无伦次的乱码的合法函数。这拯救了我免于重新转储,如果我找不到任何合法的代码,这意味着转储错得离谱。但拥有一个真实的函数表明,编写者有意试图隐藏其功能并让入口看起来像是一个转储错误。
## 动态导入地狱
点击这个函数内部的一些函数,我看到了一些关键细节。我看到了 `ProcessEnvironmentBlock + 0xc` 和一个从 PEB 块获取数据的 while true 语句。我知道接下来会面临什么,这是动态导入的明显迹象。那么这个函数在做什么?它首先挑出 2 个哈希值,检查函数是否存在,并调用第一个,然后它将第二个函数传递给更多似乎用于寻找更多导入项的函数。

那么什么是动态导入,为什么它很重要?从入口点调用的主函数,我将其命名为 `DynamicallyResolveImports`,搜索哈希的函数我命名为 `resolveImport`。resolveImport 所做的正如我刚才所说,它循环遍历所有加载的模块,从该模块中循环遍历其导出项,每个导出项都会被计算哈希值。将此哈希值与第一个参数中传递的目标哈希值进行比较,如果匹配,它会检查这个导出项是否真的有效,如果有效,就返回它,如果无效,就中断。这两个函数就是这个 payload 能够作为 shellcode 运行的原因。
那么我是如何逆向这些哈希值的?为了找到该函数,我所做的是在 x32dbg 中跳到 if 语句之后,查看它找到的函数是否与目标匹配(现在是 x32,因为这是一个 x32 二进制文件)。这样做之后,我略读了该函数的指令值,然后找到一条泄露哪个函数与目标哈希值匹配的特定指令。“LEA EAX”,指向目标函数的指针被存储在 EAX 中。我对前 2 个哈希值就是这么做的,发现:
- `-0x7f0e718` = `RtlCreateHeap`
- `0x6e6047db` = `RtlAllocateHeap`
使用动态分析为我节省了大量时间和许多头痛。动态逆向这部分最有意义,因为:这是真实的、实时的输出。重建 `resolveImport` 将会耗费大量的时间和脑力。动态逆向唯一的头痛之处(至少使用这种方法)在于试图保存每一个导入项并将其关联回静态二进制文件。
## 为什么这些调用会对自己进行 XOR 运算?
`DynamicallyResolveImports` 所做的是调用创建一个新的堆,这个堆的大小为 `0x41002`。该函数随后将这个堆以及 RtlAllocateHeap 传递给一个新函数大约 19 次。每次调用时,第 1 个参数的大小都会增加,并且第 2 个参数中包含不同的指针。

该函数将参数 2 与 `0x47063fc8` 进行 XOR 运算,然后,等等,它对一个指针进行 XOR 运算?点击参数 2 中传递的指针会将我们带到二进制文件中完全随机的地方。这个 XOR 后的值很可能包含更多导入项的哈希值,这被证明是因为它们被传回给 `resolveImport` 或其变体。这个变体似乎加载了阶段 0 可能遗漏的任何模块,并且仅在解析导入项之前被调用一次。最大的问题是:这些导入项被存储在哪里?我知道导入项必须以某种方式最终存在于指针中,因为这就是 payload 为其恶意功能调用它们的方式。我瞥了一眼 entry 中的其他一些函数来证明这一点。但是这个新函数像阶段 0 一样重新分配了 0x10 字节的固定堆。但这正是它变得奇怪的地方,它生成一个 0-4 的随机数,并根据该数字对解析的导入项执行不同的操作。比如如果数字是 2,它会对该值进行 XOR 运算,并将其与一些奇怪的十六进制数字一起写入那 10 字节的堆中。。哦,这简直是教科书式的多态性。因此,散布在二进制文件中调用函数导入项的指针实际上并不直接导向函数导入项。它导向的是小型的 shellcode 片段,即通向导入项的跳板。每次运行这个 payload 时,导入项都会有不同的 shellcode。这意味着调用点永远不会引用真实的 API 地址。这一点至关重要,因为它破坏了 CFG(控制流保护)恢复,基于 API 的启发式方法失效,并且内存扫描器看到的是代码而不是指针。在这个 payload 被特征码扫描为 LockBit 或 dropper 之前,它极有可能在短时间内被标记为干净的。

## 字符串(万幸很弱)
既然我已经弄清楚了二进制文件是如何隐藏其导入项的,那么缺乏字符串是怎么回事呢?在 entry 中调用的其他函数中漫步,我可以看到存储在数组中的大块十六进制整数被传递给一个函数。调查这个函数表明它循环遍历第一个参数,将其与 `0x47063fc8` 进行 XOR 运算,并对每个值使用按位 NOT 语句。这无疑就是字符串加密,并且解密函数重建起来简直轻而易举,这正是我所做的。为了解码字符串,我只使用了 XREF,因为显然每个字符串都使用了这个函数。使用我的解码器函数和一些时间,我设法抓取了二进制文件中几乎所有的字符串。一些字符串仍然不可打印,保留为十六进制,但所有字符串都可以在[此处](https://github.com/VeryCuteLookingCat/Lockbit-Black-Loader-Analysis/blob/main/strings.txt)访问。
## 未完待续。
# 技术报告
## 阶段 0,Dropper
# 机制
Dropper 背后的核心机制是执行 x32 shellcode。这是通过对包含解密 payload 的 RWX 内存进行直接调用实现的。Payload 在内存区域内通过 TEA/TEAX 派生的解密循环进行解密。加密的 payload 以静态方式被放入该区域。
## 控制流图

使用 [drawio](https://app.diagrams.net/) 制作。
# YARA 规则
YARA 可以临时检测出这个加载器,因为阶段 0 暴露了多个低成本的静态不变量,编写者必须对其进行有意义的重新设计才能将其移除。一些临时的解决方案可以包括:检测解密循环也是可行的,因为它使用 32 个固定轮次、每个块两个 32 位字、Feistel 混合、移位和 XOR。二进制文件中庞大连续的加密 payload 也是可以被检测到的。
### TEA 派生解密循环规则
该规则是检测阶段 0 整体上最强大的方法。该规则最大的缺陷是它只针对这个特定样本。其他样本可能包含改变解密循环从而避开此规则的变异。为了明确每个字符串定义的含义,这里是一个表格:
字符串名称 | 检测内容
--- | ---
$delta | delta 常量中使用的静态整数,强度高
$shift1 | 移位操作码 1,强度弱
$shift2 | 移位操作码 2,强度弱
$round32 | 固定循环计数器字面量,存疑,非强制
```
rule LockBit_Black_Stage0_TEA_Decryption
{
meta:
description = "Detects TEA-like decryption routine used in LockBit Black Stage 0 loader"
author = "VeryCuteLookingCat"
confidence = "medium"
scope = "static"
strings:
$delta = { 47 86 C8 61 }
$shift1 = { C1 E0 04 }
$shift2 = { C1 E8 05 }
$round32 = { 20 00 00 00 }
condition:
uint16(0) == 0x5A4D and
$delta and
2 of ($shift*) and
filesize < 2MB
}
```
### IAT 重建规则
此规则具有中等强度,因为它检测了用于隐藏 `VirtualProtect` 的方法。有可能会捕获到该阶段 0 Payload 的另一个版本。该规则最大的缺陷是它过于宽泛,会导致大量的误报。
```
rule LockBit_Black_IAT_Reconstruction
{
meta:
description = "Detects reordered character array import reconstruction"
author = "VeryCuteLookingCat"
confidence = "low"
scope = "static"
strings:
$loadlib = "LoadLibraryA" ascii
$getproc = "GetProcAddress" ascii
$kernel = "kernel32.dll" ascii
condition:
uint16(0) == 0x5A4D and
$loadlib and $getproc and $kernel and
not pe.imports("kernel32.dll", "VirtualProtect")
}
```
# 真正的缓解措施
## 任意代码保护
强制实施 [ACG(任意代码保护)](https://learn.microsoft.com/en-us/defender-endpoint/exploit-protection-reference#arbitrary-code-guard) 将通过阻止未经签名的可执行内存区域分配或篡改,来防止大多数类型 shellcode 的执行。直接引用 Microsoft 文档:“任意代码保护通过防止内存被标记为可执行,保护应用程序免于执行动态生成的代码(未加载的代码,例如不是来自 exe 本身或 dll 的代码)。”这是提供的最好解决方案,前提是没有运行 JIT(即时)编译器或应用程序,因为它们会遇到兼容性问题。
## 内存行为监控
虽然该解决方案不能阻止分配,但它能检测出不可能的场景。每个内存区域都有一组行为规则,如果违反了这些规则,就会引发怀疑。使用工业级的端点检测与响应系统可以可靠地捕获此 payload。为什么它会起作用?该 payload 产生了大量可疑活动,分配堆并将其标记为 RWX,执行该堆区域,从二进制文件中解码大量文本块等等,这些都会引发怀疑。
# 主 Payload
## 机制
该 payload 作为 shellcode 执行,但似乎是用 C 语言编写并转换而来的。该 payload 的保护措施包括字符串加密和导入项隐藏。该 payload 首先分配大小为 `0x41002` 的堆。此堆用于存储多态 shellcode,该 shellcode 解密指向导入项的指针,传入参数,并返回所需内容。每个 shellcode 存根大小为 `0x10` 字节,共有 5 种变异形式。这些变异包括 XOR、ROT14 和算术运算。字符串加密是一个单函数,它循环遍历数组,将每个值与 `0x47063fc8` 进行 XOR 运算,并使用按位 NOT 运算符。一些字符串未能解码为任何 ASCII 字符,因此将用问号代替。完整的字符串列表可以在[此处](https://github.com/VeryCuteLookingCat/Lockbit-Black-Loader-Analysis/blob/main/strings.txt)访问。
### 控制流图

使用 [drawio](https://app.diagrams.net/) 制作。
## 防御影响
阶段 1 完全作为 shellcode 在内存中运行,绕过了传统的基于 PE 的缓解措施。由于以下原因,静态扫描无效:
- 针对导入项的多态存根,破坏了 CFG 重建
- 加密字符串和导入项哈希
- 驻留在堆上的代码和 RWX 分配
有效的监控从基于特征码的防病毒软件转向行为检测:
- EDR 风格的异常内存区域和堆分配监控
- 控制流保护,以捕获模块边界外的
- 任意代码保护,以防止动态生成的代码被执行
这些特征解释了为什么阶段 1 能够逃避针对阶段 0 的缓解措施,以及为什么防御者必须关联运行时内存行为,而不是纯粹依赖静态特征码。
标签:DAST, DeepSeek, DNS 反向解析, Dropper分析, Ghidra, Loader分析, LockBit, PE文件分析, PureBasic, Shellcode分析, VirtualBox, Windows 10, 二进制分析, 云安全监控, 云安全运维, 云资产清单, 加解密, 勒索软件, 多态性, 威胁情报, 导入表混淆, 开发者工具, 恶意软件分析, 攻击路径可视化, 样本分析, 沙箱分析, 熵值分析, 网络安全, 脱壳, 逆向工程, 隐私保护, 静态分析, 黑客工具, 黑盒分析