亚洲情人网-亚洲情人-亚洲欧洲综合-亚洲欧洲自拍-欧美专区亚洲

elf是什么意思(oneself是什么意思)

  • 生活
  • 2023-04-19 10:40

本文介紹了ELF的基本結構和內存加載的原理,并用具體案例來分析如何通過ELF特性實現HIDSbypass、加固/脫殼以及輔助進行binaryfuzzing。

前言

作為一個安全研究人員,ELF可以說是一個必須了解的格式,因為這關系到程序的編譯、鏈接、封裝、加載、動態執行等方方面面。有人就說了,這不就是一種文件格式而已嘛,最多按照SPEC實現一遍也就會了,難道還能復雜過FLV/MP4?曾經我也是這么認為的,直到我在日常工作時遇到了下面的錯誤:

$r2a.outSegmentationfault

作為一個開源愛好者,我的radare2經常是用master分支編譯的,經過在github中搜索,發現radare對于ELF的處理還有不少同類的問題,比如issue#17300以及issue#17379,這還只是近一個月內的兩個openissue,歷史問題更是數不勝數。

總不能說radare的開發者不了解ELF吧?事實上他們都是軟件開發和逆向工程界的專家。不止radare,其實IDA和其他反編譯工具也曾出現過各類ELF相關的bug。

說了那么多,只是為了引出一個觀點:ELF既簡單也復雜,值得我們去深入了解。網上已經有了很多介紹ELF的文章,因此本文不會花太多篇幅在SPEC的復制粘貼上,而是結合實際案例和應用場景去進行說明。

ELF101

ELF的全稱是ExecutableandLinkingFormat,這個名字相當關鍵,包含了ELF所需要支持的兩個功能——執行和鏈接。不管是ELF,還是Windows的PE,抑或是MacOS的Mach-O,其根本目的都是為了能讓處理器正確執行我們所編寫的代碼。

大局觀

在上古時期,給CPU運行代碼也不用那么復雜,什么代碼段數據段,直接把編譯好的機器碼一把梭燒到中斷內存空間,PC直接跳過來就執行了。但隨著時代變化,大家總不能一直寫匯編了,即便編譯器很給力,也會涉及到多人協作、資源復用等問題。這時候就需要一種可拓展(Portable)的文件標準,一方面讓開發者(編譯器/鏈接器)能夠高效協作,另一方面也需要系統能夠正確、安全地將文件加載到對應內存中去執行,這就是ELF的使命。

從大局上看,ELF文件主要分為3個部分:

ELFHeaderSectionHeaderTableProgramHeaderTable

其中,ELFHeader是文件頭,包含了固定長度的文件信息;SectionHeaderTable則包含了鏈接時所需要用到的信息;ProgramHeaderTable中包含了運行時加載程序所需要的信息,后面會進行分別介紹。

ELFHeader

ELF頭部的定義在elf/elf.h中(以glibc-2.27為例),使用POD結構體表示,內存可使用結構體的字段一一映射,頭部表示如下:

#defineEI_NIDENT(16)typedefstruct{unsignedchare_ident[EI_NIDENT];/*Magicnumberandotherinfo*/Elf32_Halfe_type;/*Objectfiletype*/Elf32_Halfe_machine;/*Architecture*/Elf32_Worde_version;/*Objectfileversion*/Elf32_Addre_entry;/*Entrypointvirtualaddress*/Elf32_Offe_phoff;/*Programheadertablefileoffset*/Elf32_Offe_shoff;/*Sectionheadertablefileoffset*/Elf32_Worde_flags;/*Processor-specificflags*/Elf32_Halfe_ehsize;/*ELFheadersizeinbytes*/Elf32_Halfe_phentsize;/*Programheadertableentrysize*/Elf32_Halfe_phnum;/*Programheadertableentrycount*/Elf32_Halfe_shentsize;/*Sectionheadertableentrysize*/Elf32_Halfe_shnum;/*Sectionheadertableentrycount*/Elf32_Halfe_shstrndx;/*Sectionheaderstringtableindex*/}Elf32_Ehdr;

注釋都很清楚了,挑一些比較重要的來說。其中e_type表示ELF文件的類型,有以下幾種:

*.o*.so

