Glibc is the standard library that is linked against all the linux applications. It provides the necessary functionality like printf.

It also provides wrappers for most used system calls like open, read, write, close.

Manually building and installing libc.

Example from a StackOverflow answer

export glibc_install="$(pwd)/glibc/build/install"

git clone git://sourceware.org/git/glibc.git
cd glibc
git checkout glibc-2.28
mkdir build
cd build
../configure --prefix "$glibc_install"
make 
make install 

Followed by a test c code

#define _GNU_SOURCE
#include <assert.h>
#include <gnu/libc-version.h>
#include <stdatomic.h>
#include <stdio.h>
#include <threads.h>

atomic_int acnt;
int cnt;

int f(void* thr_data) {
    for(int n = 0; n < 1000; ++n) {
        ++cnt;
        ++acnt;
    }
    return 0;
}

int main(int argc, char **argv) {
    /* Basic library version check. */
    printf("gnu_get_libc_version() = %s\n", gnu_get_libc_version());

    /* Exercise thrd_create from -pthread,
     * which is not present in glibc 2.27 in Ubuntu 18.04.
     * https://stackoverflow.com/questions/56810/how-do-i-start-threads-in-plain-c/52453291#52453291 */
    thrd_t thr[10];
    for(int n = 0; n < 10; ++n)
        thrd_create(&thr[n], f, NULL);
    for(int n = 0; n < 10; ++n)
        thrd_join(thr[n], NULL);
    printf("The atomic counter is %u\n", acnt);
    printf("The non-atomic counter is %u\n", cnt);
}

and executing with the code

#!/usr/bin/env bash
set -eux
gcc \
  -L "${glibc_install}/lib" \
  -I "${glibc_install}/include" \
  -Wl,--rpath="${glibc_install}/lib" \
  -Wl,--dynamic-linker="${glibc_install}/lib/ld-linux-x86-64.so.2" \
  -std=c11 \
  -o test_glibc.out \
  -v \
  test_glibc.c \
  -pthread \
;
ldd ./test_glibc.out
./test_glibc.out

The answer goes on to explain how to put custom print statements inside the libc code, so that we can verify that indeed we are calling the new libc instead of the default libc.

De-tour: GOT and PLT

We are taking a de-tour to talk about GOT and PLT. As we will see in the future that an understanding of this is required to hack the libc operations. The content is a summary of an article which can be found here.

There are two types of application, statically linked or dynamically linked. As the name tells us, statically linked libraries contain all the piece of code that it is going to require inside it. This increases the size of the binary; however, it gets rid of most of the version mis-match or missing dependencies issues. The other type is dynamically linked binaries. These binaries do not contain all the code, and rely on the operating system to provide the missing piece of the code during runtime.

Taking the example of printf function. The definition of the function is provided by the libc and is linked dynamically at the run time. Now, to call the function at the runtime, the application has to know the address of the function. The address of the function cannot be added statically as it might be different for different versions. Modern hardware, to increase the security of the application, randomize the address of the loaded code by moving it by a fixed offset. This is called ASLR or address space layout randomization. To ease the process of looking for a function linking is used. The process of searching for a function is called relocation.

Relocation

The linking of the application is done by the linker ld-linux.so. This is executed before any of the function from the application is called. The ELF file contains many sections to aid this.

  • .got: Global Offset Table. This section contains the actual offset of the external symbols.
  • .plt: Procedure link table. This looks for the addresses in the .got.plt section, and either jump to the correct address or trigger the code in the linker to look up the address.
  • .got.plt contains the actual address after they have been looked-up, or address back into the .plt to trigger the lookup
  • .plt.got points to the first entry in the .got.

.plt contains the stubs (wrapper) to jump to the target addresses, the .got contains the actual address.

The author of the blog shows that the first call to the puts function triggers a look up via the linker, however, the subsequent call does not.