libc
is a short of the term 'Standard C Library'. It is a library of standard functions that can be used by all C programs. It is usually refers the glibc
(GNU C Library) in Linux, but the term libc
is simply refers 'standard C Library', so it refers any C language libraries by any vendors.
These are famous LibCs that are widely used.
Of course Windows also provide C libraries. These libraries are shipped with Visual C++, or Visual C++ Redistributables.
1. A story of C libraries
If you are familiar with Linux, you may also feel the term glibc
familiar. Because whenever you run an application in Linux, almost every executables require the glibc. Sometimes to run another application that is not really well made, you may heard that term while fixing the application.
But you should aware that the glibc
is not the only one libc
. If you have heard of the Alpine Linux, which is very lightweight Linux environment, you also might heard of the musl libc.
In BSD-derived operating system, the BSD libc is used.
For small systems like embedded devices, sometimes you can see the uClibc. There was the original uClibc by Erik Andersen, the lastest release was in 2012. If you see the uClibc library is in an embedded system, it is likely to be uClibc-ng
, the 'ng' is short of 'Next Generation'.
2. In CTF Challenge
When you solve a Linux pwnable challenge, version of glibc
is important. Sometimes it can be a hassle and you could waste your time just to fix it.
2.1. Finding version of the libc with leaked address
In CTF challenge, some challenge includes libc. You can patch the challenge to run it with the given libc, which is described in here, so it will make your exploit code run against the CTF challenge server and solve the challenge.
But some other challenges does not have libc, it can cause your exploit code not work if you target the remote CTF server, even if it runs in your local Linux system and successfully get a root shell.
But if you leaked the address of libc functions like printf
or malloc
, you can query it and guess which version of libc is used in the remote server.
Let’s assume you’ve got address of the glibc functions. The code here will print the address of glibc functions.
#define _GNU_SOURCE
#include <stdio.h>
#include <unistd.h>
#include <dlfcn.h> // gcc -ldl
int main() {
printf("write() : %p\n", dlsym(RTLD_DEFAULT, "write"));
printf("read() : %p\n", dlsym(RTLD_DEFAULT, "read"));
printf("printf() : %p\n", dlsym(RTLD_DEFAULT, "printf"));
printf("system() : %p\n", dlsym(RTLD_DEFAULT, "system"));
return 0;
}
After compiling and executing it, these were printed.
$ ./a.out
write() : 0x7fe75f952870
read() : 0x7fe75f9527d0
printf() : 0x7fe75f89e6f0
system() : 0x7fe75f88ed70
The Linux system has enabled the ASLR. So every time I run the compiled executable, I can see the address changes.
But the key is the bottom three hexadecimals. There is the libc-database, you can query it and guess the version of glibc. Let’s visit https://libc.rip, a website based on the project.
Then fill the forms by typing 'read' in Symbol name, and the last three hexadecimals in Address. The more Symbol name and Address pairs are given, the more likely to find the correct libc version.
Results show these symbols with that address should be 'libc6_2.35-0ubuntu3.x_amd64'. I compiled and run the executable in the WSL Ubuntu 22.04.
When you click one of the results, you can see other address of functions. And there is a download link. You can download libc.so.6
and patch the challenge file.
2.2. Get precompiled glibcs
Some CTF challenge requires you to use old version of the glibc. For example, you might want to solve a challenge that requires you to use the House of Force technique, which requires the version of glibc to be older than 2.29
.
There are awesome projects to download precompiled glibcs, I prefer https://github.com/matrix1001/glibc-all-in-one personally.
It is a pack of a python script and shell scripts, so you don’t need to care about how to build and install, and so on. Start cloning the Github repository first.
$ # `--depth 1` to clone only the latest revision(commit) of the repository
$ git clone --depth 1 https://github.com/matrix1001/glibc-all-in-one
To get a list of available versions of glibc
, run update_list
first. Sometimes the script won’t work,
exec: Failed to execute process './update_list': The file specified the interpreter '/usr/bin/python', which is not an executable command.
Simply fix the first line of the script or python3 update_list
. In my case, I replaced python
to python3
.
After running the script, you will see there are list
file and old_list
file.
$ cat list
2.23-0ubuntu11.3_amd64
2.23-0ubuntu11.3_i386
2.23-0ubuntu3_amd64
2.23-0ubuntu3_i386
2.27-3ubuntu1.5_amd64
2.27-3ubuntu1.5_i386
2.27-3ubuntu1.6_amd64
2.27-3ubuntu1.6_i386
...
You can choose the version and download what you desired. I will download 2.27-3ubuntu1_amd64
for example. If you want to download the version not listed in the list
file, it might be there at the old-list
file.
$ ./download 2.27-3ubuntu1_amd64
Getting 2.27-3ubuntu1_amd64
-> Location: https://mirror.tuna.tsinghua.edu.cn/ubuntu/pool/main/g/glibc/libc6_2.27-3ubuntu1_amd64.deb
-> Downloading libc binary package
-> Extracting libc binary package
x - debian-binary
x - control.tar.xz
x - data.tar.xz
/tmp/glibc-all-in-one
-> Package saved to libs/2.27-3ubuntu1_amd64
-> Location: https://mirror.tuna.tsinghua.edu.cn/ubuntu/pool/main/g/glibc/libc6-dbg_2.27-3ubuntu1_amd64.deb
-> Downloading libc debug package
-> Extracting libc debug package
x - debian-binary
x - control.tar.xz
x - data.tar.xz
/tmp/glibc-all-in-one
-> Package saved to libs/2.27-3ubuntu1_amd64/.debug
If there were no errors, there should be both libc-2.27.so
and ld-2.27.so
.
$ file libs/2.27-3ubuntu1_amd64/ld-2.27.so
libs/2.27-3ubuntu1_amd64/ld-2.27.so: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, BuildID[sha1]=64df1b961228382fe18684249ed800ab1dceaad4, stripped
$ file libs/2.27-3ubuntu1_amd64/libc-2.27.so
libs/2.27-3ubuntu1_amd64/libc-2.27.so: ELF 64-bit LSB shared object, x86-64, version 1 (GNU/Linux), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=b417c0ba7cc5cf06d1d1bed6652cedb9253c60d0, for GNU/Linux 3.2.0, stripped
As you downloaded the ld
interpreter and libc
shared object, you can patch the challenge with patchelf.
See README.md for the detail.
2.3. Finding the glibc
version if you know the sha256 of Docker image
Docker is an essential part of Linux computing, even in CTF challenges. Some CTF Pwnable challenge ships with Dockerfile, it makes you reproduce the pwnable server environment in your desktop or laptop.
But I already have my own pwnable environment based on Docker, so when a pwn challenge gives me a Dockerfile, it makes me itch. I have to build the Docker environment again, and it consumes a lot of time and disk space.
But you can see the version of glibc
of remote challenge server if the Dockerfile is provided. What you need is just a hash of the base image. This tip can be useful if you want (almost) exactly the same environment as the remote CTF server.
$ head -1 Dockerfile
FROM ubuntu:22.04@sha256:b6b83d3c331794420340093eb706a6f152d9c1fa51b262d9bf34594887c2c7ac
We’re going to extract library files in the image. To extract library files in the image, a container from the image should be run.
$ docker create ubuntu:22.04@sha256:b6b83d3c331794420340093eb706a6f152d9c1fa51b262d9bf34594887c2c7ac
4730858291d177f0cb97773d6633777ed296de2144ccc99ae533d0dcfcdb23ca
Then, copy files in the directory /lib/
to host.
$ docker cp 4730858291d177f0cb97773d6633777ed296de2144ccc99ae533d0dcfcdb23ca:/lib/x86_64-linux-gnu ./lib
Successfully copied 47.2MB to /home/ch1keen/project/wiki.ch1keen.xyz/lib
$ file lib/libc.so.6
lib/libc.so.6: ELF 64-bit LSB shared object, x86-64, version 1 (GNU/Linux), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=89c3cb85f9e55046776471fed05ec441581d1969, for GNU/Linux 3.2.0, stripped
$ file lib/ld-linux-x86-64.so.2
lib/ld-linux-x86-64.so.2: ELF 64-bit LSB shared object, x86-64, version 1 (GNU/Linux), dynamically linked, BuildID[sha1]=aa1b0b998999c397062e1016f0c95dc0e8820117, stripped
If you don’t need to be too platform-specific, listing packages inside the image might be useful.
If you’re using Docker Desktop newer than 4.7.0, you can use docker sbom
command. It will print out which packages are included in a Docker image. It includes version of glibc
, too.
$ docker sbom ubuntu:22.04@sha256:b6b83d3c331794420340093eb706a6f152d9c1fa51b262d9bf34594887c2c7ac
Syft v0.43.0
✔ Pulled image
✔ Loaded image
✔ Parsed image
✔ Cataloged packages [101 packages]
NAME VERSION TYPE
adduser 3.118ubuntu5 deb
apt 2.4.5 deb
...
libc-bin 2.35-0ubuntu3 deb
libc6 2.35-0ubuntu3 deb
...
If you find docker sbom
command does not work,
$ docker sbom ubuntu:22.04
docker: 'sbom' is not a docker command.
See 'docker --help'
$ docker scout ubuntu:22.04
docker: 'scout' is not a docker command.
See 'docker --help'
You can install syft to do exactly the same thing. Because the docker sbom
command is just a Docker plugin, a wrapper of the syft
.
curl -sSfL https://raw.githubusercontent.com/anchore/syft/main/install.sh | sh -s -- -b /usr/local/bin
3. Trivia
-
musl-libc has an issue about the locale. It may not really good at developing the multi-language, especially you need to deal with the
setlocale()
andgetlocale()
. -
Normally the
libc
files are stored in the/lib/<architecture>/libc.so.6
, for example/lib/x86_64-linux-gnu/libc.so.6
. Historically there werelibc.so.1
…libc.so.5
, andlibc.so.6
is known not to compatible with the previouslibc.so.x
s.
4. See Also
-
In patchelf, I described how to patch CTF challenge with the provided libc, if given.
-
Elixir Bootlin is a nice website to see source code of glibc.
-
Are you interested in internal logic of
malloc()
?
-
5. Reference
-
The GNU C Library: https://www.gnu.org/software/libc/
-
musl-libc: https://musl.libc.org/
-
FreeBSD libc: https://github.com/freebsd/freebsd-src/tree/main/lib/libc
-
Apache C++ Standard Library (stdcxx): https://stdcxx.apache.org/