├── .gitignore ├── AUTHORS.TXT ├── Keypatch-slides.pdf ├── screenshots ├── keypatch_search.png ├── keypatch_update.png ├── keypatch_comment.png ├── keypatch_fillrange.png ├── keypatch_menupopup.png ├── keypatch_patcher.png └── keypatch_search_result.png ├── VERSION_STABLE ├── TODO ├── CREDITS.TXT ├── ChangeLog ├── TUTORIAL.md ├── README.md ├── COPYING └── keypatch.py /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | *.pyc 3 | .idea 4 | *.swp 5 | -------------------------------------------------------------------------------- /AUTHORS.TXT: -------------------------------------------------------------------------------- 1 | Nguyen Anh Quynh 2 | Thanh Nguyen 3 | -------------------------------------------------------------------------------- /Keypatch-slides.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/keystone-engine/keypatch/HEAD/Keypatch-slides.pdf -------------------------------------------------------------------------------- /screenshots/keypatch_search.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/keystone-engine/keypatch/HEAD/screenshots/keypatch_search.png -------------------------------------------------------------------------------- /screenshots/keypatch_update.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/keystone-engine/keypatch/HEAD/screenshots/keypatch_update.png -------------------------------------------------------------------------------- /screenshots/keypatch_comment.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/keystone-engine/keypatch/HEAD/screenshots/keypatch_comment.png -------------------------------------------------------------------------------- /screenshots/keypatch_fillrange.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/keystone-engine/keypatch/HEAD/screenshots/keypatch_fillrange.png -------------------------------------------------------------------------------- /screenshots/keypatch_menupopup.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/keystone-engine/keypatch/HEAD/screenshots/keypatch_menupopup.png -------------------------------------------------------------------------------- /screenshots/keypatch_patcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/keystone-engine/keypatch/HEAD/screenshots/keypatch_patcher.png -------------------------------------------------------------------------------- /screenshots/keypatch_search_result.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/keystone-engine/keypatch/HEAD/screenshots/keypatch_search_result.png -------------------------------------------------------------------------------- /VERSION_STABLE: -------------------------------------------------------------------------------- 1 | # latest stable version - be updated with every release 2 | # this will be grepped for by "Check for update" menu 3 | VERSION_STABLE = "2.1" 4 | 5 | -------------------------------------------------------------------------------- /TODO: -------------------------------------------------------------------------------- 1 | 1) IDA syntax is different from Keystone syntax, so before compiling the input 2 | code, Keypatch always try to convert IDA syntax to Keystone syntax. This is not 3 | complete yet, so sometimes you see code that Keypatch cannot compile. 4 | See function fix_ida_syntax() for further details. 5 | 6 | 2) Some architectures such as X86, ARM & ARM64 are better tested than the rest. 7 | Need more tests for Hexagon, Mips, PPC, Sparc & SystemZ. 8 | -------------------------------------------------------------------------------- /CREDITS.TXT: -------------------------------------------------------------------------------- 1 | This file credits the contributors of Keypatch 2 | Find more information on our project at http://keystone-engine.org/keypatch 3 | --------------------------------------------------------------------------- 4 | 5 | Neoz: inspired some ideas behind Keypatch with https://github.com/neoz/asm_ops. 6 | Joxean Koret: great tips on IDA programming. 7 | Kaijern (Xwings) & Nguyen Van Luc: valuable feedbacks during beta test. 8 | Stefan Esser: search functionality. 9 | Tyler Halfpop: IDA v7 support. 10 | Bo Wang: IDA v7.4 and python3 support 11 | -------------------------------------------------------------------------------- /ChangeLog: -------------------------------------------------------------------------------- 1 | This file details the changelog of Keypatch (http://keystone-engine.org/keypatch). 2 | 3 | 4 | --------------------------------- 5 | Version 2.1: January 17th, 2017 6 | 7 | - Better documentation for Linux & Windows installs. 8 | - Added a new function to search for assembly instructions, so it is easy to grep 9 | for ROP gadgets in the binary. This will be helpful for exploitation writers. 10 | - Removed the "Assembler" function, which is redundant since now you can also do 11 | that with the "Search" function above. 12 | 13 | 14 | --------------------------------- 15 | Version 2.0.1: September 15th, 2016 16 | 17 | - Fix an off-by-one bug in Fill-Range function. 18 | 19 | 20 | --------------------------------- 21 | Version 2.0: September 14th, 2016 22 | 23 | 24 | [ Core ] 25 | 26 | - Save (at termination) & load configuration (at boot time) from local director. 27 | - Add accelerator keys for editable controls in Patcher & Assembler forms. 28 | - Optionally append original instructions - before patching - in IDA comment. 29 | - Add "Undo" feature, so it is possible to revert a modification in the past. 30 | - Add "Check for update" & "About" menus. 31 | - Better support for Python 2.6 & older IDA versions (confirmed with v6.4 & v6.6). 32 | - Support popup menu (right-mouse click). 33 | - Add "Fill Range" feature. 34 | 35 | [ Arm ] 36 | 37 | - Handle Arm mode better. Cortex-M3 binaries are now supported. 38 | - Fix a bug in Thumb mode. 39 | 40 | [ Docs ] 41 | 42 | - Better guideline for installing Keypatch on Linux 64-bit. 43 | - Add TUTORIAL.md, a quick tutorial for new users of Keypatch. 44 | 45 | 46 | ---------------------------------- 47 | [Version 1.0]: August 3rd, 2016 48 | 49 | - Initial public release. 50 | -------------------------------------------------------------------------------- /TUTORIAL.md: -------------------------------------------------------------------------------- 1 | Keypatch Tutorial 2 | ================= 3 | 4 | This is a quick tutorial for new users of [Keypatch](http://keystone-engine.org/keypatch). 5 | You are supposed to already have Keypatch installed for your IDA Pro. 6 | 7 | See [README.md](README.md) for a complete guideline of Keypatch. 8 | 9 | 10 | ### 1. Use **Patcher** tool 11 | 12 | - Load a binary into IDA. 13 | - At any place in IDA window, press the hotkey `Ctrl-Alt-K` to open the 14 | Patcher dialog. 15 | - Enter a new assembly instruction to the Assembly control. 16 | - Click button `Patch` to change the original instruction to the new instruction. 17 | 18 |

19 | 20 |

21 | 22 | 23 | ### 2. Use **Fill Range** tool 24 | 25 | - Load a binary into IDA. 26 | - Select a range of code in IDA window, then press the hotkey `Ctrl-Alt-K` to open 27 | the "Fill Range" dialog. Note that the same hotkey would open the Patcher window 28 | if you do not select a range of code, as in tutorial 1 above. 29 | - Enter a new assembly instruction to the Assembly control. Alternatively, you can 30 | also enter a string in hexcode format, such as "0x90", "90, 91", "AAh", etc. 31 | - Click button `Patch` to fill the selected range with the input above. 32 | 33 |

34 | 35 |

36 | 37 | 38 | ### 3. Revert (undo) the last patching 39 | 40 | - After any modification (like in tutorial 2 or 3 above), do right-click in 41 | IDA window, then choose from the popup menu `Keypatch | Undo last patching` 42 | to revert (undo) the last action. 43 | 44 |

45 | 46 |

47 | 48 | 49 | ### 4. Save the modification 50 | 51 | - After all the patching done in tutorial 1, 2, 3 above, save all the modifications 52 | by choosing menu `Edit | Patch program | Apply patches to input file`. Note that 53 | this really changes the original binary, so be sure this is what you desire. 54 | 55 | 56 | ### 5. Use **Search** tool 57 | 58 | - Open the Search dialog by either right-click in IDA window, then choose from 59 | the popup menu `Keypatch | Search`. Or choose from the main menu `Edit | Keypatch | Search`. 60 | - Enter assembly instructions in the Assembly control, then click "Search" to search 61 | for these instructions in code section. Keypatch would show the result in a new window. 62 | in which you can double-click each address to jump to the related code. 63 | 64 | Note that it is possible to enter more than one assembly instruction, and separating 65 | them by semi-colons signs (ie `;`). 66 | 67 |

68 | 69 |

70 | 71 | 72 | ### 6. Check for new update of Keypatch 73 | 74 | - Right-click in IDA window, then choose from the popup menu `Keypatch | Check for update`. 75 | A new window will show up, in which the current version & latest stable version 76 | (released by Keypatch developers) are reported. 77 | - Click button "Open Keypatch Website" if you want to visit the Keypatch 78 | homepage to download the latest version. 79 | 80 |

81 | 82 |

83 | 84 | 85 | ### 7. Verify the current versions of Keypatch & Keystone engine 86 | 87 | - Right-click in IDA window, then choose from the popup menu `Keypatch | About`. 88 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Keypatch 2 | ======== 3 | 4 | Keypatch is [the award winning plugin](https://www.hex-rays.com/contests/2016/index.shtml) of [IDA Pro](https://www.hex-rays.com/products/ida/) for [Keystone Assembler Engine](http://keystone-engine.org). 5 | 6 | Keypatch consists of 3 tools inside. 7 | 8 | - **Patcher** & **Fill Range**: these allow you to type in assembly to directly patch your binary. 9 | - **Search**: this interactive tool let you search for assembly instructions in binary. 10 | 11 | See [this quick tutorial](TUTORIAL.md) for how to use Keypatch, and [this slides](Keypatch-slides.pdf) for how it is implemented. 12 | 13 | Keypatch is confirmed to work on IDA Pro version 6.4, 6.5, 6.6, 6.8, 6.9, 6.95, 7.0, 7.5 but should work flawlessly on older versions. 14 | If you find any issues, please [report](http://keystone-engine.org/contact). 15 | 16 | -------------------- 17 | 18 | ### 1. Why Keypatch? 19 | 20 | Sometimes we want to patch the binary while analyzing it in IDA, but unfortunately the built-in asssembler of IDA Pro is not adequate. 21 | 22 | - This tool is not friendly and without many options that would make the life of reverser easier. 23 | - Only X86 assembler is available. Support for all other architectures is totally missing. 24 | - The X86 assembler is not in a good shape, either: it cannot understand many modern Intel instructions. 25 | 26 | Keypatch was developed to solve this problem. Thanks to the power of [Keystone](http://keystone-engine.org), our plugin offers some nice features. 27 | 28 | - Cross-architecture: support Arm, Arm64 (AArch64/Armv8), Hexagon, Mips, PowerPC, Sparc, SystemZ & X86 (include 16/32/64bit). 29 | - Cross-platform: work everywhere that IDA works, which is on Windows, MacOS, Linux. 30 | - Based on Python, so it is easy to install as no compilation is needed. 31 | - User-friendly: automatically add comments to patched code, and allow reverting (undo) modification. 32 | - Open source under GPL v2. 33 | 34 | Keypatch can be the missing piece in your toolset of reverse engineering. 35 | 36 | -------------- 37 | 38 | ### 2. Install 39 | 40 | - Install Keystone core & Python binding for Python 2.7 from [keystone-engine.org/download](http://keystone-engine.org/download). Or follow the steps in the [appendix section](#appendix-install-keystone-for-ida-pro). 41 | - Install Six module from pip because it is used by the keypatch.py: `pip install six`. 42 | - Copy file `keypatch.py` to IDA Plugin folder, then restart IDA Pro to use Keypatch. 43 | - On Windows, the folder is at `C:\Program Files (x86)\IDA 6.9\plugins` 44 | - On MacOS, the folder is at `/Applications/IDA\ Pro\ 6.9/idaq.app/Contents/MacOS/plugins` 45 | - On Linux, the folder may be at `/opt/IDA/plugins/` 46 | 47 | `NOTE` 48 | - On Windows, if you get an error message from IDA about "fail to load the dynamic library", then your machine may miss the VC++ runtime library. Fix that by downloading & installing it from https://www.microsoft.com/en-gb/download/details.aspx?id=40784 49 | - On other \*nix platforms, the above error message means you do not have 32-bit Keystone installed yet. See [appendix section](#appendix-install-keystone-for-ida-pro) below for more instructions to fix this. 50 | 51 | 52 | ------------ 53 | 54 | ### 3. Usage 55 | 56 | - For a quick tutorial, see [TUTORIAL.md](TUTORIAL.md). For a complete description of all of the features of Keypatch, keep reading. 57 | 58 | - To patch your binary, press hotkey `CTRL+ALT+K` inside IDA to open **Keypatch Patcher** dialog. 59 | - The original assembly, encode & instruction size will be displayed in 3 controls at the top part of the form. 60 | - Choose the syntax, type new assembly instruction in the `Assembly` box (you can use IDA symbols). 61 | - Keypatch would *automatically* update the encoding in the `Encode` box while you are typing, without waiting for `ENTER` keystroke. 62 | - Note that you can type IDA symbols, and the raw assembly will be displayed in the `Fixup` control. 63 | - Press `ENTER` or click `Patch` to overwrite the current instruction with the new code, then *automatically* advance to the the next instruction. 64 | - Note that when size of the new code is different from the original code, Keypatch can pad until the next instruction boundary with NOPs opcode, so the code flow is intact. Uncheck the choice `NOPs padding until next instruction boundary` if this is undesired. 65 | - By default, Keypatch appends the modified instruction with the information of the original code (before being patched). Uncheck the choice `Save original instructions in IDA comment` to disable this feature. 66 | - By default, the modification you made is only recorded in the IDA database. To apply these changes to the original binary (thus overwrite it), choose menu `Edit | Patch program | Apply patches to input file`. 67 |

