Shared object or .so
is no stranger to Linux user. The main purpose of shared object is a collection of code that can be reused by other project. Shared object can be loaded ( dynamic linked ) by executable file during runtime as oppose to static link with static library. The characteristic imply that executable may crash due to missing shared object. This text explain how dynamic linking is performed and later explore the possibilities of tapping the call between executable and shared object.
Interested reader can familiarize themself with Executable and Linking file (ELF) structure.
- Basic ELF Structure
- ELF manual
- ELF section explaination
Index
Source Code
Static Linking
Dynamic Linking
Summary
Source code
//animal.c
#include "zoo.h"
int main(){
cat();
dog();
}
//zoo.h
#include <stdio.h>
void cat();
void dog();
// library file
// zoo.c
#include "zoo.h"
void cat(){
printf("meow\n");
}
void dog(){
printf("woff\n");
}
Static linking
This section explain how static linking is performed as a preliminary to guide reader into how slightly complicated dynamic linking is done.
cc -o animal animal.c zoo.c
cc -c zoo.c
# create library
ar -cvq zoo.a zoo.o
cc -o animal animal.c zoo.a
First example may be more common to some users while second example valid as well. The difference between can be summarized as :
.o
objects : They are the output of the compiler and input to the linker/librarian.
.a
archives : They are groups of objects or static libraries and are also input into the linker.
Difference between Object file and Executable file
The result of static linking is obvious when dumping the assembly file of animal.o
and animal
1
2
3
4
5
6
7
8
9
10
11
12
13
animal.o: file format elf64-x86-64
Disassembly of section .text:
0000000000000000 <main>:
0: 55 push %rbp
1: 48 89 e5 mov %rsp,%rbp
4: b8 00 00 00 00 mov $0x0,%eax
9: e8 00 00 00 00 callq e <main+0xe>
e: b8 00 00 00 00 mov $0x0,%eax
13: e8 00 00 00 00 callq 18 <main+0x18>
18: 5d pop %rbp
19: c3 retq
1
2
3
4
5
6
7
8
9
10
11
animal: file format elf64-x86-64
000000000040052d <main>:
40052d: 55 push %rbp
40052e: 48 89 e5 mov %rsp,%rbp
400531: b8 00 00 00 00 mov $0x0,%eax
400536: e8 0c 00 00 00 callq 400547 <cat>
40053b: b8 00 00 00 00 mov $0x0,%eax
400540: e8 12 00 00 00 callq 400557 <dog>
400545: 5d pop %rbp
400546: c3 retq
Static linking has changed the address in Line 7, 9
9: e8 00 00 00 00 callq e <main+0xe>
13: e8 00 00 00 00 callq 18 <main+0x18>
400536: e8 0c 00 00 00 callq 400547 <cat>
400540: e8 12 00 00 00 callq 400557 <dog>
In example file, callq
with 0x0
as argument doesn't now make much sense since it would result in executable crash instantly. However, how does the resulting 0x0c
and 0x12
address is known in ELF file ? Upon close inspection of .rela.text
in animal object file shows that what address value linker fill in.
;readelf -r animal.o
Relocation section '.rela.text' at offset 0x570 contains 2 entries:
Offset Info Type Sym. Value Sym. Name + Addend
00000000000a 000a00000004 R_X86_64_PLT32 0000000000000000 cat - 4
000000000014 000b00000004 R_X86_64_PLT32 0000000000000000 dog - 4
Relocation section '.rela.eh_frame' at offset 0x5a0 contains 1 entries:
Offset Info Type Sym. Value Sym. Name + Addend
000000000020 000200000002 R_X86_64_PC32 0000000000000000 .text + 0
.rela.text
section clearly state that offset 0x0a
must be replaced with some value that is reference to cat()
. The process of changed address is known as "Relocation". Interested reader may read [1] for different relocation type.s
Dynamic Linking
The process of creating shared object and adding it to executable is as follow:
gcc -fPIC -c *.c
gcc -shared -Wl,-soname,libzoo.so -o libzoo.so zoo.o
gcc -Wl,-rpath,./ -I./ -L./ animal.c -lzoo -o animal
Dynamic Library entries symbols can be listed with:
nm -gC libzoo.so
0000000000201038 B __bss_start
00000000000006e5 T cat
00000000000006f7 T dog
Similarly, dependencies of executable can be shown using:
ldd animal
linux-vdso.so.1 => (0x00007fff5bef2000)
libzoo.so => ./libzoo.so (0x00007f6680d88000)
libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f66809ae000)
/lib64/ld-linux-x86-64.so.2 (0x00007f6680f8c000)
How to call from executable to entries in Shared object
objdump for animal executable
00000000004006dd <main>:
4006dd: 55 push %rbp
4006de: 48 89 e5 mov %rsp,%rbp
4006e1: b8 00 00 00 00 mov $0x0,%eax
4006e6: e8 f5 fe ff ff callq 4005e0 <cat@plt>
4006eb: b8 00 00 00 00 mov $0x0,%eax
4006f0: e8 cb fe ff ff callq 4005c0 <dog@plt>
4006f5: 5d pop %rbp
4006f6: c3 retq
4006f7: 66 0f 1f 84 00 00 00 nopw 0x0(%rax,%rax,1)
4006fe: 00 00
00000000004005c0 <dog@plt>:
4005c0: ff 25 5a 0a 20 00 jmpq *0x200a5a(%rip) # 601020 <_GLOBAL_OFFSET_TABLE_+0x20>
4005c6: 68 01 00 00 00 pushq $0x1 ;entries in .rela.plt
4005cb: e9 d0 ff ff ff jmpq 4005a0 <_init+0x28>
00000000004005e0 <cat@plt>:
4005e0: ff 25 4a 0a 20 00 jmpq *0x200a4a(%rip) # 601030 <_GLOBAL_OFFSET_TABLE_+0x30>
4005e6: 68 03 00 00 00 pushq $0x3
4005eb: e9 b0 ff ff ff jmpq 4005a0 <_init+0x28>
00000000004005a0 <__libc_start_main@plt-0x10>:
4005a0: ff 35 62 0a 20 00 pushq 0x200a62(%rip) # 601008 <_GLOBAL_OFFSET_TABLE_+0x8>
4005a6: ff 25 64 0a 20 00 jmpq *0x200a64(%rip) # 601010 <_GLOBAL_OFFSET_TABLE_+0x10>
4005ac: 0f 1f 40 00 nopl 0x0(%rax)
the execution flow of calling "cat()" is as follow
4006e6: e8 f5 fe ff ff callq 4005e0 <cat@plt>
4005c0: ff 25 5a 0a 20 00 jmpq *0x200a5a(%rip) # 601020 <_GLOBAL_OFFSET_TABLE_+0x20> ; *0x601020 point to nextline
4005c6: 68 01 00 00 00 pushq $0x1 ;entries in .rela.plt
4005eb: e9 b0 ff ff ff jmpq 4005a0 <_init+0x28>
4005a0: ff 35 62 0a 20 00 pushq 0x200a62(%rip) # 601008 <_GLOBAL_OFFSET_TABLE_+0x8>
4005a6: ff 25 64 0a 20 00 jmpq *0x200a64(%rip) # 601010 <_GLOBAL_OFFSET_TABLE_+0x10>
; pause
take a look to Global Osset Table before continue
objdump -s --start-address=0x601000 --stop-address=0x601030 animal
Offset | value |
---|---|
<GLOBALOFFSETTABLE+0x0> | 0x600e08 |
<GLOBALOFFSETTABLE+0x8> | 0x000000 |
<GLOBALOFFSETTABLE+0x10> | 0x000000 |
<GLOBALOFFSETTABLE+0x18> | 0x4005b6 |
<GLOBALOFFSETTABLE+0x20> | 0x4006c6 |
<GLOBALOFFSETTABLE+0x28> | 0x4005d6 |
<GLOBALOFFSETTABLE+0x30> | 0x4005e6 |
When looking at last intruction, jumping to <_GLOBAL_OFFSET_TABLE_+0x10>
will crash the process again due to zero address.
4006e6: e8 f5 fe ff ff callq 4005e0 <cat@plt>
4005c0: ff 25 5a 0a 20 00 jmpq *0x200a5a(%rip) # 601020 <_GLOBAL_OFFSET_TABLE_+0x20> ; *0x601020 point to nextline
4005c6: 68 01 00 00 00 pushq $0x1 ;entries in .rela.plt
4005eb: e9 b0 ff ff ff jmpq 4005a0 <_init+0x28>
4005a0: ff 35 62 0a 20 00 pushq 0x200a62(%rip) # 601008 <_GLOBAL_OFFSET_TABLE_+0x8>
4005a6: ff 25 64 0a 20 00 jmpq *0x200a64(%rip) # 601010 <_GLOBAL_OFFSET_TABLE_+0x10> ; *crash*
; pause
Static Analysis reveal that the execution flow will inevitably crash. The conclusion is obvious since dynamic linking require program to be running before linking can happen. In the next run, we will use debugger to break at 0x4005a0
and reveal Global Offset Table (GOT) content.
Upon inspecting in run time, this is content go GOT.
Offset | value |
---|---|
<GLOBALOFFSETTABLE+0x0> | 0x600e08 |
<GLOBALOFFSETTABLE+0x8> | 0x7ffff7ffe1c8 |
<GLOBALOFFSETTABLE+0x10> | 0x7ffff7df04e0 <_dl_runtime_resolve> |
<GLOBALOFFSETTABLE+0x18> | 0x7ffff7834dd0 <_libcstart_main> |
<GLOBALOFFSETTABLE+0x20> | 0x4006c6 |
<GLOBALOFFSETTABLE+0x28> | 0x4005d6 |
<GLOBALOFFSETTABLE+0x30> | 0x4005e6 |
The execution process can now continue without crashing. After pushing 0x7ffff7ffe1c8
to stack then call dl_runtime_resolve
. After runtime resolve;
Upon inspecting the source code for x86_64 architecture link, at the end of the function, it called to cat()
in libzoo.so.
asm
jmp *%r11 # Jump to function address.
`
Reader may wonder how did the execution flow ever continue after cat()
is call since ret
is never called. In fact, it is executed in cat().
# objdump -d libzoo.so
00000000000006e5 <cat>:
6e5: 55 push %rbp
6e6: 48 89 e5 mov %rsp,%rbp
6e9: 48 8d 3d 25 00 00 00 lea 0x25(%rip),%rdi # 715 <_fini+0x9>
6f0: e8 db fe ff ff callq 5d0 <puts@plt>
6f5: 5d pop %rbp
6f6: c3 retq
_DL_RUNTIME_RESOLVE
How it work ?
pushq $0x1 ;entries in .rela.plt
pushq 0x200a62(%rip) # 601008 <_GLOBAL_OFFSET_TABLE_+0x8>
all we know is this 2 parameter is pushed, but the exact search process shall be explore in later update.
Summary
Author : Ken Kawamoto
Source : slideshare