How to get the position of function when the program is dynamic linking? The technique which is called lazy-binding isn’t so difficult to understand so that I want to make a note here …
The purpose of Lazy-Binding
When a program is dynamic linking, it will load library function only which necessary for it after it runs up, and this is also why called Lazy :). Where can we see the working of Lazy-binding?
objdump -d elf and you may see something likes
call <puts@plt>, that’s where it works.
How to get the real position of function
There is a table called
GOT(global offset table) with several sections. With
.got section, it stores the position of global variables. With
.got.plt section, it is the key how program get the real position of library function:
Generally speaking in layout of
- stores the address of
link_map: put all the library functioin into linked list
dl_runtime_resolve: find out the position (
When we dive into the detail of it.
call <puts@plt>, it will comes to
.got.plt to search for position (in the left hand side of the picture above). It still not gets the position at the first time, so
.got.plt will make request to
puts@plt. In first step,
jmp puts@GOT will make request back to
.got.plt and response with
puts@plt+6. What’s interesting is that
+6 is the length of
jmp put@GOT, so it would continue to execute the next line:
index is the argument we need for finding the function position (can be seen in third point above), so we push it into stack in advance, then we jump to start point of plt - plt0.
There are two things needed to be done in
plt0. First, push link list into the stack because it is also the argument we need for searching function position (the third point above). Now,
dl_runtime_resolve can help us to find the position.
In the part of
dl_runtime_resolve, it will can called a function
_fix_up to find the address and replace and replace
.got.plt with the real address of library function.
Second time when you
call <puts@plt> and come to upper middle block, the first step is still
jmp puts@GOT. However, previous
.got.plt has already been replaced with the real address of the function! This is how lazy-binding works.
More convenient the thing is, more dangerous the thing might be. Lazy-binding also means that we all have writing permission to GOT, we can apply this concept to complete GOT-hijacking. But it also comes up with another protection called
RELRO. And there are three options of
- disabled: Welcome guys, just writing on my face!
- partial: only
.got.pltcan be written.
- full: replace with function address while loading and change to read-only while running.
In the end, I want to emphasize again on the difference between dymanic linking and static linking. Static linking will make all the necessary library compiled before execution, while dynamic linking load the necessary library in execution. Therefore
- The advantage of static linking is that we sometimes we don’t need to compile glibc on system or don’t need external dependency on library. Static linking can help us save a lot of time. For pwner, static linking always can build a complete ropchain. But for developer, static linking must not be a good choice because when library needed to be updated, the developer will need to spend a lof of time on recompiling program.
- The advantage of dynamic linking program has already been so clear that even default setting of gcc is dynamic linking. However, it also cause to some security problem. For example,
LD_PRELOADis a issue always talked about in dynamic linking, and I would recommend this article The LD-PRELOAD trick for the detail about it.