e_entry是程序的入口虛擬地址,注意不是main函數的地址,而是.text段的首地址_start。當然這也要求程序本身非PIE(-no-pie)編譯的且ASLR關閉的情況下,對于非ET_EXEC類型通常并不是實際的虛擬地址值。

其他的字段大多數是指定SectionHeader(e_sh)和ProgramHeader(e_ph)的信息。Section/ProgramHeaderTable本身可以看做是數組結構,ELF頭中的信息指定對應Table數組的位置、長度、元素大小信息。最后一個e_shstrndx表示的是sectiontable中的第e_shstrndx項元素,保存了所有sectiontable名稱的字符串信息。

SectionHeader

上節說了sectionheadertable是一個數組結構,這個數組的位置在e_shoff處,共有e_shnum個元素(即section),每個元素的大小為e_shentsize字節。每個元素的結構如下:

typedefstruct{Elf32_Wordsh_name;/*Sectionname(stringtblindex)*/Elf32_Wordsh_type;/*Sectiontype*/Elf32_Wordsh_flags;/*Sectionflags*/Elf32_Addrsh_addr;/*Sectionvirtualaddratexecution*/Elf32_Offsh_offset;/*Sectionfileoffset*/Elf32_Wordsh_size;/*Sectionsizeinbytes*/Elf32_Wordsh_link;/*Linktoanothersection*/Elf32_Wordsh_info;/*Additionalsectioninformation*/Elf32_Wordsh_addralign;/*Sectionalignment*/Elf32_Wordsh_entsize;/*Entrysizeifsectionholdstable*/}Elf32_Shdr;

其中sh_name是該section的名稱,用一個word表示其在字符表中的偏移,字符串表(.shstrtab)就是上面說到的第e_shstrndx個元素。ELF文件中經常使用這種偏移表示方式,可以方便組織不同區段之間的引用。

sh_type表示本section的類型,SPEC中定義了幾十個類型,列舉其中一些如下:

SHT_NULL:表示該section無效,通常第0個section為該類型SHT_PROGBITS:表示該section包含由程序決定的內容,如.text、.data、.plt、.gotSHT_SYMTAB/SHT_DYNSYM:表示該section中包含符號表,如.symtab、.dynsymSHT_DYNAMIC:表示該section中包含動態鏈接階段所需要的信息SHT_STRTAB:表示該section中包含字符串信息,如.strtab、.shstrtabSHT_REL/SHT_RELA:包含重定向項信息

雖然每個sectionheader的大小一樣(e_shentsize字節),但不同類型的section有不同的內容,內容部分由這幾個字段表示:

sh_offset:內容起始地址相對于文件開頭的偏移sh_size:內容的大小sh_entsize:有的內容是也是一個數組,這個字段就表示數組的元素大小

與運行時信息相關的字段為:

sh_addr:如果該section需要在運行時加載到虛擬內存中,該字段就是對應section內容(第一個字節)的虛擬地址sh_addralign:內容地址的對齊,如果有的話需要滿足sh_addr%sh_addralign=0sh_flags:表示所映射內容的權限,可根據SHF_WRITE/ALLOC/EXECINSTR進行組合

另外兩個字段sh_link和sh_info的含義根據section類型的不同而不同,如下表所示:

至于不同類型的section,有的是保存符號表,有的是保存字符串,這也是ELF表現出拓展性和復雜性的地方,因此需要在遇到具體問題的時候查看文檔去進行具體分析。

ProgramHeader

programheadertable用來保存程序加載到內存中所需要的信息,使用段(segment)來表示。與sectionheadertable類似,同樣是數組結構。數組的位置在偏移e_phoff處,每個元素(segmentheader)的大小為e_phentsize,共有e_phnum個元素。單個segmentheader的結構如下:

typedefstruct{Elf32_Wordp_type;/*Segmenttype*/Elf32_Offp_offset;/*Segmentfileoffset*/Elf32_Addrp_vaddr;/*Segmentvirtualaddress*/Elf32_Addrp_paddr;/*Segmentphysicaladdress*/Elf32_Wordp_filesz;/*Segmentsizeinfile*/Elf32_Wordp_memsz;/*Segmentsizeinmemory*/Elf32_Wordp_flags;/*Segmentflags*/Elf32_Wordp_align;/*Segmentalignment*/}Elf32_Phdr;

既然programheader的作用是提供用于初始化程序進程的段信息,那么下面這些字段就是很直觀的:

