導航:首頁 > 源碼編譯 > 編譯動態庫時會連接依賴庫嗎

編譯動態庫時會連接依賴庫嗎

發布時間:2024-10-06 09:17:23

❶ 關於動態庫 靜態庫 區別與使用 路徑查找等

一、引言

我們通常把一些公用函數製作成函數庫,供其它程序使用。
函數庫分為靜態庫和動態庫兩種。

通常情況下,對函數庫的鏈接是放在編譯時期(compile time)完成的。所有相關的對象文件(object file)與牽涉到的函數庫(library)被鏈接合成一個可執行文件(executable file)。程序在運行時,與函數庫再無瓜葛,因為所有需要的函數已拷貝到相應目錄下下。所以這些函數庫被成為靜態庫(static libaray),通常文件名為「libxxx.a」的形式。

其實,我們也可以把對一些庫函數的鏈接載入推遲到程序運行的時期(runtime)。這就是動態鏈接庫(dynamic link library)技術。

二、兩者區別:
a,靜態庫的使用需要:
1 包含一個對應的頭文件告知編譯器lib文件裡面的具體內容
2 設置lib文件允許編譯器去查找已經編譯好的二進制代碼

b,動態庫的使用:
程序運行時需要載入動態庫,對動態庫有依賴性,需要手動加入動態庫

c,依賴性:
靜態鏈接表示靜態性,在編譯鏈接之後, lib庫中需要的資源已經在可執行程序中了, 也就是靜態存在,沒有依賴性了
動態,就是實時性,在運行的時候載入需要的資源,那麼必須在運行的時候提供 需要的 動態庫,有依賴性, 運行時候沒有找到庫就不能運行了

d,區別:
簡單講,靜態庫就是直接將需要的代碼連接進可執行程序;動態庫就是在需要調用其中的函數時,根據函數映射表找到該函數然後調入堆棧執行。
做成靜態庫可執行文件本身比較大,但不必附帶動態庫
做成動態庫可執行文件本身比較小,但需要附帶動態庫
鏈接靜態庫,編譯的可執行文件比較大,當然可以用strip命令精簡一下(如:strip libtest.a),但還是要比鏈接動態庫的可執行文件大。程序運行時間速度稍微快一點。
靜態庫是程序運行的時候已經調入內存,不管有沒有調用,都會在內存里頭。靜態庫在程序編譯時會被連接到目標代碼中,程序運行時將不再需要該靜態庫。
其在編譯程序時若鏈接,程序運行時會在系統指定的路徑下搜索,然後導入內存,程序一般執行時間稍微長一點,但編譯的可執行文件比較小;動態庫是程序運行的時候需要調用的時候才裝入內存,不需要的時候是不會裝入內存的。
動態庫在程序編譯時並不會被連接到目標代碼中,而是在程序運行是才被載入,因此在程序運行時還需要動態庫存在。

三、動態鏈接庫的特點與優勢

首先讓我們來看一下,把庫函數推遲到程序運行時期載入的好處:

1. 可以實現進程之間的資源共享。

什麼概念呢?就是說,某個程序的在運行中要調用某個動態鏈接庫函數的時候,操作系統首先會查看所有正在運行的程序,看在內存里是否已有此庫函數的拷貝了。如果有,則讓其共享那一個拷貝;只有沒有才鏈接載入。這樣的模式雖然會帶來一些「動態鏈接」額外的開銷,卻大大的節省了系統的內存資源。C的標准庫就是動態鏈接庫,也就是說系統中所有運行的程序共享著同一個C標准庫的代碼段。

2. 將一些程序升級變得簡單。用戶只需要升級動態鏈接庫,而無需重新編譯鏈接其他原有的代碼就可以完成整個程序的升級。Windows 就是一個很好的例子。

3. 甚至可以真正坐到鏈接載入完全由程序員在程序代碼中控制。

程序員在編寫程序的時候,可以明確的指明什麼時候或者什麼情況下,鏈接載入哪個動態鏈接庫函數。你可以有一個相當大的軟體,但每次運行的時候,由於不同的操作需求,只有一小部分程序被載入內存。所有的函數本著「有需求才調入」的原則,於是大大節省了系統資源。比如現在的軟體通常都能打開若干種不同類型的文件,這些讀寫操作通常都用動態鏈接庫來實現。在一次運行當中,一般只有一種類型的文件將會被打開。所以直到程序知道文件的類型以後再載入相應的讀寫函數,而不是一開始就將所有的讀寫函數都載入,然後才發覺在整個程序中根本沒有用到它們。