68 | 69 |

70 | 71 | - To fill a range of code with an instruction, select the range, then either press hotkey `CTRL+ALT+K`, or choose menu `Edit | Keypatch | Fill Range`. 72 | - In the `Assembly` box, you can either enter assembly code, or raw hexcode. Some examples of acceptable raw hexcode are `90`, `aa bb`, `0xAA, 0xBB`. 73 |

74 | 75 |

76 | 77 | - To revert (undo) the last patching, choose menu `Edit | Keypatch | Undo last patching`. 78 | 79 | - To search for assembly instructions (without overwritting binary), open **Keypatch Search** from menu `Edit | Keypatch | Search`. 80 | - Choose the architecture, address, endian mode & syntax, then type assembly instructions in the `Assembly` box. 81 | - Keypatch would *automatically* update the encoding in the `Encode` box while you are typing, without waiting for `ENTER` keystroke. 82 | - When you click `Search` button, Keypatch would look for all the occurences of the instructions, and show the result in a new form. 83 | 84 |

85 | 86 |

87 | 88 | - To check for new version of Keypatch, choose menu `Edit | Keypatch | Check for update`. 89 | 90 | - At any time, you can also access to all the above Keypatch functionalities just by right-click in IDA screen, and choose from the popup menu. 91 | 92 |

93 | 94 |

