ELFs的食谱

图片


在俄语中,关于如何使用ELF文件(可执行文件和可链接格式-Linux和许多Unix系统的可执行文件的主要格式)的信息不足。 我们并不声称完全涵盖与精灵合作的所有可能情况,但我们希望这些信息以参考书和程序员和逆向工程师的食谱集的形式有用。


可以理解,基本的读者都对ELF格式很熟悉(否则,我们推荐系列文章Executable and Linkable Format 101 )。


削减部分将列出工作工具,描述的读取元信息,修改,验证和 繁殖 创建精灵,以及与有用材料的链接。


“我也是一个小精灵……红色的蓝色……精灵非常有耐心……蓝色的红色……我们是精灵!..红色的蓝色……只有魔术带来的麻烦……
(c)本和霍利小王国

工具


在大多数情况下,示例可以在Linux和Windows上运行。


在食谱中,我们将使用以下工具:


  • binutils集中的实用程序(objcopy,objdump,readelf,strip);
  • 雷达2框架 ;
  • 支持文件模板的十六进制编辑器(示例显示010Editor ,但是您可以使用例如free Veles );
  • Python和LIEF库;
  • 其他实用程序(链接在配方中)。

测试精灵


作为“实验”,我们将使用nutcake 的PieIsMyFav任务中关于crackmes.one的简单 ELF文件,但是任何“ elven”家族的代表都可以使用。 如果未在公共领域中找到具有所需特征的完成文件,则将提供创建此类elf的方法。


也可以在链接上找到免费精灵:



阅读,获取信息


文件类型,标题,部分


根据任务,可能会涉及以下内容:


  • 文件类型(DYN-库,EXEC-可执行文件,RELOC-可链接);
  • 目标体系结构(E_MACHINE-x86_64,x86,ARM等);
  • 应用程序入口点(Entry Point);
  • 部分信息。

010编辑


HEX编辑器010Editor提供了模板系统。 对于ELF文件,该模板称为ELF.bt ,这很奇怪,它位于“ 可执行文件”类别中(菜单模板-“可执行文件”)。
例如,感兴趣的可能是可执行文件的入口点(入口点)(记录在文件头中)。


图片


Readelf


readelf实用程序可以被视为获取有关ELF文件信息的事实上的标准。


  • 读取文件头:
    $ readelf -h simple 

团队成绩
 ELF Header: Magic: 7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00 Class: ELF64 Data: 2's complement, little endian Version: 1 (current) OS/ABI: UNIX - System V ABI Version: 0 Type: DYN (Shared object file) Machine: Advanced Micro Devices X86-64 Version: 0x1 Entry point address: 0x1070 Start of program headers: 64 (bytes into file) Start of section headers: 14800 (bytes into file) Flags: 0x0 Size of this header: 64 (bytes) Size of program headers: 56 (bytes) Number of program headers: 11 Size of section headers: 64 (bytes) Number of section headers: 30 Section header string table index: 29 

  • 阅读有关段和节的信息:
     $ readelf -l -W simple 

团队成绩

为了提高可读性,地址被转换为32位格式:


 Elf file type is DYN (Shared object file) Entry point 0x1070 There are 11 program headers, starting at offset 64 Program Headers: Type Offset VirtAddr PhysAddr FileSiz MemSiz Flg Align PHDR 0x000040 0x00000040 0x00000040 0x000268 0x000268 R 0x8 INTERP 0x0002a8 0x000002a8 0x000002a8 0x00001c 0x00001c R 0x1 [Requesting program interpreter: /lib64/ld-linux-x86-64.so.2] LOAD 0x000000 0x00000000 0x00000000 0x0005f8 0x0005f8 R 0x1000 LOAD 0x001000 0x00001000 0x00001000 0x00026d 0x00026d RE 0x1000 LOAD 0x002000 0x00002000 0x00002000 0x0001b8 0x0001b8 R 0x1000 LOAD 0x002de8 0x00003de8 0x00003de8 0x000258 0x000260 RW 0x1000 DYNAMIC 0x002df8 0x00003df8 0x00003df8 0x0001e0 0x0001e0 RW 0x8 NOTE 0x0002c4 0x000002c4 0x000002c4 0x000044 0x000044 R 0x4 GNU_EH_FRAME 0x002070 0x00002070 0x00002070 0x00003c 0x00003c R 0x4 GNU_STACK 0x000000 0x00000000 0x00000000 0x000000 0x000000 RW 0x10 GNU_RELRO 0x002de8 0x00003de8 0x00003de8 0x000218 0x000218 R 0x1 Section to Segment mapping: Segment Sections... 00 01 .interp 02 .interp .note.ABI-tag .note.gnu.build-id .gnu.hash .dynsym .dynstr .gnu.version .gnu.version_r .rela.dyn .rela.plt 03 .init .plt .plt.got .text .fini 04 .rodata .eh_frame_hdr .eh_frame 05 .init_array .fini_array .dynamic .got .got.plt .data .bss 06 .dynamic 07 .note.ABI-tag .note.gnu.build-id 08 .eh_frame_hdr 09 10 .init_array .fini_array .dynamic .got 

  • 阅读部分信息:
     $ readelf -S -W simple 