p_offset:該segment的數據在文件中的偏移地址(相對文件頭)p_vaddr:segment數據應該加載到進程的虛擬地址p_paddr:segment數據應該加載到進程的物理地址(如果對應系統使用的是物理地址)p_filesz:該segment數據在文件中的大小p_memsz:該segment數據在進程內存中的大小。注意需要滿足p_memsz>=p_filesz,多出的部分初始化為0,通常作為.bss段內容p_flags:進程中該segment的權限(R/W/X)p_align:該segment數據的對齊,2的整數次冪。即要求p_offset%p_align=p_vaddr。

剩下的p_type字段,表示該programsegment的類型,主要有以下幾種:

PT_NULL:表示該段未使用PT_LOAD:LoadableSegment,將文件中的segment內容映射到進程內存中對應的地址上。值得一提的是SPEC中說在programheader中的多個PT_LOAD地址是按照虛擬地址遞增排序的。PT_DYNAMIC:動態鏈接中用到的段,通常是RW映射,因為需要由interpreter(ld.so)修復對應的的入口PT_INTERP:包含interpreter的路徑,見下文PT_HDR:表示programheadertable本身。如果有這個segment的話,必須要在所有可加載的segment之前,并且在文件中不能出現超過一次。

在不同的操作系統中還可能有一些拓展的類型,比如PT_GNU_STACK、PT_GNU_RELRO等,不一而足。

小結

至此,ELF文件中相關的字段已經介紹完畢,主要組成也就是SectionHeaderTable和ProgramHeaderTable兩部分,整體框架相當簡潔。而ELF中體現拓展性的地方則是在Section和Segment的類型上(s_type和p_type),這兩個字段的類型都是ElfN_Word,在32位系統下大小為4字節,也就是說最多可以支持高達2^32-1種不同的類型!除了上面介紹的常見類型,不同操作系統或者廠商還能定義自己的類型去實現更多復雜的功能。

程序加載

在新版的ELF標準文檔中,將ELF的介紹分成了三部分,第一部分介紹ELF文件本身的結構,第二部分是處理器相關的內容,第三部分是操作系統相關的內容。ELF的加載實際上是與操作系統相關的,不過大部分情況下我們都是在GNU/Linux環境中運行,因此就以此為例介紹程序的加載流程。

Linux中分為用戶態和內核態,執行ELF文件在用戶態的表現就是執行execve系統調用,隨后陷入內核進行處理。

內核空間

內核空間對execve的處理其實可以單獨用一篇文章去介紹,其中涉及到進程的創建、文件資源的處理以及進程權限的設置等等。我們這里主要關注其中ELF處理相關的部分即可,實際上內核可以識別多種類型的可執行文件,ELF的處理代碼主要在fs/binfmt_elf.c中的load_elf_binary函數中。

對于ELF而言,Linux內核所關心的只有ProgramHeader部分,甚至大部分情況下只關心三種類型的Header,即PT_LOAD、PT_INTERP和PT_GNU_STACK。以3.18內核為例,load_elf_binary主要有下面操作:

對ELF文件做一些基本檢查,保證e_phentsize=sizeof(structelf_phdr)并且e_phnum的個數在一定范圍內;循環查看每一項programheader,如果有PT_INTERP則使用open_exec加載進來,并替換原程序的bprm->buf;根據PT_GNU_STACK段中的flag設置棧是否可執行;使用flush_old_exec來更新當前可執行文件的所有引用;使用setup_new_exec設置新的可執行文件在內核中的狀態;setup_arg_pages在棧上設置程序調用參數的內存頁;循環每一項PT_LOAD類型的段,elf_map映射到對應內存頁中,初始化BSS;如果存在interpreter,將入口(elf_entry)設置為interpreter的函數入口,否則設置為原ELF的入口地址;install_exec_creds(bprm)設置進程權限等信息;create_elf_tables添加需要的信息到程序的棧中,比如ELFauxiliaryvector;設置current->mm對應的字段;

從內核的處理流程上來看,如果是靜態鏈接的程序,實際上內核返回用戶空間執行的就是該程序的入口地址代碼;如果是動態鏈接的程序,內核返回用戶空間執行的則是interpreter的代碼,并由其加載實際的ELF程序去執行。