靜態庫:在編譯的時候載入生成目標文件,在運行時不用載入庫,在運行時對庫沒有依賴性。
動態庫:在目標文件運行時載入,手動載入,且對庫有依賴性。

具體在開發中用到哪種庫,我覺得還是根據實際的內存大小,ROM大小,運行的速度等綜合考慮。

linux 靜態庫和動態庫編譯的區別

Linux庫有動態與靜態兩種,動態通常用.so為後綴,靜態用.a為後綴。例如:libhello.so libhello.a
為了在同一系統中使用不同版本的庫,可以在庫文件名後加上版本號為後綴,例如: libhello.so.1.0,由於程序連接默認以.so為文件後綴名。所以為了使用這些庫,通常使用建立符號連接的方式。
ln -s libhello.so.1.0 libhello.so.1
ln -s libhello.so.1 libhello.so

動態庫和靜態庫的區別:
當要使用靜態的程序庫時,連接器會找出程序所需的函數,然後將它們拷貝到執行文件,由於這種拷貝是完整的,所以一旦連接成功,靜態程序庫也就不再需要了。然而,對動態庫而言,就不是這樣。動態庫會在執行程序內留下一個標記『指明當程序執行時,首先必須載入這個庫。由於動態庫節省空間,linux下進行連接的預設操作是首先連接動態庫,也就是說,如果同時存在靜態和動態庫,不特別指定的話,將與動態庫相連接。

兩種庫的編譯產生方法:
第一步要把源代碼編繹成目標代碼。以下面的代碼hello.c為例,生成hello庫:

/* hello.c */
#include
void sayhello()
{
printf("hello,world\n");
}
用gcc編繹該文件,在編繹時可以使用任何全法的編繹參數,例如-g加入調試代碼等:
gcc -c hello.c -o hello.o
1.連接成靜態庫
連接成靜態庫使用ar命令,其實ar是archive的意思
$ar cqs libhello.a hello.o
2.連接成動態庫
生成動態庫用gcc來完成,由於可能存在多個版本,因此通常指定版本號:
$gcc -shared -Wl,-soname,libhello.so.1 -o libhello.so.1.0 hello.o
另外再建立兩個符號連接:
$ln -s libhello.so.1.0 libhello.so.1
$ln -s libhello.so.1 libhello.so
這樣一個libhello的動態連接庫就生成了。最重要的是傳gcc -shared 參數使其生成是動態庫而不是普通執行程序。
-Wl 表示後面的參數也就是-soname,libhello.so.1直接傳給連接器ld進行處理。實際上,每一個庫都有一個soname,當連接器發現它正在查找的程序庫中有這樣一個名稱,連接器便會將soname嵌入連結中的二進制文件內,而不是它正在運行的實際文件名,在程序執行期間,程序會查找擁有 soname名字的文件,%B

❸ linux動態庫和靜態庫的區別

動態鏈接庫和靜態鏈接庫一般是編譯集成一系列的介面(函數)
在程序源代碼編譯完成後通過編譯器編譯並通過鏈接器與這些庫進行鏈接
動態鏈接庫與靜態鏈接庫的區別在於鏈接器在進行鏈接時靜態庫會被直接編譯進程序里
而動態鏈接庫並不會,我們這里將這些鏈接庫稱作依賴(動態庫和靜態庫)
程序的運行需要這些依賴,程序在靜態鏈接後該程序本身便已包含該依賴
而動態鏈接後的程序本身本不包含該依賴,這些依賴需要執行者自行安裝進操作系統(動態庫、運行時庫)
程序運行時會動態地載入這些庫

linux上動態庫一般的後綴後為.so
靜態庫一般的後綴為.a
由於靜態鏈接會直接將庫編譯進程序里所以靜態編譯後的程序相較於動態鏈接所要大
這就是因為靜態鏈接會將鏈接庫編譯進程序里的原因,所以佔用就要大了
出於這種原因,靜態庫不易於維護與更新,如果鏈接庫中有實現有bug等需要更新則需要更新整個程序,因為靜態庫被編譯進程序中了
但動態庫就沒有這種情況了,因為動態庫是程序運行時動態載入的,所以我們只需要更新動態庫而不需要更新所有依賴該庫的程序(軟體)

另一方面,很多程序的開發都會使用到相同的鏈接庫,也就是很多程序(軟體)會有相同的依賴
如果將這些依賴全部靜態編譯的話將會造成存儲資源佔用過多而造成資源浪費
而使用動態庫的方式這些程序(軟體)則可以共享一個鏈接庫,而不需要每個程序都帶一個鏈接庫,這樣就大大地減少了存儲資源佔用空間

