fragility in 32-bit linux program

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.



#include <dlfcn.h>
#include <unistd.h>

int main()
    void *libc_addr =  *(void **)dlopen("", RTLD_LAZY);
    write(STDOUT_FILENO, &libc_addr, sizeof(void *));
    return 0;


#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("", 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 */
            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! ");
    printf("same_times: %d / %d = %lf%%\n", same_times, TRY_TIMES, ((double)same_times/(double)TRY_TIMES) * 100);

    return 0;


cmake_minimum_required (VERSION 2.6)

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:

  1. Use 64-bit program, it is very hard to crack out the program's libc address.
  2. Keep your program without any vulnerability, although it is more difficult.