当我反转一些著名的Android聊天应用程序时(我无法透露哪些特定的应用程序,但所有这些都由拥有10亿美元资本且拥有数亿个帐户的公司所拥有),我看到了C ++代码中一个有趣的功能。 br />
这些应用程序读取未初始化的数据并将其发送到其Web服务。

以伪代码执行以下工作:


当然,我们可以认为这只是一个错误...但是当多个大公司做同一件事时,我开始思考:为什么?未初始化的数据可以提供给安全团队哪些信息?仅仅是偶然的重复错误,还是他们从定期发送到服务器的未初始化数据中提取了一些有用的信息?

评论

有趣的问题。是在.so文件中,还是这些应用程序都是用C ++编写的?

SO文件是在典型的Android应用程序中通过JNI实现Java本机方法的文件。我怀疑这可能是任何安全算法。至少在应用程序启动时,未初始化的数据或多或少具有可预测的值。随着应用程序运行时间的延长,此处的随机数据也会越来越多。但是,在此之前,malloc是否可以返回Java使用的内存是一个问题。我不知道Java如何处理低级的内存。大多数代码都是通过Java运行的,因此在这种应用程序中,Java内存比C ++难预测。

您确定您有一个实际的malloc()调用,而不是其他任何东西,例如calloc()?

是的,当然,malloc。

#1 楼

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

评论


但是为什么要将熵从Android设备发送到服务器?我怀疑他们以某种方式检测到模拟器。例如,仿真器可能具有不同的基址范围,等等。但是我没有特定的想法。

– Vitalii
19年8月8日在6:14

也许仿真器总是有特定模式填充的内存?

–伊戈尔·斯科钦斯基♦
19年8月8日在8:17

#2 楼

我不是100%知道他们如何使用此信息,但是我从这次对话的其他参与者提供的信息以及来自Internet的信息中怀疑:不使用Java分配Java对象。相反,Java具有自己的内存管理器。因此,Java代码对malloc的返回值影响很小。

malloc倾向于在相同的线程上使用相同的竞技场;因此,在大多数情况下,从其他线程进行的其他JNI调用不会影响malloc返回的内容。
这意味着malloc通常会返回同一线程分配的数据。在JNI中,我们通常不创建长期存在的C / C ++对象,而更喜欢使用Java作为内存管理器,因为在Java中释放C ++内存是有问题的。 malloc不提供任何保证!因此,即使我们尝试释放与finalize中的Java对象相关联的C ++数据,我们也永远无法确定不会发生内存泄漏,因为OS永远不会调用finalize。在JNI调用期间生成的finalize的99%将在同一调用期间调用malloc。 br />让我们想象以下代码:

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()


在这里,我们可以期望free经常等于100!现在,我们可以使用此策略来检查(以一定的可能性)ones_count之后不久是否调用了CheckMarkHeap

MarkHeap计算任何类型的安全令牌的情况下,我们担心任何人都会尝试使用我们自己的共享库来绕过我们的保护;如果是Android,我们可以从APK中提取共享库,并尝试使用Android仿真器或任何嵌入式ARM仿真库(例如CheckMarkHeap)对其进行解释。如果我们在另一个不会引起攻击者注意的共享库中实现unicorn,并以某种方式在MarkHeap之前调用了它,那么我们就有很好的机会检测到我们的安全库是从异常上下文中加载的。我们不能立即禁止这样做,因为任何随机事件都可能影响CheckMarkHeap。但是,如果在超过60%的通话中ones_count不是100,我们可以对可疑帐户进行任何软罚(例如,请求电话验证,SafetyNet验证,更频繁地显示验证码,将帐户发送给人工审核等)。

#3 楼

确切的行为取决于malloc()后面的内存管理器的实现细节。但是常见的情况是内存管理器会重用先前与free()一起发布的内存块。如果malloc()在释放之前回收未归零的内存块(例如,使用memset()),则该块通常将包含该进程中先前执行的功能剩余的缓冲区内容。如果在分配和释放时未初始化块,则此伪代码的含义是它将在进程的内存地址空间内重复获取随机内存样本(大小为1k),偶尔会捕获一些先前执行的代码中残留的数据。

评论


显然,malloc包含先前使用的数据;问题是他们将如何使用这些信息?

– Vitalii
19年8月6日在14:34

Kinda重要的是,这是在同一过程中。因为那限制了这种攻击的效用,所以方法...

– 0xC0000022L♦
19年8月13日在20:55

#4 楼

也许是窃取数据?

当您通过malloc请求内存时,您将获得之前由其他应用程序或os使用的内存块。我认为,就这些公司拥有的庞大用户群和​​计算能力而言,应该可以从那里提取有价值的信息(例如未由密码管理器和其他凭据正确清除的哈希)。 />
让我们做一些计算(不幸的是,在代码标签中,因为RE-Ex没有MathJax):

让...




n是malloc可以选择的内存数量

m是我们可以搜索的窗口大小

窗口可以具有的不同位置数量由l给出。

位置数量(将完全包含大小为n - m + 1的序列)为l

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

m - l + 1

我们假设,


平均可用内存在智能手机是p = (m - l + 1)/(n - m + 1)

我们搜索窗口的大小是n = 4GB

序列的长度是m = 1024(MD5-Hash的大小) br />这给了我们l = 16的概率。我们还假设该代码发送的数据p = (1024 - 16 + 1)/(4e9 - 1024 + 1) ~= 2.5225e-71.5 billion users的用户在内存中的某个位置具有未保留的MD5哈希。 br />我们将使用正态分布来估计预期的有用哈希数:

twice a day(因此,近似值会产生良好的结果)

one thousandth

因此,公司每天将在701和812(1.5e9 / 1000 * 2 = 3e6)可用哈希之间获得99.73%的确定性。是。我也没有使用任何有根据的值-我只是插入了一些伪逻辑数字。我也不保证计算的准确性(我从未真正喜欢过统计数据)。

评论


在实践中,您的想法的前提是错误的。当然,如果未分配的内存包含系统或其他应用程序使用的数据,那将是一个巨大的安全漏洞。这就是为什么它不这样做的原因-健全的操作系统不会将脏页交给进程。那里的任何数据都属于此过程或其祖先。而且,这一切的共同始祖,合子,在某种程度上都非常谨慎,部分原因是为时过早且没有差异化,无法对用户数据的方式包含很多兴趣。

–克里斯·斯特拉顿(Chris Stratton)
19年8月14日在4:23



@ChrisStratton您能否解释一下为什么您认为“健全的操作系统不会交出脏页”?我自己进行了一些研究,但找不到关于操作系统对其内存进行清理的任何信息(这也非常无效率),也找不到关于在特定位置分配进程堆的信息。到目前为止,我还没有时间尝试论文,但是今天晚上(GMT)我可能可以进行一些测试。但是,如果您能向我指出一些对此有所解释的论文,我将不胜感激-我一直渴望了解有关低级内存管理的更多信息:)

–匿名匿名
19年8月14日在11:33

如果您看,您会发现有关将后台内核线程中的已发布页面清零与将其分配给进程的争论。每个人都同意必须这样做,唯一的问题是在预先进行与有需要之间。似乎现在的趋势是按时分配,因为它不需要很长时间。

–克里斯·斯特拉顿(Chris Stratton)
19年8月14日在13:14