团队成绩

为了提高可读性,地址被转换为32位格式:


 There are 30 section headers, starting at offset 0x39d0: Section Headers: [Nr] Name Type Address Off Size ES Flg Lk Inf Al [ 0] NULL 00000000 000000 000000 00 0 0 0 [ 1] .interp PROGBITS 000002a8 0002a8 00001c 00 A 0 0 1 [ 2] .note.ABI-tag NOTE 000002c4 0002c4 000020 00 A 0 0 4 [ 3] .note.gnu.build-id NOTE 000002e4 0002e4 000024 00 A 0 0 4 [ 4] .gnu.hash GNU_HASH 00000308 000308 000024 00 A 5 0 8 [ 5] .dynsym DYNSYM 00000330 000330 0000d8 18 A 6 1 8 [ 6] .dynstr STRTAB 00000408 000408 0000a2 00 A 0 0 1 [ 7] .gnu.version VERSYM 000004aa 0004aa 000012 02 A 5 0 2 [ 8] .gnu.version_r VERNEED 000004c0 0004c0 000030 00 A 6 1 8 [ 9] .rela.dyn RELA 000004f0 0004f0 0000c0 18 A 5 0 8 [10] .rela.plt RELA 000005b0 0005b0 000048 18 AI 5 23 8 [11] .init PROGBITS 00001000 001000 000017 00 AX 0 0 4 [12] .plt PROGBITS 00001020 001020 000040 10 AX 0 0 16 [13] .plt.got PROGBITS 00001060 001060 000008 08 AX 0 0 8 [14] .text PROGBITS 00001070 001070 0001f2 00 AX 0 0 16 [15] .fini PROGBITS 00001264 001264 000009 00 AX 0 0 4 [16] .rodata PROGBITS 00002000 002000 000070 00 A 0 0 8 [17] .eh_frame_hdr PROGBITS 00002070 002070 00003c 00 A 0 0 4 [18] .eh_frame PROGBITS 000020b0 0020b0 000108 00 A 0 0 8 [19] .init_array INIT_ARRAY 00003de8 002de8 000008 08 WA 0 0 8 [20] .fini_array FINI_ARRAY 00003df0 002df0 000008 08 WA 0 0 8 [21] .dynamic DYNAMIC 00003df8 002df8 0001e0 10 WA 6 0 8 [22] .got PROGBITS 00003fd8 002fd8 000028 08 WA 0 0 8 [23] .got.plt PROGBITS 00004000 003000 000030 08 WA 0 0 8 [24] .data PROGBITS 00004030 003030 000010 00 WA 0 0 8 [25] .bss NOBITS 00004040 003040 000008 00 WA 0 0 1 [26] .comment PROGBITS 00000000 003040 00001c 01 MS 0 0 1 [27] .symtab SYMTAB 00000000 003060 000630 18 28 44 8 [28] .strtab STRTAB 00000000 003690 000232 00 0 0 1 [29] .shstrtab STRTAB 00000000 0038c2 000107 00 0 0 1 Key to Flags: W (write), A (alloc), X (execute), M (merge), S (strings), l (large) I (info), L (link order), G (group), T (TLS), E (exclude), x (unknown) O (extra OS processing required) o (OS specific), p (processor specific) 

  • 读取符号信息:
     $ readelf -s -W simple 

团队成绩