❹ 怎麼在命令行里用gcc去編譯連接一個程序

你的說法本身就有問題,gcc編譯的時候只能去鏈接 其他依賴文件和庫(靜態庫/動態庫)

動態庫:.so結尾,在運行時載入。
靜態庫:.a結尾,在編譯時載入。

例如編譯hello.c 輸出hello可執行文件

鏈接靜態庫:
gcc hello.c -L /home/lib -static -l mylib -o hello
-L參數可以向gcc的庫文件搜索路徑中添加新目錄
-static選項強制使用靜態鏈接庫
-l mylib -l後面是要靜態連接的庫(libhellos.a)

鏈接動態庫:
gcc -o hello hello.c -L. -lhello
-L後面的點為當前目錄
-lhello 是去鏈接libhello.so

❺ 二進制固件函數鎖定術-DYNAMIC

背景介紹

固件系統中的二進制文件依賴於特定的系統環境執行,針對固件的研究在沒有足夠的資金的支持下需要通過固件的模擬來執行二進制文件程序。依賴於特定硬體環境的固件無法完整模擬,需要hook掉其中依賴於硬體的函數。

LD_PRELOAD的鎖定

對於特定函數的鎖定技術分為動態注入鎖定和靜態注入鎖定兩種。靜態注入指的是通過修改靜態二進制文件中的內容來實現對特定函數的注入。動態注入則指的是在運行的過程中對特定的函數進行鎖定,動態注入鎖定一方面可以通過鎖定PLT表或者GOT表來實現,另一方面可以通過環境變數LD_PRELOAD來實現。

在《揭秘家用路由器0dayCVE挖掘》中作者針對D-linkDIR-605L(FW_113)路由器中的Web應用程序boa,通過hook技術鎖定apmib_init()和apmib_get()函數修復boa對硬體的依賴,使得qemu-static-mips可以模擬執行,在文中作者通過LD_PRELOAD環境變數實現對函數的鎖定。網上針對LD_PRELOAD的鎖定也有大量的描述。但是LD_PRELOAD仍舊不是普適的。

存在的問題

LD_PRELOAD環境變數的開關在編譯生成ulibc的時候指定,當關閉該選項的時候,無法使用LD_PRELOAD來預載入指定的動態鏈接庫文件。

固件的二進制文件中會將二進制文件中的Section信息去除掉,只保留下Segment的信息,使得無法通過patchelf來增加動態鏈接庫實現鎖定。patchelf支持對二進制文件的patch修改,或者添加執行過程中的鏈接lib。

本文方案思路

在ELF文件中,存在DYNAMICSegment,ld.so通過該段內容來載入程序運行過程中需要的lib文件,圖1為在IDA中反編譯後查看的DYNAMIC段的內容。

?

圖1Elf32_Dyn結構體數組

DYNAMIC段介紹

dynamic段開頭包含了由N個Elf32_Dyn組成的結構體,該結構體的D_tag代表了結構體的類型。d_un為通過union聯合的指針d_ptr或者對應的結構體的值d_val。

typedefstruct{Elf32_Swordd_tag;union{Elf32_Wordd_val;Elf32_Addrd_ptr;}d_un;}Elf32_Dyn;

d_tag欄位保存了類型的定義參數,詳見ELF(5)手冊。下面列出了動態鏈接器常用的比較重要的類型值

1-DT_NEEDED該類型的數據結構保存了程序所需要的共享庫的名字相對字元串表的偏移量

2-DT_SYMTAB動態符號表的地址,對應的節名為.dynsym

3-DT_HASH符號散列表的名稱,對應的節名為.hash又稱為.gnu.hash

4-DT_STRTAB符號字元串表的地址,對應的節名為.dynstr

5-DT_PLTGOT全局偏移表的地址

如上各個欄位對應到IDA中顯示如下,d_tag欄位代表了動態段的類型,當該值為1的時候。表示程序依賴的共享庫的名字,其對應的d_val表示了是要載入的lib的名稱在stringtable中的偏移。

共享庫文件名對應的結構體的類型值為0x01,如圖2所示,0x4001C4-0x4001E4保存有5個二進制文件所依賴的共享庫名字,其D_VAL的值是指向stringtable的偏移量。

?

圖2DT_NEEDED共享庫依賴圖

DYNAMIC段的定位

從二進制文件頭起始定位DYNAMIC段的流程如下:

