├── .gitignore ├── Hellf ├── ELF.py ├── __init__.py └── lib │ ├── __init__.py │ ├── consts.py │ ├── elf_structs.py │ ├── structures.py │ └── type.txt ├── README.md ├── img ├── logo.png └── twitter.gif ├── setup.py └── tests ├── Makefile ├── add_section.py ├── nothing.py ├── replace_text.py └── unstrip.py /.gitignore: -------------------------------------------------------------------------------- 1 | __pycache__/* 2 | build/ 3 | dist/ 4 | Hellf.egg-info/ 5 | -------------------------------------------------------------------------------- /Hellf/ELF.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | from Hellf.lib.elf_structs import * 3 | from Hellf.lib.consts import * 4 | 5 | from binascii import hexlify 6 | from struct import unpack 7 | 8 | # todo : 9 | # get segment by section 10 | # try a real way to localize .shstrtab 11 | 12 | class ELF: 13 | 14 | def __init__(selph, binary): 15 | selph.binary = binary 16 | 17 | if isinstance(binary, bytes): 18 | selph.elf_data = binary 19 | else: 20 | elf_hdl = open(binary, "rb") 21 | selph.elf_data = elf_hdl.read() 22 | 23 | # check architecture 24 | 25 | if selph.x86_or_x64() == X64: 26 | selph.archi_bits = X64 27 | selph.Elf64_Ehdr = Elf64_Ehdr_ST(selph) 28 | selph.ElfXX_Ehdr = selph.Elf64_Ehdr 29 | 30 | selph.Elf64_Phdr = Elf64_Phdr_LST(selph) 31 | selph.ElfXX_Phdr = selph.Elf64_Phdr 32 | 33 | selph.Elf64_Shdr = Elf64_Shdr_LST(selph) 34 | selph.ElfXX_Shdr = selph.Elf64_Shdr 35 | 36 | elif selph.x86_or_x64() == X32: 37 | selph.archi_bits = X32 38 | selph.Elf32_Ehdr = Elf32_Ehdr_ST(selph) 39 | selph.ElfXX_Ehdr = selph.Elf32_Ehdr 40 | 41 | selph.Elf32_Phdr = Elf32_Phdr_LST(selph) 42 | selph.ElfXX_Phdr = selph.Elf32_Phdr 43 | 44 | selph.Elf32_Shdr = Elf32_Shdr_LST(selph) 45 | selph.ElfXX_Shdr = selph.Elf32_Shdr 46 | 47 | else: 48 | print(error("wtf man")) 49 | exit(0) 50 | 51 | def __repr__(selph): 52 | return "Hellf obj \n" + \ 53 | f'{" " * 4}archi : {"x86" if selph.archi_bits == 1 else "x64"}\n' + \ 54 | f'{" " * 4}path : {selph.binary}\n' 55 | 56 | def x86_or_x64(selph): 57 | return unpack("B", selph.elf_data[4:5])[0] 58 | 59 | @property 60 | def pie(self): 61 | return True if self.ElfXX_Ehdr.e_type == ET_DYN else False 62 | 63 | 64 | 65 | def get_section_number(selph, name): 66 | i = 0 67 | for sh in selph.ElfXX_Shdr: 68 | sh_name = selph.ElfXX_Shdr[-1].data[sh.sh_name:].split(b"\x00")[0].decode("utf-8") # last sh describe .shstrtab which contains sections names 69 | if sh_name == name: 70 | return i 71 | else: 72 | i += 1 73 | 74 | def get_section_by_name(selph, name): 75 | for sh in selph.ElfXX_Shdr: 76 | sh_name = selph.ElfXX_Shdr[-1].data[sh.sh_name:].split(b"\x00")[0].decode("utf-8") # last sh describe .shstrtab which contains sections names 77 | if sh_name == name: 78 | return sh 79 | 80 | def get_section_name(selph, offset): 81 | return selph.ElfXX_Shdr[-1].data[offset:].split(b"\x00")[0].decode("utf-8") 82 | 83 | # todo but hard has if a section size is modified (if larger because if smaller size_padded will automatically fill with 00 when save will be called) 84 | # the offset of the other in Shdr have to be updated 85 | def update_section(selph, section=None, section_name=None, value=None): 86 | pass 87 | 88 | def add_section(selph, custom_section): 89 | padding_size = custom_section.sh_addralign - (custom_section.sh_size % custom_section.sh_addralign) 90 | custom_section.data += b"\x00" * padding_size 91 | 92 | # print(padding_size) 93 | 94 | # update nb of Shdr entry of Shdrt in Ehdr.e_shnum 95 | selph.ElfXX_Ehdr.e_shnum += 1 96 | # adding the custom section to the list 97 | selph.ElfXX_Shdr.append(custom_section) 98 | # saving the binary will add the section after every other sections, and so the Shdrt location will 99 | # be moved of the size of the data from the new section. 100 | selph.ElfXX_Ehdr.e_shoff += custom_section.sh_size + padding_size 101 | 102 | # we added padding for alignment so we must include it in section size. 103 | custom_section.sh_size += padding_size 104 | 105 | 106 | def save(selph, output_location): 107 | 108 | buff = selph.ElfXX_Ehdr.save() 109 | 110 | total = selph.ElfXX_Ehdr.e_ehsize 111 | # n_ElfXX_Ehdr = selph.ElfXX_Ehdr.save() 112 | pht_size = selph.ElfXX_Ehdr.e_phnum * selph.ElfXX_Ehdr.e_phentsize 113 | sht_size = selph.ElfXX_Ehdr.e_shnum * selph.ElfXX_Ehdr.e_shentsize 114 | 115 | # adding each ph to the ELF 116 | for i in range(selph.ElfXX_Ehdr.e_phnum): 117 | buff += selph.ElfXX_Phdr[i].save() 118 | 119 | # it may have some padding between last phdr and data section 120 | just_after_last_phdr = selph.ElfXX_Ehdr.e_phoff + selph.ElfXX_Ehdr.e_phnum * selph.ElfXX_Ehdr.e_phentsize 121 | 122 | 123 | # data_addr = selph.Elf64_Shdr[1].sh_offset # 0 is often (always ?) nulltype so using 1 which point to data first byte. 124 | # sometimes sections are not in the right order of offset in the sht 125 | 126 | # Section Headers: 127 | # [Nr] Name Type Addr Off Size ES Flags Lk Inf Al 128 | # [ 0] NULL 0000000000000000 00000000 00000000 0 0 0 0 129 | # [ 1] .text PROGBITS 0000000000401100 00001100 00000665 0 AX 0 0 16 130 | # [ 2] .interp PROGBITS 00000000004002a8 000002a8 0000001c 0 A 0 0 1 131 | # [ 3] .note.gnu.build-id NOTE 00000000004002c4 000002c4 00000024 0 A 0 0 4 132 | # [ 4] .note.ABI-tag NOTE 00000000004002e8 000002e8 00000020 0 A 0 0 4 133 | # [ 5] .gnu.hash GNU_HASH 0000000000400308 00000308 0000001c 0 A 6 0 8 134 | # [ 6] .dynsym DYNSYM 0000000000400328 00000328 000001b0 24 A 7 1 8 135 | # [ 7] .dynstr STRTAB 00000000004004d8 000004d8 00000152 0 A 0 0 1 136 | # [ 8] .gnu.version GNU_versym 000000000040062a 0000062a 00000024 2 A 6 0 2 137 | # [ 9] .gnu.version_r GNU_verneed 0000000000400650 00000650 00000060 0 A 7 2 8 138 | 139 | sorted_section_by_offset = sorted([ sh for sh in selph.ElfXX_Shdr ], key=lambda section: section.sh_offset) 140 | data_addr = sorted_section_by_offset[1].sh_offset 141 | 142 | buff += b"\x00" * (data_addr - just_after_last_phdr) 143 | 144 | # adding all data described by sections to the ELF 145 | 146 | # sometimes sections are not in the right order of offset in the sht 147 | actual_sh = sorted([ selph.ElfXX_Shdr[i] for i in range(selph.ElfXX_Ehdr.e_shnum) ], key=lambda sh: sh.sh_offset) 148 | 149 | for i in range(selph.ElfXX_Ehdr.e_shnum): 150 | 151 | # if sh data is modified and size less than previous size, 152 | # we need to pad with \x00 to keep the initial size 153 | # we need to keep the initial size else the offset for the next sh would change 154 | 155 | # custom section haven't got padded size attribute as sh_size is the real size so skipping this check 156 | 157 | if hasattr(actual_sh[i], "size_padded"): 158 | 159 | if len(actual_sh[i].data) != actual_sh[i].size_padded: 160 | actual_sh[i].data += b"\x00" * (actual_sh[i].size_padded - len(actual_sh[i].data)) 161 | 162 | buff += actual_sh[i].data 163 | 164 | # sometime there is padding between section, so just calculating the difference between two section and adding \x00 165 | if actual_sh[i].sh_type not in [0x00, 0x08]: # these type of section do not have space on file, only at runtime 166 | 167 | 168 | if i != selph.ElfXX_Ehdr.e_shnum - 1: 169 | 170 | # if a NOBITS section is between 2 PROGBITS the padding can be invald 171 | 172 | j = 1 173 | 174 | next_section = actual_sh[i+j] 175 | 176 | # so we are getting the next section which actually hold data (skipping NOBITS one) 177 | while next_section.sh_type in [0x00, 0x08]: 178 | j += 1 179 | next_section = actual_sh[i+j] 180 | 181 | pad_size = (next_section.sh_offset - (actual_sh[i].sh_offset + actual_sh[i].sh_size)) 182 | buff += b"\x00" * pad_size 183 | else: 184 | pad_size = (selph.ElfXX_Ehdr.e_shoff - (actual_sh[i].sh_offset + actual_sh[i].sh_size)) 185 | # print(pad_size) 186 | buff += b"\x00" * pad_size 187 | 188 | # adding each sh to the ELF 189 | for i in range(selph.ElfXX_Ehdr.e_shnum): 190 | buff += selph.ElfXX_Shdr[i].save() 191 | 192 | open(output_location, "wb").write(buff) 193 | -------------------------------------------------------------------------------- /Hellf/__init__.py: -------------------------------------------------------------------------------- 1 | from Hellf.ELF import ELF 2 | -------------------------------------------------------------------------------- /Hellf/lib/__init__.py: -------------------------------------------------------------------------------- 1 | from Hellf.lib.elf_structs import * 2 | from Hellf.lib.consts import * 3 | -------------------------------------------------------------------------------- /Hellf/lib/consts.py: -------------------------------------------------------------------------------- 1 | X32 = 1 2 | X64 = 2 3 | 4 | ET_DYN = 3 5 | -------------------------------------------------------------------------------- /Hellf/lib/elf_structs.py: -------------------------------------------------------------------------------- 1 | from .consts import * 2 | 3 | import ctypes as c 4 | from collections import OrderedDict 5 | from struct import unpack_from, pack 6 | from binascii import hexlify 7 | 8 | 9 | def typemap(cls): 10 | """ 11 | wrapper who is incharge of adding _fmt attribute holding the struct format for each field and the struct itself and also the struct size 12 | """ 13 | struct_fmt = "" 14 | for t, v in cls._fields_: 15 | 16 | if hasattr(v, "_length_"): 17 | fmt = str( v._length_) + v._type_._type_ 18 | else: 19 | fmt = v._type_ 20 | struct_fmt += fmt + " " 21 | setattr(cls, "_" + t + "_fmt", fmt) 22 | 23 | setattr(cls, "struct_fmt", struct_fmt) 24 | setattr(cls, "struct_size", c.sizeof(cls)) 25 | 26 | return cls 27 | 28 | 29 | class original_struct_list: 30 | 31 | def save(self): 32 | return b"".join([i.save() for i in self]) 33 | 34 | 35 | class orginal_struct: 36 | 37 | def __init__(cls, ELF_obj, count=None, next_sh=None): 38 | 39 | if ELF_obj.archi_bits == X32: 40 | ElfXX_Ehdr_ST = Elf32_Ehdr_ST 41 | ElfXX_Phdr_ST = Elf32_Phdr_ST 42 | Elfxx_Shdr_ST = Elf32_Shdr_ST 43 | 44 | elif ELF_obj.archi_bits == X64: 45 | ElfXX_Ehdr_ST = Elf64_Ehdr_ST 46 | ElfXX_Phdr_ST = Elf64_Phdr_ST 47 | Elfxx_Shdr_ST = Elf64_Shdr_ST 48 | 49 | 50 | if isinstance(cls, ElfXX_Ehdr_ST): 51 | cls.elf_data = ELF_obj.elf_data[:cls.struct_size] 52 | elif isinstance(cls, ElfXX_Phdr_ST): 53 | cls.elf_data = ELF_obj.elf_data[ELF_obj.ElfXX_Ehdr.e_phoff + ElfXX_Phdr_ST.struct_size * count : ELF_obj.ElfXX_Ehdr.e_phoff + ElfXX_Phdr_ST.struct_size * (count + 1)] 54 | 55 | elif isinstance(cls, Elfxx_Shdr_ST): 56 | cls.elf_data = ELF_obj.elf_data[ ELF_obj.ElfXX_Ehdr.e_shoff + Elfxx_Shdr_ST.struct_size * (count - 1) : ELF_obj.ElfXX_Ehdr.e_shoff + Elfxx_Shdr_ST.struct_size * count] 57 | 58 | for struct_field in cls.fields_names: 59 | fmt = getattr(cls, "_" + struct_field + "_fmt") 60 | offset = getattr(cls.__class__, struct_field).offset 61 | 62 | value = unpack_from(fmt, cls.elf_data, bufferoffset:=offset) 63 | 64 | if len(value) == 1: 65 | value = value[0] 66 | 67 | setattr(cls, struct_field, value) 68 | 69 | # getting data which will be in the Segment described by this segment header 70 | if isinstance(cls, ElfXX_Phdr_ST): 71 | setattr(cls, "data", ELF_obj.elf_data[cls.p_offset:cls.p_offset+cls.p_filesz]) 72 | 73 | # getting data described by this section headers 74 | if isinstance(cls, Elfxx_Shdr_ST): 75 | if cls.sh_type not in [0x00, 0x08]: # they do not have space on file only at runtime 76 | 77 | # size = next_sh - cls.sh_offset 78 | size = cls.sh_size 79 | 80 | setattr(cls, "data", ELF_obj.elf_data[cls.sh_offset:cls.sh_offset + size]) 81 | setattr(cls, "size_padded", size) 82 | else: 83 | setattr(cls, "data", b"") 84 | setattr(cls, "size_padded", 0) 85 | 86 | 87 | def __repr__(cls): 88 | msg = cls.struct_description + "\n" 89 | fmt = " {}:\t{}\n" 90 | for field in cls.fields_names: 91 | 92 | if hasattr(cls.allowed_fields[field], "_length_"): 93 | msg += fmt.format(field, " ".join(list(map(hex,getattr(cls, field))))) 94 | else: 95 | msg += fmt.format(field, hex(getattr(cls, field))) 96 | return msg 97 | 98 | def save(cls): 99 | saved_struct_bytes = b"" 100 | for struct_field in cls.fields_names: 101 | fmt = getattr(cls, "_" + struct_field + "_fmt") 102 | 103 | if not hasattr(cls.__class__.allowed_fields[struct_field], "_length_"): 104 | saved_struct_bytes += pack(fmt, getattr(cls, struct_field)) 105 | else: 106 | saved_struct_bytes += pack(fmt, *getattr(cls, struct_field)) 107 | return saved_struct_bytes#.decode("utf-8") 108 | 109 | 110 | 111 | ##################################### X64 112 | 113 | @typemap 114 | class Elf64_Ehdr_ST(c.Structure, orginal_struct): 115 | """ 116 | elf header structure 117 | """ 118 | 119 | struct_description = "ELF Header struct" 120 | 121 | # typedef struct 122 | # { 123 | # unsigned char e_ident[EI_NIDENT]; /* Magic number and other info */ 124 | # Elf64_Half e_type; /* Object file type */ 125 | # Elf64_Half e_machine; /* Architecture */ 126 | # Elf64_Word e_version; /* Object file version */ 127 | # Elf64_Addr e_entry; /* Entry point virtual address */ 128 | # Elf64_Off e_phoff; /* Program header table file offset */ 129 | # Elf64_Off e_shoff; /* Section header table file offset */ 130 | # Elf64_Word e_flags; /* Processor-specific flags */ 131 | # Elf64_Half e_ehsize; /* ELF header size in bytes */ 132 | # Elf64_Half e_phentsize; /* Program header table entry size */ 133 | # Elf64_Half e_phnum; /* Program header table entry count */ 134 | # Elf64_Half e_shentsize; /* Section header table entry size */ 135 | # Elf64_Half e_shnum; /* Section header table entry count */ 136 | # Elf64_Half e_shstrndx; /* Section header string table index */ 137 | # } Elf64_Ehdr; 138 | 139 | allowed_fields = OrderedDict([ 140 | ("e_ident", c.c_ubyte * 16), 141 | ("e_type", c.c_uint16), 142 | ("e_machine", c.c_uint16), 143 | ("e_version", c.c_uint32), 144 | ("e_entry", c.c_uint64), 145 | ("e_phoff", c.c_uint64), 146 | ("e_shoff", c.c_uint64), 147 | ("e_flags", c.c_uint32), 148 | ("e_ehsize", c.c_uint16), 149 | ("e_phentsize", c.c_uint16), 150 | ("e_phnum", c.c_uint16), 151 | ("e_shentsize", c.c_uint16), 152 | ("e_shnum", c.c_uint16), 153 | ("e_shstrndx", c.c_uint16), 154 | ]) 155 | 156 | fields_names = allowed_fields.keys() 157 | 158 | _fields_ = [(name, size) for name, size in allowed_fields.items()] 159 | 160 | def __init__(self, ELF_obj): 161 | c.Structure.__init__(self) 162 | orginal_struct.__init__(self, ELF_obj) 163 | 164 | 165 | 166 | class Elf64_Shdr_LST(list, original_struct_list): 167 | 168 | def __init__(self, ELF_obj): 169 | 170 | t = [] 171 | 172 | for i in range(ELF_obj.Elf64_Ehdr.e_shnum, 0, -1): 173 | 174 | if i == ELF_obj.Elf64_Ehdr.e_shnum: 175 | next_sh = ELF_obj.Elf64_Ehdr.e_shoff 176 | else: 177 | next_sh = t[ELF_obj.Elf64_Ehdr.e_shnum - i - 1].sh_offset 178 | 179 | t.append(Elf64_Shdr_ST(ELF_obj, count=i, next_sh=next_sh)) 180 | 181 | list.__init__(self, t[::-1]) 182 | 183 | 184 | 185 | 186 | @typemap 187 | class Elf64_Shdr_ST(c.Structure, orginal_struct): 188 | """ 189 | sections header structure 190 | """ 191 | struct_description = "ELF Sections header struct" 192 | 193 | # typedef struct 194 | # { 195 | # Elf64_Word sh_name; /* Section name (string tbl index) */ 4 196 | # Elf64_Word sh_type; /* Section type */ 4 197 | # Elf64_Xword sh_flags; /* Section flags */ 8 198 | # Elf64_Addr sh_addr; /* Section virtual addr at execution */ 8 199 | # Elf64_Off sh_offset; /* Section file offset */ 8 200 | # Elf64_Xword sh_size; /* Section size in bytes */ 8 201 | # Elf64_Word sh_link; /* Link to another section */ 4 202 | # Elf64_Word sh_info; /* Additional section information */ 4 203 | # Elf64_Xword sh_addralign; /* Section alignment */ 8 204 | # Elf64_Xword sh_entsize; /* Entry size if section holds table */ 8 205 | # } Elf64_Shdr; 206 | 207 | allowed_fields = OrderedDict([ 208 | ("sh_name" , c.c_uint32), 209 | ("sh_type" , c.c_uint32), 210 | ("sh_flags" , c.c_uint64), 211 | ("sh_addr" , c.c_uint64), 212 | ("sh_offset" , c.c_uint64), 213 | ("sh_size" , c.c_uint64), 214 | ("sh_link" , c.c_uint32), 215 | ("sh_info" , c.c_uint32), 216 | ("sh_addralign" , c.c_uint64), 217 | ("sh_entsize" , c.c_uint64) 218 | ]) 219 | 220 | fields_names = allowed_fields.keys() 221 | _fields_ = [(name, size) for name, size in allowed_fields.items()] 222 | 223 | def __init__(self, ELF_obj=None, count=None, next_sh=None): 224 | c.Structure.__init__(self) 225 | 226 | # we don't want to have the struct field automatically filled as we filled them ourself 227 | # getting the Shdr automatically by parsing an ELF 228 | if count != None and next_sh != None and ELF_obj != None: 229 | orginal_struct.__init__(self, ELF_obj, count=count, next_sh=next_sh) 230 | 231 | 232 | class Elf64_Phdr_LST(list, original_struct_list): 233 | 234 | def __init__(self, ELF_obj): 235 | 236 | list.__init__(self, [ Elf64_Phdr_ST(ELF_obj, i) for i in range(ELF_obj.Elf64_Ehdr.e_phnum)]) 237 | 238 | 239 | @typemap 240 | class Elf64_Phdr_ST(c.Structure, orginal_struct): 241 | """ 242 | program headers structure 243 | """ 244 | struct_description = "ELF Program header struct" 245 | 246 | # typedef struct 247 | # { 248 | # Elf64_Word p_type; /* Segment type */ 249 | # Elf64_Word p_flags; /* Segment flags */ 250 | # Elf64_Off p_offset; /* Segment file offset */ 251 | # Elf64_Addr p_vaddr; /* Segment virtual address */ 252 | # Elf64_Addr p_paddr; /* Segment physical address */ 253 | # Elf64_Xword p_filesz; /* Segment size in file */ 254 | # Elf64_Xword p_memsz; /* Segment size in memory */ 255 | # Elf64_Xword p_align; /* Segment alignment */ 256 | # } Elf64_Phdr; 257 | # 258 | 259 | allowed_fields = OrderedDict([ 260 | ("p_type", c.c_uint32), 261 | ("p_flags", c.c_uint32), 262 | ("p_offset", c.c_uint64), 263 | ("p_vaddr", c.c_uint64), 264 | ("p_paddr", c.c_uint64), 265 | ("p_filesz", c.c_uint64), 266 | ("p_memsz", c.c_uint64), 267 | ("p_align", c.c_uint64), 268 | ]) 269 | 270 | fields_names = allowed_fields.keys() 271 | _fields_ = [(name, size) for name, size in allowed_fields.items()] 272 | 273 | def __init__(self, ELF_obj, count): 274 | c.Structure.__init__(self) 275 | orginal_struct.__init__(self, ELF_obj, count=count) 276 | 277 | 278 | 279 | ##################################### X86 280 | 281 | @typemap 282 | class Elf32_Ehdr_ST(c.Structure, orginal_struct): 283 | """ 284 | elf 32 bits header structure 285 | """ 286 | 287 | struct_description = "ELF Header struct" 288 | 289 | 290 | # typedef struct 291 | # { 292 | # unsigned char e_ident[EI_NIDENT]; /* Magic number and other info */ 293 | # Elf32_Half e_type; /* Object file type */ 294 | # Elf32_Half e_machine; /* Architecture */ 295 | # Elf32_Word e_version; /* Object file version */ 296 | # Elf32_Addr e_entry; /* Entry point virtual address */ 297 | # Elf32_Off e_phoff; /* Program header table file offset */ 298 | # Elf32_Off e_shoff; /* Section header table file offset */ 299 | # Elf32_Word e_flags; /* Processor-specific flags */ 300 | # Elf32_Half e_ehsize; /* ELF header size in bytes */ 301 | # Elf32_Half e_phentsize; /* Program header table entry size */ 302 | # Elf32_Half e_phnum; /* Program header table entry count */ 303 | # Elf32_Half e_shentsize; /* Section header table entry size */ 304 | # Elf32_Half e_shnum; /* Section header table entry count */ 305 | # Elf32_Half e_shstrndx; /* Section header string table index */ 306 | # } Elf32_Ehdr; 307 | 308 | 309 | allowed_fields = OrderedDict([ 310 | ("e_ident", c.c_ubyte * 16), 311 | ("e_type", c.c_uint16), 312 | ("e_machine", c.c_uint16), 313 | ("e_version", c.c_uint32), 314 | ("e_entry", c.c_uint32), 315 | ("e_phoff", c.c_uint32), 316 | ("e_shoff", c.c_uint32), 317 | ("e_flags", c.c_uint32), 318 | ("e_ehsize", c.c_uint16), 319 | ("e_phentsize", c.c_uint16), 320 | ("e_phnum", c.c_uint16), 321 | ("e_shentsize", c.c_uint16), 322 | ("e_shnum", c.c_uint16), 323 | ("e_shstrndx", c.c_uint16), 324 | ]) 325 | 326 | fields_names = allowed_fields.keys() 327 | 328 | _fields_ = [(name, size) for name, size in allowed_fields.items()] 329 | 330 | def __init__(self, ELF_obj): 331 | c.Structure.__init__(self) 332 | orginal_struct.__init__(self, ELF_obj) 333 | 334 | 335 | 336 | class Elf32_Shdr_LST(list, original_struct_list): 337 | 338 | def __init__(self, ELF_obj): 339 | 340 | t = [] 341 | 342 | for i in range(ELF_obj.Elf32_Ehdr.e_shnum, 0, -1): 343 | 344 | if i == ELF_obj.Elf32_Ehdr.e_shnum: 345 | next_sh = ELF_obj.Elf32_Ehdr.e_shoff 346 | else: 347 | next_sh = t[ELF_obj.Elf32_Ehdr.e_shnum - i - 1].sh_offset 348 | 349 | t.append(Elf32_Shdr_ST(ELF_obj, count=i, next_sh=next_sh)) 350 | 351 | list.__init__(self, t[::-1]) 352 | 353 | 354 | 355 | 356 | @typemap 357 | class Elf32_Shdr_ST(c.Structure, orginal_struct): 358 | """ 359 | sections header structure 360 | """ 361 | struct_description = "ELF Sections header struct" 362 | 363 | # typedef struct 364 | # { 365 | # Elf32_Word sh_name; /* Section name (string tbl index) */ 366 | # Elf32_Word sh_type; /* Section type */ 367 | # Elf32_Word sh_flags; /* Section flags */ 368 | # Elf32_Addr sh_addr; /* Section virtual addr at execution */ 369 | # Elf32_Off sh_offset; /* Section file offset */ 370 | # Elf32_Word sh_size; /* Section size in bytes */ 371 | # Elf32_Word sh_link; /* Link to another section */ 372 | # Elf32_Word sh_info; /* Additional section information */ 373 | # Elf32_Word sh_addralign; /* Section alignment */ 374 | # Elf32_Word sh_entsize; /* Entry size if section holds table */ 375 | # } Elf32_Shdr; 376 | 377 | 378 | allowed_fields = OrderedDict([ 379 | ("sh_name" , c.c_uint32), 380 | ("sh_type" , c.c_uint32), 381 | ("sh_flags" , c.c_uint32), 382 | ("sh_addr" , c.c_uint32), 383 | ("sh_offset" , c.c_uint32), 384 | ("sh_size" , c.c_uint32), 385 | ("sh_link" , c.c_uint32), 386 | ("sh_info" , c.c_uint32), 387 | ("sh_addralign" , c.c_uint32), 388 | ("sh_entsize" , c.c_uint32) 389 | ]) 390 | 391 | fields_names = allowed_fields.keys() 392 | _fields_ = [(name, size) for name, size in allowed_fields.items()] 393 | 394 | def __init__(self, ELF_obj=None, count=None, next_sh=None): 395 | c.Structure.__init__(self) 396 | 397 | # we don't want to have the struct field automatically filled as we filled them ourself 398 | # getting the Shdr automatically by parsing an ELF 399 | if count != None and next_sh != None and ELF_obj != None: 400 | orginal_struct.__init__(self, ELF_obj, count=count, next_sh=next_sh) 401 | 402 | 403 | class Elf32_Phdr_LST(list, original_struct_list): 404 | 405 | def __init__(self, ELF_obj): 406 | 407 | list.__init__(self, [ Elf32_Phdr_ST(ELF_obj, i) for i in range(ELF_obj.Elf32_Ehdr.e_phnum)]) 408 | 409 | 410 | @typemap 411 | class Elf32_Phdr_ST(c.Structure, orginal_struct): 412 | """ 413 | program headers structure 414 | """ 415 | struct_description = "ELF Program header struct" 416 | 417 | 418 | # typedef struct 419 | # { 420 | # Elf32_Word p_type; /* Segment type */ 421 | # Elf32_Off p_offset; /* Segment file offset */ 422 | # Elf32_Addr p_vaddr; /* Segment virtual address */ 423 | # Elf32_Addr p_paddr; /* Segment physical address */ 424 | # Elf32_Word p_filesz; /* Segment size in file */ 425 | # Elf32_Word p_memsz; /* Segment size in memory */ 426 | # Elf32_Word p_flags; /* Segment flags */ 427 | # Elf32_Word p_align; /* Segment alignment */ 428 | # } Elf32_Phdr; 429 | 430 | 431 | allowed_fields = OrderedDict([ 432 | ("p_type", c.c_uint32), 433 | ("p_flags", c.c_uint32), 434 | ("p_offset", c.c_uint32), 435 | ("p_vaddr", c.c_uint32), 436 | ("p_paddr", c.c_uint32), 437 | ("p_filesz", c.c_uint32), 438 | ("p_memsz", c.c_uint32), 439 | ("p_align", c.c_uint32), 440 | ]) 441 | 442 | fields_names = allowed_fields.keys() 443 | _fields_ = [(name, size) for name, size in allowed_fields.items()] 444 | 445 | def __init__(self, ELF_obj, count): 446 | c.Structure.__init__(self) 447 | orginal_struct.__init__(self, ELF_obj, count=count) 448 | -------------------------------------------------------------------------------- /Hellf/lib/structures.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/0xswitch/Hellf/bf3fd364c9fd8212816e3eee1941dfcb1692b0cf/Hellf/lib/structures.py -------------------------------------------------------------------------------- /Hellf/lib/type.txt: -------------------------------------------------------------------------------- 1 | # /* Types for signed and unsigned 32-bit quantities. */ 2 | # typedef uint32_t Elf32_Word; 3 | # typedef int32_t Elf32_Sword; 4 | # typedef uint32_t Elf64_Word; 5 | # typedef int32_t Elf64_Sword; 6 | # 7 | # /* Types for signed and unsigned 64-bit quantities. */ 8 | # typedef uint64_t Elf32_Xword; 9 | # typedef int64_t Elf32_Sxword; 10 | # typedef uint64_t Elf64_Xword; 11 | # typedef int64_t Elf64_Sxword; 12 | # 13 | # /* Type of addresses. */ 14 | # typedef uint32_t Elf32_Addr; 15 | # typedef uint64_t Elf64_Addr; 16 | # 17 | # /* Type of file offsets. */ 18 | # typedef uint32_t Elf32_Off; 19 | # typedef uint64_t Elf64_Off; 20 | # 21 | # /* Type for section indices, which are 16-bit quantities. */ 22 | # typedef uint16_t Elf32_Section; 23 | # typedef uint16_t Elf64_Section; 24 | # 25 | # /* Type for version symbol information. */ 26 | # typedef Elf32_Half Elf32_Versym; 27 | # typedef Elf64_Half Elf64_Versym; 28 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Hellf ![](./img/logo.png) 2 | 3 | 4 | 5 | this is just a bad joke between hell and elf :( 6 | 7 | The aim of this project is to provide a python library for patching ELF binary file. It only supports for the moment `x86` and `x86_64` architecture. 8 | 9 | 10 | 11 | [![made-with-python](https://img.shields.io/badge/Made%20with-Python-1f425f.svg)](https://www.python.org/) [![Ask Me Anything !](https://img.shields.io/badge/Ask%20me-anything-1abc9c.svg)](https://GitHub.com/Naereen/ama) ![Twitter Follow](https://img.shields.io/twitter/follow/swuitch?label=ping%20%40swuitch&style=social) [![pypi](https://img.shields.io/pypi/v/Hellf)](https://pypi.org/project/Hellf/) 12 | 13 | ``` 14 | pip install Hellf 15 | ``` 16 | 17 | 18 | ```python 19 | from Hellf import ELF 20 | from pwn import shellcraft, asm, context 21 | context.arch = "amd64" 22 | 23 | e = ELF("/bin/ls") 24 | 25 | e.get_section_by_name(".text").data = asm(shellcraft.amd64.sh()) 26 | e.Elf64_Ehdr.e_entry = e.get_section_by_name(".text").sh_addr 27 | 28 | e.save("/tmp/not_really_ls_anymore") 29 | ``` 30 | 31 | ![img/poc.gif](img/twitter.gif) 32 | 33 | # Use cases 34 | 35 | Hellf allows you to modify each par of the ELF. 36 | 37 | - Program Header (segments definitions) 38 | - Section Header (sections definitions) 39 | - Section data (.text, .plt, .data, .bss, ...) 40 | 41 | You can change every single byte of a given binary. 42 | 43 | ## Adding a section 44 | 45 | ```python 46 | new_section = Elf64_Shdr_ST() 47 | 48 | # creating our shellcode (exit(14);) 49 | asm = """ 50 | mov rax, 0x3c 51 | mov rdi, 14 52 | syscall 53 | """ 54 | 55 | ks = Ks(KS_ARCH_X86, KS_MODE_64) 56 | ks.syntax = KS_OPT_SYNTAX_NASM 57 | 58 | shellcode = ks.asm(asm)[0] 59 | shellcode = (c_char * len(shellcode)).from_buffer(bytearray(shellcode)).raw 60 | 61 | new_section.data = shellcode 62 | 63 | new_section.sh_name = 1 # choose a random name 64 | new_section.sh_offset = e.Elf64_Ehdr.e_shoff 65 | new_section.sh_size = len(new_section.data) 66 | new_section.sh_addralign = 16 67 | new_section.sh_type = 0x1 # PROGBITS 68 | 69 | # adding our sections in the Section Header table 70 | e.add_section(new_section) 71 | ``` 72 | 73 | ## Extending segment 74 | 75 | ```Python 76 | # need to extend the last segment to englobe our new section 77 | segment = e.Elf64_Phdr[5] 78 | 79 | segment.p_filesz = new_section.sh_offset + new_section.sh_size - segment.p_offset 80 | segment.p_memsz = new_section.sh_offset + new_section.sh_size - segment.p_offset 81 | segment.p_flags = 7 # RWX 82 | 83 | # modifying our entrypoint to point to the shellcode 84 | e.Elf64_Ehdr.e_entry = segment.p_vaddr - segment.p_offset + new_section.sh_offset 85 | 86 | e.save("/tmp/exit") 87 | ``` 88 | 89 | # Embuche 90 | 91 | Hellf is a part of [Embuche](https://github.com/magnussen7/Embuche), a lot of Hellf scripts are used to obfuscate ELF for the purpose of this project. The scripts used are described here : [File Format Hacks](https://github.com/magnussen7/Embuche/blob/master/README.md#file-format-hacks) 92 | 93 | For example it used to create a fake `.dynsym` section or to hide the entry point [link to script](https://github.com/magnussen7/Embuche/blob/master/class_embuche/cmake_bakery/hellf_scripts/mixing_symbols_table.py) 94 | 95 | But Hellf is also the corner stone of the Embuche metamorphic packer. 96 | 97 | > A metamorphic packer is available in Embuche. This packer will load your binary and cipher it (AES 256 bits CBC). 98 | > 99 | > If you decide to use the packer, your program will be ciphered and stored in a section of our packer. When you will execute your program the packer will copy itself in memory, unciphered your program and write it on the disk for execution. 100 | > 101 | > Beside cipher your binary, the packer will also ensure its integrity. The encryption keys used for the encryption are based on the SHA sum of the `.text` section, so if the packer or your program is being debugged the SHA sum will be different of the one used for decryption. 102 | > 103 | > The ELF of the packer can be modified with the `packer_embuche` options. 104 | 105 | # Documentation 106 | 107 | There is almost no documentation for the moment although the code is a bit commented, you should be lucky. 108 | 109 | Here a list of function of the `ELF` object. 110 | 111 | ```python 112 | get_section_number(name) 113 | get_section_by_name(name) 114 | get_section_name(shstrtab_index) 115 | add_section(custom_section) 116 | ``` 117 | 118 | For the remaining, you should just interact with the object attributes them self. About the names of variables I just used the official one provided by the ABI. 119 | 120 | - /usr/include/elf.h 121 | - [http://www.sco.com/developers/gabi/latest/contents.html](http://www.sco.com/developers/gabi/latest/contents.html) 122 | 123 | 124 | For example, the ELF Header : 125 | ``` 126 | typedef struct 127 | { 128 | unsigned char e_ident[EI_NIDENT]; /* Magic number and other info */ 129 | Elf64_Half e_type; /* Object file type */ 130 | Elf64_Half e_machine; /* Architecture */ 131 | Elf64_Word e_version; /* Object file version */ 132 | Elf64_Addr e_entry; /* Entry point virtual address */ 133 | Elf64_Off e_phoff; /* Program header table file offset */ 134 | Elf64_Off e_shoff; /* Section header table file offset */ 135 | Elf64_Word e_flags; /* Processor-specific flags */ 136 | Elf64_Half e_ehsize; /* ELF header size in bytes */ 137 | Elf64_Half e_phentsize; /* Program header table entry size */ 138 | Elf64_Half e_phnum; /* Program header table entry count */ 139 | Elf64_Half e_shentsize; /* Section header table entry size */ 140 | Elf64_Half e_shnum; /* Section header table entry count */ 141 | Elf64_Half e_shstrndx; /* Section header string table index */ 142 | } Elf64_Ehdr; 143 | ``` 144 | 145 | So you can just do this to interact with the section or section header : 146 | 147 | ```python 148 | > e.Elf64_Shdr[26] 149 | ELF Sections header struct 150 | sh_name: 0x1 151 | sh_type: 0x3 152 | sh_flags: 0x0 153 | sh_addr: 0x0 154 | sh_offset: 0x222b4 155 | sh_size: 0xf7 156 | sh_link: 0x0 157 | sh_info: 0x0 158 | sh_addralign: 0x1 159 | sh_entsize: 0x0 160 | 161 | > e.Elf64_Shdr[26].data 162 | b'\x00.shstrtab\x00.interp\x00.note.gnu.build-id\x00.note.ABI-tag\x00.gnu.hash\x00.dynsym\x00.dynstr\x00.gnu.version\x00.gnu.version_r\x00.rela.dyn\x00.rela.plt\x00.init\x00.text\x00.fini\x00.rodata\x00.eh_frame_hdr\x00.eh_frame\x00.init_array\x00.fini_array\x00.data.rel.ro\x00.dynamic\x00.got\x00.data\x00.bss\x00.comment\x00' 163 | 164 | > e.get_section_name(e.Elf64_Shdr[26].sh_name) 165 | '.shstrtab' 166 | ``` 167 | 168 | Or if you want to interact with the header itself. 169 | 170 | ````python 171 | > e.Elf64_Ehdr 172 | ELF Header struct 173 | e_ident: 0x7f 0x45 0x4c 0x46 0x2 0x1 0x1 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 174 | e_type: 0x3 175 | e_machine: 0x3e 176 | e_version: 0x1 177 | e_entry: 0x5b20 178 | e_phoff: 0x40 179 | e_shoff: 0x2234b 180 | e_flags: 0x0 181 | e_ehsize: 0x40 182 | e_phentsize: 0x38 183 | e_phnum: 0xb 184 | e_shentsize: 0x40 185 | e_shnum: 0x1b 186 | e_shstrndx: 0x1a 187 | 188 | > hex(e.Elf64_Ehdr.e_entry) 189 | '0x5b20' 190 | 191 | > hex(e.Elf64_Ehdr.e_shnum) 192 | '0x1b' 193 | 194 | > hex(e.Elf64_Ehdr.e_shoff) 195 | '0x2234b' 196 | ```` 197 | -------------------------------------------------------------------------------- /img/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/0xswitch/Hellf/bf3fd364c9fd8212816e3eee1941dfcb1692b0cf/img/logo.png -------------------------------------------------------------------------------- /img/twitter.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/0xswitch/Hellf/bf3fd364c9fd8212816e3eee1941dfcb1692b0cf/img/twitter.gif -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import find_packages, setup 2 | 3 | setup(name='Hellf', 4 | version='1.1', 5 | description='The aim of this project is to provide a python library for patching ELF binary file. It only supports for the moment x86 and x86_64 architecture.', 6 | url='https://github.com/0xswitch/Hellf', 7 | author='switch', 8 | author_email='switch@switch.re', 9 | license='WTFPL', 10 | python_requires='>=3', 11 | packages=find_packages(), 12 | zip_safe=False) 13 | -------------------------------------------------------------------------------- /tests/Makefile: -------------------------------------------------------------------------------- 1 | BIN=$(BIN) 2 | OUT=$(OUT) 3 | 4 | all: 5 | mkdir -p $(OUT) 6 | python add_section.py $(BIN) $(OUT)/add_section 7 | python replace_text.py $(BIN) $(OUT)/replace_text 8 | python unstrip.py $(BIN) $(OUT)/unstrip 9 | python nothing.py $(BIN) $(OUT)/nothing 10 | 11 | chmod +x $(OUT)/* 12 | 13 | clean: 14 | rm -rf ./$(OUT) 15 | 16 | nothing: 17 | @mkdir -p $(OUT) 18 | @python nothing.py $(BIN) $(OUT)/nothing 19 | $(eval sha_new = $(shell sha256sum $(OUT)/nothing | cut -d " " -f 1)) 20 | $(eval sha_old = $(shell sha256sum $(BIN) | cut -d " " -f 1)) 21 | @if [ $(sha_new) = $(sha_old) ]; then \ 22 | echo "match"; \ 23 | else \ 24 | echo "do not match"; \ 25 | fi 26 | -------------------------------------------------------------------------------- /tests/add_section.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | from Hellf import ELF 3 | from Hellf.lib import Elf64_Shdr_ST 4 | 5 | from keystone import Ks, KS_ARCH_X86, KS_MODE_64, KS_OPT_SYNTAX_NASM 6 | from ctypes import c_char 7 | 8 | from sys import argv 9 | from IPython import embed 10 | from pprint import pprint as pp 11 | from huepy import good 12 | 13 | ### 14 | # Aim : 15 | # add a section with our shellcode, 16 | # extend a segment to map this new section in RWX memory 17 | # update the entrypoint to jmp to the shellcode 18 | ## 19 | 20 | e = ELF(argv[1]) 21 | 22 | new_section = Elf64_Shdr_ST() 23 | 24 | # creating our shellcode (exit(14);) 25 | asm = """ 26 | mov rax, 0x3c 27 | mov rdi, 14 28 | syscall 29 | """ 30 | 31 | ks = Ks(KS_ARCH_X86, KS_MODE_64) 32 | ks.syntax = KS_OPT_SYNTAX_NASM 33 | 34 | shellcode = ks.asm(asm)[0] 35 | shellcode = (c_char * len(shellcode)).from_buffer(bytearray(shellcode)).raw 36 | 37 | # we can modify the .text section to jmp to the sc but we will directly modified the entrypoint 38 | # stub = ks.asm("jmp 0x3308")[0] 39 | # stub = (c_char * len(stub)).from_buffer(bytearray(stub)).raw 40 | # e.get_section_by_name(".text").data = stub 41 | 42 | new_section.data = shellcode 43 | 44 | new_section.sh_name = 1 # choose a random name 45 | # right after the last sh there is the sht table so it offset will be the addr of the new section 46 | new_section.sh_offset = e.Elf64_Ehdr.e_shoff # nice if it would be filled automatically by save 47 | new_section.sh_size = len(new_section.data) 48 | new_section.sh_addralign = 16 49 | new_section.sh_type = 0x1 # PROGBITS 50 | 51 | # adding our sections in the Shdt 52 | e.add_section(new_section) 53 | 54 | # need to extend the last segment to englobe our new section 55 | segment = e.Elf64_Phdr[5] 56 | segment.p_filesz = new_section.sh_offset + new_section.sh_size - segment.p_offset 57 | segment.p_memsz = new_section.sh_offset + new_section.sh_size - segment.p_offset 58 | 59 | segment.p_flags = 7 # RWX 60 | 61 | # modifying our entrypoint to point to the shellcode 62 | e.Elf64_Ehdr.e_entry = segment.p_vaddr - segment.p_offset + new_section.sh_offset 63 | print(good("Entry at {}".format(hex(e.Elf64_Ehdr.e_entry)))) 64 | 65 | e.save(argv[2]) 66 | 67 | # excepted behavior: 68 | # - return 14 (echo $?) 69 | -------------------------------------------------------------------------------- /tests/nothing.py: -------------------------------------------------------------------------------- 1 | from Hellf import ELF 2 | from sys import argv 3 | 4 | e = ELF(argv[1]) 5 | e.save(argv[2]) 6 | -------------------------------------------------------------------------------- /tests/replace_text.py: -------------------------------------------------------------------------------- 1 | from Hellf import ELF 2 | from pwn import shellcraft, asm, context 3 | from sys import argv 4 | 5 | context.arch = "amd64" 6 | 7 | e = ELF(argv[1]) 8 | e.get_section_by_name(".text").data = asm(shellcraft.amd64.sh()) 9 | e.Elf64_Ehdr.e_entry = e.get_section_by_name(".text").sh_addr 10 | e.save(argv[2]) 11 | 12 | # excepted behavior : 13 | # - pop a shell 14 | -------------------------------------------------------------------------------- /tests/unstrip.py: -------------------------------------------------------------------------------- 1 | from Hellf import * 2 | from Hellf.lib import Elf64_Shdr_ST 3 | 4 | from struct import pack 5 | from switch import hx 6 | 7 | from sys import argv 8 | 9 | class unstrip(): 10 | 11 | def __init__(self, in_binary, out_binary): 12 | self.stripped_binary = ELF(in_binary) 13 | self.unstripped_binary = out_binary 14 | 15 | self.symtab_data = b"\x00" * 24 16 | self.strtab_data = b"\x00" 17 | 18 | self.text_section_number = self.stripped_binary.get_section_number(".text") 19 | 20 | def new_strtab_value(self, name): 21 | name = name.encode("utf-8") 22 | 23 | offset = self.strtab_data.find(name) 24 | 25 | if offset != -1: 26 | return offset 27 | else: 28 | self.strtab_data += name + b"\x00" 29 | return self.strtab_data.find(name) 30 | 31 | 32 | def add_function(self, name, value, size=0): 33 | new_function = b"" 34 | new_function += pack("