为了缩短可读性,输出被缩短:


 Symbol table '.dynsym' contains 9 entries: Num: Value Size Type Bind Vis Ndx Name 0: 00000000 0 NOTYPE LOCAL DEFAULT UND 1: 00000000 0 NOTYPE WEAK DEFAULT UND _ITM_deregisterTMCloneTable 2: 00000000 0 FUNC GLOBAL DEFAULT UND puts@GLIBC_2.2.5 (2) 3: 00000000 0 FUNC GLOBAL DEFAULT UND printf@GLIBC_2.2.5 (2) 4: 00000000 0 FUNC GLOBAL DEFAULT UND __libc_start_main@GLIBC_2.2.5 (2) 5: 00000000 0 NOTYPE WEAK DEFAULT UND __gmon_start__ 6: 00000000 0 FUNC GLOBAL DEFAULT UND __isoc99_scanf@GLIBC_2.7 (3) 7: 00000000 0 NOTYPE WEAK DEFAULT UND _ITM_registerTMCloneTable 8: 00000000 0 FUNC WEAK DEFAULT UND __cxa_finalize@GLIBC_2.2.5 (2) Symbol table '.symtab' contains 66 entries: Num: Value Size Type Bind Vis Ndx Name 0: 00000000 0 NOTYPE LOCAL DEFAULT UND 1: 000002a8 0 SECTION LOCAL DEFAULT 1 2: 000002c4 0 SECTION LOCAL DEFAULT 2 3: 000002e4 0 SECTION LOCAL DEFAULT 3 4: 00000308 0 SECTION LOCAL DEFAULT 4 5: 00000330 0 SECTION LOCAL DEFAULT 5 6: 00000408 0 SECTION LOCAL DEFAULT 6 7: 000004aa 0 SECTION LOCAL DEFAULT 7 .... 26: 00000000 0 SECTION LOCAL DEFAULT 26 27: 00000000 0 FILE LOCAL DEFAULT ABS crtstuff.c 28: 000010a0 0 FUNC LOCAL DEFAULT 14 deregister_tm_clones 29: 000010d0 0 FUNC LOCAL DEFAULT 14 register_tm_clones 30: 00001110 0 FUNC LOCAL DEFAULT 14 __do_global_dtors_aux 31: 00004040 1 OBJECT LOCAL DEFAULT 25 completed.7389 .... 

需要-W选项以增加控制台输出的宽度(默认为80个字符)。


列夫