ELF_HEADER->ProgramHeader->DYNAMICProgram

ELF_HEADER中保存有ProgramHeader的偏移

?

圖3ELFHeader

ProgramHeader中保存有DynamicSegment的相關結構體信息

?

圖4ProgramHeader

展開該結構體,可以找到Dynamic段在文件中的偏移量0x140h

ProgramHeader結構體

DT_NEEDED偽造

動態段由dynamic的結構體數組組成,dynamic的結構體定義如下:

?

在dynamic和elf_hash之間仍存在一段空餘空餘可填充空間。本文利用該段空間填充,實現特定lib的載入。

?

【----幫助網安學習,所有資料加weixin:yj009991,備注「掘金」獲取!】

①網安學習成長路徑思維導圖②60+網安經典常用工具包③100+SRC分析報告④150+網安攻防實戰技術電子書⑤最權威CISSP認證考試指南+題庫⑥超1800頁CTF實戰技巧手冊⑦最新網安大廠面試題合集(含答案)⑧APP客戶端安全檢測指南(安卓+IOS)

本文方案實驗與測試

利用dynamic和elf_hash之間的空餘區域,在該區域偽造出新的dynamic的一個數組。如下圖,不修改二進制文件大小,偽造增添ibcjson.so,使得二進制文件載入ibcjson.so。在ibcjson.so中編寫對應的鎖定函數。

?

思路:將原有的Elf32_Dyn數組元素依次後移,並在該數組的首部添加偽造的ibcjson.so,該lib的命名可以選用stringtable中的任一字元串即可。

?

核心移動代碼,將Elf32_Dyn中的元素依次後移一個,dynamic段dynamic[0]元素作為要偽造填充的數據,在本文的實驗中,將dynamic[0]中的value值加1。由於在MIPS下存在大小端兩種架構,在小端機器上的代碼解決大端架構的填充偽造時要注意大小端的轉換問題。

