---
# System prepended metadata

title: 'C 語言: 動態連結器'
tags: [NCKU Linux Kernel Internals, ' C語言']

---

---
tags: NCKU Linux Kernel Internals, C語言
---

# C 語言: 動態連結器

[你所不知道的 C 語言：動態連結器篇](https://hackmd.io/@sysprog/c-dynamic-linkage?type=view)

## Lazy binding
在使用 [PIC(Position Independent Code)](https://en.wikipedia.org/wiki/Position-independent_code) 且動態連結載入函式庫的程式中，程式中不能包含動態函式庫 symbol 的絕對位置，shared library 函式的位置要在執行時期才去載入。而程式所使用的到的函式並不一定總是會被呼叫到，所以就可以透過 lazy-binding 的方法，在library call 真正被呼叫時再去載入，這就是 lazy binding 的基本精神。


動態連結的 ELF binary 會透過 Global Offset Table (GOT) 來找到 shared library 中的相關函式。我們以基本的 hello world! 範例來探討其中的過程:

```cpp
#include <stdio.h>

int main(){
    printf("hello world\n");

    return 0;
}
```

你可以自行編譯程式，並透過 `objdump` 反組譯搭配 gdb 來理解其中的運行，簡單的理解連結的流程:
:::warning
這裡我使用 clang 來編譯
```
clang version 10.0.0-4ubuntu1 
Target: x86_64-pc-linux-gnu
Thread model: posix
InstalledDir: /usr/bin
```
而非 gcc
```
gcc (Ubuntu 9.3.0-17ubuntu1~20.04) 9.3.0
Copyright (C) 2019 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.  There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
```
後者編出來 elf 反組譯的結果和查到的文章所描述的不太一樣，需要再深入確定兩者在參數上的不同而導致的差異。
:::

```
Disassembly of section .plt:

0000000000401020 <.plt>:
  401020:	ff 35 e2 2f 00 00    	pushq  0x2fe2(%rip)        # 404008 <_GLOBAL_OFFSET_TABLE_+0x8>
  401026:	ff 25 e4 2f 00 00    	jmpq   *0x2fe4(%rip)        # 404010 <_GLOBAL_OFFSET_TABLE_+0x10>
  40102c:	0f 1f 40 00          	nopl   0x0(%rax)

0000000000401030 <printf@plt>:
  401030:	ff 25 e2 2f 00 00    	jmpq   *0x2fe2(%rip)        # 404018 <printf@GLIBC_2.2.5>
  401036:	68 00 00 00 00       	pushq  $0x0
  40103b:	e9 e0 ff ff ff       	jmpq   401020 <.plt>


...

0000000000401130 <main>:
  ...
  40114b:	e8 e0 fe ff ff       	callq  401030 <printf@plt>

```

1. 首先，整個函式呼叫指向一個 Procedure Linkage Table (PLT)，後者位於 elf 的 `.plt` 段，例如這裡是 `printf@plt`
2. `printf@plt` 中的指令會導向 GOT 表的 `.got.plt` 段，此段中紀錄的重要欄位包含 `link_map` 和 `dl_runtime_resolve`，前者是會用到的 library 所形成的 linked list，後者則是用來查詢需要的 function 所在位置的函式。GOT 表也紀錄每個 function 對應的地址(經查詢後)
```
$ objdump -M x86_64 -s a.out
Contents of section .got.plt:
 404000 203e4000 00000000 00000000 00000000   >@.............
 404010 00000000 00000000 36104000 00000000  ........6.@.....
```
> ![](https://i.imgur.com/xcTfF6p.png)
> https://www.slideshare.net/AngelBoy1/execution-50215114

4. 當函式是首次被呼叫時，那麼 GOT 表中改函式的位置會是原本的 PLT 的下一道指令，也就是下面看到的 `0x00401036`(位於 0x404018) 所對應的 `pushq $0x0` 指令。將 `index` 0 放入 stack，然後 `jmpq   401020 <.plt>`，跳到 `.plt` 的部份
```
(gdb) x/2x 0x404018
0x404018 <printf@got.plt>:	0x00401036	0x00000000
```
5. `.plt` 中會將 GOT 的 `link_map`(位於 0x404008) 加入 stack，然後轉去 GOT 所紀錄的地址 [`_dl_runtime_resolve`](https://github.com/lattera/glibc/blob/master/sysdeps/x86_64/dl-trampoline.h#L65)(位於 0x404010) 執行，後者就可以透過 `index` 和 `link_map` 找到 `printf` 在 shared library 的真正位置了!
 
:::info
`_dl_runtime_resolve_xsavec` 是根據編譯 config 不同的別名
:::
```
(gdb)  x/2x 0x404010
0x404010:	0xf7fe7bb0	0x00007fff

(gdb) disassemble 0x00007ffff7fe7bb0
Dump of assembler code for function _dl_runtime_resolve_xsavec:
...
```

6. 找到之後，`0x404018` 的位置會被替換成之前搜尋到的位置，因此之後再次呼叫 printf 時就不需要再重新搜尋
```
(gdb)  x/2x 0x404018
0x404018 <printf@got.plt>:	0xf7e1ae10	0x00007fff
(gdb) disassemble 0x00007ffff7e1ae10
Dump of assembler code for function __printf:
```


:::info
#### 關於 a.out 和 elf

:::

## Relro
回顧一下前面的流程，不難知道為了完成要求的 binding，GOT 的記憶體區段需要是可寫的，這造成了受到攻擊的風險。為避免這件事情的發生，同時要滿足可以做到動態連結，則可以改變 `ld` 的參數改成使用 `now` 選項 。下面引用至 [ld](https://linux.die.net/man/1/ld):
> **lazy**
When generating an executable or shared library, mark it to tell the dynamic linker to defer function call resolution to the point when the function is called (lazy binding), rather than at load time. Lazy binding is the default.

> **now**
When generating an executable or shared library, mark it to tell the dynamic linker to resolve all symbols when the program is started, or when the shared library is linked to using dlopen, instead of deferring function call resolution to the point when the function is first called.

簡單來說，相對於 defer 函式位址的載入直到真正呼叫函式時，預先載入所有需要的 library function 然後再將 GOT 重設為可讀，則可以避免前述的危險。

下面的參數可以編譯出  full RELRO 的程式。使得整個 GOT 表(包含 `.got` 和 `.got.plt`) 皆為不可寫
```
gcc -g -O0 -Wl,-z,relro,-z,now -o <binary_name> <source_code>
```
下面的參數則可以編譯出 partial RELRO 的程式。使得 `.got` 為 read only 而 `.got.plt` 則仍為可寫，不過這個做法顯然就不能解決我們在章節一開始提到的問題。

```
gcc -g -O0 -Wl,-z,relro -o <binary_name> <source_code>
```

在 [Hardening ELF binaries using Relocation Read-Only (RELRO)](https://www.redhat.com/en/blog/hardening-elf-binaries-using-relocation-read-only-relro) 可以看到相關的範例。

## Reference

> * [RELRO: RELocation Read-Only](https://hockeyinjune.medium.com/relro-relocation-read-only-c8d0933faef3)
> * [Linux動態連結庫函式載入之GOT與Lazy Binding](https://www.itread01.com/p/1391718.html)
> * [Hardening ELF binaries using Relocation Read-Only (RELRO)](https://www.redhat.com/en/blog/hardening-elf-binaries-using-relocation-read-only-relro)
> * [Lazy binding](https://rafaelchen.wordpress.com/2017/09/25/pwn%E7%9A%84%E4%BF%AE%E7%85%89%E4%B9%8B%E8%B7%AF-lazy-binding/)
## TODO

- [ ] 研究 [min-dl](https://github.com/jserv/min-dl)，瞭解如何實作一個小型的 dynamic linker