95 | 96 | -------------- 97 | 98 | ### 4. Contact 99 | 100 | Email keystone.engine@gmail.com for any questions. 101 | 102 | For future update of Keypatch, follow our Twitter [@keystone_engine](https://twitter.com/keystone_engine) for announcement. 103 | 104 | ---- 105 | 106 | ### Appendix. Install Keystone for IDA Pro 107 | 108 | We all know that before IDA 7.0, IDA Pro's Python is 32-bit itself, so it can only loads 32-bit libraries. For this reason, we have to build & install Keystone 32-bit. However, since IDA 7.0 supports both 32-bit & 64-bit, which means we also need to install a correct version of Keystone. Simply install from Pypi, with `pip` (32-bit), like followings: 109 | 110 | ```shell 111 | pip install keystone-engine 112 | ``` 113 | 114 | Done? Now go back to [section 2](#2-install) & install Keypatch for IDA Pro. Enjoy! 115 | -------------------------------------------------------------------------------- /COPYING: -------------------------------------------------------------------------------- 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 | 294 | Copyright (C) 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 | , 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. 340 | -------------------------------------------------------------------------------- /keypatch.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | # Keypatch IDA Plugin, powered by Keystone Engine (http://www.keystone-engine.org). 4 | # By Nguyen Anh Quynh & Thanh Nguyen, 2018. 5 | 6 | # Keypatch is released under the GPL v2. See COPYING for more information. 7 | # Find docs & latest version at http://keystone-engine.org/keypatch 8 | 9 | # This IDA plugin includes 3 tools inside: Patcher, Fill Range & Search. 10 | # Access to these tools via menu "Edit | Keypatch", or via right-click popup menu "Keypatch". 11 | 12 | # Hotkey Ctrl-Alt-K opens either Patcher or "Fill Range" window, depending on context. 13 | # - If there is no code selection, hotkey opens Patcher dialog 14 | # - If a range of code is selected, hotkey opens "Fill Range" dialog 15 | 16 | # To revert (undo) the last patching, choose menu "Edit | Keypatch | Undo last patching". 17 | # To check for update version, choose menu "Edit | Keypatch | Check for update". 18 | 19 | ''' 20 | Do not use shortcut(Ctrk + Alt + K) for debugging, otherwise debugging features will not be available. 21 | ''' 22 | ######################################################################################################### 23 | is_debug = False 24 | if is_debug: 25 | ''' 26 | Install pydevd: 27 | 28 | 1. sudo pip install pydevd 29 | 30 | or 31 | 32 | 2. Install pycharm-debug.egg, Ensure to use pycharm pro 33 | https://www.jetbrains.com/help/pycharm/remote-debugging-with-product.html 34 | 35 | # import site 36 | # site.addsitedir("/usr/local/lib/python2.7/site-packages") 37 | ''' 38 | try: 39 | import pydevd 40 | 41 | pydevd.settrace(host='localhost', 42 | port=51234, 43 | stdoutToServer=True, 44 | stderrToServer=True 45 | ) 46 | except Exception as e: 47 | print(e) 48 | ######################################################################################################### 49 | 50 | import os 51 | import re 52 | import json 53 | from keystone import * 54 | import idc 55 | import idaapi 56 | import ida_ida 57 | import six 58 | 59 | ################################ IDA 6/7 Compatibility import ########################################### 60 | if idaapi.IDA_SDK_VERSION >= 700: 61 | import ida_search 62 | from idc import ( 63 | get_operand_type, 64 | print_operand, 65 | get_item_end, 66 | get_item_head, 67 | get_item_size, 68 | get_sreg, 69 | has_value, 70 | get_full_flags, 71 | get_wide_byte, 72 | get_func_attr, 73 | set_func_end, 74 | warning, 75 | get_screen_ea 76 | ) 77 | from idaapi import ( 78 | get_bytes, 79 | plan_and_wait, 80 | is_mapped, 81 | Choose, 82 | jumpto 83 | ) 84 | else: 85 | from idc import ( 86 | GetOpType as get_operand_type, 87 | GetOpnd as print_operand, 88 | ItemEnd as get_item_end, 89 | ItemHead as get_item_head, 90 | ItemSize as get_item_size, 91 | GetReg as get_sreg, 92 | hasValue as has_value, 93 | GetFlags as get_full_flags, 94 | Byte as get_wide_byte, 95 | GetFunctionAttr as get_func_attr, 96 | Jump as jumpto, 97 | Warning as warning, 98 | ScreenEA as get_screen_ea 99 | ) 100 | from idaapi import ( 101 | get_many_bytes as get_bytes, 102 | analyze_area as plan_and_wait, 103 | isEnabled as is_mapped, 104 | func_setend as set_func_end, 105 | Choose2 as Choose 106 | ) 107 | ######################################################################################################### 108 | 109 | # bleeding-edge version 110 | # on a new release, this should be sync with VERSION_STABLE file 111 | VERSION = "2.2" 112 | 113 | 114 | MAX_INSTRUCTION_STRLEN = 256 115 | MAX_ENCODING_LEN = 40 116 | MAX_ADDRESS_LEN = 40 117 | ENCODING_ERR_OUTPUT = "..." 118 | KP_GITHUB_VERSION = "https://raw.githubusercontent.com/keystone-engine/keypatch/master/VERSION_STABLE" 119 | KP_HOMEPAGE = "http://keystone-engine.org/keypatch" 120 | 121 | X86_NOP = "\x90" 122 | 123 | # Configuration file 124 | KP_CFGFILE = os.path.join(idaapi.get_user_idadir(), "keypatch.cfg") 125 | 126 | # save all the info on patching 127 | patch_info = [] 128 | 129 | ################################ Python2/3 Compatibility function ####################################### 130 | # Convert unicode/string/bytes to string 131 | def to_string(s): 132 | # python3 bytes 133 | if six.PY3 and isinstance(s, bytes): 134 | return s.decode('latin-1') 135 | # python2 unicode 136 | elif six.PY2 and isinstance(s, six.text_type): 137 | return s.encode('utf-8') 138 | return str(s) 139 | 140 | def to_hexstr(buf, sep=' '): 141 | # for python3 bytes 142 | if six.PY3 and isinstance(buf, bytes): 143 | return sep.join("{0:02x}".format(c) for c in buf).upper() 144 | return sep.join("{0:02x}".format(ord(c)) for c in buf).upper() 145 | ######################################################################################################### 146 | 147 | ################################ IDA 6/7 Compatibility function ######################################### 148 | def get_dtype(ea, op_idx): 149 | if idaapi.IDA_SDK_VERSION >= 700: 150 | insn = idaapi.insn_t() 151 | idaapi.decode_insn(insn, ea) 152 | dtype = insn.ops[op_idx].dtype 153 | dtyp_size = idaapi.get_dtype_size(dtype) 154 | else: 155 | dtype = idaapi.cmd.Operands[op_idx].dtyp 156 | dtyp_size = idaapi.get_dtyp_size(dtype) 157 | return dtype, dtyp_size 158 | 159 | def set_comment(ea, comment): 160 | if idaapi.IDA_SDK_VERSION >= 700: 161 | idc.set_cmt(ea, comment, 0) 162 | else: 163 | idc.MakeComm(ea, comment) 164 | 165 | def get_comment(ea): 166 | if idaapi.IDA_SDK_VERSION >= 700: 167 | return idc.get_cmt(ea, 0) 168 | return idc.Comment(ea) 169 | 170 | def read_range_selection(): 171 | if idaapi.IDA_SDK_VERSION >= 700: 172 | return idaapi.read_range_selection(None) 173 | return idaapi.read_selection() 174 | ######################################################################################################### 175 | 176 | # return a normalized code, or None if input is invalid 177 | def convert_hexstr(code): 178 | # normalize code 179 | code = code.lower() 180 | code = code.replace(' ', '') # remove space 181 | code = code.replace('h', '') # remove trailing 'h' in 90h 182 | code = code.replace('0x', '') # remove 0x 183 | code = code.replace('\\x', '') # remove \x 184 | code = code.replace(',', '') # remove , 185 | code = code.replace(';', '') # remove ; 186 | code = code.replace('"', '') # remove " 187 | code = code.replace("'", '') # remove ' 188 | code = code.replace("+", '') # remove + 189 | 190 | # single-digit hexcode? 191 | if len(code) == 1 and ((code >= '0' and code <= '9') or (code >= 'a' and code <= 'f')): 192 | # stick 0 in front (so 'a' --> '0a') 193 | code = '0' + code 194 | 195 | # odd-length is invalid 196 | if len(code) % 2 != 0: 197 | return None 198 | 199 | try: 200 | hex_data = code.decode('hex') 201 | # we want a list of int 202 | return [ord(i) for i in hex_data] 203 | except: 204 | # invalid hex 205 | return None 206 | 207 | 208 | # download a file from @url, then return (result, file-content) 209 | # return (0, content) on success, or ({1|2}, None) on download failure 210 | def url_download(url): 211 | # Import for python2 212 | try: 213 | from urllib2 import Request, urlopen, URLError, HTTPError 214 | except: 215 | from urllib.request import Request, urlopen 216 | from urllib.error import URLError, HTTPError 217 | 218 | # create the url and the request 219 | req = Request(url) 220 | 221 | # Open the url 222 | try: 223 | # download this URL 224 | f = urlopen(req) 225 | content = f.read() 226 | return (0, content) 227 | 228 | # handle errors 229 | except HTTPError as e: 230 | # print "HTTP Error:", e.code , url 231 | # fail to download this file 232 | return (1, None) 233 | except URLError as e: 234 | # print "URL Error:", e.reason , url 235 | # fail to download this file 236 | return (1, None) 237 | except Exception as e: 238 | # fail to save the downloaded file 239 | # print("Error:", e) 240 | return (2, None) 241 | 242 | 243 | def get_name_value(_from, name): 244 | """ 245 | Fixed: the return value truncated(32 bit) of get_name_value function that analyzed 64 bit binary file about ida64 for win. 246 | 247 | eg: 248 | type == idaapi.NT_BYTE 249 | (type, value) = idaapi.get_name_value(idc.BADADDR, "wcschr") # ida64 for win 250 | 251 | value = 0x14003d3f0L is correct ida64 > 7.x for macOS 252 | value = 0x4003d3f0L is truncated ida64 >= 6.x for win, ida64 == 6.x for macOS 253 | 254 | :param _from: ea 255 | :param name: name string 256 | :return: tuple 257 | """ 258 | name = to_string(name) 259 | (typ, value) = idaapi.get_name_value(_from, name) 260 | if typ == idaapi.NT_BYTE: # typ is byte name (regular name) 261 | value = idaapi.get_name_ea(_from, name) 262 | return (typ, value) 263 | 264 | 265 | ## Main Keypatch class 266 | class Keypatch_Asm: 267 | # supported architectures 268 | arch_lists = { 269 | "X86 16-bit": (KS_ARCH_X86, KS_MODE_16), # X86 16-bit 270 | "X86 32-bit": (KS_ARCH_X86, KS_MODE_32), # X86 32-bit 271 | "X86 64-bit": (KS_ARCH_X86, KS_MODE_64), # X86 64-bit 272 | "ARM": (KS_ARCH_ARM, KS_MODE_ARM), # ARM 273 | "ARM Thumb": (KS_ARCH_ARM, KS_MODE_THUMB), # ARM Thumb 274 | "ARM64 (ARMV8)": (KS_ARCH_ARM64, KS_MODE_LITTLE_ENDIAN),# ARM64 275 | "Hexagon": (KS_ARCH_HEXAGON, KS_MODE_BIG_ENDIAN), # Hexagon 276 | "Mips32": (KS_ARCH_MIPS, KS_MODE_MIPS32), # Mips32 277 | "Mips64": (KS_ARCH_MIPS, KS_MODE_MIPS64), # Mips64 278 | "PowerPC 32": (KS_ARCH_PPC, KS_MODE_PPC32), # PPC32 279 | "PowerPC 64": (KS_ARCH_PPC, KS_MODE_PPC64), # PPC64 280 | "Sparc 32": (KS_ARCH_SPARC, KS_MODE_SPARC32), # Sparc32 281 | "Sparc 64": (KS_ARCH_SPARC, KS_MODE_SPARC64), # Sparc64 282 | "SystemZ": (KS_ARCH_SYSTEMZ, KS_MODE_BIG_ENDIAN), # SystemZ 283 | } 284 | 285 | endian_lists = { 286 | "Little Endian": KS_MODE_LITTLE_ENDIAN, # little endian 287 | "Big Endian": KS_MODE_BIG_ENDIAN, # big endian 288 | } 289 | 290 | syntax_lists = { 291 | "Intel": KS_OPT_SYNTAX_INTEL, 292 | "Nasm": KS_OPT_SYNTAX_NASM, 293 | "AT&T": KS_OPT_SYNTAX_ATT 294 | } 295 | 296 | def __init__(self, arch=None, mode=None): 297 | # update current arch and mode 298 | self.update_hardware_mode() 299 | 300 | # override arch & mode if provided 301 | if arch is not None: 302 | self.arch = arch 303 | if mode is not None: 304 | self.mode = mode 305 | 306 | # IDA uses Intel syntax by default 307 | self.syntax = KS_OPT_SYNTAX_INTEL 308 | 309 | # return Keystone arch & mode (with endianess included) 310 | @staticmethod 311 | def get_hardware_mode(): 312 | (arch, mode) = (None, None) 313 | 314 | if idaapi.IDA_SDK_VERSION >= 900: 315 | is_64bit = ida_ida.idainfo_is_64bit() 316 | is_32bit = ida_ida.idainfo_is_32bit() 317 | cpuname = ida_ida.inf_get_procname().lower() 318 | is_be = ida_ida.inf_is_be() 319 | else: 320 | # heuristically detect hardware setup 321 | info = idaapi.get_inf_structure() 322 | is_64bit = info.is_64bit() 323 | is_32bit = info.is_32bit() 324 | 325 | try: 326 | cpuname = info.procname.lower() 327 | except: 328 | cpuname = info.procName.lower() 329 | 330 | try: 331 | # since IDA7 beta 3 (170724) renamed inf.mf -> is_be()/set_be() 332 | is_be = idaapi.cvar.inf.is_be() 333 | except: 334 | # older IDA versions 335 | is_be = idaapi.cvar.inf.mf 336 | 337 | # print("Keypatch BIG_ENDIAN = %s" %is_be) 338 | 339 | if cpuname == "metapc": 340 | arch = KS_ARCH_X86 341 | if is_64bit: 342 | mode = KS_MODE_64 343 | elif is_32bit: 344 | mode = KS_MODE_32 345 | else: 346 | mode = KS_MODE_16 347 | elif cpuname.startswith("arm"): 348 | # ARM or ARM64 349 | if is_64bit: 350 | arch = KS_ARCH_ARM64 351 | if is_be: 352 | mode = KS_MODE_BIG_ENDIAN 353 | else: 354 | mode = KS_MODE_LITTLE_ENDIAN 355 | else: 356 | arch = KS_ARCH_ARM 357 | # either big-endian or little-endian 358 | if is_be: 359 | mode = KS_MODE_ARM | KS_MODE_BIG_ENDIAN 360 | else: 361 | mode = KS_MODE_ARM | KS_MODE_LITTLE_ENDIAN 362 | elif cpuname.startswith("sparc"): 363 | arch = KS_ARCH_SPARC 364 | if is_64bit: 365 | mode = KS_MODE_SPARC64 366 | else: 367 | mode = KS_MODE_SPARC32 368 | if is_be: 369 | mode |= KS_MODE_BIG_ENDIAN 370 | else: 371 | mode |= KS_MODE_LITTLE_ENDIAN 372 | elif cpuname.startswith("ppc"): 373 | arch = KS_ARCH_PPC 374 | if is_64bit: 375 | mode = KS_MODE_PPC64 376 | else: 377 | mode = KS_MODE_PPC32 378 | if cpuname == "ppc": 379 | # do not support Little Endian mode for PPC 380 | mode += KS_MODE_BIG_ENDIAN 381 | elif cpuname.startswith("mips"): 382 | arch = KS_ARCH_MIPS 383 | if is_64bit: 384 | mode = KS_MODE_MIPS64 385 | else: 386 | mode = KS_MODE_MIPS32 387 | if is_be: 388 | mode |= KS_MODE_BIG_ENDIAN 389 | else: 390 | mode |= KS_MODE_LITTLE_ENDIAN 391 | elif cpuname.startswith("systemz") or cpuname.startswith("s390x"): 392 | arch = KS_ARCH_SYSTEMZ 393 | mode = KS_MODE_BIG_ENDIAN 394 | 395 | return (arch, mode) 396 | 397 | def update_hardware_mode(self): 398 | (self.arch, self.mode) = self.get_hardware_mode() 399 | 400 | # normalize assembly code 401 | # remove comment at the end of assembly code 402 | @staticmethod 403 | def asm_normalize(text): 404 | text = ' '.join(text.split()) 405 | if text.rfind(';') != -1: 406 | return text[:text.rfind(';')].strip() 407 | 408 | return text.strip() 409 | 410 | @staticmethod 411 | # check if input address is valid 412 | # return 413 | # -1 invalid address at target binary 414 | # 0 type mismatch of input address 415 | # 1 valid address at target binary 416 | def check_address(address): 417 | try: 418 | if is_mapped(address): 419 | return 1 420 | else: 421 | return -1 422 | except: 423 | # invalid type 424 | return 0 425 | 426 | ### resolve IDA names from input asm code 427 | # todo: a better syntax parser for all archs 428 | def ida_resolve(self, assembly, address=idc.BADADDR): 429 | def _resolve(_op, ignore_kw=True): 430 | names = re.findall(r"[\$a-z0-9_:\.]+", _op, re.I) 431 | 432 | # try to resolve all names 433 | for name in names: 434 | # ignore known keywords 435 | if ignore_kw and name in ('byte', 'near', 'short', 'word', 'dword', 'ptr', 'offset'): 436 | continue 437 | 438 | sym = name 439 | 440 | # split segment reg 441 | parts = name.partition(':') 442 | if parts[2] != '': 443 | sym = parts[2] 444 | 445 | (typ, value) = get_name_value(address, sym) 446 | 447 | # skip if name doesn't exist or segment / segment registers 448 | if typ in (idaapi.NT_SEG, idaapi.NT_NONE): 449 | continue 450 | 451 | _op = _op.replace(sym, '0x{0:X}'.format(value)) 452 | 453 | return _op 454 | 455 | if self.check_address(address) == 0: 456 | print("Keypatch: WARNING: invalid input address {0}".format(address)) 457 | return assembly 458 | 459 | # for now, we only support IDA name resolve for X86, ARM, ARM64, MIPS, PPC, SPARC 460 | if not (self.arch in (KS_ARCH_X86, KS_ARCH_ARM, KS_ARCH_ARM64, KS_ARCH_MIPS, KS_ARCH_PPC, KS_ARCH_SPARC)): 461 | return assembly 462 | 463 | _asm = assembly.partition(' ') 464 | mnem = _asm[0] 465 | opers = _asm[2].split(',') 466 | 467 | for idx, op in enumerate(opers): 468 | _op = list(op.partition('[')) 469 | ignore_kw = True 470 | if _op[1] == '': 471 | _op[2] = _op[0] 472 | _op[0] = '' 473 | else: 474 | _op[0] = _resolve(_op[0], ignore_kw=True) 475 | ignore_kw = False 476 | 477 | _op[2] = _resolve(_op[2], ignore_kw=ignore_kw) 478 | 479 | opers[idx] = ''.join(_op) 480 | 481 | asm = "{0} {1}".format(mnem, ','.join(opers)) 482 | return asm 483 | 484 | # return bytes of instruction or data 485 | # return None on failure 486 | def ida_get_item(self, address, hex_output=False): 487 | if self.check_address(address) != 1: 488 | # not a valid address 489 | return (None, 0) 490 | 491 | # return None if address is in the middle of instruction / data 492 | if address != get_item_head(address): 493 | return (None, 0) 494 | 495 | size = get_item_size(address) 496 | item = get_bytes(address, size) 497 | 498 | if item is None: 499 | return (None, 0) 500 | 501 | if hex_output: 502 | item = to_hexstr(item) 503 | 504 | return (item, size) 505 | 506 | @staticmethod 507 | def get_op_dtype_name(ea, op_idx): 508 | dtyp_lists = { 509 | idaapi.dt_byte: 'byte', # 8 bit 510 | idaapi.dt_word: 'word', # 16 bit 511 | idaapi.dt_dword: 'dword', # 32 bit 512 | idaapi.dt_float: 'dword', # 4 byte 513 | idaapi.dt_double: 'dword', # 8 byte 514 | #idaapi.dt_tbyte = 5 # variable size (ph.tbyte_size) 515 | #idaapi.dt_packreal = 6 # packed real format for mc68040 516 | idaapi.dt_qword: 'qword', # 64 bit 517 | idaapi.dt_byte16: 'xmmword',# 128 bit 518 | #idaapi.dt_code = 9 # ptr to code (not used?) 519 | #idaapi.dt_void = 10 # none 520 | #idaapi.dt_fword = 11 # 48 bit 521 | #idaapi.dt_bitfild = 12 # bit field (mc680x0) 522 | #idaapi.dt_string = 13 # pointer to asciiz string 523 | #idaapi.dt_unicode = 14 # pointer to unicode string 524 | #idaapi.dt_3byte = 15 # 3-byte data 525 | #idaapi.dt_ldbl = 16 # long double (which may be different from tbyte) 526 | idaapi.dt_byte32: 'ymmword',# 256 bit 527 | } 528 | 529 | dtype, dtyp_size = get_dtype(ea, op_idx) 530 | if dtype == idaapi.dt_tbyte and dtyp_size == 10: 531 | return 'xword' 532 | 533 | dtyp_name = dtyp_lists.get(dtype, None) 534 | 535 | return dtyp_name 536 | 537 | # return asm instructions from start to end 538 | def ida_get_disasm_range(self, start, end): 539 | codes = [] 540 | while start < end: 541 | asm = self.asm_normalize(idc.GetDisasm(start)) 542 | if asm == None: 543 | asm = '' 544 | codes.append(asm) 545 | start = start + get_item_size(start) 546 | 547 | return codes 548 | 549 | # get disasm from IDA 550 | # return '' on invalid address 551 | def ida_get_disasm(self, address, fixup=False): 552 | 553 | def GetMnem(asm): 554 | sp = asm.find(' ') 555 | if sp == -1: 556 | return asm 557 | return asm[:sp] 558 | 559 | if self.check_address(address) != 1: 560 | # not a valid address 561 | return '' 562 | 563 | # return if address is in the middle of instruction / data 564 | if address != get_item_head(address): 565 | return '' 566 | 567 | asm = self.asm_normalize(idc.GetDisasm(address)) 568 | # for now, only support IDA syntax fixup for Intel CPU 569 | if not fixup or self.arch != KS_ARCH_X86: 570 | return asm 571 | 572 | # KS_ARCH_X86 mode 573 | # rebuild disasm code from IDA 574 | i = 0 575 | mnem = GetMnem(asm) 576 | if mnem == '' or mnem in ('rep', 'repne', 'repe'): 577 | return asm 578 | 579 | opers = [] 580 | while get_operand_type(address, i) > 0 and i < 6: 581 | t = get_operand_type(address, i) 582 | o = print_operand(address, i) 583 | 584 | if t in (idc.o_mem, idc.o_displ): 585 | parts = list(o.partition(':')) 586 | if parts[2] == '': 587 | parts[2] = parts[0] 588 | parts[0] = '' 589 | 590 | if '[' not in parts[2]: 591 | parts[2] = '[{0}]'.format(parts[2]) 592 | 593 | o = ''.join(parts) 594 | 595 | if 'ptr ' not in o: 596 | dtyp_name = self.get_op_dtype_name(address, i) 597 | if dtyp_name != None: 598 | o = "{0} ptr {1}".format(dtyp_name, o) 599 | 600 | opers.append(o) 601 | i += 1 602 | 603 | asm = mnem 604 | for o in opers: 605 | if o != '': 606 | asm = "{0} {1},".format(asm, o) 607 | 608 | asm = asm.strip(',') 609 | return asm 610 | 611 | # assemble code with Keystone 612 | # return (encoding, count), or (None, 0) on failure 613 | def assemble(self, assembly, address, arch=None, mode=None, syntax=None): 614 | 615 | # return assembly with arithmetic equation evaluated 616 | def eval_operand(assembly, start, stop, prefix=''): 617 | imm = assembly[start+1:stop] 618 | try: 619 | eval_imm = eval(imm) 620 | if eval_imm > 0x80000000: 621 | eval_imm = 0xffffffff - eval_imm 622 | eval_imm += 1 623 | eval_imm = -eval_imm 624 | return assembly.replace(prefix + imm, prefix + hex(eval_imm)) 625 | except: 626 | return assembly 627 | 628 | # IDA uses different syntax from Keystone 629 | # sometimes, we can convert code to be consumable by Keystone 630 | def fix_ida_syntax(assembly): 631 | 632 | # return True if this insn needs to be fixed 633 | def check_arm_arm64_insn(arch, mnem): 634 | if arch == KS_ARCH_ARM: 635 | if mnem.startswith("ldr") or mnem.startswith("str"): 636 | return True 637 | return False 638 | elif arch == KS_ARCH_ARM64: 639 | if mnem.startswith("ldr") or mnem.startswith("str"): 640 | return True 641 | return mnem in ("stp") 642 | return False 643 | 644 | # return True if this insn needs to be fixed 645 | def check_ppc_insn(mnem): 646 | return mnem in ("stw") 647 | 648 | # replace the right most string occurred 649 | def rreplace(s, old, new): 650 | li = s.rsplit(old, 1) 651 | return new.join(li) 652 | 653 | # convert some ARM pre-UAL assembly to UAL, so Keystone can handle it 654 | # example: streqb --> strbeq 655 | def fix_arm_ual(mnem, assembly): 656 | # TODO: this is not an exhaustive list yet 657 | if len(mnem) != 6: 658 | return assembly 659 | 660 | if (mnem[-1] in ('s', 'b', 'h', 'd')): 661 | #print(">> 222", mnem[3:5]) 662 | if mnem[3:5] in ("cc", "eq", "ne", "hs", "lo", "mi", "pl", "vs", "vc", "hi", "ls", "ge", "lt", "gt", "le", "al"): 663 | return assembly.replace(mnem, mnem[:3] + mnem[-1] + mnem[3:5], 1) 664 | 665 | return assembly 666 | 667 | if self.arch != KS_ARCH_X86: 668 | assembly = assembly.lower() 669 | else: 670 | # Keystone does not support immediate 0bh, but only 0Bh 671 | assembly = assembly.upper() 672 | 673 | # however, 0X must be converted to 0x 674 | # Keystone should fix this limitation in the future 675 | assembly = assembly.replace("0X", " 0x") 676 | 677 | _asm = assembly.partition(' ') 678 | mnem = _asm[0] 679 | if mnem == '': 680 | return assembly 681 | 682 | # for PPC, Keystone does not accept registers with 'r' prefix, 683 | # but only the number behind. lets try to fix that here by 684 | # removing the prefix 'r'. 685 | if self.arch == KS_ARCH_PPC: 686 | for n in range(32): 687 | r = " r%u," %n 688 | if r in assembly: 689 | assembly = assembly.replace(r, " %u," %n) 690 | for n in range(32): 691 | r = "(r%u)" %n 692 | if r in assembly: 693 | assembly = assembly.replace(r, "(%u)" %n) 694 | for n in range(32): 695 | r = ", r%u" %n 696 | if assembly.endswith(r): 697 | assembly = rreplace(assembly, r, ", %u" %n) 698 | 699 | if self.arch == KS_ARCH_X86: 700 | if mnem == "RETN": 701 | # replace retn with ret 702 | return assembly.replace('RETN', 'RET', 1) 703 | if 'OFFSET ' in assembly: 704 | return assembly.replace('OFFSET ', ' ') 705 | if mnem in ('CALL', 'JMP') or mnem.startswith('LOOP'): 706 | # remove 'NEAR PTR' 707 | if ' NEAR PTR ' in assembly: 708 | return assembly.replace(' NEAR PTR ', ' ') 709 | elif mnem[0] == 'J': 710 | # JMP instruction 711 | if ' SHORT ' in assembly: 712 | # remove ' short ' 713 | return assembly.replace(' SHORT ', ' ') 714 | elif self.arch in (KS_ARCH_ARM, KS_ARCH_ARM64, KS_ARCH_PPC): 715 | # *** ARM 716 | # LDR R1, [SP+rtld_fini],#4 717 | # STR R2, [SP,#-4+rtld_fini]! 718 | # STR R0, [SP,#fini]! 719 | # STR R12, [SP,#4+var_8]! 720 | 721 | # *** ARM64 722 | # STP X29, X30, [SP,#-0x10+var_150]! 723 | # STR W0, [X29,#0x150+var_8] 724 | # LDR X0, [X0,#(qword_4D6678 - 0x4D6660)] 725 | # TODO: 726 | # ADRP X19, #interactive@PAGE 727 | 728 | # *** PPC 729 | # stw r5, 0x120+var_108(r1) 730 | 731 | if self.arch == KS_ARCH_ARM and mode == KS_MODE_THUMB: 732 | assembly = assembly.replace('movt.w', 'movt') 733 | 734 | if self.arch == KS_ARCH_ARM: 735 | #print(">> before UAL fix: ", assembly) 736 | assembly = fix_arm_ual(mnem, assembly) 737 | #print(">> after UAL fix: ", assembly) 738 | 739 | if check_arm_arm64_insn(self.arch, mnem) or (("[" in assembly) and ("]" in assembly)): 740 | bang = assembly.find('#') 741 | bracket = assembly.find(']') 742 | if bang != -1 and bracket != -1 and bang < bracket: 743 | return eval_operand(assembly, bang, bracket, '#') 744 | elif '+0x0]' in assembly: 745 | return assembly.replace('+0x0]', ']') 746 | elif check_ppc_insn(mnem): 747 | start = assembly.find(', ') 748 | stop = assembly.find('(') 749 | if start != -1 and stop != -1 and start < stop: 750 | return eval_operand(assembly, start, stop) 751 | return assembly 752 | 753 | def is_thumb(address): 754 | return get_sreg(address, 'T') == 1 755 | 756 | if self.check_address(address) == 0: 757 | return (None, 0) 758 | 759 | # use default syntax, arch and mode if not provided 760 | if syntax is None: 761 | syntax = self.syntax 762 | if arch is None: 763 | arch = self.arch 764 | if mode is None: 765 | mode = self.mode 766 | 767 | if arch == KS_ARCH_ARM and is_thumb(address): 768 | mode = KS_MODE_THUMB 769 | 770 | try: 771 | ks = Ks(arch, mode) 772 | if arch == KS_ARCH_X86: 773 | ks.syntax = syntax 774 | encoding, count = ks.asm(fix_ida_syntax(assembly), address) 775 | except KsError as e: 776 | # keep the below code for debugging 777 | #print("Keypatch Error: {0}".format(e)) 778 | #print("Original asm: {0}".format(assembly)) 779 | #print("Fixed up asm: {0}".format(fix_ida_syntax(assembly))) 780 | encoding, count = None, 0 781 | 782 | return (encoding, count) 783 | 784 | 785 | # patch at address, return the number of written bytes & original data 786 | # this process can fail in some cases 787 | @staticmethod 788 | def patch_raw(address, patch_data, size): 789 | ea = address 790 | orig_data = '' 791 | 792 | while ea < (address + size): 793 | 794 | if not has_value(get_full_flags(ea)): 795 | print("Keypatch: FAILED to read data at 0x{0:X}".format(ea)) 796 | break 797 | 798 | orig_byte = get_wide_byte(ea) 799 | orig_data += chr(orig_byte) 800 | patch_byte = ord(patch_data[ea - address]) 801 | 802 | if patch_byte != orig_byte: 803 | # patch one byte 804 | if idaapi.patch_byte(ea, patch_byte) != 1: 805 | print("Keypatch: FAILED to patch byte at 0x{0:X} [0x{1:X}]".format(ea, patch_byte)) 806 | break 807 | ea += 1 808 | return (ea - address, orig_data) 809 | 810 | # patch at address, return the number of written bytes & original data 811 | # on patch failure, we revert to the original code, then return (None, None) 812 | def patch(self, address, patch_data, size): 813 | # save original function end to fix IDA re-analyze issue after patching 814 | orig_func_end = get_func_attr(address, idc.FUNCATTR_END) 815 | 816 | (patched_len, orig_data) = self.patch_raw(address, patch_data, size) 817 | 818 | if size != patched_len: 819 | # patch failure 820 | if patched_len > 0: 821 | # revert the changes 822 | (rlen, _) = self.patch_raw(address, orig_data, patched_len) 823 | if rlen == patched_len: 824 | print("Keypatch: successfully reverted changes of {0:d} byte(s) at 0x{1:X} [{2}]".format( 825 | patched_len, address, to_hexstr(orig_data))) 826 | else: 827 | print("Keypatch: FAILED to revert changes of {0:d} byte(s) at 0x{1:X} [{2}]".format( 828 | patched_len, address, to_hexstr(orig_data))) 829 | 830 | return (None, None) 831 | 832 | # ask IDA to re-analyze the patched area 833 | if orig_func_end == idc.BADADDR: 834 | # only analyze patched bytes, otherwise it would take a lot of time to re-analyze the whole binary 835 | plan_and_wait(address, address + patched_len + 1) 836 | else: 837 | plan_and_wait(address, orig_func_end) 838 | 839 | # try to fix IDA function re-analyze issue after patching 840 | set_func_end(address, orig_func_end) 841 | 842 | return (patched_len, orig_data) 843 | 844 | # return number of bytes patched 845 | # return 846 | # 0 Invalid assembly 847 | # -1 PatchByte failure 848 | # -2 Can't read original data 849 | # -3 Invalid address 850 | def patch_code(self, address, assembly, syntax, padding, save_origcode, orig_asm=None, patch_data=None, patch_comment=None, undo=False): 851 | global patch_info 852 | 853 | if self.check_address(address) != 1: 854 | # not a valid address 855 | return -3 856 | 857 | orig_comment = get_comment(address) 858 | if orig_comment is None: 859 | orig_comment = '' 860 | 861 | nop_comment = "" 862 | padding_len = 0 863 | if not undo: 864 | # we are patching via Patcher 865 | (orig_encoding, orig_len) = self.ida_get_item(address) 866 | if (orig_encoding, orig_len) == (None, 0): 867 | return -2 868 | 869 | (encoding, count) = self.assemble(assembly, address, syntax=syntax) 870 | if encoding is None: 871 | return 0 872 | 873 | patch_len = len(encoding) 874 | patch_data = ''.join(chr(c) for c in encoding) 875 | 876 | if patch_data == orig_encoding: 877 | #print("Keypatch: no need to patch, same encoding data [{0}] at 0x{1:X}".format(to_hexstr(orig_encoding), address)) 878 | return orig_len 879 | 880 | # for now, only support NOP padding on Intel CPU 881 | if padding and self.arch == KS_ARCH_X86: 882 | if patch_len < orig_len: 883 | padding_len = orig_len - patch_len 884 | patch_len = orig_len 885 | patch_data = patch_data.ljust(patch_len, X86_NOP) 886 | elif patch_len > orig_len: 887 | patch_end = address + patch_len - 1 888 | ins_end = get_item_end(patch_end) 889 | padding_len = ins_end - patch_end - 1 890 | 891 | if padding_len > 0: 892 | patch_len = ins_end - address 893 | patch_data = patch_data.ljust(patch_len, X86_NOP) 894 | 895 | if padding_len > 0: 896 | nop_comment = "\nKeypatch padded NOP to next boundary: {0} bytes".format(padding_len) 897 | 898 | orig_asm = self.ida_get_disasm_range(address, address + patch_len) 899 | else: 900 | # we are reverting the change via "Undo" menu 901 | patch_len = len(patch_data) 902 | 903 | (plen, p_orig_data) = self.patch(address, patch_data, patch_len) 904 | if plen is None: 905 | # failed to patch 906 | return -1 907 | 908 | if not undo: # we are patching 909 | new_patch_comment = None 910 | if save_origcode is True: 911 | # append original instruction to comments 912 | if orig_comment == '': 913 | new_patch_comment = "Keypatch modified this from:\n {0}{1}".format('\n '.join(orig_asm), nop_comment) 914 | else: 915 | new_patch_comment = "\nKeypatch modified this from:\n {0}{1}".format('\n '.join(orig_asm), nop_comment) 916 | 917 | new_comment = "{0}{1}".format(orig_comment, new_patch_comment) 918 | set_comment(address, new_comment) 919 | 920 | if padding_len == 0: 921 | print("Keypatch: successfully patched {0:d} byte(s) at 0x{1:X} from [{2}] to [{3}]".format(plen, 922 | address, to_hexstr(p_orig_data), to_hexstr(patch_data))) 923 | else: 924 | print("Keypatch: successfully patched {0:d} byte(s) at 0x{1:X} from [{2}] to [{3}], with {4} byte(s) NOP padded".format(plen, 925 | address, to_hexstr(p_orig_data), to_hexstr(patch_data), padding_len)) 926 | # save this patching for future "undo" 927 | patch_info.append((address, assembly, p_orig_data, new_patch_comment)) 928 | else: # we are reverting 929 | if patch_comment: 930 | # clean previous IDA comment by replacing it with '' 931 | new_comment = orig_comment.replace(patch_comment, '') 932 | set_comment(address, new_comment) 933 | 934 | print("Keypatch: successfully reverted {0:d} byte(s) at 0x{1:X} from [{2}] to [{3}]".format(plen, 935 | address, to_hexstr(p_orig_data), to_hexstr(patch_data))) 936 | 937 | return plen 938 | 939 | # fill a range of code [addr_begin, addr_end]. 940 | # return the length of patched area 941 | # on failure, return 0 = wrong input, -1 = failed to patch 942 | def fill_code(self, addr_begin, addr_end, assembly, syntax, padding, save_origcode, orig_asm=None): 943 | # treat input as assembly code first 944 | (encoding, _) = self.assemble(assembly, addr_begin, syntax=syntax) 945 | 946 | if encoding is None: 947 | # input might be a hexcode string. try to convert it to raw bytes 948 | encoding = convert_hexstr(assembly) 949 | 950 | if encoding is None: 951 | # invalid input: this is neither assembly nor hexcode string 952 | return 0 953 | 954 | # save original assembly code before overwritting them 955 | orig_asm = self.ida_get_disasm_range(addr_begin, addr_end) 956 | 957 | # save original comment at addr_begin 958 | # TODO: save comments in this range, but how to interleave them? 959 | orig_comment = get_comment(addr_begin) 960 | if orig_comment is None: 961 | orig_comment = '' 962 | 963 | patch_data = "" 964 | assembly_new = [] 965 | size = addr_end - addr_begin 966 | # calculate filling data 967 | encode_chr = ''.join(chr(c) for c in encoding) 968 | while True: 969 | if len(patch_data) + len(encode_chr) <= size: 970 | patch_data = patch_data + encode_chr 971 | assembly_new += [assembly.strip()] 972 | else: 973 | break 974 | 975 | # for now, only support NOP padding on Intel CPU 976 | if padding and self.arch == KS_ARCH_X86: 977 | for i in range(size -len(patch_data)): 978 | assembly_new += ["nop"] 979 | patch_data = patch_data.ljust(size, X86_NOP) 980 | 981 | (plen, p_orig_data) = self.patch(addr_begin, patch_data, len(patch_data)) 982 | if plen is None: 983 | # failed to patch 984 | return -1 985 | 986 | new_patch_comment = '' 987 | # append original instruction to comments 988 | if save_origcode is True: 989 | if orig_comment == '': 990 | new_patch_comment = "Keypatch filled range [0x{0:X}:0x{1:X}] ({2} bytes), replaced:\n {3}".format(addr_begin, addr_end - 1, addr_end - addr_begin, '\n '.join(orig_asm)) 991 | else: 992 | new_patch_comment = "\nKeypatch filled range [0x{0:X}:0x{1:X}] ({2} bytes), replaced:\n {3}".format(addr_begin, addr_end - 1, addr_end - addr_begin, '\n '.join(orig_asm)) 993 | 994 | new_comment = "{0}{1}".format(orig_comment, new_patch_comment) 995 | set_comment(addr_begin, new_comment) 996 | 997 | print("Keypatch: successfully filled range [0x{0:X}:0x{1:X}] ({2} bytes) with \"{3}\", replaced \"{4}\"".format( 998 | addr_begin, addr_end - 1, addr_end - addr_begin, assembly, '; '.join(orig_asm))) 999 | 1000 | # save this modification for future "undo" 1001 | patch_info.append((addr_begin, '\n '.join(assembly_new), p_orig_data, new_patch_comment)) 1002 | 1003 | return plen 1004 | 1005 | 1006 | ### Form helper functions 1007 | @staticmethod 1008 | def dict_to_ordered_list(dictionary): 1009 | l = sorted(list(dictionary.items()), key=lambda t: t[0], reverse=False) 1010 | keys = [i[0] for i in l] 1011 | values = [i[1] for i in l] 1012 | 1013 | return (keys, values) 1014 | 1015 | def get_value_by_idx(self, dictionary, idx, default=None): 1016 | (keys, values) = self.dict_to_ordered_list(dictionary) 1017 | 1018 | try: 1019 | val = values[idx] 1020 | except IndexError: 1021 | val = default 1022 | 1023 | return val 1024 | 1025 | def find_idx_by_value(self, dictionary, value, default=None): 1026 | (keys, values) = self.dict_to_ordered_list(dictionary) 1027 | 1028 | try: 1029 | idx = values.index(value) 1030 | except: 1031 | idx = default 1032 | 1033 | return idx 1034 | 1035 | def get_arch_by_idx(self, idx): 1036 | return self.get_value_by_idx(self.arch_lists, idx) 1037 | 1038 | def find_arch_idx(self, arch, mode): 1039 | return self.find_idx_by_value(self.arch_lists, (arch, mode)) 1040 | 1041 | def get_syntax_by_idx(self, idx): 1042 | return self.get_value_by_idx(self.syntax_lists, idx, self.syntax) 1043 | 1044 | def find_syntax_idx(self, syntax): 1045 | return self.find_idx_by_value(self.syntax_lists, syntax) 1046 | ### /Form helper functions 1047 | 1048 | 1049 | # Common ancestor form to be derived by Patcher, FillRange & Search 1050 | class Keypatch_Form(idaapi.Form): 1051 | # prepare for form initializing 1052 | def setup(self, kp_asm, address, assembly=None): 1053 | self.kp_asm = kp_asm 1054 | self.address = address 1055 | 1056 | # update ordered list of arch and syntax 1057 | self.syntax_keys = self.kp_asm.dict_to_ordered_list(self.kp_asm.syntax_lists)[0] 1058 | self.arch_keys = self.kp_asm.dict_to_ordered_list(self.kp_asm.arch_lists)[0] 1059 | 1060 | # update current arch & mode 1061 | self.kp_asm.update_hardware_mode() 1062 | 1063 | # find right value for c_arch & c_endian controls 1064 | mode = self.kp_asm.mode 1065 | self.endian_id = 0 # little endian 1066 | if self.kp_asm.mode & KS_MODE_BIG_ENDIAN: 1067 | self.endian_id = 1 # big endian 1068 | mode = self.kp_asm.mode - KS_MODE_BIG_ENDIAN 1069 | 1070 | self.arch_id = self.kp_asm.find_arch_idx(self.kp_asm.arch, mode) 1071 | 1072 | self.syntax_id = 0 # to make non-X86 arch happy 1073 | if self.kp_asm.arch == KS_ARCH_X86: 1074 | self.syntax_id = self.kp_asm.find_syntax_idx(self.kp_asm.syntax) 1075 | 1076 | # get original instruction and bytes 1077 | self.orig_asm = kp_asm.ida_get_disasm(address) 1078 | (self.orig_encoding, self.orig_len) = kp_asm.ida_get_item(address, hex_output=True) 1079 | if self.orig_encoding is None: 1080 | self.orig_encoding = '' 1081 | 1082 | if assembly is None: 1083 | self.asm = self.kp_asm.ida_get_disasm(self.address, fixup=True) 1084 | else: 1085 | self.asm = assembly 1086 | 1087 | # update Encoding control 1088 | # return True on success, False on failure 1089 | def _update_encoding(self, arch, mode): 1090 | try: 1091 | syntax = None 1092 | if arch == KS_ARCH_X86: 1093 | syntax_id = self.GetControlValue(self.c_syntax) 1094 | syntax = self.kp_asm.get_syntax_by_idx(syntax_id) 1095 | 1096 | address = self.GetControlValue(self.c_addr) 1097 | try: 1098 | is_mapped(address) 1099 | except: 1100 | # invalid address value 1101 | address = 0 1102 | 1103 | assembly = self.GetControlValue(self.c_assembly) 1104 | raw_assembly = self.kp_asm.ida_resolve(assembly, address) 1105 | self.SetControlValue(self.c_raw_assembly, raw_assembly) 1106 | 1107 | (encoding, count) = self.kp_asm.assemble(raw_assembly, address, arch=arch, 1108 | mode=mode, syntax=syntax) 1109 | 1110 | if encoding is None: 1111 | self.SetControlValue(self.c_encoding, ENCODING_ERR_OUTPUT) 1112 | self.SetControlValue(self.c_encoding_len, 0) 1113 | return False 1114 | else: 1115 | text = "" 1116 | for byte in encoding: 1117 | text += "%02X " % byte 1118 | text.strip() 1119 | if text == "": 1120 | # error? 1121 | self.SetControlValue(self.c_encoding, ENCODING_ERR_OUTPUT) 1122 | return False 1123 | else: 1124 | self.SetControlValue(self.c_encoding, text.strip()) 1125 | self.SetControlValue(self.c_encoding_len, len(encoding)) 1126 | return True 1127 | except Exception as e: 1128 | print(str(e)) 1129 | import traceback 1130 | traceback.print_exc() 1131 | self.SetControlValue(self.c_encoding, ENCODING_ERR_OUTPUT) 1132 | return False 1133 | 1134 | # callback to be executed when any form control changed 1135 | def OnFormChange(self, fid): 1136 | return 1 1137 | 1138 | # update Patcher & Fillrange controls 1139 | def update_patchform(self, fid): 1140 | self.EnableField(self.c_endian, False) 1141 | self.EnableField(self.c_addr, False) 1142 | 1143 | (arch, mode) = (self.kp_asm.arch, self.kp_asm.mode) 1144 | # assembly is focused 1145 | self.SetFocusedField(self.c_assembly) 1146 | 1147 | if arch == KS_ARCH_X86: 1148 | # do not show Endian control 1149 | self.ShowField(self.c_endian, False) 1150 | # allow to choose Syntax 1151 | self.ShowField(self.c_syntax, True) 1152 | self.ShowField(self.c_opt_padding, True) 1153 | else: # do not show Syntax control for non-X86 mode 1154 | self.ShowField(self.c_syntax, False) 1155 | # for now, we do not support padding for non-X86 archs 1156 | self.ShowField(self.c_opt_padding, False) 1157 | #self.EnableField(self.c_opt_padding, False) 1158 | 1159 | # update other controls & Encoding with live assembling 1160 | self.update_controls(arch, mode) 1161 | 1162 | return 1 1163 | 1164 | # update some controls - including Encoding control 1165 | def update_controls(self, arch, mode): 1166 | # Fixup & Encoding-len are read-only controls 1167 | self.EnableField(self.c_raw_assembly, False) 1168 | self.EnableField(self.c_encoding_len, False) 1169 | 1170 | # Encoding is enable to allow user to select & copy 1171 | self.EnableField(self.c_encoding, True) 1172 | 1173 | if self.GetControlValue(self.c_endian) == 1: 1174 | endian = KS_MODE_BIG_ENDIAN 1175 | else: 1176 | endian = KS_MODE_LITTLE_ENDIAN 1177 | 1178 | # update encoding with live assembling 1179 | self._update_encoding(arch, mode | endian) 1180 | 1181 | return 1 1182 | 1183 | # get Patcher/FillRange options 1184 | def get_opts(self, name=None): 1185 | names = self.c_opt_chk.children_names 1186 | val = self.c_opt_chk.value 1187 | opts = {} 1188 | for i in range(len(names)): 1189 | opts[names[i]] = val & (2**i) 1190 | 1191 | if name != None: 1192 | opts[name] = val 1193 | 1194 | return opts 1195 | 1196 | 1197 | # Fill Range form 1198 | class Keypatch_FillRange(Keypatch_Form): 1199 | def __init__(self, kp_asm, addr_begin, addr_end, assembly=None, opts=None): 1200 | self.setup(kp_asm, addr_begin, assembly) 1201 | self.addr_end = addr_end 1202 | 1203 | # create FillRange form 1204 | super(Keypatch_FillRange, self).__init__( 1205 | r"""STARTITEM {id:c_assembly} 1206 | BUTTON YES* Patch 1207 | KEYPATCH:: Fill Range 1208 | 1209 | {FormChangeCb} 1210 | 1211 | <~S~yntax :{c_syntax}> 1212 | 1213 | 1214 | 1215 | <~A~ssembly :{c_assembly}> 1216 | <##- Fixup :{c_raw_assembly}> 1217 | <##- Encode:{c_encoding}> 1218 | <##- Size :{c_encoding_len}> 1219 | <~N~OPs padding until next instruction boundary:{c_opt_padding}> 1220 | {c_opt_chk}> 1221 | """, { 1222 | 'c_endian': self.DropdownListControl( 1223 | items = list(self.kp_asm.endian_lists.keys()), 1224 | readonly = True, 1225 | selval = self.endian_id), 1226 | 'c_addr': self.NumericInput(value=addr_begin, swidth=MAX_ADDRESS_LEN, tp=self.FT_ADDR), 1227 | 'c_addr_end': self.NumericInput(value=addr_end - 1, swidth=MAX_ADDRESS_LEN, tp=self.FT_ADDR), 1228 | 'c_assembly': self.StringInput(value=self.asm[:MAX_INSTRUCTION_STRLEN], width=MAX_INSTRUCTION_STRLEN), 1229 | 'c_size': self.NumericInput(value=addr_end - addr_begin, swidth=8, tp=self.FT_DEC), 1230 | 'c_raw_assembly': self.StringInput(value='', width=MAX_INSTRUCTION_STRLEN), 1231 | 'c_encoding': self.StringInput(value='', width=MAX_ENCODING_LEN), 1232 | 'c_encoding_len': self.NumericInput(value=0, swidth=8, tp=self.FT_DEC), 1233 | 'c_syntax': self.DropdownListControl( 1234 | items = self.syntax_keys, 1235 | readonly = True, 1236 | selval = self.syntax_id), 1237 | 'c_opt_chk':idaapi.Form.ChkGroupControl(('c_opt_padding', 'c_opt_comment', ''), value=opts['c_opt_chk']), 1238 | 'FormChangeCb': self.FormChangeCb(self.OnFormChange), 1239 | }) 1240 | 1241 | self.Compile() 1242 | 1243 | # callback to be executed when any form control changed 1244 | def OnFormChange(self, fid): 1245 | # make some controls read-only in FillRange mode 1246 | self.EnableField(self.c_size, False) 1247 | self.EnableField(self.c_addr_end, False) 1248 | 1249 | return self.update_patchform(fid) 1250 | 1251 | 1252 | # Patcher form 1253 | class Keypatch_Patcher(Keypatch_Form): 1254 | def __init__(self, kp_asm, address, assembly=None, opts=None): 1255 | self.setup(kp_asm, address, assembly) 1256 | 1257 | # create Patcher form 1258 | super(Keypatch_Patcher, self).__init__( 1259 | r"""STARTITEM {id:c_assembly} 1260 | BUTTON YES* Patch 1261 | KEYPATCH:: Patcher 1262 | 1263 | {FormChangeCb} 1264 | 1265 | <~S~yntax :{c_syntax}> 1266 |
1267 | 1268 | <##- Encode:{c_orig_encoding}> 1269 | <##- Size :{c_orig_len}> 1270 | <~A~ssembly :{c_assembly}> 1271 | <##- Fixup :{c_raw_assembly}> 1272 | <##- Encode:{c_encoding}> 1273 | <##- Size :{c_encoding_len}> 1274 | <~N~OPs padding until next instruction boundary:{c_opt_padding}> 1275 | {c_opt_chk}> 1276 | """, { 1277 | 'c_endian': self.DropdownListControl( 1278 | items = list(self.kp_asm.endian_lists.keys()), 1279 | readonly = True, 1280 | selval = self.endian_id), 1281 | 'c_addr': self.NumericInput(value=address, swidth=MAX_ADDRESS_LEN, tp=self.FT_ADDR), 1282 | 'c_assembly': self.StringInput(value=self.asm[:MAX_INSTRUCTION_STRLEN], width=MAX_INSTRUCTION_STRLEN), 1283 | 'c_orig_assembly': self.StringInput(value=self.orig_asm[:MAX_INSTRUCTION_STRLEN], width=MAX_INSTRUCTION_STRLEN), 1284 | 'c_orig_encoding': self.StringInput(value=self.orig_encoding[:MAX_ENCODING_LEN], width=MAX_ENCODING_LEN), 1285 | 'c_orig_len': self.NumericInput(value=self.orig_len, swidth=8, tp=self.FT_DEC), 1286 | 'c_raw_assembly': self.StringInput(value='', width=MAX_INSTRUCTION_STRLEN), 1287 | 'c_encoding': self.StringInput(value='', width=MAX_ENCODING_LEN), 1288 | 'c_encoding_len': self.NumericInput(value=0, swidth=8, tp=self.FT_DEC), 1289 | 'c_syntax': self.DropdownListControl( 1290 | items = self.syntax_keys, 1291 | readonly = True, 1292 | selval = self.syntax_id), 1293 | 'c_opt_chk':self.ChkGroupControl(('c_opt_padding', 'c_opt_comment', ''), value=opts['c_opt_chk']), 1294 | 'FormChangeCb': self.FormChangeCb(self.OnFormChange), 1295 | }) 1296 | 1297 | self.Compile() 1298 | 1299 | # callback to be executed when any form control changed 1300 | def OnFormChange(self, fid): 1301 | # make some fields read-only in Patch mode 1302 | self.EnableField(self.c_orig_assembly, False) 1303 | self.EnableField(self.c_orig_encoding, False) 1304 | self.EnableField(self.c_orig_len, False) 1305 | 1306 | return self.update_patchform(fid) 1307 | 1308 | 1309 | # Search position chooser 1310 | class SearchResultChooser(Choose): 1311 | def __init__(self, title, items, flags=0, width=None, height=None, embedded=False, modal=False): 1312 | super(SearchResultChooser, self).__init__( 1313 | title, 1314 | [["Address", Choose.CHCOL_HEX|40]], 1315 | flags = flags, 1316 | width = width, 1317 | height = height, 1318 | embedded = embedded) 1319 | self.n = 0 1320 | self.items = items 1321 | self.selcount = 0 1322 | self.modal = modal 1323 | 1324 | def OnClose(self): 1325 | return 1326 | 1327 | def OnSelectLine(self, n): 1328 | self.selcount += 1 1329 | jumpto(self.items[n][0]) 1330 | 1331 | def OnGetLine(self, n): 1332 | res = self.items[n] 1333 | res = [idc.atoa(res[0])] 1334 | return res 1335 | 1336 | def OnGetSize(self): 1337 | n = len(self.items) 1338 | return n 1339 | 1340 | def show(self): 1341 | return self.Show(self.modal) >= 0 1342 | 1343 | 1344 | # Search form 1345 | class Keypatch_Search(Keypatch_Form): 1346 | def __init__(self, kp_asm, address, assembly=None): 1347 | self.setup(kp_asm, address, assembly) 1348 | 1349 | # create Search form 1350 | super(Keypatch_Search, self).__init__( 1351 | r"""STARTITEM {id:c_assembly} 1352 | BUTTON YES* Search 1353 | KEYPATCH:: Search 1354 | 1355 | {FormChangeCb} 1356 | 1357 | 1358 | <~S~yntax :{c_syntax}> 1359 | 1360 | <~A~ssembly :{c_assembly}> 1361 | <##- Fixup :{c_raw_assembly}> 1362 | <##- Encode:{c_encoding}> 1363 | <##- Size :{c_encoding_len}> 1364 | """, { 1365 | 'c_addr': self.NumericInput(value=address, swidth=MAX_ADDRESS_LEN, tp=self.FT_ADDR), 1366 | 'c_assembly': self.StringInput(value=self.asm[:MAX_INSTRUCTION_STRLEN], width=MAX_INSTRUCTION_STRLEN), 1367 | 'c_raw_assembly': self.StringInput(value='', width=MAX_INSTRUCTION_STRLEN), 1368 | 'c_encoding': self.StringInput(value='', width=MAX_ENCODING_LEN), 1369 | 'c_encoding_len': self.NumericInput(value=0, swidth=8, tp=self.FT_DEC), 1370 | 'c_arch': self.DropdownListControl( 1371 | items = self.arch_keys, 1372 | readonly = True, 1373 | selval = self.arch_id, 1374 | width = 32), 1375 | 'c_endian': self.DropdownListControl( 1376 | items = list(self.kp_asm.endian_lists.keys()), 1377 | readonly = True, 1378 | selval = self.endian_id), 1379 | 'c_syntax': self.DropdownListControl( 1380 | items = self.syntax_keys, 1381 | readonly = True, 1382 | selval = self.syntax_id), 1383 | 'FormChangeCb': self.FormChangeCb(self.OnFormChange), 1384 | }) 1385 | 1386 | self.Compile() 1387 | 1388 | ################################ IDA 6/7 Compatibility method ####################################### 1389 | def find_binary(self, ea): 1390 | if idaapi.IDA_SDK_VERSION >= 700: 1391 | return ida_search.find_binary(ea, idaapi.cvar.inf.max_ea, self.GetControlValue(self.c_encoding), 16, idc.SEARCH_DOWN) 1392 | return idc.FindBinary(ea, idc.SEARCH_DOWN, self.GetControlValue(self.c_encoding)) 1393 | ##################################################################################################### 1394 | 1395 | # callback to be executed when any form control changed 1396 | def OnFormChange(self, fid): 1397 | # handle the search button 1398 | if fid == -2: 1399 | address = 0 1400 | addresses = [] 1401 | while address != idc.BADADDR: 1402 | address = self.find_binary(address) 1403 | if address == idc.BADADDR: 1404 | break 1405 | addresses.append([address]) 1406 | address = address + 1 1407 | c = SearchResultChooser("Searching for [{0}]".format(self.GetControlValue(self.c_raw_assembly)), addresses) 1408 | c.show() 1409 | return 1 1410 | 1411 | # only Search mode allows to select arch+mode 1412 | arch_id = self.GetControlValue(self.c_arch) 1413 | (arch, mode) = self.kp_asm.get_arch_by_idx(arch_id) 1414 | 1415 | # assembly is focused 1416 | self.SetFocusedField(self.c_assembly) 1417 | 1418 | if arch == KS_ARCH_X86: 1419 | # enable Syntax and disable Endian for x86 1420 | self.ShowField(self.c_syntax, True) 1421 | self.EnableField(self.c_syntax, True) 1422 | self.syntax_id = self.GetControlValue(self.c_syntax) 1423 | self.EnableField(self.c_endian, False) 1424 | # set Endian index properly 1425 | self.SetControlValue(self.c_endian, 0) 1426 | elif arch in (KS_ARCH_ARM64, KS_ARCH_HEXAGON, KS_ARCH_SYSTEMZ): 1427 | # no Syntax & Endian option for these archs 1428 | self.ShowField(self.c_syntax, False) 1429 | self.EnableField(self.c_syntax, False) 1430 | self.EnableField(self.c_endian, False) 1431 | # set Endian index properly 1432 | self.SetControlValue(self.c_endian, (mode & KS_MODE_BIG_ENDIAN != 0)) 1433 | elif (arch == KS_ARCH_PPC) and (mode & KS_MODE_PPC32 != 0): 1434 | # no Syntax & Endian option for these archs 1435 | self.ShowField(self.c_syntax, False) 1436 | self.EnableField(self.c_syntax, False) 1437 | self.EnableField(self.c_endian, False) 1438 | # set Endian index properly 1439 | self.SetControlValue(self.c_endian, (mode & KS_MODE_BIG_ENDIAN != 0)) 1440 | else: 1441 | # no Syntax & Endian option 1442 | self.ShowField(self.c_syntax, False) 1443 | self.EnableField(self.c_syntax, False) 1444 | self.EnableField(self.c_endian, True) 1445 | 1446 | if self.GetControlValue(self.c_endian) == 1: 1447 | endian = KS_MODE_BIG_ENDIAN 1448 | else: 1449 | endian = KS_MODE_LITTLE_ENDIAN 1450 | 1451 | # update other controls & Encoding with live assembling 1452 | self.update_controls(arch, mode) 1453 | 1454 | return 1 1455 | 1456 | 1457 | # About form 1458 | class About_Form(idaapi.Form): 1459 | def __init__(self, version): 1460 | # create About form 1461 | super(About_Form, self).__init__( 1462 | r"""STARTITEM 0 1463 | BUTTON YES* Open Keypatch Website 1464 | KEYPATCH:: About 1465 | 1466 | {FormChangeCb} 1467 | Keypatch IDA plugin v%s, using Keystone Engine v%s. 1468 | (c) Nguyen Anh Quynh + Thanh Nguyen, 2018. 1469 | 1470 | Keypatch is released under the GPL v2. 1471 | Find more info at http://www.keystone-engine.org/keypatch 1472 | """ %(version, keystone.__version__), { 1473 | 'FormChangeCb': self.FormChangeCb(self.OnFormChange), 1474 | }) 1475 | 1476 | self.Compile() 1477 | 1478 | # callback to be executed when any form control changed 1479 | def OnFormChange(self, fid): 1480 | if fid == -2: # Goto homepage 1481 | import webbrowser 1482 | # open Keypatch homepage in a new tab, if possible 1483 | webbrowser.open(KP_HOMEPAGE, new = 2) 1484 | 1485 | return 1 1486 | 1487 | 1488 | # Check-for-update form 1489 | class Update_Form(idaapi.Form): 1490 | def __init__(self, version, message): 1491 | # create Update form 1492 | super(Update_Form, self).__init__( 1493 | r"""STARTITEM 0 1494 | BUTTON YES* Open Keypatch Website 1495 | KEYPATCH:: Check for update 1496 | 1497 | {FormChangeCb} 1498 | Your Keypatch is v%s 1499 | %s 1500 | """ %(version, message), { 1501 | 'FormChangeCb': self.FormChangeCb(self.OnFormChange), 1502 | }) 1503 | self.Compile() 1504 | 1505 | # callback to be executed when any form control changed 1506 | def OnFormChange(self, fid): 1507 | if fid == -2: # Goto homepage 1508 | import webbrowser 1509 | # open Keypatch homepage in a new tab, if possible 1510 | webbrowser.open(KP_HOMEPAGE, new = 2) 1511 | 1512 | return 1 1513 | 1514 | 1515 | try: 1516 | # adapted from pull request #7 by @quangnh89 1517 | class Kp_Menu_Context(idaapi.action_handler_t): 1518 | 1519 | @classmethod 1520 | def get_name(self): 1521 | return self.__name__ 1522 | 1523 | @classmethod 1524 | def get_label(self): 1525 | return self.label 1526 | 1527 | @classmethod 1528 | def register(self, plugin, label): 1529 | self.plugin = plugin 1530 | self.label = label 1531 | instance = self() 1532 | return idaapi.register_action(idaapi.action_desc_t( 1533 | self.get_name(), # Name. Acts as an ID. Must be unique. 1534 | instance.get_label(), # Label. That's what users see. 1535 | instance # Handler. Called when activated, and for updating 1536 | )) 1537 | 1538 | @classmethod 1539 | def unregister(self): 1540 | """Unregister the action. 1541 | After unregistering the class cannot be used. 1542 | """ 1543 | idaapi.unregister_action(self.get_name()) 1544 | 1545 | @classmethod 1546 | def activate(self, ctx): 1547 | # dummy method 1548 | return 1 1549 | 1550 | @classmethod 1551 | def update(self, ctx): 1552 | try: 1553 | if idaapi.IDA_SDK_VERSION >= 900: 1554 | # Since IDA 9.0, form_type is deprecated, should use widget_type 1555 | if ctx.widget_type == idaapi.BWN_DISASM: 1556 | return idaapi.AST_ENABLE_FOR_FORM 1557 | else: 1558 | return idaapi.AST_DISABLE_FOR_FORM 1559 | else: 1560 | if ctx.form_type == idaapi.BWN_DISASM: 1561 | return idaapi.AST_ENABLE_FOR_FORM 1562 | else: 1563 | return idaapi.AST_DISABLE_FOR_FORM 1564 | except Exception as e: 1565 | # Add exception for main menu on >= IDA 7.0 1566 | return idaapi.AST_ENABLE_ALWAYS 1567 | 1568 | # context menu for Patcher 1569 | class Kp_MC_Patcher(Kp_Menu_Context): 1570 | def activate(self, ctx): 1571 | self.plugin.patcher() 1572 | return 1 1573 | 1574 | # context menu for Fill Range 1575 | class Kp_MC_Fill_Range(Kp_Menu_Context): 1576 | def activate(self, ctx): 1577 | self.plugin.fill_range() 1578 | return 1 1579 | 1580 | # context menu for Undo 1581 | class Kp_MC_Undo(Kp_Menu_Context): 1582 | def activate(self, ctx): 1583 | self.plugin.undo() 1584 | return 1 1585 | 1586 | # context menu for Search 1587 | class Kp_MC_Search(Kp_Menu_Context): 1588 | def activate(self, ctx): 1589 | self.plugin.search() 1590 | return 1 1591 | 1592 | # context menu for Check Update 1593 | class Kp_MC_Updater(Kp_Menu_Context): 1594 | def activate(self, ctx): 1595 | self.plugin.updater() 1596 | return 1 1597 | 1598 | # context menu for About 1599 | class Kp_MC_About(Kp_Menu_Context): 1600 | def activate(self, ctx): 1601 | self.plugin.about() 1602 | return 1 1603 | 1604 | except: 1605 | pass 1606 | 1607 | # hooks for popup menu 1608 | class Hooks(idaapi.UI_Hooks): 1609 | if idaapi.IDA_SDK_VERSION >= 700: 1610 | # IDA >= 700 right click widget popup 1611 | def finish_populating_widget_popup(self, form, popup): 1612 | if idaapi.get_widget_type(form) == idaapi.BWN_DISASM: 1613 | try: 1614 | idaapi.attach_action_to_popup(form, popup, Kp_MC_Patcher.get_name(), 'Keypatch/') 1615 | idaapi.attach_action_to_popup(form, popup, Kp_MC_Fill_Range.get_name(), 'Keypatch/') 1616 | idaapi.attach_action_to_popup(form, popup, Kp_MC_Undo.get_name(), 'Keypatch/') 1617 | idaapi.attach_action_to_popup(form, popup, "-", 'Keypatch/') 1618 | idaapi.attach_action_to_popup(form, popup, Kp_MC_Search.get_name(), 'Keypatch/') 1619 | idaapi.attach_action_to_popup(form, popup, "-", 'Keypatch/') 1620 | idaapi.attach_action_to_popup(form, popup, Kp_MC_Updater.get_name(), 'Keypatch/') 1621 | idaapi.attach_action_to_popup(form, popup, Kp_MC_About.get_name(), 'Keypatch/') 1622 | except: 1623 | pass 1624 | else: 1625 | # IDA < 700 right click popup 1626 | def finish_populating_tform_popup(self, form, popup): 1627 | # We'll add our action to all "IDA View-*"s. 1628 | # If we wanted to add it only to "IDA View-A", we could 1629 | # also discriminate on the widget's title: 1630 | # 1631 | # if idaapi.get_tform_title(form) == "IDA View-A": 1632 | # ... 1633 | # 1634 | if idaapi.get_tform_type(form) == idaapi.BWN_DISASM: 1635 | try: 1636 | idaapi.attach_action_to_popup(form, popup, Kp_MC_Patcher.get_name(), 'Keypatch/') 1637 | idaapi.attach_action_to_popup(form, popup, Kp_MC_Fill_Range.get_name(), 'Keypatch/') 1638 | idaapi.attach_action_to_popup(form, popup, Kp_MC_Undo.get_name(), 'Keypatch/') 1639 | idaapi.attach_action_to_popup(form, popup, "-", 'Keypatch/') 1640 | idaapi.attach_action_to_popup(form, popup, Kp_MC_Search.get_name(), 'Keypatch/') 1641 | idaapi.attach_action_to_popup(form, popup, "-", 'Keypatch/') 1642 | idaapi.attach_action_to_popup(form, popup, Kp_MC_Updater.get_name(), 'Keypatch/') 1643 | idaapi.attach_action_to_popup(form, popup, Kp_MC_About.get_name(), 'Keypatch/') 1644 | except: 1645 | pass 1646 | 1647 | 1648 | # check if we already initialized Keypatch 1649 | kp_initialized = False 1650 | 1651 | #-------------------------------------------------------------------------- 1652 | # Plugin 1653 | #-------------------------------------------------------------------------- 1654 | class Keypatch_Plugin_t(idaapi.plugin_t): 1655 | comment = "Keypatch plugin for IDA Pro (using Keystone framework)" 1656 | help = "Find more information on Keypatch at http://keystone-engine.org/keypatch" 1657 | wanted_name = "Keypatch Patcher" 1658 | wanted_hotkey = "Ctrl-Alt-K" 1659 | flags = idaapi.PLUGIN_KEEP 1660 | 1661 | def load_configuration(self): 1662 | # default 1663 | self.opts = {} 1664 | 1665 | # load configuration from file 1666 | try: 1667 | f = open(KP_CFGFILE, "rt") 1668 | self.opts = json.load(f) 1669 | f.close() 1670 | except IOError: 1671 | print("Keypatch: FAILED to load config file. Use default setup now.") 1672 | except Exception as e: 1673 | print("Keypatch: FAILED to load config file, with exception: {0}".format(str(e))) 1674 | 1675 | # use default values if not defined in config file 1676 | if 'c_opt_padding' not in self.opts: 1677 | self.opts['c_opt_padding'] = 1 1678 | 1679 | if 'c_opt_comment' not in self.opts: 1680 | self.opts['c_opt_comment'] = 2 1681 | 1682 | self.opts['c_opt_chk'] = self.opts['c_opt_padding'] | self.opts['c_opt_comment'] 1683 | 1684 | def init(self): 1685 | global kp_initialized 1686 | 1687 | # register popup menu handlers 1688 | try: 1689 | Kp_MC_Patcher.register(self, "Patcher (Ctrl-Alt-K)") 1690 | Kp_MC_Fill_Range.register(self, "Fill Range") 1691 | Kp_MC_Undo.register(self, "Undo last patching") 1692 | Kp_MC_Search.register(self, "Search") 1693 | Kp_MC_Updater.register(self, "Check for update") 1694 | Kp_MC_About.register(self, "About") 1695 | except: 1696 | pass 1697 | 1698 | # setup popup menu 1699 | self.hooks = Hooks() 1700 | self.hooks.hook() 1701 | 1702 | self.opts = None 1703 | if kp_initialized == False: 1704 | kp_initialized = True 1705 | 1706 | if idaapi.IDA_SDK_VERSION >= 700: 1707 | # Add menu IDA >= 7.0 1708 | idaapi.attach_action_to_menu("Edit/Keypatch/Patcher", Kp_MC_Patcher.get_name(), idaapi.SETMENU_APP) 1709 | idaapi.attach_action_to_menu("Edit/Keypatch/About", Kp_MC_About.get_name(), idaapi.SETMENU_APP) 1710 | idaapi.attach_action_to_menu("Edit/Keypatch/Check for update", Kp_MC_Updater.get_name(), idaapi.SETMENU_APP) 1711 | idaapi.attach_action_to_menu("Edit/Keypatch/Search", Kp_MC_Search.get_name(), idaapi.SETMENU_APP) 1712 | idaapi.attach_action_to_menu("Edit/Keypatch/Undo last patching", Kp_MC_Undo.get_name(), idaapi.SETMENU_APP) 1713 | idaapi.attach_action_to_menu("Edit/Keypatch/Fill Range", Kp_MC_Fill_Range.get_name(), idaapi.SETMENU_APP) 1714 | else: 1715 | # add Keypatch menu 1716 | menu = idaapi.add_menu_item("Edit/Keypatch/", "Patcher (Ctrl-Alt-K)", "", 1, self.patcher, None) 1717 | if menu is not None: 1718 | idaapi.add_menu_item("Edit/Keypatch/", "About", "", 1, self.about, None) 1719 | idaapi.add_menu_item("Edit/Keypatch/", "Check for update", "", 1, self.updater, None) 1720 | idaapi.add_menu_item("Edit/Keypatch/", "-", "", 1, self.menu_null, None) 1721 | idaapi.add_menu_item("Edit/Keypatch/", "Search", "", 1, self.search, None) 1722 | idaapi.add_menu_item("Edit/Keypatch/", "-", "", 1, self.menu_null, None) 1723 | idaapi.add_menu_item("Edit/Keypatch/", "Undo last patching", "", 1, self.undo, None) 1724 | idaapi.add_menu_item("Edit/Keypatch/", "Fill Range", "", 1, self.fill_range, None) 1725 | elif idaapi.IDA_SDK_VERSION < 680: 1726 | # older IDAPython (such as in IDAPro 6.6) does add new submenu. 1727 | # in this case, put Keypatch menu in menu Edit \ Patch program 1728 | # not sure about v6.7, so to be safe we just check against v6.8 1729 | idaapi.add_menu_item("Edit/Patch program/", "-", "", 0, self.menu_null, None) 1730 | idaapi.add_menu_item("Edit/Patch program/", "Keypatch:: About", "", 0, self.about, None) 1731 | idaapi.add_menu_item("Edit/Patch program/", "Keypatch:: Check for update", "", 0, self.updater, None) 1732 | idaapi.add_menu_item("Edit/Patch program/", "Keypatch:: Search", "", 0, self.search, None) 1733 | idaapi.add_menu_item("Edit/Patch program/", "Keypatch:: Undo last patching", "", 0, self.undo, None) 1734 | idaapi.add_menu_item("Edit/Patch program/", "Keypatch:: Fill Range", "", 0, self.fill_range, None) 1735 | idaapi.add_menu_item("Edit/Patch program/", "Keypatch:: Patcher (Ctrl-Alt-K)", "", 0, self.patcher, None) 1736 | 1737 | print("=" * 80) 1738 | print("Keypatch v{0} (c) Nguyen Anh Quynh & Thanh Nguyen, 2018".format(VERSION)) 1739 | print("Keypatch is using Keystone v{0}".format(keystone.__version__)) 1740 | print("Keypatch Patcher's shortcut key is Ctrl-Alt-K") 1741 | print("Use the same hotkey Ctrl-Alt-K to open 'Fill Range' window on a selected range of code") 1742 | print("To revert (undo) the last patching, choose menu Edit | Keypatch | Undo last patching") 1743 | print("Keypatch Search is available from menu Edit | Keypatch | Search") 1744 | print("Find more information about Keypatch at http://keystone-engine.org/keypatch") 1745 | 1746 | self.load_configuration() 1747 | 1748 | print("=" * 80) 1749 | self.kp_asm = Keypatch_Asm() 1750 | 1751 | return idaapi.PLUGIN_KEEP 1752 | 1753 | def term(self): 1754 | if self.hooks is not None: 1755 | self.hooks.unhook() 1756 | self.hooks = None 1757 | 1758 | if self.opts is None: 1759 | return 1760 | #save configuration to file 1761 | try: 1762 | json.dump(self.opts, open(KP_CFGFILE, "wt")) 1763 | except Exception as e: 1764 | print("Keypatch: FAILED to save config file, with exception: {0}".format(str(e))) 1765 | else: 1766 | print("Keypatch: configuration is saved to {0}".format(KP_CFGFILE)) 1767 | 1768 | # null handler 1769 | def menu_null(self): 1770 | pass 1771 | 1772 | # handler for About menu 1773 | def about(self): 1774 | f = About_Form(VERSION) 1775 | f.Execute() 1776 | f.Free() 1777 | 1778 | # handler for Check-for-Update menu 1779 | def updater(self): 1780 | (r, content) = url_download(KP_GITHUB_VERSION) 1781 | content = to_string(content) 1782 | if r == 0: 1783 | # find stable version 1784 | sig = 'VERSION_STABLE = "' 1785 | tmp = content[content.find(sig)+len(sig):] 1786 | version_stable = tmp[:tmp.find('"')] 1787 | 1788 | # compare with the current version 1789 | if version_stable == VERSION: 1790 | f = Update_Form(VERSION, "Good, you are already on the latest stable version!") 1791 | f.Execute() 1792 | # free this form 1793 | f.Free() 1794 | else: 1795 | f = Update_Form(VERSION, "Download latest stable version {0} from http://keystone-engine.org/keypatch".format(version_stable)) 1796 | f.Execute() 1797 | # free this form 1798 | f.Free() 1799 | else: 1800 | # fail to download 1801 | warning("ERROR: Keypatch failed to connect to internet (Github). Try again later.") 1802 | print("Keypatch: FAILED to connect to Github to check for latest update. Try again later.") 1803 | 1804 | # handler for Undo menu 1805 | def undo(self): 1806 | global patch_info 1807 | if len(patch_info) == 0: 1808 | # TODO: disable Undo menu? 1809 | warning("ERROR: Keypatch already got to the last undo patching!") 1810 | else: 1811 | (address, assembly, p_orig_data, patch_comment) = patch_info[-1] 1812 | 1813 | # undo the patch 1814 | self.kp_asm.patch_code(address, None, None, None, None, orig_asm=[assembly], patch_data=p_orig_data, patch_comment=patch_comment, undo=True) 1815 | del(patch_info[-1]) 1816 | 1817 | # handler for Search menu 1818 | def search(self): 1819 | address = get_screen_ea() 1820 | f = Keypatch_Search(self.kp_asm, address) 1821 | f.Execute() 1822 | f.Free() 1823 | 1824 | # handler for Patcher menu 1825 | def patcher(self): 1826 | # be sure that this arch is supported by Keystone 1827 | if self.kp_asm.arch is None: 1828 | warning("ERROR: Keypatch cannot handle this architecture (unsupported by Keystone), quit!") 1829 | return 1830 | 1831 | selection, addr_begin, addr_end = read_range_selection() 1832 | if selection: 1833 | # call Fill Range function on this selected code 1834 | return self.fill_range() 1835 | 1836 | address = get_screen_ea() 1837 | 1838 | if self.opts is None: 1839 | self.load_configuration() 1840 | 1841 | init_assembly = None 1842 | while True: 1843 | f = Keypatch_Patcher(self.kp_asm, address, assembly=init_assembly, opts=self.opts) 1844 | ok = f.Execute() 1845 | if ok == 1: 1846 | try: 1847 | syntax = None 1848 | if f.kp_asm.arch == KS_ARCH_X86: 1849 | syntax_id = f.c_syntax.value 1850 | syntax = self.kp_asm.get_syntax_by_idx(syntax_id) 1851 | 1852 | assembly = f.c_assembly.value 1853 | self.opts = f.get_opts('c_opt_chk') 1854 | padding = (self.opts.get("c_opt_padding", 0) != 0) 1855 | comment = (self.opts.get("c_opt_comment", 0) != 0) 1856 | 1857 | raw_assembly = self.kp_asm.ida_resolve(assembly, address) 1858 | 1859 | print("Keypatch: attempting to modify \"{0}\" at 0x{1:X} to \"{2}\"".format( 1860 | self.kp_asm.ida_get_disasm(address), address, assembly)) 1861 | 1862 | length = self.kp_asm.patch_code(address, raw_assembly, syntax, padding, comment, None) 1863 | if length > 0: 1864 | # update start address pointing to the next instruction 1865 | init_assembly = None 1866 | address += length 1867 | else: 1868 | init_assembly = f.c_assembly.value 1869 | if length == 0: 1870 | warning("ERROR: Keypatch found invalid assembly [{0}]".format(assembly)) 1871 | elif length == -1: 1872 | warning("ERROR: Keypatch failed to patch binary at 0x{0:X}!".format(address)) 1873 | elif length == -2: 1874 | warning("ERROR: Keypatch can't read original data at 0x{0:X}, try again".format(address)) 1875 | 1876 | except KsError as e: 1877 | print("Keypatch Error: {0}".format(e)) 1878 | else: # Cancel 1879 | break 1880 | f.Free() 1881 | 1882 | # handler for Fill Range menu 1883 | def fill_range(self): 1884 | # be sure that this arch is supported by Keystone 1885 | if self.kp_asm.arch is None: 1886 | warning("ERROR: Keypatch cannot handle this architecture (unsupported by Keystone), quit!") 1887 | return 1888 | 1889 | selection, addr_begin, addr_end = read_range_selection() 1890 | if not selection: 1891 | warning("ERROR: Keypatch requires a range to be selected for fill in, try again") 1892 | return 1893 | 1894 | if self.opts is None: 1895 | self.load_configuration() 1896 | 1897 | init_assembly = None 1898 | f = Keypatch_FillRange(self.kp_asm, addr_begin, addr_end, assembly=init_assembly, opts=self.opts) 1899 | ok = f.Execute() 1900 | if ok == 1: 1901 | try: 1902 | syntax = None 1903 | if f.kp_asm.arch == KS_ARCH_X86: 1904 | syntax_id = f.c_syntax.value 1905 | syntax = self.kp_asm.get_syntax_by_idx(syntax_id) 1906 | 1907 | assembly = f.c_assembly.value 1908 | self.opts = f.get_opts('c_opt_chk') 1909 | padding = (self.opts.get("c_opt_padding", 0) != 0) 1910 | comment = (self.opts.get("c_opt_comment", 0) != 0) 1911 | 1912 | raw_assembly = self.kp_asm.ida_resolve(assembly, addr_begin) 1913 | 1914 | print("Keypatch: attempting to fill range [0x{0:X}:0x{1:X}] with \"{2}\"".format( 1915 | addr_begin, addr_end - 1, assembly)) 1916 | 1917 | length = self.kp_asm.fill_code(addr_begin, addr_end, raw_assembly, syntax, padding, comment, None) 1918 | if length == 0: 1919 | warning("ERROR: Keypatch failed to process this input.") 1920 | print("Keypatch: FAILED to process this input '{0}'".format(assembly)) 1921 | elif length == -1: 1922 | warning("ERROR: Keypatch failed to patch binary at 0x{0:X}!".format(addr_begin)) 1923 | 1924 | except KsError as e: 1925 | print("Keypatch Error: {0}".format(e)) 1926 | 1927 | # free this form 1928 | f.Free() 1929 | 1930 | def run(self, arg): 1931 | self.patcher() 1932 | 1933 | 1934 | # register IDA plugin 1935 | def PLUGIN_ENTRY(): 1936 | return Keypatch_Plugin_t() 1937 | --------------------------------------------------------------------------------