├── .gitignore ├── LICENSE ├── README.md ├── ZwoELF ├── Compatibility.py ├── Elf.py ├── ElfParserLib.py └── __init__.py └── examples ├── IDAPython └── addDynamicSymbols_ida.py ├── addRandomSections.py ├── createFalseDynstrSection.py ├── indirectJmpWithMemoryData.py ├── modifySymbolValue.py ├── newEntryPoint.py ├── obfuscatePltSection.py └── readElf.py /.gitignore: -------------------------------------------------------------------------------- 1 | *.pyc 2 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | GNU GENERAL PUBLIC LICENSE 2 | Version 2, June 1991 3 | 4 | Copyright (C) 1989, 1991 Free Software Foundation, Inc., 5 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 6 | Everyone is permitted to copy and distribute verbatim copies 7 | of this license document, but changing it is not allowed. 8 | 9 | Preamble 10 | 11 | The licenses for most software are designed to take away your 12 | freedom to share and change it. By contrast, the GNU General Public 13 | License is intended to guarantee your freedom to share and change free 14 | software--to make sure the software is free for all its users. This 15 | General Public License applies to most of the Free Software 16 | Foundation's software and to any other program whose authors commit to 17 | using it. (Some other Free Software Foundation software is covered by 18 | the GNU Lesser General Public License instead.) You can apply it to 19 | your programs, too. 20 | 21 | When we speak of free software, we are referring to freedom, not 22 | price. Our General Public Licenses are designed to make sure that you 23 | have the freedom to distribute copies of free software (and charge for 24 | this service if you wish), that you receive source code or can get it 25 | if you want it, that you can change the software or use pieces of it 26 | in new free programs; and that you know you can do these things. 27 | 28 | To protect your rights, we need to make restrictions that forbid 29 | anyone to deny you these rights or to ask you to surrender the rights. 30 | These restrictions translate to certain responsibilities for you if you 31 | distribute copies of the software, or if you modify it. 32 | 33 | For example, if you distribute copies of such a program, whether 34 | gratis or for a fee, you must give the recipients all the rights that 35 | you have. You must make sure that they, too, receive or can get the 36 | source code. And you must show them these terms so they know their 37 | rights. 38 | 39 | We protect your rights with two steps: (1) copyright the software, and 40 | (2) offer you this license which gives you legal permission to copy, 41 | distribute and/or modify the software. 42 | 43 | Also, for each author's protection and ours, we want to make certain 44 | that everyone understands that there is no warranty for this free 45 | software. If the software is modified by someone else and passed on, we 46 | want its recipients to know that what they have is not the original, so 47 | that any problems introduced by others will not reflect on the original 48 | authors' reputations. 49 | 50 | Finally, any free program is threatened constantly by software 51 | patents. We wish to avoid the danger that redistributors of a free 52 | program will individually obtain patent licenses, in effect making the 53 | program proprietary. To prevent this, we have made it clear that any 54 | patent must be licensed for everyone's free use or not licensed at all. 55 | 56 | The precise terms and conditions for copying, distribution and 57 | modification follow. 58 | 59 | GNU GENERAL PUBLIC LICENSE 60 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 61 | 62 | 0. This License applies to any program or other work which contains 63 | a notice placed by the copyright holder saying it may be distributed 64 | under the terms of this General Public License. The "Program", below, 65 | refers to any such program or work, and a "work based on the Program" 66 | means either the Program or any derivative work under copyright law: 67 | that is to say, a work containing the Program or a portion of it, 68 | either verbatim or with modifications and/or translated into another 69 | language. (Hereinafter, translation is included without limitation in 70 | the term "modification".) Each licensee is addressed as "you". 71 | 72 | Activities other than copying, distribution and modification are not 73 | covered by this License; they are outside its scope. The act of 74 | running the Program is not restricted, and the output from the Program 75 | is covered only if its contents constitute a work based on the 76 | Program (independent of having been made by running the Program). 77 | Whether that is true depends on what the Program does. 78 | 79 | 1. You may copy and distribute verbatim copies of the Program's 80 | source code as you receive it, in any medium, provided that you 81 | conspicuously and appropriately publish on each copy an appropriate 82 | copyright notice and disclaimer of warranty; keep intact all the 83 | notices that refer to this License and to the absence of any warranty; 84 | and give any other recipients of the Program a copy of this License 85 | along with the Program. 86 | 87 | You may charge a fee for the physical act of transferring a copy, and 88 | you may at your option offer warranty protection in exchange for a fee. 89 | 90 | 2. You may modify your copy or copies of the Program or any portion 91 | of it, thus forming a work based on the Program, and copy and 92 | distribute such modifications or work under the terms of Section 1 93 | above, provided that you also meet all of these conditions: 94 | 95 | a) You must cause the modified files to carry prominent notices 96 | stating that you changed the files and the date of any change. 97 | 98 | b) You must cause any work that you distribute or publish, that in 99 | whole or in part contains or is derived from the Program or any 100 | part thereof, to be licensed as a whole at no charge to all third 101 | parties under the terms of this License. 102 | 103 | c) If the modified program normally reads commands interactively 104 | when run, you must cause it, when started running for such 105 | interactive use in the most ordinary way, to print or display an 106 | announcement including an appropriate copyright notice and a 107 | notice that there is no warranty (or else, saying that you provide 108 | a warranty) and that users may redistribute the program under 109 | these conditions, and telling the user how to view a copy of this 110 | License. (Exception: if the Program itself is interactive but 111 | does not normally print such an announcement, your work based on 112 | the Program is not required to print an announcement.) 113 | 114 | These requirements apply to the modified work as a whole. If 115 | identifiable sections of that work are not derived from the Program, 116 | and can be reasonably considered independent and separate works in 117 | themselves, then this License, and its terms, do not apply to those 118 | sections when you distribute them as separate works. But when you 119 | distribute the same sections as part of a whole which is a work based 120 | on the Program, the distribution of the whole must be on the terms of 121 | this License, whose permissions for other licensees extend to the 122 | entire whole, and thus to each and every part regardless of who wrote it. 123 | 124 | Thus, it is not the intent of this section to claim rights or contest 125 | your rights to work written entirely by you; rather, the intent is to 126 | exercise the right to control the distribution of derivative or 127 | collective works based on the Program. 128 | 129 | In addition, mere aggregation of another work not based on the Program 130 | with the Program (or with a work based on the Program) on a volume of 131 | a storage or distribution medium does not bring the other work under 132 | the scope of this License. 133 | 134 | 3. You may copy and distribute the Program (or a work based on it, 135 | under Section 2) in object code or executable form under the terms of 136 | Sections 1 and 2 above provided that you also do one of the following: 137 | 138 | a) Accompany it with the complete corresponding machine-readable 139 | source code, which must be distributed under the terms of Sections 140 | 1 and 2 above on a medium customarily used for software interchange; or, 141 | 142 | b) Accompany it with a written offer, valid for at least three 143 | years, to give any third party, for a charge no more than your 144 | cost of physically performing source distribution, a complete 145 | machine-readable copy of the corresponding source code, to be 146 | distributed under the terms of Sections 1 and 2 above on a medium 147 | customarily used for software interchange; or, 148 | 149 | c) Accompany it with the information you received as to the offer 150 | to distribute corresponding source code. (This alternative is 151 | allowed only for noncommercial distribution and only if you 152 | received the program in object code or executable form with such 153 | an offer, in accord with Subsection b above.) 154 | 155 | The source code for a work means the preferred form of the work for 156 | making modifications to it. For an executable work, complete source 157 | code means all the source code for all modules it contains, plus any 158 | associated interface definition files, plus the scripts used to 159 | control compilation and installation of the executable. However, as a 160 | special exception, the source code distributed need not include 161 | anything that is normally distributed (in either source or binary 162 | form) with the major components (compiler, kernel, and so on) of the 163 | operating system on which the executable runs, unless that component 164 | itself accompanies the executable. 165 | 166 | If distribution of executable or object code is made by offering 167 | access to copy from a designated place, then offering equivalent 168 | access to copy the source code from the same place counts as 169 | distribution of the source code, even though third parties are not 170 | compelled to copy the source along with the object code. 171 | 172 | 4. You may not copy, modify, sublicense, or distribute the Program 173 | except as expressly provided under this License. Any attempt 174 | otherwise to copy, modify, sublicense or distribute the Program is 175 | void, and will automatically terminate your rights under this License. 176 | However, parties who have received copies, or rights, from you under 177 | this License will not have their licenses terminated so long as such 178 | parties remain in full compliance. 179 | 180 | 5. You are not required to accept this License, since you have not 181 | signed it. However, nothing else grants you permission to modify or 182 | distribute the Program or its derivative works. These actions are 183 | prohibited by law if you do not accept this License. Therefore, by 184 | modifying or distributing the Program (or any work based on the 185 | Program), you indicate your acceptance of this License to do so, and 186 | all its terms and conditions for copying, distributing or modifying 187 | the Program or works based on it. 188 | 189 | 6. Each time you redistribute the Program (or any work based on the 190 | Program), the recipient automatically receives a license from the 191 | original licensor to copy, distribute or modify the Program subject to 192 | these terms and conditions. You may not impose any further 193 | restrictions on the recipients' exercise of the rights granted herein. 194 | You are not responsible for enforcing compliance by third parties to 195 | this License. 196 | 197 | 7. If, as a consequence of a court judgment or allegation of patent 198 | infringement or for any other reason (not limited to patent issues), 199 | conditions are imposed on you (whether by court order, agreement or 200 | otherwise) that contradict the conditions of this License, they do not 201 | excuse you from the conditions of this License. If you cannot 202 | distribute so as to satisfy simultaneously your obligations under this 203 | License and any other pertinent obligations, then as a consequence you 204 | may not distribute the Program at all. For example, if a patent 205 | license would not permit royalty-free redistribution of the Program by 206 | all those who receive copies directly or indirectly through you, then 207 | the only way you could satisfy both it and this License would be to 208 | refrain entirely from distribution of the Program. 209 | 210 | If any portion of this section is held invalid or unenforceable under 211 | any particular circumstance, the balance of the section is intended to 212 | apply and the section as a whole is intended to apply in other 213 | circumstances. 214 | 215 | It is not the purpose of this section to induce you to infringe any 216 | patents or other property right claims or to contest validity of any 217 | such claims; this section has the sole purpose of protecting the 218 | integrity of the free software distribution system, which is 219 | implemented by public license practices. Many people have made 220 | generous contributions to the wide range of software distributed 221 | through that system in reliance on consistent application of that 222 | system; it is up to the author/donor to decide if he or she is willing 223 | to distribute software through any other system and a licensee cannot 224 | impose that choice. 225 | 226 | This section is intended to make thoroughly clear what is believed to 227 | be a consequence of the rest of this License. 228 | 229 | 8. If the distribution and/or use of the Program is restricted in 230 | certain countries either by patents or by copyrighted interfaces, the 231 | original copyright holder who places the Program under this License 232 | may add an explicit geographical distribution limitation excluding 233 | those countries, so that distribution is permitted only in or among 234 | countries not thus excluded. In such case, this License incorporates 235 | the limitation as if written in the body of this License. 236 | 237 | 9. The Free Software Foundation may publish revised and/or new versions 238 | of the General Public License from time to time. Such new versions will 239 | be similar in spirit to the present version, but may differ in detail to 240 | address new problems or concerns. 241 | 242 | Each version is given a distinguishing version number. If the Program 243 | specifies a version number of this License which applies to it and "any 244 | later version", you have the option of following the terms and conditions 245 | either of that version or of any later version published by the Free 246 | Software Foundation. If the Program does not specify a version number of 247 | this License, you may choose any version ever published by the Free Software 248 | Foundation. 249 | 250 | 10. If you wish to incorporate parts of the Program into other free 251 | programs whose distribution conditions are different, write to the author 252 | to ask for permission. For software which is copyrighted by the Free 253 | Software Foundation, write to the Free Software Foundation; we sometimes 254 | make exceptions for this. Our decision will be guided by the two goals 255 | of preserving the free status of all derivatives of our free software and 256 | of promoting the sharing and reuse of software generally. 257 | 258 | NO WARRANTY 259 | 260 | 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY 261 | FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN 262 | OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES 263 | PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED 264 | OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 265 | MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS 266 | TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE 267 | PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, 268 | REPAIR OR CORRECTION. 269 | 270 | 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 271 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR 272 | REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, 273 | INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING 274 | OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED 275 | TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY 276 | YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER 277 | PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE 278 | POSSIBILITY OF SUCH DAMAGES. 279 | 280 | END OF TERMS AND CONDITIONS 281 | 282 | How to Apply These Terms to Your New Programs 283 | 284 | If you develop a new program, and you want it to be of the greatest 285 | possible use to the public, the best way to achieve this is to make it 286 | free software which everyone can redistribute and change under these terms. 287 | 288 | To do so, attach the following notices to the program. It is safest 289 | to attach them to the start of each source file to most effectively 290 | convey the exclusion of warranty; and each file should have at least 291 | the "copyright" line and a pointer to where the full notice is found. 292 | 293 | {description} 294 | Copyright (C) {year} {fullname} 295 | 296 | This program is free software; you can redistribute it and/or modify 297 | it under the terms of the GNU General Public License as published by 298 | the Free Software Foundation; either version 2 of the License, or 299 | (at your option) any later version. 300 | 301 | This program is distributed in the hope that it will be useful, 302 | but WITHOUT ANY WARRANTY; without even the implied warranty of 303 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 304 | GNU General Public License for more details. 305 | 306 | You should have received a copy of the GNU General Public License along 307 | with this program; if not, write to the Free Software Foundation, Inc., 308 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 309 | 310 | Also add information on how to contact you by electronic and paper mail. 311 | 312 | If the program is interactive, make it output a short notice like this 313 | when it starts in an interactive mode: 314 | 315 | Gnomovision version 69, Copyright (C) year name of author 316 | Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 317 | This is free software, and you are welcome to redistribute it 318 | under certain conditions; type `show c' for details. 319 | 320 | The hypothetical commands `show w' and `show c' should show the appropriate 321 | parts of the General Public License. Of course, the commands you use may 322 | be called something other than `show w' and `show c'; they could even be 323 | mouse-clicks or menu items--whatever suits your program. 324 | 325 | You should also get your employer (if you work as a programmer) or your 326 | school, if any, to sign a "copyright disclaimer" for the program, if 327 | necessary. Here is a sample; alter the names: 328 | 329 | Yoyodyne, Inc., hereby disclaims all copyright interest in the program 330 | `Gnomovision' (which makes passes at compilers) written by James Hacker. 331 | 332 | {signature of Ty Coon}, 1 April 1989 333 | Ty Coon, President of Vice 334 | 335 | This General Public License does not permit incorporating your program into 336 | proprietary programs. If your program is a subroutine library, you may 337 | consider it more useful to permit linking proprietary applications with the 338 | library. If this is what you want to do, use the GNU Lesser General 339 | Public License instead of this License. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ZwoELF 2 | ============ 3 | 4 | An ELF parsing and manipulation library for Python 5 | 6 | Why is it named "ZwoELF" (German for "twelve")? Because ELF is German for "eleven" and I like to go one step further than others ;-) 7 | 8 | This library works only with x86 and x86_64 ELF binaries. It was written as a parser library to understand the ELF format and gained some manipulation functions after a while. In contrast to most ELF analysis tools (for example "readelf"), I tried to use the information that the ELF loader uses to load the binary. As a result, the library ignores the "sections" of the binary in order to work even if the "sections" held wrong information. 9 | 10 | It is not finished yet (due to time problems) nor the manipulation in any state of reliable use. 11 | 12 | 13 | How to use it 14 | ============ 15 | 16 | I added some examples in the directory "examples" that will show how the library can be used. 17 | -------------------------------------------------------------------------------- /ZwoELF/Compatibility.py: -------------------------------------------------------------------------------- 1 | import struct 2 | 3 | 4 | """ 5 | Python 2.7.3 issue: 6 | struct.unpack(fmt, buffer) does not work if buffer is a bytearray. 7 | (Works with Python 2.7.8.) 8 | 9 | This compatibility workaround was written when python 2.7.3 was current in 10 | Debian stable. 11 | """ 12 | try: 13 | struct.unpack('>8) 655 | #define ELF32_R_TYPE(i) ((unsigned char)(i)) 656 | #define ELF32_R_INFO(s,t) (((s)<<8)+(unsigned char)(t)) 657 | 658 | Macros for 64 bit systems 659 | #define ELF64_R_SYM(i) ((i)>>32) 660 | #define ELF64_R_TYPE(i) ((i)&0xffffffff) 661 | #define ELF64_R_INFO(s,t) (((s)<<32)+(t)) 662 | ''' 663 | def __init__(self): 664 | # in executable and share object files => r_offset holds a virtual address 665 | self.r_offset = None 666 | 667 | # for 32 bit systems: 668 | # r_info = (r_sym << 8) + (r_type & 0xFF) 669 | self.r_info = None 670 | 671 | # for 32 bit systems calculated: "(unsigned char)(r_info)" or just "r_info & 0xFF" 672 | self.r_type = None 673 | 674 | # for 32 bit systems calculated: "r_info >> 8" 675 | self.r_sym = None 676 | 677 | # for 32 bit systems 678 | self.symbol = DynamicSymbol() 679 | 680 | 681 | class ElfN_Rela(object): 682 | ''' 683 | typedef struct 684 | { 685 | Elf32_Addr r_offset; /* Address */ 686 | Elf32_Word r_info; /* Relocation type and symbol index */ 687 | Elf32_Sword r_addend; /* Addend */ 688 | } Elf32_Rela; 689 | 690 | typedef struct 691 | { 692 | Elf64_Addr r_offset; /* Address */ 693 | Elf64_Xword r_info; /* Relocation type and symbol index */ 694 | Elf64_Sxword r_addend; /* Addend */ 695 | } Elf64_Rela; 696 | 697 | Macros for 32/64 bit systems: see description for ElfN_Rel 698 | ''' 699 | def __init__(self): 700 | # in executable and share object files => r_offset holds a virtual address 701 | self.r_offset = None 702 | 703 | self.r_info = None 704 | self.r_type = None 705 | self.r_sym = None 706 | 707 | self.r_addend = None 708 | 709 | self.symbol = DynamicSymbol() 710 | 711 | 712 | class ElfN_Sym(object): 713 | ''' 714 | typedef struct elf32_sym { 715 | Elf32_Word st_name; 716 | Elf32_Addr st_value; 717 | Elf32_Word st_size; 718 | unsigned char st_info; 719 | unsigned char st_other; 720 | Elf32_Half st_shndx; 721 | } Elf32_Sym; 722 | 723 | typedef struct elf64_sym { 724 | Elf64_Word st_name; /* Symbol name, index in string tbl */ 725 | unsigned char st_info; /* Type and binding attributes */ 726 | unsigned char st_other; /* No defined meaning, 0 */ 727 | Elf64_Half st_shndx; /* Associated section index */ 728 | Elf64_Addr st_value; /* Value of the symbol */ 729 | Elf64_Xword st_size; /* Associated symbol size */ 730 | } Elf64_Sym; 731 | ''' 732 | def __init__(self): 733 | st_name = None 734 | st_value = None 735 | st_size = None 736 | st_info = None 737 | st_other = None 738 | st_shndx = None 739 | 740 | 741 | class R_type(object): 742 | ''' 743 | R_386_GOT32 This relocation type computes the distance from the 744 | base of the global offset 745 | table to the symbol's global offset table entry. 746 | It additionally instructs the link 747 | editor to build a global offset table. 748 | 749 | R_386_PLT32 This relocation type computes the address of the 750 | symbol's procedure linkage 751 | table entry and additionally instructs the link editor to 752 | build a procedure linkage table. 753 | 754 | R_386_COPY The link editor creates this relocation type for 755 | dynamic linking. Its offset 756 | member refers to a location in a writable segment. 757 | The symbol table index specifies a symbol that should exist 758 | both in the current object file and in a shared 759 | object. During execution, the dynamic linker copies data 760 | associated with the shared object's symbol to the location 761 | specified by the offset. 762 | 763 | R_386_GLOB_DAT This relocation type is used to set a global offset 764 | table entry to the address of the specified symbol. The 765 | special relocation type allows one to determine the 766 | correspondence between symbols and global offset table entries. 767 | 768 | R_3862_JMP_SLOT The link editor creates this relocation type for 769 | dynamic linking. Its offset member gives the location of a 770 | procedure linkage table entry. The dynamic linker modifies 771 | the procedure linkage table entry to transfer control to the 772 | designated symbol's address. 773 | 774 | R_386_RELATIVE The link editor creates this relocation type for 775 | dynamic linking. Its offset member gives a location within a 776 | shared object that contains a value representing a relative address. 777 | The dynamic linker computes the corresponding virtual 778 | address by adding the virtual address at which the shared object 779 | was loaded to the relative address. Relocation entries for this 780 | type must specify 0 for the symbol table index. 781 | 782 | R_386_GOTOFF This relocation type computes the difference between a 783 | symbol's value and the address of the global offset table. It 784 | additionally instructs the link editor to build the global 785 | offset table. 786 | 787 | R_386_GOTPC This relocation type resembles R_386_PC32, except it uses 788 | the address of the global offset table in its calculation. 789 | The symbol referenced in this relocation 790 | normally is _GLOBAL_OFFSET_TABLE_, which additionally instructs 791 | the link editor to build the global offset table. 792 | ''' 793 | reverse_lookup = {0: "R_386_NONE", 1: "R_386_32", 2: "R_386_PC32", 794 | 3: "R_386_GOT32", 4: "R_386_PLT32", 5: "R_386_COPY", 795 | 6: "R_386_GLOB_DAT", 7: "R_386_JMP_SLOT", 8: "R_386_RELATIVE", 796 | 9: "R_386_GOTOFF", 10: "R_386_GOTPC"} 797 | R_386_NONE = 0 798 | R_386_32 = 1 799 | R_386_PC32 = 2 800 | R_386_GOT32 = 3 801 | R_386_PLT32 = 4 802 | R_386_COPY = 5 803 | R_386_GLOB_DAT = 6 804 | R_386_JMP_SLOT = 7 805 | R_386_RELATIVE = 8 806 | R_386_GOTOFF = 9 807 | R_386_GOTPC = 10 808 | -------------------------------------------------------------------------------- /ZwoELF/ElfParserLib.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | 3 | # written by sqall 4 | # twitter: https://twitter.com/sqall01 5 | # blog: http://blog.h4des.org 6 | # github: https://github.com/sqall01 7 | # 8 | # Licensed under the GNU Public License, version 2. 9 | 10 | import binascii 11 | import struct 12 | import sys 13 | import hashlib 14 | from Elf import ElfN_Ehdr, Shstrndx, ElfN_Shdr, SH_flags, SH_type, \ 15 | Elf32_Phdr, P_type, P_flags, D_tag, ElfN_Dyn, \ 16 | ElfN_Rel, ElfN_Rela, ElfN_Sym, R_type, \ 17 | Section, Segment, DynamicSymbol 18 | 19 | 20 | class ElfParser(object): 21 | 22 | def __init__(self, filename, force=False, startOffset=0, 23 | forceDynSymParsing=0, onlyParseHeader=False): 24 | self.forceDynSymParsing = forceDynSymParsing 25 | self.header = None 26 | self.segments = list() 27 | self.sections = list() 28 | self.fileParsed = False 29 | self.dynamicSymbolEntries = list() 30 | self.dynamicSegmentEntries = list() 31 | self.jumpRelocationEntries = list() 32 | self.relocationEntries = list() 33 | self.startOffset = startOffset 34 | self.data = bytearray() 35 | self.bits = 0 36 | 37 | # read file and convert data to list 38 | f = open(filename, "rb") 39 | f.seek(self.startOffset, 0) 40 | self.data = bytearray(f.read()) 41 | f.close() 42 | 43 | # parse ELF file 44 | self.parseElf(self.data, onlyParseHeader=onlyParseHeader) 45 | 46 | # check if parsed ELF file and new generated one are the same 47 | if self.fileParsed is True and force is False: 48 | # generate md5 hash of file that was parsed 49 | tempHash = hashlib.md5() 50 | tempHash.update(self.data) 51 | oldFileHash = tempHash.digest() 52 | 53 | # generate md5 hash of file that was newly generated 54 | tempHash = hashlib.md5() 55 | tempHash.update(self.generateElf()) 56 | newFileHash = tempHash.digest() 57 | 58 | if oldFileHash != newFileHash: 59 | raise NotImplementedError('Not able to parse and ' \ 60 | + 're-generate ELF file correctly. This can happen '\ 61 | + 'when the ELF file is parsed out of an other file '\ 62 | + 'like a core dump. Use "force=True" to ignore this '\ 63 | + 'check.') 64 | 65 | # this function interprets the r_info field from ElfN_Rel(a) structs 66 | # depending on self.bits 67 | def relocationSymIdxAndTypeFromInfo(self, rInfo): 68 | if self.bits == 32: 69 | rSym = (rInfo >> 8) 70 | rType = (rInfo & 0xff) 71 | elif self.bits == 64: 72 | rSym = (rInfo >> 32) 73 | rType = (rInfo & 0xffffffff) 74 | return (rSym, rType) 75 | 76 | 77 | # this function converts a section header entry to a list of data 78 | # return values: (bytearray) converted section header entry 79 | def sectionHeaderEntryToBytearray(self, sectionHeaderEntryToWrite): 80 | if self.bits == 32: 81 | structFormat = '< 2I 4I 2I 2I' 82 | elif self.bits == 64: 83 | structFormat = '< 2I 4Q 2I 2Q' 84 | 85 | sectionHeaderEntryRaw = bytearray(struct.pack(structFormat, 86 | # uint32_t sh_name; 87 | sectionHeaderEntryToWrite.sh_name, 88 | # uint32_t sh_type; 89 | sectionHeaderEntryToWrite.sh_type, 90 | # uintN_t sh_flags; (N = 32/64) 91 | sectionHeaderEntryToWrite.sh_flags, 92 | # ElfN_Addr sh_addr; (N = 32/64) 93 | sectionHeaderEntryToWrite.sh_addr, 94 | # ElfN_Off sh_offset; (N = 32/64) 95 | sectionHeaderEntryToWrite.sh_offset, 96 | # uintN_t sh_size; (N = 32/64) 97 | sectionHeaderEntryToWrite.sh_size, 98 | # uint32_t sh_link; 99 | sectionHeaderEntryToWrite.sh_link, 100 | # uint32_t sh_info; 101 | sectionHeaderEntryToWrite.sh_info, 102 | # uintN_t sh_addralign; (N = 32/64) 103 | sectionHeaderEntryToWrite.sh_addralign, 104 | # uintN_t sh_entsize; (N = 32/64) 105 | sectionHeaderEntryToWrite.sh_entsize, 106 | )) 107 | 108 | return sectionHeaderEntryRaw 109 | 110 | 111 | # this function generates a new section 112 | # return values: (Section) new generated section 113 | def generateNewSection(self, sectionName, sh_name, sh_type, sh_flags, 114 | sh_addr, sh_offset, sh_size, sh_link, sh_info, sh_addralign, 115 | sh_entsize): 116 | newsection = Section() 117 | 118 | newsection.sectionName = sectionName 119 | 120 | ''' 121 | uint32_t sh_name; 122 | ''' 123 | newsection.elfN_shdr.sh_name = sh_name 124 | 125 | ''' 126 | uint32_t sh_type; 127 | ''' 128 | newsection.elfN_shdr.sh_type = sh_type 129 | 130 | ''' 131 | uintN_t sh_flags; (N = 32/64) 132 | ''' 133 | newsection.elfN_shdr.sh_flags = sh_flags 134 | 135 | ''' 136 | ElfN_Addr sh_addr; (N = 32/64) 137 | ''' 138 | newsection.elfN_shdr.sh_addr = sh_addr 139 | 140 | ''' 141 | ElfN_Off sh_offset; (N = 32/64) 142 | ''' 143 | newsection.elfN_shdr.sh_offset = sh_offset 144 | 145 | ''' 146 | uintN_t sh_size; (N = 32/64) 147 | ''' 148 | newsection.elfN_shdr.sh_size = sh_size 149 | 150 | ''' 151 | uint32_t sh_link; 152 | ''' 153 | newsection.elfN_shdr.sh_link = sh_link 154 | 155 | ''' 156 | uint32_t sh_info; 157 | ''' 158 | newsection.elfN_shdr.sh_info = sh_info 159 | 160 | ''' 161 | uintN_t sh_addralign; (N = 32/64) 162 | ''' 163 | newsection.elfN_shdr.sh_addralign = sh_addralign 164 | 165 | ''' 166 | uintN_t sh_entsize; (N = 32/64) 167 | ''' 168 | newsection.elfN_shdr.sh_entsize = sh_entsize 169 | 170 | return newsection 171 | 172 | 173 | # this function parses a dynamic symbol at the given offset 174 | # return values: (DynamicSymbol) the parsed dynamic symbol 175 | def _parseDynamicSymbol(self, offset, stringTableOffset, stringTableSize): 176 | 177 | # check if the file was completely parsed before 178 | if self.fileParsed is False: 179 | raise ValueError("Operation not possible. " \ 180 | + "File was not completely parsed before.") 181 | 182 | tempSymbol = DynamicSymbol() 183 | 184 | # get values from the symbol table 185 | """ 186 | typedef struct { 187 | uint32_t st_name; 188 | Elf32_Addr st_value; // * 189 | uint32_t st_size; // * 190 | unsigned char st_info; 191 | unsigned char st_other; 192 | uint16_t st_shndx; 193 | } Elf32_Sym; 194 | 195 | typedef struct { 196 | uint32_t st_name; 197 | unsigned char st_info; 198 | unsigned char st_other; 199 | uint16_t st_shndx; 200 | Elf64_Addr st_value; // * 201 | uint64_t st_size; // * 202 | } Elf64_Sym; 203 | 204 | Difference: order (*) 205 | """ 206 | if self.bits == 32: 207 | fmt = ' sh_size of string table section = 0 696 | # => Non-zero indexes to string table are invalid 697 | 698 | # list of sections not empty => read whole string table 699 | if self.sections: 700 | nStart = self.sections[self.header.e_shstrndx].elfN_shdr.sh_offset 701 | nEnd = nStart + self.sections[self.header.e_shstrndx].elfN_shdr.sh_size 702 | stringtable_str = buffer_list[nStart:nEnd] 703 | 704 | # get name from string table for each section 705 | for i in range(len(self.sections)): 706 | 707 | # check if string table exists => abort reading 708 | if len(stringtable_str) == 0: 709 | break 710 | 711 | nStart = self.sections[i].elfN_shdr.sh_name 712 | nEnd = stringtable_str.find('\x00', nStart) 713 | # use empty string if string is not terminated (nEnd == -1) 714 | nEnd = max(nStart, nEnd) 715 | self.sections[i].sectionName = bytes(stringtable_str[nStart:nEnd]) 716 | 717 | 718 | ############################################### 719 | # parse program header table 720 | 721 | ''' 722 | typedef struct { 723 | uint32_t p_type; 724 | Elf32_Off p_offset; 725 | Elf32_Addr p_vaddr; 726 | Elf32_Addr p_paddr; 727 | uint32_t p_filesz; 728 | uint32_t p_memsz; 729 | uint32_t p_flags; 730 | uint32_t p_align; 731 | } Elf32_Phdr; 732 | 733 | typedef struct { 734 | uint32_t p_type; 735 | uint32_t p_flags; 736 | Elf64_Off p_offset; 737 | Elf64_Addr p_vaddr; 738 | Elf64_Addr p_paddr; 739 | uint64_t p_filesz; 740 | uint64_t p_memsz; 741 | uint64_t p_align; 742 | } Elf64_Phdr; 743 | 744 | The main difference lies in the location of p_flags within the struct. 745 | ''' 746 | 747 | # create a list of the program_header_table 748 | self.segments = list() 749 | 750 | for i in range(self.header.e_phnum): 751 | ''' 752 | uint32_t p_type; 753 | 754 | This member of the Phdr struct tells what kind of segment 755 | this array element describes or how to interpret the array 756 | element's information. 757 | 758 | 759 | (uint32_t p_flags; (Elf64_Phdr only, see below)) 760 | 761 | 762 | ElfN_Off p_offset; (N = 32/64) 763 | 764 | This member holds the offset from the beginning of the 765 | file at which the first byte of the segment resides. 766 | 767 | 768 | ElfN_Addr p_vaddr; (N = 32/64) 769 | 770 | This member holds the virtual address at which the first 771 | byte of the segment resides in memory. 772 | 773 | 774 | ElfN_Addr p_paddr; (N = 32/64) 775 | 776 | On systems for which physical addressing is relevant, this 777 | member is reserved for the segment's physical address. 778 | Under BSD this member is not used and must be zero. 779 | 780 | 781 | uintN_t p_filesz; (N = 32/64) 782 | 783 | This member holds the number of bytes in the file image of 784 | the segment. It may be zero. 785 | 786 | 787 | uintN_t p_memsz; (N = 32/64) 788 | 789 | This member holds the number of bytes in the memory image 790 | of the segment. It may be zero. 791 | 792 | 793 | uint32_t p_flags; (Elf32_Phdr only, for 64 see above) 794 | 795 | This member holds a bitmask of flags relevant to the segment: 796 | 797 | PF_X An executable segment. 798 | PF_W A writable segment. 799 | PF_R A readable segment. 800 | 801 | A text segment commonly has the flags PF_X and PF_R. 802 | A data segment commonly has PF_X, PF_W and PF_R. 803 | 804 | uintN_t p_align; (N = 32/64) 805 | 806 | This member holds the value to which the segments are aligned 807 | in memory and in the file. Loadable process segments 808 | must have congruent values for p_vaddr and p_offset, modulo 809 | the page size. Values of zero and one mean no alignment is 810 | required. Otherwise, p_align should be a positive, integral 811 | power of two, and p_vaddr should equal p_offset, modulo 812 | p_align. 813 | ''' 814 | 815 | tempSegment = Segment() 816 | tempOffset = self.header.e_phoff + i*self.header.e_phentsize 817 | 818 | if self.bits == 32: 819 | unpackedSegment = struct.unpack('< I 5I I I', \ 820 | buffer_list[tempOffset:tempOffset+32]) 821 | elif self.bits == 64: 822 | unpackedSegment = struct.unpack('< I I 5Q Q', \ 823 | buffer_list[tempOffset:tempOffset+56]) 824 | # order elements as in Elf32_Phdr 825 | unpackedSegment = unpackedSegment[0:1] + unpackedSegment[2:7] \ 826 | + unpackedSegment[1:2] + unpackedSegment[7:8] 827 | 828 | del tempOffset 829 | 830 | ( 831 | tempSegment.elfN_Phdr.p_type, 832 | tempSegment.elfN_Phdr.p_offset, # 32/64 bit! 833 | tempSegment.elfN_Phdr.p_vaddr, # 32/64 bit! 834 | tempSegment.elfN_Phdr.p_paddr, # 32/64 bit! 835 | tempSegment.elfN_Phdr.p_filesz, # 32/64 bit! 836 | tempSegment.elfN_Phdr.p_memsz, # 32/64 bit! 837 | tempSegment.elfN_Phdr.p_flags, # position as in Elf32_Phdr 838 | tempSegment.elfN_Phdr.p_align, # 32/64 bit! 839 | ) = unpackedSegment 840 | 841 | # check which sections are in the current segment 842 | # (in memory) and add them 843 | for section in self.sections: 844 | segStart = tempSegment.elfN_Phdr.p_vaddr 845 | segEnd = segStart + tempSegment.elfN_Phdr.p_memsz 846 | sectionStart = section.elfN_shdr.sh_addr 847 | sectionEnd = sectionStart + section.elfN_shdr.sh_size 848 | 849 | if segStart <= sectionStart and sectionEnd <= segEnd: 850 | tempSegment.sectionsWithin.append(section) 851 | 852 | self.segments.append(tempSegment) 853 | 854 | 855 | # get all segments within a segment 856 | for outerSegment in self.segments: 857 | # PT_GNU_STACK only holds access rights 858 | if outerSegment.elfN_Phdr.p_type == P_type.PT_GNU_STACK: 859 | continue 860 | 861 | for segmentWithin in self.segments: 862 | # PT_GNU_STACK only holds access rights 863 | if segmentWithin.elfN_Phdr.p_type == P_type.PT_GNU_STACK: 864 | continue 865 | 866 | # skip if segments are the same 867 | if segmentWithin == outerSegment: 868 | continue 869 | 870 | # check if segmentWithin lies within the outerSegment 871 | innerStart = segmentWithin.elfN_Phdr.p_offset 872 | innerEnd = innerStart + segmentWithin.elfN_Phdr.p_filesz 873 | outerStart = outerSegment.elfN_Phdr.p_offset 874 | outerEnd = outerStart + outerSegment.elfN_Phdr.p_filesz 875 | 876 | if outerStart <= innerStart and innerEnd <= outerEnd: 877 | outerSegment.segmentsWithin.append(segmentWithin) 878 | 879 | 880 | ############################################### 881 | # parse dynamic segment entries 882 | 883 | ''' 884 | typedef struct { 885 | Elf32_Sword d_tag; 886 | union { 887 | Elf32_Word d_val; 888 | Elf32_Addr d_ptr; 889 | } d_un; 890 | } Elf32_Dyn; 891 | 892 | typedef struct { 893 | Elf64_Sxword d_tag; 894 | union { 895 | Elf64_Xword d_val; 896 | Elf64_Addr d_ptr; 897 | } d_un; 898 | } Elf64_Dyn; 899 | ''' 900 | 901 | # find dynamic segment 902 | dynamicSegment = None 903 | for segment in self.segments: 904 | if segment.elfN_Phdr.p_type == P_type.PT_DYNAMIC: 905 | dynamicSegment = segment 906 | break 907 | if dynamicSegment is None: 908 | raise ValueError("Segment of type PT_DYNAMIC was not found.") 909 | 910 | # create a list for all dynamic segment entries 911 | self.dynamicSegmentEntries = list() 912 | 913 | if self.bits == 32: 914 | structFmt = ' size of symbol table is difference between string and symbol table 1036 | estimatedSymbolTableSize = stringTableOffset - symbolTableOffset 1037 | 1038 | # find .dynsym section in sections 1039 | # and only use if it exists once 1040 | dynSymSection = None 1041 | dynSymSectionDuplicated = False 1042 | dynSymSectionIgnore = False 1043 | dynSymEstimationIgnore = False 1044 | for section in self.sections: 1045 | if section.sectionName == ".dynsym": 1046 | # check if .dynsym section only exists once 1047 | # (because section entries are optional and can 1048 | # be easily manipulated) 1049 | if dynSymSection is None: 1050 | dynSymSection = section 1051 | 1052 | # when .dynsym section exists multiple times 1053 | # do not use it 1054 | else: 1055 | dynSymSectionDuplicated = True 1056 | break 1057 | 1058 | # check if .dynsym section exists 1059 | if dynSymSection is None: 1060 | print 'NOTE: ".dynsym" section was not found. Trying to use ' \ 1061 | + 'estimation to parse all symbols from the symbol table' 1062 | dynSymSectionIgnore = True 1063 | 1064 | # check if .dynsym section was found multiple times 1065 | elif dynSymSectionDuplicated is True: 1066 | print 'NOTE: ".dynsym" section was found multiple times. ' \ 1067 | + 'Trying to use estimation to parse all symbols from' \ 1068 | + 'the symbol table' 1069 | dynSymSectionIgnore = True 1070 | 1071 | # check if symbol table offset matches the offset of the 1072 | # ".dynsym" section 1073 | elif dynSymSection.elfN_shdr.sh_offset != symbolTableOffset: 1074 | print 'NOTE: ".dynsym" section offset does not match ' \ 1075 | + 'offset of symbol table. Ignoring the section ' \ 1076 | + 'and using the estimation.' 1077 | dynSymSectionIgnore = True 1078 | 1079 | # check if the size of the ".dynsym" section matches the 1080 | # estimated size 1081 | elif dynSymSection.elfN_shdr.sh_size != estimatedSymbolTableSize: 1082 | 1083 | # check if forceDynSymParsing was not set (default value is 0) 1084 | if self.forceDynSymParsing == 0: 1085 | print 'WARNING: ".dynsym" size does not match the estimated ' \ 1086 | + 'size. One (or both) are wrong. Ignoring the dynamic ' \ 1087 | + ' symbols. You can force the using of the ".dynsym" ' \ 1088 | + 'section by setting "forceDynSymParsing=1" or force ' \ 1089 | + 'the using of the estimated size by setting ' \ 1090 | + '"forceDynSymParsing=2".' 1091 | 1092 | # ignore dynamic symbols 1093 | dynSymSectionIgnore = True 1094 | dynSymEstimationIgnore = True 1095 | 1096 | # forcing the use of the ".dynsym" section 1097 | elif self.forceDynSymParsing == 1: 1098 | 1099 | dynSymSectionIgnore = False 1100 | dynSymEstimationIgnore = True 1101 | 1102 | # forcing the use of the estimation 1103 | elif self.forceDynSymParsing == 2: 1104 | 1105 | dynSymSectionIgnore = True 1106 | dynSymEstimationIgnore = False 1107 | 1108 | # value does not exists 1109 | else: 1110 | raise TypeError('"forceDynSymParsing" uses an invalid value.') 1111 | 1112 | # use ".dynsym" section information (when considered correct) 1113 | if dynSymSectionIgnore is False: 1114 | 1115 | # parse the complete symbol table based on the 1116 | # ".dynsym" section 1117 | for i in range(dynSymSection.elfN_shdr.sh_size \ 1118 | / symbolEntrySize): 1119 | 1120 | tempOffset = symbolTableOffset + (i*symbolEntrySize) 1121 | tempSymbol = self._parseDynamicSymbol(tempOffset, 1122 | stringTableOffset, stringTableSize) 1123 | 1124 | # add entry to dynamic symbol entries list 1125 | self.dynamicSymbolEntries.append(tempSymbol) 1126 | 1127 | # use estimation to parse dynamic symbols 1128 | elif (dynSymSectionIgnore is True 1129 | and dynSymEstimationIgnore is False): 1130 | 1131 | # parse the complete symbol table based on the 1132 | # estimation 1133 | for i in range(estimatedSymbolTableSize \ 1134 | / symbolEntrySize): 1135 | 1136 | tempOffset = symbolTableOffset + (i*symbolEntrySize) 1137 | tempSymbol = self._parseDynamicSymbol(tempOffset, 1138 | stringTableOffset, stringTableSize) 1139 | 1140 | # add entry to dynamic symbol entries list 1141 | self.dynamicSymbolEntries.append(tempSymbol) 1142 | 1143 | # holds tuples: (type, offset, size, targetlist) 1144 | relocTODO = [] 1145 | 1146 | # DT_JMPREL 1147 | if jmpRelOffset is not None: 1148 | if pltRelType is None: 1149 | raise ValueError('DT_JMPREL present but DT_PLTREL not.') 1150 | if pltRelSize is None: 1151 | raise ValueError('DT_JMPREL present but DT_PLTRELSZ not.') 1152 | 1153 | if pltRelType == D_tag.DT_REL: 1154 | if relEntrySize is None: 1155 | raise ValueError('DT_JMPREL present with ' \ 1156 | + ' DT_PLTREL == DT_REL, but DT_RELSZ not present') 1157 | elif pltRelType == D_tag.DT_RELA: 1158 | if relaEntrySize is None: 1159 | raise ValueError('DT_JMPREL present with ' \ 1160 | + ' DT_PLTREL == DT_RELA, but DT_RELASZ not present') 1161 | else: 1162 | raise ValueError('Invalid/unexpected DT_PLTREL (pltRelType).') 1163 | 1164 | self.jumpRelocationEntries = list() 1165 | relocTODO.append((pltRelType, jmpRelOffset, pltRelSize, 1166 | self.jumpRelocationEntries)) 1167 | 1168 | # DT_REL (only mandatory hwn DT_RELA is not present) 1169 | if relOffset is not None: 1170 | if relSize is None: 1171 | raise ValueError('DT_REL present but DT_RELSZ not.') 1172 | 1173 | if relEntrySize is None: 1174 | raise ValueError('DT_REL present but DT_RELENT not.') 1175 | 1176 | self.relocationEntries = list() 1177 | relocTODO.append((D_tag.DT_REL, relOffset, relSize, 1178 | self.relocationEntries)) 1179 | 1180 | # DT_RELA 1181 | if relaOffset is not None: 1182 | if relaSize is None: 1183 | raise ValueError('DT_RELA present but DT_RELASZ not.') 1184 | 1185 | if relaEntrySize is None: 1186 | raise ValueError('DT_RELA present but DT_RELAENT not.') 1187 | 1188 | self.relocationEntries = list() 1189 | relocTODO.append((D_tag.DT_RELA, relaOffset, relaSize, 1190 | self.relocationEntries)) 1191 | 1192 | if relOffset is not None and relaOffset is not None: 1193 | raise RuntimeError('INTERNAL ERROR: TODO REL READ 1') 1194 | 1195 | if len(relocTODO) < 1: 1196 | raise RuntimeError('INTERNAL ERROR: TODO REL READ 2') 1197 | 1198 | 1199 | for relocType, relocOffset, relocSize, relocList in relocTODO: 1200 | if relocType == D_tag.DT_REL: 1201 | relocEntrySize = relEntrySize 1202 | elif relocType == D_tag.DT_RELA: 1203 | relocEntrySize = relaEntrySize 1204 | 1205 | if relocType == D_tag.DT_REL: 1206 | if self.bits == 32: 1207 | structFmt = ' r_offset holds a virtual address 1227 | relocEntry.r_offset, 1228 | 1229 | # ElfN_Word r_info; (N = 32/64) 1230 | relocEntry.r_info, 1231 | ) = struct.unpack(structFmt, self.data[tempOffset:tempOffset+relocEntrySize]) 1232 | elif relocType == D_tag.DT_RELA: 1233 | relocEntry = ElfN_Rela() 1234 | ( 1235 | # ElfN_Addr r_offset; (N = 32/64) 1236 | # in executable and share object files 1237 | # => r_offset holds a virtual address 1238 | relocEntry.r_offset, 1239 | 1240 | # ElfN_Word r_info; (N = 32/64) 1241 | relocEntry.r_info, 1242 | 1243 | relocEntry.r_addend, 1244 | ) = struct.unpack(structFmt, self.data[tempOffset:tempOffset+relocEntrySize]) 1245 | 1246 | del tempOffset 1247 | 1248 | (relocEntry.r_sym, relocEntry.r_type) = \ 1249 | self.relocationSymIdxAndTypeFromInfo(relocEntry.r_info) 1250 | 1251 | # get values from the symbol table 1252 | tempOffset = symbolTableOffset \ 1253 | + (relocEntry.r_sym*symbolEntrySize) 1254 | tempSymbol = self._parseDynamicSymbol(tempOffset, 1255 | stringTableOffset, stringTableSize) 1256 | 1257 | # check if parsed dynamic symbol already exists 1258 | # if it does => use already existed dynamic symbol 1259 | # else => use newly parsed dynamic symbol 1260 | dynamicSymbolFound = False 1261 | for dynamicSymbol in self.dynamicSymbolEntries: 1262 | if (tempSymbol.ElfN_Sym.st_name 1263 | == dynamicSymbol.ElfN_Sym.st_name 1264 | and tempSymbol.ElfN_Sym.st_value 1265 | == dynamicSymbol.ElfN_Sym.st_value 1266 | and tempSymbol.ElfN_Sym.st_size 1267 | == dynamicSymbol.ElfN_Sym.st_size 1268 | and tempSymbol.ElfN_Sym.st_info 1269 | == dynamicSymbol.ElfN_Sym.st_info 1270 | and tempSymbol.ElfN_Sym.st_other 1271 | == dynamicSymbol.ElfN_Sym.st_other 1272 | and tempSymbol.ElfN_Sym.st_shndx 1273 | == dynamicSymbol.ElfN_Sym.st_shndx): 1274 | relocEntry.symbol = dynamicSymbol 1275 | dynamicSymbolFound = True 1276 | break 1277 | if dynamicSymbolFound is False: 1278 | relocEntry.symbol = tempSymbol 1279 | 1280 | relocList.append(relocEntry) 1281 | 1282 | 1283 | # this function dumps a list of relocations (used in printElf()) 1284 | # return values: None 1285 | def printRelocations(self, relocationList, title): 1286 | printAddend = len(relocationList) \ 1287 | and type(relocationList[0]) == ElfN_Rela 1288 | 1289 | # output all jump relocation entries 1290 | print("%s (%d entries)" % (title, len(relocationList))) 1291 | print("No."), 1292 | print("\t"), 1293 | print("MemAddr"), 1294 | print("\t"), 1295 | print("File offset"), 1296 | print("\t"), 1297 | print("Info"), 1298 | print("\t\t"), 1299 | print("Type"), 1300 | print("\t\t"), 1301 | if printAddend: 1302 | print("Addend"), 1303 | print("\t\t"), 1304 | print("Sym. value"), 1305 | print("\t"), 1306 | print("Sym. name"), 1307 | print 1308 | print("\t"), 1309 | print("(r_offset)"), 1310 | print("\t"), 1311 | print("\t"), 1312 | print("\t"), 1313 | print("(r_info)"), 1314 | print("\t"), 1315 | print("(r_type)"), 1316 | if printAddend: 1317 | print("\t"), 1318 | print("(r_addend)"), 1319 | print 1320 | 1321 | counter = 0 1322 | for entry in relocationList: 1323 | symbol = entry.symbol.ElfN_Sym 1324 | print("%d" % counter), 1325 | print("\t"), 1326 | print("0x" + ("%x" % entry.r_offset).zfill(8)), 1327 | print("\t"), 1328 | 1329 | # try to convert the virtual memory address to a file offset 1330 | # in executable and share object files 1331 | # => r_offset holds a virtual address 1332 | try: 1333 | print("0x" + ("%x" \ 1334 | % self.virtualMemoryAddrToFileOffset( 1335 | entry.r_offset)).zfill(8)), 1336 | except: 1337 | print("None\t"), 1338 | 1339 | print("\t"), 1340 | print("0x" + ("%x" % entry.r_info).zfill(8)), 1341 | print("\t"), 1342 | 1343 | # translate type 1344 | if entry.r_type in R_type.reverse_lookup.keys(): 1345 | print("%s" % R_type.reverse_lookup[entry.r_type]), 1346 | else: 1347 | print("0x%x" % entry.r_type), 1348 | 1349 | if printAddend: 1350 | if type(entry) == ElfN_Rela: 1351 | print("\t"), 1352 | print("0x" + ("%x" % entry.r_addend).zfill(8)), 1353 | else: 1354 | print("\t\t"), 1355 | 1356 | print("\t"), 1357 | print("0x" + ("%x" % symbol.st_value).zfill(8)), 1358 | 1359 | print("\t"), 1360 | print(entry.symbol.symbolName), 1361 | 1362 | print 1363 | 1364 | counter += 1 1365 | 1366 | print 1367 | 1368 | 1369 | # this function outputs the parsed ELF file (like readelf) 1370 | # return values: None 1371 | def printElf(self): 1372 | 1373 | # check if the file was completely parsed before 1374 | if self.fileParsed is False: 1375 | raise ValueError("Operation not possible. " \ 1376 | + "File was not completely parsed before.") 1377 | 1378 | # output header 1379 | print "ELF header:" 1380 | print "Type: %s" % ElfN_Ehdr.E_type.reverse_lookup[self.header.e_type] 1381 | print "Version: %s" \ 1382 | % ElfN_Ehdr.EI_VERSION.reverse_lookup[self.header.e_ident[6]] 1383 | print "Machine: %s" \ 1384 | % ElfN_Ehdr.E_machine.reverse_lookup[self.header.e_machine] 1385 | print "Entry point address: 0x%x" % self.header.e_entry 1386 | print "Program header table offset in bytes: 0x%x (%d)" \ 1387 | % (self.header.e_phoff, self.header.e_phoff) 1388 | print "Section header table offset in bytes: 0x%x (%d)" \ 1389 | % (self.header.e_shoff, self.header.e_shoff) 1390 | print "Flags: 0x%x (%d)" % (self.header.e_flags, self.header.e_flags) 1391 | print "Size of ELF header in bytes: 0x%x (%d)" \ 1392 | % (self.header.e_ehsize, self.header.e_ehsize) 1393 | print "Size of each program header entry in bytes: 0x%x (%d)" \ 1394 | % (self.header.e_phentsize, self.header.e_phentsize) 1395 | print "Number of program header entries: %d" % self.header.e_phnum 1396 | print "Size of each sections header entry in bytes: 0x%x (%d)" \ 1397 | % (self.header.e_shentsize, self.header.e_shentsize) 1398 | print "Number of section header entries: %d" % self.header.e_shnum 1399 | print "Section header string table index: %d" % self.header.e_shstrndx 1400 | print 1401 | 1402 | 1403 | # output of all sections 1404 | counter = 0 1405 | for section in self.sections: 1406 | print "Section No. %d" % counter 1407 | print "Name: %s" % section.sectionName 1408 | 1409 | # translate type 1410 | if section.elfN_shdr.sh_type in SH_type.reverse_lookup.keys(): 1411 | print "Type: %s" \ 1412 | % SH_type.reverse_lookup[section.elfN_shdr.sh_type] 1413 | else: 1414 | print "Unknown Type: 0x%x (%d)" \ 1415 | % (section.elfN_shdr.sh_type, section.elfN_shdr.sh_type) 1416 | 1417 | print "Addr: 0x%x" % section.elfN_shdr.sh_addr 1418 | print "Off: 0x%x" % section.elfN_shdr.sh_offset 1419 | print "Size: 0x%x (%d)" \ 1420 | % (section.elfN_shdr.sh_size, section.elfN_shdr.sh_size) 1421 | print "ES: %d" % section.elfN_shdr.sh_entsize 1422 | 1423 | # translate flags 1424 | temp = "" 1425 | if (section.elfN_shdr.sh_flags & SH_flags.SHF_WRITE) != 0: 1426 | temp += "W" 1427 | if (section.elfN_shdr.sh_flags & SH_flags.SHF_ALLOC) != 0: 1428 | temp += "A" 1429 | if (section.elfN_shdr.sh_flags & SH_flags.SHF_EXECINSTR) != 0: 1430 | temp += "X" 1431 | 1432 | print "FLG: %s" % temp 1433 | print "Lk: %d" % section.elfN_shdr.sh_link 1434 | print "Inf: %d" % section.elfN_shdr.sh_info 1435 | print "Al: %d" % section.elfN_shdr.sh_addralign 1436 | print 1437 | counter += 1 1438 | 1439 | 1440 | # output of all segments 1441 | counter = 0 1442 | for segment in self.segments: 1443 | print "Segment No. %d" % counter 1444 | 1445 | # translate type 1446 | if segment.elfN_Phdr.p_type in P_type.reverse_lookup.keys(): 1447 | print "Type: %s" \ 1448 | % P_type.reverse_lookup[segment.elfN_Phdr.p_type] 1449 | else: 1450 | print "Unknown Type: 0x%x (%d)" \ 1451 | % (segment.elfN_Phdr.p_type, segment.elfN_Phdr.p_type) 1452 | 1453 | print "Offset: 0x%x" % segment.elfN_Phdr.p_offset 1454 | print "Virtual Addr: 0x%x" % segment.elfN_Phdr.p_vaddr 1455 | print "Physical Addr: 0x%x" % segment.elfN_Phdr.p_paddr 1456 | print "File Size: 0x%x (%d)" \ 1457 | % (segment.elfN_Phdr.p_filesz, segment.elfN_Phdr.p_filesz) 1458 | print "Mem Size: 0x%x (%d)" \ 1459 | % (segment.elfN_Phdr.p_memsz, segment.elfN_Phdr.p_memsz) 1460 | 1461 | # translate flags 1462 | temp = "" 1463 | if (segment.elfN_Phdr.p_flags & P_flags.PF_R) != 0: 1464 | temp += "R" 1465 | if (segment.elfN_Phdr.p_flags & P_flags.PF_W) != 0: 1466 | temp += "W" 1467 | if (segment.elfN_Phdr.p_flags & P_flags.PF_X) != 0: 1468 | temp += "X" 1469 | print "Flags: %s" % temp 1470 | 1471 | print "Align: 0x%x" % segment.elfN_Phdr.p_align 1472 | 1473 | # print which sections are in the current segment (in memory) 1474 | temp = "" 1475 | for section in segment.sectionsWithin: 1476 | temp += section.sectionName + " " 1477 | if temp != "": 1478 | print "Sections in segment: " + temp 1479 | 1480 | # print which segments are within current segment (in file) 1481 | temp = "" 1482 | for segmentWithin in segment.segmentsWithin: 1483 | for i in range(len(self.segments)): 1484 | if segmentWithin == self.segments[i]: 1485 | temp += "%d, " % i 1486 | break 1487 | if temp != "": 1488 | print "Segments within segment: " + temp 1489 | 1490 | # get interpreter if segment is for interpreter 1491 | # null-terminated string 1492 | if segment.elfN_Phdr.p_type == P_type.PT_INTERP: 1493 | nStart = segment.elfN_Phdr.p_offset 1494 | nEnd = nStart + segment.elfN_Phdr.p_filesz 1495 | print "Interpreter: %s" % self.data[nStart:nEnd] 1496 | 1497 | print 1498 | counter += 1 1499 | 1500 | 1501 | # search string table entry, string table size, 1502 | # symbol table entry and symbol table entry size 1503 | stringTableOffset = None 1504 | stringTableSize = None 1505 | symbolTableOffset = None 1506 | symbolEntrySize = None 1507 | for searchEntry in self.dynamicSegmentEntries: 1508 | if searchEntry.d_tag == D_tag.DT_STRTAB: 1509 | # data contains virtual memory address 1510 | # => calculate offset in file 1511 | stringTableOffset = \ 1512 | self.virtualMemoryAddrToFileOffset(searchEntry.d_un) 1513 | if searchEntry.d_tag == D_tag.DT_STRSZ: 1514 | stringTableSize = searchEntry.d_un 1515 | if searchEntry.d_tag == D_tag.DT_SYMTAB: 1516 | # data contains virtual memory address 1517 | # => calculate offset in file 1518 | symbolTableOffset = \ 1519 | self.virtualMemoryAddrToFileOffset(searchEntry.d_un) 1520 | if searchEntry.d_tag == D_tag.DT_SYMENT: 1521 | symbolEntrySize = searchEntry.d_un 1522 | 1523 | if (stringTableOffset is None 1524 | or stringTableSize is None 1525 | or symbolTableOffset is None 1526 | or symbolEntrySize is None): 1527 | raise ValueError("No dynamic section entry of type DT_STRTAB," \ 1528 | + " DT_STRSZ, DT_SYMTAB and/or DT_SYMENT found (malformed"\ 1529 | + " ELF executable/shared object).") 1530 | 1531 | 1532 | # output all dynamic segment entries 1533 | counter = 0 1534 | for entry in self.dynamicSegmentEntries: 1535 | print "Dynamic segment entry No. %d" % counter 1536 | if entry.d_tag in D_tag.reverse_lookup.keys(): 1537 | print "Type: %s" % D_tag.reverse_lookup[entry.d_tag] 1538 | else: 1539 | print "Unknwon Type: 0x%x (%d)" % (entry.d_tag, entry.d_tag) 1540 | 1541 | # check if entry tag equals DT_NEEDED => get library name 1542 | if entry.d_tag == D_tag.DT_NEEDED: 1543 | nStart = stringTableOffset + entry.d_un 1544 | nMaxEnd = stringTableOffset + stringTableSize 1545 | nEnd = self.data.find('\x00', nStart, nMaxEnd) 1546 | nEnd = max(nStart, nEnd) 1547 | temp = bytes(self.data[nStart:nEnd]) 1548 | print "Name/Value: 0x%x (%d) (%s)" \ 1549 | % (entry.d_un, entry.d_un, temp) 1550 | else: 1551 | print "Name/Value: 0x%x (%d)" % (entry.d_un, entry.d_un) 1552 | 1553 | print 1554 | counter += 1 1555 | 1556 | self.printRelocations(self.jumpRelocationEntries, 1557 | "Jump relocation entries") 1558 | 1559 | self.printRelocations(self.relocationEntries, 1560 | "Relocation entries") 1561 | 1562 | # output all dynamic symbol entries 1563 | print("Dynamic symbols (%d entries)" % len(self.dynamicSymbolEntries)) 1564 | print("No."), 1565 | print("\t"), 1566 | print("Value"), 1567 | print("\t\t"), 1568 | print("Size"), 1569 | print("\t"), 1570 | print("Name"), 1571 | print 1572 | 1573 | counter = 0 1574 | for entry in self.dynamicSymbolEntries: 1575 | symbol = entry.ElfN_Sym 1576 | print("%d" % counter), 1577 | print("\t"), 1578 | print("0x" + ("%x" % symbol.st_value).zfill(8)), 1579 | print("\t"), 1580 | print("0x" + ("%x" % symbol.st_size).zfill(3)), 1581 | print("\t"), 1582 | print("%s" % entry.symbolName), 1583 | 1584 | print 1585 | counter += 1 1586 | 1587 | 1588 | # this function generates a new ELF file from the attributes of the object 1589 | # return values: (list) generated ELF file data 1590 | def generateElf(self): 1591 | 1592 | # check if the file was completely parsed before 1593 | if self.fileParsed is False: 1594 | raise ValueError("Operation not possible. " \ 1595 | + "File was not completely parsed before.") 1596 | 1597 | # copy binary data to new list 1598 | newfile = self.data[:] 1599 | 1600 | # ------ 1601 | 1602 | # get position of section header table 1603 | writePosition = self.header.e_shoff 1604 | 1605 | # fill list with null until writePosition is reached 1606 | if len(newfile) < writePosition: 1607 | newfile.extend(bytearray(writePosition - len(newfile))) 1608 | 1609 | # write section header table back 1610 | for section in self.sections: 1611 | temp = self.sectionHeaderEntryToBytearray(section.elfN_shdr) 1612 | newfile[writePosition:writePosition+len(temp)] = temp 1613 | writePosition += len(temp) 1614 | 1615 | # ------ 1616 | 1617 | # when defined => write string table back 1618 | if self.header.e_shstrndx != Shstrndx.SHN_UNDEF: 1619 | for section in self.sections: 1620 | # calculate the position on which the name should be written 1621 | writePosition = \ 1622 | self.sections[self.header.e_shstrndx].elfN_shdr.sh_offset \ 1623 | + section.elfN_shdr.sh_name 1624 | 1625 | # fill list with null until writePosition is reached 1626 | if len(newfile) < writePosition: 1627 | newfile.extend(bytearray(writePosition - len(newfile))) 1628 | 1629 | # write name of all sections into string table 1630 | data = bytearray(section.sectionName) + b'\x00' 1631 | newfile[writePosition:writePosition+len(data)] = data 1632 | writePosition += len(data) 1633 | 1634 | # ------ 1635 | 1636 | # write ELF header back 1637 | newfile[0:len(self.header.e_ident)] = self.header.e_ident 1638 | 1639 | headerFields = ( 1640 | # uint16_t e_type; 1641 | self.header.e_type, 1642 | # uint16_t e_machine; 1643 | self.header.e_machine, 1644 | # uint32_t e_version; 1645 | self.header.e_version, 1646 | # ElfN_Addr e_entry; (32/64 bit) 1647 | self.header.e_entry, 1648 | # ElfN_Off e_phoff; (32/64 bit) 1649 | self.header.e_phoff, 1650 | # ElfN_Off e_shoff; (32/64 bit) 1651 | self.header.e_shoff, 1652 | # uint32_t e_flags; 1653 | self.header.e_flags, 1654 | # uint16_t e_ehsize; 1655 | self.header.e_ehsize, 1656 | # uint16_t e_phentsize; 1657 | self.header.e_phentsize, 1658 | # uint16_t e_phnum; 1659 | self.header.e_phnum, 1660 | # uint16_t e_shentsize; 1661 | self.header.e_shentsize, 1662 | # uint16_t e_shnum; 1663 | self.header.e_shnum, 1664 | # uint16_t e_shstrndx; 1665 | self.header.e_shstrndx 1666 | ) 1667 | 1668 | if self.bits == 32: 1669 | newfile[16:52] = struct.pack('< 2H I 3I I 6H', *headerFields) 1670 | elif self.bits == 64: 1671 | newfile[16:64] = struct.pack('< 2H I 3Q I 6H', *headerFields) 1672 | 1673 | # ------ 1674 | 1675 | # write programm header table back 1676 | for i in range(len(self.segments)): 1677 | 1678 | # add placeholder bytes to new file when the bytes do not already 1679 | # exist in the new file until size of header entry fits 1680 | requiredSize = self.header.e_phoff + ((i+1) * self.header.e_phentsize) 1681 | if len(newfile) < requiredSize: 1682 | newfile.extend(bytearray(requiredSize - len(newfile))) 1683 | 1684 | tempOffset = self.header.e_phoff + i*self.header.e_phentsize 1685 | ''' 1686 | typedef struct { 1687 | uint32_t p_type; 1688 | Elf32_Off p_offset; 1689 | Elf32_Addr p_vaddr; 1690 | Elf32_Addr p_paddr; 1691 | uint32_t p_filesz; 1692 | uint32_t p_memsz; 1693 | uint32_t p_flags; // * 1694 | uint32_t p_align; 1695 | } Elf32_Phdr; 1696 | 1697 | typedef struct { 1698 | uint32_t p_type; 1699 | uint32_t p_flags; // * 1700 | Elf64_Off p_offset; 1701 | Elf64_Addr p_vaddr; 1702 | Elf64_Addr p_paddr; 1703 | uint64_t p_filesz; 1704 | uint64_t p_memsz; 1705 | uint64_t p_align; 1706 | } Elf64_Phdr; 1707 | 1708 | The main difference lies in the location of p_flags within the struct. 1709 | ''' 1710 | if self.bits == 32: 1711 | fmt = '< I 5I I I' 1712 | fmtSize = struct.calcsize(fmt) 1713 | assert self.header.e_phentsize == fmtSize 1714 | newfile[tempOffset:tempOffset+fmtSize] = struct.pack(fmt, 1715 | self.segments[i].elfN_Phdr.p_type, 1716 | self.segments[i].elfN_Phdr.p_offset, 1717 | self.segments[i].elfN_Phdr.p_vaddr, 1718 | self.segments[i].elfN_Phdr.p_paddr, 1719 | self.segments[i].elfN_Phdr.p_filesz, 1720 | self.segments[i].elfN_Phdr.p_memsz, 1721 | self.segments[i].elfN_Phdr.p_flags, # <- p_flags 1722 | self.segments[i].elfN_Phdr.p_align, 1723 | ) 1724 | elif self.bits == 64: 1725 | fmt = '< I I 5Q Q' 1726 | fmtSize = struct.calcsize(fmt) 1727 | assert self.header.e_phentsize == fmtSize 1728 | newfile[tempOffset:tempOffset+fmtSize] = struct.pack(fmt, 1729 | self.segments[i].elfN_Phdr.p_type, 1730 | self.segments[i].elfN_Phdr.p_flags, # <- p_flags 1731 | self.segments[i].elfN_Phdr.p_offset, 1732 | self.segments[i].elfN_Phdr.p_vaddr, 1733 | self.segments[i].elfN_Phdr.p_paddr, 1734 | self.segments[i].elfN_Phdr.p_filesz, 1735 | self.segments[i].elfN_Phdr.p_memsz, 1736 | self.segments[i].elfN_Phdr.p_align, 1737 | ) 1738 | del tempOffset 1739 | 1740 | 1741 | # ------ 1742 | 1743 | # find dynamic segment 1744 | dynamicSegment = None 1745 | for segment in self.segments: 1746 | if segment.elfN_Phdr.p_type == P_type.PT_DYNAMIC: 1747 | dynamicSegment = segment 1748 | break 1749 | if dynamicSegment is None: 1750 | raise ValueError("Segment of type PT_DYNAMIC was not found.") 1751 | 1752 | if self.bits == 32: 1753 | structFmt = ' write (jump) relocation entries back 1859 | 1860 | # holds tuples: (type, offset, size, sourcelist) 1861 | relocTODO = [] 1862 | 1863 | # DT_JMPREL 1864 | if jmpRelOffset is not None: 1865 | relocTODO.append((pltRelType, jmpRelOffset, pltRelSize, 1866 | self.jumpRelocationEntries)) 1867 | 1868 | # DT_REL 1869 | if relOffset is not None: 1870 | relocTODO.append((D_tag.DT_REL, relOffset, relSize, 1871 | self.relocationEntries)) 1872 | 1873 | # DT_RELA 1874 | if relaOffset is not None: 1875 | relocTODO.append((D_tag.DT_RELA, relaOffset, relaSize, 1876 | self.relocationEntries)) 1877 | 1878 | if relOffset is not None and relaOffset is not None: 1879 | raise RuntimeError('INTERNAL ERROR: TODO REL WRITE 1') 1880 | 1881 | 1882 | for relocType, relocOffset, relocSize, relocList in relocTODO: 1883 | if relocType == D_tag.DT_REL: 1884 | relocEntrySize = relEntrySize 1885 | elif relocType == D_tag.DT_RELA: 1886 | relocEntrySize = relaEntrySize 1887 | 1888 | if relocType == D_tag.DT_REL: 1889 | if self.bits == 32: 1890 | structFmt = ' write dynamic symbol back 1923 | dynSym = relocEntry.symbol 1924 | if (dynSym not in dynSymSet and symbolTableOffset is not None): 1925 | self._writeDynamicSymbol(newfile, symbolTableOffset \ 1926 | + relocEntry.r_sym * symbolEntrySize, 1927 | dynSym.ElfN_Sym) 1928 | 1929 | # ------ 1930 | 1931 | return newfile 1932 | 1933 | 1934 | # this function writes the generated ELF file back 1935 | # return values: None 1936 | def writeElf(self, filename): 1937 | 1938 | # check if the file was completely parsed before 1939 | if self.fileParsed is False: 1940 | raise ValueError("Operation not possible. " \ 1941 | + "File was not completely parsed before.") 1942 | 1943 | f = open(filename, "w") 1944 | f.write(self.generateElf()) 1945 | f.close() 1946 | 1947 | 1948 | # this function appends data to a selected segment number (if it fits) 1949 | # return values: (int) offset in file of appended data, 1950 | # (int) address in memory of appended data 1951 | def appendDataToSegment(self, data, segmentNumber, addNewSection=False, 1952 | newSectionName=None, extendExistingSection=False): 1953 | 1954 | # check if the file was completely parsed before 1955 | if self.fileParsed is False: 1956 | raise ValueError("Operation not possible. " \ 1957 | + "File was not completely parsed before.") 1958 | 1959 | segmentToExtend = self.segments[segmentNumber] 1960 | 1961 | # find segment that comes directly after the segment 1962 | # to manipulate in the virtual memory 1963 | nextSegment, diff_p_vaddr \ 1964 | = self.getNextSegmentAndFreeSpace(segmentToExtend) 1965 | 1966 | # check if a segment exists directly after the segment 1967 | # to manipulate in the virtual memory 1968 | if nextSegment is None: 1969 | # segment directly after segment to 1970 | # manipulate does not exist in virtual memory 1971 | 1972 | # get memory address and offset in file of appended data 1973 | newDataMemoryAddr = segmentToExtend.elfN_Phdr.p_vaddr \ 1974 | + segmentToExtend.elfN_Phdr.p_memsz 1975 | newDataOffset = segmentToExtend.elfN_Phdr.p_offset \ 1976 | + segmentToExtend.elfN_Phdr.p_filesz 1977 | 1978 | # insert data 1979 | for i in range(len(data)): 1980 | self.data.insert((newDataOffset + i), data[i]) 1981 | 1982 | # adjust offsets of all following section 1983 | # (for example symbol sections are often behind all segments) 1984 | for section in self.sections: 1985 | if (section.elfN_shdr.sh_offset >= 1986 | (segmentToExtend.elfN_Phdr.p_offset 1987 | + segmentToExtend.elfN_Phdr.p_filesz)): 1988 | section.elfN_shdr.sh_offset += len(data) 1989 | 1990 | # extend size of data in file of the modifed segment 1991 | segmentToExtend.elfN_Phdr.p_filesz += len(data) 1992 | 1993 | # extend size of data in memory of the modifed segment 1994 | segmentToExtend.elfN_Phdr.p_memsz += len(data) 1995 | 1996 | 1997 | else: 1998 | # segment directly after segment to 1999 | # manipulate exists in virtual memory 2000 | 2001 | # check if data to append fits 2002 | if len(data) >= diff_p_vaddr: 2003 | raise ValueError("Size of data to append: %d " \ 2004 | + "Size of memory space: %d" % (len(data), diff_p_vaddr)) 2005 | 2006 | # p_offset and p_vaddr are congruend modulo alignment 2007 | # for example: 2008 | # p_align: 0x1000 (default for LOAD segment) 2009 | # p_offset: 0x016f88 2010 | # p_vaddr: 0x0805ff88 2011 | # => 0x016f88 % 0x1000 = 0xf88 2012 | # both must have 0xf88 at the end of the address 2013 | 2014 | # get how often the appended data fits in the 2015 | # alignment of the segment 2016 | alignmentMultiplier = int(len(data) \ 2017 | / segmentToExtend.elfN_Phdr.p_align) + 1 2018 | 2019 | # calculate the size to add to the offsets 2020 | offsetAddition = alignmentMultiplier \ 2021 | * segmentToExtend.elfN_Phdr.p_align 2022 | 2023 | # adjust offsets of all following section 2024 | for section in self.sections: 2025 | if (section.elfN_shdr.sh_offset 2026 | >= nextSegment.elfN_Phdr.p_offset): 2027 | section.elfN_shdr.sh_offset += offsetAddition 2028 | 2029 | # adjust offsets of following segments 2030 | # (ignore the directly followed segment) 2031 | for segment in self.segments: 2032 | if segment != segmentToExtend and segment != nextSegment: 2033 | # use offset of the directly followed segment in order to 2034 | # ignore segments that lies within the 2035 | # segment to manipulate 2036 | if (segment.elfN_Phdr.p_offset 2037 | > nextSegment.elfN_Phdr.p_offset): 2038 | segment.elfN_Phdr.p_offset += offsetAddition 2039 | 2040 | # adjust offset of the directly following segment of the 2041 | # segment to manipulate 2042 | nextSegment.elfN_Phdr.p_offset += offsetAddition 2043 | 2044 | # if program header table lies behind the segment to manipulate 2045 | # => move it 2046 | if (self.header.e_phoff > (segmentToExtend.elfN_Phdr.p_offset 2047 | + segmentToExtend.elfN_Phdr.p_filesz)): 2048 | self.header.e_phoff += offsetAddition 2049 | 2050 | # if section header table lies behind the segment to manipulate 2051 | # => move it 2052 | if (self.header.e_shoff > (segmentToExtend.elfN_Phdr.p_offset 2053 | + segmentToExtend.elfN_Phdr.p_filesz)): 2054 | self.header.e_shoff += offsetAddition 2055 | 2056 | # get memory address and offset in file of appended data 2057 | newDataMemoryAddr = segmentToExtend.elfN_Phdr.p_vaddr \ 2058 | + segmentToExtend.elfN_Phdr.p_memsz 2059 | newDataOffset = segmentToExtend.elfN_Phdr.p_offset \ 2060 | + segmentToExtend.elfN_Phdr.p_filesz 2061 | 2062 | # insert data 2063 | for i in range(len(data)): 2064 | self.data.insert((newDataOffset + i), data[i]) 2065 | 2066 | # fill the rest with 0x00 until the offset addition in the 2067 | # file is reached 2068 | for i in range((offsetAddition - len(data))): 2069 | self.data.insert((newDataOffset + len(data) + i), "\x00") 2070 | 2071 | # extend size of data in file of the modifed segment 2072 | segmentToExtend.elfN_Phdr.p_filesz += len(data) 2073 | 2074 | # extend size of data in memory of the modifed segment 2075 | segmentToExtend.elfN_Phdr.p_memsz += len(data) 2076 | 2077 | 2078 | # if added data should have an own section => add new section 2079 | if addNewSection and not extendExistingSection: 2080 | 2081 | # calculate alignment of new section 2082 | # start with 16 as alignment (is used by .text section) 2083 | newSectionAddrAlign = 16 2084 | while newSectionAddrAlign != 1: 2085 | if (len(data) % newSectionAddrAlign) == 0: 2086 | break 2087 | else: 2088 | newSectionAddrAlign = newSectionAddrAlign / 2 2089 | 2090 | # add section 2091 | # addNewSection(newSectionName, newSectionType, newSectionFlag, 2092 | # newSectionAddr, newSectionOffset, newSectionSize, 2093 | # newSectionLink, newSectionInfo, newSectionAddrAlign, 2094 | # newSectionEntsize) 2095 | self.addNewSection(newSectionName, SH_type.SHT_PROGBITS, 2096 | (SH_flags.SHF_EXECINSTR | SH_flags.SHF_ALLOC), 2097 | newDataMemoryAddr, newDataOffset, len(data), 0, 0, 2098 | newSectionAddrAlign, 0) 2099 | 2100 | # if added data should extend an existing section 2101 | # => search this section and extend it 2102 | if extendExistingSection and not addNewSection: 2103 | for section in self.sections: 2104 | # the end of an existing section in the virtual 2105 | # memory is generally equal 2106 | # to the virtual memory address of the added data 2107 | if ((section.elfN_shdr.sh_addr + section.elfN_shdr.sh_size) 2108 | == newDataMemoryAddr): 2109 | # check if data is not appended to last section 2110 | # => use free space between segments for section 2111 | if diff_p_vaddr is not None: 2112 | # extend the existing section 2113 | self.extendSection(section, diff_p_vaddr) 2114 | else: 2115 | # extend the existing section 2116 | self.extendSection(section, len(data)) 2117 | 2118 | break 2119 | 2120 | if not extendExistingSection and not addNewSection: 2121 | print "NOTE: if appended data do not belong to a section they " \ 2122 | + "will not be seen by tools that interpret sections " \ 2123 | + "(like 'IDA 6.1.x' without the correct settings or " \ 2124 | + "'strings' in the default configuration)." 2125 | 2126 | # return offset of appended data in file and address in memory 2127 | return newDataOffset, newDataMemoryAddr 2128 | 2129 | 2130 | # this function generates and adds a new section to the ELF file 2131 | # return values: None 2132 | def addNewSection(self, newSectionName, newSectionType, newSectionFlag, 2133 | newSectionAddr, newSectionOffset, newSectionSize, newSectionLink, 2134 | newSectionInfo, newSectionAddrAlign, newSectionEntsize): 2135 | 2136 | # check if the file was completely parsed before 2137 | if self.fileParsed is False: 2138 | raise ValueError("Operation not possible. " \ 2139 | + "File was not completely parsed before.") 2140 | 2141 | # check if sections do not exist 2142 | # => create new section header table 2143 | if len(self.sections) == 0: 2144 | 2145 | # restore section header entry size 2146 | if self.bits == 32: 2147 | self.header.e_shentsize = struct.calcsize('< 2I 4I 2I 2I') 2148 | elif self.bits == 64: 2149 | self.header.e_shentsize = struct.calcsize('< 2I 4Q 2I 2Q') 2150 | 2151 | # when using gcc, first section is NULL section 2152 | # => create one and add it 2153 | # generateNewSection(sectionName, sh_name, sh_type, 2154 | # sh_flags, sh_addr, sh_offset, sh_size, sh_link, 2155 | # sh_info, sh_addralign, sh_entsize) 2156 | newNullSection = self.generateNewSection("", 0, SH_type.SHT_NULL, 0, 2157 | 0, 0, 0, 0, 0, 0, 0) 2158 | self.sections.append(newNullSection) 2159 | 2160 | # increase count of sections 2161 | self.header.e_shnum += 1 2162 | 2163 | # create new ".shstrtab" section (section header string table) 2164 | # and add it to the end of the file 2165 | offsetNewShstrtab = len(self.data) 2166 | nameNewShstrtab = ".shstrtab" 2167 | 2168 | # use third entry in new section header string table 2169 | # as index for the new created section (name for ".shstrtab" is 2170 | # second, name for NULL section first) 2171 | newSectionStringTableIndex = len(nameNewShstrtab) + 1 + 1 2172 | 2173 | # generate new section object and add it 2174 | # generateNewSection(sectionName, sh_name, sh_type, 2175 | # sh_flags, sh_addr, sh_offset, sh_size, sh_link, 2176 | # sh_info, sh_addralign, sh_entsize) 2177 | newSection = self.generateNewSection(newSectionName, 2178 | newSectionStringTableIndex, newSectionType, newSectionFlag, 2179 | newSectionAddr, newSectionOffset, newSectionSize, 2180 | newSectionLink, newSectionInfo, newSectionAddrAlign, 2181 | newSectionEntsize) 2182 | self.sections.append(newSection) 2183 | 2184 | # increase count of sections 2185 | self.header.e_shnum += 1 2186 | 2187 | # calculate length of ".shstrtab" section 2188 | lengthNewShstrtab = len(nameNewShstrtab) + 1 \ 2189 | + len(newSectionName) + 1 + 1 2190 | 2191 | # generate ".shstrtab" section object and add it 2192 | # generateNewSection(sectionName, sh_name, sh_type, 2193 | # sh_flags, sh_addr, sh_offset, sh_size, sh_link, 2194 | # sh_info, sh_addralign, sh_entsize) 2195 | newShstrtabsection = self.generateNewSection(nameNewShstrtab, 2196 | 1, SH_type.SHT_STRTAB, 0, 2197 | 0, offsetNewShstrtab, lengthNewShstrtab, 0, 0, 1, 0) 2198 | self.sections.append(newShstrtabsection) 2199 | 2200 | # increase count of sections 2201 | self.header.e_shnum += 1 2202 | 2203 | # add section header table to the end of the file new file 2204 | self.header.e_shoff = offsetNewShstrtab + lengthNewShstrtab 2205 | 2206 | # new section string table index is the third section 2207 | self.header.e_shstrndx = 2 2208 | 2209 | 2210 | # sections exist 2211 | # => just add section 2212 | else: 2213 | # get index in the string table of the name of the new section 2214 | # (use size of string table to just append new name to string 2215 | # table) 2216 | newSectionStringTableIndex \ 2217 | = self.sections[self.header.e_shstrndx].elfN_shdr.sh_size 2218 | 2219 | # generate new section object 2220 | # generateNewSection(sectionName, sh_name, sh_type, 2221 | # sh_flags, sh_addr, sh_offset, sh_size, sh_link, 2222 | # sh_info, sh_addralign, sh_entsize) 2223 | newsection = self.generateNewSection(newSectionName, 2224 | newSectionStringTableIndex, newSectionType, newSectionFlag, 2225 | newSectionAddr, newSectionOffset, newSectionSize, 2226 | newSectionLink, newSectionInfo, newSectionAddrAlign, 2227 | newSectionEntsize) 2228 | 2229 | # get position of new section 2230 | positionNewSection = None 2231 | for i in range(self.header.e_shnum): 2232 | if (i+1) < self.header.e_shnum: 2233 | if (self.sections[i].elfN_shdr.sh_offset < newSectionOffset 2234 | and self.sections[i+1].elfN_shdr.sh_offset 2235 | >= newSectionOffset): 2236 | positionNewSection = i+1 2237 | 2238 | # if new section comes before string table section 2239 | # => adjust string table section index 2240 | if positionNewSection <= self.header.e_shstrndx: 2241 | self.header.e_shstrndx += 1 2242 | break 2243 | # insert new section at calculated position 2244 | if positionNewSection is None: 2245 | self.sections.append(newsection) 2246 | else: 2247 | self.sections.insert(positionNewSection, newsection) 2248 | 2249 | # section header table lies oft directly behind the string table 2250 | # check if new section name would overwrite data of 2251 | # section header table 2252 | # => move section header table 2253 | if (self.header.e_shoff 2254 | >= (self.sections[self.header.e_shstrndx].elfN_shdr.sh_offset 2255 | + self.sections[self.header.e_shstrndx].elfN_shdr.sh_size) 2256 | and self.header.e_shoff 2257 | <= (self.sections[self.header.e_shstrndx].elfN_shdr.sh_offset 2258 | + self.sections[self.header.e_shstrndx].elfN_shdr.sh_size 2259 | + len(newSectionName) + 1)): 2260 | self.header.e_shoff += len(newSectionName) + 1 2261 | 2262 | # add size of new name to string table + 1 for 2263 | # null-terminated C string 2264 | self.sections[self.header.e_shstrndx].elfN_shdr.sh_size \ 2265 | += len(newSectionName) + 1 2266 | 2267 | # increase count of sections 2268 | self.header.e_shnum += 1 2269 | 2270 | 2271 | # this function extends the section size by the given size 2272 | # return values: None 2273 | def extendSection(self, sectionToExtend, size): 2274 | 2275 | # check if the file was completely parsed before 2276 | if self.fileParsed is False: 2277 | raise ValueError("Operation not possible. " \ 2278 | + "File was not completely parsed before.") 2279 | 2280 | sectionToExtend.elfN_shdr.sh_size += size 2281 | 2282 | 2283 | # this function searches for a executable segment from type 2284 | # PT_LOAD in which the data fits 2285 | # return values: (class Segment) manipulated segment, 2286 | # (int) offset in file of appended data, 2287 | # (int) address in memory of appended data 2288 | def appendDataToExecutableSegment(self, data, addNewSection=False, 2289 | newSectionName=None, extendExistingSection=False): 2290 | 2291 | # check if the file was completely parsed before 2292 | if self.fileParsed is False: 2293 | raise ValueError("Operation not possible. " \ 2294 | + "File was not completely parsed before.") 2295 | 2296 | # get all executable segments from type PT_LOAD 2297 | possibleSegments = list() 2298 | for segment in self.segments: 2299 | if ((segment.elfN_Phdr.p_flags & P_flags.PF_X) == 1 2300 | and segment.elfN_Phdr.p_type == P_type.PT_LOAD): 2301 | possibleSegments.append(segment) 2302 | 2303 | # find space for data in all possible executable segments 2304 | found = False 2305 | for possibleSegment in possibleSegments: 2306 | diff_p_vaddr = None 2307 | # find segment that comes directly after the segment to 2308 | # manipulate in the virtual memory 2309 | # and get the free memory space in between 2310 | for i in range(len(self.segments)): 2311 | if self.segments[i] != possibleSegment: 2312 | if ((self.segments[i].elfN_Phdr.p_vaddr 2313 | - (possibleSegment.elfN_Phdr.p_vaddr 2314 | + possibleSegment.elfN_Phdr.p_memsz)) > 0): 2315 | if (diff_p_vaddr is None 2316 | or (self.segments[i].elfN_Phdr.p_vaddr 2317 | - (possibleSegment.elfN_Phdr.p_vaddr 2318 | + possibleSegment.elfN_Phdr.p_memsz)) 2319 | < diff_p_vaddr): 2320 | diff_p_vaddr = self.segments[i].elfN_Phdr.p_vaddr \ 2321 | - (possibleSegment.elfN_Phdr.p_vaddr \ 2322 | + possibleSegment.elfN_Phdr.p_memsz) 2323 | else: # get position in list of possible segment 2324 | segmentNumber = i 2325 | # check if data to append fits in space 2326 | if diff_p_vaddr > len(data): 2327 | found = True 2328 | break 2329 | if not found: 2330 | raise ValueError(("Size of data to append: %d. Not enough space" \ 2331 | + " after existing executable segment found.") % len(data)) 2332 | 2333 | # append data to segment 2334 | newDataOffset, newDataMemoryAddr = self.appendDataToSegment(data, 2335 | segmentNumber, addNewSection=addNewSection, 2336 | newSectionName=newSectionName, 2337 | extendExistingSection=extendExistingSection) 2338 | 2339 | # return manipulated segment, offset of appended data in file and 2340 | # memory address of appended data 2341 | return self.segments[segmentNumber], newDataOffset, newDataMemoryAddr 2342 | 2343 | 2344 | # this function gets the next segment of the given one and the 2345 | # free space in memory in between 2346 | # return values: (class Segment) next segment, (int) free space; 2347 | # both None if no following segment was found 2348 | def getNextSegmentAndFreeSpace(self, segmentToSearch): 2349 | 2350 | # check if the file was completely parsed before 2351 | if self.fileParsed is False: 2352 | raise ValueError("Operation not possible. " \ 2353 | + "File was not completely parsed before.") 2354 | 2355 | # find segment that comes directly after the segment to 2356 | # manipulate in the virtual memory 2357 | diff_p_vaddr = None 2358 | nextSegment = None 2359 | for segment in self.segments: 2360 | if segment != segmentToSearch: 2361 | if ((segment.elfN_Phdr.p_vaddr 2362 | - (segmentToSearch.elfN_Phdr.p_vaddr 2363 | + segmentToSearch.elfN_Phdr.p_memsz)) > 0): 2364 | if (diff_p_vaddr is None 2365 | or (segment.elfN_Phdr.p_vaddr 2366 | - (segmentToSearch.elfN_Phdr.p_vaddr 2367 | + segmentToSearch.elfN_Phdr.p_memsz)) 2368 | < diff_p_vaddr): 2369 | diff_p_vaddr = segment.elfN_Phdr.p_vaddr \ 2370 | - (segmentToSearch.elfN_Phdr.p_vaddr \ 2371 | + segmentToSearch.elfN_Phdr.p_memsz) 2372 | nextSegment = segment 2373 | 2374 | # return nextSegment and free space 2375 | return nextSegment, diff_p_vaddr 2376 | 2377 | 2378 | # this function is a wrapper function for 2379 | # getNextSegmentAndFreeSpace(segmentToSearch) 2380 | # which returns only the free space in memory after the segment 2381 | # return values: (int) free space; None if no following segment was found 2382 | def getFreeSpaceAfterSegment(self, segmentToSearch): 2383 | 2384 | # check if the file was completely parsed before 2385 | if self.fileParsed is False: 2386 | raise ValueError("Operation not possible. " \ 2387 | + "File was not completely parsed before.") 2388 | 2389 | nextSegment, diff_p_vaddr \ 2390 | = self.getNextSegmentAndFreeSpace(segmentToSearch) 2391 | return diff_p_vaddr 2392 | 2393 | 2394 | # this function removes all section header entries 2395 | # return values: None 2396 | def removeSectionHeaderTable(self): 2397 | 2398 | # check if the file was completely parsed before 2399 | if self.fileParsed is False: 2400 | raise ValueError("Operation not possible. " \ 2401 | + "File was not completely parsed before.") 2402 | 2403 | self.header.e_shoff = 0 2404 | self.header.e_shnum = 0 2405 | self.header.e_shentsize = 0 2406 | self.header.e_shstrndx = Shstrndx.SHN_UNDEF 2407 | self.sections = list() 2408 | 2409 | 2410 | # this function overwrites data on the given offset 2411 | # return values: None 2412 | def writeDataToFileOffset(self, offset, data, force=False): 2413 | 2414 | # check if the file was completely parsed before 2415 | if self.fileParsed is False: 2416 | raise ValueError("Operation not possible. " \ 2417 | + "File was not completely parsed before.") 2418 | 2419 | # get the segment to which the changed data belongs to 2420 | segmentToManipulate = None 2421 | for segment in self.segments: 2422 | segStart = segment.elfN_Phdr.p_offset 2423 | segEnd = segStart + segment.elfN_Phdr.p_filesz 2424 | if segStart <= offset and offset < segEnd: 2425 | segmentToManipulate = segment 2426 | break 2427 | 2428 | # check if segment was found 2429 | if force is False and segmentToManipulate is None: 2430 | raise ValueError(('Segment with offset 0x%x not found ' \ 2431 | + '(use "force=True" to ignore this check).') % offset) 2432 | 2433 | # (previous check ensures that now either force is True or segEnd has been set) 2434 | # check if data to manipulate fits in segment 2435 | if force is False and offset + len(data) >= segEnd: 2436 | raise ValueError(('Size of data to manipulate: %d. Not enough ' \ 2437 | + 'space in segment (Available: %d; use "force=True" to ' \ 2438 | + 'ignore this check).') \ 2439 | % (len(data), (segEnd - offset))) 2440 | 2441 | # change data 2442 | self.data[offset:offset+len(data)] = data 2443 | 2444 | 2445 | # this function converts the virtual memory address to the file offset 2446 | # return value: (int) offset in file (or None if not found) 2447 | def virtualMemoryAddrToFileOffset(self, memoryAddr): 2448 | 2449 | # check if the file was completely parsed before 2450 | if self.fileParsed is False: 2451 | raise ValueError("Operation not possible. " \ 2452 | + "File was not completely parsed before.") 2453 | 2454 | # get the segment to which the virtual memory address belongs to 2455 | foundSegment = None 2456 | for segment in self.segments: 2457 | segStart = segment.elfN_Phdr.p_vaddr 2458 | segEnd = segStart + segment.elfN_Phdr.p_memsz 2459 | if segStart <= memoryAddr and memoryAddr < segEnd: 2460 | foundSegment = segment 2461 | break 2462 | 2463 | # check if segment was found 2464 | if foundSegment is None: 2465 | return None 2466 | 2467 | relOffset = memoryAddr - foundSegment.elfN_Phdr.p_vaddr 2468 | # relOffset >= 0 due to condition in segment search loop 2469 | 2470 | # check if file is mapped 1:1 to memory 2471 | if foundSegment.elfN_Phdr.p_filesz != foundSegment.elfN_Phdr.p_memsz: 2472 | # check if the memory address relative to the virtual memory 2473 | # address of the segment lies within the file size of the segment 2474 | if relOffset >= foundSegment.elfN_Phdr.p_filesz: 2475 | raise ValueError("Can not convert virtual memory address " \ 2476 | + "to file offset.") 2477 | 2478 | return foundSegment.elfN_Phdr.p_offset + relOffset 2479 | 2480 | 2481 | # this function converts the file offset to the virtual memory address 2482 | # return value: (int) virtual memory address (or None if not found) 2483 | def fileOffsetToVirtualMemoryAddr(self, offset): 2484 | 2485 | # check if the file was completely parsed before 2486 | if self.fileParsed is False: 2487 | raise ValueError("Operation not possible. " \ 2488 | + "File was not completely parsed before.") 2489 | 2490 | # get the segment to which the file offset belongs to 2491 | foundSegment = None 2492 | for segment in self.segments: 2493 | segStart = segment.elfN_Phdr.p_offset 2494 | segEnd = segStart + segment.elfN_Phdr.p_filesz 2495 | if segStart <= offset and offset < segEnd: 2496 | foundSegment = segment 2497 | break 2498 | 2499 | # check if segment was found 2500 | if foundSegment is None: 2501 | return None 2502 | 2503 | relOffset = offset - foundSegment.elfN_Phdr.p_offset 2504 | # relOffset >= 0 due to condition in segment search loop 2505 | 2506 | # check if file is mapped 1:1 to memory 2507 | if foundSegment.elfN_Phdr.p_filesz != foundSegment.elfN_Phdr.p_memsz: 2508 | if relOffset >= foundSegment.elfN_Phdr.p_memsz: 2509 | raise ValueError("Data not mapped 1:1 from file to memory." \ 2510 | + " Can not convert virtual memory address to file offset.") 2511 | 2512 | return foundSegment.elfN_Phdr.p_vaddr + relOffset 2513 | 2514 | 2515 | # this function overwrites an entry in the got 2516 | # (global offset table) in the file 2517 | # return values: None 2518 | def modifyGotEntryAddr(self, name, memoryAddr): 2519 | 2520 | # check if the file was completely parsed before 2521 | if self.fileParsed is False: 2522 | raise ValueError("Operation not possible. " \ 2523 | + "File was not completely parsed before.") 2524 | 2525 | # search for name in jump relocation entries 2526 | entryToModify = None 2527 | for jmpEntry in self.jumpRelocationEntries: 2528 | if jmpEntry.name == name: 2529 | entryToModify = jmpEntry 2530 | break 2531 | if entryToModify is None: 2532 | raise ValueError('Jump relocation entry with the name' \ 2533 | + ' "%s" was not found.' % name) 2534 | 2535 | # calculate file offset of got 2536 | entryOffset = self.virtualMemoryAddrToFileOffset( 2537 | entryToModify.r_offset) 2538 | 2539 | # generate list with new memory address for got 2540 | if self.bits == 32: 2541 | fmt = ' change section string table index and number of sections 2630 | if sectionNo < self.header.e_shstrndx: 2631 | self.header.e_shstrndx = self.header.e_shstrndx - 1 2632 | elif sectionNo == self.header.e_shstrndx: 2633 | self.header.e_shstrndx = 0 2634 | self.header.e_shnum = self.header.e_shnum - 1 2635 | 2636 | 2637 | # this function searches for the first jump relocation entry given by name 2638 | # return values: (ElfN_Rel) jump relocation entry 2639 | def getJmpRelEntryByName(self, name): 2640 | 2641 | # check if the file was completely parsed before 2642 | if self.fileParsed is False: 2643 | raise ValueError("Operation not possible. " \ 2644 | + "File was not completely parsed before.") 2645 | 2646 | # search for the first jump relocation entry with the given name 2647 | foundEntry = None 2648 | for jmpRelEntry in self.jumpRelocationEntries: 2649 | if jmpRelEntry.symbol.symbolName == name: 2650 | foundEntry = jmpRelEntry 2651 | break 2652 | 2653 | # check if jump relocation entry was found 2654 | if foundEntry is None: 2655 | raise ValueError('Jump relocation entry with the name' \ 2656 | + ' "%s" was not found.' % name) 2657 | 2658 | return foundEntry 2659 | -------------------------------------------------------------------------------- /ZwoELF/__init__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | 3 | # written by sqall 4 | # twitter: https://twitter.com/sqall01 5 | # blog: http://blog.h4des.org 6 | # github: https://github.com/sqall01 7 | # 8 | # Licensed under the GNU Public License, version 2. 9 | 10 | import Compatibility 11 | from ElfParserLib import ElfParser, Section, Segment 12 | from Elf import ElfN_Ehdr, Shstrndx, ElfN_Shdr, SH_flags, SH_type, \ 13 | Elf32_Phdr, P_type, P_flags, D_tag, ElfN_Dyn, \ 14 | ElfN_Rel, ElfN_Rela, ElfN_Sym, R_type, \ 15 | Section, Segment, DynamicSymbol 16 | -------------------------------------------------------------------------------- /examples/IDAPython/addDynamicSymbols_ida.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | 3 | # written by sqall 4 | # twitter: https://twitter.com/sqall01 5 | # blog: http://blog.h4des.org 6 | # github: https://github.com/sqall01 7 | # 8 | # Licensed under the GNU Public License, version 2. 9 | 10 | from ZwoELF import ElfParser 11 | from idautils import * 12 | 13 | currentFile = GetInputFilePath() 14 | elfFile = ElfParser(currentFile) 15 | 16 | # rename all symbols from the jump entries in ida 17 | for jmpRelEntry in elfFile.jumpRelocationEntries: 18 | name = jmpRelEntry.symbol.symbolName 19 | 20 | print "Add references for symbol: %s (0x%x)" % (name, jmpRelEntry.r_offset) 21 | MakeRptCmt(jmpRelEntry.r_offset, "%s (restored by script)" % name) 22 | dataRefs = DataRefsTo(jmpRelEntry.r_offset) 23 | 24 | # get address of the data reference (usually there is only one reference) 25 | address = list(dataRefs)[0] 26 | 27 | # rename address 28 | MakeName(address, name + "__restored") -------------------------------------------------------------------------------- /examples/addRandomSections.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | 3 | # written by sqall 4 | # twitter: https://twitter.com/sqall01 5 | # blog: http://blog.h4des.org 6 | # 7 | # Licensed under the GNU Public License, version 2. 8 | 9 | import sys 10 | import os 11 | from ctypes import c_uint 12 | from ZwoELF import ElfParser, SH_type, SH_flags 13 | import random 14 | 15 | random.seed() 16 | 17 | try: 18 | if sys.argv[1] == '--seed': 19 | random.seed(int(sys.argv[2])) 20 | sys.argv[1:3] = [] 21 | 22 | inputFile = sys.argv[1] 23 | outputFile = sys.argv[2] 24 | except: 25 | print('usage: {} [--seed ] '.format(sys.argv[0])) 26 | print('') 27 | print(' --seed : seed rng with constant (for deterministic results)') 28 | sys.exit(1) 29 | 30 | 31 | # the added sections can be used to confuse analysis tools 32 | # for example IDA 6.1.x tries to analyze all sections and it takes a 33 | # lot of time until the file is loaded 34 | # (to circumvent this, just ignore sections and use segments) 35 | 36 | 37 | testFile = ElfParser(inputFile) 38 | 39 | 40 | tempList = list(testFile.sections) 41 | allowedSections = list() 42 | 43 | for section in tempList: 44 | 45 | # IDA 6.1.x throws error when section uses ".dynsym" area: 46 | # "Bad file structure or read error (line xxxx). Continue?" 47 | # and "Redeclared 'Dynamic symbol string table' section" 48 | # and "Relocation to non-code/data/bss section. Skip?" 49 | # and "Relocation to illegal symbol table. Skip?" 50 | 51 | # IDA 6.1.x throws error when section uses ".dynstr" area: 52 | # "Bad file structure or read error (line xxxx). Continue?" 53 | # and "Redeclared 'Dynamic symbol string table' section" 54 | # and "Relocation to non-code/data/bss section. Skip?" 55 | 56 | # IDA 6.1.x throws error when section uses ".gnu.version_r" area: 57 | # "Relocation to non-code/data/bss section. Skip?" 58 | 59 | # IDA 6.1.x throws error when section uses ".rel.dyn" area: 60 | # "Relocation to non-code/data/bss section. Skip?" 61 | 62 | # IDA 6.1.x throws error when ".bss" section is used: "Can't read input 63 | # file (file structure error?), only part of file will be loaded..." 64 | 65 | # "readelf: Error: Invalid sh_entsize" when section lies within 66 | # ".interp" section 67 | # "readelf: Error: Invalid sh_entsize" when section lies within 68 | # ".note.ABI-tag" section 69 | # "readelf: Error: Invalid sh_entsize" when section lies within 70 | # ".note.gnu.build-id" section 71 | # "readelf: Error: Invalid sh_entsize" when section lies within 72 | # ".hash" section 73 | # "readelf: Error: Invalid sh_entsize" when section lies within 74 | # ".gnu.hash" section 75 | 76 | if (section.sectionName == ".gnu.version" 77 | or section.sectionName == ".init" 78 | or section.sectionName == ".plt" 79 | or section.sectionName == ".fini" 80 | or section.sectionName == ".rodata" 81 | or section.sectionName == ".eh_frame_hdr" 82 | or section.sectionName == ".eh_frame" 83 | or section.sectionName == ".ctors" 84 | or section.sectionName == ".dtors" 85 | or section.sectionName == ".jcr" 86 | or section.sectionName == ".dynamic" 87 | or section.sectionName == ".got" 88 | or section.sectionName == ".got.plt" 89 | or section.sectionName == ".data" 90 | or section.sectionName == ".shstrtab"): 91 | 92 | # add section to list of sections in which new sections can 93 | # be set without causing any errors 94 | allowedSections.append(section) 95 | 96 | # add 128 new random sections to obfuscate original sections 97 | for count in range(128): 98 | 99 | # calculate a random position within a random chosen section 100 | inSection = random.randint(0, len(allowedSections)-1) 101 | offset = allowedSections[inSection].elfN_shdr.sh_offset 102 | addr = allowedSections[inSection].elfN_shdr.sh_addr 103 | size = allowedSections[inSection].elfN_shdr.sh_size 104 | newStart = random.randint(0, size-1) 105 | offset += newStart 106 | addr += newStart 107 | size -= newStart 108 | 109 | # pick a random section name 110 | sectionName = random.randint(0, len(allowedSections)-1) 111 | newName = allowedSections[sectionName].sectionName 112 | 113 | # pick a random section name 114 | while True: 115 | sectionName = random.randint(0, len(allowedSections)-1) 116 | newName = allowedSections[sectionName].sectionName 117 | 118 | # ignore this section names because they can generate errors 119 | # with IDA 6.1.x 120 | if (newName != ".got" 121 | and newName != ".got.plt" 122 | and newName != ".init" 123 | and newName != ".plt" 124 | and newName != ".fini"): 125 | break 126 | 127 | testFile.addNewSection(newName, SH_type.SHT_PROGBITS, 128 | (SH_flags.SHF_EXECINSTR | SH_flags.SHF_ALLOC), addr, offset, 129 | size, section.elfN_shdr.sh_link, section.elfN_shdr.sh_info, 130 | section.elfN_shdr.sh_addralign, 0) 131 | 132 | 133 | testFile.writeElf(outputFile) 134 | -------------------------------------------------------------------------------- /examples/createFalseDynstrSection.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | 3 | # written by sqall 4 | # twitter: https://twitter.com/sqall01 5 | # blog: http://blog.h4des.org 6 | # github: https://github.com/sqall01 7 | # 8 | # Licensed under the GNU Public License, version 2. 9 | 10 | from ctypes import c_uint 11 | from ZwoELF import ElfParser, D_tag, SH_type, SH_flags, P_type, P_flags 12 | import sys 13 | import random 14 | import time 15 | 16 | 17 | def replaceSymbolString(dynStrData, oldSymbol, newSymbol): 18 | 19 | print "Replacing '%s' with '%s'" % (oldSymbol, newSymbol) 20 | 21 | # check if new symbol name is longer thant old symbol name 22 | # (not possible without rewriting the complete binary) 23 | if len(oldSymbol) < len(newSymbol): 24 | raise ValueError("New symbol not longer than old symbol name.") 25 | 26 | # generate string from the bytearray to search easier for old symbol name 27 | dataString = "" 28 | for i in range(len(dynStrData)): 29 | dataString += chr(dynStrData[i]) 30 | 31 | # search old symbol with trailing and leading null termination 32 | positionOfSymbol = dataString.find("\x00" + oldSymbol + "\x00") 33 | if positionOfSymbol == -1: 34 | raise ValueError("Old symbol name was not found.") 35 | positionOfSymbol += 1 36 | 37 | # when the old symbol name is longer than the new one 38 | # fill the gab with null bytes 39 | for i in range((len(oldSymbol) - len(newSymbol))): 40 | newSymbol += "\x00" 41 | 42 | # replace old symbol name with new one 43 | for i in range(len(oldSymbol)): 44 | dynStrData[positionOfSymbol + i] = newSymbol[i] 45 | 46 | return dynStrData 47 | 48 | 49 | 50 | try: 51 | inputFile = sys.argv[1] 52 | outputFile = sys.argv[2] 53 | except: 54 | print('usage: {} '.format(sys.argv[0])) 55 | print('') 56 | sys.exit(1) 57 | 58 | # CHANGE HERE WHAT YOU WANT TO EXCHANGE 59 | # list of symbols to replace [(oldsymbol, newsymbol)] 60 | #symbolsToReplace = [("__libc_start_main", "__libc_foo"), ("malloc", "flux")] 61 | symbolsToReplace = [("printf", "fputs"), ("system", "printf"), 62 | ("strncmp", "strcmp")] 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | parsedFile = ElfParser(inputFile) 75 | 76 | # we want to add the forged dynamic string table behind the executable 77 | # loaded segment => get this segment 78 | segmentToExtend = None 79 | for segment in parsedFile.segments: 80 | if (segment.elfN_Phdr.p_type == P_type.PT_LOAD and 81 | (segment.elfN_Phdr.p_flags & P_flags.PF_X) == 0x1): 82 | segmentToExtend = segment 83 | if segmentToExtend is None: 84 | print "No loadable segment was found." 85 | sys.exit(0) 86 | 87 | # get dynamic string section 88 | dynStrSection = None 89 | for section in parsedFile.sections: 90 | if section.sectionName == ".dynstr": 91 | dynStrSection = section 92 | break 93 | if dynStrSection is None: 94 | print "No .dynstr section was found." 95 | sys.exit(0) 96 | 97 | # get data of original dynamic string table 98 | dynStrOffsetStart = dynStrSection.elfN_shdr.sh_offset 99 | dynStrOffsetEnd = dynStrSection.elfN_shdr.sh_offset \ 100 | + dynStrSection.elfN_shdr.sh_size 101 | dynStrSectionData = parsedFile.data[dynStrOffsetStart:dynStrOffsetEnd] 102 | 103 | # calculate offset of new dynamic string table 104 | newDynStrOffset = segmentToExtend.elfN_Phdr.p_offset \ 105 | + segmentToExtend.elfN_Phdr.p_filesz 106 | 107 | # generate random size of data that is added before new dynamic string table 108 | random.seed(time.time()) # not important to be unpredictable 109 | randomPrefixData = random.randint(0, 1000) 110 | 111 | # calculate size of data block that has to be inserted so the new dynamic 112 | # string table fits after the executable load segment 113 | offsetAddition = segmentToExtend.elfN_Phdr.p_align 114 | while (len(dynStrSectionData) + randomPrefixData) > offsetAddition: 115 | offsetAddition += segmentToExtend.elfN_Phdr.p_align 116 | print "Needed data to inject: %d bytes" % offsetAddition 117 | 118 | nextSegment, freeSpace = parsedFile.getNextSegmentAndFreeSpace( 119 | segmentToExtend) 120 | 121 | if nextSegment is None: 122 | raise NotImplementedError("Appending data to the end of the file " 123 | + "not implemented yet.") 124 | 125 | # adjust offsets of all following section 126 | for section in parsedFile.sections: 127 | if (section.elfN_shdr.sh_offset 128 | >= nextSegment.elfN_Phdr.p_offset): 129 | section.elfN_shdr.sh_offset += offsetAddition 130 | 131 | # adjust offsets of following segments 132 | # (ignore the directly followed segment) 133 | for segment in parsedFile.segments: 134 | if segment != segmentToExtend and segment != nextSegment: 135 | # use offset of the directly followed segment in order to 136 | # ignore segments that lies within the 137 | # segment to manipulate 138 | if (segment.elfN_Phdr.p_offset 139 | > nextSegment.elfN_Phdr.p_offset): 140 | segment.elfN_Phdr.p_offset += offsetAddition 141 | 142 | # adjust offset of the directly following segment of the 143 | # segment to manipulate 144 | nextSegment.elfN_Phdr.p_offset += offsetAddition 145 | 146 | # if program header table lies behind the segment to manipulate 147 | # => move it 148 | if (parsedFile.header.e_phoff > (segmentToExtend.elfN_Phdr.p_offset 149 | + segmentToExtend.elfN_Phdr.p_filesz)): 150 | parsedFile.header.e_phoff += offsetAddition 151 | 152 | # if section header table lies behind the segment to manipulate 153 | # => move it 154 | if (parsedFile.header.e_shoff > (segmentToExtend.elfN_Phdr.p_offset 155 | + segmentToExtend.elfN_Phdr.p_filesz)): 156 | parsedFile.header.e_shoff += offsetAddition 157 | 158 | # replace all symbols 159 | for symbolTuple in symbolsToReplace: 160 | dynStrSectionData = replaceSymbolString(dynStrSectionData, 161 | symbolTuple[0], symbolTuple[1]) 162 | 163 | # first insert the random prefix data 164 | for i in range(randomPrefixData): 165 | parsedFile.data.insert((newDynStrOffset + i), chr(random.randint(0, 255))) 166 | 167 | # second insert dynamic string table data 168 | for i in range(len(dynStrSectionData)): 169 | parsedFile.data.insert((newDynStrOffset + randomPrefixData + i), 170 | dynStrSectionData[i]) 171 | 172 | # third fill gab to next segment with random data 173 | for i in range(offsetAddition - len(dynStrSectionData) - randomPrefixData): 174 | parsedFile.data.insert((newDynStrOffset + randomPrefixData 175 | + len(dynStrSectionData) + i), chr(random.randint(0, 255))) 176 | 177 | print "Offset of new dynamic string table: 0x%x" \ 178 | % (newDynStrOffset + randomPrefixData) 179 | 180 | # set new dynamic string section offset 181 | dynStrSection.elfN_shdr.sh_offset = newDynStrOffset + randomPrefixData 182 | 183 | # write file 184 | parsedFile.writeElf(outputFile) -------------------------------------------------------------------------------- /examples/indirectJmpWithMemoryData.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | 3 | # written by sqall 4 | # twitter: https://twitter.com/sqall01 5 | # blog: http://blog.h4des.org 6 | # github: https://github.com/sqall01 7 | # 8 | # Licensed under the GNU Public License, version 2. 9 | 10 | import sys 11 | from ctypes import c_uint 12 | from ZwoELF import ElfParser 13 | 14 | 15 | try: 16 | inputFile = sys.argv[1] 17 | outputFile = sys.argv[2] 18 | except: 19 | print('usage: {} '.format(sys.argv[0])) 20 | sys.exit(1) 21 | 22 | 23 | print "Manipulating: %s" % inputFile 24 | test = ElfParser(inputFile) 25 | 26 | freeSpace = test.getFreeSpaceAfterSegment(test.segments[2]) 27 | print "Free space: %d Bytes " % freeSpace 28 | 29 | # get original entry point 30 | originalEntry = test.header.e_entry 31 | 32 | 33 | dummyData = ["\x41"] * (freeSpace-1) 34 | 35 | manipulatedSegment, newDataOffset, newDataMemoryAddr \ 36 | = test.appendDataToExecutableSegment(dummyData) 37 | 38 | print "Offset of new data: 0x%x" % newDataOffset 39 | print "Virtual memory addr of new data: 0x%x" % newDataMemoryAddr 40 | 41 | ''' 42 | first 28 bytes of "ls" entrypoint 43 | 44 | 08049bb0 <.text>: 45 | 8049bb0: 31 ed xor ebp,ebp 46 | 8049bb2: 5e pop esi 47 | 8049bb3: 89 e1 mov ecx,esp 48 | 8049bb5: 83 e4 f0 and esp,0xfffffff0 49 | 8049bb8: 50 push eax 50 | 8049bb9: 54 push esp 51 | 8049bba: 52 push edx 52 | 8049bbb: 68 50 ac 05 08 push 0x805ac50 53 | 8049bc0: 68 60 ac 05 08 push 0x805ac60 54 | 8049bc5: 51 push ecx 55 | 8049bc6: 56 push esi 56 | 8049bc7: 68 c0 fb 04 08 push 0x804fbc0 57 | ''' 58 | 59 | 60 | copiedBytesFromEntry = 8 61 | 62 | entryPointOffset = test.virtualMemoryAddrToFileOffset(originalEntry) 63 | entryPointOffsetEnd = entryPointOffset + copiedBytesFromEntry 64 | entryPointData = test.data[entryPointOffset:entryPointOffsetEnd] 65 | 66 | 67 | testData = list() 68 | 69 | # store address of newDataMemoryAddr + 4 at newDataMemoryAddr 70 | # (for instruction "mov ecx, [newDataMemoryAddr]"") 71 | testData.append(chr(((newDataMemoryAddr+4) & 0xff))) 72 | testData.append((chr(((newDataMemoryAddr+4) >> 8) & 0xff))) 73 | testData.append((chr(((newDataMemoryAddr+4) >> 16) & 0xff))) 74 | testData.append((chr(((newDataMemoryAddr+4) >> 24) & 0xff))) 75 | 76 | # copy original entrypoint data (these instructions are executed 77 | # first when control flow is altered) 78 | testData += entryPointData 79 | 80 | # calculate relative jump from current position to 81 | # entrypoint + copiedBytesFromEntry 82 | # formula: 0 - (sourceAddress - targetAddress) - 5 83 | jumpTarget = c_uint(0 - ((newDataMemoryAddr + len(testData)) 84 | - ((originalEntry + copiedBytesFromEntry))) - 5).value 85 | testData.append("\xE9") # JMP rel32 86 | testData.append(chr((jumpTarget & 0xff))) 87 | testData.append((chr((jumpTarget >> 8) & 0xff))) 88 | testData.append((chr((jumpTarget >> 16) & 0xff))) 89 | testData.append((chr((jumpTarget >> 24) & 0xff))) 90 | 91 | # overwrite dummy data 92 | test.writeDataToFileOffset(newDataOffset, testData) 93 | 94 | 95 | hookData = list() 96 | 97 | # mov ecx, [newDataMemoryAddr] 98 | hookData.append("\x8B") 99 | hookData.append("\x0D") 100 | hookData.append(chr((newDataMemoryAddr & 0xff))) 101 | hookData.append((chr((newDataMemoryAddr >> 8) & 0xff))) 102 | hookData.append((chr((newDataMemoryAddr >> 16) & 0xff))) 103 | hookData.append((chr((newDataMemoryAddr >> 24) & 0xff))) 104 | 105 | # jmp ecx 106 | hookData.append("\xFF") 107 | hookData.append("\xE1") 108 | 109 | # fill rest of missing data with nops 110 | hookData += ["\x90"] * (copiedBytesFromEntry - len(hookData)) 111 | 112 | 113 | test.writeDataToFileOffset(entryPointOffset, hookData) 114 | 115 | #test.removeSectionHeaderTable() 116 | 117 | test.writeElf(outputFile) 118 | -------------------------------------------------------------------------------- /examples/modifySymbolValue.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | 3 | # written by sqall 4 | # twitter: https://twitter.com/sqall01 5 | # blog: http://blog.h4des.org 6 | # github: https://github.com/sqall01 7 | # 8 | # Licensed under the GNU Public License, version 2. 9 | 10 | import sys 11 | from ctypes import c_uint 12 | from ZwoELF import ElfParser 13 | 14 | try: 15 | inputFile = sys.argv[1] 16 | outputFile = sys.argv[2] 17 | except: 18 | print('usage: {} '.format(sys.argv[0])) 19 | sys.exit(1) 20 | 21 | 22 | elfFile = ElfParser(inputFile) 23 | jmpRelEntry = elfFile.getJmpRelEntryByName("strlen") 24 | jmpRelEntry.symbol.ElfN_Sym.st_value = 0x41414141 25 | elfFile.writeElf(outputFile) 26 | -------------------------------------------------------------------------------- /examples/newEntryPoint.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | 3 | # written by sqall 4 | # twitter: https://twitter.com/sqall01 5 | # blog: http://blog.h4des.org 6 | # github: https://github.com/sqall01 7 | # 8 | # Licensed under the GNU Public License, version 2. 9 | 10 | import sys 11 | from ctypes import c_uint 12 | from ZwoELF import ElfParser 13 | 14 | try: 15 | inputFile = sys.argv[1] 16 | outputFile = sys.argv[2] 17 | except: 18 | print('usage: {} '.format(sys.argv[0])) 19 | sys.exit(1) 20 | 21 | 22 | print "Manipulating: %s" % inputFile 23 | test = ElfParser(inputFile) 24 | 25 | freeSpace = test.getFreeSpaceAfterSegment(test.segments[2]) 26 | print "Free space: %d Bytes " % freeSpace 27 | 28 | # get original entry point 29 | originalEntry = test.header.e_entry 30 | 31 | 32 | dummyData = list() 33 | for i in range(freeSpace-1): 34 | #dummyData.append("\x00") 35 | dummyData.append("\x41") 36 | 37 | #manipulatedSegment, newDataOffset, newDataMemoryAddr 38 | # = test.appendDataToExecutableSegment(dummyData, 39 | # addNewSection=True, newSectionName=".blahblub") 40 | # manipulatedSegment, newDataOffset, newDataMemoryAddr 41 | # = test.appendDataToExecutableSegment(dummyData, extendExistingSection=True) 42 | manipulatedSegment, newDataOffset, newDataMemoryAddr \ 43 | = test.appendDataToExecutableSegment(dummyData) 44 | 45 | print "Offset of new data: 0x%x" % newDataOffset 46 | print "Virtual memory addr of new data: 0x%x" % newDataMemoryAddr 47 | 48 | # jump from newDataMemoryAddr to originalEntry 49 | # 0 - (newDataMemoryAddr - originalEntry) - 5 50 | jumpTarget = c_uint(0 - (newDataMemoryAddr - originalEntry) - 5).value 51 | 52 | # jump from new code to old entry point 53 | testData = list() 54 | testData.append("\xE9") # JMP rel32 55 | testData.append(chr((jumpTarget & 0xff))) 56 | testData.append((chr((jumpTarget >> 8) & 0xff))) 57 | testData.append((chr((jumpTarget >> 16) & 0xff))) 58 | testData.append((chr((jumpTarget >> 24) & 0xff))) 59 | 60 | # overwrite dummy data 61 | test.writeDataToFileOffset(newDataOffset, testData) 62 | 63 | # change entry point to new data 64 | test.header.e_entry = newDataMemoryAddr 65 | 66 | test.writeElf(outputFile) 67 | 68 | print "\n\n-----------\n\n" 69 | -------------------------------------------------------------------------------- /examples/obfuscatePltSection.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | 3 | # written by sqall 4 | # twitter: https://twitter.com/sqall01 5 | # blog: http://blog.h4des.org 6 | # github: https://github.com/sqall01 7 | # 8 | # Licensed under the GNU Public License, version 2. 9 | 10 | import sys 11 | import os 12 | from ctypes import c_uint 13 | from ZwoELF import ElfParser, SH_type, SH_flags 14 | 15 | try: 16 | inputFile = sys.argv[1] 17 | outputFile = sys.argv[2] 18 | except: 19 | print('usage: {} '.format(sys.argv[0])) 20 | sys.exit(1) 21 | 22 | 23 | # remove original ".got.plt" and ".plt" section and move them 24 | # to the ".text" section 25 | # analysis tools like IDA 6.1.x and gdb try to read information 26 | # from this sections 27 | # and show irritating informations, for example gdb shows plt 28 | # information when analyzing code in the .text section 29 | # or calls to external functions are not resolved (even when IDA 6.1.x 30 | # uses segments instead of sections the 31 | # external functions are not resolved) 32 | 33 | 34 | testFile = ElfParser(inputFile) 35 | 36 | # remove ".got.plt" and ".plt" section 37 | testFile.deleteSectionByName(".got.plt") 38 | testFile.deleteSectionByName(".plt") 39 | 40 | # copy section list to iterate on copied sections 41 | tempList = list(testFile.sections) 42 | 43 | # iterate over sections 44 | for section in tempList: 45 | 46 | # when ".text" section was found, create two new sections 47 | # (".got.plt" and ".plt") with the same boundaries 48 | # this means that ".text", ".got.plt" and ".plt" overlap which 49 | # confuses analysis tools like IDA 6.1.x and gdb 50 | if (section.sectionName == ".text"): 51 | testFile.addNewSection(".got.plt", SH_type.SHT_PROGBITS, 52 | (SH_flags.SHF_EXECINSTR | SH_flags.SHF_ALLOC), 53 | section.elfN_shdr.sh_addr, section.elfN_shdr.sh_offset, 54 | section.elfN_shdr.sh_size, section.elfN_shdr.sh_link, 55 | section.elfN_shdr.sh_info, section.elfN_shdr.sh_addralign, 0) 56 | testFile.addNewSection(".plt", SH_type.SHT_PROGBITS, 57 | (SH_flags.SHF_EXECINSTR | SH_flags.SHF_ALLOC), 58 | section.elfN_shdr.sh_addr, section.elfN_shdr.sh_offset, 59 | section.elfN_shdr.sh_size, section.elfN_shdr.sh_link, 60 | section.elfN_shdr.sh_info, section.elfN_shdr.sh_addralign, 0) 61 | break 62 | 63 | 64 | testFile.writeElf(outputFile) 65 | print "written to %s" % (outputFile) 66 | -------------------------------------------------------------------------------- /examples/readElf.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | 3 | # written by sqall 4 | # twitter: https://twitter.com/sqall01 5 | # blog: http://blog.h4des.org 6 | # github: https://github.com/sqall01 7 | # 8 | # Licensed under the GNU Public License, version 2. 9 | 10 | from ctypes import c_uint 11 | from ZwoELF import ElfParser 12 | import sys 13 | 14 | 15 | try: 16 | inputFile = sys.argv[1] 17 | except: 18 | print('usage: {} '.format(sys.argv[0])) 19 | sys.exit(1) 20 | 21 | 22 | test = ElfParser(inputFile) 23 | test.printElf() 24 | --------------------------------------------------------------------------------