Shared library injection into a Linux process (bonus)
Introduction
This bonus article describes a second easy method to retrieve a symbol address into a process. The first method was exposed here. As explained previously, the injected lib is loaded into the victim process (yeah, that’s the goal) but also into the injector in order to compute an offset. This last loading can cause several drawbacks:
- Load a library just to compute an offset may be overkill.
- If the library contains some constructors, these ones will be run into the injector process. They can potentially failed (segmentation faults, etc.).
- etc.
So, in this short article, we are going to approach a method to retrieve a symbol address without library loading into the injector.
Parse the ELF
The ELF format (man page) provides a lot of information and we can use them to compute the symbol address. Let’s see how to get the address of __libc_dlopen_mode
by using the ELF format.
Step 1: get the __libc_dlopen_mode
offset
The readelf
command (man page) allows to display some information about a ELF file. Its argument -s
lists all the entries of the symbols table:
$ readelf -s /usr/lib64/libc-2.30.so | grep __libc_dlopen_mode
2271: 000000000013ba20 128 FUNC GLOBAL DEFAULT 15 __libc_dlopen_mode@@GLIBC_PRIVATE
23052: 000000000013ba20 128 FUNC LOCAL DEFAULT 15 __GI___libc_dlopen_mode
26673: 000000000013ba20 128 FUNC GLOBAL DEFAULT 15 __libc_dlopen_mode
So, the __libc_dlopen_mode
offset into the libc is: offset = 0x13ba20
.
Step 2: get the virtual address of the libc executable segment
By parsing the elf
The readelf
argument -l
displays all the segments of the ELF file. In the excerpt below, I just keep the interesting executable segment:
$ readelf -l /usr/lib64/libc-2.30.so
Elf file type is DYN (Shared object file)
Entry point 0x272b0
There are 14 program headers, starting at offset 64
Program Headers:
Type Offset VirtAddr PhysAddr
FileSiz MemSiz Flags Align
# [...]
LOAD 0x0000000000025000 0x0000000000025000 0x0000000000025000
0x000000000014e9bc 0x000000000014e9bc R E 0x1000
# [...]
Thus, the virtual address of the executable segment is: vaddr = 0x25000
.
By parsing the proc maps file
This value can also be computed with the maps file of the victim process (in our case, the victim is vlc) by subtracting the address of the executable mapping from the address of the first mapping:
$ cat /proc/$(pidof vlc)/maps | grep 'libc-2.30.so'
7f668e288000-7f668e2ad000 r--p 00000000 fd:00 12981143 /usr/lib64/libc-2.30.so
7f668e2ad000-7f668e3fc000 r-xp 00025000 fd:00 12981143 /usr/lib64/libc-2.30.so
[...]
Here is the calculation:
vaddr = 0x7f668e2ad000 - 0x7f668e288000 = 0x25000
Step 3: get the libc executable mapping address into the victim process
Again, just use the victim proc maps file:
$ cat /proc/$(pidof vlc)/maps | grep 'libc-2.30.so' | grep x
7f668e2ad000-7f668e3fc000 r-xp 00025000 fd:00 12981143 /usr/lib64/libc-2.30.so
In this case, the base address is: mapping = 0x7f668e2ad000
.
Step 4: compute!
The address of __libc_dlopen_mode
into our victim process (vlc) is:
offset + (mapping - vaddr)
= 0x13ba20 + (0x7f668e2ad000 - 0x25000)
= 0x7f668e3c3a20
A little check with gdb:
$ gdb -p $(pidof vlc)
# [...]
gdb-peda$ info sym 0x7f668e3c3a20
__libc_dlopen_mode in section .text of /lib64/libc.so.6
Done!
Implementation into my Python proc lib
As I write this article, this strategy is not yet implemented into my proc library. I would like to limit Python dependencies but I’m too lazy to implement an ELF parser… Maybe the first implementation will slackly relies on the readelf
output parsing…