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