您可以使用Python代码和LIEF库(不仅为Python提供API)读取标题和节信息:


 import lief binary = lief.parse("simple.elf") header = binary.header print("Entry point: %08x" % header.entrypoint) print("Architecture: ", header.machine_type) for section in binary.sections: print("Section %s - size: %s bytes" % (section.name, section.size) 

编译器信息


有关编译器和构建的信息,请参见.comment.note


objdump


 $ objdump -s --section .comment simple 

团队成绩
 simple: file format elf64-x86-64 Contents of section .comment: 0000 4743433a 20284465 6269616e 20382e32 GCC: (Debian 8.2 0010 2e302d39 2920382e 322e3000 .0-9) 8.2.0. 

Readelf


 $ readelf -p .comment simple 

团队成绩
 String dump of section '.comment': [ 0] GCC: (Debian 8.2.0-9) 8.2.0 

 $ readelf -n simple 

团队成绩
 Displaying notes found at file offset 0x000002c4 with length 0x00000020: Owner Data size Description GNU 0x00000010 NT_GNU_ABI_TAG (ABI version tag) OS: Linux, ABI: 3.2.0 Displaying notes found at file offset 0x000002e4 with length 0x00000024: Owner Data size Description GNU 0x00000014 NT_GNU_BUILD_ID (unique build ID bitstring) Build ID: dae0509e4edb79719a65af37962b74e4cf2a8c2e 

列夫


 import lief binary = lief.parse("simple") comment = binary.get_section(".comment") print("Comment: ", bytes(comment.content)) 

我将通过...计算您


精灵可以保存查找动态连接的库的路径。 为了在启动应用程序之前不设置系统变量LD_LIBRARY_PATH ,您可以简单地将此路径“嵌入”到ELF文件中。


为此,请使用.dynamic节中类型为DT_RPATHDT_RUNPATH (请参见文档中“运行时链接程序搜索目录”一章 )。


小心,年轻的开发人员,请不要“沉睡”您的项目目录!


RPATH如何出现?


在elf中出现RPATH记录的主要原因是用于搜索动态库的链接器-rpath选项。 像这样:


 $ gcc -L./lib -Wall -Wl,-rpath=/run/media/pablo/disk1/projects/cheat_sheets/ELF/lib/ -o test_rpath.elf bubble_main.c -lbubble 

该命令将在.dynamic节中创建一个RPATH记录,其值是/run/media/pablo/disk1/projects/cheat_sheets/ELF/lib/


Readelf


您可以从.dynamic部分(其中包含RPATH)查看元素,如下所示:


 $ readelf -d test_rpath.elf 

团队成绩

为了便于阅读,该命令的结果被减少:


 Dynamic section at offset 0x2dd8 contains 28 entries: Tag Type Name/Value 0x0000000000000001 (NEEDED) Shared library: [libbubble.so] 0x0000000000000001 (NEEDED) Shared library: [libc.so.6] 0x000000000000000f (RPATH) Library rpath: [/run/media/pablo/disk1/projects/cheat_sheets/ELF/lib/] 0x000000000000000c (INIT) 0x1000 0x000000000000000d (FINI) 0x11c8 .... 

列夫


使用LIEF库,您还可以读取elf中的RPATH记录:


 import lief from lief.ELF import DYNAMIC_TAGS elf = lief.parse("test_rpath.elf") if elf.has(DYNAMIC_TAGS.RPATH): rpath = next(filter(lambda x: x.tag == DYNAMIC_TAGS.RPATH, elf.dynamic_entries)) for path in rpath.paths: print(path) else: print("No RPATH in ELF") 

了解有关.dynamic部分的信息


检查精灵的安全性


自2011年以来,研究人员Tobias Klein( A Bug Hunter's Diary的作者 )的安全检查脚本checksec.sh尚未更新。 该ELF文件脚本检查选项RelRO(只读重定位),NX(非可执行堆栈),堆栈Canaries,PIE(位置独立可执行文件)的可用性,并使用readelf实用程序进行工作。


列夫


您可以在 膝关节 Python和LIEF(比祖先短一些,并带有对分开代码选项的附加验证):


 import lief from lief.ELF import DYNAMIC_TAGS, SEGMENT_TYPES def filecheck(filename): binary = lief.parse(filename) # check RELRO if binary.has(SEGMENT_TYPES.GNU_RELRO): print("+ Full RELRO") if binary.has(DYNAMIC_TAGS.BIND_NOW) else print("~ Partial RELRO") else: print("- No RELRO") # check for stack canary support print("+ Canary found") if binary.has_symbol("__stack_chk_fail") else print("- No canary found") # check for NX support (check X-flag for GNU_STACK-segment) print("+ NX enabled") if binary.has_nx else print("- NX disabled") # check for PIE support print("+ PIE enabled") if binary.is_pie else print("- No PIE") # check for rpath / run path print("+ RPATH") if binary.has(DYNAMIC_TAGS.RPATH) else print("- No RPATH") print("+ RUNPATH")if binary.has(DYNAMIC_TAGS.RUNPATH) else print("- No RUNPATH") # check separate-code option if set(binary.get_section('.text').segments) == set(binary.get_section('.rodata').segments): print("- Not Separated Code Sections") else: print("+ Separated Code Sections") filecheck('test_rpath.elf') 

拉达雷2


感谢dukebarman额外使用Radare2显示类似于checksec的信息:


 > r2 -ci~pic,canary,nx,crypto,stripped,static,relocs test_stack_proteck 

Elf的原始代码(ELF的二进制文件)


在某些情况下,不需要ELF结构形式的“精灵服装”,而仅需要“裸”可执行应用程序代码。


objcopy


编写固件的人员可能熟悉使用objcopy


 $ objcopy -O binary -S -g simple.elf simple.bin 

  • -S删除字符信息;
  • -g删除调试信息。

列夫


没魔术 只需获取已加载部分的内容并根据它们创建一个二进制文件:


 import lief from lief.ELF import SECTION_FLAGS, SECTION_TYPES binary = lief.parse("test") end_addr = 0 data = [] for section in filter(lambda x: x.has(SECTION_FLAGS.ALLOC) and x.type != SECTION_TYPES.NOBITS, binary.sections): if 0 < end_addr < section.virtual_address: align_bytes = b'\x00' * (section.virtual_address - end_addr) data.append(align_bytes) data.append(bytes(section.content)) end_addr = section.virtual_address + section.size with open('test.lief.bin', 'wb') as f: for d_bytes in data: f.write(d_bytes) 

拼凑-拆解函数名称


在使用C ++代码创建的ELF中,对函数名称进行修饰(修饰)以简化对相应类函数的搜索。 但是,在分析中读取此类名称不是很方便。


测试精灵


纳米


要以易于理解的形式表示名称,可以使用binutils集中的nm实用程序:


 #        $ nm -D demangle-test-cpp ... U _Unwind_Resume U _ZdlPv U _Znwm U _ZSt17__throw_bad_allocv U _ZSt20__throw_length_errorPKc #        $ nm -D --demangle demangle-test-cpp ... U _Unwind_Resume U operator delete(void*) U operator new(unsigned long) U std::__throw_bad_alloc() U std::__throw_length_error(char const*) 

列夫


使用LIEF库以分解形式显示符号名称:


 import lief binary = lief.parse("demangle-test-cpp") for symb in binary.symbols: print(symb.name, symb.demangled_name) 

精灵的组装,记录,修改


没有元信息的精灵


在将应用程序调试并发布到野外环境之后,删除元信息是有意义的:


  • 调试部分-在大多数情况下无用;
  • 变量和函数的名称-绝对不会影响最终用户(使反向操作稍微复杂化);
  • 节表-绝对不需要运行该应用程序(缺少它会使反向操作稍微复杂化)。

删除角色信息


字符信息是对象和功能的名称。 没有它,应用程序的反向操作将变得更加复杂。


剥离


在最简单的情况下,可以使用binutils集中的strip实用程序。 要删除所有字符信息,只需运行以下命令:


  • 对于可执行文件:
     $ strip -s simple 
  • 对于动态库:
     $ strip --strip-unneeded libsimple.so 

带状


要仔细删除字符信息(文件末尾包括不必要的零字节),可以使用ELFkickers套件中的sstrip实用程序。 要删除所有字符信息,只需运行以下命令:


 $ sstrip -z simple 

列夫


使用LIEF库,您还可以进行快速剥离(符号表已删除.symtab节):


 import lief binary = lief.parse("simple") binary.strip() binary.write("simple.stripped") 

删除分区表


如上所述,节表的存在/不存在不会影响应用程序的操作。 但是同时,如果没有节表,应用程序的反向操作将变得更加复杂。
我们将使用Python下的LIEF库以及删除节表示例


 import lief binary = lief.parse("simple") binary.header.numberof_sections = 0 binary.header.section_header_offset = 0 binary.write("simple.modified") 

修改和删除RPATH


chrpath,PatchELF


要在Linux下更改RPATH,可以使用chrpath实用程序(在大多数发行版中都可用)或PatchELF


  • 更改RPATH:


     $ chrpath -r /opt/my-libs/lib:/foo/lib test_rpath.elf 


     $ patchelf --set-rpath /opt/my-libs/lib:/foo/lib test_rpath.elf 

  • 删除RPATH:


     $ chrpath -d test_rpath.elf 


     $ patchelf --shrink-rpath test_rpath.elf 


列夫


LIEF库还允许您修改和删除RPATH记录。


  • 更改RPATH:


     import lief binary = lief.parse("test_rpath.elf") rpath = next(filter(lambda x: x.tag == lief.ELF.DYNAMIC_TAGS.RPATH, binary.dynamic_entries)) rpath.paths = ["/opt/my-lib/here"] binary.write("test_rpath.patched") 

  • 删除RPATH:


     import lief binary = lief.parse("test_rpath.elf") binary.remove(lief.ELF.DYNAMIC_TAGS.RPATH) binary.write("test_rpath.patched") 


角色信息混淆


要使应用程序复杂化,可以保存符号信息,但会使对象名称混乱。 我们使用seveb的来自crackme01的小精灵crackme01_32bit作为测试对象


LIEF库中的示例的简化版本如下所示:


 import lief binary = lief.parse("crackme01_32bit") for i, symb in enumerate(binary.static_symbols): symb.name = "zzz_%d" % i binary.write("crackme01_32bit.obfuscated") 

结果,我们得到:


 $ readelf -s crackme01_32bit.obfuscated ... Symbol table '.symtab' contains 78 entries: Num: Value Size Type Bind Vis Ndx Name 0: 00000000 0 NOTYPE LOCAL DEFAULT UND zzz_0 1: 08048154 0 SECTION LOCAL DEFAULT 1 zzz_1 2: 08048168 0 SECTION LOCAL DEFAULT 2 zzz_2 3: 08048188 0 SECTION LOCAL DEFAULT 3 zzz_3 4: 080481ac 0 SECTION LOCAL DEFAULT 4 zzz_4 5: 080481d0 0 SECTION LOCAL DEFAULT 5 zzz_5 6: 080482b0 0 SECTION LOCAL DEFAULT 6 zzz_6 7: 0804835a 0 SECTION LOCAL DEFAULT 7 zzz_7 8: 08048378 0 SECTION LOCAL DEFAULT 8 zzz_8 9: 080483b8 0 SECTION LOCAL DEFAULT 9 zzz_9 10: 080483c8 0 SECTION LOCAL DEFAULT 10 zzz_10 ... 

通过PLT / GOT进行功能替换


也称为ELF PLT感染


为了不进行复制粘贴,只需在主题上保留链接:



更改入口点


在创建补丁程序,安装挂钩和其他动态工具或调用隐藏功能时,它很有用。 作为实验,我们使用seveb的crackme01的小精灵crackme01_32bit


雷达2


radare2以记录模式(选项-w )启动-将对原始文件进行更改:


 $ ./crackme01_32bit Please enter the secret number: ^C $ r2 -w -nn crackme01_32bit [0x00000000]> .pf.elf_header.entry=0x0804860D [0x00000000]> q $ ./crackme01_32bit Nope. 

列夫


 import lief binary = lief.parse("crackme01_32bit") header = binary.header header.entrypoint = 0x0804860D binary.write("crackme01_32bit.patched") 

代码补丁


作为一个简单的测试,将novn91的crackmepal与crack一起使用 。 不带参数启动时,程序显示:


 $ ./crackmeMario usage <password> 

使用任意字符串参数启动时,它显示:


 ./crackmeMario qwerty try again pal. 

我们将打补丁,以便程序在启动时立即显示消息“好工作! 现在注册我!”


雷达2


Radee2可以修补其支持的任何格式。 在这种情况下,可以用文本格式描述补丁:


 # Rapatch for https://crackmes.one/crackme/5ccecc7e33c5d4419da559b3 !echo Patching crackme 0x115D : jmp 0x1226 

您可以使用以下命令应用此类补丁:


 $ r2 -P patch.txt crackmeMario 

阅读有关通过radare2修补代码的信息:



列夫


LIEF允许您在指定的虚拟地址处修补elf(覆盖字节)。 补丁可以是字节数组的形式,也可以是整数值:


 import lief binary = lief.parse("crackmeMario") binary.patch_address(0x115D, bytearray(b"\xe9\xc4\x00\x00\x00")) binary.write("crackmeMario.patched") 

应用补丁后,程序将输出:


 $ ./crackmeMario.patched good job! now keygen me! 

将部分添加到ELF


objcopy


objcopy允许您添加一个部分,但是该部分将不属于任何段,并且在应用程序启动时不会被加载到RAM中:


 $ objcopy --add-section .testme=data.zip \ --set-section-flags .testme=alloc,contents,load,readonly \ --change-section-address .testme=0x08777777 \ simple simple.patched.elf 

列夫


LIEF库允许您向现有的ELF中添加新节及其对应的段(loading loaded=True标志):


 import lief binary = lief.parse("simple") data = bytearray(b"\xFF" * 16) section = lief.ELF.Section(".testme", lief.ELF.SECTION_TYPES.PROGBITS) section += lief.ELF.SECTION_FLAGS.EXECINSTR section += lief.ELF.SECTION_FLAGS.ALLOC section.content = data binary.add(section, loaded=True) binary.write("simple.testme.lief") 

变更科


objcopy


objcopy允许用文件中的数据替换节的内容,以及更改节的虚拟地址和标志:


 $ objcopy --update-section .testme=patch.bin \ --change-section-address .testme=0x08999999 simple simple.testme.elf 

列夫


 import lief binary = lief.parse("simple") data = bytearray(b"\xFF" * 17) section = binary.get_section(".text") section.content = data binary.write("simple.patched") 

删除部分


objcopy


objcopy允许通过名称删除特定部分:


 $ objcopy --remove-section .testme simple.testme.elf simple.no_testme.elf 

列夫


使用LIEF库删除部分的过程如下所示:


 import lief binary = lief.parse("simple.testme.elf") binary.remove_section(".testme") binary.write("simple.no_testme") 

精灵容器


该食谱的灵感来自Gremlins和ELF魔术:如果ELF文件是容器,该怎么办? 。 还有一些关于Solaris的elfwrap实用程序的手册,它允许您从任意数据创建ELF文件,并且ELF格式仅用作容器。


让我们尝试在Python和LIEF中执行相同的操作。
不幸的是,目前,LIEF库无法从头开始创建elf文件,因此您需要提供帮助-创建一个空的ELF模板:


 $ echo "" | gcc -m32 -fpic -o empty.o -c -xc - $ gcc -m32 -shared -o libempty.so empty.o 

现在,您可以使用此模板来填充数据:


 import lief binary = lief.parse("libempty.so") filename = "crackme.zip" data = open(filename, 'rb').read() # Add section with zip-archive as content section = lief.ELF.Section() section.content = data section.name = ".%s"%filename binary.add(section, loaded=True) # Add symbol as a reference to zip-archive symb = lief.ELF.Symbol() symb.type = lief.ELF.SYMBOL_TYPES.OBJECT symb.binding = lief.ELF.SYMBOL_BINDINGS.GLOBAL symb.size = len(data) symb.name = filename symb.value = section.virtual_address binary.add_static_symbol(symb) binary.write("libdata.crackme.container") 

精灵“带拖车”


ELF格式不对文件中的数据施加限制,但不属于任何段。 因此,可以创建一个可执行文件,该文件将存储在ELF结构之后。 这是在运行时不会加载到RAM中的内容,但是会被写入磁盘,并且随时可以从磁盘读取。


  • IDA Pro在分析时不会考虑此数据

带预告片的文件结构示例
图片


雷达2


可以通过比较实际文件大小和计算文件大小来确定是否存在“预告片”:


 $ radare2 test.elf [0x00001040]> ?v $s 0x40c1 [0x00001040]> iZ 14699 

Readelf


readelf不会显示有关“预告片”的信息,但是可以手动计算:


 $ ls -l test.elf #   16577  $ readelf -h test.elf Start of section headers e_shoff 14704 Size of section headers e_shentsize 64 Number of section headers e_shnum 29 #  ELF-: e_shoff + ( e_shentsize * e_shnum ) = 16560 

列夫


LIEF库使您既可以检查“预告片”的存在,也可以添加它。 使用LIEF,一切看起来都非常简洁:


 import lief binary = lief.parse("test") # check if overlay exists print('ELF has overlay data') if binary.has_overlay else print("No overlay data") # add overlay data to ELF data = bytearray(b'\xFF'*17) binary.overlay = data binary.write('test.overlay') 

虚空精灵(从头开始的ELF)


在Internet上,您可以找到用于“手动”创建ELF文件的项目,而无需使用通用名称为“ ELF from scratch”的编译器和链接器:



熟悉这些项目会对ELF格式的吸收产生有益的影响。


最小的精灵


在文章中介绍了将精灵大小最小化的有趣实验:



简而言之,操作系统中的elf加载程序不会使用所有标头字段和段表,并且一些最少的可执行代码可以直接放在ELF标头结构中(该代码摘自第一篇文章):


 ; tiny.asm BITS 32 org 0x00010000 db 0x7F, "ELF" ; e_ident dd 1 ; p_type dd 0 ; p_offset dd $$ ; p_vaddr dw 2 ; e_type ; p_paddr dw 3 ; e_machine dd _start ; e_version ; p_filesz dd _start ; e_entry ; p_memsz dd 4 ; e_phoff ; p_flags _start: mov bl, 42 ; e_shoff ; p_align xor eax, eax inc eax ; e_flags int 0x80 db 0 dw 0x34 ; e_ehsize dw 0x20 ; e_phentsize db 1 ; e_phnum ; e_shentsize ; e_shnum ; e_shstrndx filesize equ $ - $$ 

组装并获得大小为... 45字节的ELF:


  $ nasm -f bin -o a.out tiny.asm $ chmod +x a.out $ ./a.out ; echo $? 42 $ wc -c a.out 45 a.out 

模式精灵


要使用LIEF库创建一个elf,可以执行以下步骤(请参见配方“ Elf-container”):


  • 以简单的ELF文件为模板;
  • 替换节的内容,添加新节;
  • 配置必要的参数(入口点,标志)。

而不是结论


在文章中添加内容后,我们发现它实际上就像是LIEF库的颂歌。 但这是没有计划的-我想展示如何使用其他工具处理ELF文件。


当然,这里有或没有需要的脚本-在注释中写一下。


参考文献


Source: https://habr.com/ru/post/zh-CN460247/


All Articles