linux 动态解析,Linux 动态函式库解析[转]Linux -电脑资料
在本文的這個(gè)部分,針對(duì) Linux 系統(tǒng)是如何來(lái)辨別這些不同的可執(zhí)行檔,以及整體的執(zhí)行流程來(lái)作一個(gè)說(shuō)明,
ByWing
程序啟動(dòng)的流程
在 linux 的環(huán)境中最常見(jiàn)的可執(zhí)行檔的種類包括了 Script. 檔、Aout 格式的執(zhí)行檔、ELF 格式的執(zhí)行檔。在本文的這個(gè)部分,我會(huì)針對(duì) Linux 系統(tǒng)是如何來(lái)辨別這些不同的可執(zhí)行檔,以及整體的執(zhí)行流程來(lái)作一個(gè)說(shuō)明。
我在此大略說(shuō)明一下程序啟動(dòng)的流程,當(dāng)我們?cè)?shell 中輸入指令時(shí),會(huì)先去系統(tǒng)的路徑中來(lái)尋找是否有該可執(zhí)行檔存在,如果找不到的話,就會(huì)顯示出找不到該可執(zhí)行檔的訊息。如果找到的話,就會(huì)去呼叫 execve()來(lái)執(zhí)行該檔案,接下來(lái) execve() 會(huì)呼叫 System Call sys_execv(),這是在Linux 中 User Mode 透過(guò) 80 號(hào)中斷(int 80 ah=11)進(jìn)入 Kernel Mode 所執(zhí)行的第一個(gè)指令,之後在 Kernel 中陸續(xù)執(zhí)行 do_exec()、 prepare_binprm()、read_exec()、search_binary_handler(),而在 search_binary_handler() 函式中,會(huì)逐一的去檢查目前所執(zhí)行檔案的型態(tài)(看看是否為Script. File、aout 或 ELF 檔),不過(guò) Linux 所采用的方式是透過(guò)各個(gè)檔案格式的處理程序來(lái)決定目前的執(zhí)行檔所屬的處理程序。
如下圖,會(huì)先去檢驗(yàn)檔案是否為 Script. 檔,若是直進(jìn)入 Script. 檔的處理程序。若不是,則再進(jìn)入 Aout 檔案格式的處理程序,若該執(zhí)行檔為 Aout 的檔案格式便交由 Aout檔案格式的處理程序來(lái)執(zhí)行。如果仍然不是的話,便再進(jìn)入 ELF 檔案格式的處理程序,如果都找不到的話,則傳回錯(cuò)誤訊息。
由這種執(zhí)行的流程來(lái)看的話,如果 Linux Kernel 想要加入其他的執(zhí)行檔格式的話,就要在 search_binary_handler() 加入新的執(zhí)行檔的處理程序,這樣一旦新的執(zhí)行檔格式產(chǎn)生後,在 Linux 下要執(zhí)行時(shí),因?yàn)樵赿o_load_script、do_load_aout_binary、do_load_elf_binary都會(huì)傳回錯(cuò)誤,因此只有我們自己的 do_load_xxxx_binary 函式可以正確的接手整個(gè)執(zhí)行檔的處理流程,因此便可以達(dá)成新的檔案格式置入的動(dòng)作哩。
在函式 do_load_elf_binary () 執(zhí)行時(shí),首先會(huì)去檢視目前的檔案是否為 ELF 格式,如下程序碼clearcase/" target="_blank" >cccccc">if (elf_ex.e_ident[0] != 0x7f' 'strncmp(&elf_ex.e_ident[1], "ELF", 3) != 0) goto out;
便是去檢查該檔的前四個(gè) bytes 是否為 0x7f 加上 “ELF” (0x 45 0x4c 0x46),若非,則結(jié)束 do_load_elf_binary 的執(zhí)行。之後,便是去檢視我們之前提過(guò)的 e_type 屬性,來(lái)得知是否為 ET_EXEC(Executable File) 或是ET_DYN(Shared Object File) 這兩個(gè)值的其中之一if (elf_ex.e_type != ET_EXEC && elf_ex.e_type != ET_DYN) goto out;
如果都不是這兩個(gè)值之一,便結(jié)束 do_load_elf_binary 的執(zhí)行之後便是一連串讀取 ELF 檔表格的動(dòng)作,在此就不多說(shuō),有興趣的讀者可以自行參閱/usr/src/linux/fs/binfmt_elf.c 的內(nèi)容即可。
在此我們檢視一個(gè)執(zhí)行檔由啟動(dòng)到結(jié)束的完整流程,首先這個(gè)執(zhí)行檔具有如下的程序碼#include
? ? int main()
? ? {
? ? printf(" test ");
? ? }
然後,透過(guò)如下的編程過(guò)程gcc test.c ˉo test
我們?nèi)绻麢z視執(zhí)行檔的 ELF Header 可以得知它主要呼叫了 /lib/libc.so.6函式庫(kù)中以下的函式printf __deregister_frame_info __libc_start_main __register_frame_info
接下來(lái),我們便把程序的執(zhí)行流程大略整理如下,而 execve("./test", ["./test"], []) 執(zhí)行的流程,就是剛剛我們所提到的內(nèi)容,若不熟悉的讀者,可以再回頭看看剛剛的內(nèi)容,即可對(duì) execve("./test", ["./test"], []) 的執(zhí)行流程有大略的了解。在這里,我們會(huì)把整個(gè)執(zhí)行流程更完整的來(lái)檢視一遍。
首先,我們所在的執(zhí)行環(huán)境會(huì)透過(guò) execve("./test", ["./test"], []) 的函式呼叫來(lái)啟動(dòng) test 執(zhí)行檔。
呼叫 open("/etc/ld.so.cache", O_RDONLY),以唯讀模式開啟 ld.so.cache,這個(gè)檔案的功能是作為動(dòng)態(tài)函式庫(kù)的快取,它會(huì)記錄了目前系統(tǒng)中所存在的動(dòng)態(tài)函式庫(kù)的資訊以及這些函式庫(kù)所存在的位置。所以說(shuō),如果我們?cè)谙到y(tǒng)中安裝了新的函式庫(kù)時(shí),我們便需要去更新這個(gè)檔案的內(nèi)容,以使新的函式庫(kù)可以在我們的 Linux 環(huán)境中發(fā)生作用,我們可以透過(guò) ldconfig 這個(gè)指令來(lái)更新 ld.so.cache 的內(nèi)容。
呼叫 mmap(0, 9937, PROT_READ, MAP_PRIVATE, 3, 0),把 ld.so.cache 檔案映射到記憶體中,mmap 函式的宣告為 mmap(void *start, size_t length, int prot , int flags, int fd, off_t offset),在筆者的電腦上 ld.so.cache 的檔案大小為 9937 bytes,PROT_READ代表這塊記憶體位置是可讀取的,MAP_PRIVATE 則表示產(chǎn)生一個(gè)行程私有的 copy-on-write 映射,因此這個(gè)呼叫會(huì)把整個(gè) ld.so.cache 檔案映射到記憶體中,在筆者電腦上所傳回的映射記憶體起始位置為 0x40013000。
注: mmap(void *start, size_t length, int prot , int flags, int fd, off_t offset)代表我們要求在檔案 fd中,起始位置為offset去映射 length 長(zhǎng)度的資料,到記憶體位置 start ,而 prot 是用來(lái)描述該記憶體位置的保護(hù)權(quán)限(例如:讀、寫、執(zhí)行),flags用來(lái)定義所映射物件的型態(tài),例如這塊記憶體是否允許多個(gè) Process 同時(shí)映射到,也就是說(shuō)一旦有一個(gè) Process 更改了這個(gè)記憶體空間,那所有映射到這塊記憶體的Process 都會(huì)受到影響,或是 flag 設(shè)定為 Process 私有的記憶體映射,這樣就會(huì)透過(guò) copy-on-write 的機(jī)制,當(dāng)這塊記憶體被別的 Process 修改後,會(huì)自動(dòng)配置實(shí)體的記憶體位置,讓其他的 Process 所映射到的記憶體內(nèi)容與原本的相同,Linux 動(dòng)態(tài)函式庫(kù)解析[轉(zhuǎn)]Linux》(https://www.unjs.com)。(有關(guān)mmap的其它應(yīng)用,可參考本文最後的注一)
呼叫 open("/lib/libc.so.6", O_RDONLY),開啟 libc.so.6。
呼叫 read(3, "177ELF111331250202"..., 4096) 讀取libc.so.6的檔頭。
呼叫 mmap(0, 993500, PROT_READ|PROT_EXEC, MAP_PRIVATE, 3, 0),把 libc.so.6 映射到記憶體中,由檔頭開始映射 993500 bytes,若是使用 RedHat 6.1(或其它版本的 RedHat)的讀者或許會(huì)好奇 libc.so.6 所 link 到的檔案 libc-2.1.2.so 大小不是 4118715 bytes 嗎? 其實(shí)原本 RedHat 所附的 libc.so.6 動(dòng)態(tài)函式庫(kù)是沒(méi)有經(jīng)過(guò) strip 過(guò)的,如果經(jīng)過(guò) strip 後,大小會(huì)變?yōu)?1052428 bytes,而 libc.so.6 由檔頭開始在 993500 bytes 之後都是一些版本的資訊,筆者猜想應(yīng)該是這樣的原因,所以在映射檔時(shí),并沒(méi)有把整個(gè) libc.so.6 檔案映射到記憶體中,只映射前面有意義的部分。與映射 ld.so.cache 不同的是,除了 PROT_READ 屬性之外,libc.so.6 的屬性還多了PROT_EXEC,這代表了所映射的這塊記憶體是可讀可執(zhí)行的。在筆者的電腦中,libc.so.6 所映射到的記憶體起始位置為 0x40016000。
呼叫 mprotect(0x40101000, 30940, PROT_NONE),用來(lái)設(shè)定記憶體的使用權(quán)限,而 PROT_NONE 屬性是代表這塊記憶體區(qū)間(0x40101000—0x401088DC)是不能讀取、寫入與執(zhí)行的。
呼叫 mmap(0x40101000, 16384, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED, 3, 0xea000),映射 libc.so.6 由起始位置 0xea000 映射 16384bytes 到記憶體位置 0x40101000。
呼叫 mmap(0x40105000, 14556, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0),MAP_ANONYMOUS 表示沒(méi)有檔案被映射,且產(chǎn)生一個(gè)初始值全為 0 的記憶體區(qū)塊。
呼叫 munmap(0x40013000, 9937),把原本映射到 ld.so.cache 的記憶體解除映射(此時(shí)已把執(zhí)行檔所需的動(dòng)態(tài)函式庫(kù)都映射到記憶體中了)。
呼叫 personality(0),可以設(shè)定目前 Process 的執(zhí)行區(qū)間(execution domain),換個(gè)說(shuō)法就是 Linux 支援了多個(gè)執(zhí)行區(qū)間,而我們所設(shè)定的執(zhí)行區(qū)間會(huì)告訴 Linux 如何去映射我們的訊息號(hào)碼(signal numbers)到各個(gè)不同的訊息動(dòng)作(signal actions)中。這執(zhí)行區(qū)間的功能,允許 Linux 對(duì)其它Unix-Like 的操作系統(tǒng),提供有限度的二進(jìn)位檔支援。如這個(gè)例子中,personality(0) 的參數(shù)為 0,就是指定為 PER_LINUX 的執(zhí)行區(qū)間(execution domain)。#define PER_MASK (0x00ff) #define PER_LINUX (0x0000) #define PER_LINUX_32BIT (0x0000 | ADDR_LIMIT_32BIT) #define PER_SVR4 (0x0001 | STICKY_TIMEOUTS) #define PER_SVR3 (0x0002 | STICKY_TIMEOUTS) #define PER_SCOSVR3 (0x0003 | STICKY_TIMEOUTS | WHOLE_SECONDS) #define PER_WYSEV386 (0x0004 | STICKY_TIMEOUTS) #define PER_ISCR4 (0x0005 | STICKY_TIMEOUTS) #define PER_BSD (0x0006) #define PER_XENIX (0x0007 | STICKY_TIMEOUTS) #define PER_LINUX32 (0x0008) #define PER_IRIX32 (0x0009 | STICKY_TIMEOUTS) /* IRIX5 32-bit */ #define PER_IRIXN32 (0x000a | STICKY_TIMEOUTS) /* IRIX6 new 32-bit */ #define PER_IRIX64 (0x000b | STICKY_TIMEOUTS) /* IRIX6 64-bit */
呼叫 getpid(),取得目前 Process 的 Process ID。
呼叫 mmap(0, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0),傳回值為 0x400130,MAP_ANONYMOUS 表示沒(méi)有檔案被映射,且產(chǎn)生一個(gè)初始值全為 0 的記憶體區(qū)塊。
呼叫 write(1, " test ", 6),顯示字串在畫面上。
呼叫 munmap(0x40013000, 4096),解除記憶體位置0x40013000的記憶體映射。
呼叫 _exit(6),結(jié)束程序執(zhí)行。
在這段所舉的例子,只用到了一個(gè)函式庫(kù) libc.so.6,我們可以舉像是 RedHat 中 Telnet 指令為例,首先檢視他的 ELF Header==>libncurses.so.4 tgetent ==>libc.so.6 strcpy ioctl printf cfgetospeed recv connect ............┅ sigsetmask __register_frame_info close free
它主要呼叫了函式庫(kù) libncurses.so.4 的函式 tgetent,以及函式庫(kù) libc.so.6 中為數(shù)不少的函式,當(dāng)然我們也可以去檢視它執(zhí)行的流程,與之前只呼叫了 libc.so.6 的printf 函式來(lái)比較,我們可以發(fā)現(xiàn)它主要的不同就是去載入了 libncurses.so.4open("/usr/lib/libncurses.so.4", O_RDONLY) ; fstat(3, {st_mode=S_IFREG|0755, st_size=274985, ...}) ; read(3, "177ELF111331340335"..., 4096) ; mmap(0, 254540, PROT_READ|PROT_EXEC, MAP_PRIVATE, 3, 0); mprotect(0x40048000, 49740, PROT_NONE); mmap(0x40048000, 36864, PROT_READ|PROT_WRITE,MAP_PRIVATE|MAP_FIXED, 3, 0x31000); mmap(0x40051000, 12876, PROT_READ|PROT_WRITE,MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) ; close(3);
原文轉(zhuǎn)自:http://www.ltesting.net
總結(jié)
以上是生活随笔為你收集整理的linux 动态解析,Linux 动态函式库解析[转]Linux -电脑资料的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: 专门入侵检测linux叫什么,入侵检测系
- 下一篇: 手机控制linux电脑,通过Amora用