為什么要這么做呢?如果把動態鏈接相關的代碼也放到內核中,就會導致內核執行功能過多,內核的理念一直是能不在內核中執行的就不在內核中處理,以避免出現問題時難以更新而且影響系統整體的穩定性。事實上內核中對ELF文件結構的支持是相當有限的,只能讀取并理解部分的字段。

用戶空間

內核返回用戶空間后,對于靜態鏈接的程序是直接執行,沒什么好說的。而對于動態鏈接的程序,實際是執行interpreter的代碼。ELF的interpreter作為一個段,自然是編譯鏈接的時候加進去的,因此和編譯使用的工具鏈有關。對于Linux系統而言,使用的一般是GCC工具鏈,而interpreter的實現,代碼就在glibc的elf/rtld.c中。

interpreter又稱為dynamiclinker,以glibc2.27為例,它的大致功能如下:

將實際要執行的ELF程序中的內存段加載到當前進程空間中;將動態庫的內存段加載到當前進程空間中;對ELF程序和動態庫進行重定向操作(relocation);調用動態庫的初始化函數(如.preinit_array,.init,.init_array);將控制流傳遞給目標ELF程序,讓其看起來自己是直接啟動的;

其中參與動態加載和重定向所需要的重要部分就是ProgramHeaderTable中PT_DYNAMIC類型的Segment。前面我們提到在SectionHeader中也有一部分參與動態鏈接的section,即.dynamic。我在自己解析動態鏈接文件的時候發現,實際上.dynamicsection中的數據,和PT_DYNAMIC中的數據指向的是文件中的同一個地方,即這兩個entry的s_offset和p_offset是相同。每個元素的類型如下:

typedefstruct{Elf32_Swordd_tag;/*Dynamicentrytype*/union{Elf32_Wordd_val;/*Integervalue*/Elf32_Addrd_ptr;/*Addressvalue*/}d_un;}Elf32_Dyn;

d_tag表示實際類型,并且d_un和d_tag相關,可能說是很有拓展性了:)同樣的,標準中定義了幾十個d_tag類型,比較常用的幾個如下:

DT_NULL:表示_DYNAMIC的結尾DT_NEEDED:d_val保存了一個到字符串表頭的偏移,指定的字符串表示該ELF所依賴的動態庫名稱DT_STRTAB:d_ptr指定了地址保存了符號、動態庫名稱以及其他用到的字符串DT_STRSZ:字符串表的大小DT_SYMTAB:指定地址保存了符號表DT_INIT/DT_FINI:指定初始化函數和結束函數的地址DT_RPATH:指定動態庫搜索目錄DT_SONAME:SharedObjectName,指定當前動態庫的名字(logicalname)

其中有部分的類型可以和Section中的SHT_xxx類型進行類比,完整的列表可以參考ELF標準中的BookIII:OperatingSystemSpecific一節。

在interpreter根據DT_NEEDED加載完所有需要的動態庫后,就實現了完整進程虛擬內存映像的布局。在尋找某個動態符號時,interpreter會使用廣度優先的方式去進行搜索,即先在當前ELF符號表中找,然后再從當前ELF的DT_NEEDED動態庫中找,再然后從動態庫中的DT_NEEDED里查找。

因為動態庫本身是位置無關的(PIE),支持被加載到內存中的隨機位置,因此為了程序中用到的符號可以被正確引用,需要對其進行重定向操作,指向對應符號的真實地址。這部分我在之前寫的關于GOT,PLT和動態鏈接的文章中已經詳細介紹過了,因此不再贅述,感興趣的朋友可以參考該文章。

實際案例

有人也許會問,我看你bibi了這么多,有什么實際意義嗎?呵呵,本節就來分享幾個我認為比較有用的應用場景。

InterpreterHack

在滲透測試中,紅隊小伙伴們經常能拿到目標的后臺shell權限,但是遇到一些部署了HIDS的大企業,很可能在執行惡意程序的時候被攔截,或者甚至觸發監測異常直接被藍隊拔網線。這里不考慮具體的HIDS產品,假設現在面對兩種場景:

目標環境的可寫磁盤直接mount為noexec,無法執行代碼目標環境內核監控任何非系統路徑的程序的執行都會直接告警

不管什么樣的環境,我相信老紅隊都有辦法去繞過,這里我們運用上面學到的ELF知識,其實有一種更為簡單的解法,即利用interpreter。示例如下:

$cathello.c#include<stdio.h>intmain(){returnputs("hello!");}$gcchello.c-ohello$./hellohello!$chmod-xhello$./hellobash:./hello:Permissiondenied$/lib64/ld-linux-x86-64.so.2./hellohello!$strace/lib64/ld-linux-x86-64.so.2./hello2>&1|grepexecexecve("/lib64/ld-linux-x86-64.so.2",["/lib64/ld-linux-x86-64.so.2","./hello"],0x7fff1206f208/*9vars*/)=0

/lib64/ld-linux-x86-64.so.2本身應該是內核調用執行的,但我們這里可以直接進行調用。這樣一方面可以在沒有執行權限的情況下執行任意代碼,另一方面也可以在一定程度上避免內核對execve的異常監控。

利用(濫用)interpreter我們還可以做其他有趣的事情,比如通過修改指定ELF文件的interpreter為我們自己的可執行文件,可讓內核在處理目標ELF時將控制器交給我們的interpreter,這可以通過直接修改字符串表或者使用一些工具如patchelf來輕松實現。

對于惡意軟件分析的場景,很多安全研究人員看到ELF就喜歡用ldd去看看有什么依賴庫,一般ldd腳本實際上是調用系統默認的ld.so并通過環境變量來打印信息,不過對于某些glibc實現(如glibc2.27之前的ld.so),會調用ELF指定的interpreter運行,從而存在非預期命令執行的風險。

當然還有更多其他的思路可以進行拓展,這就需要大家發揮腦洞了。

加固/脫殼

與逆向分析比較相關的就是符號表,一個有符號的程序在逆向時基本上和讀源碼差不多。因此對于想保護應用程序的開發者而言,最簡單的防護***就是去除符號表,一個簡單的strip命令就可實現。strip刪除的主要是Section中的信息,因為這不影響程序的執行。去除前后進行diff對比可看到刪除的section主要有下面這些:

$diff011c1<Thereare35sectionheaders,startingatoffset0x1fdc:--->Thereare28sectionheaders,startingatoffset0x1144:32,39c32<[27].debug_arangesPROGBITS0000000000104d00002000001<[28].debug_infoPROGBITS0000000000106d00035000001<[29].debug_abbrevPROGBITS000000000013bd00010000001<[30].debug_linePROGBITS000000000014bd0000cd00001<[31].debug_strPROGBITS0000000000158a00029301MS001<[32].symtabSYMTAB000000000018200004801033494<[33].strtabSTRTAB00000000001ca00001f400001<[34].shstrtabSTRTAB00000000001e9400014500001--->[27].shstrtabSTRTAB0000000000104d0000f500001

其中.symtab是符號表,.strtab是符號表中用到的字符串。

僅僅去掉符號感覺還不夠,熟悉匯編的人放到反編譯工具中還是可以慢慢還原程序邏輯。通過前面的分析我們知道,ELF執行需要的只是ProgramHeader中的幾個段,SectionHeader實際上是不需要的,只不過在運行時動態鏈接過程會引用到部分關聯的區域。大部分反編譯工具,如IDA、Ghidra等,處理ELF是需要某些section信息來構建程序視圖的,所以我們可以通過構造一個損壞SectionTable或者ELFHeader令這些反編譯工具出錯,從而干擾逆向人員。

當然,這個***并不總是奏效,逆向人員可以通過動態調試把程序dump出來并對運行視圖進行還原。一個典型的例子是Android中的JNI動態庫,有的安全人員對這些so文件進行了加密處理,并且在.init/.initarray這些動態庫初始化函數中進行動態解密。破解這種加固***的策略就是將其從內存中復制出來并進行重建,重建的過程可根據segment對section進行還原,因為segment和section之間共享了許多內存空間,例如:

$readelf-lmain1...SectiontoSegmentmapping:SegmentSections...0001.interp02.interp.note.ABI-tag.note.gnu.build-id.gnu.hash.dynsym.dynstr.gnu.version.gnu.version_r.rel.dyn.rel.plt.init.plt.plt.got.text.fini.rodata.eh_frame_hdr.eh_frame03.init_array.fini_array.dynamic.got.got.plt.data.bss04.dynamic05.note.ABI-tag.note.gnu.build-id06.eh_frame_hdr0708.init_array.fini_array.dynamic.got

