As we all know, the ASLR can protect our program from vulnerability, but the ASLR is not enough secure for 32-bit linux program, we can see following example to verify it.
Example
child.c:
#include <dlfcn.h>
#include <unistd.h>
int main()
{
void *libc_addr = *(void **)dlopen("libc.so.6", RTLD_LAZY);
write(STDOUT_FILENO, &libc_addr, sizeof(void *));
return 0;
}
parent.c:
#include <stdio.h>
#include <stdlib.h>
#include <dlfcn.h>
#include <unistd.h>
#include <sys/wait.h>
#include <stdint.h>
#define CHILD_PATH "./child"
#define TRY_TIMES 0x10000 /* Number of experiments */
int main(int argc, char **args, char **envp)
{
char *child_args[] = {CHILD_PATH, NULL};
void *child_libc_addr, *parent_libc_addr = *(void **)dlopen("libc.so.6", RTLD_LAZY);
int i, same_times = 0, pipefd[2], pid, result;
printf("parent_libc_addr: %p\n", parent_libc_addr);
pipe(pipefd); /* Create a pipe to communicate with child process. */
for(i = 0; i < TRY_TIMES; i++)
{
pid = fork();
if(pid) /* Parent process */
{
wait(&result); /* Wait for the end of child process */
if(result){exit(EXIT_FAILURE);}
read(pipefd[0], &child_libc_addr, sizeof(void *));
if((size_t)child_libc_addr == (size_t)parent_libc_addr)
{
same_times ++;
}
}
else /* Child process */
{
dup2(pipefd[1], STDOUT_FILENO);
if(execve(child_args[0], child_args, envp) == -1)
{
perror("execve error! ");
exit(EXIT_FAILURE);
}
}
}
printf("same_times: %d / %d = %lf%%\n", same_times, TRY_TIMES, ((double)same_times/(double)TRY_TIMES) * 100);
return 0;
}
CMakeLists.txt:
cmake_minimum_required (VERSION 2.6)
project(parent)
project(child)
set(CMAKE_C_FLAGS "-m32")
add_executable(parent parent.c)
add_executable(child child.c)
target_link_libraries(parent dl)
target_link_libraries(child dl)
The result of running the program is shown below:
ex@Ex:~/test$ mkdir build
ex@Ex:~/test$ cd build/
ex@Ex:~/test/build$ cmake ..
-- The C compiler identification is GNU 7.4.0
-- The CXX compiler identification is GNU 7.4.0
-- Check for working C compiler: /usr/bin/cc
-- Check for working C compiler: /usr/bin/cc -- works
-- Detecting C compiler ABI info
-- Detecting C compiler ABI info - done
-- Detecting C compile features
-- Detecting C compile features - done
-- Check for working CXX compiler: /usr/bin/c++
-- Check for working CXX compiler: /usr/bin/c++ -- works
-- Detecting CXX compiler ABI info
-- Detecting CXX compiler ABI info - done
-- Detecting CXX compile features
-- Detecting CXX compile features - done
-- Configuring done
-- Generating done
-- Build files have been written to: /home/ex/test/build
ex@Ex:~/test/build$ make
Scanning dependencies of target parent
[ 25%] Building C object CMakeFiles/parent.dir/parent.c.o
[ 50%] Linking C executable parent
[ 50%] Built target parent
Scanning dependencies of target child
[ 75%] Building C object CMakeFiles/child.dir/child.c.o
[100%] Linking C executable child
[100%] Built target child
ex@Ex:~/test/build$ ./parent
parent_libc_addr: 0xf7cf6000
same_times: 303 / 65536 = 0.462341%
The 0.462341%
chance is very dangerous, it means that hacker can crack your libc
successfully in 512 times, then may lead to Arbitrary code execution
.
Why is the probability so high?
Let's see the libc addresss above - 0xf7cf6000
. If you tried the example many times,
you will find the 1-high-bytes and 1.5-low-bytes is fixed, the 1-high-bytes is always 0xf7 and the
1.5-low-bytes is always 0x000, so we need only crack the 1.5-middle-bytes.
You might have question about the probability, why the probability is 512 times instead of 4096 times for 1.5-bytes, I already told you above, the ASLR is weak.
Now, there are two solutions to this problem:
- Use 64-bit program, it is very hard to crack out the program's libc address.
- Keep your program without any vulnerability, although it is more difficult.