哪些信息可以向安全团队提供未初始化的数据?

逆向工程 C++ 安卓
2021-06-10 00:01:39

当我在逆向一些众所周知的Android聊天应用程序时(我无法透露具体是哪些,但所有这些都由拥有10亿+资本和数亿账户的公司拥有),我在C++代码中看到了一个有趣的功能。

这些应用程序读取未初始化的数据并将其发送到其 Web 服务。

在伪代码中,他们完成以下工作:

static constexpr const size_t uninitialized_data_size = 1024;
auto uninitialized_data = malloc(uninitialized_data_size);
HttpPost("http://my-url.com", uninitialized_data, uninitialized_data_size);

当然,我们可以认为它只是一个错误……但是当多个大公司做同样的事情时,我开始思考:为什么?

有谁知道未初始化的数据可以向安全团队提供哪些信息?是偶然重复犯了错误,还是他们从定期发送到服务器的未初始化数据中提取了一些有用的信息?

4个回答

在某些情况下,未初始化的内存被用作熵源,例如OpenSSL,但我怀疑这在这里​​发生。您发布的代码段中可能不存在某些内容,或者它确实可能是一个真正的错误。

我不是 100% 知道他们如何使用这些信息,但是我从这次对话的其他参与者提供的信息以及来自互联网的信息中怀疑:

  • 在Android JNI 应用程序中,Javamalloc用于分配Java 对象。相反,Java 有自己的内存管理器。所以,Java 代码对malloc返回什么影响很小
  • malloc如果可能,更喜欢为同一个线程使用同一个竞技场;因此,在大多数情况下,从其他线程进行的其他 JNI 调用不会影响malloc返回的内容。
  • 这意味着,这malloc通常会返回由同一线程分配的数据。在 JNI 中,我们通常不创建长期存在的 C/C++ 对象,而更喜欢使用 Java 作为内存管理器,因为在 Java 中释放 C++ 内存是有问题的。finalize不提供任何保证!因此,即使我们尝试释放与 Java 对象相关的 C++ 数据finalize,我们也无法确定不会发生内存泄漏,因为操作系统永远不会调用finalize. 因此,我们可以预期malloc在 JNI 调用free期间进行的99%在同一个调用期间调用。
  • 因此,我们可以使用未初始化的数据来检测(有一定的概率)我们的共享库是在一些不寻常的环境中加载的。

让我们想象以下代码:

void MarkHeap()
{
    static const char *ones = "11....1"; // String that contains 1024 ones
    auto some_data = malloc(1024);
    memcpy(some_data, ones, 1024);
    free(some_data);
}

size_t CheckMarkHeap()
{
    auto some_data = malloc(100);
    size_t ones_count = 0;
    for(size_t i = 0; i < 100; ++i)
        if(some_data[i] == '1')
            ++ones_count;
    free(some_data);
    return ones_count;
}

MarkHeap();
auto ones_count = CheckMarkHeap()

在这里,我们可以预期它经常ones_count等于 100!我们现在可以使用这个策略来检查(有一定的概率)是否CheckMarkHeap在 之后不久被调用MarkHeap

CheckMarkHeap计算任何类型的安全令牌的情况下,我们可以担心任何人会尝试使用我们自己的共享库来绕过我们的保护;在 Android 的情况下,我们可以从 APK 中提取共享库并尝试使用 Android 模拟器或任何嵌入式 ARM 模拟库(如unicorn. 如果我们MarkHeap在另一个不会引起攻击者注意的共享库中实现并以某种方式在之前调用它CheckMarkHeap,我们就有很好的机会检测到我们的安全库是从异常上下文加载的。

当然,我们不能因此立即禁止,因为任何随机事件都可能影响ones_count. 但是,如果ones_count超过 60% 的通话不是 100,我们可以对可疑帐户进行任何软惩罚(例如,要求电话验证、SafetyNet 验证、更频繁地显示 CAPTCHA、将帐户发送给人工审核等)。

确切的行为将取决于背后的内存管理器的实现细节malloc()但内存管理器会重用先前使用free(). 如果malloc()回收一个memset()在释放之前没有归零(例如使用的内存块,该块通常将包含进程内先前执行的函数剩余的缓冲区内容。

鉴于内存管理器在分配和释放时不会初始化块,这个伪代码的含义是它会在进程的内存地址空间内重复获取随机内存样本(1k 大小),偶尔会从内存中捕获一些剩余的数据。先前执行的代码的工作。

也许是为了窃取数据?

当您通过 malloc 请求内存时,您将获得一块之前被其他应用程序或操作系统使用过的内存块。我认为,就这些公司拥有的庞大用户群和​​计算能力而言,应该可以从那里提取有价值的信息(例如密码管理器和其他凭据没有正确清理的哈希值)。

让我们做一些计算(不幸的是在代码标签中,因为 RE-Ex 没有 MathJax):

让...

  • n 是 malloc 可以从中选择的内存数量
  • m 是我们可以搜索的窗口的大小
  • l 是搜索序列的大小

窗口可以具有的不同位置的数量由 给出n - m + 1

将完全包含大小序列的仓位数量lm - l + 1

因此,随机选择的搜索窗口将包含我们的序列的概率是

p = (m - l + 1)/(n - m + 1)

我们将假设,

  • 智能手机的平均可用内存是 n = 4GB
  • 我们的搜索窗口的大小是 m = 1024
  • 序列的长度是l = 16(一个 MD5-Hash 的大小)

这给了我们一个概率p = (1024 - 16 + 1)/(4e9 - 1024 + 1) ~= 2.5225e-7

让我们再做一些假设:假设信使有1.5 billion users就像 WhatsApp 有)。我们还假设代码发送数据,twice a day并且one thousandth用户在内存中的某处有一个未保留的 MD5 哈希。

这为我们提供了1.5e9 / 1000 * 2 = 3e6每天要评估样本总数

我们将用正态分布近似估计有用散列的预期数量:

sigma = sqrt(3e9*2.5225e-7*(1-2.5225e-7)) ~= 27.50909 (所以大约应该产生良好的结果)

my = 3e9*2.5525e-7 ~= 756.75

因此,该公司my-2*sigma, my+2*sigma每天将在 701 到 812 ( ) 个可用哈希之间获得 99.73% 的确定性

免责声明:我不确定 java 如何处理内存以及这种情况的合理性(和有效性)。我也没有使用任何有根据的值——我只是插入了一些伪逻辑数字。我也不保证我的计算的正确性(我从来没有真正喜欢统计)。

尽管如此:如果我计算错误,请随意修改这些值并纠正我。