voidmove_dynamic(char*buf){intx=0;Elf32_Dyn*dyn=(Elf32_Dyn*)buf;while(1){if(dyn[x].d_tag==0&&dyn[x].d_un.d_ptr==0){break;}x++;if(x>100){printf("Errorbreak ");break;}}while(x--){//printf("theindexxis%x ",x);mem_cpy(&dyn[x],&dyn[x+1],8);}dyn[x+1].d_un.d_val=b2l(l2b(dyn[x+1].d_un.d_val)+1);}

測試環境

TOTOLinkN210RE中boa程序,鎖定函數參考DIR605A,鎖定apmib_init以及apmib_get

該固件的ld,關閉了LD_PRELOAD程序選項,未提供/etc/ld.preload

鎖定函數代碼,採用了《揭秘家用路由器CVE挖掘》提供的示例代碼如下,在原有的基礎上,增加printf來查看顯示是否鎖定成功。

#include<stdio.h>#defineMIB_HW_VER0x250#defineMIB_IP_ADDR170#defineMIB_CAPTCHA0x2C1intapmib_init(void){printf("helllo");return1;}intfork(void){return0;}voidapmib_get(intcode,int*value){switch(code){caseMIB_HW_VER:*value=1;break;caseMIB_IP_ADDR:*value=1;break;caseMIB_CAPTCHA:*value=1;break;}return;}

通過mips-linux-gcc進行編譯,這里注意mips-linux-gcc在編譯過程中的編譯文件的位置要位於參數之後(踩坑了!!!!)

mips-linux-gcc-Wall-fPIC-sharedapmib.c-oibcjson.so

通過如下代碼,給原有的boa二進制文件添加一個dynamic

#include<stdio.h>#include<stdio.h>#include<stdlib.h>#include"elf.h"#definePATCH"boa_patch"intb2l(intbe){return((be>>24)&0xff)|((be>>8)&0xFF00)|((be<<8)&0xFF0000)|((be<<24));}char*buf=NULL;intl2b(intle){return(le&0xff)<<24|(le&0xff00)<<8|(le&0xff0000)>>8|(le>>24)&0xff;}staticchar*_get_interp(char*buf){intx;//_Ehdr*hdr=(Elf_Ehdr*)buf;Elf_Phdr*phdr=(Elf_Phdr*)(buf+l2b(hdr->e_phoff));printf("thephdraddressis:0x%x0x%x0x%x0x%x ",phdr,l2b(hdr->e_phoff),buf,sizeof(hdr->e_phoff));for(x=0;x<hdr->e_phnum;x++){if(l2b(phdr[x].p_type)==PT_DYNAMIC){//Thereisadynamicloaderpresent,soloaditreturnbuf+l2b(phdr[x].p_offset);}}returnNULL;}intmem_cpy(char*src,char*dst,intlen){printf("thesrcaddris%x,%x ",src-buf,dst-buf);for(intx=0;x<len;x++){dst[x]=src[x];}return0;}/*1234temp=2strcpy(1,2)12strcpy()*/voidmove_dynamic(char*buf){intx=0;Elf32_Dyn*dyn=(Elf32_Dyn*)buf;//Elf32_Dyntmp_dyn;//mem_cpy()while(1){if(dyn[x].d_tag==0&&dyn[x].d_un.d_ptr==0){printf("thexis%d ",x);break;}x++;if(x>100){printf("Errorbreak ");break;}}while(x--){//printf("theindexxis%x ",x);mem_cpy(&dyn[x],&dyn[x+1],8);}dyn[x+1].d_un.d_val=b2l(l2b(dyn[x+1].d_un.d_val)+1);//FILE*fw=fopen("")}voidanalyse(char*buf){char*phdr_address=NULL;phdr_address=_get_interp(buf);printf("phdraddress:0x%x ",phdr_address-buf);move_dynamic(phdr_address);}voidsave_binary(char*buf,intsize){FILE*fw=fopen(PATCH,"wb");fwrite(buf,size,1,fw);fclose(fw);}intmain(intargc,char*argv[],char*envp[]){if(argc<2){printf("notenoughargc ");}FILE*fp=fopen(argv[1],"rb");fseek(fp,0,SEEK_END);intsize=ftell(fp);fseek(fp,0L,SEEK_SET);buf=malloc(size);fread(buf,size,1,fp);analyse(buf);save_binary(buf,size);free(buf);return0;}

Makefile如下

all:elf.hanalyse_ph.cgccanalyse_ph.c-m32-g3-oanalyse./analyseboa_real_n210

針對N210RE的測試截圖如下,通過exportLD_LIBRARY_PATH使得程序載入ibcjson.so,成功鎖定boa,輸出helllo

?

Ubuntu下rand函數鎖定測試

rand函數的頭文件是stdlib.h

編寫rand.c

#include<stdio.h>#include<stdlib.h>intmain(){inta=0;a=rand()%100;printf("theais%d ",a);return0;}//gcc-m32rand.c-orand

編寫rand_hook.so

#include<stdio.h>intrand(){printf("hook! ");return100;}//gcc-fPIC-Wall-shared-m32rand_hook.c-orand_hook.so

使用LD_PRELOAD測試,成功實現對該函數的鎖定

?

使用patch,針對dynamic段元素添加偽造,測試rand_patch,依賴的庫文件增加ibc.so.6,ibc.so.6需要通過exportLD_LIBRARY_PATH導入ibc.so.6的文件路徑

?

實現對rand的鎖定

?

總結

本文通過研究二進制文件中的dynamic段,通過修改二進制文件增加依賴共享庫,可以解決在模擬固件的過程時,固件缺少節信息且固件函數無法通過LD_PRELOAD鎖定的問題。該方案仍有不足之處,對於ld載入共享庫的依賴順序、共享庫鎖定的底層原理尚未深入探究。

參考

《揭秘家用路由器0day挖掘技術》

《二進制分析實戰》

更多靶場實驗練習、網安學習資料,請點擊這里>>

原文:https://juejin.cn/post/7104190931556892679
閱讀全文

與編譯動態庫時會連接依賴庫嗎相關的資料

熱點內容
lol壓縮秀 瀏覽:525
編譯燒錄失敗 瀏覽:270
安卓如何讓充電快起來 瀏覽:16
手機qqdisk文件夾 瀏覽:935
文件夾怎麼放進U盤 瀏覽:293
手機系統編譯語言 瀏覽:422
華為手機nfc加密卡怎麼復制 瀏覽:19
androidjni開發流程 瀏覽:881
如何解除vivo應用加密鎖 瀏覽:732
菜單創建文件夾方法 瀏覽:376
o型密封圈壓縮率 瀏覽:452
lpilinux認證 瀏覽:205
編譯文法原理是什麼 瀏覽:16
python基礎教程源代碼 瀏覽:521
編程兩個圈是什麼 瀏覽:433
程序員掉頭發怎麼辦 瀏覽:317
csgo電腦命令 瀏覽:590
pop和smtp伺服器地址 瀏覽:524
使用境外伺服器有什麼好處和弊端 瀏覽:314
如何教育孩子有禮貌的app 瀏覽:46