Linux系統病毒研究之一
2024-07-21 02:37:22
供稿:網友
作者:Silvio Cesare
編譯:nixe0n
摘要:本文討論了一個修改ELF文件實現共享庫調用重定向的方法。修改可執行文件的程序連接表(PRocedure Linkage Table)可以使被感染的文件調用外部的函數。這要比修改LD_PRELOAD環境變量實現調用的重定向優越的多,首先不牽扯到環境變量的修改,其次是更為隱蔽。本文將提供一個基于x86/linux的實現
前言
?。牐犨@是nixe0n搜集的一組有關Linux系統下病毒的研究文章,沒有先后的次序。文中的代碼可能有破壞性,只能用于研究,假如你用于非法目的,后果自負。
感染ELF文件程序連接表實現共享庫調用的重定向
1.簡介
?。牐牨疚挠懻摿艘粋€修改ELF文件實現共享庫調用重定向的方法。修改可執行文件的程序連接表(Procedure Linkage Table)可以使被感染的文件調用外部的函數。這要比修改LD_PRELOAD環境變量實現調用的重定向優越的多,首先不牽扯到環境變量的修改,其次是更為隱蔽。本文將提供一個基于x86/Linux的實現。假如你對UNIX系統病毒比較感愛好請參考以下網址:
http://virus.beergrave.net (UNIX病毒郵件列表)
?。牐爃ttp://www.big.net.au/~silvio (作者主頁)
2.程序連接表(Procedure Linkage Table)
?。牐犗旅媸荅LF規范中,關于程序連接表的敘述:
程序連接表(PLT)
在ELF文件中,全局偏移表(Global Offset Table,GOT)能夠把位置無關的地址定位到絕對地址,程序連接表也有類似的作用,它能夠把位置無關的函數調用定向到絕對地址。連接編輯器(link editor)不能解決程序從一個可執行文件或者共享庫目標到另外一個的執行轉移。結果,連接編輯器只能把包含程序轉移控制的一些入口安排到程序連接表(PLT)中。在system V體系中,程序連接表位于共享正文中,但是它們使用私有全局偏移表(private global offset table)中的地址。動態連接器(例如:ld-2.2.2.so)會決定目標的絕對地址并且修改全局偏移表在內存中的影象。因而,動態連接器能夠重定向這些入口,而勿需破壞程序正文的位置無關性和共享特性??蓤绦形募凸蚕砟繕宋募懈髯缘某绦蜻B接表。
表2-12:使用絕對地址的程序連接表
.PLT0:pushl got_plus_4
?。牐牐牐牐爅mp *got_plus_8
?。牐牐牐牐爊op; nop
nop; nop
.PLT1:jmp *name1_in_GOT
pushl $offset
?。牐牐牐牐爅mp .PLT0@PC
.PLT2:jmp *name2_in_GOT
pushl $offset
?。牐牐牐牐爅mp .PLT0@PC
?。牐牐牐牐?......
表2-13:位置無關的程序連接表
.PLT0:pushl 4(%ebx)
?。牐牐牐牐爅mp *8(%ebx)
?。牐牐牐牐爊op; nop
?。牐牐爊op; nop
.PLT1:jmp *name1@GOT(%ebx)
?。牐牐牐牐爌ushl $offset
?。牐牐牐牐爅mp .PLT0@PC
.PLT2:jmp *name2@GOT(%ebx)
?。牐牐牐牐爌ushl $offset
?。牐牐牐牐爅mp .PLT0@PC
....
注重:從兩個表中可以看出,兩種方式的指令使用不同的操作數尋址模式。但是,它們和動態連接器的接口是一樣的。
下一步,動態連接器和程序本身使用程序連接表和全局偏移表共同解析符號引用。
當第一次建立程序的內存影象時,動態連接器會把全局偏移表的第二和第三個入口設置為特定的值。下面會對這些值進行介紹。
假如程序連接表是位置無關的,需要把全局偏移表地址保存在%ebx中。進程影象中的每個共享目標文件都有自己的程序連接表,而且程序的執行流程改變時,也只能跳轉到同一個目標文件的程序連接表入口。例如:一個程序foo,它的動態連接庫為bar.so,它們都有自己程序連接表,那么foo正文段調用某個程序連接表入口時,只能跳轉到foo文件自己的程序連接表,而不能轉到bar.so的程序連接表中。因此,在調用程序連接表入口之前,函數調用代碼應該設置全局偏移表的基址寄存器。
為了便于描述,我們假設程序要調用另一個目標文件的函數name1,因此首先需要把程序執行控制權轉移到標記為.PLT1的代碼處。
這段代碼的第一條指令就是,跳轉到name1在全局偏移表的入口地址。因為name1是在另一個目標文件中的調用,所以在初始化時,全局偏移表沒有保存name1的真實地址,而只是保存了這段代碼第二條指令pushl的地址。
因而,程序會接著執行第二條指令,在棧壓入一個重新定位的偏移值(offset)。這個重新定位的偏移值是重定位表中的一個32位的非負字節偏移值。這個特指的重定位入口是R_386_JMP_SLOT類型的,它的偏移值將指定先前jmp指令用到的全局偏移表的入口。
重定位入口還有一個符號表索引,告訴動態連接器哪個符號被引用,在這個例子中是name1。
在棧中壓入重定位偏移值以后,程序接著就跳轉到.PLT0,它是程序連接表的第一個入口。pushl指令在棧中壓入第二個全局偏移表的入口(got_plus_4或者4(%ebx)),從而給動態連接器一個單字識別信息。程序接著跳轉到全局偏移表的第三個入口中的地址(got_plus_8或者8(%ebx)),將控制權轉移給動態連接器。
當動態連接器獲得控制權,它就會展開棧,讀出指定的重定位入口,找出符號表的值,把name1的真正地址保存到全局偏移表的name1入口中,然后將控制權轉移給目的目標。
因此,假如再次調用name1,就會直接從程序連接表入口轉移到name1,而不必再次調用動態連接器。也就是說,.PLT1的jmp指令將轉移到name1,而不是接著執行push1指令。
LD_BIND_NOW環境變量能夠改變動態連接行為。假如這個環境變量不為空,動態連接器在把控制權交給程序之前會先為程序連接表賦值。也就是說,在進程初始化期間,動態連接器為R_386_JMP_SLOT類型的重定位入口賦值,以便在第一次調用時,不必通過動態連接器就能夠跳轉到目標地址。反之,假如這個環境變量為空,動態連接器就暫不為程序連接表入口賦值,不對符號進行解析和重定位,直到第一次調用一個程序連接表入口,才對其做相應的處理。這種方式叫作后期連接(lazy binding)方式。
注重:后期連接(lazy binding)方式一般會大大提高應用程序的性能,因為不必為解析無用的符號浪費動態連接器的開銷。不過,有兩種情況例外。第一,對一個共享目標函數進行初始化處理花費的時間比調用正式的執行時間長,因為動態連接器會攔截調用以解析符號,而這個函數功能又比較簡單;第二,假如發生錯誤和動態連接器無法解析符號,動態連接器就會終止程序。使用后期連接方式,這種錯誤可能會在程序執行過程中,隨時發生。而有些應用程序對這種不確定性有比較嚴格的限制。因此,需要關閉后期連接方式,在應用程序接受控制權之前,讓動態連接器處理進程初始化期間發生的這些錯誤。
?。牐犗旅鎸ζ浼毠澴鲆恍┙忉專?br />
因為在編譯時共享庫的調用不能被連接到程序中,所以需要對其做非凡處理。直到程序運行時,共享庫才是有效的。PLT就是為了處理這種情況。PLT保存調用動態連接器的有關代碼,由動態連接器對所需例程進行定位。
可執行目標是調用PLT的某個入口來實現對共享庫例程的調用,而不是直接調用共享庫例程。然后,由PLT解析符號表示什么以及進行其它操作。
下列代碼來自ELF規范:
?。牐?PLT1:jmp *name1_in_GOT
?。牐牐牐牐爌ushl $offset
?。牐牐牐牐爅mp .PLT0@PC
?。牐爮倪@段代碼中可以得到一些重要的信息。這是一個例程調用,而不是庫調用。進程初始化之后,name1_in_GOT指向后面的push1指令。offset代表一個重定位偏移值(參見ELF規范),它包含一個符號引用,這個符號表示這個庫調用,使后面的jmp指令能夠跳轉到動態連接器。為了避免下次調用這個共享庫例程時重復這個流程,動態連接器接著會修改name1_in_GOT,讓其直接指向這個例程,這樣就能夠節約再次調用的時間。
?。牐犐厦娴臄⑹隹偨Y了PLT在搜索庫調用時的重要性。因此,我們可以修改name_in_GOT使其指向我們自己的代碼,取代原先庫調用,實現病毒的傳染。假如在取代之前,我們保存GOT的狀態,那么還能夠重新調用原來的庫調用,而且可以實現任意庫調用的重定向。
3.感染ELF文件
?。牐牉榱藢崿F庫調用的重定向,需要在可執行目標文件中加入新的代碼。本文我們將不涉及這方面的問題,這在http://www.big.net.au/~silvio已經有專門的文章論述。
4.PLT重定向
?。牐犎肟邳c的算法如下:
把正文段標記為可寫
保存PLT(GOT)入口
使用新的庫調用地址代替PLT(GOT)入口
新的庫調用算法如下:
實現新的庫調用的功能
保存原來的PLT(GOT)入口
調用庫調用
再次保存PLT(GOT),假如它被修改了的
使用新的庫調用的地址代替PLT(GOT)入口
?。牐牉榱烁宄亟忉孭LT重定向是如何工作的,我們在此解析一段簡單的代碼。在這段代碼中被重定向的是printf,新的代碼是在printf輸出一個字符串之前,打印一條消息。
?。牐牶冒?,現在開始:
首先保存寄存器
"x60" /* pusha */
把正文段標記為rwx。因為正文段通常是不可寫的,所以為了能夠修改PLT,我們需要把它改為可寫的,通過mprotect系統調用。
"xb8x7dx00x00x00" /* movl $125,%eax */
"xbbx00x80x04x08" /* movl $text_start,%ebx */
"xb9x00x40x00x00" /* movl $0x4000,%ecx */
"xbax07x00x00x00" /* movl $7,%edx */
"xcdx80" /* int $0x