在SectiontoSegmentmapping中可以看到這些段的內容是跟對應section的內容重疊的,雖然一個segment可能對應多個section,但是可以根據內存的讀寫屬性、內存特征以及對應段的一般順序進行區分。

如果程序中有比較詳細的日志函數,我們還可以通過反編譯工具的腳本拓展去修改.symtab/.strtab段來批量還原ELF文件的符號,從而高效地輔助動態調試。

BinaryFuzzing

考慮這么一種場景,我們在分析某個IoT設備時發現了一個定制的ELF網絡程序,類似于httpd,其中有個靜態函數負責處理輸入數據。現在想要單獨對這個函數進行fuzz應該怎么做?直接從網絡請求中進行變異是一種***,但是網絡請求的效率太低,而且觸達該函數的程序邏輯也可能太長。

既然我們已經了解了ELF,那就可以有更好的辦法將該函數抽取出來進行獨立調用。在介紹ELF類型的時候其實有提到,可執行文件可以有兩種類型,即可執行類型(ET_EXEC)和共享對象(ET_DYN),一個動態鏈接的可執行程序默認是共享對象類型的:

$gcchello.c-ohello$readelf-hhello|grepTypeType:DYN(Sharedobjectfile)

而動態庫(.so)本身也是共享對象類型,他們之間的本質區別在于前者鏈接了libc并且定義了main函數。對于動態庫,我們可以通過dlopen/dlsym獲取對應的符號進行調用,因此對于上面的場景,一個解決方式就是修改目標ELF文件,并且將對應的靜態函數導出添加到dynamicsection中,并修復對應的ELF頭。

這個思想其實很早就已經有人實現了,比如lief的bin2lib。通過該***,我們就能將目標程序任意的函數抽取出來執行,比如hugsy就用這個方式復現了Exim中的溢出漏洞(CVE-2018-6789),詳見FuzzingarbitraryfunctionsinELFbinaries(中文翻譯)。

總結

本文主要介紹了32位環境下ELF文件的格式和布局,然后從內核空間和用戶空間兩個方向分析了ELF程序的加載過程,最后列舉了幾個依賴于ELF文件特性的案例進行具體分析,包括dynamiclinker的濫用、程序加固和反加固以及在二進制fuzzing中的應用。

ELF文件本身并不復雜,只有三個關鍵部分,只不過在section和segment的類型上保留了極大的拓展性。操作系統可以根據自己的需求在不同字段上實現和拓展自己的功能,比如Linux中通過dymamic類型實現動態加載。但這不是必須的,例如在Android中就通過ELF格式封裝了特有的.odex、.oat文件來保存優化后的dex。另外對于64位環境,大部分字段含義都是類似的,只是字段大小稍有變化(Elf32->Elf64),并不影響文中的結論。

作者:PansLabyrinth

猜你喜歡

主站蜘蛛池模板: 国产精品视频网址 | 婷婷丁香六月天 | 男人天堂a| 在线视频这里只有精品 | 男女男精品视频 | 五月婷婷丁香色 | 最新在线精品国自拍视频 | 中文国产成人精品少久久 | 羞羞动漫在线免费观看 | 国产成人一区二区三区在线视频 | 无遮免费网站在线入口 | 在线日韩欧美 | 亚洲精品美女久久久久 | 午夜欧美精品久久久久久久久 | 五月天激激婷婷大综合丁香 | 欧美日本亚洲国产一区二区 | 在线观看日本免费 | 四虎在线观看一区二区 | 全部免费国产潢色一级 | 小明免费视频一区二区 | 亚洲毛片在线 | 在线成人aa在线看片 | 亚洲 欧美 自拍 卡通 综合 | 波多野结衣高清在线播放 | 婷婷色激情 | 在线精品视频成人网 | 国内精品亚洲 | 国产伦精品一区二区三区免费迷 | www.亚洲欧美| 日本在线视频二区 | 99爱在线视频这里只有精品 | 5月色婷婷 | 精品日韩在线视频 | 武松金莲肉体交战在线观看 | 伊人激情久久综合中文字幕 | 波多野结衣视频网址 | 久久艹综合 | 2021亚洲天堂 | 羞羞视频官网 | 免费在线观看一区二区 | 男人天堂2019 |