Excerpt from phrack blog on ELF infection.
Much as the global offset table redirects position-independent address
calculations to absolute locations, the procedure linkage table
redirects position-independent function calls to absolute locations.
The link editor cannot resolve execution transfers (such as function
calls) from one executable or shared object to another. Consequently,
the link editor arranges to have the program transfer control to
entries in the procedure linkage table. On the SYSTEM V architecture,
procedure linkage tables reside in shared text, but they use addresses
in the private global offset table. The dynamic linker determines the
destinations’ absolute addresses and modifies the global offset
table’s memory image accordingly. The dynamic linker thus can redirect
the entries without compromising the position-independence and
sharability of the program’s text. Executable files and shared object
files have separate procedure linkage tables.
+ Figure 2-12: Absolute Procedure Linkage Table {*}
.PLT0:pushl got_plus_4
jmp *got_plus_8
nop; nop
nop; nop
.PLT1:jmp *name1_in_GOT
pushl $offset
jmp .PLT0@PC
.PLT2:jmp *name2_in_GOT
pushl $offset
jmp .PLT0@PC
…
+ Figure 2-13: Position-Independent Procedure Linkage Table
.PLT0:pushl 4(%ebx)
jmp *8(%ebx)
nop; nop
nop; nop
.PLT1:jmp *name1@GOT(%ebx)
pushl $offset
jmp .PLT0@PC
.PLT2:jmp *name2@GOT(%ebx)
pushl $offset
jmp .PLT0@PC
…
NOTE: As the figures show, the procedure linkage table instructions use
different operand addressing modes for absolute code and for position-
independent code. Nonetheless, their interfaces to the dynamic linker are
the same.
Following the steps below, the dynamic linker and the program “cooperate”
to resolve symbolic references through the procedure linkage table and the
global offset table.
1. When first creating the memory image of the program, the dynamic
linker sets the second and the third entries in the global offset
table to special values. Steps below explain more about these
values.
2. If the procedure linkage table is position-independent, the address
of the global offset table must reside in %ebx. Each shared object
file in the process image has its own procedure linkage table, and
control transfers to a procedure linkage table entry only from
within the same object file. Consequently, the calling function is
responsible for setting the global offset table base register before
calling the procedure linkage table entry.
3. For illustration, assume the program calls name1, which transfers
control to the label .PLT1.
4. The first instruction jumps to the address in the global offset
table entry for name1. Initially, the global offset table holds the
address of the following pushl instruction, not the real address of
name1.
5. Consequently, the program pushes a relocation offset (offset) on
the stack. The relocation offset is a 32-bit, non-negative byte
offset into the relocation table. The designated relocation entry
will have type R_386_JMP_SLOT, and its offset will specify the
global offset table entry used in the previous jmp instruction. The
relocation entry also contains a symbol table index, thus telling
the dynamic linker what symbol is being referenced, name1 in this
case.
6. After pushing the relocation offset, the program then jumps to
.PLT0, the first entry in the procedure linkage table. The pushl
instruction places the value of the second global offset table
entry (got_plus_4 or 4(%ebx)) on the stack, thus giving the dynamic
linker one word of identifying information. The program then jumps
to the address in the third global offset table entry (got_plus_8
or 8(%ebx)), which transfers control to the dynamic linker.
7. When the dynamic linker receives control, it unwinds the stack,
looks at the designated relocation entry, finds the symbol’s value,
stores the “real” address for name1 in its global offset table
entry, and transfers control to the desired destination.
8. Subsequent executions of the procedure linkage table entry will
transfer directly to name1, without calling the dynamic linker a
second time. That is, the jmp instruction at .PLT1 will transfer to
name1, instead of “falling through” to the pushl instruction.
The LD_BIND_NOW environment variable can change dynamic linking
behavior. If its value is non-null, the dynamic linker evaluates
procedure linkage table entries before transferring control to the
program. That is, the dynamic linker processes relocation entries of
type R_386_JMP_SLOT during process initialization. Otherwise, the
dynamic linker evaluates procedure linkage table entries lazily,
delaying symbol resolution and relocation until the first execution of
a table entry.
NOTE: Lazy binding generally improves overall application performance,
because unused symbols do not incur the dynamic linking overhead.
Nevertheless, two situations make lazy binding undesirable for some
applications. First, the initial reference to a shared object function
takes longer than subsequent calls, because the dynamic linker
intercepts the call to resolve the symbol. Some applications cannot
tolerate this unpredictability. Second, if an error occurs and the
dynamic linker cannot resolve the symbol, the dynamic linker will
terminate the program. Under lazy binding, this might occur at
arbitrary times. Once again, some applications cannot tolerate this
unpredictability. By turning off lazy binding, the dynamic linker
forces the failure to occur during process initialization, before the
application receives control.
”
To explain in more detail…
Shared library calls are treated special in executable objects because they
cannot be linked to the executable at compile time. This is due to the fact
that shared libraries are not available to the executable until runtime.
The PLT was designed to handle such cases like these. The PLT holds the code
responsible for calling the dynamic linker to locate these desired routines.
Instead of calling the real shared library routine in the executable, the
executable calls an entry in the PLT. It is then up to the PLT to resolve the
symbol it represents and do the right thing.
From the ELF specifications…
” .PLT1:jmp *name1_in_GOT
pushl $offset
jmp .PLT0@PC
”
This is the important info. This is the routine called instead of the library
call. name1_in_GOT originally starts off pointing to the following pushl
instruction. The offset represents a relocation (see the ELF specifications)
offset which has a reference to the symbol the library call represents. This
is used for the final jmp which jumps to the dynamic linker. The dynamic
linker then changes name1_in_GOT to point directly to the routine thus avoiding
dynamic linking a second time.
This summarizes the importance of the PLT in library lookups. It can be noted
that we can change name_in_GOT to point to our own code, thus replacing
library calls. If we save the state of the GOT before replacing, we can call
the old library routine and thus redirect any library call.
References : another good link -> http://althing.cs./secref/resources/plt-got.txt
|