commit 33f8d22f280692bf2714f6fc6d763857fcd6a757 Author: Boris Batteux Date: Thu Oct 29 11:09:07 2020 +0100 Initial commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..8dc9798 --- /dev/null +++ b/.gitignore @@ -0,0 +1,7 @@ +**/__pycache__/ +**/.idea/ +**/.vscode/ +**/venv/ +**~ +**.pyc +**.log diff --git a/AUTHORS.md b/AUTHORS.md new file mode 100644 index 0000000..a235d15 --- /dev/null +++ b/AUTHORS.md @@ -0,0 +1,3 @@ +# Authors list + +- Boris Batteux diff --git a/D810.py b/D810.py new file mode 100644 index 0000000..fffaee1 --- /dev/null +++ b/D810.py @@ -0,0 +1,75 @@ +import os +import idaapi +import ida_hexrays +import ida_kernwin + + +from d810.conf import D810Configuration +from d810.manager import D810State, D810_LOG_DIR_NAME +from d810.log import configure_loggers, clear_logs + + +class D810Plugin(idaapi.plugin_t): + # variables required by IDA + flags = 0 # normal plugin + wanted_name = "D-810" + wanted_hotkey = "Ctrl-Shift-D" + comment = "Interface to the D-810 plugin" + help = "" + initialized = False + + def __init__(self): + super(D810Plugin, self).__init__() + self.d810_config = None + self.state = None + self.initialized = False + + + def reload_plugin(self): + if self.initialized: + self.term() + + self.d810_config = D810Configuration() + + #TO-DO: if [...].get raises an exception because log_dir is not found, handle exception + real_log_dir = os.path.join(self.d810_config.get("log_dir"), D810_LOG_DIR_NAME) + + #TO-DO: if [...].get raises an exception because erase_logs_on_reload is not found, handle exception + if self.d810_config.get("erase_logs_on_reload"): + clear_logs(real_log_dir) + + configure_loggers(real_log_dir) + self.state = D810State(self.d810_config) + print("D-810 reloading...") + self.state.start_plugin() + self.initialized = True + + + # IDA API methods: init, run, term + def init(self): + if not ida_hexrays.init_hexrays_plugin(): + print("D-810 need Hex-Rays decompiler. Skipping") + return idaapi.PLUGIN_SKIP + + kv = ida_kernwin.get_kernel_version().split(".") + if (int(kv[0]) < 7) or (int(kv[1]) < 5): + print("D-810 need IDA version >= 7.5. Skipping") + return idaapi.PLUGIN_SKIP + + return idaapi.PLUGIN_OK + + + def run(self, args): + self.reload_plugin() + + + def term(self): + print("Terminating D-810...") + if self.state is not None: + self.state.stop_plugin() + + self.initialized = False + + +def PLUGIN_ENTRY(): + return D810Plugin() diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..9c3b923 --- /dev/null +++ b/LICENSE @@ -0,0 +1,661 @@ + GNU AFFERO GENERAL PUBLIC LICENSE + Version 3, 19 November 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU Affero General Public License is a free, copyleft license for +software and other kinds of works, specifically designed to ensure +cooperation with the community in the case of network server software. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +our General Public Licenses are intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + Developers that use our General Public Licenses protect your rights +with two steps: (1) assert copyright on the software, and (2) offer +you this License which gives you legal permission to copy, distribute +and/or modify the software. + + A secondary benefit of defending all users' freedom is that +improvements made in alternate versions of the program, if they +receive widespread use, become available for other developers to +incorporate. Many developers of free software are heartened and +encouraged by the resulting cooperation. However, in the case of +software used on network servers, this result may fail to come about. +The GNU General Public License permits making a modified version and +letting the public access it on a server without ever releasing its +source code to the public. + + The GNU Affero General Public License is designed specifically to +ensure that, in such cases, the modified source code becomes available +to the community. It requires the operator of a network server to +provide the source code of the modified version running there to the +users of that server. Therefore, public use of a modified version, on +a publicly accessible server, gives the public access to the source +code of the modified version. + + An older license, called the Affero General Public License and +published by Affero, was designed to accomplish similar goals. This is +a different license, not a version of the Affero GPL, but Affero has +released a new version of the Affero GPL which permits relicensing under +this license. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU Affero General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Remote Network Interaction; Use with the GNU General Public License. + + Notwithstanding any other provision of this License, if you modify the +Program, your modified version must prominently offer all users +interacting with it remotely through a computer network (if your version +supports such interaction) an opportunity to receive the Corresponding +Source of your version by providing access to the Corresponding Source +from a network server at no charge, through some standard or customary +means of facilitating copying of software. This Corresponding Source +shall include the Corresponding Source for any work covered by version 3 +of the GNU General Public License that is incorporated pursuant to the +following paragraph. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the work with which it is combined will remain governed by version +3 of the GNU General Public License. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU Affero General Public License from time to time. Such new versions +will be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU Affero General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU Affero General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU Affero General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published + by the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If your software can interact with users remotely through a computer +network, you should also make sure that it provides a way for users to +get its source. For example, if your program is a web application, its +interface could display a "Source" link that leads users to an archive +of the code. There are many ways you could offer source, and different +solutions will be better for different programs; see section 13 for the +specific requirements. + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU AGPL, see +. diff --git a/README.md b/README.md new file mode 100644 index 0000000..752ef89 --- /dev/null +++ b/README.md @@ -0,0 +1,67 @@ +# Introduction + +## What is D-810 + +D-810 is an IDA Pro plugin which can be used to deobfuscate code at decompilation time by modifying IDA Pro microcode. +It was designed with the following goals in mind: + +* It should have as least as possible impact on our standard reverse engineering workflow + * Fully integrated to IDA Pro +* It should be easily extensible and configurable + * Fast creation of new deobfuscation rules + * Configurable so that we don't have to modify the source code to use rules for a specific project +* Performance impact should be reasonable + * Our goal is to be transparent for the reverse engineer + * But we don't care if the decompilation of a function takes 1 more second if the resulting code is much more simplier. + + +# Installation + +**Only IDA v7.5 or later is supported** (since we need the microcode Python API) + +Copy this repository in `.idapro/plugins` + +We recommend to install Z3 to be able to use several features of D-810: +```bash +pip3 install z3-solver +``` + +# Using D-810 + +* Load the plugin by using the `Ctrl-Shift-D` shortcut, you should see this configuration GUI + +![](d810/docs/source/images/gui_plugin_configuration.png) + +* Choose or create your project configuration + * If you are not sure what to do here, leave *default_instruction_only.json*. +* Click on the `Start` button to enable deobfuscation +* Decompile an obfuscated function, the code should be simplified (hopefully) +* When you want to disable deobfuscation, just click on the `Stop` button. + +# Warnings + +This plugin is still in early stage of development, so issues ~~may~~ will happen. + + * Modifying incorrectly IDA microcode may lead IDA to crash. We try to detect that as much as possible to avoid crash, but since it may still happen **save you IDA database often** + * We only tested this plugin on Linux, but it should work on Windows too. + +# Documentation + +Work in progress + +Currently, you can read our [blog post](https://eshard.com/posts/) to get some information. + + +# Licenses + +This library is licensed under LGPL V3 license. See the [LICENSE](LICENSE) file for details. + +## Authors + +See [AUTHORS](AUTHORS.md) for the list of contributors to the project. + +# Acknowledgement + +Rolf Rolles for the huge work he has done with his [HexRaysDeob plugin](https://github.com/RolfRolles/HexRaysDeob) and all the information about Hex-Rays microcode internals described in his [blog post](https://www.hex-rays.com/blog/hex-rays-microcode-api-vs-obfuscating-compiler/). We are still using some part of his plugin in D-810. + +Dennis Elser for the [genmc plugin](https://github.com/patois/genmc) plugin which was very helpful for debugging D-810 errors. diff --git a/d810/__init__.py b/d810/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/d810/ast.py b/d810/ast.py new file mode 100644 index 0000000..4f21e43 --- /dev/null +++ b/d810/ast.py @@ -0,0 +1,605 @@ +from __future__ import annotations +import logging +from typing import List, Union, Dict, Tuple + +from ida_hexrays import * + +from d810.utils import unsigned_to_signed, signed_to_unsigned, \ + get_add_cf, get_add_of, get_sub_of, get_parity_flag +from d810.hexrays_helpers import OPCODES_INFO, MBA_RELATED_OPCODES, Z3_SPECIAL_OPERANDS, MINSN_TO_AST_FORBIDDEN_OPCODES, \ + equal_mops_ignore_size, AND_TABLE +from d810.hexrays_formatters import format_minsn_t, format_mop_t +from d810.errors import AstEvaluationException + +logger = logging.getLogger('D810') + + +def check_and_add_to_list(new_ast: Union[AstNode, AstLeaf], known_ast_list: List[Union[AstNode, AstLeaf]]): + is_new_ast_known = False + for existing_elt in known_ast_list: + if equal_mops_ignore_size(new_ast.mop, existing_elt.mop): + new_ast.ast_index = existing_elt.ast_index + is_new_ast_known = True + break + + if not is_new_ast_known: + ast_index = len(known_ast_list) + new_ast.ast_index = ast_index + known_ast_list.append(new_ast) + + +def mop_to_ast_internal(mop: mop_t, ast_list: List[Union[AstNode, AstLeaf]]) -> Union[None, AstNode, AstLeaf]: + if mop is None: + return None + + if mop.t != mop_d or (mop.d.opcode not in MBA_RELATED_OPCODES): + tree = AstLeaf(format_mop_t(mop)) + tree.mop = mop + dest_size = mop.size if mop.t != mop_d else mop.d.d.size + tree.dest_size = dest_size + else: + left_ast = mop_to_ast_internal(mop.d.l, ast_list) + right_ast = mop_to_ast_internal(mop.d.r, ast_list) + dst_ast = mop_to_ast_internal(mop.d.d, ast_list) + tree = AstNode(mop.d.opcode, left_ast, right_ast, dst_ast) + tree.mop = mop + tree.dest_size = mop.d.d.size + tree.ea = mop.d.ea + + check_and_add_to_list(tree, ast_list) + return tree + + +def mop_to_ast(mop: mop_t) -> Union[None, AstNode, AstLeaf]: + mop_ast = mop_to_ast_internal(mop, []) + mop_ast.compute_sub_ast() + return mop_ast + + +def minsn_to_ast(instruction: minsn_t) -> Union[None, AstNode, AstLeaf]: + try: + if instruction.opcode in MINSN_TO_AST_FORBIDDEN_OPCODES: + # To avoid error 50278 + return None + + ins_mop = mop_t() + ins_mop.create_from_insn(instruction) + + if instruction.opcode == m_mov: + tmp = AstNode(m_mov, mop_to_ast(ins_mop)) + tmp.mop = ins_mop + tmp.dest_size = instruction.d.size + tmp.ea = instruction.ea + tmp.dst_mop = instruction.d + return tmp + + tmp = mop_to_ast(ins_mop) + tmp.dst_mop = instruction.d + return tmp + except RuntimeError as e: + logger.error("Error while transforming instruction {0}: {1}".format(format_minsn_t(instruction), e)) + return None + + +class AstInfo(object): + def __init__(self, ast: Union[AstNode, AstLeaf], number_of_use: int): + self.ast = ast + self.number_of_use = number_of_use + + def __str__(self): + return "{0} used {1} times: {2}".format(self.ast, self.number_of_use, format_mop_t(self.ast.mop)) + + +class AstNode(dict): + def __init__(self, opcode, left=None, right=None, dst=None): + super(dict, self).__init__() + self.opcode = opcode + self.left = left + self.right = right + self.dst = dst + self.dst_mop = None + + self.opcodes = [] + self.mop = None + self.is_candidate_ok = False + + self.leafs = [] + self.leafs_by_name = {} + + self.ast_index = 0 + self.sub_ast_info_by_index = {} + + self.dest_size = None + self.ea = None + + @property + def size(self): + return self.mop.d.d.size + + def compute_sub_ast(self): + self.sub_ast_info_by_index = {} + self.sub_ast_info_by_index[self.ast_index] = AstInfo(self, 1) + + if self.left is not None: + self.left.compute_sub_ast() + for ast_index, ast_info in self.left.sub_ast_info_by_index.items(): + if ast_index not in self.sub_ast_info_by_index.keys(): + self.sub_ast_info_by_index[ast_index] = AstInfo(ast_info.ast, 0) + self.sub_ast_info_by_index[ast_index].number_of_use += ast_info.number_of_use + + if self.right is not None: + self.right.compute_sub_ast() + for ast_index, ast_info in self.right.sub_ast_info_by_index.items(): + if ast_index not in self.sub_ast_info_by_index.keys(): + self.sub_ast_info_by_index[ast_index] = AstInfo(ast_info.ast, 0) + self.sub_ast_info_by_index[ast_index].number_of_use += ast_info.number_of_use + + def get_information(self): + leaf_info_list = [] + cst_list = [] + opcode_list = [] + self.compute_sub_ast() + + for _, ast_info in self.sub_ast_info_by_index.items(): + if (ast_info.ast.mop is not None) and (ast_info.ast.mop.t != mop_z): + if ast_info.ast.is_leaf(): + if ast_info.ast.is_constant(): + cst_list.append(ast_info.ast.mop.nnn.value) + else: + leaf_info_list.append(ast_info) + else: + opcode_list += [ast_info.ast.opcode] * ast_info.number_of_use + + return leaf_info_list, cst_list, opcode_list + + def __getitem__(self, k) -> AstLeaf: + return self.leafs_by_name[k] + + def get_leaf_list(self) -> List[AstLeaf]: + leafs = [] + if self.left is not None: + leafs += self.left.get_leaf_list() + if self.right is not None: + leafs += self.right.get_leaf_list() + return leafs + + def is_leaf(self) -> bool: + # An AstNode is not a leaf, so returns False + return False + + def add_leaf(self, leaf_name: str, leaf_mop: mop_t): + leaf = AstLeaf(leaf_name) + leaf.mop = leaf_mop + self.leafs.append(leaf) + self.leafs_by_name[leaf_name] = leaf + + def add_constant_leaf(self, leaf_name: str, cst_value: int, cst_size: int): + cst_mop = mop_t() + cst_mop.make_number(cst_value & AND_TABLE[cst_size], cst_size) + self.add_leaf(leaf_name, cst_mop) + + def check_pattern_and_copy_mops(self, ast: Union[AstNode, AstLeaf]) -> bool: + self.reset_mops() + is_matching_shape = self._copy_mops_from_ast(ast) + if not is_matching_shape: + return False + return self._check_implicit_equalities() + + def reset_mops(self): + self.mop = None + if self.left is not None: + self.left.reset_mops() + if self.right is not None: + self.right.reset_mops() + + def _copy_mops_from_ast(self, other: Union[AstNode, AstLeaf]) -> bool: + self.mop = other.mop + self.dst_mop = other.dst_mop + self.dest_size = other.dest_size + self.ea = other.ea + + if not isinstance(other, AstNode): + return False + if self.opcode != other.opcode: + return False + if self.left is not None: + if not self.left._copy_mops_from_ast(other.left): + return False + if self.right is not None: + if not self.right._copy_mops_from_ast(other.right): + return False + return True + + def _check_implicit_equalities(self) -> bool: + self.leafs = self.get_leaf_list() + self.leafs_by_name = {} + self.is_candidate_ok = True + + for leaf in self.leafs: + ref_leaf = self.leafs_by_name.get(leaf.name) + if ref_leaf is not None: + if not equal_mops_ignore_size(ref_leaf.mop, leaf.mop): + self.is_candidate_ok = False + self.leafs_by_name[leaf.name] = leaf + return self.is_candidate_ok + + def update_leafs_mop(self, other: Union[AstNode, AstLeaf], other2: Union[None, AstNode, AstLeaf] = None) -> bool: + self.leafs = self.get_leaf_list() + all_leafs_found = True + for leaf in self.leafs: + if leaf.name in other.leafs_by_name.keys(): + leaf.mop = other.leafs_by_name[leaf.name].mop + elif (other2 is not None) and (leaf.name in other2.leafs_by_name.keys()): + leaf.mop = other2.leafs_by_name[leaf.name].mop + else: + all_leafs_found = False + return all_leafs_found + + def create_mop(self, ea: int) -> mop_t: + new_ins = self.create_minsn(ea) + new_ins_mop = mop_t() + new_ins_mop.create_from_insn(new_ins) + return new_ins_mop + + def create_minsn(self, ea: int, dest=None) -> minsn_t: + new_ins = minsn_t(ea) + new_ins.opcode = self.opcode + + if self.left is not None: + new_ins.l = self.left.create_mop(ea) + if self.right is not None: + new_ins.r = self.right.create_mop(ea) + + new_ins.d = mop_t() + + if self.left is not None: + new_ins.d.size = new_ins.l.size + if dest is not None: + new_ins.d = dest + return new_ins + + def get_pattern(self) -> str: + nb_operands = OPCODES_INFO[self.opcode]["nb_operands"] + if nb_operands == 0: + return "AstNode({0})".format(OPCODES_INFO[self.opcode]["name"]) + elif nb_operands == 1: + return "AstNode(m_{0}, {1})".format(OPCODES_INFO[self.opcode]["name"], self.left.get_pattern()) + elif nb_operands == 2: + return "AstNode(m_{0}, {1}, {2})" \ + .format(OPCODES_INFO[self.opcode]["name"], self.left.get_pattern(), self.right.get_pattern()) + + def evaluate_with_leaf_info(self, leafs_info, leafs_value): + dict_index_to_value = {leaf_info.ast.ast_index: leaf_value for leaf_info, leaf_value in + zip(leafs_info, leafs_value)} + res = self.evaluate(dict_index_to_value) + return res + + def evaluate(self, dict_index_to_value): + if self.ast_index in dict_index_to_value: + return dict_index_to_value[self.ast_index] + res_mask = AND_TABLE[self.dest_size] + if self.opcode == m_mov: + return (self.left.evaluate(dict_index_to_value)) & res_mask + elif self.opcode == m_neg: + return (- self.left.evaluate(dict_index_to_value)) & res_mask + elif self.opcode == m_lnot: + return self.left.evaluate(dict_index_to_value) != 0 + elif self.opcode == m_bnot: + return (self.left.evaluate(dict_index_to_value) ^ res_mask) & res_mask + elif self.opcode == m_xds: + left_value_signed = unsigned_to_signed(self.left.evaluate(dict_index_to_value), self.left.dest_size) + return signed_to_unsigned(left_value_signed, self.dest_size) & res_mask + elif self.opcode == m_xdu: + return (self.left.evaluate(dict_index_to_value)) & res_mask + elif self.opcode == m_low: + return (self.left.evaluate(dict_index_to_value)) & res_mask + elif self.opcode == m_add: + return (self.left.evaluate(dict_index_to_value) + self.right.evaluate(dict_index_to_value)) & res_mask + elif self.opcode == m_sub: + return (self.left.evaluate(dict_index_to_value) - self.right.evaluate(dict_index_to_value)) & res_mask + elif self.opcode == m_mul: + return (self.left.evaluate(dict_index_to_value) * self.right.evaluate(dict_index_to_value)) & res_mask + elif self.opcode == m_udiv: + return (self.left.evaluate(dict_index_to_value) // self.right.evaluate(dict_index_to_value)) & res_mask + elif self.opcode == m_sdiv: + return (self.left.evaluate(dict_index_to_value) // self.right.evaluate(dict_index_to_value)) & res_mask + elif self.opcode == m_umod: + return (self.left.evaluate(dict_index_to_value) % self.right.evaluate(dict_index_to_value)) & res_mask + elif self.opcode == m_smod: + return (self.left.evaluate(dict_index_to_value) % self.right.evaluate(dict_index_to_value)) & res_mask + elif self.opcode == m_or: + return (self.left.evaluate(dict_index_to_value) | self.right.evaluate(dict_index_to_value)) & res_mask + elif self.opcode == m_and: + return (self.left.evaluate(dict_index_to_value) & self.right.evaluate(dict_index_to_value)) & res_mask + elif self.opcode == m_xor: + return (self.left.evaluate(dict_index_to_value) ^ self.right.evaluate(dict_index_to_value)) & res_mask + elif self.opcode == m_shl: + return (self.left.evaluate(dict_index_to_value) << self.right.evaluate(dict_index_to_value)) & res_mask + elif self.opcode == m_shr: + return (self.left.evaluate(dict_index_to_value) >> self.right.evaluate(dict_index_to_value)) & res_mask + elif self.opcode == m_sar: + left_value_signed = unsigned_to_signed(self.left.evaluate(dict_index_to_value), self.left.dest_size) + res_signed = left_value_signed >> self.right.evaluate(dict_index_to_value) + return signed_to_unsigned(res_signed, self.dest_size) & res_mask + elif self.opcode == m_cfadd: + tmp = get_add_cf(self.left.evaluate(dict_index_to_value), self.right.evaluate(dict_index_to_value), + self.left.dest_size) + return tmp & res_mask + elif self.opcode == m_ofadd: + tmp = get_add_of(self.left.evaluate(dict_index_to_value), self.right.evaluate(dict_index_to_value), + self.left.dest_size) + return tmp & res_mask + elif self.opcode == m_sets: + left_value_signed = unsigned_to_signed(self.left.evaluate(dict_index_to_value), self.left.dest_size) + res = 1 if left_value_signed < 0 else 0 + return res & res_mask + elif self.opcode == m_seto: + left_value_signed = unsigned_to_signed(self.left.evaluate(dict_index_to_value), self.left.dest_size) + right_value_signed = unsigned_to_signed(self.right.evaluate(dict_index_to_value), self.right.dest_size) + sub_overflow = get_sub_of(left_value_signed, right_value_signed, self.left.dest_size) + return sub_overflow & res_mask + elif self.opcode == m_setnz: + res = 1 if self.left.evaluate(dict_index_to_value) != self.right.evaluate(dict_index_to_value) else 0 + return res & res_mask + elif self.opcode == m_setz: + res = 1 if self.left.evaluate(dict_index_to_value) == self.right.evaluate(dict_index_to_value) else 0 + return res & res_mask + elif self.opcode == m_setae: + res = 1 if self.left.evaluate(dict_index_to_value) >= self.right.evaluate(dict_index_to_value) else 0 + return res & res_mask + elif self.opcode == m_setb: + res = 1 if self.left.evaluate(dict_index_to_value) < self.right.evaluate(dict_index_to_value) else 0 + return res & res_mask + elif self.opcode == m_seta: + res = 1 if self.left.evaluate(dict_index_to_value) > self.right.evaluate(dict_index_to_value) else 0 + return res & res_mask + elif self.opcode == m_setbe: + res = 1 if self.left.evaluate(dict_index_to_value) <= self.right.evaluate(dict_index_to_value) else 0 + return res & res_mask + elif self.opcode == m_setg: + left_value_signed = unsigned_to_signed(self.left.evaluate(dict_index_to_value), self.left.dest_size) + right_value_signed = unsigned_to_signed(self.right.evaluate(dict_index_to_value), self.right.dest_size) + res = 1 if left_value_signed > right_value_signed else 0 + return res & res_mask + elif self.opcode == m_setge: + left_value_signed = unsigned_to_signed(self.left.evaluate(dict_index_to_value), self.left.dest_size) + right_value_signed = unsigned_to_signed(self.right.evaluate(dict_index_to_value), self.right.dest_size) + res = 1 if left_value_signed >= right_value_signed else 0 + return res & res_mask + elif self.opcode == m_setl: + left_value_signed = unsigned_to_signed(self.left.evaluate(dict_index_to_value), self.left.dest_size) + right_value_signed = unsigned_to_signed(self.right.evaluate(dict_index_to_value), self.right.dest_size) + res = 1 if left_value_signed < right_value_signed else 0 + return res & res_mask + elif self.opcode == m_setle: + left_value_signed = unsigned_to_signed(self.left.evaluate(dict_index_to_value), self.left.dest_size) + right_value_signed = unsigned_to_signed(self.right.evaluate(dict_index_to_value), self.right.dest_size) + res = 1 if left_value_signed <= right_value_signed else 0 + return res & res_mask + elif self.opcode == m_setp: + res = get_parity_flag(self.left.evaluate(dict_index_to_value), self.right.evaluate(dict_index_to_value), + self.left.dest_size) + return res & res_mask + else: + raise AstEvaluationException("Can't evaluate opcode: {0}".format(self.opcode)) + + def get_depth_signature(self, depth): + if depth == 1: + return ["{0}".format(self.opcode)] + tmp = [] + nb_operands = OPCODES_INFO[self.opcode]["nb_operands"] + if (nb_operands >= 1) and self.left is not None: + tmp += self.left.get_depth_signature(depth - 1) + else: + tmp += ["N"] * (2 ** (depth - 2)) + if (nb_operands >= 2) and self.right is not None: + tmp += self.right.get_depth_signature(depth - 1) + else: + tmp += ["N"] * (2 ** (depth - 2)) + return tmp + + def __str__(self): + try: + nb_operands = OPCODES_INFO[self.opcode]["nb_operands"] + if "symbol" in OPCODES_INFO[self.opcode].keys(): + if nb_operands == 0: + return "{0}()".format(OPCODES_INFO[self.opcode]["symbol"]) + elif nb_operands == 1: + return "{0}({1})".format(OPCODES_INFO[self.opcode]["symbol"], self.left) + elif nb_operands == 2: + if OPCODES_INFO[self.opcode]["symbol"] not in Z3_SPECIAL_OPERANDS: + return "({1} {0} {2})".format(OPCODES_INFO[self.opcode]["symbol"], self.left, self.right) + else: + return "{0}({1}, {2})".format(OPCODES_INFO[self.opcode]["symbol"], self.left, self.right) + else: + if nb_operands == 0: + return "{0}()".format(OPCODES_INFO[self.opcode]["name"]) + elif nb_operands == 1: + return "{0}({1})".format(OPCODES_INFO[self.opcode]["name"], self.left) + elif nb_operands == 2: + return "{0}({1}, {2})".format(OPCODES_INFO[self.opcode]["name"], self.left, self.right) + return "Error_AstNode" + except RuntimeError as e: + logger.info("Error while calling __str__ on AstNode: {0}".format(e)) + return "Error_AstNode" + + +class AstLeaf(object): + def __init__(self, name): + self.name = name + self.ast_index = None + + self.mop = None + self.z3_var = None + self.z3_var_name = None + + self.dest_size = None + self.ea = None + + self.sub_ast_info_by_index = {} + + def __getitem__(self, name): + if name == self.name: + return self + raise KeyError + + @property + def size(self): + return self.mop.size + + @property + def dst_mop(self): + return self.mop + + @dst_mop.setter + def dst_mop(self, mop): + self.mop = mop + + @property + def value(self): + if self.is_constant(): + return self.mop.nnn.value + else: + return None + + def compute_sub_ast(self): + self.sub_ast_info_by_index = {} + self.sub_ast_info_by_index[self.ast_index] = AstInfo(self, 1) + + def get_information(self): + # Just here to allow calling get_information on either a AstNode or AstLeaf + return [], [], [] + + def get_leaf_list(self): + return [self] + + def is_leaf(self): + return True + + def is_constant(self): + if self.mop is None: + return False + return self.mop.t == mop_n + + def create_mop(self, ea): + # Currently, we are not creating a new mop but returning the one defined + return self.mop + + def update_leafs_mop(self, other, other2=None): + if self.name in other.leafs_by_name.keys(): + self.mop = other.leafs_by_name[self.name].mop + return True + elif (other2 is not None) and (self.name in other2.leafs_by_name.keys()): + self.mop = other2.leafs_by_name[self.name].mop + return True + return False + + def check_pattern_and_copy_mops(self, ast): + self.reset_mops() + is_matching_shape = self._copy_mops_from_ast(ast) + + if not is_matching_shape: + return False + return self._check_implicit_equalities() + + def reset_mops(self): + self.z3_var = None + self.z3_var_name = None + self.mop = None + + def _copy_mops_from_ast(self, other): + self.mop = other.mop + return True + + @staticmethod + def _check_implicit_equalities(): + # An AstLeaf does not have any implicit equalities to be checked, so we always returns True + return True + + def get_pattern(self): + if self.is_constant(): + return "AstConstant('{0}', {0})".format(self.mop.nnn.value) + if self.ast_index is not None: + return "AstLeaf('x_{0}')".format(self.ast_index) + if self.name is not None: + return "AstLeaf('{0}')".format(self.name) + + def evaluate_with_leaf_info(self, leafs_info, leafs_value): + dict_index_to_value = {leaf_info.ast.ast_index: leaf_value for leaf_info, leaf_value in + zip(leafs_info, leafs_value)} + res = self.evaluate(dict_index_to_value) + return res + + def evaluate(self, dict_index_to_value): + if self.is_constant(): + return self.mop.nnn.value + return dict_index_to_value.get(self.ast_index) + + def get_depth_signature(self, depth): + if depth == 1: + if self.is_constant(): + return ["C"] + return ["L"] + else: + return ["N"] * (2 ** (depth - 1)) + + def __str__(self): + try: + if self.is_constant(): + return "{0}".format(self.mop.nnn.value) + if self.z3_var_name is not None: + return self.z3_var_name + if self.ast_index is not None: + return "x_{0}".format(self.ast_index) + if self.mop is not None: + return format_mop_t(self.mop) + return self.name + except RuntimeError as e: + logger.info("Error while calling __str__ on AstLeaf: {0}".format(e)) + return "Error_AstLeaf" + + +class AstConstant(AstLeaf): + def __init__(self, name, expected_value=None, expected_size=None): + super().__init__(name) + self.expected_value = expected_value + self.expected_size = expected_size + + @property + def value(self): + return self.mop.nnn.value + + def is_constant(self): + # An AstConstant is always constant, so return True + return True + + def _copy_mops_from_ast(self, other): + if other.mop is not None and other.mop.t != mop_n: + return False + + self.mop = other.mop + if self.expected_value is None: + return True + return self.expected_value == other.mop.nnn.value + + def evaluate(self, dict_index_to_value=None): + if self.mop is not None and self.mop.t == mop_n: + return self.mop.nnn.value + return self.expected_value + + def get_depth_signature(self, depth): + if depth == 1: + return ["C"] + else: + return ["N"] * (2 ** (depth - 1)) + + def __str__(self): + try: + if self.mop is not None and self.mop.t == mop_n: + return "0x{0:x}".format(self.mop.nnn.value) + if self.expected_value is not None: + return "0x{0:x}".format(self.expected_value) + return self.name + except RuntimeError as e: + logger.info("Error while calling __str__ on AstConstant: {0}".format(e)) + return "Error_AstConstant" diff --git a/d810/cfg_utils.py b/d810/cfg_utils.py new file mode 100644 index 0000000..4f31f50 --- /dev/null +++ b/d810/cfg_utils.py @@ -0,0 +1,473 @@ +import logging +from ida_hexrays import * +from typing import List, Union, Dict, Tuple + +from d810.errors import ControlFlowException +from d810.hexrays_helpers import CONDITIONAL_JUMP_OPCODES +from d810.hexrays_formatters import block_printer + + +helper_logger = logging.getLogger('D810.helper') + + +def log_block_info(blk: mblock_t, logger_func=helper_logger.info): + if blk is None: + logger_func("Block is None") + return + vp = block_printer() + blk._print(vp) + logger_func("Block {0} with successors {1} and predecessors {2}:\n{3}" + .format(blk.serial, [x for x in blk.succset], [x for x in blk.predset], vp.get_block_mc())) + + +def insert_goto_instruction(blk: mblock_t, goto_blk_serial: int, nop_previous_instruction=False): + if blk.tail is not None: + goto_ins = minsn_t(blk.tail) + else: + goto_ins = minsn_t(blk.start) + + if nop_previous_instruction: + blk.make_nop(blk.tail) + blk.insert_into_block(goto_ins, blk.tail) + + # We nop instruction before setting it to goto to avoid error 52123 + blk.make_nop(blk.tail) + goto_ins.opcode = m_goto + goto_ins.l = mop_t() + goto_ins.l.make_blkref(goto_blk_serial) + + +def change_1way_call_block_successor(call_blk: mblock_t, call_blk_successor_serial: int) -> bool: + if call_blk.nsucc() != 1: + return False + + mba = call_blk.mba + previous_call_blk_successor_serial = call_blk.succset[0] + previous_call_blk_successor = mba.get_mblock(previous_call_blk_successor_serial) + + nop_blk = insert_nop_blk(call_blk) + insert_goto_instruction(nop_blk, call_blk_successor_serial, nop_previous_instruction=True) + is_ok = change_1way_block_successor(nop_blk, call_blk_successor_serial) + if not is_ok: + return False + + # Bookkeeping + call_blk.succset._del(previous_call_blk_successor_serial) + call_blk.succset.push_back(nop_blk.serial) + call_blk.mark_lists_dirty() + + previous_call_blk_successor.predset._del(call_blk.serial) + if previous_call_blk_successor.serial != mba.qty - 1: + previous_call_blk_successor.mark_lists_dirty() + + mba.mark_chains_dirty() + try: + mba.verify(True) + return True + except RuntimeError as e: + helper_logger.error("Error in change_1way_block_successor: {0}".format(e)) + log_block_info(call_blk, helper_logger.error) + log_block_info(nop_blk, helper_logger.error) + raise e + + +def change_1way_block_successor(blk: mblock_t, blk_successor_serial: int) -> bool: + if blk.nsucc() != 1: + return False + + mba: mbl_array_t = blk.mba + previous_blk_successor_serial = blk.succset[0] + previous_blk_successor = mba.get_mblock(previous_blk_successor_serial) + + if blk.tail is None: + # We add a goto instruction + insert_goto_instruction(blk, blk_successor_serial, nop_previous_instruction=False) + elif blk.tail.opcode == m_goto: + # We change goto target directly + blk.tail.l.make_blkref(blk_successor_serial) + elif blk.tail.opcode == m_ijmp: + # We replace ijmp instruction with goto instruction + insert_goto_instruction(blk, blk_successor_serial, nop_previous_instruction=True) + elif blk.tail.opcode == m_call: + # Before maturity MMAT_CALLS, we can't add a goto after a call instruction + if mba.maturity < MMAT_CALLS: + return change_1way_call_block_successor(blk, blk_successor_serial) + else: + insert_goto_instruction(blk, blk_successor_serial, nop_previous_instruction=False) + else: + # We add a goto instruction + insert_goto_instruction(blk, blk_successor_serial, nop_previous_instruction=False) + + # Update block properties + blk.type = BLT_1WAY + blk.flags |= MBL_GOTO + + # Bookkeeping + blk.succset._del(previous_blk_successor_serial) + blk.succset.push_back(blk_successor_serial) + blk.mark_lists_dirty() + + previous_blk_successor.predset._del(blk.serial) + if previous_blk_successor.serial != mba.qty - 1: + previous_blk_successor.mark_lists_dirty() + + new_blk_successor = blk.mba.get_mblock(blk_successor_serial) + new_blk_successor.predset.push_back(blk.serial) + + if new_blk_successor.serial != mba.qty - 1: + new_blk_successor.mark_lists_dirty() + + mba.mark_chains_dirty() + try: + mba.verify(True) + return True + except RuntimeError as e: + helper_logger.error("Error in change_1way_block_successor: {0}".format(e)) + log_block_info(blk, helper_logger.error) + log_block_info(new_blk_successor, helper_logger.error) + log_block_info(previous_blk_successor, helper_logger.error) + raise e + + +def change_0way_block_successor(blk: mblock_t, blk_successor_serial: int) -> bool: + if blk.nsucc() != 0: + return False + mba = blk.mba + + if blk.tail.opcode == m_ijmp: + # We replace ijmp instruction with goto instruction + insert_goto_instruction(blk, blk_successor_serial, nop_previous_instruction=True) + else: + # We add a goto instruction + insert_goto_instruction(blk, blk_successor_serial, nop_previous_instruction=False) + + # Update block properties + blk.type = BLT_1WAY + blk.flags |= MBL_GOTO + + # Bookkeeping + blk.succset.push_back(blk_successor_serial) + blk.mark_lists_dirty() + + new_blk_successor = blk.mba.get_mblock(blk_successor_serial) + new_blk_successor.predset.push_back(blk.serial) + if new_blk_successor.serial != mba.qty - 1: + new_blk_successor.mark_lists_dirty() + + mba.mark_chains_dirty() + try: + mba.verify(True) + return True + except RuntimeError as e: + helper_logger.error("Error in change_0way_block_successor: {0}".format(e)) + log_block_info(blk, helper_logger.error) + log_block_info(new_blk_successor, helper_logger.error) + raise e + + +def change_2way_block_conditional_successor(blk: mblock_t, blk_successor_serial: int) -> bool: + if blk.nsucc() != 2: + return False + + mba = blk.mba + previous_blk_conditional_successor_serial = blk.tail.d.b + previous_blk_conditional_successor = mba.get_mblock(previous_blk_conditional_successor_serial) + + blk.tail.d = mop_t() + blk.tail.d.make_blkref(blk_successor_serial) + + # Bookkeeping + blk.succset._del(previous_blk_conditional_successor_serial) + blk.succset.push_back(blk_successor_serial) + blk.mark_lists_dirty() + + previous_blk_conditional_successor.predset._del(blk.serial) + if previous_blk_conditional_successor.serial != mba.qty - 1: + previous_blk_conditional_successor.mark_lists_dirty() + + new_blk_conditional_successor = blk.mba.get_mblock(blk_successor_serial) + new_blk_conditional_successor.predset.push_back(blk.serial) + if new_blk_conditional_successor.serial != mba.qty - 1: + new_blk_conditional_successor.mark_lists_dirty() + + # Step4: Final stuff and checks + mba.mark_chains_dirty() + try: + mba.verify(True) + except RuntimeError as e: + helper_logger.error("Error in change_2way_block_conditional_successor: {0}".format(e)) + log_block_info(blk, helper_logger.error) + log_block_info(new_blk_conditional_successor, helper_logger.error) + raise e + + +def update_blk_successor(blk: mblock_t, old_successor_serial: int, new_successor_serial: int) -> int: + if blk.nsucc() == 1: + change_1way_block_successor(blk, new_successor_serial) + elif blk.nsucc() == 2: + if old_successor_serial == blk.serial + 1: + helper_logger.info("Can't update direct block successor: {0} - {1} - {2}" + .format(blk.serial, old_successor_serial, new_successor_serial)) + return 0 + else: + change_2way_block_conditional_successor(blk, new_successor_serial) + else: + helper_logger.info("Can't update block successor: {0} ".format(blk.serial)) + return 0 + return 1 + + +def make_2way_block_goto(blk: mblock_t, blk_successor_serial: int) -> bool: + if blk.nsucc() != 2: + return False + mba = blk.mba + previous_blk_successor_serials = [x for x in blk.succset] + previous_blk_successors = [mba.get_mblock(x) for x in previous_blk_successor_serials] + + insert_goto_instruction(blk, blk_successor_serial, nop_previous_instruction=True) + + # Update block properties + blk.type = BLT_1WAY + blk.flags |= MBL_GOTO + + # Bookkeeping + for prev_serial in previous_blk_successor_serials: + blk.succset._del(prev_serial) + blk.succset.push_back(blk_successor_serial) + blk.mark_lists_dirty() + + for prev_blk in previous_blk_successors: + prev_blk.predset._del(blk.serial) + if prev_blk.serial != mba.qty - 1: + prev_blk.mark_lists_dirty() + + new_blk_successor = blk.mba.get_mblock(blk_successor_serial) + new_blk_successor.predset.push_back(blk.serial) + if new_blk_successor.serial != mba.qty - 1: + new_blk_successor.mark_lists_dirty() + + mba.mark_chains_dirty() + try: + mba.verify(True) + return True + except RuntimeError as e: + helper_logger.error("Error in make_2way_block_goto: {0}".format(e)) + log_block_info(blk, helper_logger.error) + log_block_info(new_blk_successor, helper_logger.error) + raise e + + +def create_block(blk: mblock_t, blk_ins: List[minsn_t], is_0_way: bool = False) -> mblock_t: + mba = blk.mba + new_blk = insert_nop_blk(blk) + for ins in blk_ins: + tmp_ins = minsn_t(ins) + new_blk.insert_into_block(tmp_ins, new_blk.tail) + + if is_0_way: + new_blk.type = BLT_0WAY + # Bookkeeping + prev_successor_serial = new_blk.succset[0] + new_blk.succset._del(prev_successor_serial) + prev_succ = mba.get_mblock(prev_successor_serial) + prev_succ.predset._del(new_blk.serial) + if prev_succ.serial != mba.qty - 1: + prev_succ.mark_lists_dirty() + + new_blk.mark_lists_dirty() + mba.mark_chains_dirty() + try: + mba.verify(True) + return new_blk + except RuntimeError as e: + helper_logger.error("Error in create_block: {0}".format(e)) + log_block_info(new_blk, helper_logger.error) + raise e + + +def update_block_successors(blk: mblock_t, blk_succ_serial_list: List[int]): + mba = blk.mba + if len(blk_succ_serial_list) == 0: + blk.type = BLT_0WAY + elif len(blk_succ_serial_list) == 1: + blk.type = BLT_1WAY + elif len(blk_succ_serial_list) == 2: + blk.type = BLT_2WAY + else: + raise + + # Remove old successors + prev_successor_serials = [x for x in blk.succset] + for prev_successor_serial in prev_successor_serials: + blk.succset._del(prev_successor_serial) + prev_succ = mba.get_mblock(prev_successor_serial) + prev_succ.predset._del(blk.serial) + if prev_succ.serial != mba.qty - 1: + prev_succ.mark_lists_dirty() + # Add new successors + for blk_succ_serial in blk_succ_serial_list: + blk.succset.push_back(blk_succ_serial) + new_blk_successor = mba.get_mblock(blk_succ_serial) + new_blk_successor.predset.push_back(blk.serial) + if new_blk_successor.serial != mba.qty - 1: + new_blk_successor.mark_lists_dirty() + + blk.mark_lists_dirty() + + +def insert_nop_blk(blk: mblock_t) -> mblock_t: + mba = blk.mba + nop_block = mba.copy_block(blk, blk.serial + 1) + cur_ins = nop_block.head + while cur_ins is not None: + nop_block.make_nop(cur_ins) + cur_ins = cur_ins.next + + nop_block.type = BLT_1WAY + + # We might have clone a block with multiple or no successor, thus we need to clean all + prev_successor_serials = [x for x in nop_block.succset] + + # Bookkeeping + for prev_successor_serial in prev_successor_serials: + nop_block.succset._del(prev_successor_serial) + prev_succ = mba.get_mblock(prev_successor_serial) + prev_succ.predset._del(nop_block.serial) + if prev_succ.serial != mba.qty - 1: + prev_succ.mark_lists_dirty() + + nop_block.succset.push_back(nop_block.serial + 1) + nop_block.mark_lists_dirty() + + new_blk_successor = mba.get_mblock(nop_block.serial + 1) + new_blk_successor.predset.push_back(nop_block.serial) + if new_blk_successor.serial != mba.qty - 1: + new_blk_successor.mark_lists_dirty() + + mba.mark_chains_dirty() + try: + mba.verify(True) + return nop_block + except RuntimeError as e: + helper_logger.error("Error in insert_nop_blk: {0}".format(e)) + log_block_info(nop_block, helper_logger.error) + raise e + + +def ensure_last_block_is_goto(mba: mbl_array_t) -> int: + last_blk = mba.get_mblock(mba.qty - 2) + if last_blk.nsucc() == 1: + change_1way_block_successor(last_blk, last_blk.succset[0]) + return 1 + elif last_blk.nsucc() == 0: + return 0 + else: + raise ControlFlowException("Last block {0} is not one way (not supported yet)".format(last_blk.serial)) + + +def duplicate_block(block_to_duplicate: mblock_t) -> Tuple[mblock_t, mblock_t]: + mba = block_to_duplicate.mba + duplicated_blk = mba.copy_block(block_to_duplicate, mba.qty - 1) + helper_logger.debug(" Duplicated {0} -> {1}".format(block_to_duplicate.serial, duplicated_blk.serial)) + duplicated_blk_default = None + if (block_to_duplicate.tail is not None) and is_mcode_jcond(block_to_duplicate.tail.opcode): + block_to_duplicate_default_successor = mba.get_mblock(block_to_duplicate.serial + 1) + duplicated_blk_default = insert_nop_blk(duplicated_blk) + change_1way_block_successor(duplicated_blk_default, block_to_duplicate.serial + 1) + helper_logger.debug(" {0} is conditional, so created a default child {1} for {2} which goto {3}" + .format(block_to_duplicate.serial, duplicated_blk_default.serial, duplicated_blk.serial, + block_to_duplicate_default_successor.serial)) + elif duplicated_blk.nsucc() == 1: + helper_logger.debug(" Making {0} goto {1}".format(duplicated_blk.serial, block_to_duplicate.succset[0])) + change_1way_block_successor(duplicated_blk, block_to_duplicate.succset[0]) + elif duplicated_blk.nsucc() == 0: + helper_logger.debug(" Duplicated block {0} has no successor => Nothing to do".format(duplicated_blk.serial)) + + return duplicated_blk, duplicated_blk_default + + +def change_block_address(block: mblock_t, new_ea: int): + # Can be used to fix error 50357 + mb_curr = block.head + while mb_curr: + mb_curr.ea = new_ea + mb_curr = mb_curr.next + + +def is_conditional_jump(blk: mblock_t) -> bool: + if (blk is not None) and (blk.tail is not None): + return blk.tail.opcode in CONDITIONAL_JUMP_OPCODES + return False + + +def is_indirect_jump(blk: mblock_t) -> bool: + if (blk is not None) and (blk.tail is not None): + return blk.tail.opcode == m_ijmp + return False + + +def get_block_serials_by_address(mba: mbl_array_t, address: int) -> List[int]: + blk_serial_list = [] + for i in range(mba.qty): + blk = mba.get_mblock(i) + if blk.start == address: + blk_serial_list.append(i) + return blk_serial_list + + +def get_block_serials_by_address_range(mba: mbl_array_t, address: int) -> List[int]: + blk_serial_list = [] + for i in range(mba.qty): + blk = mba.get_mblock(i) + if blk.start <= address <= blk.end: + blk_serial_list.append(i) + return blk_serial_list + + +def mba_remove_simple_goto_blocks(mba: mbl_array_t) -> int: + last_block_index = mba.qty - 1 + nb_change = 0 + for goto_blk_serial in range(last_block_index): + goto_blk: mblock_t = mba.get_mblock(goto_blk_serial) + if goto_blk.is_simple_goto_block(): + goto_blk_dst_serial = goto_blk.tail.l.b + goto_blk_preset = [x for x in goto_blk.predset] + for father_serial in goto_blk_preset: + father_blk: mblock_t = mba.get_mblock(father_serial) + nb_change += update_blk_successor(father_blk, goto_blk_serial, goto_blk_dst_serial) + return nb_change + + +def mba_deep_cleaning(mba: mbl_array_t) -> int: + if mba.maturity < MMAT_CALLS: + # Doing this optimization before MMAT_CALLS may create blocks with call instruction (not last instruction) + # IDA does like that and will raise a 50864 error + return 0 + mba.remove_empty_blocks() + mba.combine_blocks() + nb_change = mba_remove_simple_goto_blocks(mba) + if nb_change > 0: + mba.remove_empty_blocks() + mba.combine_blocks() + return nb_change + + +def ensure_child_has_an_unconditional_father(father_block: mblock_t, child_block: mblock_t) -> int: + if father_block is None: + return 0 + mba = father_block.mba + if father_block.nsucc() == 1: + return 0 + + if father_block.tail.d.b == child_block.serial: + helper_logger.debug("Father {0} is a conditional jump to child {1}, creating a new father" + .format(father_block.serial, child_block.serial)) + new_father_block = insert_nop_blk(mba.get_mblock(mba.qty - 2)) + change_1way_block_successor(new_father_block, child_block.serial) + change_2way_block_conditional_successor(father_block, new_father_block.serial) + else: + helper_logger.info("Father {0} is a conditional jump to child {1} (default child), creating a new father" + .format(father_block.serial, child_block.serial)) + new_father_block = insert_nop_blk(father_block) + change_1way_block_successor(new_father_block, child_block.serial) + return 1 diff --git a/d810/conf/__init__.py b/d810/conf/__init__.py new file mode 100644 index 0000000..d6290ed --- /dev/null +++ b/d810/conf/__init__.py @@ -0,0 +1,73 @@ +import os +import json + + +class D810Configuration(object): + def __init__(self): + self.config_dir = os.path.join(os.getenv("HOME"), ".idapro", "plugins", "d810", "conf") + self.config_file = os.path.join(self.config_dir, "options.json") + with open(self.config_file, "r") as fp: + self._options = json.load(fp) + + def get(self, name): + if (name == "log_dir") and (self._options[name] is None): + return os.path.abspath(os.path.join(os.path.dirname(os.path.abspath(__file__)), "..", "..")) + return self._options[name] + + def set(self, name, value): + self._options[name] = value + + def save(self): + with open(self.config_file, "w") as fp: + json.dump(self._options, fp, indent=2) + + +class RuleConfiguration(object): + def __init__(self, name=None, is_activated=False, config=None): + self.name = name + self.is_activated = is_activated + self.config = config if config is not None else {} + + def to_dict(self): + return { + "name": self.name, + "is_activated": self.is_activated, + "config": self.config + } + + @staticmethod + def from_dict(kwargs): + return RuleConfiguration(**kwargs) + + +class ProjectConfiguration(object): + def __init__(self, path=None, description=None, ins_rules=None, blk_rules=None, conf_dir=None): + self.path = path + self.description = description + self.conf_dir = conf_dir + self.ins_rules = [] if ins_rules is None else ins_rules + self.blk_rules = [] if blk_rules is None else blk_rules + self.additional_configuration = {} + + def load(self): + try: + with open(self.path, "r") as fp: + project_conf = json.load(fp) + except FileNotFoundError as e: + if self.conf_dir is not None: + self.path = os.path.join(self.conf_dir, self.path) + with open(self.path, "r") as fp: + project_conf = json.load(fp) + + self.description = project_conf["description"] + self.ins_rules = [RuleConfiguration.from_dict(x) for x in project_conf["ins_rules"]] + self.blk_rules = [RuleConfiguration.from_dict(x) for x in project_conf["blk_rules"]] + + def save(self): + project_conf = { + "description": self.description, + "ins_rules": [x.to_dict() for x in self.ins_rules], + "blk_rules": [x.to_dict() for x in self.blk_rules], + } + with open(self.path, "w") as fp: + json.dump(project_conf, fp, indent=2) diff --git a/d810/conf/default_instruction_only.json b/d810/conf/default_instruction_only.json new file mode 100644 index 0000000..f95ad56 --- /dev/null +++ b/d810/conf/default_instruction_only.json @@ -0,0 +1,921 @@ +{ + "description": "Default configuration of D-810 with only instruction level optimization", + "ins_rules": [ + { + "name": "AddXor_Rule_1", + "is_activated": true, + "config": {} + }, + { + "name": "AddXor_Rule_2", + "is_activated": true, + "config": {} + }, + { + "name": "Add_HackersDelightRule_1", + "is_activated": true, + "config": {} + }, + { + "name": "Add_HackersDelightRule_2", + "is_activated": true, + "config": {} + }, + { + "name": "Add_HackersDelightRule_3", + "is_activated": true, + "config": {} + }, + { + "name": "Add_HackersDelightRule_4", + "is_activated": true, + "config": {} + }, + { + "name": "Add_HackersDelightRule_5", + "is_activated": true, + "config": {} + }, + { + "name": "Add_OllvmRule_1", + "is_activated": true, + "config": {} + }, + { + "name": "Add_OllvmRule_2", + "is_activated": true, + "config": {} + }, + { + "name": "Add_OllvmRule_3", + "is_activated": true, + "config": {} + }, + { + "name": "Add_OllvmRule_4", + "is_activated": true, + "config": {} + }, + { + "name": "Add_SpecialConstantRule_1", + "is_activated": true, + "config": {} + }, + { + "name": "Add_SpecialConstantRule_2", + "is_activated": true, + "config": {} + }, + { + "name": "Add_SpecialConstantRule_3", + "is_activated": true, + "config": {} + }, + { + "name": "And1_MbaRule_1", + "is_activated": true, + "config": {} + }, + { + "name": "AndBnot_FactorRule_1", + "is_activated": true, + "config": {} + }, + { + "name": "AndBnot_FactorRule_2", + "is_activated": true, + "config": {} + }, + { + "name": "AndBnot_FactorRule_3", + "is_activated": true, + "config": {} + }, + { + "name": "AndBnot_FactorRule_4", + "is_activated": true, + "config": {} + }, + { + "name": "AndBnot_HackersDelightRule_1", + "is_activated": true, + "config": {} + }, + { + "name": "AndBnot_HackersDelightRule_2", + "is_activated": true, + "config": {} + }, + { + "name": "AndGetUpperBits_FactorRule_1", + "is_activated": true, + "config": {} + }, + { + "name": "AndOr_FactorRule_1", + "is_activated": true, + "config": {} + }, + { + "name": "AndXor_FactorRule_1", + "is_activated": true, + "config": {} + }, + { + "name": "And_FactorRule_1", + "is_activated": true, + "config": {} + }, + { + "name": "And_FactorRule_2", + "is_activated": true, + "config": {} + }, + { + "name": "And_HackersDelightRule_1", + "is_activated": true, + "config": {} + }, + { + "name": "And_HackersDelightRule_2", + "is_activated": true, + "config": {} + }, + { + "name": "And_HackersDelightRule_3", + "is_activated": true, + "config": {} + }, + { + "name": "And_HackersDelightRule_4", + "is_activated": true, + "config": {} + }, + { + "name": "And_OllvmRule_1", + "is_activated": true, + "config": {} + }, + { + "name": "And_OllvmRule_2", + "is_activated": true, + "config": {} + }, + { + "name": "And_OllvmRule_3", + "is_activated": true, + "config": {} + }, + { + "name": "BnotAdd_MbaRule_1", + "is_activated": true, + "config": {} + }, + { + "name": "BnotAnd_FactorRule_1", + "is_activated": true, + "config": {} + }, + { + "name": "BnotAnd_FactorRule_2", + "is_activated": true, + "config": {} + }, + { + "name": "BnotAnd_FactorRule_3", + "is_activated": true, + "config": {} + }, + { + "name": "BnotAnd_FactorRule_4", + "is_activated": true, + "config": {} + }, + { + "name": "BnotOr_FactorRule_1", + "is_activated": true, + "config": {} + }, + { + "name": "BnotXor_FactorRule_1", + "is_activated": true, + "config": {} + }, + { + "name": "BnotXor_Rule_1", + "is_activated": true, + "config": {} + }, + { + "name": "BnotXor_Rule_2", + "is_activated": true, + "config": {} + }, + { + "name": "BnotXor_Rule_3", + "is_activated": true, + "config": {} + }, + { + "name": "Bnot_FactorRule_1", + "is_activated": true, + "config": {} + }, + { + "name": "Bnot_FactorRule_2", + "is_activated": true, + "config": {} + }, + { + "name": "Bnot_FactorRule_3", + "is_activated": true, + "config": {} + }, + { + "name": "Bnot_FactorRule_4", + "is_activated": true, + "config": {} + }, + { + "name": "Bnot_HackersDelightRule_1", + "is_activated": true, + "config": {} + }, + { + "name": "Bnot_HackersDelightRule_2", + "is_activated": true, + "config": {} + }, + { + "name": "Bnot_MbaRule_1", + "is_activated": true, + "config": {} + }, + { + "name": "Bnot_Rule_1", + "is_activated": true, + "config": {} + }, + { + "name": "Bnot_XorRule_1", + "is_activated": true, + "config": {} + }, + { + "name": "CstSimplificationRule1", + "is_activated": true, + "config": {} + }, + { + "name": "CstSimplificationRule10", + "is_activated": true, + "config": {} + }, + { + "name": "CstSimplificationRule11", + "is_activated": true, + "config": {} + }, + { + "name": "CstSimplificationRule12", + "is_activated": true, + "config": {} + }, + { + "name": "CstSimplificationRule13", + "is_activated": true, + "config": {} + }, + { + "name": "CstSimplificationRule14", + "is_activated": true, + "config": {} + }, + { + "name": "CstSimplificationRule15", + "is_activated": true, + "config": {} + }, + { + "name": "CstSimplificationRule16", + "is_activated": true, + "config": {} + }, + { + "name": "CstSimplificationRule17", + "is_activated": true, + "config": {} + }, + { + "name": "CstSimplificationRule18", + "is_activated": true, + "config": {} + }, + { + "name": "CstSimplificationRule19", + "is_activated": true, + "config": {} + }, + { + "name": "CstSimplificationRule2", + "is_activated": true, + "config": {} + }, + { + "name": "CstSimplificationRule20", + "is_activated": true, + "config": {} + }, + { + "name": "CstSimplificationRule21", + "is_activated": true, + "config": {} + }, + { + "name": "CstSimplificationRule22", + "is_activated": true, + "config": {} + }, + { + "name": "CstSimplificationRule3", + "is_activated": true, + "config": {} + }, + { + "name": "CstSimplificationRule4", + "is_activated": true, + "config": {} + }, + { + "name": "CstSimplificationRule5", + "is_activated": true, + "config": {} + }, + { + "name": "CstSimplificationRule6", + "is_activated": true, + "config": {} + }, + { + "name": "CstSimplificationRule7", + "is_activated": true, + "config": {} + }, + { + "name": "CstSimplificationRule8", + "is_activated": true, + "config": {} + }, + { + "name": "CstSimplificationRule9", + "is_activated": true, + "config": {} + }, + { + "name": "GetIdentRule1", + "is_activated": true, + "config": {} + }, + { + "name": "GetIdentRule2", + "is_activated": true, + "config": {} + }, + { + "name": "GetIdentRule3", + "is_activated": true, + "config": {} + }, + { + "name": "Mul_FactorRule_1", + "is_activated": true, + "config": {} + }, + { + "name": "Mul_FactorRule_2", + "is_activated": true, + "config": {} + }, + { + "name": "Mul_MbaRule_1", + "is_activated": true, + "config": {} + }, + { + "name": "Mul_MbaRule_2", + "is_activated": true, + "config": {} + }, + { + "name": "Mul_MbaRule_3", + "is_activated": true, + "config": {} + }, + { + "name": "Mul_MbaRule_4", + "is_activated": true, + "config": {} + }, + { + "name": "NegAdd_HackersDelightRule_1", + "is_activated": true, + "config": {} + }, + { + "name": "NegAdd_HackersDelightRule_2", + "is_activated": true, + "config": {} + }, + { + "name": "NegOr_HackersDelightRule_1", + "is_activated": true, + "config": {} + }, + { + "name": "NegXor_HackersDelightRule_1", + "is_activated": true, + "config": {} + }, + { + "name": "NegXor_HackersDelightRule_2", + "is_activated": true, + "config": {} + }, + { + "name": "Neg_HackersDelightRule_1", + "is_activated": true, + "config": {} + }, + { + "name": "Neg_HackersDelightRule_2", + "is_activated": true, + "config": {} + }, + { + "name": "OrBnot_FactorRule_1", + "is_activated": true, + "config": {} + }, + { + "name": "OrBnot_FactorRule_2", + "is_activated": true, + "config": {} + }, + { + "name": "OrBnot_FactorRule_3", + "is_activated": true, + "config": {} + }, + { + "name": "OrBnot_FactorRule_4", + "is_activated": true, + "config": {} + }, + { + "name": "Or_FactorRule_1", + "is_activated": true, + "config": {} + }, + { + "name": "Or_FactorRule_2", + "is_activated": true, + "config": {} + }, + { + "name": "Or_FactorRule_3", + "is_activated": true, + "config": {} + }, + { + "name": "Or_HackersDelightRule_1", + "is_activated": true, + "config": {} + }, + { + "name": "Or_HackersDelightRule_2", + "is_activated": true, + "config": {} + }, + { + "name": "Or_HackersDelightRule_2_variant_1", + "is_activated": true, + "config": {} + }, + { + "name": "Or_MbaRule_1", + "is_activated": true, + "config": {} + }, + { + "name": "Or_MbaRule_2", + "is_activated": true, + "config": {} + }, + { + "name": "Or_MbaRule_3", + "is_activated": true, + "config": {} + }, + { + "name": "Or_OllvmRule_1", + "is_activated": true, + "config": {} + }, + { + "name": "Or_Rule_1", + "is_activated": true, + "config": {} + }, + { + "name": "Or_Rule_2", + "is_activated": true, + "config": {} + }, + { + "name": "Or_Rule_3", + "is_activated": true, + "config": {} + }, + { + "name": "Or_Rule_4", + "is_activated": true, + "config": {} + }, + { + "name": "Pred0Rule1", + "is_activated": true, + "config": {} + }, + { + "name": "Pred0Rule2", + "is_activated": true, + "config": {} + }, + { + "name": "Pred0Rule3", + "is_activated": true, + "config": {} + }, + { + "name": "Pred0Rule4", + "is_activated": true, + "config": {} + }, + { + "name": "Pred0Rule5", + "is_activated": true, + "config": {} + }, + { + "name": "PredFFRule1", + "is_activated": true, + "config": {} + }, + { + "name": "PredFFRule2", + "is_activated": true, + "config": {} + }, + { + "name": "PredFFRule3", + "is_activated": true, + "config": {} + }, + { + "name": "PredFFRule4", + "is_activated": true, + "config": {} + }, + { + "name": "PredOdd1", + "is_activated": true, + "config": {} + }, + { + "name": "PredOdd2", + "is_activated": true, + "config": {} + }, + { + "name": "PredOr1_Rule_1", + "is_activated": true, + "config": {} + }, + { + "name": "PredOr2_Rule_1", + "is_activated": true, + "config": {} + }, + { + "name": "PredSetbRule1", + "is_activated": true, + "config": {} + }, + { + "name": "PredSetnzRule1", + "is_activated": true, + "config": {} + }, + { + "name": "PredSetnzRule2", + "is_activated": true, + "config": {} + }, + { + "name": "PredSetnzRule3", + "is_activated": true, + "config": {} + }, + { + "name": "PredSetnzRule4", + "is_activated": true, + "config": {} + }, + { + "name": "PredSetnzRule5", + "is_activated": true, + "config": {} + }, + { + "name": "PredSetnzRule6", + "is_activated": true, + "config": {} + }, + { + "name": "PredSetnzRule8", + "is_activated": true, + "config": {} + }, + { + "name": "PredSetzRule1", + "is_activated": true, + "config": {} + }, + { + "name": "PredSetzRule2", + "is_activated": true, + "config": {} + }, + { + "name": "PredSetzRule3", + "is_activated": true, + "config": {} + }, + { + "name": "Sub1Add_HackersDelightRule_1", + "is_activated": true, + "config": {} + }, + { + "name": "Sub1And1_MbaRule_1", + "is_activated": true, + "config": {} + }, + { + "name": "Sub1And_HackersDelightRule_1", + "is_activated": true, + "config": {} + }, + { + "name": "Sub1Or_MbaRule_1", + "is_activated": true, + "config": {} + }, + { + "name": "Sub1_FactorRule_1", + "is_activated": true, + "config": {} + }, + { + "name": "Sub1_FactorRule_2", + "is_activated": true, + "config": {} + }, + { + "name": "Sub_HackersDelightRule_1", + "is_activated": true, + "config": {} + }, + { + "name": "Sub_HackersDelightRule_2", + "is_activated": true, + "config": {} + }, + { + "name": "Sub_HackersDelightRule_3", + "is_activated": true, + "config": {} + }, + { + "name": "Sub_HackersDelightRule_4", + "is_activated": true, + "config": {} + }, + { + "name": "WeirdRule1", + "is_activated": true, + "config": {} + }, + { + "name": "WeirdRule2", + "is_activated": true, + "config": {} + }, + { + "name": "WeirdRule3", + "is_activated": true, + "config": {} + }, + { + "name": "WeirdRule4", + "is_activated": true, + "config": {} + }, + { + "name": "WeirdRule5", + "is_activated": true, + "config": {} + }, + { + "name": "WeirdRule6", + "is_activated": true, + "config": {} + }, + { + "name": "Xor1_MbaRule_1", + "is_activated": true, + "config": {} + }, + { + "name": "XorAlmost_Rule_1", + "is_activated": true, + "config": {} + }, + { + "name": "Xor_FactorRule_1", + "is_activated": true, + "config": {} + }, + { + "name": "Xor_FactorRule_2", + "is_activated": true, + "config": {} + }, + { + "name": "Xor_FactorRule_3", + "is_activated": true, + "config": {} + }, + { + "name": "Xor_HackersDelightRule_1", + "is_activated": true, + "config": {} + }, + { + "name": "Xor_HackersDelightRule_2", + "is_activated": true, + "config": {} + }, + { + "name": "Xor_HackersDelightRule_3", + "is_activated": true, + "config": {} + }, + { + "name": "Xor_HackersDelightRule_4", + "is_activated": true, + "config": {} + }, + { + "name": "Xor_HackersDelightRule_5", + "is_activated": true, + "config": {} + }, + { + "name": "Xor_MbaRule_1", + "is_activated": true, + "config": {} + }, + { + "name": "Xor_MbaRule_2", + "is_activated": true, + "config": {} + }, + { + "name": "Xor_MbaRule_3", + "is_activated": true, + "config": {} + }, + { + "name": "Xor_NestedStuff", + "is_activated": true, + "config": {} + }, + { + "name": "Xor_Rule_1", + "is_activated": true, + "config": {} + }, + { + "name": "Xor_Rule_2", + "is_activated": true, + "config": {} + }, + { + "name": "Xor_Rule_3", + "is_activated": true, + "config": {} + }, + { + "name": "Xor_SpecialConstantRule_1", + "is_activated": true, + "config": {} + }, + { + "name": "Xor_SpecialConstantRule_2", + "is_activated": true, + "config": {} + }, + { + "name": "AndChain", + "is_activated": true, + "config": {} + }, + { + "name": "ArithmeticChain", + "is_activated": true, + "config": {} + }, + { + "name": "OrChain", + "is_activated": true, + "config": {} + }, + { + "name": "XorChain", + "is_activated": true, + "config": {} + }, + { + "name": "Z3ConstantOptimization", + "is_activated": true, + "config": { + "min_nb_opcode": 4, + "min_nb_constant": 3 + } + }, + { + "name": "Z3SmodRuleGeneric", + "is_activated": true, + "config": {} + }, + { + "name": "Z3lnotRuleGeneric", + "is_activated": true, + "config": {} + }, + { + "name": "Z3setnzRuleGeneric", + "is_activated": true, + "config": {} + }, + { + "name": "Z3setzRuleGeneric", + "is_activated": true, + "config": {} + }, + { + "name": "ExampleGuessingRule", + "is_activated": true, + "config": { + "min_nb_var": 1, + "max_nb_var": 3, + "min_nb_diff_opcodes": 3, + "max_nb_diff_opcodes": 6 + } + } + ], + "blk_rules": [ + { + "name": "JumpFixer", + "is_activated": true, + "config": { + "enabled_rules": [ + "CompareConstantRule1", + "CompareConstantRule2", + "CompareConstantRule3", + "JaeRule1", + "JbRule1", + "JnzRule1", + "JnzRule2", + "JnzRule3", + "JnzRule4", + "JnzRule5", + "JnzRule6", + "JnzRule7", + "JnzRule8" + ] + } + } + ] +} \ No newline at end of file diff --git a/d810/conf/default_unflattening_ollvm.json b/d810/conf/default_unflattening_ollvm.json new file mode 100644 index 0000000..f31e10b --- /dev/null +++ b/d810/conf/default_unflattening_ollvm.json @@ -0,0 +1,926 @@ +{ + "description": "Unflattening O-LLVM with control flow flattening", + "ins_rules": [ + { + "name": "AddXor_Rule_1", + "is_activated": true, + "config": {} + }, + { + "name": "AddXor_Rule_2", + "is_activated": true, + "config": {} + }, + { + "name": "Add_HackersDelightRule_1", + "is_activated": true, + "config": {} + }, + { + "name": "Add_HackersDelightRule_2", + "is_activated": true, + "config": {} + }, + { + "name": "Add_HackersDelightRule_3", + "is_activated": true, + "config": {} + }, + { + "name": "Add_HackersDelightRule_4", + "is_activated": true, + "config": {} + }, + { + "name": "Add_HackersDelightRule_5", + "is_activated": true, + "config": {} + }, + { + "name": "Add_OllvmRule_1", + "is_activated": true, + "config": {} + }, + { + "name": "Add_OllvmRule_2", + "is_activated": true, + "config": {} + }, + { + "name": "Add_OllvmRule_3", + "is_activated": true, + "config": {} + }, + { + "name": "Add_OllvmRule_4", + "is_activated": true, + "config": {} + }, + { + "name": "Add_SpecialConstantRule_1", + "is_activated": true, + "config": {} + }, + { + "name": "Add_SpecialConstantRule_2", + "is_activated": true, + "config": {} + }, + { + "name": "Add_SpecialConstantRule_3", + "is_activated": true, + "config": {} + }, + { + "name": "And1_MbaRule_1", + "is_activated": true, + "config": {} + }, + { + "name": "AndBnot_FactorRule_1", + "is_activated": true, + "config": {} + }, + { + "name": "AndBnot_FactorRule_2", + "is_activated": true, + "config": {} + }, + { + "name": "AndBnot_FactorRule_3", + "is_activated": true, + "config": {} + }, + { + "name": "AndBnot_FactorRule_4", + "is_activated": true, + "config": {} + }, + { + "name": "AndBnot_HackersDelightRule_1", + "is_activated": true, + "config": {} + }, + { + "name": "AndBnot_HackersDelightRule_2", + "is_activated": true, + "config": {} + }, + { + "name": "AndGetUpperBits_FactorRule_1", + "is_activated": true, + "config": {} + }, + { + "name": "AndOr_FactorRule_1", + "is_activated": true, + "config": {} + }, + { + "name": "AndXor_FactorRule_1", + "is_activated": true, + "config": {} + }, + { + "name": "And_FactorRule_1", + "is_activated": true, + "config": {} + }, + { + "name": "And_FactorRule_2", + "is_activated": true, + "config": {} + }, + { + "name": "And_HackersDelightRule_1", + "is_activated": true, + "config": {} + }, + { + "name": "And_HackersDelightRule_2", + "is_activated": true, + "config": {} + }, + { + "name": "And_HackersDelightRule_3", + "is_activated": true, + "config": {} + }, + { + "name": "And_HackersDelightRule_4", + "is_activated": true, + "config": {} + }, + { + "name": "And_OllvmRule_1", + "is_activated": true, + "config": {} + }, + { + "name": "And_OllvmRule_2", + "is_activated": true, + "config": {} + }, + { + "name": "And_OllvmRule_3", + "is_activated": true, + "config": {} + }, + { + "name": "BnotAdd_MbaRule_1", + "is_activated": true, + "config": {} + }, + { + "name": "BnotAnd_FactorRule_1", + "is_activated": true, + "config": {} + }, + { + "name": "BnotAnd_FactorRule_2", + "is_activated": true, + "config": {} + }, + { + "name": "BnotAnd_FactorRule_3", + "is_activated": true, + "config": {} + }, + { + "name": "BnotAnd_FactorRule_4", + "is_activated": true, + "config": {} + }, + { + "name": "BnotOr_FactorRule_1", + "is_activated": true, + "config": {} + }, + { + "name": "BnotXor_FactorRule_1", + "is_activated": true, + "config": {} + }, + { + "name": "BnotXor_Rule_1", + "is_activated": true, + "config": {} + }, + { + "name": "BnotXor_Rule_2", + "is_activated": true, + "config": {} + }, + { + "name": "BnotXor_Rule_3", + "is_activated": true, + "config": {} + }, + { + "name": "Bnot_FactorRule_1", + "is_activated": true, + "config": {} + }, + { + "name": "Bnot_FactorRule_2", + "is_activated": true, + "config": {} + }, + { + "name": "Bnot_FactorRule_3", + "is_activated": true, + "config": {} + }, + { + "name": "Bnot_FactorRule_4", + "is_activated": true, + "config": {} + }, + { + "name": "Bnot_HackersDelightRule_1", + "is_activated": true, + "config": {} + }, + { + "name": "Bnot_HackersDelightRule_2", + "is_activated": true, + "config": {} + }, + { + "name": "Bnot_MbaRule_1", + "is_activated": true, + "config": {} + }, + { + "name": "Bnot_Rule_1", + "is_activated": true, + "config": {} + }, + { + "name": "Bnot_XorRule_1", + "is_activated": true, + "config": {} + }, + { + "name": "CstSimplificationRule1", + "is_activated": true, + "config": {} + }, + { + "name": "CstSimplificationRule10", + "is_activated": true, + "config": {} + }, + { + "name": "CstSimplificationRule11", + "is_activated": true, + "config": {} + }, + { + "name": "CstSimplificationRule12", + "is_activated": true, + "config": {} + }, + { + "name": "CstSimplificationRule13", + "is_activated": true, + "config": {} + }, + { + "name": "CstSimplificationRule14", + "is_activated": true, + "config": {} + }, + { + "name": "CstSimplificationRule15", + "is_activated": true, + "config": {} + }, + { + "name": "CstSimplificationRule16", + "is_activated": true, + "config": {} + }, + { + "name": "CstSimplificationRule17", + "is_activated": true, + "config": {} + }, + { + "name": "CstSimplificationRule18", + "is_activated": true, + "config": {} + }, + { + "name": "CstSimplificationRule19", + "is_activated": true, + "config": {} + }, + { + "name": "CstSimplificationRule2", + "is_activated": true, + "config": {} + }, + { + "name": "CstSimplificationRule20", + "is_activated": true, + "config": {} + }, + { + "name": "CstSimplificationRule21", + "is_activated": true, + "config": {} + }, + { + "name": "CstSimplificationRule22", + "is_activated": true, + "config": {} + }, + { + "name": "CstSimplificationRule3", + "is_activated": true, + "config": {} + }, + { + "name": "CstSimplificationRule4", + "is_activated": true, + "config": {} + }, + { + "name": "CstSimplificationRule5", + "is_activated": true, + "config": {} + }, + { + "name": "CstSimplificationRule6", + "is_activated": true, + "config": {} + }, + { + "name": "CstSimplificationRule7", + "is_activated": true, + "config": {} + }, + { + "name": "CstSimplificationRule8", + "is_activated": true, + "config": {} + }, + { + "name": "CstSimplificationRule9", + "is_activated": true, + "config": {} + }, + { + "name": "GetIdentRule1", + "is_activated": true, + "config": {} + }, + { + "name": "GetIdentRule2", + "is_activated": true, + "config": {} + }, + { + "name": "GetIdentRule3", + "is_activated": true, + "config": {} + }, + { + "name": "Mul_FactorRule_1", + "is_activated": true, + "config": {} + }, + { + "name": "Mul_FactorRule_2", + "is_activated": true, + "config": {} + }, + { + "name": "Mul_MbaRule_1", + "is_activated": true, + "config": {} + }, + { + "name": "Mul_MbaRule_2", + "is_activated": true, + "config": {} + }, + { + "name": "Mul_MbaRule_3", + "is_activated": true, + "config": {} + }, + { + "name": "Mul_MbaRule_4", + "is_activated": true, + "config": {} + }, + { + "name": "NegAdd_HackersDelightRule_1", + "is_activated": true, + "config": {} + }, + { + "name": "NegAdd_HackersDelightRule_2", + "is_activated": true, + "config": {} + }, + { + "name": "NegOr_HackersDelightRule_1", + "is_activated": true, + "config": {} + }, + { + "name": "NegXor_HackersDelightRule_1", + "is_activated": true, + "config": {} + }, + { + "name": "NegXor_HackersDelightRule_2", + "is_activated": true, + "config": {} + }, + { + "name": "Neg_HackersDelightRule_1", + "is_activated": true, + "config": {} + }, + { + "name": "Neg_HackersDelightRule_2", + "is_activated": true, + "config": {} + }, + { + "name": "OrBnot_FactorRule_1", + "is_activated": true, + "config": {} + }, + { + "name": "OrBnot_FactorRule_2", + "is_activated": true, + "config": {} + }, + { + "name": "OrBnot_FactorRule_3", + "is_activated": true, + "config": {} + }, + { + "name": "OrBnot_FactorRule_4", + "is_activated": true, + "config": {} + }, + { + "name": "Or_FactorRule_1", + "is_activated": true, + "config": {} + }, + { + "name": "Or_FactorRule_2", + "is_activated": true, + "config": {} + }, + { + "name": "Or_FactorRule_3", + "is_activated": true, + "config": {} + }, + { + "name": "Or_HackersDelightRule_1", + "is_activated": true, + "config": {} + }, + { + "name": "Or_HackersDelightRule_2", + "is_activated": true, + "config": {} + }, + { + "name": "Or_HackersDelightRule_2_variant_1", + "is_activated": true, + "config": {} + }, + { + "name": "Or_MbaRule_1", + "is_activated": true, + "config": {} + }, + { + "name": "Or_MbaRule_2", + "is_activated": true, + "config": {} + }, + { + "name": "Or_MbaRule_3", + "is_activated": true, + "config": {} + }, + { + "name": "Or_OllvmRule_1", + "is_activated": true, + "config": {} + }, + { + "name": "Or_Rule_1", + "is_activated": true, + "config": {} + }, + { + "name": "Or_Rule_2", + "is_activated": true, + "config": {} + }, + { + "name": "Or_Rule_3", + "is_activated": true, + "config": {} + }, + { + "name": "Or_Rule_4", + "is_activated": true, + "config": {} + }, + { + "name": "Pred0Rule1", + "is_activated": true, + "config": {} + }, + { + "name": "Pred0Rule2", + "is_activated": true, + "config": {} + }, + { + "name": "Pred0Rule3", + "is_activated": true, + "config": {} + }, + { + "name": "Pred0Rule4", + "is_activated": true, + "config": {} + }, + { + "name": "Pred0Rule5", + "is_activated": true, + "config": {} + }, + { + "name": "PredFFRule1", + "is_activated": true, + "config": {} + }, + { + "name": "PredFFRule2", + "is_activated": true, + "config": {} + }, + { + "name": "PredFFRule3", + "is_activated": true, + "config": {} + }, + { + "name": "PredFFRule4", + "is_activated": true, + "config": {} + }, + { + "name": "PredOdd1", + "is_activated": true, + "config": {} + }, + { + "name": "PredOdd2", + "is_activated": true, + "config": {} + }, + { + "name": "PredOr1_Rule_1", + "is_activated": true, + "config": {} + }, + { + "name": "PredOr2_Rule_1", + "is_activated": true, + "config": {} + }, + { + "name": "PredSetbRule1", + "is_activated": true, + "config": {} + }, + { + "name": "PredSetnzRule1", + "is_activated": true, + "config": {} + }, + { + "name": "PredSetnzRule2", + "is_activated": true, + "config": {} + }, + { + "name": "PredSetnzRule3", + "is_activated": true, + "config": {} + }, + { + "name": "PredSetnzRule4", + "is_activated": true, + "config": {} + }, + { + "name": "PredSetnzRule5", + "is_activated": true, + "config": {} + }, + { + "name": "PredSetnzRule6", + "is_activated": true, + "config": {} + }, + { + "name": "PredSetnzRule8", + "is_activated": true, + "config": {} + }, + { + "name": "PredSetzRule1", + "is_activated": true, + "config": {} + }, + { + "name": "PredSetzRule2", + "is_activated": true, + "config": {} + }, + { + "name": "PredSetzRule3", + "is_activated": true, + "config": {} + }, + { + "name": "Sub1Add_HackersDelightRule_1", + "is_activated": true, + "config": {} + }, + { + "name": "Sub1And1_MbaRule_1", + "is_activated": true, + "config": {} + }, + { + "name": "Sub1And_HackersDelightRule_1", + "is_activated": true, + "config": {} + }, + { + "name": "Sub1Or_MbaRule_1", + "is_activated": true, + "config": {} + }, + { + "name": "Sub1_FactorRule_1", + "is_activated": true, + "config": {} + }, + { + "name": "Sub1_FactorRule_2", + "is_activated": true, + "config": {} + }, + { + "name": "Sub_HackersDelightRule_1", + "is_activated": true, + "config": {} + }, + { + "name": "Sub_HackersDelightRule_2", + "is_activated": true, + "config": {} + }, + { + "name": "Sub_HackersDelightRule_3", + "is_activated": true, + "config": {} + }, + { + "name": "Sub_HackersDelightRule_4", + "is_activated": true, + "config": {} + }, + { + "name": "WeirdRule1", + "is_activated": true, + "config": {} + }, + { + "name": "WeirdRule2", + "is_activated": true, + "config": {} + }, + { + "name": "WeirdRule3", + "is_activated": true, + "config": {} + }, + { + "name": "WeirdRule4", + "is_activated": true, + "config": {} + }, + { + "name": "WeirdRule5", + "is_activated": true, + "config": {} + }, + { + "name": "WeirdRule6", + "is_activated": true, + "config": {} + }, + { + "name": "Xor1_MbaRule_1", + "is_activated": true, + "config": {} + }, + { + "name": "XorAlmost_Rule_1", + "is_activated": true, + "config": {} + }, + { + "name": "Xor_FactorRule_1", + "is_activated": true, + "config": {} + }, + { + "name": "Xor_FactorRule_2", + "is_activated": true, + "config": {} + }, + { + "name": "Xor_FactorRule_3", + "is_activated": true, + "config": {} + }, + { + "name": "Xor_HackersDelightRule_1", + "is_activated": true, + "config": {} + }, + { + "name": "Xor_HackersDelightRule_2", + "is_activated": true, + "config": {} + }, + { + "name": "Xor_HackersDelightRule_3", + "is_activated": true, + "config": {} + }, + { + "name": "Xor_HackersDelightRule_4", + "is_activated": true, + "config": {} + }, + { + "name": "Xor_HackersDelightRule_5", + "is_activated": true, + "config": {} + }, + { + "name": "Xor_MbaRule_1", + "is_activated": true, + "config": {} + }, + { + "name": "Xor_MbaRule_2", + "is_activated": true, + "config": {} + }, + { + "name": "Xor_MbaRule_3", + "is_activated": true, + "config": {} + }, + { + "name": "Xor_NestedStuff", + "is_activated": true, + "config": {} + }, + { + "name": "Xor_Rule_1", + "is_activated": true, + "config": {} + }, + { + "name": "Xor_Rule_2", + "is_activated": true, + "config": {} + }, + { + "name": "Xor_Rule_3", + "is_activated": true, + "config": {} + }, + { + "name": "Xor_SpecialConstantRule_1", + "is_activated": true, + "config": {} + }, + { + "name": "Xor_SpecialConstantRule_2", + "is_activated": true, + "config": {} + }, + { + "name": "AndChain", + "is_activated": true, + "config": {} + }, + { + "name": "ArithmeticChain", + "is_activated": true, + "config": {} + }, + { + "name": "OrChain", + "is_activated": true, + "config": {} + }, + { + "name": "XorChain", + "is_activated": true, + "config": {} + }, + { + "name": "Z3ConstantOptimization", + "is_activated": true, + "config": { + "min_nb_opcode": 4, + "min_nb_constant": 3 + } + }, + { + "name": "Z3SmodRuleGeneric", + "is_activated": true, + "config": {} + }, + { + "name": "Z3lnotRuleGeneric", + "is_activated": true, + "config": {} + }, + { + "name": "Z3setnzRuleGeneric", + "is_activated": true, + "config": {} + }, + { + "name": "Z3setzRuleGeneric", + "is_activated": true, + "config": {} + }, + { + "name": "ExampleGuessingRule", + "is_activated": true, + "config": { + "min_nb_var": 1, + "max_nb_var": 3, + "min_nb_diff_opcodes": 3, + "max_nb_diff_opcodes": 6 + } + } + ], + "blk_rules": [ + { + "name": "Unflattener", + "is_activated": true, + "config": {} + }, + { + "name": "JumpFixer", + "is_activated": true, + "config": { + "enabled_rules": [ + "CompareConstantRule1", + "CompareConstantRule2", + "CompareConstantRule3", + "JaeRule1", + "JbRule1", + "JnzRule1", + "JnzRule2", + "JnzRule3", + "JnzRule4", + "JnzRule5", + "JnzRule6", + "JnzRule7", + "JnzRule8" + ] + } + } + ] +} \ No newline at end of file diff --git a/d810/conf/default_unflattening_switch_case.json b/d810/conf/default_unflattening_switch_case.json new file mode 100644 index 0000000..81db15a --- /dev/null +++ b/d810/conf/default_unflattening_switch_case.json @@ -0,0 +1,926 @@ +{ + "description": "Unflattening Tigress with switch case dispatcher", + "ins_rules": [ + { + "name": "AddXor_Rule_1", + "is_activated": true, + "config": {} + }, + { + "name": "AddXor_Rule_2", + "is_activated": true, + "config": {} + }, + { + "name": "Add_HackersDelightRule_1", + "is_activated": true, + "config": {} + }, + { + "name": "Add_HackersDelightRule_2", + "is_activated": true, + "config": {} + }, + { + "name": "Add_HackersDelightRule_3", + "is_activated": true, + "config": {} + }, + { + "name": "Add_HackersDelightRule_4", + "is_activated": true, + "config": {} + }, + { + "name": "Add_HackersDelightRule_5", + "is_activated": true, + "config": {} + }, + { + "name": "Add_OllvmRule_1", + "is_activated": true, + "config": {} + }, + { + "name": "Add_OllvmRule_2", + "is_activated": true, + "config": {} + }, + { + "name": "Add_OllvmRule_3", + "is_activated": true, + "config": {} + }, + { + "name": "Add_OllvmRule_4", + "is_activated": true, + "config": {} + }, + { + "name": "Add_SpecialConstantRule_1", + "is_activated": true, + "config": {} + }, + { + "name": "Add_SpecialConstantRule_2", + "is_activated": true, + "config": {} + }, + { + "name": "Add_SpecialConstantRule_3", + "is_activated": true, + "config": {} + }, + { + "name": "And1_MbaRule_1", + "is_activated": true, + "config": {} + }, + { + "name": "AndBnot_FactorRule_1", + "is_activated": true, + "config": {} + }, + { + "name": "AndBnot_FactorRule_2", + "is_activated": true, + "config": {} + }, + { + "name": "AndBnot_FactorRule_3", + "is_activated": true, + "config": {} + }, + { + "name": "AndBnot_FactorRule_4", + "is_activated": true, + "config": {} + }, + { + "name": "AndBnot_HackersDelightRule_1", + "is_activated": true, + "config": {} + }, + { + "name": "AndBnot_HackersDelightRule_2", + "is_activated": true, + "config": {} + }, + { + "name": "AndGetUpperBits_FactorRule_1", + "is_activated": true, + "config": {} + }, + { + "name": "AndOr_FactorRule_1", + "is_activated": true, + "config": {} + }, + { + "name": "AndXor_FactorRule_1", + "is_activated": true, + "config": {} + }, + { + "name": "And_FactorRule_1", + "is_activated": true, + "config": {} + }, + { + "name": "And_FactorRule_2", + "is_activated": true, + "config": {} + }, + { + "name": "And_HackersDelightRule_1", + "is_activated": true, + "config": {} + }, + { + "name": "And_HackersDelightRule_2", + "is_activated": true, + "config": {} + }, + { + "name": "And_HackersDelightRule_3", + "is_activated": true, + "config": {} + }, + { + "name": "And_HackersDelightRule_4", + "is_activated": true, + "config": {} + }, + { + "name": "And_OllvmRule_1", + "is_activated": true, + "config": {} + }, + { + "name": "And_OllvmRule_2", + "is_activated": true, + "config": {} + }, + { + "name": "And_OllvmRule_3", + "is_activated": true, + "config": {} + }, + { + "name": "BnotAdd_MbaRule_1", + "is_activated": true, + "config": {} + }, + { + "name": "BnotAnd_FactorRule_1", + "is_activated": true, + "config": {} + }, + { + "name": "BnotAnd_FactorRule_2", + "is_activated": true, + "config": {} + }, + { + "name": "BnotAnd_FactorRule_3", + "is_activated": true, + "config": {} + }, + { + "name": "BnotAnd_FactorRule_4", + "is_activated": true, + "config": {} + }, + { + "name": "BnotOr_FactorRule_1", + "is_activated": true, + "config": {} + }, + { + "name": "BnotXor_FactorRule_1", + "is_activated": true, + "config": {} + }, + { + "name": "BnotXor_Rule_1", + "is_activated": true, + "config": {} + }, + { + "name": "BnotXor_Rule_2", + "is_activated": true, + "config": {} + }, + { + "name": "BnotXor_Rule_3", + "is_activated": true, + "config": {} + }, + { + "name": "Bnot_FactorRule_1", + "is_activated": true, + "config": {} + }, + { + "name": "Bnot_FactorRule_2", + "is_activated": true, + "config": {} + }, + { + "name": "Bnot_FactorRule_3", + "is_activated": true, + "config": {} + }, + { + "name": "Bnot_FactorRule_4", + "is_activated": true, + "config": {} + }, + { + "name": "Bnot_HackersDelightRule_1", + "is_activated": true, + "config": {} + }, + { + "name": "Bnot_HackersDelightRule_2", + "is_activated": true, + "config": {} + }, + { + "name": "Bnot_MbaRule_1", + "is_activated": true, + "config": {} + }, + { + "name": "Bnot_Rule_1", + "is_activated": true, + "config": {} + }, + { + "name": "Bnot_XorRule_1", + "is_activated": true, + "config": {} + }, + { + "name": "CstSimplificationRule1", + "is_activated": true, + "config": {} + }, + { + "name": "CstSimplificationRule10", + "is_activated": true, + "config": {} + }, + { + "name": "CstSimplificationRule11", + "is_activated": true, + "config": {} + }, + { + "name": "CstSimplificationRule12", + "is_activated": true, + "config": {} + }, + { + "name": "CstSimplificationRule13", + "is_activated": true, + "config": {} + }, + { + "name": "CstSimplificationRule14", + "is_activated": true, + "config": {} + }, + { + "name": "CstSimplificationRule15", + "is_activated": true, + "config": {} + }, + { + "name": "CstSimplificationRule16", + "is_activated": true, + "config": {} + }, + { + "name": "CstSimplificationRule17", + "is_activated": true, + "config": {} + }, + { + "name": "CstSimplificationRule18", + "is_activated": true, + "config": {} + }, + { + "name": "CstSimplificationRule19", + "is_activated": true, + "config": {} + }, + { + "name": "CstSimplificationRule2", + "is_activated": true, + "config": {} + }, + { + "name": "CstSimplificationRule20", + "is_activated": true, + "config": {} + }, + { + "name": "CstSimplificationRule21", + "is_activated": true, + "config": {} + }, + { + "name": "CstSimplificationRule22", + "is_activated": true, + "config": {} + }, + { + "name": "CstSimplificationRule3", + "is_activated": true, + "config": {} + }, + { + "name": "CstSimplificationRule4", + "is_activated": true, + "config": {} + }, + { + "name": "CstSimplificationRule5", + "is_activated": true, + "config": {} + }, + { + "name": "CstSimplificationRule6", + "is_activated": true, + "config": {} + }, + { + "name": "CstSimplificationRule7", + "is_activated": true, + "config": {} + }, + { + "name": "CstSimplificationRule8", + "is_activated": true, + "config": {} + }, + { + "name": "CstSimplificationRule9", + "is_activated": true, + "config": {} + }, + { + "name": "GetIdentRule1", + "is_activated": true, + "config": {} + }, + { + "name": "GetIdentRule2", + "is_activated": true, + "config": {} + }, + { + "name": "GetIdentRule3", + "is_activated": true, + "config": {} + }, + { + "name": "Mul_FactorRule_1", + "is_activated": true, + "config": {} + }, + { + "name": "Mul_FactorRule_2", + "is_activated": true, + "config": {} + }, + { + "name": "Mul_MbaRule_1", + "is_activated": true, + "config": {} + }, + { + "name": "Mul_MbaRule_2", + "is_activated": true, + "config": {} + }, + { + "name": "Mul_MbaRule_3", + "is_activated": true, + "config": {} + }, + { + "name": "Mul_MbaRule_4", + "is_activated": true, + "config": {} + }, + { + "name": "NegAdd_HackersDelightRule_1", + "is_activated": true, + "config": {} + }, + { + "name": "NegAdd_HackersDelightRule_2", + "is_activated": true, + "config": {} + }, + { + "name": "NegOr_HackersDelightRule_1", + "is_activated": true, + "config": {} + }, + { + "name": "NegXor_HackersDelightRule_1", + "is_activated": true, + "config": {} + }, + { + "name": "NegXor_HackersDelightRule_2", + "is_activated": true, + "config": {} + }, + { + "name": "Neg_HackersDelightRule_1", + "is_activated": true, + "config": {} + }, + { + "name": "Neg_HackersDelightRule_2", + "is_activated": true, + "config": {} + }, + { + "name": "OrBnot_FactorRule_1", + "is_activated": true, + "config": {} + }, + { + "name": "OrBnot_FactorRule_2", + "is_activated": true, + "config": {} + }, + { + "name": "OrBnot_FactorRule_3", + "is_activated": true, + "config": {} + }, + { + "name": "OrBnot_FactorRule_4", + "is_activated": true, + "config": {} + }, + { + "name": "Or_FactorRule_1", + "is_activated": true, + "config": {} + }, + { + "name": "Or_FactorRule_2", + "is_activated": true, + "config": {} + }, + { + "name": "Or_FactorRule_3", + "is_activated": true, + "config": {} + }, + { + "name": "Or_HackersDelightRule_1", + "is_activated": true, + "config": {} + }, + { + "name": "Or_HackersDelightRule_2", + "is_activated": true, + "config": {} + }, + { + "name": "Or_HackersDelightRule_2_variant_1", + "is_activated": true, + "config": {} + }, + { + "name": "Or_MbaRule_1", + "is_activated": true, + "config": {} + }, + { + "name": "Or_MbaRule_2", + "is_activated": true, + "config": {} + }, + { + "name": "Or_MbaRule_3", + "is_activated": true, + "config": {} + }, + { + "name": "Or_OllvmRule_1", + "is_activated": true, + "config": {} + }, + { + "name": "Or_Rule_1", + "is_activated": true, + "config": {} + }, + { + "name": "Or_Rule_2", + "is_activated": true, + "config": {} + }, + { + "name": "Or_Rule_3", + "is_activated": true, + "config": {} + }, + { + "name": "Or_Rule_4", + "is_activated": true, + "config": {} + }, + { + "name": "Pred0Rule1", + "is_activated": true, + "config": {} + }, + { + "name": "Pred0Rule2", + "is_activated": true, + "config": {} + }, + { + "name": "Pred0Rule3", + "is_activated": true, + "config": {} + }, + { + "name": "Pred0Rule4", + "is_activated": true, + "config": {} + }, + { + "name": "Pred0Rule5", + "is_activated": true, + "config": {} + }, + { + "name": "PredFFRule1", + "is_activated": true, + "config": {} + }, + { + "name": "PredFFRule2", + "is_activated": true, + "config": {} + }, + { + "name": "PredFFRule3", + "is_activated": true, + "config": {} + }, + { + "name": "PredFFRule4", + "is_activated": true, + "config": {} + }, + { + "name": "PredOdd1", + "is_activated": true, + "config": {} + }, + { + "name": "PredOdd2", + "is_activated": true, + "config": {} + }, + { + "name": "PredOr1_Rule_1", + "is_activated": true, + "config": {} + }, + { + "name": "PredOr2_Rule_1", + "is_activated": true, + "config": {} + }, + { + "name": "PredSetbRule1", + "is_activated": true, + "config": {} + }, + { + "name": "PredSetnzRule1", + "is_activated": true, + "config": {} + }, + { + "name": "PredSetnzRule2", + "is_activated": true, + "config": {} + }, + { + "name": "PredSetnzRule3", + "is_activated": true, + "config": {} + }, + { + "name": "PredSetnzRule4", + "is_activated": true, + "config": {} + }, + { + "name": "PredSetnzRule5", + "is_activated": true, + "config": {} + }, + { + "name": "PredSetnzRule6", + "is_activated": true, + "config": {} + }, + { + "name": "PredSetnzRule8", + "is_activated": true, + "config": {} + }, + { + "name": "PredSetzRule1", + "is_activated": true, + "config": {} + }, + { + "name": "PredSetzRule2", + "is_activated": true, + "config": {} + }, + { + "name": "PredSetzRule3", + "is_activated": true, + "config": {} + }, + { + "name": "Sub1Add_HackersDelightRule_1", + "is_activated": true, + "config": {} + }, + { + "name": "Sub1And1_MbaRule_1", + "is_activated": true, + "config": {} + }, + { + "name": "Sub1And_HackersDelightRule_1", + "is_activated": true, + "config": {} + }, + { + "name": "Sub1Or_MbaRule_1", + "is_activated": true, + "config": {} + }, + { + "name": "Sub1_FactorRule_1", + "is_activated": true, + "config": {} + }, + { + "name": "Sub1_FactorRule_2", + "is_activated": true, + "config": {} + }, + { + "name": "Sub_HackersDelightRule_1", + "is_activated": true, + "config": {} + }, + { + "name": "Sub_HackersDelightRule_2", + "is_activated": true, + "config": {} + }, + { + "name": "Sub_HackersDelightRule_3", + "is_activated": true, + "config": {} + }, + { + "name": "Sub_HackersDelightRule_4", + "is_activated": true, + "config": {} + }, + { + "name": "WeirdRule1", + "is_activated": true, + "config": {} + }, + { + "name": "WeirdRule2", + "is_activated": true, + "config": {} + }, + { + "name": "WeirdRule3", + "is_activated": true, + "config": {} + }, + { + "name": "WeirdRule4", + "is_activated": true, + "config": {} + }, + { + "name": "WeirdRule5", + "is_activated": true, + "config": {} + }, + { + "name": "WeirdRule6", + "is_activated": true, + "config": {} + }, + { + "name": "Xor1_MbaRule_1", + "is_activated": true, + "config": {} + }, + { + "name": "XorAlmost_Rule_1", + "is_activated": true, + "config": {} + }, + { + "name": "Xor_FactorRule_1", + "is_activated": true, + "config": {} + }, + { + "name": "Xor_FactorRule_2", + "is_activated": true, + "config": {} + }, + { + "name": "Xor_FactorRule_3", + "is_activated": true, + "config": {} + }, + { + "name": "Xor_HackersDelightRule_1", + "is_activated": true, + "config": {} + }, + { + "name": "Xor_HackersDelightRule_2", + "is_activated": true, + "config": {} + }, + { + "name": "Xor_HackersDelightRule_3", + "is_activated": true, + "config": {} + }, + { + "name": "Xor_HackersDelightRule_4", + "is_activated": true, + "config": {} + }, + { + "name": "Xor_HackersDelightRule_5", + "is_activated": true, + "config": {} + }, + { + "name": "Xor_MbaRule_1", + "is_activated": true, + "config": {} + }, + { + "name": "Xor_MbaRule_2", + "is_activated": true, + "config": {} + }, + { + "name": "Xor_MbaRule_3", + "is_activated": true, + "config": {} + }, + { + "name": "Xor_NestedStuff", + "is_activated": true, + "config": {} + }, + { + "name": "Xor_Rule_1", + "is_activated": true, + "config": {} + }, + { + "name": "Xor_Rule_2", + "is_activated": true, + "config": {} + }, + { + "name": "Xor_Rule_3", + "is_activated": true, + "config": {} + }, + { + "name": "Xor_SpecialConstantRule_1", + "is_activated": true, + "config": {} + }, + { + "name": "Xor_SpecialConstantRule_2", + "is_activated": true, + "config": {} + }, + { + "name": "AndChain", + "is_activated": true, + "config": {} + }, + { + "name": "ArithmeticChain", + "is_activated": true, + "config": {} + }, + { + "name": "OrChain", + "is_activated": true, + "config": {} + }, + { + "name": "XorChain", + "is_activated": true, + "config": {} + }, + { + "name": "Z3ConstantOptimization", + "is_activated": true, + "config": { + "min_nb_opcode": 4, + "min_nb_constant": 3 + } + }, + { + "name": "Z3SmodRuleGeneric", + "is_activated": true, + "config": {} + }, + { + "name": "Z3lnotRuleGeneric", + "is_activated": true, + "config": {} + }, + { + "name": "Z3setnzRuleGeneric", + "is_activated": true, + "config": {} + }, + { + "name": "Z3setzRuleGeneric", + "is_activated": true, + "config": {} + }, + { + "name": "ExampleGuessingRule", + "is_activated": true, + "config": { + "min_nb_var": 1, + "max_nb_var": 3, + "min_nb_diff_opcodes": 3, + "max_nb_diff_opcodes": 6 + } + } + ], + "blk_rules": [ + { + "name": "UnflattenerSwitchCase", + "is_activated": true, + "config": {} + }, + { + "name": "JumpFixer", + "is_activated": true, + "config": { + "enabled_rules": [ + "CompareConstantRule1", + "CompareConstantRule2", + "CompareConstantRule3", + "JaeRule1", + "JbRule1", + "JnzRule1", + "JnzRule2", + "JnzRule3", + "JnzRule4", + "JnzRule5", + "JnzRule6", + "JnzRule7", + "JnzRule8" + ] + } + } + ] +} \ No newline at end of file diff --git a/d810/conf/example_anel.json b/d810/conf/example_anel.json new file mode 100644 index 0000000..33dec07 --- /dev/null +++ b/d810/conf/example_anel.json @@ -0,0 +1,942 @@ +{ + "description": "Configuration to deobfuscate ANEL malware", + "ins_rules": [ + { + "name": "AddXor_Rule_1", + "is_activated": true, + "config": {} + }, + { + "name": "AddXor_Rule_2", + "is_activated": true, + "config": {} + }, + { + "name": "Add_HackersDelightRule_1", + "is_activated": true, + "config": {} + }, + { + "name": "Add_HackersDelightRule_2", + "is_activated": true, + "config": {} + }, + { + "name": "Add_HackersDelightRule_3", + "is_activated": true, + "config": {} + }, + { + "name": "Add_HackersDelightRule_4", + "is_activated": true, + "config": {} + }, + { + "name": "Add_HackersDelightRule_5", + "is_activated": true, + "config": {} + }, + { + "name": "Add_OllvmRule_1", + "is_activated": true, + "config": {} + }, + { + "name": "Add_OllvmRule_2", + "is_activated": true, + "config": {} + }, + { + "name": "Add_OllvmRule_3", + "is_activated": true, + "config": {} + }, + { + "name": "Add_OllvmRule_4", + "is_activated": true, + "config": {} + }, + { + "name": "Add_SpecialConstantRule_1", + "is_activated": true, + "config": {} + }, + { + "name": "Add_SpecialConstantRule_2", + "is_activated": true, + "config": {} + }, + { + "name": "Add_SpecialConstantRule_3", + "is_activated": true, + "config": {} + }, + { + "name": "And1_MbaRule_1", + "is_activated": true, + "config": {} + }, + { + "name": "AndBnot_FactorRule_1", + "is_activated": true, + "config": {} + }, + { + "name": "AndBnot_FactorRule_2", + "is_activated": true, + "config": {} + }, + { + "name": "AndBnot_FactorRule_3", + "is_activated": true, + "config": {} + }, + { + "name": "AndBnot_FactorRule_4", + "is_activated": true, + "config": {} + }, + { + "name": "AndBnot_HackersDelightRule_1", + "is_activated": true, + "config": {} + }, + { + "name": "AndBnot_HackersDelightRule_2", + "is_activated": true, + "config": {} + }, + { + "name": "AndGetUpperBits_FactorRule_1", + "is_activated": true, + "config": {} + }, + { + "name": "AndOr_FactorRule_1", + "is_activated": true, + "config": {} + }, + { + "name": "AndXor_FactorRule_1", + "is_activated": true, + "config": {} + }, + { + "name": "And_FactorRule_1", + "is_activated": true, + "config": {} + }, + { + "name": "And_FactorRule_2", + "is_activated": true, + "config": {} + }, + { + "name": "And_HackersDelightRule_1", + "is_activated": true, + "config": {} + }, + { + "name": "And_HackersDelightRule_2", + "is_activated": true, + "config": {} + }, + { + "name": "And_HackersDelightRule_3", + "is_activated": true, + "config": {} + }, + { + "name": "And_HackersDelightRule_4", + "is_activated": true, + "config": {} + }, + { + "name": "And_OllvmRule_1", + "is_activated": true, + "config": {} + }, + { + "name": "And_OllvmRule_2", + "is_activated": true, + "config": {} + }, + { + "name": "And_OllvmRule_3", + "is_activated": true, + "config": {} + }, + { + "name": "BnotAdd_MbaRule_1", + "is_activated": true, + "config": {} + }, + { + "name": "BnotAnd_FactorRule_1", + "is_activated": true, + "config": {} + }, + { + "name": "BnotAnd_FactorRule_2", + "is_activated": true, + "config": {} + }, + { + "name": "BnotAnd_FactorRule_3", + "is_activated": true, + "config": {} + }, + { + "name": "BnotAnd_FactorRule_4", + "is_activated": true, + "config": {} + }, + { + "name": "BnotOr_FactorRule_1", + "is_activated": true, + "config": {} + }, + { + "name": "BnotXor_FactorRule_1", + "is_activated": true, + "config": {} + }, + { + "name": "BnotXor_Rule_1", + "is_activated": true, + "config": {} + }, + { + "name": "BnotXor_Rule_2", + "is_activated": true, + "config": {} + }, + { + "name": "BnotXor_Rule_3", + "is_activated": true, + "config": {} + }, + { + "name": "Bnot_FactorRule_1", + "is_activated": true, + "config": {} + }, + { + "name": "Bnot_FactorRule_2", + "is_activated": true, + "config": {} + }, + { + "name": "Bnot_FactorRule_3", + "is_activated": true, + "config": {} + }, + { + "name": "Bnot_FactorRule_4", + "is_activated": true, + "config": {} + }, + { + "name": "Bnot_HackersDelightRule_1", + "is_activated": true, + "config": {} + }, + { + "name": "Bnot_HackersDelightRule_2", + "is_activated": true, + "config": {} + }, + { + "name": "Bnot_MbaRule_1", + "is_activated": true, + "config": {} + }, + { + "name": "Bnot_Rule_1", + "is_activated": true, + "config": {} + }, + { + "name": "Bnot_XorRule_1", + "is_activated": true, + "config": {} + }, + { + "name": "CstSimplificationRule1", + "is_activated": true, + "config": {} + }, + { + "name": "CstSimplificationRule10", + "is_activated": true, + "config": {} + }, + { + "name": "CstSimplificationRule11", + "is_activated": true, + "config": {} + }, + { + "name": "CstSimplificationRule12", + "is_activated": true, + "config": {} + }, + { + "name": "CstSimplificationRule13", + "is_activated": true, + "config": {} + }, + { + "name": "CstSimplificationRule14", + "is_activated": true, + "config": {} + }, + { + "name": "CstSimplificationRule15", + "is_activated": true, + "config": {} + }, + { + "name": "CstSimplificationRule16", + "is_activated": true, + "config": {} + }, + { + "name": "CstSimplificationRule17", + "is_activated": true, + "config": {} + }, + { + "name": "CstSimplificationRule18", + "is_activated": true, + "config": {} + }, + { + "name": "CstSimplificationRule19", + "is_activated": true, + "config": {} + }, + { + "name": "CstSimplificationRule2", + "is_activated": true, + "config": {} + }, + { + "name": "CstSimplificationRule20", + "is_activated": true, + "config": {} + }, + { + "name": "CstSimplificationRule21", + "is_activated": true, + "config": {} + }, + { + "name": "CstSimplificationRule22", + "is_activated": true, + "config": {} + }, + { + "name": "CstSimplificationRule3", + "is_activated": true, + "config": {} + }, + { + "name": "CstSimplificationRule4", + "is_activated": true, + "config": {} + }, + { + "name": "CstSimplificationRule5", + "is_activated": true, + "config": {} + }, + { + "name": "CstSimplificationRule6", + "is_activated": true, + "config": {} + }, + { + "name": "CstSimplificationRule7", + "is_activated": true, + "config": {} + }, + { + "name": "CstSimplificationRule8", + "is_activated": true, + "config": {} + }, + { + "name": "CstSimplificationRule9", + "is_activated": true, + "config": {} + }, + { + "name": "GetIdentRule1", + "is_activated": true, + "config": {} + }, + { + "name": "GetIdentRule2", + "is_activated": true, + "config": {} + }, + { + "name": "GetIdentRule3", + "is_activated": true, + "config": {} + }, + { + "name": "Mul_FactorRule_1", + "is_activated": true, + "config": {} + }, + { + "name": "Mul_FactorRule_2", + "is_activated": true, + "config": {} + }, + { + "name": "Mul_MbaRule_1", + "is_activated": true, + "config": {} + }, + { + "name": "Mul_MbaRule_2", + "is_activated": true, + "config": {} + }, + { + "name": "Mul_MbaRule_3", + "is_activated": true, + "config": {} + }, + { + "name": "Mul_MbaRule_4", + "is_activated": true, + "config": {} + }, + { + "name": "NegAdd_HackersDelightRule_1", + "is_activated": true, + "config": {} + }, + { + "name": "NegAdd_HackersDelightRule_2", + "is_activated": true, + "config": {} + }, + { + "name": "NegOr_HackersDelightRule_1", + "is_activated": true, + "config": {} + }, + { + "name": "NegXor_HackersDelightRule_1", + "is_activated": true, + "config": {} + }, + { + "name": "NegXor_HackersDelightRule_2", + "is_activated": true, + "config": {} + }, + { + "name": "Neg_HackersDelightRule_1", + "is_activated": true, + "config": {} + }, + { + "name": "Neg_HackersDelightRule_2", + "is_activated": true, + "config": {} + }, + { + "name": "OrBnot_FactorRule_1", + "is_activated": true, + "config": {} + }, + { + "name": "OrBnot_FactorRule_2", + "is_activated": true, + "config": {} + }, + { + "name": "OrBnot_FactorRule_3", + "is_activated": true, + "config": {} + }, + { + "name": "OrBnot_FactorRule_4", + "is_activated": true, + "config": {} + }, + { + "name": "Or_FactorRule_1", + "is_activated": true, + "config": {} + }, + { + "name": "Or_FactorRule_2", + "is_activated": true, + "config": {} + }, + { + "name": "Or_FactorRule_3", + "is_activated": true, + "config": {} + }, + { + "name": "Or_HackersDelightRule_1", + "is_activated": true, + "config": {} + }, + { + "name": "Or_HackersDelightRule_2", + "is_activated": true, + "config": {} + }, + { + "name": "Or_HackersDelightRule_2_variant_1", + "is_activated": true, + "config": {} + }, + { + "name": "Or_MbaRule_1", + "is_activated": true, + "config": {} + }, + { + "name": "Or_MbaRule_2", + "is_activated": true, + "config": {} + }, + { + "name": "Or_MbaRule_3", + "is_activated": true, + "config": {} + }, + { + "name": "Or_OllvmRule_1", + "is_activated": true, + "config": {} + }, + { + "name": "Or_Rule_1", + "is_activated": true, + "config": {} + }, + { + "name": "Or_Rule_2", + "is_activated": true, + "config": {} + }, + { + "name": "Or_Rule_3", + "is_activated": true, + "config": {} + }, + { + "name": "Or_Rule_4", + "is_activated": true, + "config": {} + }, + { + "name": "Pred0Rule1", + "is_activated": true, + "config": {} + }, + { + "name": "Pred0Rule2", + "is_activated": true, + "config": {} + }, + { + "name": "Pred0Rule3", + "is_activated": true, + "config": {} + }, + { + "name": "Pred0Rule4", + "is_activated": true, + "config": {} + }, + { + "name": "Pred0Rule5", + "is_activated": true, + "config": {} + }, + { + "name": "PredFFRule1", + "is_activated": true, + "config": {} + }, + { + "name": "PredFFRule2", + "is_activated": true, + "config": {} + }, + { + "name": "PredFFRule3", + "is_activated": true, + "config": {} + }, + { + "name": "PredFFRule4", + "is_activated": true, + "config": {} + }, + { + "name": "PredOdd1", + "is_activated": true, + "config": {} + }, + { + "name": "PredOdd2", + "is_activated": true, + "config": {} + }, + { + "name": "PredOr1_Rule_1", + "is_activated": true, + "config": {} + }, + { + "name": "PredOr2_Rule_1", + "is_activated": true, + "config": {} + }, + { + "name": "PredSetbRule1", + "is_activated": true, + "config": {} + }, + { + "name": "PredSetnzRule1", + "is_activated": true, + "config": {} + }, + { + "name": "PredSetnzRule2", + "is_activated": true, + "config": {} + }, + { + "name": "PredSetnzRule3", + "is_activated": true, + "config": {} + }, + { + "name": "PredSetnzRule4", + "is_activated": true, + "config": {} + }, + { + "name": "PredSetnzRule5", + "is_activated": true, + "config": {} + }, + { + "name": "PredSetnzRule6", + "is_activated": true, + "config": {} + }, + { + "name": "PredSetnzRule8", + "is_activated": true, + "config": {} + }, + { + "name": "PredSetzRule1", + "is_activated": true, + "config": {} + }, + { + "name": "PredSetzRule2", + "is_activated": true, + "config": {} + }, + { + "name": "PredSetzRule3", + "is_activated": true, + "config": {} + }, + { + "name": "Sub1Add_HackersDelightRule_1", + "is_activated": true, + "config": {} + }, + { + "name": "Sub1And1_MbaRule_1", + "is_activated": true, + "config": {} + }, + { + "name": "Sub1And_HackersDelightRule_1", + "is_activated": true, + "config": {} + }, + { + "name": "Sub1Or_MbaRule_1", + "is_activated": true, + "config": {} + }, + { + "name": "Sub1_FactorRule_1", + "is_activated": true, + "config": {} + }, + { + "name": "Sub1_FactorRule_2", + "is_activated": true, + "config": {} + }, + { + "name": "Sub_HackersDelightRule_1", + "is_activated": true, + "config": {} + }, + { + "name": "Sub_HackersDelightRule_2", + "is_activated": true, + "config": {} + }, + { + "name": "Sub_HackersDelightRule_3", + "is_activated": true, + "config": {} + }, + { + "name": "Sub_HackersDelightRule_4", + "is_activated": true, + "config": {} + }, + { + "name": "WeirdRule1", + "is_activated": true, + "config": {} + }, + { + "name": "WeirdRule2", + "is_activated": true, + "config": {} + }, + { + "name": "WeirdRule3", + "is_activated": true, + "config": {} + }, + { + "name": "WeirdRule4", + "is_activated": true, + "config": {} + }, + { + "name": "WeirdRule5", + "is_activated": true, + "config": {} + }, + { + "name": "WeirdRule6", + "is_activated": true, + "config": {} + }, + { + "name": "Xor1_MbaRule_1", + "is_activated": true, + "config": {} + }, + { + "name": "XorAlmost_Rule_1", + "is_activated": true, + "config": {} + }, + { + "name": "Xor_FactorRule_1", + "is_activated": true, + "config": {} + }, + { + "name": "Xor_FactorRule_2", + "is_activated": true, + "config": {} + }, + { + "name": "Xor_FactorRule_3", + "is_activated": true, + "config": {} + }, + { + "name": "Xor_HackersDelightRule_1", + "is_activated": true, + "config": {} + }, + { + "name": "Xor_HackersDelightRule_2", + "is_activated": true, + "config": {} + }, + { + "name": "Xor_HackersDelightRule_3", + "is_activated": true, + "config": {} + }, + { + "name": "Xor_HackersDelightRule_4", + "is_activated": true, + "config": {} + }, + { + "name": "Xor_HackersDelightRule_5", + "is_activated": true, + "config": {} + }, + { + "name": "Xor_MbaRule_1", + "is_activated": true, + "config": {} + }, + { + "name": "Xor_MbaRule_2", + "is_activated": true, + "config": {} + }, + { + "name": "Xor_MbaRule_3", + "is_activated": true, + "config": {} + }, + { + "name": "Xor_NestedStuff", + "is_activated": true, + "config": {} + }, + { + "name": "Xor_Rule_1", + "is_activated": true, + "config": {} + }, + { + "name": "Xor_Rule_2", + "is_activated": true, + "config": {} + }, + { + "name": "Xor_Rule_3", + "is_activated": true, + "config": {} + }, + { + "name": "Xor_SpecialConstantRule_1", + "is_activated": true, + "config": {} + }, + { + "name": "Xor_SpecialConstantRule_2", + "is_activated": true, + "config": {} + }, + { + "name": "AndChain", + "is_activated": true, + "config": {} + }, + { + "name": "ArithmeticChain", + "is_activated": true, + "config": {} + }, + { + "name": "OrChain", + "is_activated": true, + "config": {} + }, + { + "name": "XorChain", + "is_activated": true, + "config": {} + }, + { + "name": "Z3ConstantOptimization", + "is_activated": true, + "config": { + "min_nb_opcode": 4, + "min_nb_constant": 3 + } + }, + { + "name": "Z3SmodRuleGeneric", + "is_activated": true, + "config": {} + }, + { + "name": "Z3lnotRuleGeneric", + "is_activated": true, + "config": {} + }, + { + "name": "Z3setnzRuleGeneric", + "is_activated": true, + "config": {} + }, + { + "name": "Z3setzRuleGeneric", + "is_activated": true, + "config": {} + }, + { + "name": "SetGlobalVariablesToZeroIfDetectedReadOnly", + "is_activated": true, + "config": {} + }, + { + "name": "ExampleGuessingRule", + "is_activated": true, + "config": { + "min_nb_var": 1, + "max_nb_var": 3, + "min_nb_diff_opcodes": 3, + "max_nb_diff_opcodes": 6 + } + } + ], + "blk_rules": [ + { + "name": "Unflattener", + "is_activated": true, + "config": { + "maturities": [ + "MMAT_CALLS", + "MMAT_GLBOPT1", + "MMAT_GLBOPT2" + ] + } + }, + { + "name": "UnflattenerFakeJump", + "is_activated": true, + "config": {} + }, + { + "name": "JumpFixer", + "is_activated": true, + "config": { + "enabled_rules": [ + "CompareConstantRule1", + "CompareConstantRule2", + "CompareConstantRule3", + "JaeRule1", + "JbRule1", + "JnzRule1", + "JnzRule2", + "JnzRule3", + "JnzRule4", + "JnzRule5", + "JnzRule6", + "JnzRule7", + "JnzRule8" + ] + } + } + ] +} \ No newline at end of file diff --git a/d810/conf/options.json b/d810/conf/options.json new file mode 100644 index 0000000..cee4a67 --- /dev/null +++ b/d810/conf/options.json @@ -0,0 +1,14 @@ +{ + "erase_logs_on_reload": true, + "generate_z3_code": true, + "dump_intermediate_microcode": true, + "log_dir": null, + "configurations": [ + "default_instruction_only.json", + "default_unflattening_ollvm.json", + "default_unflattening_switch_case.json", + "example_anel.json", + "example_unflattening_indirect.json" + ], + "last_project_index": 0 +} \ No newline at end of file diff --git a/d810/docs/source/images/gui_plugin_configuration.png b/d810/docs/source/images/gui_plugin_configuration.png new file mode 100644 index 0000000..0c634d8 Binary files /dev/null and b/d810/docs/source/images/gui_plugin_configuration.png differ diff --git a/d810/emulator.py b/d810/emulator.py new file mode 100644 index 0000000..340ddaf --- /dev/null +++ b/d810/emulator.py @@ -0,0 +1,505 @@ +from __future__ import annotations +import logging +from typing import List, Union, Dict +from idaapi import getseg, get_qword, SEGPERM_WRITE +from ida_hexrays import * + +from d810.utils import unsigned_to_signed, signed_to_unsigned, get_add_cf, get_add_of, get_sub_of, ror, get_parity_flag +from d810.hexrays_helpers import equal_mops_ignore_size, get_mop_index, AND_TABLE, CONTROL_FLOW_OPCODES, \ + CONDITIONAL_JUMP_OPCODES +from d810.hexrays_formatters import format_minsn_t, format_mop_t, mop_type_to_string, opcode_to_string +from d810.cfg_utils import get_block_serials_by_address +from d810.errors import EmulationException, EmulationIndirectJumpException, UnresolvedMopException, \ + WritableMemoryReadException + +emulator_log = logging.getLogger('D810.emulator') + + +class MicroCodeInterpreter(object): + def __init__(self, global_environment=None): + self.global_environment = MicroCodeEnvironment() if global_environment is None else global_environment + + def _eval_instruction_and_update_environment(self, blk: mblock_t, ins: minsn_t, environment: MicroCodeEnvironment) -> Union[None, int]: + environment.set_cur_flow(blk, ins) + res = self._eval_instruction(ins, environment) + if res is not None: + if (ins.d is not None) and ins.d.t != mop_z: + environment.assign(ins.d, res, auto_define=True) + return res + + def _eval_instruction(self, ins: minsn_t, environment: MicroCodeEnvironment) -> Union[None, int]: + if ins is None: + return None + is_flow_instruction = self._eval_control_flow_instruction(ins, environment) + if is_flow_instruction: + return None + call_helper_res = self._eval_call_helper(ins, environment) + if call_helper_res is not None: + return call_helper_res + if ins.opcode == m_call: + return self._eval_call(ins, environment) + elif ins.opcode == m_icall: + return self._eval_call(ins, environment) + res_mask = AND_TABLE[ins.d.size] + if ins.opcode == m_ldx: + return self._eval_load(ins, environment) + elif ins.opcode == m_stx: + return self._eval_store(ins, environment) + elif ins.opcode == m_mov: + return (self.eval(ins.l, environment)) & res_mask + elif ins.opcode == m_neg: + return (- self.eval(ins.l, environment)) & res_mask + elif ins.opcode == m_lnot: + return self.eval(ins.l, environment) != 0 + elif ins.opcode == m_bnot: + return (self.eval(ins.l, environment) ^ res_mask) & res_mask + elif ins.opcode == m_xds: + left_value_signed = unsigned_to_signed(self.eval(ins.l, environment), ins.l.size) + return signed_to_unsigned(left_value_signed, ins.d.size) & res_mask + elif ins.opcode == m_xdu: + return (self.eval(ins.l, environment)) & res_mask + elif ins.opcode == m_low: + return (self.eval(ins.l, environment)) & res_mask + elif ins.opcode == m_add: + return (self.eval(ins.l, environment) + self.eval(ins.r, environment)) & res_mask + elif ins.opcode == m_sub: + return (self.eval(ins.l, environment) - self.eval(ins.r, environment)) & res_mask + elif ins.opcode == m_mul: + return (self.eval(ins.l, environment) * self.eval(ins.r, environment)) & res_mask + elif ins.opcode == m_udiv: + return (self.eval(ins.l, environment) // self.eval(ins.r, environment)) & res_mask + elif ins.opcode == m_sdiv: + return (self.eval(ins.l, environment) // self.eval(ins.r, environment)) & res_mask + elif ins.opcode == m_umod: + return (self.eval(ins.l, environment) % self.eval(ins.r, environment)) & res_mask + elif ins.opcode == m_smod: + return (self.eval(ins.l, environment) % self.eval(ins.r, environment)) & res_mask + elif ins.opcode == m_or: + return (self.eval(ins.l, environment) | self.eval(ins.r, environment)) & res_mask + elif ins.opcode == m_and: + return (self.eval(ins.l, environment) & self.eval(ins.r, environment)) & res_mask + elif ins.opcode == m_xor: + return (self.eval(ins.l, environment) ^ self.eval(ins.r, environment)) & res_mask + elif ins.opcode == m_shl: + return (self.eval(ins.l, environment) << self.eval(ins.r, environment)) & res_mask + elif ins.opcode == m_shr: + return (self.eval(ins.l, environment) >> self.eval(ins.r, environment)) & res_mask + elif ins.opcode == m_sar: + res_signed = unsigned_to_signed(self.eval(ins.l, environment), ins.l.size) >> self.eval(ins.r, environment) + return signed_to_unsigned(res_signed, ins.d.size) & res_mask + elif ins.opcode == m_cfadd: + tmp = get_add_cf(self.eval(ins.l, environment), self.eval(ins.r, environment), ins.l.size) + return tmp & res_mask + elif ins.opcode == m_ofadd: + tmp = get_add_of(self.eval(ins.l, environment), self.eval(ins.r, environment), ins.l.size) + return tmp & res_mask + elif ins.opcode == m_sets: + left_value_signed = unsigned_to_signed(self.eval(ins.l, environment), ins.l.size) + res = 1 if left_value_signed < 0 else 0 + return res & res_mask + elif ins.opcode == m_seto: + left_value_signed = unsigned_to_signed(self.eval(ins.l, environment), ins.l.size) + right_value_signed = unsigned_to_signed(self.eval(ins.r, environment), ins.r.size) + sub_overflow = get_sub_of(left_value_signed, right_value_signed, ins.l.size) + return sub_overflow & res_mask + elif ins.opcode == m_setnz: + res = 1 if self.eval(ins.l, environment) != self.eval(ins.r, environment) else 0 + return res & res_mask + elif ins.opcode == m_setz: + res = 1 if self.eval(ins.l, environment) == self.eval(ins.r, environment) else 0 + return res & res_mask + elif ins.opcode == m_setae: + res = 1 if self.eval(ins.l, environment) >= self.eval(ins.r, environment) else 0 + return res & res_mask + elif ins.opcode == m_setb: + res = 1 if self.eval(ins.l, environment) < self.eval(ins.r, environment) else 0 + return res & res_mask + elif ins.opcode == m_seta: + res = 1 if self.eval(ins.l, environment) > self.eval(ins.r, environment) else 0 + return res & res_mask + elif ins.opcode == m_setbe: + res = 1 if self.eval(ins.l, environment) <= self.eval(ins.r, environment) else 0 + return res & res_mask + elif ins.opcode == m_setg: + left_value = unsigned_to_signed(self.eval(ins.l, environment), ins.l.size) + right_value = unsigned_to_signed(self.eval(ins.r, environment), ins.r.size) + res = 1 if left_value > right_value else 0 + return res & res_mask + elif ins.opcode == m_setge: + left_value = unsigned_to_signed(self.eval(ins.l, environment), ins.l.size) + right_value = unsigned_to_signed(self.eval(ins.r, environment), ins.r.size) + res = 1 if left_value >= right_value else 0 + return res & res_mask + elif ins.opcode == m_setl: + left_value = unsigned_to_signed(self.eval(ins.l, environment), ins.l.size) + right_value = unsigned_to_signed(self.eval(ins.r, environment), ins.r.size) + res = 1 if left_value < right_value else 0 + return res & res_mask + elif ins.opcode == m_setle: + left_value = unsigned_to_signed(self.eval(ins.l, environment), ins.l.size) + right_value = unsigned_to_signed(self.eval(ins.r, environment), ins.r.size) + res = 1 if left_value <= right_value else 0 + return res & res_mask + elif ins.opcode == m_setp: + res = get_parity_flag(self.eval(ins.l, environment), self.eval(ins.r, environment), ins.l.size) + return res & res_mask + raise EmulationException("Unsupported instruction opcode '{0}': '{1}'" + .format(opcode_to_string(ins.opcode), format_minsn_t(ins))) + + @staticmethod + def _get_blk_serial(mop: mop_t) -> int: + if mop.t == mop_b: + return mop.b + raise EmulationException("Get block serial with an unsupported mop type '{0}': '{1}'" + .format(mop_type_to_string(mop.t), format_mop_t(mop))) + + def _eval_conditional_jump(self, ins: minsn_t, environment: MicroCodeEnvironment) -> Union[None, int]: + if ins.opcode not in CONDITIONAL_JUMP_OPCODES: + return None + if ins.opcode == m_jtbl: + # This is not handled the same way + return None + cur_blk = environment.cur_blk + direct_child_serial = cur_blk.serial + 1 + if ins.opcode == m_jcnd: + jump_taken = self.eval(ins.l, environment) != 0 + elif ins.opcode == m_jnz: + jump_taken = self.eval(ins.l, environment) != self.eval(ins.r, environment) + elif ins.opcode == m_jz: + jump_taken = self.eval(ins.l, environment) == self.eval(ins.r, environment) + elif ins.opcode == m_jae: + jump_taken = self.eval(ins.l, environment) >= self.eval(ins.r, environment) + elif ins.opcode == m_jb: + jump_taken = self.eval(ins.l, environment) < self.eval(ins.r, environment) + elif ins.opcode == m_ja: + jump_taken = self.eval(ins.l, environment) > self.eval(ins.r, environment) + elif ins.opcode == m_jbe: + jump_taken = self.eval(ins.l, environment) <= self.eval(ins.r, environment) + elif ins.opcode == m_jg: + left_value = unsigned_to_signed(self.eval(ins.l, environment), ins.l.size) + right_value = unsigned_to_signed(self.eval(ins.r, environment), ins.r.size) + jump_taken = left_value > right_value + elif ins.opcode == m_jge: + left_value = unsigned_to_signed(self.eval(ins.l, environment), ins.l.size) + right_value = unsigned_to_signed(self.eval(ins.r, environment), ins.r.size) + jump_taken = left_value >= right_value + elif ins.opcode == m_jl: + left_value = unsigned_to_signed(self.eval(ins.l, environment), ins.l.size) + right_value = unsigned_to_signed(self.eval(ins.r, environment), ins.r.size) + jump_taken = left_value < right_value + elif ins.opcode == m_jle: + left_value = unsigned_to_signed(self.eval(ins.l, environment), ins.l.size) + right_value = unsigned_to_signed(self.eval(ins.r, environment), ins.r.size) + jump_taken = left_value <= right_value + else: + # This should never happen + raise EmulationException("Unhandled conditional jump: '{0}'".format(format_minsn_t(ins))) + return self._get_blk_serial(ins.d) if jump_taken else direct_child_serial + + def _eval_control_flow_instruction(self, ins: minsn_t, environment: MicroCodeEnvironment) -> bool: + if ins.opcode not in CONTROL_FLOW_OPCODES: + return False + cur_blk = environment.cur_blk + if cur_blk is None: + raise EmulationException("Can't evaluate control flow instruction with null block: '{0}'" + .format(format_minsn_t(ins))) + + next_blk_serial = self._eval_conditional_jump(ins, environment) + if next_blk_serial is not None: + next_blk = cur_blk.mba.get_mblock(next_blk_serial) + next_ins = next_blk.head + environment.set_next_flow(next_blk, next_ins) + return True + + if ins.opcode == m_goto: + next_blk_serial = self._get_blk_serial(ins.l) + elif ins.opcode == m_jtbl: + left_value = self.eval(ins.l, environment) + cases = ins.r.c + # Initialize to default case + next_blk_serial = [x for x in cases.targets][-1] + for possible_values, target_block_serial in zip(cases.values, cases.targets): + for test_value in possible_values: + if left_value == test_value: + next_blk_serial = target_block_serial + break + elif ins.opcode == m_ijmp: + ijmp_dest_ea = self.eval(ins.d, environment) + dest_block_serials = get_block_serials_by_address(environment.cur_blk.mba, ijmp_dest_ea) + if len(dest_block_serials) == 0: + raise EmulationIndirectJumpException("No blocks found at address {0:x}".format(ijmp_dest_ea), + ijmp_dest_ea, dest_block_serials) + + if len(dest_block_serials) > 1: + raise EmulationIndirectJumpException("Multiple blocks at address {0:x}: {1}".format(ijmp_dest_ea, + dest_block_serials), + ijmp_dest_ea, dest_block_serials) + next_blk_serial = dest_block_serials[0] + + if next_blk_serial is None: + return False + next_blk = cur_blk.mba.get_mblock(next_blk_serial) + next_ins = next_blk.head + environment.set_next_flow(next_blk, next_ins) + return True + + def _eval_call_helper(self, ins: minsn_t, environment: MicroCodeEnvironment) -> Union[None, int]: + # Currently, we only support helper calls, (but end goal is to allow to hook calls) + if ins.opcode != m_call or ins.l.t != mop_h: + return None + res_mask = AND_TABLE[ins.d.size] + helper_name = ins.l.helper + args_list = ins.d + + emulator_log.debug("Call helper for {0}".format(helper_name)) + # and we support only __ROR4__ (we should add other Hex-Rays created helper calls) + if helper_name == "__ROR4__": + data_1 = self.eval(args_list.f.args[0], environment) + data_2 = self.eval(args_list.f.args[1], environment) + return ror(data_1, data_2, 8 * args_list.f.args[0].size) & res_mask + elif helper_name == "__readfsqword": + return 0 + return None + + def _eval_load(self, ins: minsn_t, environment: MicroCodeEnvironment) -> Union[None, int]: + res_mask = AND_TABLE[ins.d.size] + if ins.opcode == m_ldx: + load_address = self.eval(ins.r, environment) + formatted_seg_register = format_mop_t(ins.l) + if formatted_seg_register == "ss.2": + stack_mop = mop_t() + stack_mop.erase() + stack_mop._make_stkvar(environment.cur_blk.mba, load_address) + emulator_log.debug("Searching for stack mop {0}".format(format_mop_t(stack_mop))) + stack_mop_value = environment.lookup(stack_mop) + emulator_log.debug(" stack mop {0} value : {1}".format(format_mop_t(stack_mop), stack_mop_value)) + return stack_mop_value & res_mask + else: + mem_seg = getseg(load_address) + seg_perm = mem_seg.perm + if (seg_perm & SEGPERM_WRITE) != 0: + raise WritableMemoryReadException("ldx {0:x} (writable -> return None)".format(load_address)) + else: + memory_value = get_qword(load_address) + emulator_log.debug("ldx {0:x} (non writable -> return {1:x})" + .format(load_address, memory_value & res_mask)) + return memory_value & res_mask + + def _eval_store(self, ins: minsn_t, environment: MicroCodeEnvironment) -> Union[None, int]: + # TODO: implement + emulator_log.warning("Evaluation of {0} not implemented: bypassing".format(format_minsn_t(ins))) + return None + + def _eval_call(self, ins: minsn_t, environment: MicroCodeEnvironment) -> Union[None, int]: + # TODO: implement + emulator_log.warning("Evaluation of {0} not implemented: bypassing".format(format_minsn_t(ins))) + return None + + def eval(self, mop: mop_t, environment: MicroCodeEnvironment) -> Union[None, int]: + if mop.t == mop_n: + return mop.nnn.value + elif mop.t in [mop_r, mop_S]: + return environment.lookup(mop) + elif mop.t == mop_d: + return self._eval_instruction(mop.d, environment) + elif mop.t == mop_a: + if mop.a.t == mop_v: + emulator_log.debug("Reading a mop_a '{0}' -> {1:x}".format(format_mop_t(mop), mop.a.g)) + return mop.a.g + elif mop.a.t == mop_S: + emulator_log.debug("Reading a mop_a '{0}' -> {1:x}".format(format_mop_t(mop), mop.a.s.off)) + return mop.a.s.off + raise UnresolvedMopException("Calling get_cst with unsupported mop type {0} - {1}: '{2}'" + .format(mop.t, mop.a.t, format_mop_t(mop))) + elif mop.t == mop_v: + mem_seg = getseg(mop.g) + seg_perm = mem_seg.perm + if (seg_perm & SEGPERM_WRITE) != 0: + emulator_log.debug("Reading a (writable) mop_v {0}".format(format_mop_t(mop))) + return environment.lookup(mop) + else: + memory_value = get_qword(mop.g) + emulator_log.debug("Reading a mop_v {0:x} (non writable -> return {1:x})".format(mop.g, memory_value)) + return mop.g + raise EmulationException("Unsupported mop type '{0}': '{1}'" + .format(mop_type_to_string(mop.t), format_mop_t(mop))) + + def eval_instruction(self, blk: mblock_t, ins: minsn_t, environment: Union[None, MicroCodeEnvironment] = None, + raise_exception: bool = False) -> bool: + try: + if environment is None: + environment = self.global_environment + emulator_log.info("Evaluating microcode instruction : '{0}'".format(format_minsn_t(ins))) + if ins is None: + return False + self._eval_instruction_and_update_environment(blk, ins, environment) + return True + except EmulationException as e: + emulator_log.warning("Can't evaluate instruction: '{0}': {1}".format(format_minsn_t(ins), e)) + if raise_exception: + raise e + except Exception as e: + emulator_log.warning("Error during evaluation of: '{0}': {1}".format(format_minsn_t(ins), e)) + if raise_exception: + raise e + return False + + def eval_mop(self, mop: mop_t, environment: Union[None, MicroCodeEnvironment] = None, + raise_exception: bool = False) -> Union[None, int]: + try: + if environment is None: + environment = self.global_environment + res = self.eval(mop, environment) + return res + except EmulationException as e: + emulator_log.warning("Can't get constant mop value: '{0}': {1}".format(format_mop_t(mop), e)) + if raise_exception: + raise e + else: + return None + + +class MopMapping(object): + def __init__(self): + self.mops = [] + self.mops_values = [] + + def __setitem__(self, mop: mop_t, mop_value: int): + mop_index = get_mop_index(mop, self.mops) + mop_value &= AND_TABLE[mop.size] + if mop_index != -1: + self.mops_values[mop_index] = mop_value + return + self.mops.append(mop) + self.mops_values.append(mop_value) + + def __getitem__(self, mop: mop_t) -> int: + mop_index = get_mop_index(mop, self.mops) + if mop_index == -1: + raise KeyError + return self.mops_values[mop_index] + + def __len__(self): + return len(self.mops) + + def __delitem__(self, mop: mop_t): + mop_index = get_mop_index(mop, self.mops) + if mop_index == -1: + raise KeyError + del self.mops[mop_index] + del self.mops_values[mop_index] + + def clear(self): + self.mops = [] + self.mops_values = [] + + def copy(self): + new_mapping = MopMapping() + for mop, mop_value in self.items(): + new_mapping[mop] = mop_value + return new_mapping + + def has_key(self, mop: mop_t): + mop_index = get_mop_index(mop, self.mops) + return mop_index != -1 + + def keys(self) -> List[mop_t]: + return self.mops + + def values(self) -> List[int]: + return self.mops_values + + def items(self): + return [(x, y) for x, y in zip(self.mops, self.mops_values)] + + def __contains__(self, mop: mop_t): + return self.has_key(mop) + + +class MicroCodeEnvironment(object): + def __init__(self, parent: Union[None, MicroCodeEnvironment] = None): + self.parent = parent + self.mop_r_record = MopMapping() + self.mop_S_record = MopMapping() + + self.cur_blk = None + self.cur_ins = None + self.next_blk = None + self.next_ins = None + + def items(self): + return [x for x in self.mop_r_record.items() + self.mop_S_record.items()] + + def get_copy(self, copy_parent=True) -> MicroCodeEnvironment: + parent_copy = self.parent + if parent_copy is not None and copy_parent: + parent_copy = self.parent.get_copy(copy_parent=True) + new_env = MicroCodeEnvironment(parent_copy) + for mop, mop_value in self.mop_r_record.items(): + new_env.define(mop, mop_value) + for mop, mop_value in self.mop_S_record.items(): + new_env.define(mop, mop_value) + new_env.cur_blk = self.cur_blk + new_env.cur_ins = self.cur_ins + new_env.next_blk = self.next_blk + new_env.next_ins = self.next_ins + return new_env + + def set_cur_flow(self, cur_blk: mblock_t, cur_ins: minsn_t): + self.cur_blk = cur_blk + self.cur_ins = cur_ins + self.next_blk = cur_blk + if self.cur_ins is None: + self.next_blk = self.cur_blk.mba.get_mblock(self.cur_blk.serial + 1) + self.next_ins = self.next_blk.head + else: + self.next_ins = self.cur_ins.next + if self.next_ins is None: + self.next_blk = self.cur_blk.mba.get_mblock(self.cur_blk.serial + 1) + self.next_ins = self.next_blk.head + emulator_log.debug( + "Setting next block {0} and next ins {1}".format(self.next_blk.serial, format_minsn_t(self.next_ins))) + + def set_next_flow(self, next_blk: mblock_t, next_ins: minsn_t): + self.next_blk = next_blk + self.next_ins = next_ins + + def define(self, mop: mblock_t, value: int) -> int: + if mop.t == mop_r: + self.mop_r_record[mop] = value + return value + elif mop.t == mop_S: + self.mop_S_record[mop] = value + return value + raise EmulationException("Defining an unsupported mop type '{0}': '{1}'" + .format(mop_type_to_string(mop.t), format_mop_t(mop))) + + def _lookup_mop(self, searched_mop: mop_t, mop_value_dict: Dict[mop_t, int], new_mop_value: Union[None, int] = None, + auto_define=True, raise_exception=True) -> int: + for known_mop, mop_value in mop_value_dict.items(): + if equal_mops_ignore_size(searched_mop, known_mop): + if new_mop_value is not None: + mop_value_dict[searched_mop] = new_mop_value + return new_mop_value + return mop_value + if (new_mop_value is not None) and auto_define: + self.define(searched_mop, new_mop_value) + return new_mop_value + if raise_exception: + raise EmulationException("Variable '{0}' is not defined".format(format_mop_t(searched_mop))) + else: + return None + + def lookup(self, mop: mop_t, raise_exception=True) -> int: + if mop.t == mop_r: + return self._lookup_mop(mop, self.mop_r_record, raise_exception=raise_exception) + elif mop.t == mop_S: + return self._lookup_mop(mop, self.mop_S_record, raise_exception=raise_exception) + + def assign(self, mop: mop_t, value: int, auto_define=True) -> int: + if mop.t == mop_r: + return self._lookup_mop(mop, self.mop_r_record, value, auto_define) + elif mop.t == mop_S: + return self._lookup_mop(mop, self.mop_S_record, value, auto_define) + raise EmulationException("Assigning an unsupported mop type '{0}': '{1}'" + .format(mop_type_to_string(mop.t), format_mop_t(mop))) diff --git a/d810/errors.py b/d810/errors.py new file mode 100644 index 0000000..23c486c --- /dev/null +++ b/d810/errors.py @@ -0,0 +1,41 @@ +class D810Exception(Exception): + pass + + +class AstException(D810Exception): + pass + + +class AstEvaluationException(AstException): + pass + + +class D810Z3Exception(D810Exception): + pass + + +class ControlFlowException(D810Exception): + pass + + +class EmulationException(D810Exception): + pass + + +class EmulationIndirectJumpException(EmulationException): + def __init__(self, message, dest_ea, dest_serial_list): + super().__init__(message) + self.dest_ea = dest_ea + self.dest_serial_list = dest_serial_list + + +class UnresolvedMopException(EmulationException): + pass + + +class WritableMemoryReadException(EmulationException): + pass + + +class UnsupportedInstructionException(EmulationException): + pass diff --git a/d810/hexrays_formatters.py b/d810/hexrays_formatters.py new file mode 100644 index 0000000..10336d2 --- /dev/null +++ b/d810/hexrays_formatters.py @@ -0,0 +1,95 @@ +import os +import logging +from typing import List + +from d810.hexrays_helpers import OPCODES_INFO, MATURITY_TO_STRING_DICT, STRING_TO_MATURITY_DICT, MOP_TYPE_TO_STRING_DICT +from ida_hexrays import minsn_t, mop_t, vd_printer_t, mbl_array_t + + +logger = logging.getLogger('D810.helper') + + +def format_minsn_t(ins: minsn_t) -> str: + if ins is None: + return "minsn_t is None" + + tmp = ins._print() + pp_ins = "".join([c if 0x20 <= ord(c) <= 0x7e else "" for c in tmp]) + return pp_ins + + +def format_mop_t(mop_in: mop_t) -> str: + if mop_in is None: + return "mop_t is None" + if mop_in.t > 15: + # To avoid error 50581 + return "Unknown mop type {0}".format(mop_in.t) + return mop_in.dstr() + + +def format_mop_list(mop_list: List[mop_t]) -> str: + return ", ".join([format_mop_t(x) for x in mop_list]) + + +def maturity_to_string(maturity_level: int) -> str: + return MATURITY_TO_STRING_DICT.get(maturity_level, "Unknown maturity: {0}".format(maturity_level)) + + +def string_to_maturity(maturity_string: str) -> int: + return STRING_TO_MATURITY_DICT.get(maturity_string) + + +def mop_type_to_string(mop_type: int) -> str: + return MOP_TYPE_TO_STRING_DICT.get(mop_type, "Unknown mop type: {0}".format(mop_type)) + + +def opcode_to_string(opcode) -> str: + try: + return OPCODES_INFO[opcode]["name"] + except KeyError: + return "Unknown opcode: {0}".format(opcode) + + +class mba_printer(vd_printer_t): + def __init__(self): + vd_printer_t.__init__(self) + self.mc = [] + + def get_mc(self): + return self.mc + + def _print(self, indent, line): + self.mc.append("".join([c if 0x20 <= ord(c) <= 0x7e else "" for c in line])+"\n") + return 1 + + +class block_printer(vd_printer_t): + def __init__(self): + vd_printer_t.__init__(self) + self.block_ins = [] + + def get_block_mc(self): + return "\n".join(self.block_ins) + + def _print(self, indent, line): + self.block_ins.append("".join([c if 0x20 <= ord(c) <= 0x7e else "" for c in line])) + return 1 + + +def write_mc_to_file(mba: mbl_array_t, filename: str, mba_flags: int = 0) -> bool: + if not mba: + return False + + vp = mba_printer() + mba.set_mba_flags(mba_flags) + mba._print(vp) + + with open(filename, "w") as f: + f.writelines(vp.get_mc()) + return True + + +def dump_microcode_for_debug(mba: mbl_array_t, log_dir_path: str, name: str = ""): + mc_filename = os.path.join(log_dir_path, "{0:x}_maturity_{1}_{2}.log".format(mba.entry_ea, mba.maturity, name)) + logger.info("Dumping microcode in file {0}...".format(mc_filename)) + write_mc_to_file(mba, mc_filename) diff --git a/d810/hexrays_helpers.py b/d810/hexrays_helpers.py new file mode 100644 index 0000000..d8192f9 --- /dev/null +++ b/d810/hexrays_helpers.py @@ -0,0 +1,356 @@ +from ida_hexrays import * +from typing import List, Tuple +from ida_hexrays import mop_d, mop_n, m_stx, m_ldx, m_xdu, m_xds, mop_z, mop_fn, mop_S, mop_v, EQ_IGNSIZE, mop_b, \ + mop_r, mop_f, mop_l, mop_a, mop_h, mop_str, mop_c, mop_p, mop_sc + + +OPCODES_INFO = { + m_nop: {"name": "nop", "nb_operands": 0, "is_commutative": True}, + m_stx: {"name": "stx", "nb_operands": 2, "is_commutative": False}, + m_ldx: {"name": "ldx", "nb_operands": 2, "is_commutative": False}, + m_ldc: {"name": "ldc", "nb_operands": 1, "is_commutative": False}, + m_mov: {"name": "mov", "nb_operands": 1, "is_commutative": False, "symbol": ""}, + m_neg: {"name": "neg", "nb_operands": 1, "is_commutative": False, "symbol": "-"}, + m_lnot: {"name": "lnot", "nb_operands": 1, "is_commutative": False, "symbol": "!"}, + m_bnot: {"name": "bnot", "nb_operands": 1, "is_commutative": False, "symbol": "~"}, + m_xds: {"name": "xds", "nb_operands": 1, "is_commutative": False, "symbol": "xds"}, + m_xdu: {"name": "xdu", "nb_operands": 1, "is_commutative": False, "symbol": "xdu"}, + m_low: {"name": "low", "nb_operands": 1, "is_commutative": False, "symbol": "low"}, + m_high: {"name": "high", "nb_operands": 1, "is_commutative": False, "symbol": "high"}, + m_add: {"name": "add", "nb_operands": 2, "is_commutative": True, "symbol": "+"}, + m_sub: {"name": "sub", "nb_operands": 2, "is_commutative": False, "symbol": "-"}, + m_mul: {"name": "mul", "nb_operands": 2, "is_commutative": True, "symbol": "*"}, + m_udiv: {"name": "udiv", "nb_operands": 2, "is_commutative": False, "symbol": "UDiv"}, + m_sdiv: {"name": "sdiv", "nb_operands": 2, "is_commutative": False, "symbol": "/"}, + m_umod: {"name": "umod", "nb_operands": 2, "is_commutative": False, "symbol": "URem"}, + m_smod: {"name": "smod", "nb_operands": 2, "is_commutative": False, "symbol": "%"}, + m_or: {"name": "or", "nb_operands": 2, "is_commutative": True, "symbol": "|"}, + m_and: {"name": "and", "nb_operands": 2, "is_commutative": True, "symbol": "&"}, + m_xor: {"name": "xor", "nb_operands": 2, "is_commutative": True, "symbol": "^"}, + m_shl: {"name": "shl", "nb_operands": 2, "is_commutative": False, "symbol": "<<"}, + m_shr: {"name": "shr", "nb_operands": 2, "is_commutative": False, "symbol": "LShR"}, + m_sar: {"name": "sar", "nb_operands": 2, "is_commutative": False, "symbol": ">>"}, + m_cfadd: {"name": "cfadd", "nb_operands": 2, "is_commutative": True}, + m_ofadd: {"name": "ofadd", "nb_operands": 2, "is_commutative": True}, + m_cfshl: {"name": "cfshl", "nb_operands": 2, "is_commutative": False}, + m_cfshr: {"name": "cfshr", "nb_operands": 2, "is_commutative": False}, + m_sets: {"name": "sets", "nb_operands": 2, "is_commutative": False}, + m_seto: {"name": "seto", "nb_operands": 2, "is_commutative": False}, + m_setp: {"name": "setp", "nb_operands": 2, "is_commutative": False}, + m_setnz: {"name": "setnz", "nb_operands": 2, "is_commutative": True, "symbol": "!="}, + m_setz: {"name": "setz", "nb_operands": 2, "is_commutative": True, "symbol": "=="}, + m_seta: {"name": "seta", "nb_operands": 2, "is_commutative": False, "symbol": ">"}, + m_setae: {"name": "setae", "nb_operands": 2, "is_commutative": False, "symbol": ">="}, + m_setb: {"name": "setb", "nb_operands": 2, "is_commutative": False, "symbol": "<"}, + m_setbe: {"name": "setbe", "nb_operands": 2, "is_commutative": False, "symbol": "<="}, + m_setg: {"name": "setg", "nb_operands": 2, "is_commutative": False, "symbol": "UGT"}, + m_setge: {"name": "setge", "nb_operands": 2, "is_commutative": False, "symbol": "UGE"}, + m_setl: {"name": "setl", "nb_operands": 2, "is_commutative": False, "symbol": "ULT"}, + m_setle: {"name": "setle", "nb_operands": 2, "is_commutative": False, "symbol": "ULE"}, + m_jcnd: {"name": "jcnd", "nb_operands": 1, "is_commutative": False}, + m_jnz: {"name": "jnz", "nb_operands": 2, "is_commutative": True}, + m_jz: {"name": "jz", "nb_operands": 2, "is_commutative": True}, + m_jae: {"name": "jae", "nb_operands": 2, "is_commutative": False}, + m_jb: {"name": "jb", "nb_operands": 2, "is_commutative": False}, + m_ja: {"name": "ja", "nb_operands": 2, "is_commutative": False}, + m_jbe: {"name": "jbe", "nb_operands": 2, "is_commutative": False}, + m_jg: {"name": "jg", "nb_operands": 2, "is_commutative": False}, + m_jge: {"name": "jge", "nb_operands": 2, "is_commutative": False}, + m_jl: {"name": "jl", "nb_operands": 2, "is_commutative": False}, + m_jle: {"name": "jle", "nb_operands": 2, "is_commutative": False}, + m_jtbl: {"name": "jtbl", "nb_operands": 2, "is_commutative": False}, + m_ijmp: {"name": "ijmp", "nb_operands": 2, "is_commutative": False}, + m_goto: {"name": "goto", "nb_operands": 1, "is_commutative": False}, + m_call: {"name": "call", "nb_operands": 2, "is_commutative": False}, + m_icall: {"name": "icall", "nb_operands": 2, "is_commutative": False}, + m_ret: {"name": "ret", "nb_operands": 0, "is_commutative": False}, + m_push: {"name": "push", "nb_operands": 0, "is_commutative": False}, + m_pop: {"name": "pop", "nb_operands": 0, "is_commutative": False}, + m_und: {"name": "und", "nb_operands": 0, "is_commutative": False}, + m_ext: {"name": "ext", "nb_operands": 0, "is_commutative": False}, + m_f2i: {"name": "f2i", "nb_operands": 2, "is_commutative": False}, + m_f2u: {"name": "f2u", "nb_operands": 2, "is_commutative": False}, + m_i2f: {"name": "i2f", "nb_operands": 2, "is_commutative": False}, + m_u2f: {"name": "u2f", "nb_operands": 2, "is_commutative": False}, + m_f2f: {"name": "f2f", "nb_operands": 2, "is_commutative": False}, + m_fneg: {"name": "fneg", "nb_operands": 2, "is_commutative": False}, + m_fadd: {"name": "fadd", "nb_operands": 2, "is_commutative": True}, + m_fsub: {"name": "fsub", "nb_operands": 2, "is_commutative": False}, + m_fmul: {"name": "fmul", "nb_operands": 2, "is_commutative": True}, + m_fdiv: {"name": "fdiv", "nb_operands": 2, "is_commutative": False}, +} + + +MATURITY_TO_STRING_DICT = { + MMAT_ZERO: "MMAT_ZERO", + MMAT_GENERATED: "MMAT_GENERATED", + MMAT_PREOPTIMIZED: "MMAT_PREOPTIMIZED", + MMAT_LOCOPT: "MMAT_LOCOPT", + MMAT_CALLS: "MMAT_CALLS", + MMAT_GLBOPT1: "MMAT_GLBOPT1", + MMAT_GLBOPT2: "MMAT_GLBOPT2", + MMAT_GLBOPT3: "MMAT_GLBOPT3", + MMAT_LVARS: "MMAT_LVARS", +} +STRING_TO_MATURITY_DICT = {v: k for k, v in MATURITY_TO_STRING_DICT.items()} + +MOP_TYPE_TO_STRING_DICT = { + mop_z: "mop_z", + mop_r: "mop_r", + mop_n: "mop_n", + mop_str: "mop_str", + mop_d: "mop_d", + mop_S: "mop_S", + mop_v: "mop_v", + mop_b: "mop_b", + mop_f: "mop_f", + mop_l: "mop_l", + mop_a: "mop_a", + mop_h: "mop_h", + mop_c: "mop_c", + mop_fn: "mop_fn", + mop_p: "mop_p", + mop_sc: "mop_sc", +} + +Z3_SPECIAL_OPERANDS = ["UDiv", "URem", "LShR", "UGT", "UGE", "ULT", "ULE"] + +BOOLEAN_OPCODES = [m_lnot, m_bnot, m_or, m_and, m_xor] +ARITHMETICAL_OPCODES = [m_neg, m_add, m_sub, m_mul, m_udiv, m_sdiv, m_umod, m_smod] +BIT_OPERATIONS_OPCODES = [m_shl, m_shr, m_sar, m_mov, m_xds, m_xdu, m_low, m_high] +CHECK_OPCODES = [m_sets, m_seto, m_setp, m_setnz, m_setz, m_seta, m_setae, m_setb, + m_setbe, m_setg, m_setge, m_setl, m_setle] + +MBA_RELATED_OPCODES = BOOLEAN_OPCODES + ARITHMETICAL_OPCODES + BIT_OPERATIONS_OPCODES + CHECK_OPCODES + +CONDITIONAL_JUMP_OPCODES = [m_jcnd, m_jnz, m_jz, m_jae, m_ja, m_jb, m_jbe, m_jg, m_jge, m_jl, m_jle, m_jtbl] +UNCONDITIONAL_JUMP_OPCODES = [m_goto, m_ijmp] +CONTROL_FLOW_OPCODES = CONDITIONAL_JUMP_OPCODES + UNCONDITIONAL_JUMP_OPCODES + +MINSN_TO_AST_FORBIDDEN_OPCODES = CONTROL_FLOW_OPCODES + [m_ret, m_nop, m_stx, m_push, m_pop, m_und, m_ext, m_call] + +SUB_TABLE = {1: 0x100, 2: 0x10000, 4: 0x100000000, 8: 0x10000000000000000} +AND_TABLE = {1: 0xff, 2: 0xffff, 4: 0xffffffff, 8: 0xffffffffffffffff} +MSB_TABLE = {1: 0x80, 2: 0x8000, 4: 0x80000000, 8: 0x8000000000000000} + + +# Hex-Rays mop equality checking +def equal_bnot_cst(lo: mop_t, ro: mop_t) -> bool: + if (lo.t != mop_n) or (ro.t != mop_n): + return False + if lo.size != ro.size: + return False + return lo.nnn.value ^ ro.nnn.value == AND_TABLE[lo.size] + + +def equal_bnot_mop(lo: mop_t, ro: mop_t, test_two_sides=True) -> bool: + if lo.t == mop_n: + return equal_bnot_cst(lo, ro) + + # We first check for a bnot operand + if (lo.t == mop_d) and lo.d.opcode == m_bnot: + if equal_mops_ignore_size(lo.d.l, ro): + return True + + # Otherwise Hexrays may have optimized using ~(-x) = x - 1 + if (lo.t == mop_d) and lo.d.opcode == m_neg: + if (ro.t == mop_d) and ro.d.opcode == m_sub: + if ro.d.r.t == mop_n and ro.d.r.nnn.value == 1: + if equal_mops_ignore_size(ro.d.l, lo.d.l): + return True + + if (lo.t == mop_d) and lo.d.opcode == m_xds: + if equal_bnot_mop(lo.d.l, ro): + return True + + if test_two_sides: + return equal_bnot_mop(ro, lo, test_two_sides=False) + return False + + +def equal_ignore_msb_cst(lo: mop_t, ro: mop_t) -> bool: + if (lo.t != mop_n) or (ro.t != mop_n): + return False + if lo.size != ro.size: + return False + mask = AND_TABLE[lo.size] ^ MSB_TABLE[lo.size] + return lo.nnn.value & mask == ro.nnn.value & mask + + +def equal_mops_bypass_xdu(lo: mop_t, ro: mop_t) -> bool: + if (lo is None) or (ro is None): + return False + if (lo.t == mop_d) and (lo.d.opcode == m_xdu): + return equal_mops_bypass_xdu(lo.d.l, ro) + if (ro.t == mop_d) and (ro.d.opcode == m_xdu): + return equal_mops_bypass_xdu(lo, ro.d.l) + return equal_mops_ignore_size(lo, ro) + + +def equal_mops_ignore_size(lo: mop_t, ro: mop_t) -> bool: + if (lo is None) or (ro is None): + return False + if lo.t != ro.t: + return False + if lo.t == mop_z: + return True + elif lo.t == mop_fn: + return lo.fpc == ro.fpc + elif lo.t == mop_n: + return lo.nnn.value == ro.nnn.value + elif lo.t == mop_S: + if lo.s == ro.s: + return True + if lo.s.off == ro.s.off: + # Is it right? + return True + return False + elif lo.t == mop_v: + return lo.g == ro.g + elif lo.t == mop_d: + return lo.d.equal_insns(ro.d, EQ_IGNSIZE) + # return lo.d.equal_insns(ro.d, EQ_IGNSIZE | EQ_IGNCODE) + elif lo.t == mop_b: + return lo.b == ro.b + elif lo.t == mop_r: + return lo.r == ro.r + elif lo.t == mop_f: + return False + elif lo.t == mop_l: + return lo.l == ro.l + elif lo.t == mop_a: + if lo.a.insize != ro.a.insize: + return False + if lo.a.outsize != ro.a.outsize: + return False + return equal_mops_ignore_size(lo.a, ro.a) + elif lo.t == mop_h: + return ro.helper == lo.helper + elif lo.t == mop_str: + return ro.cstr == lo.cstr + elif lo.t == mop_c: + return ro.c == lo.c + elif lo.t == mop_p: + return equal_mops_ignore_size(lo.pair.lop, ro.pair.lop) and equal_mops_ignore_size(lo.pair.hop, ro.pair.hop) + elif lo.t == mop_sc: + return False + else: + return False + + +def is_check_mop(lo: mop_t) -> bool: + if lo.t != mop_d: + return False + if lo.d.opcode in CHECK_OPCODES: + return True + if lo.d.opcode in [m_xds, m_xdu]: + return is_check_mop(lo.d.l) + return False + + +def extract_num_mop(ins: minsn_t) -> Tuple[mop_t, mop_t]: + num_mop = None + other_mop = None + + if ins.l.t == mop_n: + num_mop = ins.l + other_mop = ins.r + if ins.r.t == mop_n: + num_mop = ins.r + other_mop = ins.l + return [num_mop, other_mop] + + +def check_ins_mop_size_are_ok(ins: minsn_t) -> bool: + """ + This function can be used to check if a created instruction has consistent mop size + Use it to avoid Hex-Rays decompilation errors when replacing instructions + + :param ins: + :return: + """ + ins_dest_size = ins.d.size + if ins.opcode in [m_stx, m_ldx]: + if ins.r.t == mop_d: + if not check_ins_mop_size_are_ok(ins.r.d): + return False + return True + + if ins.opcode in [m_xdu, m_xds, m_low, m_high]: + if (ins.l.t == mop_d) and (not check_ins_mop_size_are_ok(ins.l.d)): + return False + return True + + if ins.opcode in [m_sar, m_shr, m_shl]: + if ins.l.size != ins_dest_size: + return False + if (ins.l.t == mop_d) and (not check_ins_mop_size_are_ok(ins.l.d)): + return False + if (ins.r.t == mop_d) and (not check_ins_mop_size_are_ok(ins.r.d)): + return False + return True + + if ins.opcode in CHECK_OPCODES: + if (ins.l.t == mop_d) and (not check_ins_mop_size_are_ok(ins.l.d)): + return False + if (ins.r.t == mop_d) and (not check_ins_mop_size_are_ok(ins.r.d)): + return False + return True + + if ins.l is not None: + if ins.l.size != ins_dest_size: + return False + if ins.l.t == mop_d and (not check_ins_mop_size_are_ok(ins.l.d)): + return False + + if ins.r is not None and ins.r.t != mop_z: + if ins.r.size != ins_dest_size: + return False + if ins.r.t == mop_d and (not check_ins_mop_size_are_ok(ins.r.d)): + return False + return True + + +def check_mop_is_result_of(lo: mop_t, mc) -> bool: + if lo.t != mop_d: + return False + return lo.d.opcode == mc + + +def extract_by_opcode_type(ins: minsn_t, mc) -> Tuple[mop_t, mop_t]: + if check_mop_is_result_of(ins.l, mc): + return [ins.l, ins.r] + if check_mop_is_result_of(ins.r, mc): + return [ins.r, ins.l] + return [None, None] + + +def check_ins_have_same_operands(ins1: minsn_t, ins2: minsn_t, ignore_order=False) -> bool: + if equal_mops_ignore_size(ins1.l, ins2.l) and equal_mops_ignore_size(ins1.r, ins2.r): + return True + if not ignore_order: + return False + return equal_mops_ignore_size(ins1.l, ins2.r) and equal_mops_ignore_size(ins1.r, ins2.l) + + +def get_mop_index(searched_mop: mop_t, mop_list) -> int: + for i, test_mop in enumerate(mop_list): + if equal_mops_ignore_size(searched_mop, test_mop): + return i + return -1 + + +def append_mop_if_not_in_list(mop: mop_t, mop_list) -> bool: + mop_index = get_mop_index(mop, mop_list) + if mop_index == -1: + mop_list.append(mop) + return True + return False + + +def get_blk_index(searched_blk: mblock_t, blk_list: List[mblock_t]) -> int: + blk_serial_list = [blk.serial for blk in blk_list] + try: + return blk_serial_list.index(searched_blk.serial) + except ValueError: + return -1 diff --git a/d810/hexrays_hooks.py b/d810/hexrays_hooks.py new file mode 100644 index 0000000..ed1e788 --- /dev/null +++ b/d810/hexrays_hooks.py @@ -0,0 +1,280 @@ +from __future__ import annotations +import logging + +from ida_hexrays import * + +from d810.optimizers.instructions import PatternOptimizer, ChainOptimizer, Z3Optimizer, EarlyOptimizer, \ + InstructionAnalyzer +from d810.hexrays_helpers import check_ins_mop_size_are_ok, append_mop_if_not_in_list +from d810.hexrays_formatters import format_minsn_t, format_mop_t, maturity_to_string, mop_type_to_string, \ + dump_microcode_for_debug +from d810.errors import D810Exception +from d810.z3_utils import log_z3_instructions + +from typing import TYPE_CHECKING, List +if TYPE_CHECKING: + from d810.manager import D810Manager + from d810.optimizers.instructions.handler import InstructionOptimizer, InstructionOptimizationRule + from d810.optimizers.flow.handler import FlowOptimizationRule + +main_logger = logging.getLogger('D810') +optimizer_logger = logging.getLogger('D810.optimizer') +helper_logger = logging.getLogger('D810.helper') + +DEFAULT_OPTIMIZATION_PATTERN_MATURITIES = [MMAT_PREOPTIMIZED, MMAT_LOCOPT, MMAT_CALLS, MMAT_GLBOPT1] +DEFAULT_OPTIMIZATION_CHAIN_MATURITIES = [MMAT_PREOPTIMIZED, MMAT_LOCOPT, MMAT_CALLS, MMAT_GLBOPT1] +DEFAULT_OPTIMIZATION_Z3_MATURITIES = [MMAT_LOCOPT, MMAT_CALLS, MMAT_GLBOPT1] +DEFAULT_OPTIMIZATION_EARLY_MATURITIES = [MMAT_GENERATED, MMAT_PREOPTIMIZED] +DEFAULT_ANALYZER_MATURITIES = [MMAT_PREOPTIMIZED, MMAT_LOCOPT, MMAT_CALLS, MMAT_GLBOPT1] + + +class InstructionDefUseCollector(mop_visitor_t): + def __init__(self): + super().__init__() + self.unresolved_ins_mops = [] + self.memory_unresolved_ins_mops = [] + self.target_mops = [] + + def visit_mop(self, op: mop_t, op_type: int, is_target: bool): + if is_target: + append_mop_if_not_in_list(op, self.target_mops) + else: + # TODO whatever the case, in the end we will always return 0. May be this code can be better optimized. + # TODO handle other special case (e.g. ldx ins, ...) + if op.t == mop_S: + append_mop_if_not_in_list(op, self.unresolved_ins_mops) + elif op.t == mop_r: + append_mop_if_not_in_list(op, self.unresolved_ins_mops) + elif op.t == mop_v: + append_mop_if_not_in_list(op, self.memory_unresolved_ins_mops) + elif op.t == mop_a: + if op.a.t == mop_v: + return 0 + elif op.a.t == mop_S: + return 0 + helper_logger.warning("Calling visit_mop with unsupported mop type {0} - {1}: '{2}'" + .format(mop_type_to_string(op.t), mop_type_to_string(op.a.t), format_mop_t(op))) + return 0 + elif op.t == mop_n: + return 0 + elif op.t == mop_d: + return 0 + elif op.t == mop_h: + return 0 + elif op.t == mop_b: + return 0 + else: + helper_logger.warning("Calling visit_mop with unsupported mop type {0}: '{1}'" + .format(mop_type_to_string(op.t), format_mop_t(op))) + return 0 + + +class InstructionOptimizerManager(optinsn_t): + def __init__(self, manager: D810Manager): + optimizer_logger.debug("Initializing {0}...".format(self.__class__.__name__)) + super().__init__() + self.manager = manager + self.instruction_visitor = InstructionVisitorManager(self) + self._last_optimizer_tried = None + self.current_maturity = None + self.current_blk_serial = None + self.generate_z3_code = False + self.dump_intermediate_microcode = False + + self.instruction_optimizers = [] + self.optimizer_usage_info = {} + self.add_optimizer(PatternOptimizer(DEFAULT_OPTIMIZATION_PATTERN_MATURITIES, log_dir=self.manager.log_dir)) + self.add_optimizer(ChainOptimizer(DEFAULT_OPTIMIZATION_CHAIN_MATURITIES, log_dir=self.manager.log_dir)) + self.add_optimizer(Z3Optimizer(DEFAULT_OPTIMIZATION_Z3_MATURITIES, log_dir=self.manager.log_dir)) + self.add_optimizer(EarlyOptimizer(DEFAULT_OPTIMIZATION_EARLY_MATURITIES, log_dir=self.manager.log_dir)) + self.analyzer = InstructionAnalyzer(DEFAULT_ANALYZER_MATURITIES, log_dir=self.manager.log_dir) + + def func(self, blk: mblock_t, ins: minsn_t) -> bool: + self.log_info_on_input(blk, ins) + try: + optimization_performed = self.optimize(blk, ins) + + if not optimization_performed: + optimization_performed = ins.for_all_insns(self.instruction_visitor) + + if optimization_performed: + ins.optimize_solo() + + if blk is not None: + blk.mark_lists_dirty() + blk.mba.verify(True) + + return optimization_performed + except RuntimeError as e: + optimizer_logger.error("RuntimeError while optimizing ins {0} with {1}: {2}" + .format(format_minsn_t(ins), self._last_optimizer_tried, e)) + except D810Exception as e: + optimizer_logger.error("D810Exception while optimizing ins {0} with {1}: {2}" + .format(format_minsn_t(ins), self._last_optimizer_tried, e)) + return False + + def reset_rule_usage_statistic(self): + self.optimizer_usage_info = {} + for ins_optimizer in self.instruction_optimizers: + self.optimizer_usage_info[ins_optimizer.name] = 0 + ins_optimizer.reset_rule_usage_statistic() + + def show_rule_usage_statistic(self): + for optimizer_name, optimizer_nb_match in self.optimizer_usage_info.items(): + if optimizer_nb_match > 0: + main_logger.info("Instruction optimizer '{0}' has been used {1} times" + .format(optimizer_name, optimizer_nb_match)) + for ins_optimizer in self.instruction_optimizers: + ins_optimizer.show_rule_usage_statistic() + + def log_info_on_input(self, blk: mblock_t, ins: minsn_t): + if blk is None: + return + mba: mbl_array_t = blk.mba + + if (mba is not None) and (mba.maturity != self.current_maturity): + self.current_maturity = mba.maturity + main_logger.debug("Instruction optimization function called at maturity: {0}" + .format(maturity_to_string(self.current_maturity))) + self.analyzer.set_maturity(self.current_maturity) + self.current_blk_serial = None + + for ins_optimizer in self.instruction_optimizers: + ins_optimizer.cur_maturity = self.current_maturity + + if self.dump_intermediate_microcode: + dump_microcode_for_debug(mba, self.manager.log_dir, "input_instruction_optimizer") + + if blk.serial != self.current_blk_serial: + self.current_blk_serial = blk.serial + + def add_optimizer(self, optimizer: InstructionOptimizer): + self.instruction_optimizers.append(optimizer) + self.optimizer_usage_info[optimizer.name] = 0 + + def add_rule(self, rule: InstructionOptimizationRule): + # optimizer_log.info("Trying to add rule {0}".format(rule)) + for ins_optimizer in self.instruction_optimizers: + ins_optimizer.add_rule(rule) + self.analyzer.add_rule(rule) + + def configure(self, generate_z3_code=False, dump_intermediate_microcode=False, **kwargs): + self.generate_z3_code = generate_z3_code + self.dump_intermediate_microcode = dump_intermediate_microcode + + def optimize(self, blk: mblock_t, ins: minsn_t) -> bool: + # optimizer_log.info("Trying to optimize {0}".format(format_minsn_t(ins))) + for ins_optimizer in self.instruction_optimizers: + self._last_optimizer_tried = ins_optimizer + new_ins = ins_optimizer.get_optimized_instruction(blk, ins) + + if new_ins is not None: + if not check_ins_mop_size_are_ok(new_ins): + if check_ins_mop_size_are_ok(ins): + main_logger.error("Invalid optimized instruction: {0} (original was {1})".format( + format_minsn_t(new_ins), format_minsn_t(ins))) + else: + main_logger.error("Invalid original instruction : {0} (original was {1})".format( + format_minsn_t(new_ins), format_minsn_t(ins))) + else: + ins.swap(new_ins) + self.optimizer_usage_info[ins_optimizer.name] += 1 + if self.generate_z3_code: + try: + log_z3_instructions(new_ins, ins) + except KeyError: + pass + return True + + self.analyzer.analyze(blk, ins) + return False + + +class InstructionVisitorManager(minsn_visitor_t): + def __init__(self, optimizer: InstructionOptimizerManager): + optimizer_logger.debug("Initializing {0}...".format(self.__class__.__name__)) + super().__init__() + self.instruction_optimizer = optimizer + + def visit_minsn(self) -> bool: + return self.instruction_optimizer.optimize(self.blk, self.curins) + + +class BlockOptimizerManager(optblock_t): + def __init__(self, manager: D810Manager): + optimizer_logger.debug("Initializing {0}...".format(self.__class__.__name__)) + super().__init__() + self.manager = manager + self.cfg_rules = set() + + self.current_maturity = None + self.cfg_rules_usage_info = {} + + def func(self, blk: mblock_t): + self.log_info_on_input(blk) + nb_patch = self.optimize(blk) + return nb_patch + + def reset_rule_usage_statistic(self): + self.cfg_rules_usage_info = {} + for rule in self.cfg_rules: + self.cfg_rules_usage_info[rule.name] = [] + + def show_rule_usage_statistic(self): + for rule_name, rule_nb_patch_list in self.cfg_rules_usage_info.items(): + nb_use = len(rule_nb_patch_list) + if nb_use > 0: + main_logger.info("BlkRule '{0}' has been used {1} times for a total of {2} patches" + .format(rule_name, nb_use, sum(rule_nb_patch_list))) + + def log_info_on_input(self, blk: mblock_t): + if blk is None: + return + mba: mbl_array_t = blk.mba + + if (mba is not None) and (mba.maturity != self.current_maturity): + main_logger.debug("BlockOptimizer called at maturity: {0}".format(maturity_to_string(mba.maturity))) + self.current_maturity = mba.maturity + + def optimize(self, blk: mblock_t): + for cfg_rule in self.cfg_rules: + if self.check_if_rule_is_activated_for_address(cfg_rule, blk.mba.entry_ea): + nb_patch = cfg_rule.optimize(blk) + if nb_patch > 0: + optimizer_logger.info("Rule {0} matched: {1} patches".format(cfg_rule.name, nb_patch)) + self.cfg_rules_usage_info[cfg_rule.name].append(nb_patch) + return nb_patch + return 0 + + def add_rule(self, cfg_rule: FlowOptimizationRule): + optimizer_logger.info("Adding cfg rule {0}".format(cfg_rule)) + self.cfg_rules.add(cfg_rule) + self.cfg_rules_usage_info[cfg_rule.name] = [] + + def configure(self, **kwargs): + pass + + def check_if_rule_is_activated_for_address(self, cfg_rule: FlowOptimizationRule, func_entry_ea: int): + if cfg_rule.use_whitelist and (func_entry_ea not in cfg_rule.whitelisted_function_ea_list): + return False + if cfg_rule.use_blacklist and (func_entry_ea in cfg_rule.blacklisted_function_ea_list): + return False + return True + + +class HexraysDecompilationHook(Hexrays_Hooks): + def __init__(self, manager): + super().__init__() + self.manager = manager + + def prolog(self, mba: mbl_array_t, fc, reachable_blocks, decomp_flags) -> "int": + main_logger.info("Starting decompilation of function at 0x{0:x}".format(mba.entry_ea)) + self.manager.instruction_optimizer.reset_rule_usage_statistic() + self.manager.block_optimizer.reset_rule_usage_statistic() + return 0 + + def glbopt(self, mba: mbl_array_t) -> "int": + main_logger.info("glbopt finished for function at 0x{0:x}".format(mba.entry_ea)) + self.manager.instruction_optimizer.show_rule_usage_statistic() + self.manager.block_optimizer.show_rule_usage_statistic() + return 0 diff --git a/d810/ida_ui.py b/d810/ida_ui.py new file mode 100644 index 0000000..9479a0c --- /dev/null +++ b/d810/ida_ui.py @@ -0,0 +1,541 @@ +# -*- coding: utf-8 -*- +import json +import logging +import idaapi +import ida_kernwin +from PyQt5 import QtCore, QtWidgets, QtGui + +from d810.conf import ProjectConfiguration, RuleConfiguration + +logger = logging.getLogger('D810.ui') + + +class PluginConfigurationFileForm_t(QtWidgets.QDialog): + def __init__(self, parent, state): + logger.debug("Initializing PluginConfigurationFileForm_t") + super().__init__(parent) + self.state = state + self.log_dir_changed = False + + self.log_dir = self.state.d810_config.get("log_dir") + self.erase_logs_on_reload = self.state.d810_config.get("erase_logs_on_reload") + self.generate_z3_code = self.state.d810_config.get("generate_z3_code") + self.dump_intermediate_microcode = self.state.d810_config.get("dump_intermediate_microcode") + + self.resize(1000, 500) + self.setWindowTitle("Plugin Configuration") + + # Main layout + self.config_layout = QtWidgets.QVBoxLayout(self) + + self.layout_log_dir = QtWidgets.QHBoxLayout() + self.lbl_log_dir_info = QtWidgets.QLabel(self) + self.lbl_log_dir_info.setText("Current log directory path: ") + self.layout_log_dir.addWidget(self.lbl_log_dir_info) + self.lbl_log_dir = QtWidgets.QLabel(self) + self.lbl_log_dir.setText(self.log_dir) + self.layout_log_dir.addWidget(self.lbl_log_dir) + self.button_change_log_dir = QtWidgets.QPushButton(self) + self.button_change_log_dir.setText("Change log directory") + self.button_change_log_dir.clicked.connect(self.choose_log_dir) + self.layout_log_dir.addWidget(self.button_change_log_dir) + + self.config_layout.addLayout(self.layout_log_dir) + + self.checkbox_generate_z3_code = QtWidgets.QCheckBox("Generate Z3 code for simplification performed", self) + self.checkbox_generate_z3_code.setChecked(self.state.d810_config.get("generate_z3_code")) + self.config_layout.addWidget(self.checkbox_generate_z3_code) + self.checkbox_dump_intermediate_microcode = QtWidgets.QCheckBox("Dump functions microcode at each maturity", self) + self.checkbox_dump_intermediate_microcode.setChecked(self.state.d810_config.get("dump_intermediate_microcode")) + self.config_layout.addWidget(self.checkbox_dump_intermediate_microcode) + self.checkbox_erase_logs_on_reload = QtWidgets.QCheckBox("Erase log directory content when plugin is reloaded", self) + self.checkbox_erase_logs_on_reload.setChecked(self.state.d810_config.get("erase_logs_on_reload")) + self.config_layout.addWidget(self.checkbox_erase_logs_on_reload) + + self.layout_button = QtWidgets.QHBoxLayout() + self.button_save = QtWidgets.QPushButton(self) + self.button_save.setText("Save") + self.button_save.clicked.connect(self.save_config) + self.layout_button.addWidget(self.button_save) + self.button_cancel = QtWidgets.QPushButton(self) + self.button_cancel.setText("Cancel") + self.button_cancel.clicked.connect(self.reject) + self.layout_button.addWidget(self.button_cancel) + self.config_layout.addLayout(self.layout_button) + + self.setLayout(self.config_layout) + + def choose_log_dir(self): + logger.debug("Calling save_rule_configuration") + log_dir = QtWidgets.QFileDialog.getExistingDirectory(self, "Open Directory", "/home", + QtWidgets.QFileDialog.ShowDirsOnly | + QtWidgets.QFileDialog.DontResolveSymlinks) + if log_dir != "": + self.log_dir = log_dir + self.log_dir_changed = True + self.lbl_log_dir.setText(self.log_dir) + + def save_config(self): + if self.log_dir_changed: + self.state.d810_config.set("log_dir", self.log_dir) + self.state.d810_config.set("erase_logs_on_reload", self.checkbox_erase_logs_on_reload.isChecked()) + self.state.d810_config.set("generate_z3_code", self.checkbox_generate_z3_code.isChecked()) + self.state.d810_config.set("dump_intermediate_microcode", self.checkbox_dump_intermediate_microcode.isChecked()) + self.state.d810_config.save() + self.accept() + + +class EditConfigurationFileForm_t(QtWidgets.QDialog): + def __init__(self, parent, state): + logger.debug("Initializing EditConfigurationFileForm_t") + super().__init__(parent) + self.state = state + self.resize(1000, 500) + self.setWindowTitle("Rule Configuration Editor") + + # Main layout + self.config_layout = QtWidgets.QVBoxLayout(self) + + # Configuration Name Selection Layout + self.layout_cfg_name = QtWidgets.QHBoxLayout() + self.lbl_cfg_name = QtWidgets.QLabel(self) + self.lbl_cfg_name.setText("Rule Name") + self.layout_cfg_name.addWidget(self.lbl_cfg_name) + self.in_cfg_name = QtWidgets.QLineEdit(self) + self.layout_cfg_name.addWidget(self.in_cfg_name) + self.config_layout.addLayout(self.layout_cfg_name) + + # Instructions rule Selection Layout + self.table_ins_rule_selection = QtWidgets.QTableWidget(self) + # self.table_ins_rule_selection.setEditTriggers(QtWidgets.QAbstractItemView.NoEditTriggers) + self.table_ins_rule_selection.setRowCount(2) + self.table_ins_rule_selection.setColumnCount(4) + item = QtWidgets.QTableWidgetItem() + item.setText("Is activated") + self.table_ins_rule_selection.setHorizontalHeaderItem(0, item) + item = QtWidgets.QTableWidgetItem() + item.setText("Rule Name") + self.table_ins_rule_selection.setHorizontalHeaderItem(1, item) + item = QtWidgets.QTableWidgetItem() + item.setText("Rule Description") + self.table_ins_rule_selection.setHorizontalHeaderItem(2, item) + item = QtWidgets.QTableWidgetItem() + item.setText("Rule Configuration") + self.table_ins_rule_selection.setHorizontalHeaderItem(3, item) + self.table_ins_rule_selection.horizontalHeader().setStretchLastSection(True) + self.table_ins_rule_selection.verticalHeader().setVisible(False) + self.table_ins_rule_selection.setSortingEnabled(True) + # self.table_ins_rule_selection.setSizeAdjustPolicy(QtWidgets.QAbstractScrollArea.AdjustToContents) + self.config_layout.addWidget(self.table_ins_rule_selection) + + # Block rule Selection Layout + self.table_blk_rule_selection = QtWidgets.QTableWidget(self) + # self.table_blk_rule_selection.setEditTriggers(QtWidgets.QAbstractItemView.NoEditTriggers) + self.table_blk_rule_selection.setRowCount(2) + self.table_blk_rule_selection.setColumnCount(4) + item = QtWidgets.QTableWidgetItem() + item.setText("Is activated") + self.table_blk_rule_selection.setHorizontalHeaderItem(0, item) + item = QtWidgets.QTableWidgetItem() + item.setText("Rule Name") + self.table_blk_rule_selection.setHorizontalHeaderItem(1, item) + item = QtWidgets.QTableWidgetItem() + item.setText("Rule Description") + self.table_blk_rule_selection.setHorizontalHeaderItem(2, item) + item = QtWidgets.QTableWidgetItem() + item.setText("Rule Configuration") + self.table_blk_rule_selection.setHorizontalHeaderItem(3, item) + self.table_blk_rule_selection.horizontalHeader().setStretchLastSection(True) + self.table_blk_rule_selection.verticalHeader().setVisible(False) + self.table_blk_rule_selection.setSortingEnabled(True) + # self.table_blk_rule_selection.setSizeAdjustPolicy(QtWidgets.QAbstractScrollArea.AdjustToContents) + self.config_layout.addWidget(self.table_blk_rule_selection) + + self.layout_button = QtWidgets.QHBoxLayout() + self.button_save = QtWidgets.QPushButton(self) + self.button_save.setText("Save") + self.button_save.clicked.connect(self.save_rule_configuration) + self.layout_button.addWidget(self.button_save) + self.button_cancel = QtWidgets.QPushButton(self) + self.button_cancel.setText("Cancel") + self.button_cancel.clicked.connect(self.reject) + self.layout_button.addWidget(self.button_cancel) + self.config_layout.addLayout(self.layout_button) + + self.setLayout(self.config_layout) + + self.config_path = None + self.config_description = None + self.config_rules = [] + self.update_table_rule_selection() + + def update_form(self, config_description=None, activated_ins_rule_config_list=None, activated_blk_rule_config_list=None, config_path=None): + logger.debug("Calling update_form") + if config_description is not None: + self.in_cfg_name.setText(config_description) + if activated_ins_rule_config_list is not None or activated_blk_rule_config_list is not None: + self.update_table_rule_selection(activated_ins_rule_config_list, activated_blk_rule_config_list) + if config_path is not None: + self.config_path = config_path + + def update_table_rule_selection(self, activated_ins_rule_config_list=None, activated_blk_rule_config_list=None): + logger.debug("Calling update_table_rule_selection") + self.update_table_ins_rule_selection(activated_ins_rule_config_list) + self.update_table_blk_rule_selection(activated_blk_rule_config_list) + + def _get_rule_config(self, rule_name, rule_config_list): + logger.debug("Calling _get_rule_config") + try: + rule_name_list = [rule_conf.name for rule_conf in rule_config_list] + rule_index = rule_name_list.index(rule_name) + return rule_config_list[rule_index] + except ValueError: + return None + + def update_table_ins_rule_selection(self, activated_ins_rule_config_list=None): + logger.debug("Calling update_table_ins_rule_selection") + if activated_ins_rule_config_list is None: + activated_ins_rule_config_list = [] + self.table_ins_rule_selection.setRowCount(len(self.state.known_ins_rules)) + for i, rule in enumerate(self.state.known_ins_rules): + rule_config = self._get_rule_config(rule.name, activated_ins_rule_config_list) + item = QtWidgets.QTableWidgetItem() + item.setTextAlignment(QtCore.Qt.AlignCenter) + if rule_config is not None and rule_config.is_activated: + item.setCheckState(QtCore.Qt.Checked) + else: + item.setCheckState(QtCore.Qt.Unchecked) + self.table_ins_rule_selection.setItem(i, 0, item) + item = QtWidgets.QTableWidgetItem() + item.setText(rule.name) + item.setFlags(QtCore.Qt.ItemIsEnabled) + self.table_ins_rule_selection.setItem(i, 1, item) + item = QtWidgets.QTableWidgetItem() + item.setText(rule.description) + item.setFlags(QtCore.Qt.ItemIsEnabled) + self.table_ins_rule_selection.setItem(i, 2, item) + item = QtWidgets.QTableWidgetItem() + if rule_config is not None: + item.setText(json.dumps(rule_config.config)) + else: + item.setText("{}") + self.table_ins_rule_selection.setItem(i, 3, item) + self.table_ins_rule_selection.resizeColumnsToContents() + + def update_table_blk_rule_selection(self, activated_blk_rule_config_list=None): + logger.debug("Calling update_table_blk_rule_selection") + if activated_blk_rule_config_list is None: + activated_blk_rule_config_list = [] + self.table_blk_rule_selection.setRowCount(len(self.state.known_blk_rules)) + for i, rule in enumerate(self.state.known_blk_rules): + rule_config = self._get_rule_config(rule.name, activated_blk_rule_config_list) + item = QtWidgets.QTableWidgetItem() + item.setTextAlignment(QtCore.Qt.AlignCenter) + if rule_config is not None and rule_config.is_activated: + item.setCheckState(QtCore.Qt.Checked) + else: + item.setCheckState(QtCore.Qt.Unchecked) + self.table_blk_rule_selection.setItem(i, 0, item) + item = QtWidgets.QTableWidgetItem() + item.setText(rule.name) + item.setFlags(QtCore.Qt.ItemIsEnabled) + self.table_blk_rule_selection.setItem(i, 1, item) + item = QtWidgets.QTableWidgetItem() + item.setText(rule.description) + item.setFlags(QtCore.Qt.ItemIsEnabled) + self.table_blk_rule_selection.setItem(i, 2, item) + item = QtWidgets.QTableWidgetItem() + if rule_config is not None: + item.setText(json.dumps(rule_config.config)) + else: + item.setText("{}") + self.table_blk_rule_selection.setItem(i, 3, item) + self.table_blk_rule_selection.resizeColumnsToContents() + + def save_rule_configuration(self): + logger.debug("Calling save_rule_configuration") + fname, _ = QtWidgets.QFileDialog.getSaveFileName(self, 'Save file', self.config_path, "Project configuration (*.json)") + if fname: + self.config_path = fname + self.config_description = self.in_cfg_name.text() + self.config_ins_rules = self.get_ins_rules() + self.config_blk_rules = self.get_blk_rules() + self.accept() + + def get_ins_rules(self): + logger.debug("Calling get_ins_rules") + activated_rule_names = [] + nb_rules = self.table_ins_rule_selection.rowCount() + for i in range(nb_rules): + if self.table_ins_rule_selection.item(i, 0).checkState(): + rule_conf = RuleConfiguration(name=self.table_ins_rule_selection.item(i, 1).text(), + is_activated=self.table_ins_rule_selection.item(i, 0).checkState() == QtCore.Qt.Checked, + config=json.loads(self.table_ins_rule_selection.item(i, 3).text())) + activated_rule_names.append(rule_conf) + # activated_rule_names.append(self.table_ins_rule_selection.item(i, 1).text()) + return activated_rule_names + + def get_blk_rules(self): + logger.debug("Calling get_blk_rules") + activated_rule_names = [] + nb_rules = self.table_blk_rule_selection.rowCount() + for i in range(nb_rules): + if self.table_blk_rule_selection.item(i, 0).checkState(): + rule_conf = RuleConfiguration(name=self.table_blk_rule_selection.item(i, 1).text(), + is_activated=self.table_blk_rule_selection.item(i, 0).checkState() == QtCore.Qt.Checked, + config=json.loads(self.table_blk_rule_selection.item(i, 3).text())) + activated_rule_names.append(rule_conf) + # activated_rule_names.append(self.table_blk_rule_selection.item(i, 1).text()) + return activated_rule_names + + +class D810ConfigForm_t(ida_kernwin.PluginForm): + def __init__(self, state): + super().__init__() + self.state = state + self.shown = False + self.created = False + self.parent = None + + def OnClose(self, form): + logger.debug("Calling OnClose") + self.shown = False + # self.parent.close() + + def Show(self): + logger.debug("Calling Show") + if self.shown: + return + self.shown = True + return ida_kernwin.PluginForm.Show( + self, "D-810 Configuration", + options=(ida_kernwin.PluginForm.WOPN_PERSIST | + ida_kernwin.PluginForm.WCLS_SAVE | + ida_kernwin.PluginForm.WOPN_MENU | + ida_kernwin.PluginForm.WOPN_RESTORE | + ida_kernwin.PluginForm.WOPN_TAB)) + + def OnCreate(self, form): + logger.debug("Calling OnCreate") + self.created = True + + # Get parent widget + self.parent = self.FormToPyQtWidget(form) + layout = QtWidgets.QGridLayout(self.parent) + + # ----------- Config options ----------------------- + # Horizontal splitter for config boxes + cfg_split = QtWidgets.QSplitter(self.parent) + layout.addWidget(cfg_split, 0, 0) + # Config name label + self.curlabel = QtWidgets.QLabel('Current file loaded:') + cfg_split.addWidget(self.curlabel) + + self.cfg_select = QtWidgets.QComboBox(self.parent) + cfg_split.addWidget(self.cfg_select) + + self.btn_new_cfg = QtWidgets.QPushButton('New') + self.btn_new_cfg.clicked.connect(self._create_config) + cfg_split.addWidget(self.btn_new_cfg) + + self.btn_duplicate_cfg = QtWidgets.QPushButton('Duplicate') + self.btn_duplicate_cfg.clicked.connect(self._duplicate_config) + cfg_split.addWidget(self.btn_duplicate_cfg) + + self.btn_edit_cfg = QtWidgets.QPushButton('Edit') + self.btn_edit_cfg.clicked.connect(self._edit_config) + cfg_split.addWidget(self.btn_edit_cfg) + + self.btn_delele_cfg = QtWidgets.QPushButton('Delete') + self.btn_delele_cfg.clicked.connect(self._delete_config) + cfg_split.addWidget(self.btn_delele_cfg) + + # leave space for comboboxes in cfg_split, rather than between widgets + cfg_split.setStretchFactor(0, 0) + cfg_split.setStretchFactor(1, 1) + cfg_split.setStretchFactor(2, 0) + + description_split = QtWidgets.QSplitter(self.parent) + layout.addWidget(description_split, 1, 0) + self.cfg_description_layout = QtWidgets.QHBoxLayout(description_split) + self.cfg_description_label = QtWidgets.QLabel("Description") + description_split.addWidget(self.cfg_description_label) + self.cfg_description = QtWidgets.QLabel("No description") + description_split.addWidget(self.cfg_description) + description_split.setStretchFactor(0, 0) + description_split.setStretchFactor(1, 1) + + self.cfg_ins_preview = QtWidgets.QTableWidget(self.parent) + layout.addWidget(self.cfg_ins_preview, 2, 0) + + self.cfg_blk_preview = QtWidgets.QTableWidget(self.parent) + layout.addWidget(self.cfg_blk_preview, 3, 0) + self.update_cfg_preview() + + # ----------- Analysis buttons ----------------------- + # Horizontal splitter for buttons + btn_split = QtWidgets.QSplitter(self.parent) + layout.addWidget(btn_split, 4, 0) + + self.btn_config = QtWidgets.QPushButton('Configuration') + self.btn_config.clicked.connect(self._configure_plugin) + btn_split.addWidget(self.btn_config) + + + self.btn_start = QtWidgets.QPushButton('Start') + self.btn_start.clicked.connect(self._start_d810) + btn_split.addWidget(self.btn_start) + + self.btn_stop = QtWidgets.QPushButton('Stop') + self.btn_stop.clicked.connect(self._stop_d810) + btn_split.addWidget(self.btn_stop) + + self.plugin_status = QtWidgets.QLabel() + self.plugin_status.setText("Not Loaded") + description_split.addWidget(self.plugin_status) + btn_split.addWidget(self.plugin_status) + + + self.update_cfg_select() + self.cfg_select.setCurrentIndex(self.state.current_project_index) + self.cfg_select.currentIndexChanged.connect(self._load_config) + + def update_cfg_preview(self): + logger.debug("Calling update_cfg_preview") + self.update_cfg_ins_preview() + self.update_cfg_blk_preview() + + def update_cfg_ins_preview(self): + # return + logger.debug("Calling update_cfg_ins_preview") + self.cfg_ins_preview.setRowCount(len(self.state.current_ins_rules)) + self.cfg_ins_preview.setColumnCount(3) + self.cfg_ins_preview.setHorizontalHeaderLabels(("Name", "Description", "Configuration")) + self.cfg_ins_preview.horizontalHeader().setStretchLastSection(True) + self.cfg_ins_preview.setSortingEnabled(True) + # self.cfg_ins_preview.setSizeAdjustPolicy(QtWidgets.QAbstractScrollArea.AdjustToContents) + i = 0 + for rule in self.state.current_ins_rules: + cell_file_path = QtWidgets.QTableWidgetItem(rule.name) + cell_file_path.setFlags(QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled) + cell_rule_description = QtWidgets.QTableWidgetItem(rule.description) + cell_rule_description.setFlags(QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled) + cell_rule_config = QtWidgets.QTableWidgetItem(json.dumps(rule.config)) + cell_rule_config.setFlags(QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled) + self.cfg_ins_preview.setItem(i, 0, cell_file_path) + self.cfg_ins_preview.setItem(i, 1, cell_rule_description) + self.cfg_ins_preview.setItem(i, 2, cell_rule_config) + i += 1 + self.cfg_ins_preview.resizeColumnsToContents() + + def update_cfg_blk_preview(self): + logger.debug("Calling update_cfg_blk_preview") + self.cfg_blk_preview.setRowCount(len(self.state.current_blk_rules)) + self.cfg_blk_preview.setColumnCount(3) + self.cfg_blk_preview.setHorizontalHeaderLabels(("Name", "Description", "Configuration")) + self.cfg_blk_preview.horizontalHeader().setStretchLastSection(True) + self.cfg_blk_preview.setSortingEnabled(True) + # self.cfg_blk_preview.setSizeAdjustPolicy(QtWidgets.QAbstractScrollArea.AdjustToContents) + i = 0 + for rule in self.state.current_blk_rules: + cell_file_path = QtWidgets.QTableWidgetItem(rule.name) + cell_file_path.setFlags(QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled) + cell_rule_description = QtWidgets.QTableWidgetItem(rule.description) + cell_rule_description.setFlags(QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled) + cell_rule_config = QtWidgets.QTableWidgetItem(json.dumps(rule.config)) + cell_rule_config.setFlags(QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled) + self.cfg_blk_preview.setItem(i, 0, cell_file_path) + self.cfg_blk_preview.setItem(i, 1, cell_rule_description) + self.cfg_blk_preview.setItem(i, 2, cell_rule_config) + i += 1 + self.cfg_blk_preview.resizeColumnsToContents() + + def update_cfg_select(self): + logger.debug("Calling update_cfg_select") + tmp = self.state.current_project_index + self.cfg_select.clear() + self.cfg_select.addItems([proj.path for proj in self.state.projects]) + self.cfg_select.setCurrentIndex(tmp) + + def _create_config(self): + logger.debug("Calling _create_config") + self._internal_config_creation(None, None, None, self.state.d810_config.config_dir) + + def _duplicate_config(self): + logger.debug("Calling _duplicate_config") + cur_cfg = self.state.current_project + self._internal_config_creation(None, cur_cfg.ins_rules, cur_cfg.blk_rules, self.state.d810_config.config_dir) + + def _edit_config(self): + logger.debug("Calling _edit_config") + cur_cfg = self.state.current_project + self._internal_config_creation(cur_cfg.description, cur_cfg.ins_rules, cur_cfg.blk_rules, cur_cfg.path, cur_cfg) + + def _internal_config_creation(self, description, start_ins_rules, start_blk_rules, path, old_conf=None): + logger.debug("Calling _internal_config_creation") + editdlg = EditConfigurationFileForm_t(self.parent, self.state) + editdlg.update_form(description, start_ins_rules, start_blk_rules, path) + if editdlg.exec_() == QtWidgets.QDialog.Accepted: + new_config = ProjectConfiguration(editdlg.config_path, editdlg.config_description, editdlg.config_ins_rules, editdlg.config_blk_rules) + new_config.save() + if old_conf is None: + self.state.add_project(new_config) + else: + self.state.update_project(old_conf, new_config) + self.update_cfg_select() + return new_config + return None + + # callback when the "Delete" button is clicked + def _delete_config(self): + logger.debug("Calling _delete_config") + self.state.del_project(self.state.current_project) + self.update_cfg_select() + + # Called when the edit combo is changed + def _load_config(self, index): + logger.debug("Calling _load_config") + self.state.load_project(index) + self.cfg_description.setText(self.state.current_project.description) + self.update_cfg_preview() + return + + def _configure_plugin(self): + editdlg = PluginConfigurationFileForm_t(self.parent, self.state) + if editdlg.exec_() == QtWidgets.QDialog.Accepted: + return + return + + def _start_d810(self): + logger.debug("Calling _start_d810") + self.state.start_d810() + # self.plugin_status.clear() + self.plugin_status.setText("Loaded") + return + + def _stop_d810(self): + logger.debug("Calling _stop_d810") + self.state.stop_d810() + # self.plugin_status.clear() + self.plugin_status.setText("Not Loaded") + return + + +class D810GUI(object): + def __init__(self, state): + """ + Instanciate D-810 views + """ + logger.debug("Initializing D810GUI") + self.state = state + self.d810_config_form = D810ConfigForm_t(self.state) + # XXX fix + idaapi.set_dock_pos("D-810", "IDA View-A", idaapi.DP_TAB) + + def show_windows(self): + logger.debug("Calling show_windows") + self.d810_config_form.Show() + + def term(self): + logger.debug("Calling term") + self.d810_config_form.Close(ida_kernwin.PluginForm.WCLS_SAVE) diff --git a/d810/log.ini b/d810/log.ini new file mode 100644 index 0000000..3763dff --- /dev/null +++ b/d810/log.ini @@ -0,0 +1,102 @@ +[loggers] +keys=root,D810,D810Ui,D810Optimizer,D810RulesChain,D810PatternSearch,D810BranchFixer,D810Unflat,D810Tracker,D810Emulator,D810Helper,D810Z3Test + +[handlers] +keys=consoleHandler,defaultFileHandler,z3FileHandler + +[formatters] +keys=defaultFormatter,rawFormatter + +[logger_root] +level=DEBUG +handlers=consoleHandler + +[logger_D810] +level=DEBUG +handlers=consoleHandler,defaultFileHandler +qualname=D810 +propagate=0 + +[logger_D810Ui] +level=ERROR +handlers=defaultFileHandler +qualname=D810.ui +propagate=0 + +[logger_D810Optimizer] +level=INFO +handlers=defaultFileHandler +qualname=D810.optimizer +propagate=0 + +[logger_D810RulesChain] +level=INFO +handlers=defaultFileHandler +qualname=D810.chain +propagate=0 + +[logger_D810BranchFixer] +level=INFO +handlers=defaultFileHandler +qualname=D810.branch_fixer +propagate=0 + +[logger_D810Unflat] +level=INFO +handlers=defaultFileHandler +qualname=D810.unflat +propagate=0 + +[logger_D810Tracker] +level=INFO +handlers=defaultFileHandler +qualname=D810.tracker +propagate=0 + +[logger_D810Emulator] +level=INFO +handlers=defaultFileHandler +qualname=D810.emulator +propagate=0 + +[logger_D810Helper] +level=INFO +handlers=defaultFileHandler +qualname=D810.helper +propagate=0 + +[logger_D810PatternSearch] +level=ERROR +handlers=defaultFileHandler +qualname=D810.pattern_search +propagate=0 + +[logger_D810Z3Test] +level=INFO +handlers=z3FileHandler +qualname=D810.z3_test +propagate=0 + +[handler_consoleHandler] +class=StreamHandler +level=INFO +formatter=defaultFormatter +args=(sys.stdout,) + +[handler_defaultFileHandler] +class=FileHandler +level=DEBUG +formatter=defaultFormatter +args=('%(default_log_filename)s',) + +[handler_z3FileHandler] +class=FileHandler +level=DEBUG +formatter=rawFormatter +args=('%(z3_log_filename)s',) + +[formatter_defaultFormatter] +format=%(asctime)s - %(name)s - %(levelname)s - %(message)s + +[formatter_rawFormatter] +format=%(message)s \ No newline at end of file diff --git a/d810/log.py b/d810/log.py new file mode 100644 index 0000000..70226b6 --- /dev/null +++ b/d810/log.py @@ -0,0 +1,23 @@ +import os +import shutil +import logging +import logging.config + +LOG_CONFIG_FILENAME = "log.ini""" +LOG_FILENAME = "d810.log" +Z3_TEST_FILENAME = "z3_check_instructions_substitution.py" + + +def clear_logs(log_dir): + shutil.rmtree(log_dir, ignore_errors=True) + + +def configure_loggers(log_dir): + os.makedirs(log_dir, exist_ok=True) + log_main_file = os.path.join(log_dir, LOG_FILENAME) + z3_test_file = os.path.join(log_dir, Z3_TEST_FILENAME) + log_conf_file = os.path.join(os.path.dirname(os.path.abspath(__file__)), LOG_CONFIG_FILENAME) + logging.config.fileConfig(log_conf_file, defaults={"default_log_filename": log_main_file, + "z3_log_filename": z3_test_file}) + z3_file_logger = logging.getLogger('D810.z3_test') + z3_file_logger.info("from z3 import BitVec, BitVecVal, UDiv, URem, LShR, UGT, UGE, ULT, ULE, prove\n\n") diff --git a/d810/manager.py b/d810/manager.py new file mode 100644 index 0000000..d80edcc --- /dev/null +++ b/d810/manager.py @@ -0,0 +1,196 @@ +from __future__ import annotations +import os +import json +import logging +import idaapi + +from typing import TYPE_CHECKING, List +if TYPE_CHECKING: + from d810.conf import D810Configuration, ProjectConfiguration + + +# Note that imports are performed directly in the functions so that they are reloaded each time the plugin is restarted +# This allow to load change code/drop new rules without having to reboot IDA +d810_state = None + +D810_LOG_DIR_NAME = "d810_logs" + +MANAGER_INFO_FILENAME = "manager_info.json" +logger = logging.getLogger('D810') + + +def reload_all_modules(): + manager_info_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), MANAGER_INFO_FILENAME) + + with open(manager_info_path, "r") as f: + manager_info = json.load(f) + + for module_name in manager_info["module_list"]: + idaapi.require(module_name) + + +class D810Manager(object): + def __init__(self, log_dir): + self.instruction_optimizer_rules = [] + self.instruction_optimizer_config = {} + self.block_optimizer_rules = [] + self.block_optimizer_config = {} + self.instruction_optimizer = None + self.block_optimizer = None + self.hx_decompiler_hook = None + self.log_dir = log_dir + self.config = {} + + def configure(self, **kwargs): + self.config = kwargs + + def reload(self): + self.stop() + logger.debug("Reloading manager...") + + from d810.hexrays_hooks import InstructionOptimizerManager, BlockOptimizerManager, HexraysDecompilationHook + + self.instruction_optimizer = InstructionOptimizerManager(self) + self.instruction_optimizer.configure(**self.instruction_optimizer_config) + self.block_optimizer = BlockOptimizerManager(self) + self.block_optimizer.configure(**self.block_optimizer_config) + + for rule in self.instruction_optimizer_rules: + rule.log_dir = self.log_dir + self.instruction_optimizer.add_rule(rule) + + for cfg_rule in self.block_optimizer_rules: + cfg_rule.log_dir = self.log_dir + self.block_optimizer.add_rule(cfg_rule) + + self.instruction_optimizer.install() + self.block_optimizer.install() + + self.hx_decompiler_hook = HexraysDecompilationHook(self) + self.hx_decompiler_hook.hook() + + def configure_instruction_optimizer(self, rules, **kwargs): + self.instruction_optimizer_rules = [rule for rule in rules] + self.instruction_optimizer_config = kwargs + + def configure_block_optimizer(self, rules, **kwargs): + self.block_optimizer_rules = [rule for rule in rules] + self.block_optimizer_config = kwargs + + def stop(self): + if self.instruction_optimizer is not None: + logger.debug("Removing InstructionOptimizer...") + self.instruction_optimizer.remove() + self.instruction_optimizer = None + if self.block_optimizer is not None: + logger.debug("Removing ControlFlowFixer...") + self.block_optimizer.remove() + self.block_optimizer = None + if self.hx_decompiler_hook is not None: + logger.debug("Removing HexraysDecompilationHook...") + self.hx_decompiler_hook.unhook() + self.hx_decompiler_hook = None + + +class D810State(object): + def __init__(self, d810_config: D810Configuration): + # For debugging purposes, to interact with this object from the console + # Type in IDA Python shell 'from d810.manager import d810_state' to access it + global d810_state + d810_state = self + reload_all_modules() + + self.d810_config = d810_config + self.log_dir = os.path.join(self.d810_config.get("log_dir"), D810_LOG_DIR_NAME) + self.manager = D810Manager(self.log_dir) + + from d810.optimizers.instructions import KNOWN_INS_RULES + from d810.optimizers.flow import KNOWN_BLK_RULES + self.known_ins_rules = [x for x in KNOWN_INS_RULES] + self.known_blk_rules = [x for x in KNOWN_BLK_RULES] + + self.gui = None + self.current_project = None + self.projects: List[ProjectConfiguration] = [] + self.current_project_index = self.d810_config.get("last_project_index") + self.current_ins_rules = [] + self.current_blk_rules = [] + + self.register_default_projects() + self.load_project(self.current_project_index) + + def register_default_projects(self): + from d810.conf import ProjectConfiguration + self.projects = [] + for project_configuration_path in self.d810_config.get("configurations"): + project_configuration = ProjectConfiguration(project_configuration_path, + conf_dir=self.d810_config.config_dir) + project_configuration.load() + self.projects.append(project_configuration) + logger.debug("Rule configurations loaded: {0}".format(self.projects)) + + def add_project(self, config: ProjectConfiguration): + self.projects.append(config) + self.d810_config.get("configurations").append(config.path) + self.d810_config.save() + + def update_project(self, old_config: ProjectConfiguration, new_config: ProjectConfiguration): + old_config_index = self.projects.index(old_config) + self.projects[old_config_index] = new_config + + def del_project(self, config: ProjectConfiguration): + self.projects.remove(config) + self.d810_config.get("configurations").remove(config.path) + self.d810_config.save() + os.remove(config.path) + + def load_project(self, project_index: int): + self.current_project_index = project_index + self.current_project = self.projects[project_index] + self.current_ins_rules = [] + self.current_blk_rules = [] + + for rule in self.known_ins_rules: + for rule_conf in self.current_project.ins_rules: + if rule.name == rule_conf.name: + rule.configure(rule_conf.config) + rule.set_log_dir(self.log_dir) + self.current_ins_rules.append(rule) + logger.debug("Instruction rules configured") + for blk_rule in self.known_blk_rules: + for rule_conf in self.current_project.blk_rules: + if blk_rule.name == rule_conf.name: + blk_rule.configure(rule_conf.config) + blk_rule.set_log_dir(self.log_dir) + self.current_blk_rules.append(blk_rule) + logger.debug("Block rules configured") + self.manager.configure(**self.current_project.additional_configuration) + logger.debug("Project loaded.") + + def start_d810(self): + print("D-810 ready to deobfuscate...") + self.manager.configure_instruction_optimizer([rule for rule in self.current_ins_rules], + generate_z3_code=self.d810_config.get("generate_z3_code"), + dump_intermediate_microcode=self.d810_config.get( + "dump_intermediate_microcode"), + **self.current_project.additional_configuration) + self.manager.configure_block_optimizer([rule for rule in self.current_blk_rules], + **self.current_project.additional_configuration) + self.manager.reload() + self.d810_config.set("last_project_index", self.current_project_index) + self.d810_config.save() + + def stop_d810(self): + print("Stopping D-810...") + self.manager.stop() + + def start_plugin(self): + from d810.ida_ui import D810GUI + self.gui = D810GUI(self) + self.gui.show_windows() + + def stop_plugin(self): + self.manager.stop() + if self.gui: + self.gui.term() + self.gui = None diff --git a/d810/manager_info.json b/d810/manager_info.json new file mode 100644 index 0000000..1f38c8f --- /dev/null +++ b/d810/manager_info.json @@ -0,0 +1,60 @@ +{ + "_comment": "Order of module in module list matters", + "module_list": [ + "d810.cfg_utils", + "d810.emulator", + "d810.ast", + "d810.optimizers.handler", + "d810.optimizers.instructions.handler", + "d810.optimizers.instructions.pattern_matching.handler", + "d810.optimizers.instructions.pattern_matching.rewrite_add", + "d810.optimizers.instructions.pattern_matching.rewrite_and", + "d810.optimizers.instructions.pattern_matching.rewrite_bnot", + "d810.optimizers.instructions.pattern_matching.rewrite_cst", + "d810.optimizers.instructions.pattern_matching.rewrite_mov", + "d810.optimizers.instructions.pattern_matching.rewrite_mul", + "d810.optimizers.instructions.pattern_matching.rewrite_neg", + "d810.optimizers.instructions.pattern_matching.rewrite_or", + "d810.optimizers.instructions.pattern_matching.rewrite_predicates", + "d810.optimizers.instructions.pattern_matching.rewrite_sub", + "d810.optimizers.instructions.pattern_matching.rewrite_xor", + "d810.optimizers.instructions.pattern_matching.weird", + "d810.optimizers.instructions.pattern_matching", + "d810.optimizers.instructions.chain.handler", + "d810.optimizers.instructions.chain.chain_rules", + "d810.optimizers.instructions.chain", + "d810.optimizers.instructions.z3.handler", + "d810.optimizers.instructions.z3.cst", + "d810.optimizers.instructions.z3.predicates", + "d810.optimizers.instructions.z3", + "d810.optimizers.instructions.analysis.utils", + "d810.optimizers.instructions.analysis.handler", + "d810.optimizers.instructions.analysis.pattern_guess", + "d810.optimizers.instructions.analysis", + "d810.optimizers.instructions.early.handler", + "d810.optimizers.instructions.early.mem_read", + "d810.optimizers.instructions.early", + "d810.optimizers.instructions", + "d810.optimizers.flow.handler", + "d810.optimizers.flow.jumps.handler", + "d810.optimizers.flow.jumps.opaque", + "d810.optimizers.flow.jumps.tricks", + "d810.optimizers.flow.jumps", + "d810.optimizers.flow.flattening.utils", + "d810.optimizers.flow.flattening.generic", + "d810.optimizers.flow.flattening.unflattener", + "d810.optimizers.flow.flattening.unflattener_fake_jump", + "d810.optimizers.flow.flattening.unflattener_switch_case", + "d810.optimizers.flow.flattening.unflattener_indirect", + "d810.optimizers.flow.flattening", + "d810.optimizers.flow", + "d810.hexrays_helpers", + "d810.hexrays_formatters", + "d810.hexrays_hooks", + "d810.ida_ui", + "d810.log", + "d810.tracker", + "d810.utils", + "d810.z3_utils" + ] +} diff --git a/d810/optimizers/__init__.py b/d810/optimizers/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/d810/optimizers/flow/__init__.py b/d810/optimizers/flow/__init__.py new file mode 100644 index 0000000..83ec7c3 --- /dev/null +++ b/d810/optimizers/flow/__init__.py @@ -0,0 +1,5 @@ +from d810.optimizers.flow.flattening import UNFLATTENING_BLK_RULES +from d810.optimizers.flow.jumps import JUMP_OPTIMIZATION_BLOCK_RULES, JUMP_OPTIMIZATION_RULES + +KNOWN_BLK_RULES = UNFLATTENING_BLK_RULES + JUMP_OPTIMIZATION_BLOCK_RULES + diff --git a/d810/optimizers/flow/flattening/__init__.py b/d810/optimizers/flow/flattening/__init__.py new file mode 100644 index 0000000..acf7f16 --- /dev/null +++ b/d810/optimizers/flow/flattening/__init__.py @@ -0,0 +1,6 @@ +from d810.optimizers.flow.flattening.unflattener import Unflattener +from d810.optimizers.flow.flattening.unflattener_switch_case import UnflattenerSwitchCase +from d810.optimizers.flow.flattening.unflattener_indirect import UnflattenerTigressIndirect +from d810.optimizers.flow.flattening.unflattener_fake_jump import UnflattenerFakeJump + +UNFLATTENING_BLK_RULES = [Unflattener(), UnflattenerSwitchCase(), UnflattenerTigressIndirect(), UnflattenerFakeJump()] diff --git a/d810/optimizers/flow/flattening/generic.py b/d810/optimizers/flow/flattening/generic.py new file mode 100644 index 0000000..951b084 --- /dev/null +++ b/d810/optimizers/flow/flattening/generic.py @@ -0,0 +1,469 @@ +from __future__ import annotations +import logging +from typing import List, Union, Tuple + +from ida_hexrays import * + +from d810.optimizers.flow.handler import FlowOptimizationRule + +from d810.tracker import MopTracker, MopHistory, remove_segment_registers, duplicate_histories +from d810.emulator import MicroCodeEnvironment, MicroCodeInterpreter +from d810.hexrays_hooks import InstructionDefUseCollector +from d810.hexrays_helpers import extract_num_mop, get_mop_index, append_mop_if_not_in_list, CONTROL_FLOW_OPCODES, \ + CONDITIONAL_JUMP_OPCODES +from d810.hexrays_formatters import format_minsn_t, format_mop_t, dump_microcode_for_debug, format_mop_list +from d810.cfg_utils import mba_deep_cleaning, ensure_child_has_an_unconditional_father, ensure_last_block_is_goto, \ + change_1way_block_successor, create_block +from d810.optimizers.flow.flattening.utils import NotResolvableFatherException, NotDuplicableFatherException, \ + DispatcherUnflatteningException, get_all_possibles_values, check_if_all_values_are_found + +unflat_logger = logging.getLogger('D810.unflat') + + +class GenericDispatcherBlockInfo(object): + + def __init__(self, blk, father=None): + self.blk = blk + self.ins = [] + self.use_list = [] + self.use_before_def_list = [] + self.def_list = [] + self.assume_def_list = [] + self.comparison_value = None + self.compared_mop = None + + self.father = None + if father is not None: + self.register_father(father) + + @property + def serial(self) -> int: + return self.blk.serial + + def register_father(self, father: GenericDispatcherBlockInfo): + self.father = father + self.assume_def_list = [x for x in father.assume_def_list] + + def update_use_def_lists(self, ins_mops_used: List[mop_t], ins_mops_def: List[mop_t]): + for mop_used in ins_mops_used: + append_mop_if_not_in_list(mop_used, self.use_list) + mop_used_index = get_mop_index(mop_used, self.def_list) + if mop_used_index == -1: + append_mop_if_not_in_list(mop_used, self.use_before_def_list) + for mop_def in ins_mops_def: + append_mop_if_not_in_list(mop_def, self.def_list) + + def update_with_ins(self, ins: minsn_t): + ins_mop_info = InstructionDefUseCollector() + ins.for_all_ops(ins_mop_info) + cleaned_unresolved_ins_mops = remove_segment_registers(ins_mop_info.unresolved_ins_mops) + self.update_use_def_lists(cleaned_unresolved_ins_mops + ins_mop_info.memory_unresolved_ins_mops, + ins_mop_info.target_mops) + self.ins.append(ins) + if ins.opcode in CONDITIONAL_JUMP_OPCODES: + num_mop, other_mop = extract_num_mop(ins) + if num_mop is not None: + self.comparison_value = num_mop.nnn.value + self.compared_mop = other_mop + + def parse(self): + curins = self.blk.head + while curins is not None: + self.update_with_ins(curins) + curins = curins.next + for mop_def in self.def_list: + append_mop_if_not_in_list(mop_def, self.assume_def_list) + + def does_only_need(self, prerequisite_mop_list: List[mop_t]) -> bool: + for used_before_def_mop in self.use_before_def_list: + mop_index = get_mop_index(used_before_def_mop, prerequisite_mop_list) + if mop_index == -1: + return False + return True + + def recursive_get_father(self) -> List[GenericDispatcherBlockInfo]: + if self.father is None: + return [self] + else: + return self.father.recursive_get_father() + [self] + + def show_history(self): + full_father_list = self.recursive_get_father() + unflat_logger.info(" Show history of Block {0}".format(self.blk.serial)) + for father in full_father_list[:-1]: + for ins in father.ins: + unflat_logger.info(" {0}.{1}".format(father.blk.serial, format_minsn_t(ins))) + + def print_info(self): + unflat_logger.info("Block {0} information:".format(self.blk.serial)) + unflat_logger.info(" USE list: {0}".format(format_mop_list(self.use_list))) + unflat_logger.info(" DEF list: {0}".format(format_mop_list(self.def_list))) + unflat_logger.info(" USE BEFORE DEF list: {0}".format(format_mop_list(self.use_before_def_list))) + unflat_logger.info(" ASSUME DEF list: {0}".format(format_mop_list(self.assume_def_list))) + + +class GenericDispatcherInfo(object): + def __init__(self, mba: mbl_array_t): + self.mba = mba + self.mop_compared = None + self.entry_block = None + self.comparison_values = [] + self.dispatcher_internal_blocks = [] + self.dispatcher_exit_blocks = [] + + def reset(self): + self.mop_compared = None + self.entry_block = None + self.comparison_values = [] + self.dispatcher_internal_blocks = [] + self.dispatcher_exit_blocks = [] + + def explore(self, blk: mblock_t) -> bool: + return False + + def get_shared_internal_blocks(self, other_dispatcher: GenericDispatcherInfo) -> List[mblock_t]: + my_dispatcher_block_serial = [blk_info.blk.serial for blk_info in self.dispatcher_internal_blocks] + other_dispatcher_block_serial = [blk_info.blk.serial + for blk_info in other_dispatcher.dispatcher_internal_blocks] + return [self.mba.get_mblock(blk_serial) for blk_serial in my_dispatcher_block_serial + if blk_serial in other_dispatcher_block_serial] + + def is_sub_dispatcher(self, other_dispatcher: GenericDispatcherInfo) -> bool: + shared_blocks = self.get_shared_internal_blocks(other_dispatcher) + if (len(shared_blocks) > 0) and (self.entry_block.blk.npred() < other_dispatcher.entry_block.blk.npred()): + return True + return False + + def should_emulation_continue(self, cur_blk: mblock_t) -> bool: + exit_block_serial_list = [exit_block.serial for exit_block in self.dispatcher_exit_blocks] + if (cur_blk is not None) and (cur_blk.serial not in exit_block_serial_list): + return True + return False + + def emulate_dispatcher_with_father_history(self, father_history: MopHistory) -> Tuple[mblock_t, List[minsn_t]]: + microcode_interpreter = MicroCodeInterpreter() + microcode_environment = MicroCodeEnvironment() + dispatcher_input_info = [] + for initialization_mop in self.entry_block.use_before_def_list: + initialization_mop_value = father_history.get_mop_constant_value(initialization_mop) + if initialization_mop_value is None: + raise NotResolvableFatherException("Can't emulate dispatcher {0} with history {1}" + .format(self.entry_block.serial, father_history.block_serial_path)) + microcode_environment.define(initialization_mop, initialization_mop_value) + dispatcher_input_info.append("{0} = {1:x}".format(format_mop_t(initialization_mop), + initialization_mop_value)) + + unflat_logger.info("Executing dispatcher {0} with: {1}" + .format(self.entry_block.blk.serial, ", ".join(dispatcher_input_info))) + + instructions_executed = [] + cur_blk = self.entry_block.blk + cur_ins = cur_blk.head + while self.should_emulation_continue(cur_blk): + unflat_logger.debug(" Executing: {0}.{1}".format(cur_blk.serial, format_minsn_t(cur_ins))) + is_ok = microcode_interpreter.eval_instruction(cur_blk, cur_ins, microcode_environment) + if not is_ok: + return cur_blk, instructions_executed + instructions_executed.append(cur_ins) + cur_blk = microcode_environment.next_blk + cur_ins = microcode_environment.next_ins + return cur_blk, instructions_executed + + def print_info(self, verbose=False): + unflat_logger.info("Dispatcher information: ") + unflat_logger.info(" Entry block: {0}.{1}: ".format(self.entry_block.blk.serial, + format_minsn_t(self.entry_block.blk.tail))) + unflat_logger.info(" Entry block predecessors: {0}: " + .format([blk_serial for blk_serial in self.entry_block.blk.predset])) + unflat_logger.info(" Compared mop: {0} ".format(format_mop_t(self.mop_compared))) + unflat_logger.info(" Comparison values: {0} ".format(", ".join([hex(x) for x in self.comparison_values]))) + self.entry_block.print_info() + unflat_logger.info(" Number of internal blocks: {0} ({1})" + .format(len(self.dispatcher_internal_blocks), + [blk_info.blk.serial for blk_info in self.dispatcher_internal_blocks])) + if verbose: + for disp_blk in self.dispatcher_internal_blocks: + unflat_logger.info(" Internal block: {0}.{1} ".format(disp_blk.blk.serial, + format_minsn_t(disp_blk.blk.tail))) + disp_blk.show_history() + unflat_logger.info(" Number of Exit blocks: {0} ({1})" + .format(len(self.dispatcher_exit_blocks), + [blk_info.blk.serial for blk_info in self.dispatcher_exit_blocks])) + if verbose: + for exit_blk in self.dispatcher_exit_blocks: + unflat_logger.info(" Exit block: {0}.{1} ".format(exit_blk.blk.serial, + format_minsn_t(exit_blk.blk.head))) + exit_blk.show_history() + + +class GenericDispatcherCollector(minsn_visitor_t): + DISPATCHER_CLASS = GenericDispatcherInfo + DEFAULT_DISPATCHER_MIN_INTERNAL_BLOCK = 2 + DEFAULT_DISPATCHER_MIN_EXIT_BLOCK = 2 + DEFAULT_DISPATCHER_MIN_COMPARISON_VALUE = 2 + + def __init__(self): + super().__init__() + self.dispatcher_list = [] + self.explored_blk_serials = [] + self.dispatcher_min_internal_block = self.DEFAULT_DISPATCHER_MIN_INTERNAL_BLOCK + self.dispatcher_min_exit_block = self.DEFAULT_DISPATCHER_MIN_EXIT_BLOCK + self.dispatcher_min_comparison_value = self.DEFAULT_DISPATCHER_MIN_COMPARISON_VALUE + + def configure(self, kwargs): + if "min_dispatcher_internal_block" in kwargs.keys(): + self.dispatcher_min_internal_block = kwargs["min_dispatcher_internal_block"] + if "min_dispatcher_exit_block" in kwargs.keys(): + self.dispatcher_min_exit_block = kwargs["min_dispatcher_exit_block"] + if "min_dispatcher_comparison_value" in kwargs.keys(): + self.dispatcher_min_comparison_value = kwargs["min_dispatcher_comparison_value"] + + def specific_checks(self, disp_info: GenericDispatcherInfo) -> bool: + unflat_logger.debug("DispatcherInfo {0} : {1} internals, {2} exits, {3} comparison" + .format(self.blk.serial, len(disp_info.dispatcher_internal_blocks), + len(disp_info.dispatcher_exit_blocks), len(set(disp_info.comparison_values)))) + if len(disp_info.dispatcher_internal_blocks) < self.dispatcher_min_internal_block: + return False + if len(disp_info.dispatcher_exit_blocks) < self.dispatcher_min_exit_block: + return False + if len(set(disp_info.comparison_values)) < self.dispatcher_min_comparison_value: + return False + self.dispatcher_list.append(disp_info) + return True + + def visit_minsn(self): + if self.blk.serial in self.explored_blk_serials: + return 0 + self.explored_blk_serials.append(self.blk.serial) + disp_info = self.DISPATCHER_CLASS(self.blk.mba) + is_good_candidate = disp_info.explore(self.blk) + if not is_good_candidate: + return 0 + if not self.specific_checks(disp_info): + return 0 + self.dispatcher_list.append(disp_info) + return 0 + + def remove_sub_dispatchers(self): + main_dispatcher_list = [] + for dispatcher_1 in self.dispatcher_list: + is_dispatcher_1_sub_dispatcher = False + for dispatcher_2 in self.dispatcher_list: + if dispatcher_1.is_sub_dispatcher(dispatcher_2): + is_dispatcher_1_sub_dispatcher = True + break + if not is_dispatcher_1_sub_dispatcher: + main_dispatcher_list.append(dispatcher_1) + self.dispatcher_list = [x for x in main_dispatcher_list] + + def reset(self): + self.dispatcher_list = [] + self.explored_blk_serials = [] + + def get_dispatcher_list(self) -> List[GenericDispatcherInfo]: + self.remove_sub_dispatchers() + return self.dispatcher_list + + +class GenericUnflatteningRule(FlowOptimizationRule): + DEFAULT_UNFLATTENING_MATURITIES = [MMAT_CALLS, MMAT_GLBOPT1, MMAT_GLBOPT2] + + def __init__(self): + super().__init__() + self.mba = None + self.cur_maturity = MMAT_ZERO + self.cur_maturity_pass = 0 + self.last_pass_nb_patch_done = 0 + self.maturities = self.DEFAULT_UNFLATTENING_MATURITIES + + def check_if_rule_should_be_used(self, blk: mblock_t) -> bool: + if self.cur_maturity == self.mba.maturity: + self.cur_maturity_pass += 1 + else: + self.cur_maturity = self.mba.maturity + self.cur_maturity_pass = 0 + if self.cur_maturity not in self.maturities: + return False + return True + + +class GenericDispatcherUnflatteningRule(GenericUnflatteningRule): + DISPATCHER_COLLECTOR_CLASS = GenericDispatcherCollector + MOP_TRACKER_MAX_NB_BLOCK = 100 + MOP_TRACKER_MAX_NB_PATH = 100 + DEFAULT_MAX_DUPLICATION_PASSES = 20 + DEFAULT_MAX_PASSES = 5 + + def __init__(self): + super().__init__() + self.dispatcher_collector = self.DISPATCHER_COLLECTOR_CLASS() + self.dispatcher_list = [] + self.max_duplication_passes = self.DEFAULT_MAX_DUPLICATION_PASSES + self.max_passes = self.DEFAULT_MAX_PASSES + + def check_if_rule_should_be_used(self, blk: mblock_t) -> bool: + if not super().check_if_rule_should_be_used(blk): + return False + if (self.cur_maturity_pass >= 1) and (self.last_pass_nb_patch_done == 0): + return False + if (self.max_passes is not None) and (self.cur_maturity_pass >= self.max_passes): + return False + return True + + def configure(self, kwargs): + super().configure(kwargs) + if "max_passes" in self.config.keys(): + self.max_passes = self.config["max_passes"] + if "max_duplication_passes" in self.config.keys(): + self.max_duplication_passes = self.config["max_duplication_passes"] + self.dispatcher_collector.configure(kwargs) + + def retrieve_all_dispatchers(self): + self.dispatcher_list = [] + self.dispatcher_collector.reset() + self.mba.for_all_topinsns(self.dispatcher_collector) + self.dispatcher_list = [x for x in self.dispatcher_collector.get_dispatcher_list()] + + def ensure_all_dispatcher_fathers_are_direct(self) -> int: + nb_change = 0 + for dispatcher_info in self.dispatcher_list: + dispatcher_father_list = [self.mba.get_mblock(x) for x in dispatcher_info.entry_block.blk.predset] + for dispatcher_father in dispatcher_father_list: + nb_change += ensure_child_has_an_unconditional_father(dispatcher_father, + dispatcher_info.entry_block.blk) + return nb_change + + def register_initialization_variables(self, mop_tracker): + pass + + def get_dispatcher_father_histories(self, dispatcher_father: mblock_t, + dispatcher_entry_block: GenericDispatcherBlockInfo) -> List[MopHistory]: + father_tracker = MopTracker(dispatcher_entry_block.use_before_def_list, + max_nb_block=self.MOP_TRACKER_MAX_NB_BLOCK, max_path=self.MOP_TRACKER_MAX_NB_PATH) + father_tracker.reset() + self.register_initialization_variables(father_tracker) + father_histories = father_tracker.search_backward(dispatcher_father, None) + return father_histories + + def check_if_histories_are_resolved(self, mop_histories: List[MopHistory]) -> bool: + return all([mop_history.is_resolved() for mop_history in mop_histories]) + + def ensure_dispatcher_father_is_resolvable(self, dispatcher_father: mblock_t, + dispatcher_entry_block: GenericDispatcherBlockInfo) -> int: + father_histories = self.get_dispatcher_father_histories(dispatcher_father, dispatcher_entry_block) + father_histories_cst = get_all_possibles_values(father_histories, dispatcher_entry_block.use_before_def_list, + verbose=False) + father_is_resolvable = self.check_if_histories_are_resolved(father_histories) + if not father_is_resolvable: + raise NotDuplicableFatherException("Dispatcher {0} predecessor {1} is not duplicable: {2}" + .format(dispatcher_entry_block.serial, dispatcher_father.serial, + father_histories_cst)) + + unflat_logger.info("Dispatcher {0} predecessor {1} is resolvable: {2}" + .format(dispatcher_entry_block.serial, dispatcher_father.serial, father_histories_cst)) + nb_duplication, nb_change = duplicate_histories(father_histories, max_nb_pass=self.max_duplication_passes) + unflat_logger.info("Dispatcher {0} predecessor {1} duplication: {2} blocks created, {3} changes made" + .format(dispatcher_entry_block.serial, dispatcher_father.serial, nb_duplication, nb_change)) + return nb_duplication + nb_change + + def resolve_dispatcher_father(self, dispatcher_father: mblock_t, dispatcher_info: GenericDispatcherInfo) -> int: + dispatcher_father_histories = self.get_dispatcher_father_histories(dispatcher_father, + dispatcher_info.entry_block) + father_is_resolvable = self.check_if_histories_are_resolved(dispatcher_father_histories) + if not father_is_resolvable: + raise NotResolvableFatherException("Can't fix block {0}".format(dispatcher_father.serial)) + mop_searched_values_list = get_all_possibles_values(dispatcher_father_histories, + dispatcher_info.entry_block.use_before_def_list, + verbose=False) + all_values_found = check_if_all_values_are_found(mop_searched_values_list) + if not all_values_found: + raise NotResolvableFatherException("Can't fix block {0}".format(dispatcher_father.serial)) + + ref_mop_searched_values = mop_searched_values_list[0] + for tmp_mop_searched_values in mop_searched_values_list: + if tmp_mop_searched_values != ref_mop_searched_values: + raise NotResolvableFatherException("Dispatcher {0} predecessor {1} is not resolvable: {2}" + .format(dispatcher_info.entry_block.serial, dispatcher_father.serial, + mop_searched_values_list)) + + target_blk, disp_ins = dispatcher_info.emulate_dispatcher_with_father_history(dispatcher_father_histories[0]) + if target_blk is not None: + unflat_logger.debug("Unflattening graph: Making {0} goto {1}" + .format(dispatcher_father.serial, target_blk.serial)) + ins_to_copy = [ins for ins in disp_ins if ((ins is not None) and (ins.opcode not in CONTROL_FLOW_OPCODES))] + if len(ins_to_copy) > 0: + unflat_logger.info("Instruction copied: {0}: {1}" + .format(len(ins_to_copy), + ", ".join([format_minsn_t(ins_copied) for ins_copied in ins_to_copy]))) + dispatcher_side_effect_blk = create_block(self.mba.get_mblock(self.mba.qty - 2), ins_to_copy, + is_0_way=(target_blk.type == BLT_0WAY)) + change_1way_block_successor(dispatcher_father, dispatcher_side_effect_blk.serial) + change_1way_block_successor(dispatcher_side_effect_blk, target_blk.serial) + else: + change_1way_block_successor(dispatcher_father, target_blk.serial) + return 2 + + raise NotResolvableFatherException("Can't fix block {0}: no block for key: {1}" + .format(dispatcher_father.serial, mop_searched_values_list)) + + def remove_flattening(self) -> int: + total_nb_change = ensure_last_block_is_goto(self.mba) + total_nb_change += self.ensure_all_dispatcher_fathers_are_direct() + nb_flattened_branches = 0 + for dispatcher_info in self.dispatcher_list: + dump_microcode_for_debug(self.mba, self.log_dir, "unflat_{0}_dispatcher_{1}_before_duplication" + .format(self.cur_maturity_pass, dispatcher_info.entry_block.serial)) + unflat_logger.info("Searching dispatcher for entry block {0} {1} -> with variables ({2})..." + .format(dispatcher_info.entry_block.serial, format_mop_t(dispatcher_info.mop_compared), + format_mop_list(dispatcher_info.entry_block.use_before_def_list))) + dispatcher_father_list = [self.mba.get_mblock(x) for x in dispatcher_info.entry_block.blk.predset] + for dispatcher_father in dispatcher_father_list: + try: + total_nb_change += self.ensure_dispatcher_father_is_resolvable(dispatcher_father, + dispatcher_info.entry_block) + except NotDuplicableFatherException as e: + unflat_logger.warning(e) + pass + dump_microcode_for_debug(self.mba, self.log_dir, "unflat_{0}_dispatcher_{1}_after_duplication" + .format(self.cur_maturity_pass, dispatcher_info.entry_block.serial)) + # During the previous step we changed dispatcher entry block fathers, so we need to reload them + dispatcher_father_list = [self.mba.get_mblock(x) for x in dispatcher_info.entry_block.blk.predset] + nb_flattened_branches = 0 + for dispatcher_father in dispatcher_father_list: + try: + nb_flattened_branches += self.resolve_dispatcher_father(dispatcher_father, dispatcher_info) + except NotResolvableFatherException as e: + unflat_logger.warning(e) + pass + dump_microcode_for_debug(self.mba, self.log_dir, "unflat_{0}_dispatcher_{1}_after_unflattening" + .format(self.cur_maturity_pass, dispatcher_info.entry_block.serial)) + + unflat_logger.info("Unflattening removed {0} branch".format(nb_flattened_branches)) + total_nb_change += nb_flattened_branches + return total_nb_change + + def optimize(self, blk: mblock_t) -> int: + self.mba = blk.mba + if not self.check_if_rule_should_be_used(blk): + return 0 + self.last_pass_nb_patch_done = 0 + unflat_logger.info("Unflattening at maturity {0} path {1}".format(self.cur_maturity, self.cur_maturity_pass)) + dump_microcode_for_debug(self.mba, self.log_dir, "unflat_{0}_start".format(self.cur_maturity_pass)) + self.retrieve_all_dispatchers() + if len(self.dispatcher_list) == 0: + unflat_logger.info("No dispatcher found at maturity {0}".format(self.mba.maturity)) + return 0 + else: + unflat_logger.info("Unflattening: {0} dispatcher(s) found".format(len(self.dispatcher_list))) + for dispatcher_info in self.dispatcher_list: + dispatcher_info.print_info() + self.last_pass_nb_patch_done = self.remove_flattening() + unflat_logger.info("Unflattening at maturity {0} path {1}: {2} changes" + .format(self.cur_maturity, self.cur_maturity_pass, self.last_pass_nb_patch_done)) + mba_deep_cleaning(self.mba) + dump_microcode_for_debug(self.mba, self.log_dir, "unflat_{0}_after_cleaning".format(self.cur_maturity_pass)) + if self.last_pass_nb_patch_done > 0: + self.mba.mark_chains_dirty() + self.mba.optimize_local(0) + self.mba.verify(True) + return self.last_pass_nb_patch_done diff --git a/d810/optimizers/flow/flattening/unflattener.py b/d810/optimizers/flow/flattening/unflattener.py new file mode 100644 index 0000000..9d4cf28 --- /dev/null +++ b/d810/optimizers/flow/flattening/unflattener.py @@ -0,0 +1,115 @@ +import logging +from typing import Tuple, List +from ida_hexrays import * + +from d810.hexrays_helpers import extract_num_mop, append_mop_if_not_in_list +from d810.optimizers.flow.flattening.generic import GenericDispatcherCollector, GenericDispatcherInfo, \ + GenericDispatcherBlockInfo, GenericDispatcherUnflatteningRule + + +unflat_logger = logging.getLogger('D810.unflat') +FLATTENING_JUMP_OPCODES = [m_jnz, m_jz, m_jae, m_jb, m_ja, m_jbe, m_jg, m_jge, m_jl, m_jle] + + +class OllvmDispatcherBlockInfo(GenericDispatcherBlockInfo): + pass + + +class OllvmDispatcherInfo(GenericDispatcherInfo): + def explore(self, blk: mblock_t) -> bool: + self.reset() + if not self._is_candidate_for_dispatcher_entry_block(blk): + return False + self.entry_block = OllvmDispatcherBlockInfo(blk) + self.entry_block.parse() + for used_mop in self.entry_block.use_list: + append_mop_if_not_in_list(used_mop, self.entry_block.assume_def_list) + self.dispatcher_internal_blocks.append(self.entry_block) + num_mop, self.mop_compared = self._get_comparison_info(self.entry_block.blk) + self.comparison_values.append(num_mop.nnn.value) + self._explore_children(self.entry_block) + dispatcher_blk_with_external_father = self._get_dispatcher_blocks_with_external_father() + if len(dispatcher_blk_with_external_father) != 0: + return False + return True + + def _is_candidate_for_dispatcher_entry_block(self, blk: mblock_t) -> bool: + # blk must be a condition branch with one numerical operand + num_mop, mop_compared = self._get_comparison_info(blk) + if (num_mop is None) or (mop_compared is None): + return False + # Its fathers are not conditional branch with this mop + for father_serial in blk.predset: + father_blk = self.mba.get_mblock(father_serial) + father_num_mop, father_mop_compared = self._get_comparison_info(father_blk) + if (father_num_mop is not None) and (father_mop_compared is not None): + if mop_compared.equal_mops(father_mop_compared, EQ_IGNSIZE): + return False + return True + + def _get_comparison_info(self, blk: mblock_t) -> Tuple[mop_t, mop_t]: + # We check if blk is a good candidate for dispatcher entry block: blk.tail must be a conditional branch + if (blk.tail is None) or (blk.tail.opcode not in FLATTENING_JUMP_OPCODES): + return None, None + # One operand must be numerical + num_mop, mop_compared = extract_num_mop(blk.tail) + if num_mop is None or mop_compared is None: + return None, None + return num_mop, mop_compared + + def is_part_of_dispatcher(self, block_info: OllvmDispatcherBlockInfo) -> bool: + is_ok = block_info.does_only_need(block_info.father.assume_def_list) + if not is_ok: + return False + if (block_info.blk.tail is not None) and (block_info.blk.tail.opcode not in FLATTENING_JUMP_OPCODES): + return False + return True + + def _explore_children(self, father_info: OllvmDispatcherBlockInfo): + for child_serial in father_info.blk.succset: + if child_serial in [blk_info.blk.serial for blk_info in self.dispatcher_internal_blocks]: + return + if child_serial in [blk_info.blk.serial for blk_info in self.dispatcher_exit_blocks]: + return + child_blk = self.mba.get_mblock(child_serial) + child_info = OllvmDispatcherBlockInfo(child_blk, father_info) + child_info.parse() + if not self.is_part_of_dispatcher(child_info): + self.dispatcher_exit_blocks.append(child_info) + else: + self.dispatcher_internal_blocks.append(child_info) + if child_info.comparison_value is not None: + self.comparison_values.append(child_info.comparison_value) + self._explore_children(child_info) + + def _get_external_fathers(self, block_info: OllvmDispatcherBlockInfo) -> List[mblock_t]: + internal_serials = [blk_info.blk.serial for blk_info in self.dispatcher_internal_blocks] + external_fathers = [] + for blk_father in block_info.blk.predset: + if blk_father not in internal_serials: + external_fathers.append(blk_father) + return external_fathers + + def _get_dispatcher_blocks_with_external_father(self) -> List[mblock_t]: + dispatcher_blocks_with_external_father = [] + for blk_info in self.dispatcher_internal_blocks: + if blk_info.blk.serial != self.entry_block.blk.serial: + external_fathers = self._get_external_fathers(blk_info) + if len(external_fathers) > 0: + dispatcher_blocks_with_external_father.append(blk_info) + return dispatcher_blocks_with_external_father + + +class OllvmDispatcherCollector(GenericDispatcherCollector): + DISPATCHER_CLASS = OllvmDispatcherInfo + DEFAULT_DISPATCHER_MIN_INTERNAL_BLOCK = 2 + DEFAULT_DISPATCHER_MIN_EXIT_BLOCK = 2 + DEFAULT_DISPATCHER_MIN_COMPARISON_VALUE = 2 + + +class Unflattener(GenericDispatcherUnflatteningRule): + DESCRIPTION = "Remove control flow flattening generated by OLLVM" + DISPATCHER_COLLECTOR_CLASS = OllvmDispatcherCollector + DEFAULT_UNFLATTENING_MATURITIES = [MMAT_CALLS, MMAT_GLBOPT1, MMAT_GLBOPT2] + DEFAULT_MAX_DUPLICATION_PASSES = 20 + DEFAULT_MAX_PASSES = 5 diff --git a/d810/optimizers/flow/flattening/unflattener_fake_jump.py b/d810/optimizers/flow/flattening/unflattener_fake_jump.py new file mode 100644 index 0000000..0475672 --- /dev/null +++ b/d810/optimizers/flow/flattening/unflattener_fake_jump.py @@ -0,0 +1,95 @@ +import logging +from typing import List +from ida_hexrays import * + +from d810.tracker import MopTracker +from d810.cfg_utils import change_1way_block_successor +from d810.hexrays_formatters import format_minsn_t, dump_microcode_for_debug +from d810.optimizers.flow.flattening.utils import get_all_possibles_values +from d810.optimizers.flow.flattening.generic import GenericUnflatteningRule + +unflat_logger = logging.getLogger('D810.unflat') + +FAKE_LOOP_OPCODES = [m_jz, m_jnz] + + +class UnflattenerFakeJump(GenericUnflatteningRule): + DESCRIPTION = "Check if a jump is always taken for each father blocks and remove them" + DEFAULT_UNFLATTENING_MATURITIES = [MMAT_CALLS, MMAT_GLBOPT1] + DEFAULT_MAX_PASSES = None + + def analyze_blk(self, blk: mblock_t) -> int: + if (blk.tail is None) or blk.tail.opcode not in FAKE_LOOP_OPCODES: + return 0 + if blk.get_reginsn_qty() != 1: + return 0 + if blk.tail.r.t != mop_n: + return 0 + unflat_logger.info("Checking if block {0} is fake loop: {1}".format(blk.serial, format_minsn_t(blk.tail))) + op_compared = mop_t(blk.tail.l) + blk_preset_list = [x for x in blk.predset] + nb_change = 0 + for pred_serial in blk_preset_list: + cmp_variable_tracker = MopTracker([op_compared], max_nb_block=100, max_path=1000) + cmp_variable_tracker.reset() + pred_blk = blk.mba.get_mblock(pred_serial) + pred_histories = cmp_variable_tracker.search_backward(pred_blk, pred_blk.tail) + + father_is_resolvable = all([father_history.is_resolved() for father_history in pred_histories]) + if not father_is_resolvable: + return 0 + pred_values = get_all_possibles_values(pred_histories, [op_compared]) + pred_values = [x[0] for x in pred_values] + if None in pred_values: + unflat_logger.info("Some path are not resolved, can't fix jump") + return 0 + unflat_logger.info("Pred {0} has {1} possible path ({2} different cst): {3}" + .format(pred_blk.serial, len(pred_values), len(set(pred_values)), pred_values)) + if self.fix_successor(blk, pred_blk, pred_values): + nb_change += 1 + return nb_change + + def fix_successor(self, fake_loop_block: mblock_t, pred: mblock_t, pred_comparison_values: List[int]) -> bool: + if len(pred_comparison_values) == 0: + return False + jmp_ins = fake_loop_block.tail + compared_value = jmp_ins.r.nnn.value + jmp_taken = False + jmp_not_taken = False + dst_serial = None + if jmp_ins.opcode == m_jz: + jmp_taken = all([possible_value == compared_value for possible_value in pred_comparison_values]) + + jmp_not_taken = all([possible_value != compared_value for possible_value in pred_comparison_values]) + elif jmp_ins.opcode == m_jnz: + jmp_taken = all([possible_value != compared_value for possible_value in pred_comparison_values]) + jmp_not_taken = all([possible_value == compared_value for possible_value in pred_comparison_values]) + # TODO: handles other jumps cases + if jmp_taken: + unflat_logger.info("It seems that '{0}' is always taken when coming from {1}: {2}" + .format(format_minsn_t(jmp_ins), pred.serial, pred_comparison_values)) + dst_serial = jmp_ins.d.b + if jmp_not_taken: + unflat_logger.info("It seems that '{0}' is never taken when coming from {1}: {2}" + .format(format_minsn_t(jmp_ins), pred.serial, pred_comparison_values)) + dst_serial = fake_loop_block.serial + 1 + if dst_serial is None: + unflat_logger.debug("Jump seems legit '{0}' from {1}: {2}" + .format(format_minsn_t(jmp_ins), pred.serial, pred_comparison_values)) + return False + dump_microcode_for_debug(self.mba, self.log_dir, "{0}_before_fake_jump".format(self.cur_maturity_pass)) + unflat_logger.info("Making pred {0} with value {1} goto {2} ({3})" + .format(pred.serial, pred_comparison_values, dst_serial, format_minsn_t(jmp_ins))) + dump_microcode_for_debug(self.mba, self.log_dir, "{0}_after_fake_jump".format(self.cur_maturity_pass)) + return change_1way_block_successor(pred, dst_serial) + + def optimize(self, blk: mblock_t) -> int: + self.mba = blk.mba + if not self.check_if_rule_should_be_used(blk): + return 0 + self.last_pass_nb_patch_done = self.analyze_blk(blk) + if self.last_pass_nb_patch_done > 0: + self.mba.mark_chains_dirty() + self.mba.optimize_local(0) + self.mba.verify(True) + return self.last_pass_nb_patch_done diff --git a/d810/optimizers/flow/flattening/unflattener_indirect.py b/d810/optimizers/flow/flattening/unflattener_indirect.py new file mode 100644 index 0000000..e4c73ca --- /dev/null +++ b/d810/optimizers/flow/flattening/unflattener_indirect.py @@ -0,0 +1,116 @@ +import logging +import idaapi +from typing import List +from ida_hexrays import * + +from d810.hexrays_helpers import append_mop_if_not_in_list, AND_TABLE, CONTROL_FLOW_OPCODES +from d810.tracker import MopTracker, MopHistory +from d810.optimizers.flow.flattening.generic import GenericDispatcherBlockInfo, GenericDispatcherInfo, \ + GenericDispatcherCollector, GenericDispatcherUnflatteningRule, NotDuplicableFatherException, DispatcherUnflatteningException, NotResolvableFatherException +from d810.optimizers.flow.flattening.utils import configure_mop_tracker_log_verbosity, restore_mop_tracker_log_verbosity +from d810.tracker import duplicate_histories +from d810.cfg_utils import create_block, change_1way_block_successor +from d810.hexrays_formatters import format_minsn_t, format_mop_t +from d810.emulator import MicroCodeEnvironment, MicroCodeInterpreter + +unflat_logger = logging.getLogger('D810.unflat') +FLATTENING_JUMP_OPCODES = [m_jtbl] + + +class TigressIndirectDispatcherBlockInfo(GenericDispatcherBlockInfo): + pass + + +class TigressIndirectDispatcherInfo(GenericDispatcherInfo): + def explore(self, blk: mblock_t): + self.reset() + if not self._is_candidate_for_dispatcher_entry_block(blk): + return + self.mop_compared = self._get_comparison_info(blk) + self.entry_block = TigressIndirectDispatcherBlockInfo(blk) + self.entry_block.parse() + for used_mop in self.entry_block.use_list: + append_mop_if_not_in_list(used_mop, self.entry_block.assume_def_list) + self.dispatcher_internal_blocks.append(self.entry_block) + + self.dispatcher_exit_blocks = [] + self.comparison_values = [] + return True + + def _get_comparison_info(self, blk: mblock_t): + # blk.tail must be a jtbl + if (blk.tail is None) or (blk.tail.opcode != m_ijmp): + return None, None + return blk.tail.l + + def _is_candidate_for_dispatcher_entry_block(self, blk: mblock_t): + if (blk.tail is None) or (blk.tail.opcode != m_ijmp): + return False + return True + + def should_emulation_continue(self, cur_blk: mblock_t): + if (cur_blk is not None) and (cur_blk.serial == self.entry_block.serial): + return True + return False + + +class TigressIndirectDispatcherCollector(GenericDispatcherCollector): + DISPATCHER_CLASS = TigressIndirectDispatcherInfo + DEFAULT_DISPATCHER_MIN_INTERNAL_BLOCK = 0 + DEFAULT_DISPATCHER_MIN_EXIT_BLOCK = 0 + DEFAULT_DISPATCHER_MIN_COMPARISON_VALUE = 0 + + +class LabelTableInfo(object): + def __init__(self, sp_offset, mem_offset, nb_elt): + self.sp_offset = sp_offset + self.mem_offset = mem_offset + self.nb_elt = nb_elt + + def update_mop_tracker(self, mba: mbl_array_t, mop_tracker: MopTracker): + stack_array_base_address = mba.stkoff_ida2vd(self.sp_offset) + # print("stack_array_base_address: {0:x}".format(stack_array_base_address)) + for i in range(self.nb_elt): + tmp_mop = mop_t() + tmp_mop.erase() + tmp_mop._make_stkvar(mba, stack_array_base_address + 8 * i) + tmp_mop.size = 8 + mem_val = idaapi.get_qword(self.mem_offset + 8 * i) & AND_TABLE[8] + mop_tracker.add_mop_definition(tmp_mop, mem_val) + + +class UnflattenerTigressIndirect(GenericDispatcherUnflatteningRule): + DESCRIPTION = "" + DISPATCHER_COLLECTOR_CLASS = TigressIndirectDispatcherCollector + DEFAULT_UNFLATTENING_MATURITIES = [MMAT_LOCOPT] + DEFAULT_MAX_DUPLICATION_PASSES = 20 + DEFAULT_MAX_PASSES = 1 + + def __init__(self): + super().__init__() + self.label_info = None + self.goto_table_info = {} + + def configure(self, kwargs): + super().configure(kwargs) + if "goto_table_info" in self.config.keys(): + for ea_str, table_info in self.config["goto_table_info"].items(): + self.goto_table_info[int(ea_str, 16)] = LabelTableInfo(sp_offset=int(table_info["stack_table_offset"], 16), + mem_offset=int(table_info["table_address"], 16), + nb_elt=table_info["table_nb_elt"]) + + def check_if_rule_should_be_used(self, blk: mblock_t): + if not super().check_if_rule_should_be_used(blk): + return False + if self.mba.entry_ea not in self.goto_table_info: + return False + if (self.cur_maturity_pass >= 1) and (self.last_pass_nb_patch_done == 0): + return False + self.label_info = self.goto_table_info[self.mba.entry_ea] + return True + + def register_initialization_variables(self, mop_tracker: MopTracker): + self.label_info.update_mop_tracker(self.mba, mop_tracker) + + def check_if_histories_are_resolved(self, mop_histories: List[MopHistory]): + return True diff --git a/d810/optimizers/flow/flattening/unflattener_switch_case.py b/d810/optimizers/flow/flattening/unflattener_switch_case.py new file mode 100644 index 0000000..b2ff232 --- /dev/null +++ b/d810/optimizers/flow/flattening/unflattener_switch_case.py @@ -0,0 +1,60 @@ +import logging +from ida_hexrays import * + +from d810.hexrays_helpers import append_mop_if_not_in_list +from d810.optimizers.flow.flattening.generic import GenericDispatcherBlockInfo, GenericDispatcherInfo, \ + GenericDispatcherCollector, GenericDispatcherUnflatteningRule + + +unflat_logger = logging.getLogger('D810.unflat') +FLATTENING_JUMP_OPCODES = [m_jtbl] + + +class TigressSwitchDispatcherBlockInfo(GenericDispatcherBlockInfo): + pass + + +class TigressSwitchDispatcherInfo(GenericDispatcherInfo): + def explore(self, blk: mblock_t): + self.reset() + if not self._is_candidate_for_dispatcher_entry_block(blk): + return + self.mop_compared, mcases = self._get_comparison_info(blk) + self.entry_block = TigressSwitchDispatcherBlockInfo(blk) + self.entry_block.parse() + for used_mop in self.entry_block.use_list: + append_mop_if_not_in_list(used_mop, self.entry_block.assume_def_list) + self.dispatcher_internal_blocks.append(self.entry_block) + for possible_values, target_block_serial in zip(mcases.c.values, mcases.c.targets): + if target_block_serial == self.entry_block.blk.serial: + continue + exit_block = TigressSwitchDispatcherBlockInfo(blk.mba.get_mblock(target_block_serial), self.entry_block) + self.dispatcher_exit_blocks.append(exit_block) + self.comparison_values.append(possible_values[0]) + return True + + def _get_comparison_info(self, blk: mblock_t): + # blk.tail must be a jtbl + if (blk.tail is None) or (blk.tail.opcode != m_jtbl): + return None, None + return blk.tail.l, blk.tail.r + + def _is_candidate_for_dispatcher_entry_block(self, blk: mblock_t): + if (blk.tail is None) or (blk.tail.opcode != m_jtbl): + return False + return True + + +class TigressSwitchDispatcherCollector(GenericDispatcherCollector): + DISPATCHER_CLASS = TigressSwitchDispatcherInfo + DEFAULT_DISPATCHER_MIN_INTERNAL_BLOCK = 0 + DEFAULT_DISPATCHER_MIN_EXIT_BLOCK = 4 + DEFAULT_DISPATCHER_MIN_COMPARISON_VALUE = 4 + + +class UnflattenerSwitchCase(GenericDispatcherUnflatteningRule): + DESCRIPTION = "Remove control flow flattening generated by Tigress with Switch case dispatcher" + DISPATCHER_COLLECTOR_CLASS = TigressSwitchDispatcherCollector + DEFAULT_UNFLATTENING_MATURITIES = [MMAT_GLBOPT1] + DEFAULT_MAX_DUPLICATION_PASSES = 20 + DEFAULT_MAX_PASSES = 5 diff --git a/d810/optimizers/flow/flattening/utils.py b/d810/optimizers/flow/flattening/utils.py new file mode 100644 index 0000000..0136e9c --- /dev/null +++ b/d810/optimizers/flow/flattening/utils.py @@ -0,0 +1,56 @@ +import logging + + +tracker_logger = logging.getLogger('D810.tracker') +emulator_logger = logging.getLogger('D810.emulator') + + +class UnflatteningException(Exception): + pass + + +class DispatcherUnflatteningException(UnflatteningException): + pass + + +class NotDuplicableFatherException(UnflatteningException): + pass + + +class NotResolvableFatherException(UnflatteningException): + pass + + + + +def configure_mop_tracker_log_verbosity(verbose=False): + tracker_log_level = tracker_logger.getEffectiveLevel() + emulator_log_level = emulator_logger.getEffectiveLevel() + if not verbose: + tracker_logger.setLevel(logging.ERROR) + emulator_logger.setLevel(logging.ERROR) + return [tracker_log_level, emulator_log_level] + + +def restore_mop_tracker_log_verbosity(tracker_log_level, emulator_log_level): + tracker_logger.setLevel(tracker_log_level) + emulator_logger.setLevel(emulator_log_level) + + +def get_all_possibles_values(mop_histories, searched_mop_list, verbose=False): + log_levels = configure_mop_tracker_log_verbosity(verbose) + mop_cst_values_list = [] + for mop_history in mop_histories: + mop_cst_values_list.append([mop_history.get_mop_constant_value(searched_mop) + for searched_mop in searched_mop_list]) + restore_mop_tracker_log_verbosity(*log_levels) + return mop_cst_values_list + + +def check_if_all_values_are_found(mop_cst_values_list): + all_values_are_found = True + for cst_list in mop_cst_values_list: + if None in cst_list: + all_values_are_found = False + break + return all_values_are_found diff --git a/d810/optimizers/flow/handler.py b/d810/optimizers/flow/handler.py new file mode 100644 index 0000000..8410e27 --- /dev/null +++ b/d810/optimizers/flow/handler.py @@ -0,0 +1,39 @@ +import logging +import idc + +from d810.optimizers.handler import OptimizationRule, DEFAULT_FLOW_MATURITIES + +logger = logging.getLogger('D810.optimizer') + + +class FlowOptimizationRule(OptimizationRule): + def __init__(self): + super().__init__() + self.maturities = DEFAULT_FLOW_MATURITIES + self.use_whitelist = False + self.whitelisted_function_ea_list = [] + self.use_blacklist = False + self.blacklisted_function_ea_list = [] + + def configure(self, kwargs): + super().configure(kwargs) + self.use_whitelist = False + self.whitelisted_function_ea_list = [] + self.use_blacklist = False + self.blacklisted_function_ea_list = [] + if "whitelisted_functions" in self.config.keys(): + self.use_whitelist = True + for func_ea in self.config["whitelisted_functions"]: + self.whitelisted_function_ea_list.append(int(func_ea, 16)) + func_name_list = [idc.get_func_name(ea) for ea in self.whitelisted_function_ea_list] + logger.info("Whitelisted functions for {0}: {1} -> {2}".format(self.__class__.__name__, + self.whitelisted_function_ea_list, + func_name_list)) + if "blacklisted_functions" in self.config.keys(): + self.use_blacklist = True + for func_ea in self.config["whitelisted_functions"]: + self.blacklisted_function_ea_list.append(int(func_ea, 16)) + func_name_list = [idc.get_func_name(ea) for ea in self.blacklisted_function_ea_list] + logger.info("Blacklisted functions for {0}: {1} -> {2}".format(self.__class__.__name__, + self.blacklisted_function_ea_list, + func_name_list)) diff --git a/d810/optimizers/flow/jumps/__init__.py b/d810/optimizers/flow/jumps/__init__.py new file mode 100644 index 0000000..bcf409e --- /dev/null +++ b/d810/optimizers/flow/jumps/__init__.py @@ -0,0 +1,11 @@ +from d810.utils import get_all_subclasses +from d810.optimizers.flow.jumps.handler import JumpOptimizationRule, JumpFixer +from d810.optimizers.flow.jumps.opaque import * +from d810.optimizers.flow.jumps.tricks import * + + +JUMP_OPTIMIZATION_RULES = [x() for x in get_all_subclasses(JumpOptimizationRule)] +jump_fixer = JumpFixer() +for jump_optimization_rule in JUMP_OPTIMIZATION_RULES: + jump_fixer.register_rule(jump_optimization_rule) +JUMP_OPTIMIZATION_BLOCK_RULES = [jump_fixer] diff --git a/d810/optimizers/flow/jumps/handler.py b/d810/optimizers/flow/jumps/handler.py new file mode 100644 index 0000000..bc8d158 --- /dev/null +++ b/d810/optimizers/flow/jumps/handler.py @@ -0,0 +1,183 @@ +import logging +from ida_hexrays import * +from typing import Union + +from d810.optimizers.instructions.handler import InstructionOptimizationRule +from d810.optimizers.instructions.pattern_matching.handler import ast_generator +from d810.ast import mop_to_ast, AstNode +from d810.hexrays_formatters import format_minsn_t, opcode_to_string +from d810.optimizers.flow.handler import FlowOptimizationRule +from d810.cfg_utils import make_2way_block_goto, is_conditional_jump, change_2way_block_conditional_successor + + +logger = logging.getLogger("D810.branch_fixer") +optimizer_logger = logging.getLogger('D810.optimizer') + + +class JumpOptimizationRule(InstructionOptimizationRule): + ORIGINAL_JUMP_OPCODES = [] + LEFT_PATTERN = None + RIGHT_PATTERN = None + + REPLACEMENT_OPCODE = None + REPLACEMENT_LEFT_PATTERN = None + REPLACEMENT_RIGHT_PATTERN = None + + FUZZ_PATTERNS = True + + def __init__(self): + super().__init__() + self.fuzz_patterns = self.FUZZ_PATTERNS + self.left_pattern_candidates = [] + self.right_pattern_candidates = [] + self.jump_original_block_serial = None + self.direct_block_serial = None + self.jump_replacement_block_serial = None + + def configure(self, fuzz_pattern=None, **kwargs): + super().configure(kwargs) + if fuzz_pattern is not None: + self.fuzz_patterns = fuzz_pattern + self._generate_pattern_candidates() + + def _generate_pattern_candidates(self): + self.fuzz_patterns = self.FUZZ_PATTERNS + if self.LEFT_PATTERN is not None: + self.LEFT_PATTERN.reset_mops() + if not self.fuzz_patterns: + self.left_pattern_candidates = [self.LEFT_PATTERN] + else: + self.left_pattern_candidates = ast_generator(self.LEFT_PATTERN) + if self.RIGHT_PATTERN is not None: + self.RIGHT_PATTERN.reset_mops() + if not self.fuzz_patterns: + self.right_pattern_candidates = [self.RIGHT_PATTERN] + else: + self.right_pattern_candidates = ast_generator(self.RIGHT_PATTERN) + + def check_candidate(self, opcode, left_candidate: AstNode, right_candidate: AstNode): + return False + + def get_valid_candidates(self, instruction, left_ast: AstNode, right_ast: AstNode, stop_early=True): + valid_candidates = [] + if left_ast is None or right_ast is None: + return [] + + for left_candidate_pattern in self.left_pattern_candidates: + if not left_candidate_pattern.check_pattern_and_copy_mops(left_ast): + continue + for right_candidate_pattern in self.right_pattern_candidates: + if not right_candidate_pattern.check_pattern_and_copy_mops(right_ast): + continue + if not self.check_candidate(instruction.opcode, left_candidate_pattern, right_candidate_pattern): + continue + valid_candidates.append([left_candidate_pattern, right_candidate_pattern]) + if stop_early: + return valid_candidates + return [] + + def check_pattern_and_replace(self, blk: mblock_t, instruction: minsn_t, left_ast: AstNode, right_ast: AstNode): + if instruction.opcode not in self.ORIGINAL_JUMP_OPCODES: + return None + self.jump_original_block_serial = instruction.d.b + self.direct_block_serial = blk.serial + 1 + self.jump_replacement_block_serial = None + valid_candidates = self.get_valid_candidates(instruction, left_ast, right_ast, stop_early=True) + if len(valid_candidates) == 0: + return None + if self.jump_original_block_serial is None: + self.jump_replacement_block_serial = self.jump_original_block_serial + left_candidate, right_candidate = valid_candidates[0] + new_ins = self.get_replacement(instruction, left_candidate, right_candidate) + return new_ins + + def get_replacement(self, original_ins: minsn_t, left_candidate: AstNode, right_candidate: AstNode): + new_left_mop = None + new_right_mop = None + new_dst_mop = None + + if self.jump_original_block_serial is not None: + new_dst_mop = mop_t() + new_dst_mop.make_blkref(self.jump_replacement_block_serial) + + if self.REPLACEMENT_LEFT_PATTERN is not None: + is_ok = self.REPLACEMENT_LEFT_PATTERN.update_leafs_mop(left_candidate, right_candidate) + if not is_ok: + return None + new_left_mop = self.REPLACEMENT_LEFT_PATTERN.create_mop(original_ins.ea) + if self.REPLACEMENT_RIGHT_PATTERN is not None: + is_ok = self.REPLACEMENT_RIGHT_PATTERN.update_leafs_mop(left_candidate, right_candidate) + if not is_ok: + return None + new_right_mop = self.REPLACEMENT_RIGHT_PATTERN.create_mop(original_ins.ea) + + new_ins = self.create_new_ins(original_ins, new_left_mop, new_right_mop, new_dst_mop) + return new_ins + + def create_new_ins(self, original_ins: minsn_t, new_left_mop: mop_t, + new_right_mop: Union[None, mop_t] = None, new_dst_mop: Union[None, mop_t] = None) -> minsn_t: + new_ins = minsn_t(original_ins) + new_ins.opcode = self.REPLACEMENT_OPCODE + if self.REPLACEMENT_OPCODE == m_goto: + new_ins.l = new_dst_mop + new_ins.r.erase() + new_ins.d.erase() + return new_ins + new_ins.l = new_left_mop + if new_right_mop is not None: + new_ins.r = new_right_mop + if new_dst_mop is not None: + new_ins.d = new_dst_mop + return new_ins + + @property + def description(self): + self.LEFT_PATTERN.reset_mops() + self.RIGHT_PATTERN.reset_mops() + return "{0}: {1}, {2}".format(",".join([opcode_to_string(x) for x in self.JMP_OPCODES]), + self.LEFT_PATTERN, self.RIGHT_PATTERN) + + +class JumpFixer(FlowOptimizationRule): + def __init__(self): + super().__init__() + self.known_rules = [] + self.rules = [] + + def register_rule(self, rule: JumpOptimizationRule): + self.known_rules.append(rule) + + def configure(self, kwargs): + super().configure(kwargs) + if "enabled_rules" in self.config.keys(): + for rule in self.known_rules: + if rule.name in self.config["enabled_rules"]: + rule.configure() + self.rules.append(rule) + optimizer_logger.debug("JumpFixer enables rule {0}".format(rule.name)) + else: + optimizer_logger.debug("JumpFixer disables rule {0}".format(rule.name)) + + def optimize(self, blk: mblock_t) -> bool: + if not is_conditional_jump(blk): + return False + left_ast = mop_to_ast(blk.tail.l) + right_ast = mop_to_ast(blk.tail.r) + for rule in self.rules: + try: + new_ins = rule.check_pattern_and_replace(blk, blk.tail, left_ast, right_ast) + if new_ins: + optimizer_logger.info("Rule {0} matched:".format(rule.name)) + optimizer_logger.info(" orig: {0}".format(format_minsn_t(blk.tail))) + optimizer_logger.info(" new : {0}".format(format_minsn_t(new_ins))) + if new_ins.opcode == m_goto: + make_2way_block_goto(blk, new_ins.d.b) + else: + change_2way_block_conditional_successor(blk, new_ins.d.b) + blk.make_nop(blk.tail) + blk.insert_into_block(new_ins, blk.tail) + return True + except RuntimeError as e: + optimizer_logger.error("Error during rule {0} for instruction {1}: {2}" + .format(rule, format_minsn_t(blk.tail), e)) + return False diff --git a/d810/optimizers/flow/jumps/opaque.py b/d810/optimizers/flow/jumps/opaque.py new file mode 100644 index 0000000..4968efa --- /dev/null +++ b/d810/optimizers/flow/jumps/opaque.py @@ -0,0 +1,184 @@ +from ida_hexrays import * + +from d810.ast import AstLeaf, AstConstant, AstNode +from d810.optimizers.flow.jumps.handler import JumpOptimizationRule + + +class JnzRule1(JumpOptimizationRule): + ORIGINAL_JUMP_OPCODES = [m_jnz, m_jz] + LEFT_PATTERN = AstNode(m_neg, + AstNode(m_and, + AstNode(m_bnot, + AstLeaf("x_0")), + AstConstant("1", 1))) + RIGHT_PATTERN = AstLeaf("x_0") + REPLACEMENT_OPCODE = m_goto + + def check_candidate(self, opcode, left_candidate, right_candidate): + if opcode == m_jnz: + self.jump_replacement_block_serial = self.jump_original_block_serial + else: + self.jump_replacement_block_serial = self.direct_block_serial + return True + + +class JnzRule2(JumpOptimizationRule): + ORIGINAL_JUMP_OPCODES = [m_jnz, m_jz] + LEFT_PATTERN = AstNode(m_or, + AstNode(m_bnot, + AstLeaf("x_0")), + AstConstant("1", 1)) + RIGHT_PATTERN = AstConstant("0", 0) + REPLACEMENT_OPCODE = m_goto + + def check_candidate(self, opcode, left_candidate, right_candidate): + if opcode == m_jnz: + self.jump_replacement_block_serial = self.jump_original_block_serial + else: + self.jump_replacement_block_serial = self.direct_block_serial + return True + + +class JnzRule3(JumpOptimizationRule): + ORIGINAL_JUMP_OPCODES = [m_jnz, m_jz] + LEFT_PATTERN = AstNode(m_xor, + AstNode(m_xor, + AstLeaf("x_0"), + AstConstant("c_1")), + AstNode(m_and, + AstLeaf("x_0"), + AstConstant("c_2"))) + RIGHT_PATTERN = AstConstant("0", 0) + REPLACEMENT_OPCODE = m_goto + + def check_candidate(self, opcode, left_candidate, right_candidate): + tmp = left_candidate["c_1"].value & left_candidate["c_2"].value + if tmp == 0: + return False + if opcode == m_jnz: + self.jump_replacement_block_serial = self.jump_original_block_serial + else: + self.jump_replacement_block_serial = self.direct_block_serial + return True + + +class JnzRule4(JumpOptimizationRule): + ORIGINAL_JUMP_OPCODES = [m_jnz, m_jz] + LEFT_PATTERN = AstNode(m_sub, + AstConstant("3", 3), + AstLeaf("x_0")) + RIGHT_PATTERN = AstLeaf("x_0") + REPLACEMENT_OPCODE = m_goto + + def check_candidate(self, opcode, left_candidate, right_candidate): + if opcode == m_jnz: + self.jump_replacement_block_serial = self.jump_original_block_serial + else: + self.jump_replacement_block_serial = self.direct_block_serial + return True + + +class JnzRule5(JumpOptimizationRule): + ORIGINAL_JUMP_OPCODES = [m_jnz, m_jz] + LEFT_PATTERN = AstNode(m_xor, + AstNode(m_sub, + AstConstant("3", 3), + AstLeaf("x_0")), + AstLeaf("x_0")) + RIGHT_PATTERN = AstConstant("0", 0) + REPLACEMENT_OPCODE = m_goto + + def check_candidate(self, opcode, left_candidate, right_candidate): + if opcode == m_jnz: + self.jump_replacement_block_serial = self.jump_original_block_serial + else: + self.jump_replacement_block_serial = self.direct_block_serial + return True + + +class JnzRule6(JumpOptimizationRule): + ORIGINAL_JUMP_OPCODES = [m_jnz, m_jz] + LEFT_PATTERN = AstNode(m_xor, + AstNode(m_bnot, + AstNode(m_sub, + AstConstant("3", 3), + AstLeaf("x_0"))), + AstNode(m_bnot, + AstLeaf("x_0"))) + RIGHT_PATTERN = AstConstant("0", 0) + REPLACEMENT_OPCODE = m_goto + + def check_candidate(self, opcode, left_candidate, right_candidate): + if opcode == m_jnz: + self.jump_replacement_block_serial = self.jump_original_block_serial + else: + self.jump_replacement_block_serial = self.direct_block_serial + return True + + +class JnzRule7(JumpOptimizationRule): + ORIGINAL_JUMP_OPCODES = [m_jnz, m_jz] + LEFT_PATTERN = AstNode(m_and, + AstLeaf("x_0"), + AstConstant("c_1")) + RIGHT_PATTERN = AstConstant("c_2") + REPLACEMENT_OPCODE = m_goto + + def check_candidate(self, opcode, left_candidate, right_candidate): + tmp = left_candidate["c_1"].value & right_candidate["c_2"].value + if tmp == right_candidate["c_2"].value: + return False + if opcode == m_jnz: + self.jump_replacement_block_serial = self.jump_original_block_serial + else: + self.jump_replacement_block_serial = self.direct_block_serial + return True + + +class JnzRule8(JumpOptimizationRule): + ORIGINAL_JUMP_OPCODES = [m_jnz, m_jz] + PATTERN = AstNode(m_or, + AstLeaf("x_0"), + AstConstant("c_1")) + RIGHT_PATTERN = AstConstant("c_2") + REPLACEMENT_OPCODE = m_goto + + def check_candidate(self, opcode, left_candidate, right_candidate): + tmp = left_candidate["c_1"].value & right_candidate["c_2"].value + if tmp == left_candidate["c_1"].value: + return False + + if opcode == m_jnz: + self.jump_replacement_block_serial = self.jump_original_block_serial + else: + self.jump_replacement_block_serial = self.direct_block_serial + return True + + +class JbRule1(JumpOptimizationRule): + ORIGINAL_JUMP_OPCODES = [m_jb] + PATTERN = AstNode(m_xdu, + AstNode(m_and, + AstLeaf("x_0"), + AstConstant("1", 1))) + RIGHT_PATTERN = AstConstant("2", 2) + REPLACEMENT_OPCODE = m_goto + + def check_candidate(self, opcode, left_candidate, right_candidate): + self.jump_replacement_block_serial = self.jump_original_block_serial + return True + + +class JaeRule1(JumpOptimizationRule): + ORIGINAL_JUMP_OPCODES = [m_jae] + PATTERN = AstNode(m_and, + AstLeaf("x_0"), + AstConstant("c_1")) + RIGHT_PATTERN = AstConstant("c_2") + REPLACEMENT_OPCODE = m_goto + + def check_candidate(self, opcode, left_candidate, right_candidate): + if left_candidate["c_1"].value >= right_candidate["c_2"].value: + return False + self.jump_replacement_block_serial = self.direct_block_serial + return True diff --git a/d810/optimizers/flow/jumps/tricks.py b/d810/optimizers/flow/jumps/tricks.py new file mode 100644 index 0000000..94de757 --- /dev/null +++ b/d810/optimizers/flow/jumps/tricks.py @@ -0,0 +1,98 @@ +from ida_hexrays import * + +from d810.ast import AstLeaf, AstConstant, AstNode +from d810.hexrays_helpers import equal_mops_bypass_xdu, equal_bnot_mop +from d810.optimizers.flow.jumps.handler import JumpOptimizationRule + + +class CompareConstantRule1(JumpOptimizationRule): + ORIGINAL_JUMP_OPCODES = [m_jge] + LEFT_PATTERN = AstNode(m_and, + AstNode(m_or, AstLeaf("xdu_x_0"), AstConstant("c_2")), + AstNode(m_or, + AstNode(m_xor, AstLeaf("x_0"), AstConstant("c_1")), + AstNode(m_bnot, AstNode(m_sub, AstLeaf("x_0"), AstConstant("c_1"))))) + RIGHT_PATTERN = AstConstant("0", 0) + + REPLACEMENT_OPCODE = m_jl + REPLACEMENT_LEFT_PATTERN = AstLeaf("x_0") + REPLACEMENT_RIGHT_PATTERN = AstLeaf("c_1") + + def check_candidate(self, opcode, left_candidate, right_candidate): + if not equal_mops_bypass_xdu(left_candidate["xdu_x_0"].mop, left_candidate["x_0"].mop): + return False + if not equal_bnot_mop(left_candidate["c_2"].mop, left_candidate["c_1"].mop): + return False + self.jump_replacement_block_serial = self.jump_original_block_serial + return True + + +class CompareConstantRule2(JumpOptimizationRule): + ORIGINAL_JUMP_OPCODES = [m_jge] + LEFT_PATTERN = AstNode(m_or, + AstNode(m_xdu, + AstNode(m_and, + AstNode(m_bnot, AstLeaf("x_0")), AstConstant("c_1"))), + AstNode(m_and, + AstNode(m_sub, AstLeaf('xdu_x_0'), AstConstant('xdu_c_1')), + AstNode(m_bnot, AstNode(m_xdu, AstNode(m_xor, AstLeaf('xdu1_x_0'), AstConstant('xdu_c_1')))))) + RIGHT_PATTERN = AstConstant("0", 0) + + REPLACEMENT_OPCODE = m_jge + REPLACEMENT_LEFT_PATTERN = AstLeaf("x_0") + REPLACEMENT_RIGHT_PATTERN = AstLeaf("c_1") + + def check_candidate(self, opcode, left_candidate, right_candidate): + if not equal_mops_bypass_xdu(left_candidate["xdu_x_0"].mop, left_candidate["x_0"].mop): + return False + if not equal_mops_bypass_xdu(left_candidate["xdu1_x_0"].mop, left_candidate["x_0"].mop): + return False + self.jump_replacement_block_serial = self.jump_original_block_serial + return True + + +class CompareConstantRule3(JumpOptimizationRule): + ORIGINAL_JUMP_OPCODES = [m_jge] + LEFT_PATTERN = AstNode(m_and, + AstNode(m_sub, AstLeaf('x_0'), AstConstant('c_1')), + AstNode(m_bnot, AstLeaf("x_0"))) + RIGHT_PATTERN = AstConstant("0", 0) + + REPLACEMENT_OPCODE = m_jg + REPLACEMENT_LEFT_PATTERN = AstLeaf("x_0") + REPLACEMENT_RIGHT_PATTERN = AstLeaf("c_1") + + def check_candidate(self, opcode, left_candidate, right_candidate): + self.jump_replacement_block_serial = self.jump_original_block_serial + return True + + +class CompareConstantRule4(JumpOptimizationRule): + ORIGINAL_JUMP_OPCODES = [m_jl, m_jge] + LEFT_PATTERN = AstNode(m_and, + AstNode(m_or, + AstNode(m_bnot, + AstNode(m_sub, + AstLeaf('x_0'), + AstConstant('c_1'))), + AstNode(m_xor, + AstLeaf('x_0'), + AstConstant('c_1'))), + AstNode(m_or, + AstLeaf("xdu_x_0"), + AstConstant('bnot_c_1'))) + + RIGHT_PATTERN = AstConstant("0", 0) + + REPLACEMENT_OPCODE = m_jge + REPLACEMENT_LEFT_PATTERN = AstLeaf("x_0") + REPLACEMENT_RIGHT_PATTERN = AstLeaf("c_1") + + def check_candidate(self, opcode, left_candidate, right_candidate): + print("dflighdrth") + if not equal_mops_bypass_xdu(left_candidate["xdu_x_0"].mop, left_candidate["x_0"].mop): + return False + if not equal_bnot_mop(left_candidate["c_1"].mop, left_candidate["bnot_c_1"].mop): + return False + self.jump_replacement_block_serial = self.jump_original_block_serial + return True diff --git a/d810/optimizers/handler.py b/d810/optimizers/handler.py new file mode 100644 index 0000000..e24d9f1 --- /dev/null +++ b/d810/optimizers/handler.py @@ -0,0 +1,36 @@ +from ida_hexrays import * + +from d810.hexrays_formatters import string_to_maturity + +DEFAULT_INSTRUCTION_MATURITIES = [MMAT_LOCOPT, MMAT_CALLS, MMAT_GLBOPT1] +DEFAULT_FLOW_MATURITIES = [MMAT_CALLS, MMAT_GLBOPT1] + + +class OptimizationRule(object): + NAME = None + DESCRIPTION = None + + def __init__(self): + self.maturities = [] + self.config = {} + self.log_dir = None + + def set_log_dir(self, log_dir): + self.log_dir = log_dir + + def configure(self, kwargs): + self.config = kwargs if kwargs is not None else {} + if "maturities" in self.config.keys(): + self.maturities = [string_to_maturity(x) for x in self.config["maturities"]] + + @property + def name(self): + if self.NAME is not None: + return self.NAME + return self.__class__.__name__ + + @property + def description(self): + if self.DESCRIPTION is not None: + return self.DESCRIPTION + return "No description available" diff --git a/d810/optimizers/instructions/__init__.py b/d810/optimizers/instructions/__init__.py new file mode 100644 index 0000000..327f43a --- /dev/null +++ b/d810/optimizers/instructions/__init__.py @@ -0,0 +1,7 @@ +from d810.optimizers.instructions.chain import CHAIN_RULES, ChainOptimizer +from d810.optimizers.instructions.pattern_matching import PATTERN_MATCHING_RULES, PatternOptimizer +from d810.optimizers.instructions.z3 import Z3_RULES, Z3Optimizer +from d810.optimizers.instructions.analysis import INSTRUCTION_ANALYSIS_RULES, InstructionAnalyzer +from d810.optimizers.instructions.early import EARLY_RULES, EarlyOptimizer + +KNOWN_INS_RULES = PATTERN_MATCHING_RULES + CHAIN_RULES + Z3_RULES + EARLY_RULES + INSTRUCTION_ANALYSIS_RULES diff --git a/d810/optimizers/instructions/analysis/__init__.py b/d810/optimizers/instructions/analysis/__init__.py new file mode 100644 index 0000000..bca3c6b --- /dev/null +++ b/d810/optimizers/instructions/analysis/__init__.py @@ -0,0 +1,5 @@ +from d810.utils import get_all_subclasses +from d810.optimizers.instructions.analysis.handler import InstructionAnalyzer, InstructionAnalysisRule +from d810.optimizers.instructions.analysis.pattern_guess import * + +INSTRUCTION_ANALYSIS_RULES = CHAIN_RULES = [x() for x in get_all_subclasses(InstructionAnalysisRule)] diff --git a/d810/optimizers/instructions/analysis/handler.py b/d810/optimizers/instructions/analysis/handler.py new file mode 100644 index 0000000..01d7bff --- /dev/null +++ b/d810/optimizers/instructions/analysis/handler.py @@ -0,0 +1,42 @@ +import logging +from ida_hexrays import * +from d810.hexrays_formatters import format_minsn_t +from d810.optimizers.instructions.handler import InstructionOptimizer, InstructionOptimizationRule + + +optimizer_logger = logging.getLogger('D810.optimizer') + + +class InstructionAnalysisRule(InstructionOptimizationRule): + def analyze_instruction(self, blk, ins): + raise NotImplementedError + + +class InstructionAnalyzer(InstructionOptimizer): + RULE_CLASSES = [InstructionAnalysisRule] + + def set_maturity(self, maturity: int): + self.cur_maturity = maturity + for rule in self.rules: + rule.set_maturity(self.cur_maturity) + + def analyze(self, blk: mblock_t, ins: minsn_t): + if blk is not None: + self.cur_maturity = blk.mba.maturity + + if self.cur_maturity not in self.maturities: + return None + + for rule in self.rules: + try: + rule.analyze_instruction(blk, ins) + except RuntimeError: + optimizer_logger.error("error during rule {0} for instruction {1}".format(rule, format_minsn_t(ins))) + return None + + + @property + def name(self): + if self.NAME is not None: + return self.NAME + return self.__class__.__name__ diff --git a/d810/optimizers/instructions/analysis/pattern_guess.py b/d810/optimizers/instructions/analysis/pattern_guess.py new file mode 100644 index 0000000..5d879c8 --- /dev/null +++ b/d810/optimizers/instructions/analysis/pattern_guess.py @@ -0,0 +1,89 @@ +import os + +from d810.ast import minsn_to_ast +from d810.hexrays_formatters import format_minsn_t, format_mop_t, maturity_to_string + +from d810.optimizers.instructions.analysis.handler import InstructionAnalysisRule +from d810.optimizers.instructions.analysis.utils import get_possible_patterns + + +class ExampleGuessingRule(InstructionAnalysisRule): + DESCRIPTION = "Detect pattern with variable used multiple times and with multiple different opcodes" + + def __init__(self): + super().__init__() + self.cur_maturity = None + self.min_nb_var = 1 + self.max_nb_var = 3 + self.min_nb_diff_opcodes = 3 + self.max_nb_diff_opcodes = -1 + + self.cur_index = 0 + self.max_index = 1000 + self.cur_ins_guessed = [""] * self.max_index + self.pattern_filename_path = None + + def log_info(self, message): + with open(self.pattern_filename_path, "a") as f: + f.write('{0}\n'.format(message)) + + def set_maturity(self, maturity): + self.log_info("Patterns guessed at maturity {0}".format(maturity_to_string(maturity))) + self.cur_maturity = maturity + + def set_log_dir(self, log_dir): + super().set_log_dir(log_dir) + self.pattern_filename_path = os.path.join(self.log_dir, "pattern_guess.log") + f = open(self.pattern_filename_path, "w") + f.close() + + def configure(self, kwargs): + super().configure(kwargs) + if "min_nb_var" in kwargs.keys(): + self.min_nb_var = kwargs["min_nb_var"] + if "max_nb_var" in kwargs.keys(): + self.max_nb_var = kwargs["max_nb_var"] + if "min_nb_diff_opcodes" in kwargs.keys(): + self.min_nb_diff_opcodes = kwargs["min_nb_diff_opcodes"] + if "max_nb_diff_opcodes" in kwargs.keys(): + self.max_nb_diff_opcodes = kwargs["max_nb_diff_opcodes"] + + if self.max_nb_var == -1: + self.max_nb_var = 0xff + if self.max_nb_diff_opcodes == -1: + self.max_nb_diff_opcodes = 0xff + + def analyze_instruction(self, blk, ins): + if self.cur_maturity not in self.maturities: + return None + formatted_ins = str(format_minsn_t(ins)) + if formatted_ins in self.cur_ins_guessed: + return False + tmp = minsn_to_ast(ins) + if tmp is None: + return False + is_good_candidate = self.check_if_possible_pattern(tmp) + if is_good_candidate: + self.cur_ins_guessed[self.cur_index] = formatted_ins + self.cur_index = (self.cur_index + 1) % self.max_index + return is_good_candidate + + def check_if_possible_pattern(self, test_ast): + patterns = get_possible_patterns(test_ast, min_nb_use=2, ref_ast_info_by_index=None, max_nb_pattern=64) + for pattern in patterns: + leaf_info_list, cst_leaf_values, opcodes = pattern.get_information() + leaf_nb_use = [leaf_info.number_of_use for leaf_info in leaf_info_list] + if not(self.min_nb_var <= len(leaf_info_list) <= self.max_nb_var): + continue + if not(self.min_nb_diff_opcodes <= len(set(opcodes)) <= self.max_nb_diff_opcodes): + continue + if not(min(leaf_nb_use) >= 2): + continue + ins = pattern.mop.d + self.log_info("IR: 0x{0:x} - {1}".format(ins.ea, format_minsn_t(ins))) + for leaf_info in leaf_info_list: + self.log_info(" {0} -> {1}".format(leaf_info.ast, format_mop_t(leaf_info.ast.mop))) + self.log_info("Pattern: {0}".format(pattern)) + self.log_info("AstNode: {0}\n".format(pattern.get_pattern())) + return True + return False diff --git a/d810/optimizers/instructions/analysis/utils.py b/d810/optimizers/instructions/analysis/utils.py new file mode 100644 index 0000000..32a537d --- /dev/null +++ b/d810/optimizers/instructions/analysis/utils.py @@ -0,0 +1,39 @@ +from d810.ast import AstNode, AstLeaf + + +def get_possible_patterns(ast, min_nb_use=2, ref_ast_info_by_index=None, max_nb_pattern=64): + # max_nb_pattern is used to prevent memory explosion when very large patterns are parsed + if ast.is_leaf(): + return [ast] + if ref_ast_info_by_index is None: + if ast.ast_index not in ast.sub_ast_info_by_index.keys(): + ast.compute_sub_ast() + ref_ast_info_by_index = ast.sub_ast_info_by_index + possible_patterns = [] + if ref_ast_info_by_index[ast.ast_index].number_of_use >= min_nb_use: + node_as_leaf = AstLeaf("x_{0}".format(ast.ast_index)) + node_as_leaf.mop = ast.mop + node_as_leaf.ast_index = ast.ast_index + possible_patterns.append(node_as_leaf) + left_patterns = [] + right_patterns = [] + if ast.left is not None: + left_patterns = get_possible_patterns(ast.left, min_nb_use, ref_ast_info_by_index, max_nb_pattern) + if ast.right is not None: + right_patterns = get_possible_patterns(ast.right, min_nb_use, ref_ast_info_by_index, max_nb_pattern) + + for left_pattern in left_patterns: + if ast.right is not None: + for right_pattern in right_patterns: + node = AstNode(ast.opcode, left_pattern, right_pattern) + node.mop = ast.mop + node.ast_index = ast.ast_index + if len(possible_patterns) < max_nb_pattern: + possible_patterns.append(node) + else: + node = AstNode(ast.opcode, left_pattern) + node.mop = ast.mop + node.ast_index = ast.ast_index + if len(possible_patterns) < max_nb_pattern: + possible_patterns.append(node) + return possible_patterns diff --git a/d810/optimizers/instructions/chain/__init__.py b/d810/optimizers/instructions/chain/__init__.py new file mode 100644 index 0000000..f99718b --- /dev/null +++ b/d810/optimizers/instructions/chain/__init__.py @@ -0,0 +1,5 @@ +from d810.utils import get_all_subclasses +from d810.optimizers.instructions.chain.handler import ChainSimplificationRule, ChainOptimizer +from d810.optimizers.instructions.chain.chain_rules import * + +CHAIN_RULES = [x() for x in get_all_subclasses(ChainSimplificationRule)] diff --git a/d810/optimizers/instructions/chain/chain_rules.py b/d810/optimizers/instructions/chain/chain_rules.py new file mode 100644 index 0000000..8beb156 --- /dev/null +++ b/d810/optimizers/instructions/chain/chain_rules.py @@ -0,0 +1,375 @@ +import logging +from functools import reduce +from ida_hexrays import * + + +from d810.optimizers.instructions.chain.handler import ChainSimplificationRule +from d810.hexrays_helpers import equal_bnot_mop, equal_mops_ignore_size, \ + SUB_TABLE, AND_TABLE +from d810.hexrays_formatters import format_minsn_t + +rules_chain_logger = logging.getLogger('D810.rules.chain') + + +class ChainSimplification(object): + def __init__(self, opcode): + self.opcode = opcode + self.formatted_ins = "" + self.non_cst_mop_list = [] + self.cst_mop_list = [] + self._is_instruction_simplified = False + + def add_mop(self, mop): + if (mop.t == mop_d) and (mop.d.opcode == self.opcode): + self.add_mop(mop.d.l) + self.add_mop(mop.d.r) + else: + if mop.t == mop_n: + self.cst_mop_list.append(mop) + else: + self.non_cst_mop_list.append(mop) + + def do_simplification(self): + final_mop_list = self.get_simplified_non_constant() + final_mop_list += self.get_simplified_constant() + return final_mop_list + + def get_simplified_constant(self): + if len(self.cst_mop_list) == 0: + return [] + elif len(self.cst_mop_list) == 1: + return self.cst_mop_list + else: + cst_size_list = [c.size for c in self.cst_mop_list] + cst_value_list = [c.nnn.value for c in self.cst_mop_list] + final_cst_size = max(cst_size_list) + rules_chain_logger.debug("Doing cst simplification: {0}".format(cst_value_list)) + self._is_instruction_simplified = True + if self.opcode == m_xor: + final_cst = reduce(lambda x, y: x ^ y, cst_value_list) + elif self.opcode == m_and: + final_cst = reduce(lambda x, y: x & y, cst_value_list) + elif self.opcode == m_or: + final_cst = reduce(lambda x, y: x | y, cst_value_list) + elif self.opcode == m_add: + final_cst = reduce(lambda x, y: x + y, cst_value_list) + else: + raise NotImplementedError("Euh") + final_cst = final_cst & AND_TABLE[final_cst_size] + rules_chain_logger.debug("Final cst: {0}".format(final_cst)) + final_cst_mop = mop_t() + final_cst_mop.make_number(final_cst, max(cst_size_list)) + return [final_cst_mop] + + def get_simplified_non_constant(self): + if len(self.non_cst_mop_list) == 0: + return [] + elif len(self.non_cst_mop_list) == 1: + return self.non_cst_mop_list + else: + is_always_0 = False + index_removed = [] + for i in range(len(self.non_cst_mop_list)): + for j in range(i + 1, len(self.non_cst_mop_list)): + if (i not in index_removed) and (j not in index_removed): + if equal_mops_ignore_size(self.non_cst_mop_list[i], self.non_cst_mop_list[j]): + if self.opcode == m_xor: + # x ^ x == 0 + rules_chain_logger.debug("Doing non cst simplification (xor): {0}, {1} in {2}" + .format(i, j, self.formatted_ins)) + index_removed += [i, j] + elif self.opcode == m_and: + # x & x == x + rules_chain_logger.debug("Doing non cst simplification (and): {0}, {1} in {2}" + .format(i, j, self.formatted_ins)) + index_removed += [j] + elif self.opcode == m_or: + # x | x == x + rules_chain_logger.debug("Doing non cst simplification (or): {0}, {1} in {2}" + .format(i, j, self.formatted_ins)) + index_removed += [j] + elif equal_bnot_mop(self.non_cst_mop_list[i], self.non_cst_mop_list[j]): + if self.opcode == m_and: + is_always_0 = True + + if len(index_removed) == 0 and not is_always_0: + return self.non_cst_mop_list + final_mop_list = [] + self._is_instruction_simplified = True + if is_always_0: + final_mop_list.append(self.create_cst_mop(0, self.res_mop_size)) + return final_mop_list + for i in range(len(self.non_cst_mop_list)): + if i not in index_removed: + final_mop_list.append(self.non_cst_mop_list[i]) + return final_mop_list + + def simplify(self, ins): + self.res_mop_size = ins.d.size + if ins.opcode != self.opcode: + return None + + self.formatted_ins = format_minsn_t(ins) + self.non_cst_mop_list = [] + self.cst_mop_list = [] + self.add_mop(ins.l) + self.add_mop(ins.r) + + self._is_instruction_simplified = False + final_mop_list = self.do_simplification() + if not self._is_instruction_simplified: + return None + + return self.create_new_chain(ins, final_mop_list) + + def create_new_chain(self, original_ins, mop_list): + new_ins = minsn_t(original_ins.ea) + new_ins.opcode = self.opcode + if len(mop_list) == 0: + mop_list.append(self.create_cst_mop(0, original_ins.d.size)) + if len(mop_list) == 1: + mop_list.append(self.create_cst_mop(0, original_ins.d.size)) + new_ins.l = self._create_mop_chain(original_ins, mop_list[:-1], original_ins.d.size) + new_ins.r = mop_list[-1] + if new_ins.r.t == mop_n: + new_ins.r.size = original_ins.d.size + new_ins.d = original_ins.d + return new_ins + + def create_cst_mop(self, value, size): + cst_mop = mop_t() + cst_mop.make_number(value, size) + return cst_mop + + def _create_mop_chain(self, ea, mop_list, size): + if len(mop_list) == 1: + return mop_list[0] + new_ins = minsn_t(ea) + new_ins.opcode = self.opcode + new_ins.l = self._create_mop_chain(ea, mop_list[:-1], size) + new_ins.r = mop_list[-1] + new_ins.d = mop_t() + new_ins.d.size = size + mop = mop_t() + mop.create_from_insn(new_ins) + return mop + + +class ArithmeticChainSimplification(object): + def __init__(self): + self.formatted_ins = "" + self.add_non_cst_mop_list = [] + self.add_cst_mop_list = [] + self.sub_non_cst_mop_list = [] + self.sub_cst_mop_list = [] + self.add_other_cst_list = [] + self.sub_other_cst_list = [] + self._is_instruction_simplified = False + + def add_mop(self, sign, mop): + # sign is 0 if +, 1 is minus => minus minus = 1 ^ 1 = 0 so add + if (mop.t == mop_d) and (mop.d.opcode in [m_add, m_sub]): + + self.add_mop(sign, mop.d.l) + if mop.d.opcode == m_add: + self.add_mop(sign, mop.d.r) + else: + self.add_mop(sign ^ 1, mop.d.r) + elif (mop.t == mop_d) and (mop.d.opcode == m_neg): + self.add_mop(sign ^ 1, mop.d.l) + else: + if mop.t == mop_n: + if sign == 0: + self.add_cst_mop_list.append(mop) + else: + self.sub_cst_mop_list.append(mop) + else: + if sign == 0: + self.add_non_cst_mop_list.append(mop) + else: + self.sub_non_cst_mop_list.append(mop) + + def do_simplification(self): + final_add_cst_list, final_sub_cst_list = self.get_simplified_constant() + final_add_list, final_sub_list, final_add_cst_mop = self.get_simplified_non_constant() + if final_add_cst_mop.nnn.value != 0: + final_add_cst_list.append(final_add_cst_mop) + return final_add_list, final_sub_list, final_add_cst_list, final_sub_cst_list + + def get_simplified_constant(self): + if len(self.add_cst_mop_list) == 0 and len(self.sub_cst_mop_list) == 0: + return [[], []] + if len(self.add_cst_mop_list) == 1 and len(self.sub_cst_mop_list) == 0: + return self.add_cst_mop_list, [] + if len(self.add_cst_mop_list) == 0 and len(self.sub_cst_mop_list) == 1: + return [], self.sub_cst_mop_list + add_cst_size_list = [c.size for c in self.add_cst_mop_list] + add_cst_value_list = [c.nnn.value for c in self.add_cst_mop_list] + sub_cst_size_list = [c.size for c in self.sub_cst_mop_list] + sub_cst_value_list = [SUB_TABLE[c.size] - c.nnn.value for c in self.sub_cst_mop_list] + self._is_instruction_simplified = True + + final_cst_size = max(add_cst_size_list + sub_cst_size_list) + rules_chain_logger.debug("Doing arithmetic cst simplification: {0} {1}" + .format(add_cst_value_list, sub_cst_value_list)) + final_cst = reduce(lambda x, y: x + y, add_cst_value_list + sub_cst_value_list) + final_cst = final_cst & AND_TABLE[final_cst_size] + rules_chain_logger.debug("Final cst: {0}".format(final_cst)) + final_cst_mop = mop_t() + final_cst_mop.make_number(final_cst, final_cst_size) + return [final_cst_mop], [] + + def get_simplified_non_constant(self): + if len(self.add_non_cst_mop_list) == 0 and len(self.sub_non_cst_mop_list) == 0: + return [[], []] + final_add_list = self.add_non_cst_mop_list + final_sub_list = self.sub_non_cst_mop_list + index_add_removed = [] + index_sub_removed = [] + for (i, add_mop) in enumerate(self.add_non_cst_mop_list): + for (j, sub_mop) in enumerate(self.sub_non_cst_mop_list): + if (i not in index_add_removed) and (j not in index_sub_removed): + if equal_mops_ignore_size(add_mop, sub_mop): + index_add_removed.append(i) + index_sub_removed.append(j) + + if len(index_add_removed) > 0: + self._is_instruction_simplified = True + final_add_list = [] + for i in range(len(self.add_non_cst_mop_list)): + if i not in index_add_removed: + final_add_list.append(self.add_non_cst_mop_list[i]) + final_sub_list = [] + for i in range(len(self.sub_non_cst_mop_list)): + if i not in index_sub_removed: + final_sub_list.append(self.sub_non_cst_mop_list[i]) + + final_add_list, final_sub_list, final_add_cst_mop = self.check_bnot_mop(final_add_list, final_sub_list) + return final_add_list, final_sub_list, final_add_cst_mop + + def check_bnot_mop(self, add_non_cst_mop_list, sub_non_cst_mop_list): + add_index_removed = [] + sub_index_removed = [] + cst_value = 0 + final_add_non_cst_mop_list = add_non_cst_mop_list + final_sub_non_cst_mop_list = sub_non_cst_mop_list + add_size_list = [c.size for c in add_non_cst_mop_list] + sub_size_list = [c.size for c in sub_non_cst_mop_list] + final_cst_size = max(add_size_list + sub_size_list) + + for i in range(len(add_non_cst_mop_list)): + for j in range(i + 1, len(add_non_cst_mop_list)): + if (i not in add_index_removed) and (j not in add_index_removed): + if equal_bnot_mop(add_non_cst_mop_list[i], add_non_cst_mop_list[j]): + cst_value += AND_TABLE[add_non_cst_mop_list[i].size] + add_index_removed += [i, j] + + for i in range(len(sub_non_cst_mop_list)): + for j in range(i + 1, len(sub_non_cst_mop_list)): + if (i not in sub_index_removed) and (j not in sub_index_removed): + if equal_bnot_mop(sub_non_cst_mop_list[i], sub_non_cst_mop_list[j]): + cst_value += 1 + sub_index_removed += [i, j] + + final_add_cst_mop = mop_t() + final_add_cst_mop.make_number(cst_value & AND_TABLE[final_cst_size], final_cst_size) + + if len(add_index_removed) > 0: + final_add_non_cst_mop_list = [] + self._is_instruction_simplified = True + for i in range(len(add_non_cst_mop_list)): + if i not in add_index_removed: + final_add_non_cst_mop_list.append(add_non_cst_mop_list[i]) + if len(sub_index_removed) > 0: + final_sub_non_cst_mop_list = [] + self._is_instruction_simplified = True + for i in range(len(sub_non_cst_mop_list)): + if i not in sub_index_removed: + final_sub_non_cst_mop_list.append(sub_non_cst_mop_list[i]) + return final_add_non_cst_mop_list, final_sub_non_cst_mop_list, final_add_cst_mop + + def simplify(self, ins): + if ins.opcode not in [m_add, m_sub]: + return None + self.formatted_ins = format_minsn_t(ins) + self.add_non_cst_mop_list = [] + self.add_cst_mop_list = [] + self.sub_non_cst_mop_list = [] + self.sub_cst_mop_list = [] + self.add_mop(0, ins.l) + if ins.opcode == m_add: + self.add_mop(0, ins.r) + else: + self.add_mop(1, ins.r) + + self._is_instruction_simplified = False + final_add_list, final_sub_list, final_add_cst_list, final_sub_cst_list = self.do_simplification() + if not self._is_instruction_simplified: + return None + + simplified_ins = self.create_new_chain(ins, final_add_list, final_sub_list, final_add_cst_list, final_sub_cst_list) + + return simplified_ins + + def create_new_chain(self, original_ins, final_add_list, final_sub_list, final_add_cst_list, final_sub_cst_list): + mod_add = self._create_mop_add_chain(original_ins.ea, final_add_list + final_add_cst_list, original_ins.d.size) + mod_sub = self._create_mop_add_chain(original_ins.ea, final_sub_list + final_sub_cst_list, original_ins.d.size) + new_ins = minsn_t(original_ins.ea) + new_ins.opcode = m_sub + new_ins.l = mod_add + new_ins.r = mod_sub + new_ins.d = original_ins.d + return new_ins + + def _create_mop_add_chain(self, ea, mop_list, size): + if len(mop_list) == 0: + res = mop_t() + res.make_number(0, size) + return res + elif len(mop_list) == 1: + return mop_list[0] + new_ins = minsn_t(ea) + new_ins.opcode = m_add + new_ins.l = self._create_mop_add_chain(ea, mop_list[:-1], size) + new_ins.r = mop_list[-1] + new_ins.d = mop_t() + new_ins.d.size = size + mop = mop_t() + mop.create_from_insn(new_ins) + return mop + + +class XorChain(ChainSimplificationRule): + DESCRIPTION = "Remove XOR chains with common terms. E.g. x ^ 4 ^ y ^ 6 ^ 5 ^ x ==> y ^ 7" + + def check_and_replace(self, blk, ins): + xor_simplifier = ChainSimplification(m_xor) + new_ins = xor_simplifier.simplify(ins) + return new_ins + + +class AndChain(ChainSimplificationRule): + DESCRIPTION = "Remove AND chains with common terms. E.g. x & 4 & y & 6 & 5 & x ==> x & y & 4" + + def check_and_replace(self, blk, ins): + and_simplifier = ChainSimplification(m_and) + new_ins = and_simplifier.simplify(ins) + return new_ins + + +class OrChain(ChainSimplificationRule): + DESCRIPTION = "Remove OR chains with common terms. E.g. x | 4 | y | 6 | 5 | x ==> x | y | 7" + + def check_and_replace(self, blk, ins): + or_simplifier = ChainSimplification(m_or) + new_ins = or_simplifier.simplify(ins) + return new_ins + + +class ArithmeticChain(ChainSimplificationRule): + DESCRIPTION = "Remove arithmetic chains with common terms. E.g. x + 4 + y - (6 + x - 5) ==> y + 3" + + def check_and_replace(self, blk, ins): + arithmetic_simplifier = ArithmeticChainSimplification() + new_ins = arithmetic_simplifier.simplify(ins) + return new_ins diff --git a/d810/optimizers/instructions/chain/handler.py b/d810/optimizers/instructions/chain/handler.py new file mode 100644 index 0000000..fce5a3d --- /dev/null +++ b/d810/optimizers/instructions/chain/handler.py @@ -0,0 +1,9 @@ +from d810.optimizers.instructions.handler import InstructionOptimizationRule, InstructionOptimizer + + +class ChainSimplificationRule(InstructionOptimizationRule): + pass + + +class ChainOptimizer(InstructionOptimizer): + RULE_CLASSES = [ChainSimplificationRule] diff --git a/d810/optimizers/instructions/early/__init__.py b/d810/optimizers/instructions/early/__init__.py new file mode 100644 index 0000000..8405af0 --- /dev/null +++ b/d810/optimizers/instructions/early/__init__.py @@ -0,0 +1,5 @@ +from d810.utils import get_all_subclasses +from d810.optimizers.instructions.early.handler import EarlyRule, EarlyOptimizer +from d810.optimizers.instructions.early.mem_read import * + +EARLY_RULES = [x() for x in get_all_subclasses(EarlyRule)] diff --git a/d810/optimizers/instructions/early/handler.py b/d810/optimizers/instructions/early/handler.py new file mode 100644 index 0000000..082d3ad --- /dev/null +++ b/d810/optimizers/instructions/early/handler.py @@ -0,0 +1,9 @@ +from d810.optimizers.instructions.handler import GenericPatternRule, InstructionOptimizer + + +class EarlyRule(GenericPatternRule): + pass + + +class EarlyOptimizer(InstructionOptimizer): + RULE_CLASSES = [EarlyRule] diff --git a/d810/optimizers/instructions/early/mem_read.py b/d810/optimizers/instructions/early/mem_read.py new file mode 100644 index 0000000..43755e5 --- /dev/null +++ b/d810/optimizers/instructions/early/mem_read.py @@ -0,0 +1,73 @@ +from ida_hexrays import * +from idaapi import SEGPERM_READ, SEGPERM_WRITE, xrefblk_t, getseg, segment_t, XREF_DATA, dr_W, is_loaded + +from d810.optimizers.instructions.early.handler import EarlyRule +from d810.ast import AstLeaf, AstConstant, AstNode + + +class SetGlobalVariablesToZero(EarlyRule): + DESCRIPTION = "This rule can be used to patch memory read" + + PATTERN = AstNode(m_mov, AstLeaf("ro_dword")) + REPLACEMENT_PATTERN = AstNode(m_mov, AstConstant("val_res")) + + def __init__(self): + super().__init__() + self.ro_dword_min_ea = None + self.ro_dword_max_ea = None + + def configure(self, kwargs): + super().configure(kwargs) + self.ro_dword_min_ea = None + self.ro_dword_max_ea = None + if "ro_dword_min_ea" in kwargs.keys(): + self.ro_dword_min_ea = int(kwargs["ro_dword_min_ea"], 16) + if "ro_dword_max_ea" in kwargs.keys(): + self.ro_dword_max_ea = int(kwargs["ro_dword_max_ea"], 16) + + def check_candidate(self, candidate): + if (self.ro_dword_min_ea is None) or (self.ro_dword_max_ea is None): + return False + if candidate["ro_dword"].mop.t != mop_v: + return False + mem_read_address = candidate["ro_dword"].mop.g + if not(self.ro_dword_min_ea <= mem_read_address <= self.ro_dword_max_ea): + return False + + candidate.add_constant_leaf("val_res", 0, candidate["ro_dword"].mop.size) + return True + + +# This rule is from +# https://www.carbonblack.com/blog/defeating-compiler-level-obfuscations-used-in-apt10-malware/ +class SetGlobalVariablesToZeroIfDetectedReadOnly(EarlyRule): + DESCRIPTION = "WARNING: Use it only if you know what you are doing as it may patch data not related to obfuscation" + + PATTERN = AstNode(m_mov, AstLeaf("ro_dword")) + REPLACEMENT_PATTERN = AstNode(m_mov, AstConstant("val_res")) + + def is_read_only_inited_var(self, address): + s: segment_t = getseg(address) + if s is None: + return False + if s.perm != (SEGPERM_READ | SEGPERM_WRITE): + return False + if is_loaded(address): + return False + ref_finder = xrefblk_t() + is_ok = ref_finder.first_to(address, XREF_DATA) + while is_ok: + if ref_finder.type == dr_W: + return False + is_ok = ref_finder.next_to() + return True + + def check_candidate(self, candidate): + if candidate["ro_dword"].mop.t != mop_v: + return False + mem_read_address = candidate["ro_dword"].mop.g + if not self.is_read_only_inited_var(mem_read_address): + return False + + candidate.add_constant_leaf("val_res", 0, candidate["ro_dword"].mop.size) + return True diff --git a/d810/optimizers/instructions/handler.py b/d810/optimizers/instructions/handler.py new file mode 100644 index 0000000..6220934 --- /dev/null +++ b/d810/optimizers/instructions/handler.py @@ -0,0 +1,138 @@ +from __future__ import annotations +import logging +from typing import List +from ida_hexrays import * + +from d810.optimizers.handler import OptimizationRule, DEFAULT_INSTRUCTION_MATURITIES +from d810.hexrays_formatters import format_minsn_t +from d810.ast import minsn_to_ast, AstNode +from d810.errors import D810Exception + + +d810_logger = logging.getLogger('D810') +optimizer_logger = logging.getLogger('D810.optimizer') + + +class InstructionOptimizationRule(OptimizationRule): + def __init__(self): + super().__init__() + self.maturities = DEFAULT_INSTRUCTION_MATURITIES + + def check_and_replace(self, blk, ins): + return None + + +class GenericPatternRule(InstructionOptimizationRule): + PATTERN = None + PATTERNS = None + REPLACEMENT_PATTERN = None + + def __init__(self): + super().__init__() + self.pattern_candidates = [self.PATTERN] + if self.PATTERNS is not None: + self.pattern_candidates += self.PATTERNS + + def check_candidate(self, candidate: AstNode): + # Perform rule specific checks + return False + + def get_valid_candidates(self, instruction: minsn_t, stop_early=True): + valid_candidates = [] + tmp = minsn_to_ast(instruction) + if tmp is None: + return [] + for candidate_pattern in self.pattern_candidates: + if not candidate_pattern.check_pattern_and_copy_mops(tmp): + continue + if not self.check_candidate(candidate_pattern): + continue + valid_candidates.append(candidate_pattern) + if stop_early: + return valid_candidates + return [] + + def get_replacement(self, candidate: AstNode): + is_ok = self.REPLACEMENT_PATTERN.update_leafs_mop(candidate) + if not is_ok: + return None + new_ins = self.REPLACEMENT_PATTERN.create_minsn(candidate.ea, candidate.dst_mop) + return new_ins + + def check_and_replace(self, blk: mblock_t, instruction: minsn_t): + valid_candidates = self.get_valid_candidates(instruction, stop_early=True) + if len(valid_candidates) == 0: + return None + new_instruction = self.get_replacement(valid_candidates[0]) + return new_instruction + + @property + def description(self): + if self.DESCRIPTION is not None: + return self.DESCRIPTION + if (self.PATTERN is None) or (self.REPLACEMENT_PATTERN is None): + return "" + self.PATTERN.reset_mops() + self.REPLACEMENT_PATTERN.reset_mops() + return "{0} => {1}".format(self.PATTERN, self.REPLACEMENT_PATTERN) + + +class InstructionOptimizer(object): + RULE_CLASSES = [] + NAME = None + + def __init__(self, maturities: List[int], log_dir=None): + self.rules = set() + self.rules_usage_info = {} + self.maturities = maturities + self.log_dir = log_dir + self.cur_maturity = MMAT_PREOPTIMIZED + + def add_rule(self, rule: InstructionOptimizationRule): + is_valid_rule_class = False + for rule_class in self.RULE_CLASSES: + if isinstance(rule, rule_class): + is_valid_rule_class = True + break + if not is_valid_rule_class: + return False + optimizer_logger.debug("Adding rule {0}".format(rule)) + self.rules.add(rule) + self.rules_usage_info[rule.name] = 0 + return True + + def reset_rule_usage_statistic(self): + self.rules_usage_info = {} + for rule in self.rules: + self.rules_usage_info[rule.name] = 0 + + def show_rule_usage_statistic(self): + for rule_name, rule_nb_match in self.rules_usage_info.items(): + if rule_nb_match > 0: + d810_logger.info("Instruction Rule '{0}' has been used {1} times".format(rule_name, rule_nb_match)) + + def get_optimized_instruction(self, blk: mblock_t, ins: minsn_t): + if blk is not None: + self.cur_maturity = blk.mba.maturity + if self.cur_maturity not in self.maturities: + return None + for rule in self.rules: + try: + new_ins = rule.check_and_replace(blk, ins) + if new_ins is not None: + self.rules_usage_info[rule.name] += 1 + optimizer_logger.info("Rule {0} matched:".format(rule.name)) + optimizer_logger.info(" orig: {0}".format(format_minsn_t(ins))) + optimizer_logger.info(" new : {0}".format(format_minsn_t(new_ins))) + return new_ins + except RuntimeError as e: + optimizer_logger.error("Runtime error during rule {0} for instruction {1}: {2}".format(rule, format_minsn_t(ins), e)) + except D810Exception as e: + optimizer_logger.error("D810Exception during rule {0} for instruction {1}: {2}".format(rule, format_minsn_t(ins), e)) + return None + + @property + def name(self): + if self.NAME is not None: + return self.NAME + return self.__class__.__name__ diff --git a/d810/optimizers/instructions/pattern_matching/__init__.py b/d810/optimizers/instructions/pattern_matching/__init__.py new file mode 100644 index 0000000..3124555 --- /dev/null +++ b/d810/optimizers/instructions/pattern_matching/__init__.py @@ -0,0 +1,18 @@ +from d810.utils import get_all_subclasses +from d810.optimizers.instructions.pattern_matching.handler import PatternMatchingRule, PatternOptimizer +from d810.optimizers.instructions.pattern_matching.rewrite_add import * +from d810.optimizers.instructions.pattern_matching.rewrite_and import * +from d810.optimizers.instructions.pattern_matching.rewrite_bnot import * +from d810.optimizers.instructions.pattern_matching.rewrite_cst import * +from d810.optimizers.instructions.pattern_matching.rewrite_mov import * +from d810.optimizers.instructions.pattern_matching.rewrite_mul import * +from d810.optimizers.instructions.pattern_matching.rewrite_neg import * +from d810.optimizers.instructions.pattern_matching.rewrite_predicates import * +from d810.optimizers.instructions.pattern_matching.rewrite_or import * +from d810.optimizers.instructions.pattern_matching.rewrite_sub import * +from d810.optimizers.instructions.pattern_matching.rewrite_xor import * +from d810.optimizers.instructions.pattern_matching.weird import * + +PATTERN_MATCHING_RULES = [x() for x in get_all_subclasses(PatternMatchingRule)] + + diff --git a/d810/optimizers/instructions/pattern_matching/handler.py b/d810/optimizers/instructions/pattern_matching/handler.py new file mode 100644 index 0000000..781f92e --- /dev/null +++ b/d810/optimizers/instructions/pattern_matching/handler.py @@ -0,0 +1,340 @@ +import logging +import itertools +from ida_hexrays import * +from typing import List, Union +from d810.optimizers.instructions.handler import GenericPatternRule, InstructionOptimizer, InstructionOptimizationRule +from d810.ast import minsn_to_ast, AstNode, AstLeaf +from d810.hexrays_formatters import format_minsn_t, format_mop_t + +optimizer_logger = logging.getLogger('D810.optimizer') +pattern_search_logger = logging.getLogger('D810.pattern_search') + + +class PatternMatchingRule(GenericPatternRule): + PATTERN = None + PATTERNS = None + FUZZ_PATTERN = True + REPLACEMENT_PATTERN = None + + def __init__(self): + super().__init__() + self.fuzz_pattern = self.FUZZ_PATTERN + + def configure(self, fuzz_pattern=None, **kwargs): + super().configure(kwargs) + if fuzz_pattern is not None: + self.fuzz_pattern = fuzz_pattern + self._generate_pattern_candidates() + pattern_search_logger.debug("Rule {0} configured with {1} patterns" + .format(self.__class__.__name__, len(self.pattern_candidates))) + + def _generate_pattern_candidates(self): + self.fuzz_pattern = self.FUZZ_PATTERN + if self.PATTERN is not None: + self.PATTERN.reset_mops() + if not self.fuzz_pattern: + if self.PATTERN is not None: + self.pattern_candidates = [self.PATTERN] + if self.PATTERNS is not None: + self.pattern_candidates += [x for x in self.PATTERNS] + else: + self.pattern_candidates = [x for x in self.PATTERNS] + else: + self.pattern_candidates = ast_generator(self.PATTERN) + + def check_candidate(self, candidate: AstNode): + return True + + def check_pattern_and_replace(self, candidate_pattern: AstNode, test_ast: AstNode): + if not candidate_pattern.check_pattern_and_copy_mops(test_ast): + return None + if not self.check_candidate(candidate_pattern): + return None + new_instruction = self.get_replacement(candidate_pattern) + return new_instruction + + +class RulePatternInfo(object): + def __init__(self, rule, pattern): + self.rule = rule + self.pattern = pattern + + +def signature_generator(ref_sig): + for i, x in enumerate(ref_sig): + if x not in ["N", "L"]: + for sig_suffix in signature_generator(ref_sig[i + 1:]): + yield ref_sig[:i] + ["L"] + sig_suffix + yield ref_sig + + +class PatternStorage(object): + # The PatternStorage object is used to store patterns associated to rules + # A PatternStorage contains a dictionary (next_layer_patterns) where: + # - keys are the signature of a pattern at a specific depth (i.e. the opcodes, the variable and constant) + # - values are PatternStorage object for the next depth + # Additionally, it stores the rule objects which are resolved for the PatternStorage depth + def __init__(self, depth=1): + self.depth = depth + self.next_layer_patterns = {} + self.rule_resolved = [] + + def add_pattern_for_rule(self, pattern: AstNode, rule: InstructionOptimizationRule): + layer_signature = self.layer_signature_to_key(pattern.get_depth_signature(self.depth)) + if len(layer_signature.replace(",", "")) == (layer_signature.count("N")): + self.rule_resolved.append(RulePatternInfo(rule, pattern)) + else: + if layer_signature not in self.next_layer_patterns.keys(): + self.next_layer_patterns[layer_signature] = PatternStorage(self.depth + 1) + self.next_layer_patterns[layer_signature].add_pattern_for_rule(pattern, rule) + + @staticmethod + def layer_signature_to_key(sig: List[str]) -> str: + return ",".join(sig) + + @staticmethod + def is_layer_signature_compatible(instruction_signature: str, pattern_signature: str) -> bool: + if instruction_signature == pattern_signature: + return True + instruction_node_list = instruction_signature.split(",") + pattern_node_list = pattern_signature.split(",") + for ins_node_sig, pattern_node_sig in zip(instruction_node_list, pattern_node_list): + if pattern_node_sig not in ["L", "C", "N"] and ins_node_sig != pattern_node_sig: + return False + return True + + def get_matching_rule_pattern_info(self, pattern: AstNode): + pattern_search_logger.info("Searching : {0}".format(pattern)) + return self.explore_one_level(pattern, 1) + + def explore_one_level(self, searched_pattern: AstNode, cur_level: int): + # We need to check if searched_pattern is in self.next_layer_patterns + # Easy solution: try/except self.next_layer_patterns[searched_pattern] + # Problem is that known patterns may not exactly match the microcode instruction, e.g. + # -> Pattern layer 3 signature is ["L", "N", "15", "L"] + # -> Multiple instruction can match that: ["L", "N", "15", "L"], ["C", "N", "15", "L"], ["C", "N", "15", "13"] + # This piece of code tries to handles that in a (semi) efficient way + if len(self.next_layer_patterns) == 0: + return [] + searched_layer_signature = searched_pattern.get_depth_signature(cur_level) + nb_possible_signature = 2 ** (len(searched_layer_signature) - searched_layer_signature.count("N") - \ + searched_layer_signature.count("L")) + pattern_search_logger.debug(" Layer {0}: {1} -> {2} variations (storage has {3} signature)" + .format(cur_level, searched_layer_signature, nb_possible_signature, + len(self.next_layer_patterns))) + matched_rule_pattern_info = [] + if nb_possible_signature < len(self.next_layer_patterns): + pattern_search_logger.debug(" => Using method 1") + for possible_sig in signature_generator(searched_layer_signature): + try: + test_sig = self.layer_signature_to_key(possible_sig) + pattern_storage = self.next_layer_patterns[test_sig] + pattern_search_logger.info(" Compatible signature: {0} -> resolved: {1}" + .format(test_sig, pattern_storage.rule_resolved)) + matched_rule_pattern_info += pattern_storage.rule_resolved + matched_rule_pattern_info += pattern_storage.explore_one_level(searched_pattern, cur_level + 1) + except KeyError: + pass + else: + pattern_search_logger.debug(" => Using method 2") + searched_layer_signature_key = self.layer_signature_to_key(searched_layer_signature) + for test_sig, pattern_storage in self.next_layer_patterns.items(): + if self.is_layer_signature_compatible(searched_layer_signature_key, test_sig): + pattern_search_logger.info(" Compatible signature: {0} -> resolved: {1}" + .format(test_sig, pattern_storage.rule_resolved)) + matched_rule_pattern_info += pattern_storage.rule_resolved + matched_rule_pattern_info += pattern_storage.explore_one_level(searched_pattern, cur_level + 1) + return matched_rule_pattern_info + + +class PatternOptimizer(InstructionOptimizer): + # The main idea of PatternOptimizer is to generate/store all possible patterns associated to all known rules in a $ + # dictionary-like object (PatternStorage) when the plugin is loaded. + # => it means that we generate a very large number of patterns + # + # At runtime, we transform the microcode instruction in a list of keys that we search in the PatternStorage object + # to speed up the checks + # => we don't want to test all patterns, so we use the PatternStorage object to (quickly) get the patterns + # which have the same shape as the microcode instruction + + RULE_CLASSES = [PatternMatchingRule] + + def __init__(self, maturities, log_dir=None): + super().__init__(maturities, log_dir=log_dir) + self.pattern_storage = PatternStorage(depth=1) + + def add_rule(self, rule: InstructionOptimizationRule): + is_ok = super().add_rule(rule) + if not is_ok: + return False + for pattern in rule.pattern_candidates: + self.pattern_storage.add_pattern_for_rule(pattern, rule) + return True + + def get_optimized_instruction(self, blk: mblock_t, ins: minsn_t) -> Union[None, minsn_t]: + if blk is not None: + self.cur_maturity = blk.mba.maturity + if self.cur_maturity not in self.maturities: + return None + tmp = minsn_to_ast(ins) + if tmp is None: + return None + + all_matchs = self.pattern_storage.get_matching_rule_pattern_info(tmp) + for rule_pattern_info in all_matchs: + try: + new_ins = rule_pattern_info.rule.check_pattern_and_replace(rule_pattern_info.pattern, tmp) + if new_ins is not None: + self.rules_usage_info[rule_pattern_info.rule.name] += 1 + optimizer_logger.info("Rule {0} matched:".format(rule_pattern_info.rule.name)) + optimizer_logger.info(" orig: {0}".format(format_minsn_t(ins))) + optimizer_logger.info(" new : {0}".format(format_minsn_t(new_ins))) + return new_ins + except RuntimeError as e: + optimizer_logger.error("Error during rule {0} for instruction {1}: {2}" + .format(rule_pattern_info.rule, format_minsn_t(ins), e)) + return None + +# AST equivalent pattern generation stuff +# TODO: refactor/clean this + + +def rec_get_all_binary_subtree_representation(elt_list): + if len(elt_list) == 1: + return elt_list + if len(elt_list) == 2: + return [elt_list] + tmp_res = [] + for i in range(1, len(elt_list)): + left_list = rec_get_all_binary_subtree_representation(elt_list[:i]) + right_list = rec_get_all_binary_subtree_representation(elt_list[i:]) + for l in left_list: + for r in right_list: + tmp_res.append([l, r]) + return tmp_res + + +def rec_get_all_binary_tree_representation(elt_list): + if len(elt_list) <= 1: + return elt_list + tmp = list(itertools.permutations(elt_list)) + tmp2 = [] + for perm_tmp in tmp: + tmp2 += rec_get_all_binary_subtree_representation(perm_tmp) + return tmp2 + + +def get_all_binary_tree_representation(all_elt): + tmp = rec_get_all_binary_tree_representation(all_elt) + return tmp + + +def generate_ast(opcode, leafs): + if isinstance(leafs, AstLeaf): + return leafs + if isinstance(leafs, AstNode): + return leafs + if len(leafs) == 1: + return leafs[0] + if len(leafs) == 2: + return AstNode(opcode, generate_ast(opcode, leafs[0]), generate_ast(opcode, leafs[1])) + + +def get_addition_operands(ast_node): + if not isinstance(ast_node, AstNode): + return [ast_node] + if ast_node.opcode == m_add: + return get_addition_operands(ast_node.left) + get_addition_operands(ast_node.right) + elif ast_node.opcode == m_sub: + tmp = get_addition_operands(ast_node.left) + for aaa in get_addition_operands(ast_node.right): + tmp.append(AstNode(m_neg, aaa)) + return tmp + else: + return [ast_node] + + +def get_opcode_operands(ref_opcode, ast_node): + if not isinstance(ast_node, AstNode): + return [ast_node] + if ast_node.opcode == ref_opcode: + return get_opcode_operands(ref_opcode, ast_node.left) + get_opcode_operands(ref_opcode, ast_node.right) + else: + return [ast_node] + + +def get_similar_opcode_operands(ast_node): + if ast_node.opcode in [m_add, m_sub]: + add_elts = get_addition_operands(ast_node) + all_add_ordering = get_all_binary_tree_representation(add_elts) + ast_res = [] + for leaf_ordering in all_add_ordering: + ast_res.append(generate_ast(m_add, leaf_ordering)) + return ast_res + elif ast_node.opcode in [m_xor, m_or, m_and, m_mul]: + same_elts = get_opcode_operands(ast_node.opcode, ast_node) + all_same_ordering = get_all_binary_tree_representation(same_elts) + ast_res = [] + for leaf_ordering in all_same_ordering: + ast_res.append(generate_ast(ast_node.opcode, leaf_ordering)) + return ast_res + + else: + return [ast_node] + + +def get_ast_variations_with_add_sub(opcode, left, right): + possible_ast = [AstNode(opcode, left, right)] + if opcode == m_add: + if isinstance(left, AstNode) and isinstance(right, AstNode): + if (left.opcode == m_neg) and (right.opcode == m_neg): + possible_ast.append(AstNode(m_neg, AstNode(m_add, left.left, right.left))) + if isinstance(right, AstNode) and (right.opcode == m_neg): + possible_ast.append(AstNode(m_sub, left, right.left)) + return possible_ast + + +def ast_generator(ast_node, excluded_opcodes=None): + if not isinstance(ast_node, AstNode): + return [ast_node] + res_ast = [] + excluded_opcodes = excluded_opcodes if excluded_opcodes is not None else [] + if ast_node.opcode not in excluded_opcodes: + if ast_node.opcode in [m_add, m_sub]: + similar_ast_list = get_similar_opcode_operands(ast_node) + for similar_ast in similar_ast_list: + sub_ast_left_list = ast_generator(similar_ast.left, excluded_opcodes=[m_add, m_sub]) + sub_ast_right_list = ast_generator(similar_ast.right, excluded_opcodes=[m_add, m_sub]) + for sub_ast_left in sub_ast_left_list: + for sub_ast_right in sub_ast_right_list: + res_ast += get_ast_variations_with_add_sub(m_add, sub_ast_left, sub_ast_right) + return res_ast + if ast_node.opcode in [m_xor, m_or, m_and, m_mul]: + similar_ast_list = get_similar_opcode_operands(ast_node) + for similar_ast in similar_ast_list: + sub_ast_left_list = ast_generator(similar_ast.left, excluded_opcodes=[ast_node.opcode]) + sub_ast_right_list = ast_generator(similar_ast.right, excluded_opcodes=[ast_node.opcode]) + for sub_ast_left in sub_ast_left_list: + for sub_ast_right in sub_ast_right_list: + res_ast += get_ast_variations_with_add_sub(ast_node.opcode, sub_ast_left, sub_ast_right) + return res_ast + if ast_node.opcode not in [m_add, m_sub, m_or, m_and, m_mul]: + excluded_opcodes = [] + nb_operands = 0 + if ast_node.left is not None: + nb_operands += 1 + if ast_node.right is not None: + nb_operands += 1 + if nb_operands == 1: + sub_ast_list = ast_generator(ast_node.left, excluded_opcodes=excluded_opcodes) + for sub_ast in sub_ast_list: + res_ast.append(AstNode(ast_node.opcode, sub_ast)) + return res_ast + if nb_operands == 2: + sub_ast_left_list = ast_generator(ast_node.left, excluded_opcodes=excluded_opcodes) + sub_ast_right_list = ast_generator(ast_node.right, excluded_opcodes=excluded_opcodes) + for sub_ast_left in sub_ast_left_list: + for sub_ast_right in sub_ast_right_list: + res_ast += get_ast_variations_with_add_sub(ast_node.opcode, sub_ast_left, sub_ast_right) + return res_ast + return [] diff --git a/d810/optimizers/instructions/pattern_matching/rewrite_add.py b/d810/optimizers/instructions/pattern_matching/rewrite_add.py new file mode 100644 index 0000000..11a6e90 --- /dev/null +++ b/d810/optimizers/instructions/pattern_matching/rewrite_add.py @@ -0,0 +1,243 @@ +from ida_hexrays import * + +from d810.optimizers.instructions.pattern_matching.handler import PatternMatchingRule +from d810.ast import AstLeaf, AstConstant, AstNode +from d810.hexrays_helpers import equal_ignore_msb_cst, equal_bnot_mop, AND_TABLE + + +class Add_HackersDelightRule_1(PatternMatchingRule): + PATTERN = AstNode(m_sub, + AstLeaf("x_0"), + AstNode(m_sub, + AstNode(m_bnot, + AstLeaf("x_1")), + AstConstant("1", 1))) + REPLACEMENT_PATTERN = AstNode(m_add, AstLeaf("x_0"), AstLeaf("x_1")) + + +class Add_HackersDelightRule_2(PatternMatchingRule): + PATTERN = AstNode(m_add, + AstNode(m_xor, + AstLeaf("x_0"), + AstLeaf("x_1")), + AstNode(m_mul, + AstConstant("2", 2), + AstNode(m_and, + AstLeaf("x_0"), + AstLeaf("x_1")))) + REPLACEMENT_PATTERN = AstNode(m_add, AstLeaf("x_0"), AstLeaf("x_1")) + + +class Add_HackersDelightRule_3(PatternMatchingRule): + PATTERN = AstNode(m_add, + AstNode(m_or, + AstLeaf("x_0"), + AstLeaf("x_1")), + AstNode(m_and, + AstLeaf("x_0"), + AstLeaf("x_1"))) + REPLACEMENT_PATTERN = AstNode(m_add, AstLeaf("x_0"), AstLeaf("x_1")) + + +class Add_HackersDelightRule_4(PatternMatchingRule): + PATTERN = AstNode(m_sub, + AstNode(m_mul, + AstConstant("2", 2), + AstNode(m_or, + AstLeaf("x_0"), + AstLeaf("x_1"))), + AstNode(m_xor, + AstLeaf("x_0"), + AstLeaf("x_1"))) + REPLACEMENT_PATTERN = AstNode(m_add, AstLeaf("x_0"), AstLeaf("x_1")) + + +class Add_HackersDelightRule_5(PatternMatchingRule): + PATTERN = AstNode(m_sub, + AstNode(m_mul, + AstConstant("2", 2), + AstNode(m_or, + AstNode(m_or, + AstLeaf("x_0"), + AstLeaf("x_1")), + AstLeaf("x_2"))), + AstNode(m_xor, + AstLeaf("x_0"), + AstNode(m_or, + AstLeaf("x_1"), + AstLeaf("x_2")))) + REPLACEMENT_PATTERN = AstNode(m_add, AstLeaf("x_0"), AstNode(m_or, AstLeaf("x_1"), AstLeaf("x_2"))) + + +class Add_SpecialConstantRule_1(PatternMatchingRule): + PATTERN = AstNode(m_add, + AstNode(m_xor, + AstLeaf("x_0"), + AstConstant("c_1")), + AstNode(m_mul, + AstConstant("2", 2), + AstNode(m_and, + AstLeaf("x_0"), + AstConstant("c_2")))) + REPLACEMENT_PATTERN = AstNode(m_add, AstLeaf("x_0"), AstConstant("c_1")) + + def check_candidate(self, candidate): + return equal_ignore_msb_cst(candidate["c_1"].mop, candidate["c_2"].mop) + + +class Add_SpecialConstantRule_2(PatternMatchingRule): + PATTERN = AstNode(m_add, + AstNode(m_xor, + AstNode(m_and, + AstLeaf("x_0"), + AstConstant("val_ff", 0xff)), + AstConstant("c_1")), + AstNode(m_mul, + AstConstant("2", 2), + AstNode(m_and, + AstLeaf("x_0"), + AstConstant("c_2")))) + REPLACEMENT_PATTERN = AstNode(m_add, AstLeaf("x_0"), AstConstant("c_1")) + + def check_candidate(self, candidate): + return (candidate["c_1"].value & 0xff) == candidate["c_2"].value + + +class Add_SpecialConstantRule_3(PatternMatchingRule): + PATTERN = AstNode(m_add, + AstNode(m_xor, + AstLeaf("x_0"), + AstConstant("c_1")), + AstNode(m_mul, + AstConstant("2", 2), + AstNode(m_or, + AstLeaf("x_0"), + AstConstant("c_2")))) + REPLACEMENT_PATTERN = AstNode(m_add, AstLeaf("x_0"), AstConstant("val_res")) + + def check_candidate(self, candidate): + if not equal_bnot_mop(candidate["c_1"].mop, candidate["c_2"].mop): + return False + candidate.add_constant_leaf("val_res", candidate["c_2"].value - 1, candidate["x_0"].size) + return True + + +class Add_OllvmRule_1(PatternMatchingRule): + PATTERN = AstNode(m_add, + AstNode(m_bnot, + AstNode(m_xor, + AstLeaf('x_0'), + AstLeaf('x_1'))), + AstNode(m_mul, + AstConstant("2", 2), + AstNode(m_or, + AstLeaf('x_1'), + AstLeaf('x_0')))) + REPLACEMENT_PATTERN = AstNode(m_sub, + AstNode(m_add, + AstLeaf('x_0'), + AstLeaf('x_1')), + AstConstant("val_1")) + + def check_candidate(self, candidate): + candidate.add_constant_leaf("val_1", 1, candidate.size) + return True + + +class Add_OllvmRule_2(PatternMatchingRule): + PATTERN = AstNode(m_sub, + AstNode(m_bnot, + AstNode(m_xor, + AstLeaf('x_0'), + AstLeaf('x_1'))), + AstNode(m_mul, + AstConstant("val_fe"), + AstNode(m_or, + AstLeaf('x_0'), + AstLeaf('x_1')))) + REPLACEMENT_PATTERN = AstNode(m_sub, + AstNode(m_add, + AstLeaf('x_0'), + AstLeaf('x_1')), + AstConstant("val_1")) + + def check_candidate(self, candidate): + if (candidate["val_fe"].value + 2) & AND_TABLE[candidate["val_fe"].size] != 0: + return False + candidate.add_constant_leaf("val_1", 1, candidate.size) + return True + + +class Add_OllvmRule_3(PatternMatchingRule): + PATTERN = AstNode(m_add, + AstNode(m_xor, + AstLeaf('x_0'), + AstLeaf('x_1')), + AstNode(m_mul, + AstConstant("2", 2), + AstNode(m_and, + AstLeaf('x_0'), + AstLeaf('x_1')))) + REPLACEMENT_PATTERN = AstNode(m_add, AstLeaf('x_0'), AstLeaf('x_1')) + + +class Add_OllvmRule_4(PatternMatchingRule): + PATTERN = AstNode(m_sub, + AstNode(m_xor, + AstLeaf('x_0'), + AstLeaf('x_1')), + AstNode(m_mul, + AstConstant("val_fe"), + AstNode(m_and, + AstLeaf('x_0'), + AstLeaf('x_1')))) + REPLACEMENT_PATTERN = AstNode(m_add, AstLeaf('x_0'), AstLeaf('x_1')) + + +class AddXor_Rule_1(PatternMatchingRule): + PATTERN = AstNode(m_sub, + AstNode(m_sub, + AstLeaf("x_0"), + AstLeaf("x_1")), + AstNode(m_mul, + AstConstant("2", 2), + AstNode(m_or, + AstLeaf("x_0"), + AstLeaf("bnot_x_1")))) + REPLACEMENT_PATTERN = AstNode(m_add, + AstNode(m_xor, + AstLeaf("x_0"), + AstLeaf("x_1")), + AstConstant("val_2")) + + def check_candidate(self, candidate): + if not equal_bnot_mop(candidate["x_1"].mop, candidate["bnot_x_1"].mop): + return False + candidate.add_constant_leaf("val_2", 2, candidate["x_0"].size) + return True + + +class AddXor_Rule_2(PatternMatchingRule): + PATTERN = AstNode(m_sub, + AstNode(m_sub, + AstLeaf("x_0"), + AstLeaf("x_1")), + AstNode(m_mul, + AstConstant("2", 2), + AstNode(m_bnot, + AstNode(m_and, + AstLeaf("bnot_x_0"), + AstLeaf("x_1"))))) + + REPLACEMENT_PATTERN = AstNode(m_add, + AstNode(m_xor, + AstLeaf("x_0"), + AstLeaf("x_1")), + AstLeaf("val_2")) + + def check_candidate(self, candidate): + if not equal_bnot_mop(candidate["x_0"].mop, candidate["bnot_x_0"].mop): + return False + candidate.add_constant_leaf("val_2", 2, candidate["x_0"].size) + return True + diff --git a/d810/optimizers/instructions/pattern_matching/rewrite_and.py b/d810/optimizers/instructions/pattern_matching/rewrite_and.py new file mode 100644 index 0000000..31af9f7 --- /dev/null +++ b/d810/optimizers/instructions/pattern_matching/rewrite_and.py @@ -0,0 +1,253 @@ +from ida_hexrays import * + +from d810.optimizers.instructions.pattern_matching.handler import PatternMatchingRule +from d810.ast import AstLeaf, AstConstant, AstNode +from d810.hexrays_helpers import equal_bnot_mop, SUB_TABLE + + +class And_HackersDelightRule_1(PatternMatchingRule): + PATTERN = AstNode(m_sub, + AstNode(m_or, + AstNode(m_bnot, + AstLeaf("x_0")), + AstLeaf("x_1")), + AstNode(m_bnot, AstLeaf("x_0"))) + + REPLACEMENT_PATTERN = AstNode(m_and, AstLeaf("x_0"), AstLeaf("x_1")) + + +class And_HackersDelightRule_2(PatternMatchingRule): + PATTERN = AstNode(m_add, + AstNode(m_or, + AstLeaf("bnot_x_0"), + AstLeaf("x_1")), + AstNode(m_add, + AstLeaf("x_0"), + AstConstant("1", 1))) + + REPLACEMENT_PATTERN = AstNode(m_and, AstLeaf("x_0"), AstLeaf("x_1")) + + def check_candidate(self, candidate): + return equal_bnot_mop(candidate["x_0"].mop, candidate["bnot_x_0"].mop) + + +class And_HackersDelightRule_3(PatternMatchingRule): + PATTERN = AstNode(m_sub, + AstNode(m_add, + AstLeaf("x_0"), + AstLeaf("x_1")), + AstNode(m_or, + AstLeaf("x_0"), + AstLeaf("x_1"))) + + REPLACEMENT_PATTERN = AstNode(m_and, AstLeaf("x_0"), AstLeaf("x_1")) + + +class And_HackersDelightRule_4(PatternMatchingRule): + PATTERN = AstNode(m_sub, + AstNode(m_or, + AstLeaf('x_0'), + AstLeaf('x_1')), + AstNode(m_xor, + AstLeaf('x_0'), + AstLeaf('x_1'))) + REPLACEMENT_PATTERN = AstNode(m_and, AstLeaf('x_0'), AstLeaf('x_1')) + + +class And_OllvmRule_1(PatternMatchingRule): + PATTERN = AstNode(m_and, + AstNode(m_or, + AstLeaf('x_0'), + AstLeaf('x_1')), + AstNode(m_bnot, + AstNode(m_xor, + AstLeaf('x_0'), + AstLeaf('x_1')))) + REPLACEMENT_PATTERN = AstNode(m_and, AstLeaf('x_0'), AstLeaf('x_1')) + + +class And_OllvmRule_2(PatternMatchingRule): + PATTERN = AstNode(m_and, + AstNode(m_or, + AstLeaf('x_0'), + AstLeaf('x_1')), + AstNode(m_xor, + AstLeaf('x_0'), + AstLeaf('bnot_x_1'))) + REPLACEMENT_PATTERN = AstNode(m_and, AstLeaf('x_0'), AstLeaf('x_1')) + + def check_candidate(self, candidate): + return equal_bnot_mop(candidate["x_1"].mop, candidate["bnot_x_1"].mop) + + +class And_OllvmRule_3(PatternMatchingRule): + PATTERN = AstNode(m_and, + AstNode(m_and, + AstLeaf('x_0'), + AstLeaf('x_1')), + AstNode(m_bnot, + AstNode(m_xor, + AstLeaf('x_0'), + AstLeaf('x_1')))) + REPLACEMENT_PATTERN = AstNode(m_and, AstLeaf('x_0'), AstLeaf('x_1')) + + + +class And_FactorRule_1(PatternMatchingRule): + PATTERN = AstNode(m_and, + AstNode(m_xor, + AstLeaf("x_0"), + AstLeaf("bnot_x_1")), + AstLeaf("x_1")) + REPLACEMENT_PATTERN = AstNode(m_and, AstLeaf("x_0"), AstLeaf("x_1")) + + def check_candidate(self, candidate): + return equal_bnot_mop(candidate["x_1"].mop, candidate["bnot_x_1"].mop) + + +class And_FactorRule_2(PatternMatchingRule): + PATTERN = AstNode(m_and, + AstLeaf('x_0'), + AstNode(m_bnot, + AstNode(m_xor, + AstLeaf('x_0'), + AstLeaf('x_1')))) + REPLACEMENT_PATTERN = AstNode(m_and, AstLeaf('x_0'), AstLeaf('x_1')) + + +class AndBnot_HackersDelightRule_1(PatternMatchingRule): + PATTERN = AstNode(m_sub, + AstNode(m_or, + AstLeaf("x_0"), + AstLeaf("x_1")), + AstLeaf("x_1")) + REPLACEMENT_PATTERN = AstNode(m_and, + AstLeaf("x_0"), + AstNode(m_bnot, AstLeaf("x_1"))) + + +class AndBnot_HackersDelightRule_2(PatternMatchingRule): + PATTERN = AstNode(m_sub, + AstLeaf("x_0"), + AstNode(m_and, + AstLeaf("x_0"), + AstLeaf("x_1"))) + REPLACEMENT_PATTERN = AstNode(m_and, + AstLeaf("x_0"), + AstNode(m_bnot, AstLeaf("x_1"))) + + +class AndBnot_FactorRule_1(PatternMatchingRule): + PATTERN = AstNode(m_xor, + AstLeaf("x_0"), + AstNode(m_and, + AstLeaf("x_0"), + AstLeaf("x_1"))) + + REPLACEMENT_PATTERN = AstNode(m_and, + AstLeaf("x_0"), + AstNode(m_bnot, AstLeaf("x_1"))) + + +class AndBnot_FactorRule_2(PatternMatchingRule): + PATTERN = AstNode(m_and, + AstLeaf("x_0"), + AstNode(m_xor, + AstLeaf("x_0"), + AstLeaf("x_1"))) + + REPLACEMENT_PATTERN = AstNode(m_and, + AstLeaf("x_0"), + AstNode(m_bnot, AstLeaf("x_1"))) + + +class AndBnot_FactorRule_3(PatternMatchingRule): + PATTERN = AstNode(m_xor, + AstNode(m_or, + AstLeaf("x_0"), + AstLeaf("x_1")), + AstLeaf("x_1")) + + REPLACEMENT_PATTERN = AstNode(m_and, + AstLeaf("x_0"), + AstNode(m_bnot, AstLeaf("x_1"))) + + +class AndBnot_FactorRule_4(PatternMatchingRule): + PATTERN = AstNode(m_and, + AstNode(m_xor, + AstLeaf('x_1'), + AstLeaf('x_0')), + AstNode(m_bnot, + AstNode(m_and, + AstLeaf('x_0'), + AstLeaf('bnot_x_1')))) + + REPLACEMENT_PATTERN = AstNode(m_and, + AstLeaf("x_1"), + AstNode(m_bnot, AstLeaf("x_0"))) + + def check_candidate(self, candidate): + return equal_bnot_mop(candidate["x_1"].mop, candidate["bnot_x_1"].mop) + + +class AndOr_FactorRule_1(PatternMatchingRule): + PATTERN = AstNode(m_or, + AstNode(m_and, + AstLeaf("x_0"), + AstLeaf("x_2")), + AstNode(m_and, + AstLeaf("x_1"), + AstLeaf("x_2"))) + REPLACEMENT_PATTERN = AstNode(m_and, + AstNode(m_or, + AstLeaf("x_0"), + AstLeaf("x_1")), + AstLeaf("x_2")) + + +class AndXor_FactorRule_1(PatternMatchingRule): + PATTERN = AstNode(m_xor, + AstNode(m_and, + AstLeaf("x_0"), + AstLeaf("x_2")), + AstNode(m_and, + AstLeaf("x_1"), + AstLeaf("x_2"))) + REPLACEMENT_PATTERN = AstNode(m_and, + AstNode(m_xor, + AstLeaf("x_0"), + AstLeaf("x_1")), + AstLeaf("x_2")) + + +class And1_MbaRule_1(PatternMatchingRule): + PATTERN = AstNode(m_and, + AstNode(m_mul, AstLeaf("x_0"), AstLeaf("x_0")), + AstConstant("3", 3)) + REPLACEMENT_PATTERN = AstNode(m_and, + AstLeaf("x_0"), + AstConstant("val_1")) + + def check_candidate(self, candidate): + candidate.add_constant_leaf("val_1", 1, candidate.size) + return True + + +class AndGetUpperBits_FactorRule_1(PatternMatchingRule): + PATTERN = AstNode(m_mul, + AstConstant("c_1"), + AstNode(m_and, + AstNode(m_shr, + AstLeaf('x_0'), + AstConstant("c_2")), + AstConstant("c_3"))) + + REPLACEMENT_PATTERN = AstNode(m_and, AstLeaf('x_0'), AstConstant("c_res")) + + def check_candidate(self, candidate): + if (2 ** candidate["c_2"].value) != candidate["c_1"].value: + return False + c_res = (SUB_TABLE[candidate["c_1"].size] - candidate["c_1"].value) & candidate["c_3"].value + candidate.add_constant_leaf("c_res", c_res, candidate["x_0"].size) + return True diff --git a/d810/optimizers/instructions/pattern_matching/rewrite_bnot.py b/d810/optimizers/instructions/pattern_matching/rewrite_bnot.py new file mode 100644 index 0000000..1c97adf --- /dev/null +++ b/d810/optimizers/instructions/pattern_matching/rewrite_bnot.py @@ -0,0 +1,272 @@ +from ida_hexrays import * + +from d810.optimizers.instructions.pattern_matching.handler import PatternMatchingRule +from d810.ast import AstLeaf, AstConstant, AstNode +from d810.hexrays_helpers import equal_bnot_mop, SUB_TABLE + + +class Bnot_HackersDelightRule_1(PatternMatchingRule): + PATTERN = AstNode(m_sub, + AstNode(m_neg, + AstLeaf("x_0")), + AstConstant("1", 1)) + REPLACEMENT_PATTERN = AstNode(m_bnot, AstLeaf("x_0")) + + +class Bnot_HackersDelightRule_2(PatternMatchingRule): + PATTERN = AstNode(m_or, + AstNode(m_bnot, + AstNode(m_or, + AstLeaf("x_0"), + AstLeaf("x_1"))), + AstNode(m_bnot, + AstLeaf("x_1"))) + REPLACEMENT_PATTERN = AstNode(m_bnot, AstLeaf("x_1")) + + +class Bnot_MbaRule_1(PatternMatchingRule): + PATTERN = AstNode(m_sub, + AstNode(m_sub, + AstLeaf("x_0"), + AstConstant("1", 1)), + AstNode(m_mul, + AstConstant("2", 2), + AstLeaf("x_0"))) + REPLACEMENT_PATTERN = AstNode(m_bnot, AstLeaf("x_0")) + + +class Bnot_FactorRule_1(PatternMatchingRule): + PATTERN = AstNode(m_xor, + AstNode(m_bnot, + AstNode(m_xor, + AstLeaf("x_0"), + AstLeaf("x_1"))), + AstLeaf("x_1")) + REPLACEMENT_PATTERN = AstNode(m_bnot, AstLeaf("x_0")) + + +class Bnot_FactorRule_2(PatternMatchingRule): + PATTERN = AstNode(m_sub, + AstConstant("minus_1"), + AstLeaf("x_0")) + REPLACEMENT_PATTERN = AstNode(m_bnot, AstLeaf("x_0")) + + def check_candidate(self, candidate): + if candidate["minus_1"].value != SUB_TABLE[candidate["minus_1"].size] - 1: + return False + return True + + +class Bnot_FactorRule_3(PatternMatchingRule): + PATTERN = AstNode(m_xor, + AstNode(m_and, + AstLeaf('x_0'), + AstLeaf('x_1')), + AstNode(m_or, + AstLeaf('x_0'), + AstLeaf('bnot_x_1'))) + REPLACEMENT_PATTERN = AstNode(m_bnot, AstLeaf("x_1")) + + def check_candidate(self, candidate): + if not equal_bnot_mop(candidate["x_1"].mop, candidate["bnot_x_1"].mop): + return False + return True + + +class Bnot_FactorRule_4(PatternMatchingRule): + PATTERN = AstNode(m_xor, + AstNode(m_bnot, AstLeaf('x_0')), + AstNode(m_bnot, AstLeaf('x_1'))) + REPLACEMENT_PATTERN = AstNode(m_xor, AstLeaf('x_0'), AstLeaf("x_1")) + + +class BnotXor_Rule_1(PatternMatchingRule): + PATTERN = AstNode(m_or, + AstNode(m_and, + AstLeaf("x_0"), + AstLeaf("x_1")), + AstNode(m_and, + AstLeaf("bnot_x_0"), + AstLeaf("bnot_x_1"))) + + REPLACEMENT_PATTERN = AstNode(m_bnot, + AstNode(m_xor, AstLeaf("x_0"), AstLeaf("x_1"))) + + def check_candidate(self, candidate): + if not equal_bnot_mop(candidate["x_0"].mop, candidate["bnot_x_0"].mop): + return False + if not equal_bnot_mop(candidate["x_1"].mop, candidate["bnot_x_1"].mop): + return False + return True + + +class BnotXor_Rule_2(PatternMatchingRule): + PATTERN = AstNode(m_xor, + AstNode(m_or, + AstLeaf('x_0'), + AstLeaf('x_1')), + AstNode(m_or, + AstLeaf('bnot_x_0'), + AstLeaf('bnot_x_1'))) + + REPLACEMENT_PATTERN = AstNode(m_bnot, + AstNode(m_xor, AstLeaf("x_0"), AstLeaf("x_1"))) + + def check_candidate(self, candidate): + if not equal_bnot_mop(candidate["x_0"].mop, candidate["bnot_x_0"].mop): + return False + if not equal_bnot_mop(candidate["x_1"].mop, candidate["bnot_x_1"].mop): + return False + return True + +class BnotXor_Rule_3(PatternMatchingRule): + PATTERN = AstNode(m_and, + AstNode(m_or, + AstLeaf('x_0'), + AstLeaf('bnot_x_1')), + AstNode(m_or, + AstLeaf('bnot_x_0'), + AstLeaf('x_1'))) + + REPLACEMENT_PATTERN = AstNode(m_bnot, + AstNode(m_xor, AstLeaf("x_0"), AstLeaf("x_1"))) + + def check_candidate(self, candidate): + if not equal_bnot_mop(candidate["x_0"].mop, candidate["bnot_x_0"].mop): + return False + if not equal_bnot_mop(candidate["x_1"].mop, candidate["bnot_x_1"].mop): + return False + return True + + +class BnotXor_FactorRule_1(PatternMatchingRule): + PATTERN = AstNode(m_xor, + AstLeaf("x_0"), + AstNode(m_bnot, + AstLeaf("x_1"))) + + REPLACEMENT_PATTERN = AstNode(m_bnot, + AstNode(m_xor, AstLeaf("x_0"), AstLeaf("x_1"))) + + +class BnotAnd_FactorRule_1(PatternMatchingRule): + PATTERN = AstNode(m_or, + AstNode(m_xor, + AstLeaf("x_0"), + AstLeaf("x_1")), + AstNode(m_bnot, + AstNode(m_or, + AstLeaf("x_0"), + AstLeaf("x_1")))) + + REPLACEMENT_PATTERN = AstNode(m_bnot, + AstNode(m_and, AstLeaf("x_0"), AstLeaf("x_1"))) + + + +class BnotAnd_FactorRule_2(PatternMatchingRule): + PATTERN = AstNode(m_or, + AstNode(m_or, + AstLeaf("bnot_x_0"), + AstLeaf("bnot_x_1")), + AstNode(m_xor, + AstLeaf("x_0"), + AstLeaf("x_1"))) + + REPLACEMENT_PATTERN = AstNode(m_bnot, + AstNode(m_and, AstLeaf("x_0"), AstLeaf("x_1"))) + + def check_candidate(self, candidate): + if not equal_bnot_mop(candidate["x_0"].mop, candidate["bnot_x_0"].mop): + return False + if not equal_bnot_mop(candidate["x_1"].mop, candidate["bnot_x_1"].mop): + return False + return True + + +class BnotAnd_FactorRule_3(PatternMatchingRule): + PATTERN = AstNode(m_or, + AstNode(m_bnot, + AstLeaf("x_0")), + AstNode(m_bnot, + AstLeaf("x_1"))) + + REPLACEMENT_PATTERN = AstNode(m_bnot, + AstNode(m_and, AstLeaf("x_0"), AstLeaf("x_1"))) + + +class BnotAnd_FactorRule_4(PatternMatchingRule): + PATTERN = AstNode(m_or, + AstLeaf("bnot_x_0"), + AstNode(m_xor, + AstLeaf("x_0"), + AstLeaf("x_1"))) + REPLACEMENT_PATTERN = AstNode(m_bnot, + AstNode(m_and, AstLeaf("x_0"), AstLeaf("x_1"))) + + def check_candidate(self, candidate): + if not equal_bnot_mop(candidate["x_0"].mop, candidate["bnot_x_0"].mop): + return False + return True + + +class BnotOr_FactorRule_1(PatternMatchingRule): + PATTERN = AstNode(m_and, + AstNode(m_bnot, + AstLeaf("x_0")), + AstNode(m_bnot, + AstLeaf("x_1"))) + + REPLACEMENT_PATTERN = AstNode(m_bnot, + AstNode(m_or, AstLeaf("x_0"), AstLeaf("x_1"))) + + +class BnotAdd_MbaRule_1(PatternMatchingRule): + PATTERN = AstNode(m_sub, + AstNode(m_xor, + AstLeaf("x_0"), + AstLeaf("bnot_x_1")), + AstNode(m_mul, + AstConstant("2", 2), + AstNode(m_and, + AstLeaf("x_0"), + AstLeaf("x_1")))) + + REPLACEMENT_PATTERN = AstNode(m_bnot, + AstNode(m_add, AstLeaf("x_0"), AstLeaf("x_1"))) + + def check_candidate(self, candidate): + if not equal_bnot_mop(candidate["x_1"].mop, candidate["bnot_x_1"].mop): + return False + return True + + +class Bnot_Rule_1(PatternMatchingRule): + PATTERN = AstNode(m_or, + AstNode(m_and, + AstLeaf("x_0"), + AstLeaf("bnot_x_1")), + AstNode(m_bnot, + AstNode(m_or, + AstLeaf("x_0"), + AstLeaf("x_1")))) + + REPLACEMENT_PATTERN = AstNode(m_mov, AstLeaf("bnot_x_1")) + + def check_candidate(self, candidate): + if not equal_bnot_mop(candidate["x_1"].mop, candidate["bnot_x_1"].mop): + return False + return True + + +class Bnot_XorRule_1(PatternMatchingRule): + PATTERN = AstNode(m_or, + AstNode(m_and, + AstLeaf("x_0"), + AstLeaf("x_1")), + AstNode(m_bnot, + AstNode(m_or, + AstLeaf("x_0"), + AstLeaf("x_1")))) + + REPLACEMENT_PATTERN = AstNode(m_bnot, AstNode(m_xor, AstLeaf("x_0"), AstLeaf("x_1"))) diff --git a/d810/optimizers/instructions/pattern_matching/rewrite_cst.py b/d810/optimizers/instructions/pattern_matching/rewrite_cst.py new file mode 100644 index 0000000..c60a5b1 --- /dev/null +++ b/d810/optimizers/instructions/pattern_matching/rewrite_cst.py @@ -0,0 +1,448 @@ +from ida_hexrays import * + +from d810.ast import AstLeaf, AstConstant, AstNode +from d810.optimizers.instructions.pattern_matching.handler import PatternMatchingRule +from d810.hexrays_helpers import equal_bnot_cst, SUB_TABLE, AND_TABLE, equal_bnot_mop + + +class CstSimplificationRule1(PatternMatchingRule): + PATTERN = AstNode(m_and, + AstNode(m_bnot, + AstLeaf("x_0")), + AstNode(m_xor, + AstNode(m_bnot, + AstLeaf("x_0")), + AstConstant("c_1"))) + REPLACEMENT_PATTERN = AstNode(m_xor, + AstNode(m_and, + AstLeaf("x_0"), + AstNode(m_bnot, + AstConstant("c_1"))), + AstNode(m_bnot, AstConstant("c_1"))) + + +class CstSimplificationRule2(PatternMatchingRule): + PATTERN = AstNode(m_or, + AstNode(m_and, + AstNode(m_xor, + AstLeaf("x_0"), + AstConstant("c_1_1")), + AstConstant("c_2_1")), + AstNode(m_and, + AstNode(m_xor, + AstLeaf("x_0"), + AstConstant("c_1_2")), + AstConstant("c_2_2"))) + REPLACEMENT_PATTERN = AstNode(m_xor, AstLeaf("x_0"), AstConstant("c_res")) + + def check_candidate(self, candidate): + if not equal_bnot_cst(candidate["c_2_1"].mop, candidate["c_2_2"].mop): + return False + c_res = ((candidate["c_1_1"].value ^ candidate["c_1_2"].value) & candidate["c_2_1"].value) + c_res ^= candidate["c_1_2"].value + candidate.add_constant_leaf("c_res", c_res, candidate["c_1_1"].size) + return True + + +class CstSimplificationRule3(PatternMatchingRule): + PATTERN = AstNode(m_add, + AstNode(m_sub, + AstLeaf("x_0"), + AstConstant("c_0")), + AstNode(m_mul, + AstConstant("c_1"), + AstNode(m_sub, + AstLeaf("x_0"), + AstConstant("c_2")))) + REPLACEMENT_PATTERN = AstNode(m_sub, + AstNode(m_mul, + AstConstant("c_coeff"), + AstLeaf("x_0")), + AstConstant("c_sub")) + + def check_candidate(self, candidate): + c_coeff = candidate["c_1"].value + 1 + c_sub = (candidate["c_1"].value * candidate["c_2"].value) + candidate["c_0"].value + candidate.add_constant_leaf("c_coeff", c_coeff, candidate["c_1"].size) + candidate.add_constant_leaf("c_sub", c_sub, candidate["c_2"].size) + return True + + +class CstSimplificationRule4(PatternMatchingRule): + PATTERN = AstNode(m_sub, + AstLeaf("x_0"), + AstNode(m_sub, + AstConstant("c_1"), + AstLeaf("x_1"))) + REPLACEMENT_PATTERN = AstNode(m_add, + AstLeaf("x_0"), + AstNode(m_add, + AstLeaf("x_1"), + AstConstant("c_res"))) + + def check_candidate(self, candidate): + c_res = SUB_TABLE[candidate["c_1"].size] - candidate["c_1"].value + candidate.add_constant_leaf("c_res", c_res, candidate["c_1"].size) + return True + + +class CstSimplificationRule5(PatternMatchingRule): + PATTERN = AstNode(m_or, + AstNode(m_and, + AstLeaf("x_0"), + AstConstant("c_1")), + AstNode(m_and, + AstLeaf("x_1"), + AstConstant("c_2"))) + REPLACEMENT_PATTERN = AstNode(m_xor, + AstNode(m_and, + AstNode(m_xor, + AstLeaf("x_0"), + AstLeaf("x_1")), + AstConstant("c_1")), + AstLeaf("x_1")) + + def check_candidate(self, candidate): + return equal_bnot_cst(candidate["c_1"].mop, candidate["c_2"].mop) + + +class CstSimplificationRule6(PatternMatchingRule): + PATTERN = AstNode(m_and, + AstNode(m_xor, + AstLeaf("x_0"), + AstConstant("c_1")), + AstConstant("c_2")) + REPLACEMENT_PATTERN = AstNode(m_xor, + AstNode(m_and, + AstLeaf("x_0"), + AstConstant("c_2")), + AstConstant("c_res")) + + def check_candidate(self, candidate): + c_res = candidate["c_1"].value & candidate["c_2"].value + candidate.add_constant_leaf("c_res", c_res, candidate["c_2"].size) + return True + + +class CstSimplificationRule7(PatternMatchingRule): + PATTERN = AstNode(m_shr, + AstNode(m_and, + AstLeaf("x_0"), + AstConstant("c_1")), + AstConstant("c_2")) + REPLACEMENT_PATTERN = AstNode(m_and, + AstNode(m_shr, + AstLeaf("x_0"), + AstConstant("c_2")), + AstConstant("c_res")) + + def check_candidate(self, candidate): + c_res = candidate["c_1"].value >> candidate["c_2"].value + candidate.add_constant_leaf("c_res", c_res, candidate["c_1"].size) + return True + + +class CstSimplificationRule8(PatternMatchingRule): + PATTERN = AstNode(m_or, + AstNode(m_and, + AstLeaf("x_0"), + AstConstant("c_1")), + AstConstant("c_2")) + REPLACEMENT_PATTERN = AstNode(m_or, + AstNode(m_and, + AstLeaf("x_0"), + AstConstant("c_res")), + AstConstant("c_2")) + + def check_candidate(self, candidate): + c_res = candidate["c_1"].value & ~candidate["c_2"].value + if c_res == candidate["c_1"].value: + return False + candidate.add_constant_leaf("c_res", c_res, candidate["c_1"].size) + return True + + +class CstSimplificationRule9(PatternMatchingRule): + PATTERN = AstNode(m_and, + AstNode(m_or, + AstLeaf("x_0"), + AstConstant("c_1")), + AstConstant("c_2")) + REPLACEMENT_PATTERN = AstNode(m_xor, + AstNode(m_and, + AstLeaf("x_0"), + AstConstant("c_and")), + AstConstant("c_xor")) + + def check_candidate(self, candidate): + c_and = (AND_TABLE[candidate["c_1"].size] ^ candidate["c_1"].value) & candidate["c_2"].value + c_xor = candidate["c_1"].value & candidate["c_2"].value + candidate.add_constant_leaf("c_and", c_and, candidate["x_0"].size) + candidate.add_constant_leaf("c_xor", c_xor, candidate["x_0"].size) + return True + + +class CstSimplificationRule10(PatternMatchingRule): + PATTERN = AstNode(m_sub, + AstNode(m_and, + AstLeaf("x_0"), + AstConstant("c_1")), + AstNode(m_and, + AstLeaf("x_0"), + AstConstant("c_2"))) + REPLACEMENT_PATTERN = AstNode(m_neg, + AstNode(m_and, + AstLeaf("x_0"), + AstConstant("c_and"))) + + def check_candidate(self, candidate): + if (candidate["c_1"].value & candidate["c_2"].value) != candidate["c_1"].value: + return False + c_and = (AND_TABLE[candidate["c_1"].size] ^ candidate["c_1"].value) & candidate["c_2"].value + candidate.add_constant_leaf("c_and", c_and, candidate["x_0"].size) + return True + + +class CstSimplificationRule11(PatternMatchingRule): + PATTERN = AstNode(m_or, + AstNode(m_xor, + AstNode(m_bnot, + AstLeaf("x_0")), + AstConstant("c_1")), + AstNode(m_and, + AstLeaf("x_0"), + AstConstant("c_2"))) + REPLACEMENT_PATTERN = AstNode(m_xor, + AstNode(m_xor, + AstLeaf("x_0"), + AstConstant("c_1_bnot")), + AstNode(m_and, + AstLeaf("x_0"), + AstConstant("c_and"))) + + def check_candidate(self, candidate): + c_1_bnot = (AND_TABLE[candidate["c_1"].size] ^ candidate["c_1"].value) + c_and = c_1_bnot & candidate["c_2"].value + candidate.add_constant_leaf("c_1_bnot", c_1_bnot, candidate["c_1"].size) + candidate.add_constant_leaf("c_and", c_and, candidate["c_1"].size) + return True + + +class CstSimplificationRule12(PatternMatchingRule): + PATTERN = AstNode(m_sub, + AstNode(m_sub, + AstConstant("c_1"), + AstLeaf("x_0")), + AstNode(m_mul, + AstConstant("2", 2), + AstNode(m_and, + AstNode(m_bnot, + AstLeaf("x_0")), + AstConstant("c_2")))) + REPLACEMENT_PATTERN = AstNode(m_sub, + AstNode(m_xor, + AstNode(m_bnot, AstLeaf("x_0")), + AstConstant("c_2")), + AstConstant("c_diff")) + + def check_candidate(self, candidate): + c_diff = candidate["c_2"].value - candidate["c_1"].value + candidate.add_constant_leaf("c_diff", c_diff, candidate["c_1"].size) + return True + + +class CstSimplificationRule13(PatternMatchingRule): + PATTERN = AstNode(m_xor, + AstNode(m_and, + AstConstant("cst_1"), + AstNode(m_xor, + AstLeaf("x_0"), + AstLeaf("x_1"))), + AstLeaf("x_1")) + REPLACEMENT_PATTERN = AstNode(m_xor, + AstNode(m_and, + AstLeaf("x_0"), + AstConstant("cst_1")), + AstNode(m_and, AstLeaf("x_1"), + AstConstant("not_cst_1"))) + + def check_candidate(self, candidate): + candidate.add_constant_leaf("not_cst_1", ~candidate["cst_1"].value, candidate["cst_1"].size) + return True + + +class CstSimplificationRule14(PatternMatchingRule): + PATTERN = AstNode(m_add, + AstNode(m_and, + AstLeaf("x_0"), + AstConstant("c_1")), + AstConstant("c_2")) + REPLACEMENT_PATTERN = AstNode(m_add, + AstNode(m_or, + AstLeaf("x_0"), + AstLeaf("lnot_c_1")), + AstConstant("val_1")) + + def check_candidate(self, candidate): + lnot_c_1_value = candidate["c_1"].value ^ AND_TABLE[candidate["c_1"].size] + tmp = lnot_c_1_value ^ candidate["c_2"].value + if tmp != 1: + return False + candidate.add_constant_leaf("val_1", 1, candidate["c_2"].size) + candidate.add_constant_leaf("lnot_c_1", lnot_c_1_value, candidate["c_1"].size) + + +class CstSimplificationRule15(PatternMatchingRule): + PATTERN = AstNode(m_shr, + AstNode(m_shr, + AstLeaf("x_0"), + AstConstant("c_1")), + AstConstant("c_2")) + REPLACEMENT_PATTERN = AstNode(m_shr, AstLeaf("x_0"), AstConstant("c_res")) + + def check_candidate(self, candidate): + candidate.add_constant_leaf("c_res", candidate["c_1"].value + candidate["c_2"].value, candidate["c_1"].size) + return True + + +class CstSimplificationRule16(PatternMatchingRule): + PATTERN = AstNode(m_bnot, + AstNode(m_xor, + AstLeaf("x_0"), + AstConstant("c_1"))) + REPLACEMENT_PATTERN = AstNode(m_xor, + AstLeaf("x_0"), + AstLeaf("bnot_c_1")) + + def check_candidate(self, candidate): + candidate.add_constant_leaf("bnot_c_1", candidate["c_1"].value ^ AND_TABLE[candidate["c_1"].size], + candidate["c_1"].size) + return True + + +class CstSimplificationRule17(PatternMatchingRule): + PATTERN = AstNode(m_bnot, + AstNode(m_or, + AstLeaf("x_0"), + AstConstant("c_1"))) + REPLACEMENT_PATTERN = AstNode(m_and, + AstNode(m_bnot, AstLeaf("x_0")), + AstLeaf("bnot_c_1")) + + def check_candidate(self, candidate): + candidate.add_constant_leaf("bnot_c_1", candidate["c_1"].value ^ AND_TABLE[candidate["c_1"].size], + candidate["c_1"].size) + return True + + +class CstSimplificationRule18(PatternMatchingRule): + PATTERN = AstNode(m_bnot, + AstNode(m_and, + AstLeaf("x_0"), + AstConstant("c_1"))) + REPLACEMENT_PATTERN = AstNode(m_or, + AstNode(m_bnot, AstLeaf("x_0")), + AstLeaf("bnot_c_1")) + + def check_candidate(self, candidate): + candidate.add_constant_leaf("bnot_c_1", candidate["c_1"].value ^ AND_TABLE[candidate["c_1"].size], + candidate["c_1"].size) + return True + + +class CstSimplificationRule19(PatternMatchingRule): + PATTERN = AstNode(m_sar, + AstNode(m_and, + AstLeaf("x_0"), + AstConstant("c_1")), + AstConstant("c_2")) + REPLACEMENT_PATTERN = AstNode(m_and, AstNode(m_shr, AstLeaf("x_0"), AstConstant("c_2")), AstConstant("c_res")) + + def check_candidate(self, candidate): + candidate.add_constant_leaf("c_res", candidate["c_1"].value >> candidate["c_2"].value, + candidate["c_1"].size) + return True + + +# Found sometimes with OLLVM +class CstSimplificationRule20(PatternMatchingRule): + PATTERN = AstNode(m_or, + AstNode(m_and, + AstLeaf('bnot_x_0'), + AstConstant('c_and_1')), + AstNode(m_xor, + AstNode(m_and, + AstLeaf('x_0'), + AstConstant('c_and_2')), + AstConstant('c_xor'))) + + REPLACEMENT_PATTERN = AstNode(m_xor, + AstNode(m_and, + AstLeaf("x_0"), + AstConstant("c_and_res")), + AstConstant("c_xor_res")) + + def check_candidate(self, candidate): + if not equal_bnot_mop(candidate["x_0"].mop, candidate["bnot_x_0"].mop): + return False + if candidate["c_and_1"].value & candidate["c_and_2"].value != 0: + return False + candidate.add_constant_leaf("c_and_res", candidate["c_and_1"].value ^ candidate["c_and_2"].value, + candidate["c_and_1"].size) + candidate.add_constant_leaf("c_xor_res", candidate["c_and_1"].value ^ candidate["c_xor"].value, + candidate["c_and_1"].size) + return True + + +# Found sometimes with OLLVM +class CstSimplificationRule21(PatternMatchingRule): + PATTERN = AstNode(m_or, + AstNode(m_xor, + AstNode(m_and, + AstLeaf('x_0'), + AstConstant('c_and')), + AstConstant('c_xor_1')), + AstNode(m_xor, + AstNode(m_and, + AstLeaf('x_0'), + AstConstant('bnot_c_and')), + AstConstant('c_xor_2'))) + REPLACEMENT_PATTERN = AstNode(m_xor, AstLeaf("x_0"), AstConstant("c_xor_res")) + + def check_candidate(self, candidate): + if not equal_bnot_cst(candidate["c_and"].mop, candidate["bnot_c_and"].mop): + return False + if candidate["c_xor_1"].mop.nnn.value & candidate["c_xor_2"].mop.nnn.value != 0: + return False + candidate.add_constant_leaf("c_xor_res", candidate["c_xor_1"].value ^ candidate["c_xor_2"].value, + candidate["c_xor_1"].size) + return True + + +# Found sometimes with OLLVM +class CstSimplificationRule22(PatternMatchingRule): + PATTERN = AstNode(m_or, + AstNode(m_xor, + AstNode(m_and, + AstLeaf('x_0'), + AstConstant('c_and')), + AstConstant('c_xor_1')), + AstNode(m_xor, + AstNode(m_and, + AstLeaf('bnot_x_0'), + AstConstant('bnot_c_and')), + AstConstant('c_xor_2'))) + REPLACEMENT_PATTERN = AstNode(m_xor, AstLeaf("x_0"), AstConstant("c_xor_res")) + + def check_candidate(self, candidate): + if not equal_bnot_mop(candidate["x_0"].mop, candidate["bnot_x_0"].mop): + return False + if not equal_bnot_cst(candidate["c_and"].mop, candidate["bnot_c_and"].mop): + return False + if candidate["c_xor_1"].mop.nnn.value & candidate["c_xor_2"].mop.nnn.value != 0: + return False + if candidate["c_xor_1"].mop.nnn.value & candidate["bnot_c_and"].mop.nnn.value != 0: + return False + candidate.add_constant_leaf("c_xor_res", candidate["c_xor_1"].value ^ candidate["c_xor_2"].value ^ candidate["bnot_c_and"].value, + candidate["c_xor_1"].size) + return True diff --git a/d810/optimizers/instructions/pattern_matching/rewrite_mov.py b/d810/optimizers/instructions/pattern_matching/rewrite_mov.py new file mode 100644 index 0000000..28b1800 --- /dev/null +++ b/d810/optimizers/instructions/pattern_matching/rewrite_mov.py @@ -0,0 +1,50 @@ +from ida_hexrays import * + + +from d810.optimizers.instructions.pattern_matching.handler import PatternMatchingRule +from d810.ast import AstLeaf, AstConstant, AstNode +from d810.hexrays_helpers import equal_bnot_mop, AND_TABLE + + +# GetIdentRule1: ((x_0 & x_1) + (x_0 & ~x_1)) == x_0 +class GetIdentRule1(PatternMatchingRule): + PATTERN = AstNode(m_add, + AstNode(m_and, + AstLeaf('x_0'), + AstLeaf('x_1')), + AstNode(m_and, + AstLeaf('x_0'), + AstLeaf('bnot_x_1'))) + REPLACEMENT_PATTERN = AstNode(m_mov, AstLeaf("x_0")) + + def check_candidate(self, candidate): + if not equal_bnot_mop(candidate["x_1"].mop, candidate["bnot_x_1"].mop): + return False + return True + + +# GetIdentRule2: ((x_0 & x_1) ^ (x_0 & ~x_1)) == x_0 i +class GetIdentRule2(PatternMatchingRule): + PATTERN = AstNode(m_xor, + AstNode(m_and, + AstLeaf('x_0'), + AstLeaf('x_1')), + AstNode(m_and, + AstLeaf('x_0'), + AstLeaf('bnot_x_1'))) + REPLACEMENT_PATTERN = AstNode(m_mov, AstLeaf("x_0")) + + def check_candidate(self, candidate): + if not equal_bnot_mop(candidate["x_1"].mop, candidate["bnot_x_1"].mop): + return False + return True + + +class GetIdentRule3(PatternMatchingRule): + PATTERN = AstNode(m_and, + AstLeaf("x_0"), + AstNode(m_or, + AstLeaf("x_0"), + AstLeaf("x_1"))) + + REPLACEMENT_PATTERN = AstNode(m_mov, AstLeaf("x_0")) diff --git a/d810/optimizers/instructions/pattern_matching/rewrite_mul.py b/d810/optimizers/instructions/pattern_matching/rewrite_mul.py new file mode 100644 index 0000000..8ee1cc5 --- /dev/null +++ b/d810/optimizers/instructions/pattern_matching/rewrite_mul.py @@ -0,0 +1,153 @@ +from ida_hexrays import * + +from d810.optimizers.instructions.pattern_matching.handler import PatternMatchingRule +from d810.ast import AstLeaf, AstConstant, AstNode +from d810.hexrays_helpers import equal_bnot_mop, is_check_mop, SUB_TABLE + + +class Mul_MbaRule_1(PatternMatchingRule): + PATTERN = AstNode(m_add, + AstNode(m_mul, + AstNode(m_or, + AstLeaf('x_0'), + AstLeaf('x_1')), + AstNode(m_and, + AstLeaf('x_0'), + AstLeaf('x_1'))), + AstNode(m_mul, + AstNode(m_and, + AstLeaf('x_0'), + AstLeaf('bnot_x_1')), + AstNode(m_and, + AstLeaf('x_1'), + AstLeaf('bnot_x_0')))) + REPLACEMENT_PATTERN = AstNode(m_mul, AstLeaf("x_0"), AstLeaf("x_1")) + + def check_candidate(self, candidate): + if not equal_bnot_mop(candidate["x_0"].mop, candidate["bnot_x_0"].mop): + return False + if not equal_bnot_mop(candidate["x_1"].mop, candidate["bnot_x_1"].mop): + return False + return True + + +class Mul_MbaRule_2(PatternMatchingRule): + PATTERN = AstNode(m_add, + AstNode(m_mul, + AstNode(m_or, + AstLeaf('x_0'), + AstConstant('c_1')), + AstLeaf('x_0')), + AstNode(m_mul, + AstNode(m_and, + AstLeaf('x_0'), + AstConstant('bnot_c_1')), + AstNode(m_and, + AstConstant('c_1'), + AstLeaf('bnot_x_0')))) + REPLACEMENT_PATTERN = AstNode(m_mul, AstLeaf("x_0"), AstConstant('c_1')) + + def check_candidate(self, candidate): + if not is_check_mop(candidate["x_0"].mop): + return False + if candidate["c_1"].value & 0x1 != 1: + return False + if not equal_bnot_mop(candidate["c_1"].mop, candidate["bnot_c_1"].mop): + return False + if not equal_bnot_mop(candidate["x_0"].mop, candidate["bnot_x_0"].mop): + return False + return True + + +class Mul_MbaRule_3(PatternMatchingRule): + PATTERN = AstNode(m_add, + AstNode(m_mul, + AstNode(m_or, + AstLeaf('x_0'), + AstConstant('c_1')), + AstNode(m_and, + AstLeaf('x_0'), + AstConstant('c_1'))), + AstNode(m_mul, + AstLeaf('x_0'), + AstNode(m_and, + AstConstant('c_1'), + AstLeaf('bnot_x_0')))) + REPLACEMENT_PATTERN = AstNode(m_mul, AstLeaf("x_0"), AstConstant('c_1')) + + def check_candidate(self, candidate): + if not is_check_mop(candidate["x_0"].mop): + return False + if candidate["c_1"].value & 0x1 == 1: + return False + if not equal_bnot_mop(candidate["x_0"].mop, candidate["bnot_x_0"].mop): + return False + return True + + +class Mul_MbaRule_4(PatternMatchingRule): + PATTERN = AstNode(m_add, + AstNode(m_mul, + AstNode(m_or, + AstLeaf("x_0"), + AstLeaf("x_1")), + AstNode(m_and, + AstLeaf("x_0"), + AstLeaf("x_1"))), + AstNode(m_mul, + AstNode(m_bnot, + AstNode(m_or, + AstLeaf("x_0"), + AstLeaf("bnot_x_1"))), + AstNode(m_and, + AstLeaf("x_0"), + AstLeaf("bnot_x_1")))) + REPLACEMENT_PATTERN = AstNode(m_mul, AstLeaf("x_0"), AstLeaf("x_1")) + + def check_candidate(self, candidate): + if not equal_bnot_mop(candidate["x_1"].mop, candidate["bnot_x_1"].mop): + return False + return True + + +class Mul_FactorRule_1(PatternMatchingRule): + PATTERN = AstNode(m_add, + AstConstant("2", 2), + AstNode(m_mul, + AstConstant("2", 2), + AstNode(m_add, + AstLeaf("x_1"), + AstNode(m_or, + AstLeaf("x_0"), + AstLeaf("bnot_x_1"))))) + + REPLACEMENT_PATTERN = AstNode(m_mul, + AstConstant("2", 2), + AstNode(m_and, + AstLeaf("x_0"), + AstLeaf("x_1"))) + + def check_candidate(self, candidate): + if not equal_bnot_mop(candidate["x_1"].mop, candidate["bnot_x_1"].mop): + return False + return True + + +class Mul_FactorRule_2(PatternMatchingRule): + PATTERN = AstNode(m_sub, + AstNode(m_neg, + AstNode(m_and, + AstLeaf("x_0"), + AstLeaf("x_1"))), + AstNode(m_and, + AstLeaf("x_0"), + AstLeaf("x_1"))) + REPLACEMENT_PATTERN = AstNode(m_mul, + AstConstant("val_fe"), + AstNode(m_and, + AstLeaf("x_0"), + AstLeaf("x_1"))) + + def check_candidate(self, candidate): + candidate.add_constant_leaf("val_fe", SUB_TABLE[candidate.size] - 2, candidate.size) + return True \ No newline at end of file diff --git a/d810/optimizers/instructions/pattern_matching/rewrite_neg.py b/d810/optimizers/instructions/pattern_matching/rewrite_neg.py new file mode 100644 index 0000000..4d2a5a3 --- /dev/null +++ b/d810/optimizers/instructions/pattern_matching/rewrite_neg.py @@ -0,0 +1,126 @@ +from ida_hexrays import * + +from d810.optimizers.instructions.pattern_matching.handler import PatternMatchingRule +from d810.hexrays_helpers import AND_TABLE +from d810.ast import AstLeaf, AstConstant, AstNode + + +class Neg_HackersDelightRule_1(PatternMatchingRule): + PATTERN = AstNode(m_add, + AstNode(m_bnot, + AstLeaf("x_0")), + AstConstant("1", 1)) + REPLACEMENT_PATTERN = AstNode(m_neg, AstLeaf("x_0")) + + +class Neg_HackersDelightRule_2(PatternMatchingRule): + PATTERN = AstNode(m_bnot, + AstNode(m_sub, + AstLeaf("x_0"), + AstConstant("1", 1))) + REPLACEMENT_PATTERN = AstNode(m_neg, AstLeaf("x_0")) + + +class NegAdd_HackersDelightRule_1(PatternMatchingRule): + PATTERN = AstNode(m_sub, + AstNode(m_xor, + AstLeaf("x_0"), + AstLeaf("x_1")), + AstNode(m_mul, + AstConstant("2", 2), + AstNode(m_or, + AstLeaf("x_0"), + AstLeaf("x_1")))) + REPLACEMENT_PATTERN = AstNode(m_neg, + AstNode(m_add, + AstLeaf("x_0"), + AstLeaf("x_1"))) + + +class NegAdd_HackersDelightRule_2(PatternMatchingRule): + PATTERN = AstNode(m_sub, + AstNode(m_xor, + AstLeaf("x_0"), + AstNode(m_or, + AstLeaf("x_1"), + AstLeaf("x_2"))), + AstNode(m_mul, + AstConstant("2", 2), + AstNode(m_or, + AstNode(m_or, + AstLeaf("x_0"), + AstLeaf("x_1")), + AstLeaf("x_2")))) + REPLACEMENT_PATTERN = AstNode(m_neg, + AstNode(m_add, + AstLeaf("x_0"), + AstNode(m_or, + AstLeaf("x_1"), + AstLeaf("x_2")))) + + +class NegAdd_HackersDelightRule_1(PatternMatchingRule): + PATTERN = AstNode(m_add, + AstNode(m_mul, + AstConstant('val_fe'), + AstNode(m_or, + AstLeaf('x_0'), + AstLeaf('x_1'))), + AstNode(m_xor, + AstLeaf('x_0'), + AstLeaf('x_1'))) + + REPLACEMENT_PATTERN = AstNode(m_neg, + AstNode(m_add, + AstLeaf("x_0"), + AstLeaf("x_1"))) + + + def check_candidate(self, candidate): + if (candidate["val_fe"].value + 2) & AND_TABLE[candidate["val_fe"].size] != 0: + return False + return True + +class NegOr_HackersDelightRule_1(PatternMatchingRule): + PATTERN = AstNode(m_sub, + AstNode(m_and, + AstLeaf("x_0"), + AstLeaf("x_1")), + AstNode(m_add, + AstLeaf("x_0"), + AstLeaf("x_1"))) + + REPLACEMENT_PATTERN = AstNode(m_neg, + AstNode(m_or, + AstLeaf("x_0"), + AstLeaf("x_1"))) + + +class NegXor_HackersDelightRule_1(PatternMatchingRule): + PATTERN = AstNode(m_sub, + AstNode(m_and, + AstLeaf('x_0'), + AstLeaf('x_1')), + AstNode(m_or, + AstLeaf('x_0'), + AstLeaf('x_1'))) + REPLACEMENT_PATTERN = AstNode(m_neg, + AstNode(m_xor, + AstLeaf("x_0"), + AstLeaf("x_1"))) + + +class NegXor_HackersDelightRule_2(PatternMatchingRule): + PATTERN = AstNode(m_sub, + AstNode(m_add, + AstLeaf('x_0'), + AstLeaf('x_1')), + AstNode(m_mul, + AstConstant('2', 2), + AstNode(m_or, + AstLeaf('x_0'), + AstLeaf('x_1')))) + REPLACEMENT_PATTERN = AstNode(m_neg, + AstNode(m_xor, + AstLeaf("x_0"), + AstLeaf("x_1"))) diff --git a/d810/optimizers/instructions/pattern_matching/rewrite_or.py b/d810/optimizers/instructions/pattern_matching/rewrite_or.py new file mode 100644 index 0000000..58564c7 --- /dev/null +++ b/d810/optimizers/instructions/pattern_matching/rewrite_or.py @@ -0,0 +1,259 @@ +from ida_hexrays import * + +from d810.optimizers.instructions.pattern_matching.handler import PatternMatchingRule +from d810.ast import AstLeaf, AstConstant, AstNode +from d810.hexrays_helpers import equal_bnot_mop + + +class Or_HackersDelightRule_1(PatternMatchingRule): + PATTERN = AstNode(m_add, + AstNode(m_and, + AstLeaf("x_0"), + AstLeaf("bnot_x_1")), + AstLeaf("x_1")) + + REPLACEMENT_PATTERN = AstNode(m_or, AstLeaf("x_0"), AstLeaf("x_1")) + + def check_candidate(self, candidate): + if not equal_bnot_mop(candidate["x_1"].mop, candidate["bnot_x_1"].mop): + return False + return True + + +class Or_HackersDelightRule_2(PatternMatchingRule): + PATTERN = AstNode(m_sub, + AstNode(m_add, + AstLeaf("x_0"), + AstLeaf("x_1")), + AstNode(m_and, + AstLeaf("x_0"), + AstLeaf("x_1"))) + + REPLACEMENT_PATTERN = AstNode(m_or, AstLeaf("x_0"), AstLeaf("x_1")) + + +class Or_HackersDelightRule_2_variant_1(PatternMatchingRule): + PATTERN = AstNode(m_sub, + AstNode(m_sub, + AstLeaf("x_0"), + AstLeaf("x_1")), + AstNode(m_and, + AstLeaf("x_0"), + AstNode(m_neg, AstLeaf("x_1")))) + REPLACEMENT_PATTERN = AstNode(m_or, + AstLeaf("x_0"), + AstNode(m_neg, AstLeaf("x_1"))) + + +class Or_MbaRule_1(PatternMatchingRule): + PATTERN = AstNode(m_add, + AstNode(m_and, + AstLeaf("x_0"), + AstLeaf("x_1")), + AstNode(m_xor, + AstLeaf("x_0"), + AstLeaf("x_1"))) + REPLACEMENT_PATTERN = AstNode(m_or, AstLeaf("x_0"), AstLeaf("x_1")) + + +class Or_MbaRule_2(PatternMatchingRule): + PATTERN = AstNode(m_add, + AstNode(m_add, + AstNode(m_add, + AstLeaf('x_0'), + AstLeaf('x_1')), + AstConstant('1', 1)), + AstNode(m_bnot, + AstNode(m_and, + AstLeaf('x_1'), + AstLeaf('x_0')))) + REPLACEMENT_PATTERN = AstNode(m_or, AstLeaf("x_0"), AstLeaf("x_1")) + + +class Or_MbaRule_3(PatternMatchingRule): + PATTERN = AstNode(m_sub, + AstNode(m_add, + AstLeaf('x_0'), + AstNode(m_xor, + AstLeaf('x_0'), + AstLeaf('x_1'))), + AstNode(m_and, + AstLeaf('x_0'), + AstNode(m_bnot, + AstLeaf('x_1')))) + REPLACEMENT_PATTERN = AstNode(m_or, AstLeaf("x_0"), AstLeaf("x_1")) + + +class Or_FactorRule_1(PatternMatchingRule): + PATTERN = AstNode(m_or, + AstNode(m_and, + AstLeaf("x_0"), + AstLeaf("x_1")), + AstNode(m_xor, + AstLeaf("x_0"), + AstLeaf("x_1"))) + REPLACEMENT_PATTERN = AstNode(m_or, AstLeaf("x_0"), AstLeaf("x_1")) + + +class Or_FactorRule_2(PatternMatchingRule): + PATTERN = AstNode(m_or, + AstNode(m_and, + AstLeaf("x_0"), + AstNode(m_xor, + AstLeaf("x_1"), + AstLeaf("x_2"))), + AstNode(m_xor, + AstNode(m_xor, + AstLeaf("x_0"), + AstLeaf("x_1")), + AstLeaf("x_2"))) + REPLACEMENT_PATTERN = AstNode(m_or, AstLeaf("x_0"), AstNode(m_xor, AstLeaf("x_1"), AstLeaf("x_2"))) + + +class Or_FactorRule_3(PatternMatchingRule): + PATTERN = AstNode(m_or, + AstNode(m_or, + AstLeaf("x_0"), + AstLeaf("x_1")), + AstNode(m_xor, + AstLeaf("bnot_x_0"), + AstLeaf("bnot_x_1"))) + + REPLACEMENT_PATTERN = AstNode(m_or, AstLeaf("x_0"), AstLeaf("x_1")) + + def check_candidate(self, candidate): + if not equal_bnot_mop(candidate["x_0"].mop, candidate["bnot_x_0"].mop): + return False + if not equal_bnot_mop(candidate["x_1"].mop, candidate["bnot_x_1"].mop): + return False + return True + + +class Or_OllvmRule_1(PatternMatchingRule): + PATTERN = AstNode(m_or, + AstNode(m_and, + AstLeaf("x_0"), + AstLeaf("x_1")), + AstNode(m_bnot, + AstNode(m_xor, + AstLeaf("bnot_x_0"), + AstLeaf("x_1")))) + + REPLACEMENT_PATTERN = AstNode(m_or, AstLeaf("x_0"), AstLeaf("x_1")) + + def check_candidate(self, candidate): + if not equal_bnot_mop(candidate["x_0"].mop, candidate["bnot_x_0"].mop): + return False + return True + + +class Or_Rule_1(PatternMatchingRule): + PATTERN = AstNode(m_or, + AstNode(m_and, + AstLeaf("bnot_x_0"), + AstLeaf("x_1")), + AstLeaf("x_0")) + + REPLACEMENT_PATTERN = AstNode(m_or, AstLeaf("x_0"), AstLeaf("x_1")) + + def check_candidate(self, candidate): + if not equal_bnot_mop(candidate["x_0"].mop, candidate["bnot_x_0"].mop): + return False + return True + + +class Or_Rule_2(PatternMatchingRule): + PATTERN = AstNode(m_or, + AstNode(m_xor, + AstLeaf("x_0"), + AstLeaf("x_1")), + AstLeaf("x_1")) + + REPLACEMENT_PATTERN = AstNode(m_or, AstLeaf("x_0"), AstLeaf("x_1")) + + +class Or_Rule_3(PatternMatchingRule): + PATTERN = AstNode(m_or, + AstNode(m_bnot, + AstNode(m_or, + AstLeaf('bnot_x_0'), + AstLeaf('bnot_x_1'))), + AstNode(m_xor, + AstLeaf('x_0'), + AstLeaf('x_1'))) + + REPLACEMENT_PATTERN = AstNode(m_or, AstLeaf("x_0"), AstLeaf("x_1")) + + def check_candidate(self, candidate): + if not equal_bnot_mop(candidate["x_0"].mop, candidate["bnot_x_0"].mop): + return False + if not equal_bnot_mop(candidate["x_1"].mop, candidate["bnot_x_1"].mop): + return False + return True + + +class Or_Rule_4(PatternMatchingRule): + PATTERN = AstNode(m_xor, + AstNode(m_and, + AstLeaf("x_0"), + AstLeaf("x_1")), + AstNode(m_xor, + AstLeaf("x_0"), + AstLeaf("x_1"))) + + REPLACEMENT_PATTERN = AstNode(m_or, AstLeaf("x_0"), AstLeaf("x_1")) + + +class OrBnot_FactorRule_1(PatternMatchingRule): + PATTERN = AstNode(m_xor, + AstNode(m_bnot, + AstLeaf("x_0")), + AstNode(m_and, + AstLeaf("x_0"), + AstLeaf("x_1"))) + REPLACEMENT_PATTERN = AstNode(m_or, + AstNode(m_bnot, + AstLeaf("x_0")), + AstLeaf("x_1")) + + +class OrBnot_FactorRule_2(PatternMatchingRule): + PATTERN = AstNode(m_xor, + AstLeaf("x_0"), + AstNode(m_and, + AstNode(m_bnot, + AstLeaf("x_0")), + AstLeaf("x_1"))) + REPLACEMENT_PATTERN = AstNode(m_or, AstLeaf("x_0"), AstLeaf("x_1")) + + +class OrBnot_FactorRule_3(PatternMatchingRule): + PATTERN = AstNode(m_add, + AstNode(m_sub, + AstLeaf("x_0"), + AstLeaf("x_1")), + AstNode(m_or, + AstLeaf("bnot_x_0"), + AstLeaf("x_1"))) + REPLACEMENT_PATTERN = AstNode(m_or, AstLeaf("x_0"), AstNode(m_bnot, AstLeaf("x_1"))) + + def check_candidate(self, candidate): + if not equal_bnot_mop(candidate["x_0"].mop, candidate["bnot_x_0"].mop): + return False + return True + + +class OrBnot_FactorRule_4(PatternMatchingRule): + PATTERN = AstNode(m_xor, + AstNode(m_or, + AstLeaf("bnot_x_0"), + AstLeaf("x_1")), + AstNode(m_xor, + AstLeaf("x_0"), + AstLeaf("x_1"))) + REPLACEMENT_PATTERN = AstNode(m_or, AstLeaf("x_0"), AstNode(m_bnot, AstLeaf("x_1"))) + + def check_candidate(self, candidate): + if not equal_bnot_mop(candidate["x_0"].mop, candidate["bnot_x_0"].mop): + return False + return True diff --git a/d810/optimizers/instructions/pattern_matching/rewrite_predicates.py b/d810/optimizers/instructions/pattern_matching/rewrite_predicates.py new file mode 100644 index 0000000..688c5d2 --- /dev/null +++ b/d810/optimizers/instructions/pattern_matching/rewrite_predicates.py @@ -0,0 +1,403 @@ +from ida_hexrays import * + +from d810.optimizers.instructions.pattern_matching.handler import PatternMatchingRule +from d810.ast import AstLeaf, AstConstant, AstNode +from d810.hexrays_helpers import equal_bnot_mop, SUB_TABLE, AND_TABLE + + +# PredSetnzRule1: (x_0 | c_1) != c_2 ==> 1 if c_1 | c_2 != c_2 +class PredSetnzRule1(PatternMatchingRule): + PATTERN = AstNode(m_setnz, + AstNode(m_or, + AstLeaf("x_0"), + AstConstant("c_1")), + AstConstant("c_2")) + REPLACEMENT_PATTERN = AstNode(m_mov, AstConstant("val_1")) + + def check_candidate(self, candidate): + if (candidate["c_1"].value | candidate["c_2"].value) == candidate["c_2"].value: + return False + candidate.add_constant_leaf("val_1", 1, candidate.size) + return True + + +# PredSetnzRule2: (x_0 & c_1) != c_2 ==> 1 if c_1 & c_2 != c_2 +class PredSetnzRule2(PatternMatchingRule): + PATTERN = AstNode(m_setnz, + AstNode(m_and, + AstLeaf("x_0"), + AstConstant("c_1")), + AstConstant("c_2")) + REPLACEMENT_PATTERN = AstNode(m_mov, AstConstant("val_1")) + + def check_candidate(self, candidate): + if (candidate["c_1"].value & candidate["c_2"].value) == candidate["c_2"].value: + return False + candidate.add_constant_leaf("val_1", 1, candidate.size) + return True + + +# PredSetnzRule3: (x_0 | 2) + (x_0 ^ 2) != 0 ==> 1 (because math) +class PredSetnzRule3(PatternMatchingRule): + PATTERN = AstNode(m_setnz, + AstNode(m_add, + AstNode(m_or, + AstLeaf("x_0"), + AstConstant("2", 2)), + AstNode(m_xor, + AstLeaf("x_0"), + AstConstant("2", 2))), + AstConstant("0", 0)) + REPLACEMENT_PATTERN = AstNode(m_mov, AstConstant("val_1")) + + def check_candidate(self, candidate): + candidate.add_constant_leaf("val_1", 1, candidate.size) + return True + + +# PredSetnzRule4: (cst_1 - x_0) ^ x_0 != 0 ==> 1 if cst_1 % 2 == 1 (because math) +class PredSetnzRule4(PatternMatchingRule): + PATTERN = AstNode(m_setnz, + AstNode(m_xor, + AstNode(m_sub, + AstConstant("cst_1"), + AstLeaf("x_0")), + AstLeaf("x_0")), + AstConstant("0", 0)) + REPLACEMENT_PATTERN = AstNode(m_mov, AstConstant("val_1")) + + def check_candidate(self, candidate): + if (candidate["cst_1"].value % 2) == 0: + return False + candidate.add_constant_leaf("val_1", 1, candidate.size) + return True + + +# PredSetnzRule5: (-(~x_0 & 1)) != x_0 ==> 1 (because math) +class PredSetnzRule5(PatternMatchingRule): + PATTERN = AstNode(m_setnz, + AstNode(m_neg, + AstNode(m_and, + AstNode(m_bnot, + AstLeaf("x_0")), + AstConstant("1", 1))), + AstLeaf("x_0")) + REPLACEMENT_PATTERN = AstNode(m_mov, AstConstant("val_1")) + + def check_candidate(self, candidate): + candidate.add_constant_leaf("val_1", 1, candidate.size) + return True + + +# PredSetnzRule6: ((x_0 + c_1) + ((x_0 + c_2) & 1)) != 0 ==> 1 (if (c_2 - c_1) & 1 == 1) +class PredSetnzRule6(PatternMatchingRule): + PATTERN = AstNode(m_setnz, + AstNode(m_add, + AstNode(m_add, + AstLeaf("x_0"), + AstConstant("c_1")), + AstNode(m_and, + AstNode(m_add, + AstLeaf("x_0"), + AstConstant("c_2")), + AstConstant("1", 1))), + AstConstant("0", 0)) + REPLACEMENT_PATTERN = AstNode(m_mov, AstConstant("val_1")) + + def check_candidate(self, candidate): + if (candidate["c_2"].value - candidate["c_1"].value) & 0x1 != 1: + return False + candidate.add_constant_leaf("val_1", 1, candidate.size) + return True + + +# PredSetnzRule8: bnot((3 - x_0)) ^ bnot(x_0) != 0 ==> 1 +class PredSetnzRule8(PatternMatchingRule): + PATTERN = AstNode(m_setnz, + AstNode(m_xor, + AstNode(m_bnot, + AstNode(m_sub, + AstConstant("3", 3), + AstLeaf("x_0"))), + AstNode(m_bnot, + AstLeaf("x_0"))), + AstConstant("0", 0)) + REPLACEMENT_PATTERN = AstNode(m_mov, AstConstant("val_1")) + + def check_candidate(self, candidate): + candidate.add_constant_leaf("val_1", 1, candidate.size) + return True + + +# PredSetzRule1: (x_0 | c_1) == c_2 ==> 0 if c_1 | c_2 != c_2 +class PredSetzRule1(PatternMatchingRule): + PATTERN = AstNode(m_setz, + AstNode(m_or, + AstLeaf("x_0"), + AstConstant("c_1")), + AstConstant("c_2")) + REPLACEMENT_PATTERN = AstNode(m_mov, AstConstant("val_0")) + + def check_candidate(self, candidate): + if (candidate["c_1"].value | candidate["c_2"].value) == candidate["c_2"].value: + return False + candidate.add_constant_leaf("val_0", 0, candidate.size) + return True + + +# PredSetzRule2: (x_0 & c_1) == c_2 ==> 0 if c_1 & c_2 != c_2 +class PredSetzRule2(PatternMatchingRule): + PATTERN = AstNode(m_setz, + AstNode(m_and, + AstLeaf("x_0"), + AstConstant("c_1")), + AstConstant("c_2")) + REPLACEMENT_PATTERN = AstNode(m_mov, AstConstant("val_0")) + + def check_candidate(self, candidate): + if (candidate["c_1"].value & candidate["c_2"].value) == candidate["c_2"].value: + return False + candidate.add_constant_leaf("val_0", 0, candidate.size) + return True + + +# PredSetzRule3: (x_0 | 2) + (x_0 ^ 2) == 0 ==> 0 (because math) +class PredSetzRule3(PatternMatchingRule): + PATTERN = AstNode(m_setz, + AstNode(m_add, + AstNode(m_or, + AstLeaf("x_0"), + AstConstant("2", 2)), + AstNode(m_xor, + AstLeaf("x_0"), + AstConstant("2", 2))), + AstConstant("0", 0)) + REPLACEMENT_PATTERN = AstNode(m_mov, AstConstant("val_0")) + + def check_candidate(self, candidate): + candidate.add_constant_leaf("val_0", 0, candidate.size) + return True + + +# PredSetbRule1: (x_0 & c_1) 0 if c_1 = candidate["c_2"].value: + return False + candidate.add_constant_leaf("val_0", 0, candidate.size) + return True + + +class PredOdd1(PatternMatchingRule): + PATTERN = AstNode(m_and, + AstNode(m_mul, + AstLeaf('x_0'), + AstNode(m_sub, + AstLeaf('x_0'), + AstConstant('1', 1))), + AstConstant('1', 1)) + REPLACEMENT_PATTERN = AstNode(m_mov, AstConstant('val_0')) + + def check_candidate(self, candidate): + candidate.add_constant_leaf("val_0", 0, candidate.size) + return True + + +class PredOdd2(PatternMatchingRule): + PATTERN = AstNode(m_and, + AstNode(m_mul, + AstLeaf('x_0'), + AstNode(m_add, + AstLeaf('x_0'), + AstConstant('1', 1))), + AstConstant('1', 1)) + REPLACEMENT_PATTERN = AstNode(m_mov, AstConstant('val_0')) + + def check_candidate(self, candidate): + candidate.add_constant_leaf("val_0", 0, candidate.size) + return True + + +# Pred0Rule1: (x_0 & ~x_0) ==> 0 +class Pred0Rule1(PatternMatchingRule): + PATTERN = AstNode(m_and, + AstLeaf("x_0"), + AstNode(m_bnot, + AstLeaf("x_0"))) + REPLACEMENT_PATTERN = AstNode(m_mov, AstConstant("val_0")) + + def check_candidate(self, candidate): + candidate.add_constant_leaf("val_0", 0, candidate.size) + return True + + +# Pred0Rule2: (xdu(x_0 & 1) == 2) ==> 0 +class Pred0Rule2(PatternMatchingRule): + PATTERN = AstNode(m_setz, + AstNode(m_xdu, + AstNode(m_and, + AstLeaf("x_0"), + AstConstant("c_1", 1))), + AstConstant("c_2", 2)) + REPLACEMENT_PATTERN = AstNode(m_mov, AstConstant("val_0")) + + def check_candidate(self, candidate): + candidate.add_constant_leaf("val_0", 0, candidate.size) + return True + + +class Pred0Rule3(PatternMatchingRule): + PATTERN = AstNode(m_and, + AstLeaf("x_0"), + AstNode(m_bnot, + AstNode(m_or, + AstLeaf("x_0"), + AstLeaf("x_1")))) + REPLACEMENT_PATTERN = AstNode(m_mov, AstLeaf("val_0")) + + def check_candidate(self, candidate): + candidate.add_constant_leaf("val_0", 0, candidate.size) + return True + + +class Pred0Rule4(PatternMatchingRule): + PATTERN = AstNode(m_and, + AstNode(m_and, + AstLeaf('x_0'), + AstLeaf('x_1')), + AstNode(m_bnot, + AstNode(m_or, + AstLeaf('x_0'), + AstLeaf('x_1')))) + REPLACEMENT_PATTERN = AstNode(m_mov, AstLeaf("val_0")) + + def check_candidate(self, candidate): + candidate.add_constant_leaf("val_0", 0, candidate.size) + return True + + +class Pred0Rule5(PatternMatchingRule): + PATTERN = AstNode(m_and, + AstNode(m_and, + AstLeaf('x_0'), + AstLeaf('x_1')), + AstNode(m_xor, + AstLeaf('x_0'), + AstLeaf('x_1'))) + REPLACEMENT_PATTERN = AstNode(m_mov, AstLeaf("val_0")) + + def check_candidate(self, candidate): + candidate.add_constant_leaf("val_0", 0, candidate.size) + return True + + +class PredFFRule1(PatternMatchingRule): + PATTERN = AstNode(m_or, + AstLeaf("x_0"), + AstNode(m_bnot, + AstLeaf("x_0"))) + REPLACEMENT_PATTERN = AstNode(m_mov, AstConstant("val_ff")) + + def check_candidate(self, candidate): + candidate.add_constant_leaf("val_ff", AND_TABLE[candidate.size], candidate.size) + return True + + +# Pred1Rule2: (x_0 ^ x_1) | (~x_0 | x_1) ==> 0xff +class PredFFRule2(PatternMatchingRule): + PATTERN = AstNode(m_or, + AstNode(m_xor, + AstLeaf("x_0"), + AstLeaf("x_1")), + AstNode(m_or, + AstLeaf("bnot_x_0"), + AstLeaf("x_1"))) + REPLACEMENT_PATTERN = AstNode(m_mov, AstConstant("val_ff")) + + def check_candidate(self, candidate): + if not equal_bnot_mop(candidate["x_0"].mop, candidate["bnot_x_0"].mop): + return False + candidate.add_constant_leaf("val_ff", AND_TABLE[candidate.size], candidate.size) + return True + + +class PredFFRule3(PatternMatchingRule): + PATTERN = AstNode(m_or, + AstLeaf("x_0"), + AstNode(m_bnot, + AstNode(m_and, + AstLeaf("x_0"), + AstLeaf("x_1")))) + + REPLACEMENT_PATTERN = AstNode(m_mov, AstLeaf("val_ff")) + + def check_candidate(self, candidate): + candidate.add_constant_leaf("val_ff", AND_TABLE[candidate.size], candidate.size) + return True + + +class PredFFRule4(PatternMatchingRule): + DESCRIPTION = "(x_0 | x_1) | (~(x_0 & x_1)) ==> 0xff" + PATTERN = AstNode(m_or, + AstNode(m_or, + AstLeaf('x_0'), + AstLeaf('x_1')), + AstNode(m_bnot, + AstNode(m_and, + AstLeaf('x_0'), + AstLeaf('x_1')))) + REPLACEMENT_PATTERN = AstNode(m_mov, AstConstant("val_ff")) + + def check_candidate(self, candidate): + candidate.add_constant_leaf("val_ff", AND_TABLE[candidate.size], candidate.size) + return True + + +class PredOr2_Rule_1(PatternMatchingRule): + PATTERN = AstNode(m_and, + AstNode(m_bnot, + AstNode(m_mul, + AstLeaf('x_0'), + AstLeaf('x_0'))), + AstConstant('3', 3)) + REPLACEMENT_PATTERN = AstNode(m_or, + AstNode(m_and, + AstNode(m_bnot, AstLeaf('x_0')), + AstConstant('val_1')), + AstConstant('val_2')) + + def check_candidate(self, candidate): + candidate.add_constant_leaf("val_1", 1, candidate["x_0"].mop.size) + candidate.add_constant_leaf("val_2", 2, candidate["x_0"].mop.size) + return True + + +class PredOr1_Rule_1(PatternMatchingRule): + PATTERN = AstNode(m_xor, + AstLeaf('x_0'), + AstNode(m_add, + AstNode(m_and, + AstLeaf('x_0'), + AstConstant('1', 1)), + AstConstant('1', 1))) + REPLACEMENT_PATTERN = AstNode(m_or, + AstNode(m_xor, + AstLeaf('x_0'), + AstNode(m_mul, + AstConstant('val_2'), + AstNode(m_and, + AstLeaf('x_0'), + AstConstant('val_1')))), + AstConstant('val_1')) + + def check_candidate(self, candidate): + candidate.add_constant_leaf("val_1", 1, candidate["x_0"].mop.size) + candidate.add_constant_leaf("val_2", 2, candidate["x_0"].mop.size) + return True diff --git a/d810/optimizers/instructions/pattern_matching/rewrite_sub.py b/d810/optimizers/instructions/pattern_matching/rewrite_sub.py new file mode 100644 index 0000000..27dc850 --- /dev/null +++ b/d810/optimizers/instructions/pattern_matching/rewrite_sub.py @@ -0,0 +1,177 @@ +from ida_hexrays import * + +from d810.optimizers.instructions.pattern_matching.handler import PatternMatchingRule +from d810.ast import AstLeaf, AstConstant, AstNode +from d810.hexrays_helpers import equal_bnot_mop, SUB_TABLE + + +class Sub_HackersDelightRule_1(PatternMatchingRule): + PATTERN = AstNode(m_add, + AstLeaf("x_0"), + AstNode(m_add, + AstNode(m_bnot, + AstLeaf("x_1")), + AstConstant("1", 1))) + REPLACEMENT_PATTERN = AstNode(m_sub, AstLeaf("x_0"), AstLeaf("x_1")) + + +class Sub_HackersDelightRule_2(PatternMatchingRule): + PATTERN = AstNode(m_sub, + AstNode(m_xor, + AstLeaf("x_0"), + AstLeaf("x_1")), + AstNode(m_mul, + AstConstant("2", 2), + AstNode(m_and, + AstNode(m_bnot, + AstLeaf("x_0")), + AstLeaf("x_1")))) + REPLACEMENT_PATTERN = AstNode(m_sub, AstLeaf("x_0"), AstLeaf("x_1")) + + +class Sub_HackersDelightRule_3(PatternMatchingRule): + PATTERN = AstNode(m_sub, + AstNode(m_and, + AstLeaf("x_0"), + AstLeaf("bnot_x_1")), + AstNode(m_and, + AstLeaf("bnot_x_0"), + AstLeaf("x_1"))) + REPLACEMENT_PATTERN = AstNode(m_sub, AstLeaf("x_0"), AstLeaf("x_1")) + + def check_candidate(self, candidate): + if not equal_bnot_mop(candidate["x_0"].mop, candidate["bnot_x_0"].mop): + return False + if not equal_bnot_mop(candidate["x_1"].mop, candidate["bnot_x_1"].mop): + return False + return True + + +class Sub_HackersDelightRule_4(PatternMatchingRule): + PATTERN = AstNode(m_sub, + AstNode(m_mul, + AstConstant("2", 2), + AstNode(m_and, + AstLeaf("x_0"), + AstLeaf("bnot_x_1"))), + AstNode(m_xor, + AstLeaf("x_0"), + AstLeaf("x_1"))) + REPLACEMENT_PATTERN = AstNode(m_sub, AstLeaf("x_0"), AstLeaf("x_1")) + + def check_candidate(self, candidate): + if not equal_bnot_mop(candidate["x_1"].mop, candidate["bnot_x_1"].mop): + return False + return True + + +class Sub1_FactorRule_1(PatternMatchingRule): + PATTERN = AstNode(m_sub, + AstNode(m_sub, + AstNode(m_neg, + AstLeaf('x_0')), + AstConstant('1', 1)), + AstNode(m_mul, + AstConstant('c_minus_2'), + AstLeaf('x_0'))) + REPLACEMENT_PATTERN = AstNode(m_sub, AstLeaf("x_0"), AstConstant("val_1")) + + def check_candidate(self, candidate): + if candidate["c_minus_2"].value != SUB_TABLE[candidate["c_minus_2"].size] - 2: + return False + candidate.add_constant_leaf("val_1", 1, candidate["x_0"].size) + return True + + +class Sub1_FactorRule_2(PatternMatchingRule): + PATTERN = AstNode(m_add, + AstNode(m_mul, + AstConstant("2", 2), + AstLeaf("x_0")), + AstNode(m_bnot, + AstLeaf("x_0"))) + + REPLACEMENT_PATTERN = AstNode(m_sub, AstLeaf("x_0"), AstConstant("1", 1)) + + +class Sub1Add_HackersDelightRule_1(PatternMatchingRule): + PATTERN = AstNode(m_add, + AstNode(m_mul, + AstConstant("2", 2), + AstNode(m_or, + AstLeaf("x_0"), + AstLeaf("x_1"))), + AstNode(m_xor, + AstLeaf("x_0"), + AstLeaf("bnot_x_1"))) + REPLACEMENT_PATTERN = AstNode(m_sub, + AstNode(m_add, + AstLeaf("x_0"), + AstLeaf("x_1")), + AstConstant("val_1")) + + def check_candidate(self, candidate): + if not equal_bnot_mop(candidate["x_1"].mop, candidate["bnot_x_1"].mop): + return False + candidate.add_constant_leaf("val_1", 1, candidate["x_1"].size) + return True + + +class Sub1And_HackersDelightRule_1(PatternMatchingRule): + PATTERN = AstNode(m_add, + AstNode(m_or, + AstLeaf("x_0"), + AstLeaf("bnot_x_1")), + AstLeaf("x_1")) + + REPLACEMENT_PATTERN = AstNode(m_sub, + AstNode(m_and, + AstLeaf("x_0"), + AstLeaf("x_1")), + AstConstant("val_1")) + + def check_candidate(self, candidate): + if not equal_bnot_mop(candidate["x_1"].mop, candidate["bnot_x_1"].mop): + return False + candidate.add_constant_leaf("val_1", 1, candidate["x_0"].size) + return True + + +class Sub1Or_MbaRule_1(PatternMatchingRule): + PATTERN = AstNode(m_add, + AstNode(m_add, + AstLeaf("x_0"), + AstLeaf("x_1")), + AstNode(m_bnot, + AstNode(m_and, + AstLeaf("x_0"), + AstLeaf("x_1")))) + REPLACEMENT_PATTERN = AstNode(m_sub, + AstNode(m_or, + AstLeaf("x_0"), + AstLeaf("x_1")), + AstConstant("val_1")) + + + def check_candidate(self, candidate): + candidate.add_constant_leaf("val_1", 1, candidate.size) + return True + + +class Sub1And1_MbaRule_1(PatternMatchingRule): + PATTERN = AstNode(m_add, + AstNode(m_or, + AstNode(m_bnot, + AstLeaf('x_0')), + AstConstant("1", 1)), + AstLeaf('x_0')) + REPLACEMENT_PATTERN = AstNode(m_sub, + AstNode(m_and, + AstLeaf('x_0'), + AstConstant("val_1_1")), + AstConstant("val_1_2")) + + def check_candidate(self, candidate): + candidate.add_constant_leaf("val_1_1", 1, candidate["x_0"].size) + candidate.add_constant_leaf("val_1_2", 1, candidate["x_0"].size) + return True diff --git a/d810/optimizers/instructions/pattern_matching/rewrite_xor.py b/d810/optimizers/instructions/pattern_matching/rewrite_xor.py new file mode 100644 index 0000000..a06b7e8 --- /dev/null +++ b/d810/optimizers/instructions/pattern_matching/rewrite_xor.py @@ -0,0 +1,325 @@ +from ida_hexrays import * + +from d810.optimizers.instructions.pattern_matching.handler import PatternMatchingRule +from d810.ast import AstLeaf, AstConstant, AstNode +from d810.hexrays_helpers import equal_bnot_mop, SUB_TABLE + + +class Xor_HackersDelightRule_1(PatternMatchingRule): + PATTERN = AstNode(m_sub, + AstNode(m_or, + AstLeaf("x_0"), + AstLeaf("x_1")), + AstNode(m_and, + AstLeaf("x_0"), + AstLeaf("x_1"))) + REPLACEMENT_PATTERN = AstNode(m_xor, AstLeaf("x_0"), AstLeaf("x_1")) + + +class Xor_HackersDelightRule_2(PatternMatchingRule): + PATTERN = AstNode(m_sub, + AstNode(m_mul, + AstConstant("2", 2), + AstNode(m_or, + AstLeaf("x_0"), + AstLeaf("x_1"))), + AstNode(m_add, + AstLeaf("x_0"), + AstLeaf("x_1"))) + + REPLACEMENT_PATTERN = AstNode(m_xor, AstLeaf("x_0"), AstLeaf("x_1")) + + +class Xor_HackersDelightRule_3(PatternMatchingRule): + PATTERN = AstNode(m_sub, + AstNode(m_add, + AstLeaf("x_0"), + AstLeaf("x_1")), + AstNode(m_mul, + AstConstant("2", 2), + AstNode(m_and, + AstLeaf("x_0"), + AstLeaf("x_1")))) + + REPLACEMENT_PATTERN = AstNode(m_xor, AstLeaf("x_0"), AstLeaf("x_1")) + + +class Xor_HackersDelightRule_4(PatternMatchingRule): + PATTERN = AstNode(m_sub, + AstNode(m_sub, + AstNode(m_sub, + AstLeaf('x_0'), + AstLeaf('x_1')), + AstNode(m_mul, + AstConstant('2', 2), + AstNode(m_or, + AstLeaf('x_0'), + AstNode(m_bnot, AstLeaf('x_1'))))), + AstConstant('2', 2)) + + REPLACEMENT_PATTERN = AstNode(m_xor, AstLeaf("x_0"), AstLeaf("x_1")) + + +class Xor_HackersDelightRule_5(PatternMatchingRule): + FUZZ_PATTERN = False + PATTERN = AstNode(m_sub, + AstLeaf("x_0"), + AstNode(m_sub, + AstNode(m_mul, + AstConstant('2', 2), + AstNode(m_and, + AstLeaf("x_0"), + AstLeaf("x_1"))), + AstLeaf("x_1"))) + PATTERNS = [ + AstNode(m_sub, AstLeaf("x_0"), AstNode(m_sub, AstNode(m_mul, AstConstant('2', 2), AstNode(m_and, AstLeaf("x_1"), AstLeaf("x_0"))), AstLeaf("x_1"))) + ] + + REPLACEMENT_PATTERN = AstNode(m_xor, AstLeaf("x_0"), AstLeaf("x_1")) + + +class Xor_MbaRule_1(PatternMatchingRule): + PATTERN = AstNode(m_sub, + AstLeaf('x_0'), + AstNode(m_sub, + AstNode(m_mul, + AstConstant('2', 2), + AstNode(m_and, + AstLeaf('x_1'), + AstNode(m_bnot, + AstNode(m_xor, + AstLeaf('x_0'), + AstLeaf('x_1'))))), + AstLeaf('x_1'))) + REPLACEMENT_PATTERN = AstNode(m_xor, AstLeaf("x_0"), AstLeaf("x_1")) + + +class Xor_MbaRule_2(PatternMatchingRule): + PATTERN = AstNode(m_sub, + AstLeaf('x_0'), + AstNode(m_sub, + AstNode(m_mul, + AstConstant('2', 2), + AstNode(m_and, + AstLeaf('x_0'), + AstLeaf('x_1'))), + AstLeaf('x_1'))) + REPLACEMENT_PATTERN = AstNode(m_xor, AstLeaf("x_0"), AstLeaf("x_1")) + + +class Xor_MbaRule_3(PatternMatchingRule): + PATTERN = AstNode(m_sub, + AstLeaf('x_0'), + AstNode(m_mul, + AstConstant('2', 2), + AstNode(m_and, + AstLeaf('x_0'), + AstLeaf('x_1')))) + REPLACEMENT_PATTERN = AstNode(m_sub, + AstNode(m_xor, + AstLeaf("x_0"), + AstLeaf("x_1")), + AstLeaf("x_1")) + + +class Xor_FactorRule_1(PatternMatchingRule): + PATTERN = AstNode(m_or, + AstNode(m_and, + AstLeaf("x_0"), + AstLeaf("bnot_x_1")), + AstNode(m_and, + AstLeaf("bnot_x_0"), + AstLeaf("x_1"))) + REPLACEMENT_PATTERN = AstNode(m_xor, AstLeaf("x_0"), AstLeaf("x_1")) + + def check_candidate(self, candidate): + if not equal_bnot_mop(candidate["x_0"].mop, candidate["bnot_x_0"].mop): + return False + if not equal_bnot_mop(candidate["x_1"].mop, candidate["bnot_x_1"].mop): + return False + return True + + +class Xor_FactorRule_2(PatternMatchingRule): + PATTERN = AstNode(m_xor, + AstNode(m_and, + AstLeaf('bnot_x_0'), + AstLeaf('x_1')), + AstNode(m_and, + AstLeaf('x_0'), + AstLeaf('bnot_x_1'))) + REPLACEMENT_PATTERN = AstNode(m_xor, AstLeaf("x_0"), AstLeaf("x_1")) + + def check_candidate(self, candidate): + if not equal_bnot_mop(candidate["x_0"].mop, candidate["bnot_x_0"].mop): + return False + if not equal_bnot_mop(candidate["x_1"].mop, candidate["bnot_x_1"].mop): + return False + return True + + +class Xor_FactorRule_3(PatternMatchingRule): + PATTERN = AstNode(m_xor, + AstNode(m_and, + AstLeaf('x_0'), + AstLeaf('x_1')), + AstNode(m_or, + AstLeaf('x_0'), + AstLeaf('x_1'))) + REPLACEMENT_PATTERN = AstNode(m_xor, AstLeaf("x_0"), AstLeaf("x_1")) + + +class Xor_SpecialConstantRule_1(PatternMatchingRule): + PATTERN = AstNode(m_add, + AstNode(m_sub, + AstLeaf("x_0"), + AstLeaf("x_1")), + AstNode(m_mul, + AstConstant("2", 2), + AstNode(m_and, + AstNode(m_bnot, + AstLeaf("x_0")), + AstLeaf("x_1")))) + REPLACEMENT_PATTERN = AstNode(m_xor, AstLeaf("x_0"), AstLeaf("x_1")) + + +class Xor_SpecialConstantRule_2(PatternMatchingRule): + PATTERN = AstNode(m_add, + AstLeaf('x_0'), + AstNode(m_add, + AstNode(m_mul, + AstConstant('0xfe'), + AstNode(m_and, + AstLeaf('x_0'), + AstLeaf('x_1'))), + AstLeaf('x_1'))) + REPLACEMENT_PATTERN = AstNode(m_xor, AstLeaf("x_0"), AstLeaf("x_1")) + + def check_candidate(self, candidate): + return candidate["0xfe"].value == SUB_TABLE[candidate["0xfe"].size] - 2 + + +class Xor1_MbaRule_1(PatternMatchingRule): + PATTERN = AstNode(m_add, + AstNode(m_bnot, + AstLeaf('x_0')), + AstNode(m_or, + AstNode(m_mul, + AstConstant('2', 2), + AstLeaf('x_0')), + AstConstant('2', 2))) + REPLACEMENT_PATTERN = AstNode(m_xor, AstLeaf('x_0'), AstConstant("val_1")) + + def check_candidate(self, candidate): + candidate.add_constant_leaf("val_1", 1, candidate.size) + return True + + +class Xor_Rule_1(PatternMatchingRule): + PATTERN = AstNode(m_or, + AstNode(m_and, + AstLeaf("x_0"), + AstLeaf("x_1")), + AstNode(m_bnot, + AstNode(m_or, + AstLeaf("x_0"), + AstLeaf("x_1")))) + + REPLACEMENT_PATTERN = AstNode(m_xor, + AstLeaf('x_0'), + AstNode(m_bnot, AstLeaf("x_1"))) + + +# Found sometimes with OLLVM +class Xor_Rule_2(PatternMatchingRule): + PATTERN = AstNode(m_or, + AstNode(m_and, + AstNode(m_xor, + AstLeaf('x_0'), + AstLeaf('x_2')), + AstNode(m_xor, + AstLeaf('x_1'), + AstLeaf('bnot_x2'))), + AstNode(m_and, + AstNode(m_xor, + AstLeaf('x_0'), + AstLeaf('bnot_x2')), + AstNode(m_xor, + AstLeaf('x_1'), + AstLeaf('x_2')))) + REPLACEMENT_PATTERN = AstNode(m_xor, AstLeaf('x_0'), AstLeaf('x_1')) + + +# Found sometimes with OLLVM +class Xor_Rule_3(PatternMatchingRule): + PATTERN = AstNode(m_or, + AstNode(m_and, + AstNode(m_xor, + AstLeaf('x_0'), + AstLeaf('x_2')), + AstNode(m_xor, + AstLeaf('x_1'), + AstLeaf('x_2'))), + AstNode(m_and, + AstNode(m_xor, + AstLeaf('x_0'), + AstLeaf('bnot_x2')), + AstNode(m_xor, + AstLeaf('x_1'), + AstLeaf('bnot_x2')))) + REPLACEMENT_PATTERN = AstNode(m_xor, AstNode(m_bnot, AstLeaf('x_0')), AstLeaf('x_1')) + + +class XorAlmost_Rule_1(PatternMatchingRule): + PATTERN = AstNode(m_sub, + AstNode(m_add, + AstLeaf("x_0"), + AstLeaf("x_1")), + AstNode(m_mul, + AstConstant("2", 2), + AstNode(m_or, + AstLeaf("x_0"), + AstNode(m_sub, + AstLeaf("x_1"), + AstConstant("1", 1))))) + + REPLACEMENT_PATTERN = AstNode(m_add, + AstNode(m_xor, + AstLeaf("x_0"), + AstNode(m_neg, AstLeaf("x_1"))), + AstLeaf("val_2")) + + def check_candidate(self, candidate): + candidate.add_constant_leaf("val_2", 2, candidate.size) + return True + + +class Xor_NestedStuff(PatternMatchingRule): + PATTERN = AstNode(m_sub, + AstNode(m_add, + AstNode(m_add, + AstLeaf('x_9'), + AstLeaf('x_10')), + AstLeaf("x_11")), + AstNode(m_add, + AstLeaf("x_14"), + AstNode(m_mul, + AstConstant('2', 2), + AstNode(m_and, + AstLeaf('x_10'), + AstNode(m_sub, + AstNode(m_add, + AstLeaf('x_9'), + AstLeaf("x_11")), + AstLeaf("x_14")))))) + + + REPLACEMENT_PATTERN = AstNode(m_xor, + AstLeaf("x_10"), + AstNode(m_sub, + AstNode(m_add, + AstLeaf('x_9'), + AstLeaf("x_11")), + AstLeaf("x_14"))) + FUZZ_PATTERN = False + diff --git a/d810/optimizers/instructions/pattern_matching/weird.py b/d810/optimizers/instructions/pattern_matching/weird.py new file mode 100644 index 0000000..25a7314 --- /dev/null +++ b/d810/optimizers/instructions/pattern_matching/weird.py @@ -0,0 +1,117 @@ +from ida_hexrays import * +from d810.optimizers.instructions.pattern_matching.handler import PatternMatchingRule +from d810.ast import AstLeaf, AstConstant, AstNode +from d810.hexrays_helpers import equal_bnot_mop + + +class WeirdRule1(PatternMatchingRule): + PATTERN = AstNode(m_sub, + AstLeaf("x_0"), + AstNode(m_or, + AstLeaf("x_0"), + AstLeaf("x_1"))) + REPLACEMENT_PATTERN = AstNode(m_add, + AstNode(m_or, + AstLeaf("x_0"), + AstNode(m_bnot, AstLeaf("x_1"))), + AstConstant("val_1")) + + def check_candidate(self, candidate): + candidate.add_constant_leaf("val_1", 1, candidate.size) + return True + + +class WeirdRule2(PatternMatchingRule): + PATTERN = AstNode(m_sub, + AstNode(m_mul, + AstConstant("2", 2), + AstLeaf("x_0")), + AstNode(m_and, + AstLeaf("x_0"), + AstNode(m_bnot, AstLeaf("x_1")))) + REPLACEMENT_PATTERN = AstNode(m_add, + AstLeaf("x_0"), + AstNode(m_and, + AstLeaf("x_0"), + AstLeaf("x_1"))) + + +class WeirdRule3(PatternMatchingRule): + PATTERN = AstNode(m_sub, + AstNode(m_and, + AstLeaf("x_0"), + AstNode(m_bnot, AstLeaf("x_1"))), + AstNode(m_mul, + AstConstant("2", 2), + AstLeaf("x_0"))) + REPLACEMENT_PATTERN = AstNode(m_neg, + AstNode(m_add, + AstLeaf("x_0"), + AstNode(m_and, + AstLeaf("x_0"), + AstLeaf("x_1")))) + + +class WeirdRule4(PatternMatchingRule): + PATTERN = AstNode(m_sub, + AstNode(m_and, + AstLeaf("x_0"), + AstLeaf("bnot_x_1")), + AstNode(m_and, + AstLeaf("x_0"), + AstLeaf("x_1"))) + REPLACEMENT_PATTERN = AstNode(m_sub, + AstNode(m_xor, + AstLeaf("x_0"), + AstLeaf("x_1")), + AstLeaf("x_1")) + + def check_candidate(self, candidate): + if not equal_bnot_mop(candidate["x_1"].mop, candidate["bnot_x_1"].mop): + return False + return True + + +class WeirdRule5(PatternMatchingRule): + PATTERN = AstNode(m_sub, + AstNode(m_add, + AstNode(m_or, + AstLeaf("bnot_x_0"), + AstNode(m_and, + AstLeaf("bnot_x_1"), + AstLeaf("x_2"))), + AstNode(m_add, + AstLeaf("x_0"), + AstNode(m_and, + AstLeaf("x_1"), + AstLeaf("x_2")))), + AstLeaf("x_2")) + REPLACEMENT_PATTERN = AstNode(m_or, + AstLeaf("x_0"), + AstNode(m_or, + AstLeaf("x_1"), + AstNode(m_bnot, + AstLeaf("x_2")))) + + def check_candidate(self, candidate): + if not equal_bnot_mop(candidate["x_0"].mop, candidate["bnot_x_0"].mop): + return False + if not equal_bnot_mop(candidate["x_1"].mop, candidate["bnot_x_1"].mop): + return False + return True + + +class WeirdRule6(PatternMatchingRule): + PATTERN = AstNode(m_add, + AstNode(m_or, + AstLeaf('x_0'), + AstLeaf('x_1')), + AstNode(m_and, + AstLeaf('x_0'), + AstNode(m_bnot, + AstLeaf('x_1')))) + REPLACEMENT_PATTERN = AstNode(m_add, + AstNode(m_xor, + AstLeaf("x_0"), + AstLeaf("x_1")), + AstLeaf('x_0')) diff --git a/d810/optimizers/instructions/z3/__init__.py b/d810/optimizers/instructions/z3/__init__.py new file mode 100644 index 0000000..f96ce2c --- /dev/null +++ b/d810/optimizers/instructions/z3/__init__.py @@ -0,0 +1,7 @@ +from d810.utils import get_all_subclasses +from d810.optimizers.instructions.z3.handler import Z3Rule, Z3Optimizer +from d810.optimizers.instructions.z3.cst import * +from d810.optimizers.instructions.z3.predicates import * + + +Z3_RULES = [x() for x in get_all_subclasses(Z3Rule)] diff --git a/d810/optimizers/instructions/z3/cst.py b/d810/optimizers/instructions/z3/cst.py new file mode 100644 index 0000000..66112bd --- /dev/null +++ b/d810/optimizers/instructions/z3/cst.py @@ -0,0 +1,51 @@ +from ida_hexrays import * +from d810.optimizers.instructions.z3.handler import Z3Rule +from d810.ast import AstConstant, AstNode +from d810.ast import minsn_to_ast +from d810.errors import AstEvaluationException +from d810.z3_utils import z3_check_mop_equality + + +class Z3ConstantOptimization(Z3Rule): + DESCRIPTION = "Detect and replace obfuscated constants" + REPLACEMENT_PATTERN = AstNode(m_mov, AstConstant("c_res")) + + def __init__(self): + super().__init__() + self.min_nb_opcode = 3 + self.min_nb_constant = 3 + + def configure(self, kwargs): + super().configure(kwargs) + if "min_nb_opcode" in kwargs.keys(): + self.min_nb_opcode = kwargs["min_nb_opcode"] + if "min_nb_constant" in kwargs.keys(): + self.min_nb_constant = kwargs["min_nb_constant"] + + def check_and_replace(self, blk, instruction): + tmp = minsn_to_ast(instruction) + if tmp is None: + return None + leaf_info_list, cst_leaf_values, opcodes = tmp.get_information() + if len(leaf_info_list) == 1 and \ + len(opcodes) >= self.min_nb_opcode and \ + (len(cst_leaf_values) >= self.min_nb_constant): + try: + val_0 = tmp.evaluate_with_leaf_info(leaf_info_list, [0]) + val_1 = tmp.evaluate_with_leaf_info(leaf_info_list, [0xffffffff]) + + if val_0 == val_1: + c_res_mop = mop_t() + c_res_mop.make_number(val_0, tmp.mop.size) + is_ok = z3_check_mop_equality(tmp.mop, c_res_mop) + if is_ok: + tmp.add_leaf("c_res", c_res_mop) + new_instruction = self.get_replacement(tmp) + return new_instruction + return None + except ZeroDivisionError: + pass + except AstEvaluationException as e: + print("Error while evaluating {0}: {1}".format(tmp, e)) + pass + return None diff --git a/d810/optimizers/instructions/z3/handler.py b/d810/optimizers/instructions/z3/handler.py new file mode 100644 index 0000000..4ccbfe9 --- /dev/null +++ b/d810/optimizers/instructions/z3/handler.py @@ -0,0 +1,9 @@ +from d810.optimizers.instructions.handler import GenericPatternRule, InstructionOptimizer + + +class Z3Rule(GenericPatternRule): + pass + + +class Z3Optimizer(InstructionOptimizer): + RULE_CLASSES = [Z3Rule] diff --git a/d810/optimizers/instructions/z3/predicates.py b/d810/optimizers/instructions/z3/predicates.py new file mode 100644 index 0000000..f91bafd --- /dev/null +++ b/d810/optimizers/instructions/z3/predicates.py @@ -0,0 +1,78 @@ +from ida_hexrays import * + +from d810.optimizers.instructions.z3.handler import Z3Rule +from d810.ast import AstLeaf, AstConstant, AstNode +from d810.z3_utils import z3_check_mop_equality, z3_check_mop_inequality + + +class Z3setzRuleGeneric(Z3Rule): + DESCRIPTION = "Check with Z3 if a m_setz check is always True or False" + PATTERN = AstNode(m_setz, + AstLeaf("x_0"), + AstLeaf("x_1")) + REPLACEMENT_PATTERN = AstNode(m_mov, AstConstant("val_res")) + + def check_candidate(self, candidate): + if z3_check_mop_equality(candidate["x_0"].mop, candidate["x_1"].mop): + candidate.add_constant_leaf("val_res", 1, candidate.size) + return True + if z3_check_mop_inequality(candidate["x_0"].mop, candidate["x_1"].mop): + candidate.add_constant_leaf("val_res", 0, candidate.size) + return True + return False + + +class Z3setnzRuleGeneric(Z3Rule): + DESCRIPTION = "Check with Z3 if a m_setnz check is always True or False" + PATTERN = AstNode(m_setnz, + AstLeaf("x_0"), + AstLeaf("x_1")) + REPLACEMENT_PATTERN = AstNode(m_mov, AstConstant("val_res")) + + def check_candidate(self, candidate): + if z3_check_mop_equality(candidate["x_0"].mop, candidate["x_1"].mop): + candidate.add_constant_leaf("val_res", 0, candidate.size) + return True + if z3_check_mop_inequality(candidate["x_0"].mop, candidate["x_1"].mop): + candidate.add_constant_leaf("val_res", 1, candidate.size) + return True + return False + + +class Z3lnotRuleGeneric(Z3Rule): + DESCRIPTION = "Check with Z3 if a m_lnot check is always True or False" + PATTERN = AstNode(m_lnot, + AstLeaf("x_0")) + REPLACEMENT_PATTERN = AstNode(m_mov, AstConstant("val_res")) + + def check_candidate(self, candidate): + val_0_mop = mop_t() + val_0_mop.make_number(0, candidate["x_0"].size) + if z3_check_mop_equality(candidate["x_0"].mop, val_0_mop): + candidate.add_constant_leaf("val_res", 1, candidate.size) + return True + if z3_check_mop_inequality(candidate["x_0"].mop, val_0_mop): + candidate.add_constant_leaf("val_res", 0, candidate.size) + return True + return False + + +class Z3SmodRuleGeneric(Z3Rule): + DESCRIPTION = "Check with Z3 if a m_setz check is always True or False" + PATTERN = AstNode(m_smod, + AstLeaf("x_0"), + AstConstant("2", 2)) + REPLACEMENT_PATTERN = AstNode(m_mov, AstConstant("val_res")) + + def check_candidate(self, candidate): + cst_0_mop = mop_t() + cst_0_mop.make_number(0, candidate.size) + if z3_check_mop_equality(candidate.mop, cst_0_mop): + candidate.add_leaf("val_res", cst_0_mop) + return True + cst_1_mop = mop_t() + cst_1_mop.make_number(1, candidate.size) + if z3_check_mop_equality(candidate.mop, cst_1_mop): + candidate.add_leaf("val_res", cst_1_mop) + return True + return False diff --git a/d810/tracker.py b/d810/tracker.py new file mode 100644 index 0000000..259b509 --- /dev/null +++ b/d810/tracker.py @@ -0,0 +1,477 @@ +from __future__ import annotations +import logging +from typing import List, Union, Tuple, Dict +from ida_hexrays import * + +from d810.emulator import MicroCodeEnvironment, MicroCodeInterpreter +from d810.cfg_utils import change_1way_block_successor, change_2way_block_conditional_successor, duplicate_block +from d810.hexrays_hooks import InstructionDefUseCollector +from d810.hexrays_helpers import equal_mops_ignore_size, get_mop_index, get_blk_index +from d810.hexrays_formatters import format_minsn_t, format_mop_t + +# This module can be use to find the instruction that define the value of a mop. Basically, you: +# 1 - Create a MopTracker object with the list of mops to search +# 2 - Call search_backward while specifying the instruction where the search should start +# It will return a list if MopHistory, each MopHistory object of this list: +# * Represents one possible path to compute the searched mops +# * Stores all instructions used to compute the searched mops +# +# You can get the value of one of the searched mop by calling the get_mop_constant_value API of a MopHistory object. +# Behind the scene, it will emulate all microcode instructions on the MopHistory path. +# +# Finally the duplicate_histories API can be used to duplicate microcode blocks so that for each microcode block, +# the searched mops have only one possible values. For instance, this is a preliminary step used in code unflattening. + + +logger = logging.getLogger('D810.tracker') + + +class BlockInfo(object): + def __init__(self, blk: mblock_t, ins=None): + self.blk = blk + self.ins_list = [] + if ins is not None: + self.ins_list.append(ins) + + def get_copy(self) -> BlockInfo: + new_block_info = BlockInfo(self.blk) + new_block_info.ins_list = [x for x in self.ins_list] + return new_block_info + + +class MopHistory(object): + def __init__(self, searched_mop_list: List[mop_t]): + self.searched_mop_list = [mop_t(x) for x in searched_mop_list] + self.history = [] + self.unresolved_mop_list = [] + + self._mc_interpreter = MicroCodeInterpreter() + self._mc_initial_environment = MicroCodeEnvironment() + self._mc_current_environment = self._mc_initial_environment.get_copy() + self._is_dirty = True + + def add_mop_initial_value(self, mop: mop_t, value: int): + self._is_dirty = True + self._mc_initial_environment.define(mop, value) + + def get_copy(self) -> MopHistory: + new_mop_history = MopHistory(self.searched_mop_list) + new_mop_history.history = [x.get_copy() for x in self.history] + new_mop_history.unresolved_mop_list = [x for x in self.unresolved_mop_list] + new_mop_history._mc_initial_environment = self._mc_initial_environment.get_copy() + new_mop_history._mc_current_environment = new_mop_history._mc_initial_environment.get_copy() + return new_mop_history + + def is_resolved(self) -> bool: + if len(self.unresolved_mop_list) == 0: + return True + for x in self.unresolved_mop_list: + x_value = self._mc_initial_environment.lookup(x, raise_exception=False) + if x_value is None: + return False + return True + + @property + def block_path(self) -> List[mblock_t]: + return [blk_info.blk for blk_info in self.history] + + @property + def block_serial_path(self) -> List[int]: + return [blk.serial for blk in self.block_path] + + def replace_block_in_path(self, old_blk: mblock_t, new_blk: mblock_t) -> bool: + blk_index = get_blk_index(old_blk, self.block_path) + if blk_index > 0: + self.history[blk_index].blk = new_blk + self._is_dirty = True + return True + else: + logger.error("replace_block_in_path: should not happen") + return False + + def insert_block_in_path(self, blk: mblock_t, where_index: int): + self.history = self.history[:where_index] + [BlockInfo(blk)] + self.history[where_index:] + self._is_dirty = True + + def insert_ins_in_block(self, blk: mblock_t, ins: minsn_t, before=True): + blk_index = get_blk_index(blk, self.block_path) + if blk_index < 0: + return False + blk_info = self.history[blk_index] + if before: + blk_info.ins_list = [ins] + blk_info.ins_list + else: + blk_info.ins_list = blk_info.ins_list + [ins] + self._is_dirty = True + + def _execute_microcode(self) -> bool: + if not self._is_dirty: + return True + formatted_mop_searched_list = "['" + "', '".join([format_mop_t(x) for x in self.searched_mop_list]) + "']" + logger.debug("Computing: {0} for path {1}".format(formatted_mop_searched_list, self.block_serial_path)) + self._mc_current_environment = self._mc_initial_environment.get_copy() + for blk_info in self.history: + for blk_ins in blk_info.ins_list: + logger.debug("Executing: {0}.{1}".format(blk_info.blk.serial, format_minsn_t(blk_ins))) + if not self._mc_interpreter.eval_instruction(blk_info.blk, blk_ins, self._mc_current_environment): + self._is_dirty = False + return False + self._is_dirty = False + return True + + def get_mop_constant_value(self, searched_mop: mop_t) -> Union[None, int]: + if not self._execute_microcode(): + return None + return self._mc_interpreter.eval_mop(searched_mop, self._mc_current_environment) + + def print_info(self, detailed_info=False): + formatted_mop_searched_list = [format_mop_t(x) for x in self.searched_mop_list] + tmp = ", ".join(["{0}={1}".format(formatted_mop, self.get_mop_constant_value(mop)) + for formatted_mop, mop in zip(formatted_mop_searched_list, self.searched_mop_list)]) + logger.info("MopHistory: resolved={0}, path={1}, mops={2}" + .format(self.is_resolved(), self.block_serial_path, tmp)) + if detailed_info: + str_mop_list = "['" + "', '".join(formatted_mop_searched_list) + "']" + if len(self.block_path) == 0: + logger.info("MopHistory for {0} => nothing".format(str_mop_list)) + return + + end_blk = self.block_path[-1] + end_ins = end_blk.tail + if self.history[-1].ins_list: + end_ins = self.history[-1].ins_list[-1] + + if end_ins: + logger.info("MopHistory for {0} {1}.{2}".format(str_mop_list, end_blk.serial, format_minsn_t(end_ins))) + else: + logger.info("MopHistory for '{0}' {1}.tail".format(str_mop_list, end_blk.serial)) + logger.info(" path {0}".format(self.block_serial_path)) + for blk_info in self.history: + for blk_ins in blk_info.ins_list: + logger.info(" {0}.{1}".format(blk_info.blk.serial, format_minsn_t(blk_ins))) + + +def get_standard_and_memory_mop_lists(mop_in: mop_t) -> Tuple[List[mop_t], List[mop_t]]: + if mop_in.t in [mop_r, mop_S]: + return [mop_in], [] + elif mop_in.t == mop_v: + return [], [mop_in] + elif mop_in.t == mop_d: + ins_mop_info = InstructionDefUseCollector() + mop_in.d.for_all_ops(ins_mop_info) + return remove_segment_registers(ins_mop_info.unresolved_ins_mops), ins_mop_info.memory_unresolved_ins_mops + else: + logger.warning("Calling get_standard_and_memory_mop_lists with unsupported mop type {0}: '{1}'" + .format(mop_in.t, format_mop_t(mop_in))) + return [], [] + + +# A MopTracker will create new MopTracker to recursively track variable when multiple paths are possible, +# The cur_mop_tracker_nb_path global variable is used to limit the number of MopTracker created +cur_mop_tracker_nb_path = 0 + + +class MopTracker(object): + def __init__(self, searched_mop_list: List[mop_t], max_nb_block=-1, max_path=-1): + self.mba = None + self._unresolved_mops = [] + self._memory_unresolved_mops = [] + for searched_mop in searched_mop_list: + a, b = get_standard_and_memory_mop_lists(searched_mop) + self._unresolved_mops += a + self._memory_unresolved_mops += b + self.history = MopHistory(searched_mop_list) + self.max_nb_block = max_nb_block + self.max_path = max_path + self.avoid_list = [] + self.call_detected = False + self.constant_mops = [] + + @staticmethod + def reset(): + global cur_mop_tracker_nb_path + cur_mop_tracker_nb_path = 0 + + def add_mop_definition(self, mop: mop_t, cst_value: int): + self.constant_mops.append([mop, cst_value]) + self.history.add_mop_initial_value(mop, cst_value) + + def get_copy(self) -> MopTracker: + global cur_mop_tracker_nb_path + new_mop_tracker = MopTracker(self._unresolved_mops, self.max_nb_block, self.max_path) + new_mop_tracker._memory_unresolved_mops = [x for x in self._memory_unresolved_mops] + new_mop_tracker.constant_mops = [[x[0], x[1]] for x in self.constant_mops] + new_mop_tracker.history = self.history.get_copy() + cur_mop_tracker_nb_path += 1 + return new_mop_tracker + + def search_backward(self, blk: mblock_t, ins: minsn_t, avoid_list=None, must_use_pred=None, + stop_at_first_duplication=False) -> List[MopHistory]: + logger.debug("Searching backward (reg): {0}".format([format_mop_t(x) for x in self._unresolved_mops])) + logger.debug("Searching backward (mem): {0}".format([format_mop_t(x) for x in self._memory_unresolved_mops])) + logger.debug("Searching backward (cst): {0}" + .format(["{0}: {1:x}".format(format_mop_t(x[0]), x[1]) for x in self.constant_mops])) + self.mba = blk.mba + self.avoid_list = avoid_list if avoid_list else [] + blk_with_multiple_pred = self.search_until_multiple_predecessor(blk, ins) + if self.is_resolved(): + logger.debug("MopTracker is resolved: {0}".format(self.history.block_serial_path)) + self.history.unresolved_mop_list = [x for x in self._unresolved_mops] + return [self.history] + elif blk_with_multiple_pred is None: + logger.debug("MopTracker unresolved: (blk_with_multiple_pred): {0}".format(self.history.block_serial_path)) + self.history.unresolved_mop_list = [x for x in self._unresolved_mops] + return [self.history] + elif self.max_nb_block != -1 and len(self.history.block_serial_path) > self.max_nb_block: + logger.debug("MopTracker unresolved: (max_nb_block): {0}".format(self.history.block_serial_path)) + self.history.unresolved_mop_list = [x for x in self._unresolved_mops] + return [self.history] + elif self.max_path != -1 and cur_mop_tracker_nb_path > self.max_path: + logger.debug("MopTracker unresolved: (max_path: {0}".format(cur_mop_tracker_nb_path)) + self.history.unresolved_mop_list = [x for x in self._unresolved_mops] + return [self.history] + elif self.call_detected: + logger.debug("MopTracker unresolved: (call): {0}".format(self.history.block_serial_path)) + self.history.unresolved_mop_list = [x for x in self._unresolved_mops] + return [self.history] + + if stop_at_first_duplication: + self.history.unresolved_mop_list = [x for x in self._unresolved_mops] + return [self.history] + logger.debug("MopTracker creating child because multiple pred: {0}".format(self.history.block_serial_path)) + possible_histories = [] + if must_use_pred is not None and must_use_pred.serial in blk_with_multiple_pred.predset: + new_tracker = self.get_copy() + possible_histories += new_tracker.search_backward(must_use_pred, None, self.avoid_list, must_use_pred) + else: + for blk_pred_serial in blk_with_multiple_pred.predset: + new_tracker = self.get_copy() + possible_histories += new_tracker.search_backward(self.mba.get_mblock(blk_pred_serial), None, + self.avoid_list, must_use_pred) + return possible_histories + + def search_until_multiple_predecessor(self, blk: mblock_t, ins: Union[None, minsn_t] = None) -> Union[None, mblock_t]: + # By default, we start searching from block tail + cur_ins = ins if ins else blk.tail + cur_blk = blk + + while not self.is_resolved(): + # Explore one block + if cur_blk.serial in self.history.block_serial_path: + self.history.insert_block_in_path(cur_blk, 0) + return None + if cur_blk.serial in self.avoid_list: + self.history.insert_block_in_path(cur_blk, 0) + return None + self.history.insert_block_in_path(cur_blk, 0) + cur_ins = self.blk_find_def_backward(cur_blk, cur_ins) + while cur_ins: + cur_ins = self.blk_find_def_backward(cur_blk, cur_ins) + if cur_blk.npred() > 1: + return cur_blk + elif cur_blk.npred() == 0: + return None + else: + cur_blk = self.mba.get_mblock(cur_blk.predset[0]) + cur_ins = cur_blk.tail + + # We want to handle cases where the self.is_resolved() is True without doing anything + if len(self.history.block_serial_path) == 0: + self.history.insert_block_in_path(cur_blk, 0) + return None + + def is_resolved(self) -> bool: + if (len(self._unresolved_mops) == 0) and (len(self._memory_unresolved_mops) == 0): + return True + + for x in self._unresolved_mops: + x_index = get_mop_index(x, [y[0] for y in self.constant_mops]) + if x_index == -1: + return False + return True + + def _build_ml_list(self, blk: mblock_t) -> Union[None, mlist_t]: + ml = mlist_t() + for unresolved_mop in self._unresolved_mops: + if unresolved_mop.t not in [mop_r, mop_S]: + logger.warning("_build_ml_list: Not supported mop type '{0}'".format(unresolved_mop.t)) + return None + blk.append_use_list(ml, unresolved_mop, MUST_ACCESS) + return ml + + def blk_find_def_backward(self, blk: mblock_t, ins_start: minsn_t) -> Union[None, minsn_t]: + if self.is_resolved(): + return None + ml = self._build_ml_list(blk) + if not ml: + logger.warning("blk_find_def_backward: _build_ml_list failed") + return None + ins_def = self._blk_find_ins_def_backward(blk, ins_start, ml) + if ins_def: + is_ok = self.update_history(blk, ins_def) + if not is_ok: + return None + ins_def = ins_def.prev + return ins_def + + def update_history(self, blk: mblock_t, ins_def: minsn_t) -> bool: + logger.debug("Updating history with {0}.{1}".format(blk.serial, format_minsn_t(ins_def))) + self.history.insert_ins_in_block(blk, ins_def, before=True) + if ins_def.opcode == m_call: + self.call_detected = True + return False + ins_mop_info = InstructionDefUseCollector() + ins_def.for_all_ops(ins_mop_info) + + for target_mop in ins_mop_info.target_mops: + resolved_mop_index = get_mop_index(target_mop, self._unresolved_mops) + if resolved_mop_index != -1: + logger.debug("Removing {0} from unresolved mop".format(format_mop_t(target_mop))) + self._unresolved_mops.pop(resolved_mop_index) + cleaned_unresolved_ins_mops = remove_segment_registers(ins_mop_info.unresolved_ins_mops) + for ins_def_mop in cleaned_unresolved_ins_mops: + ins_def_mop_index = get_mop_index(ins_def_mop, self._unresolved_mops) + if ins_def_mop_index == -1: + logger.debug("Adding {0} in unresolved mop".format(format_mop_t(ins_def_mop))) + self._unresolved_mops.append(ins_def_mop) + + for target_mop in ins_mop_info.target_mops: + resolved_mop_index = get_mop_index(target_mop, self._memory_unresolved_mops) + if resolved_mop_index != -1: + logger.debug("Removing {0} from memory unresolved mop".format(format_mop_t(target_mop))) + self._memory_unresolved_mops.pop(resolved_mop_index) + for ins_def_mem_mop in ins_mop_info.memory_unresolved_ins_mops: + ins_def_mop_index = get_mop_index(ins_def_mem_mop, self._memory_unresolved_mops) + if ins_def_mop_index == -1: + logger.debug("Adding {0} in memory unresolved mop".format(format_mop_t(ins_def_mem_mop))) + self._memory_unresolved_mops.append(ins_def_mem_mop) + return True + + def _blk_find_ins_def_backward(self, blk: mblock_t, ins_start: minsn_t, ml: mlist_t) -> Union[None, minsn_t]: + cur_ins = ins_start + while cur_ins is not None: + def_list = blk.build_def_list(cur_ins, MAY_ACCESS | FULL_XDSU) + if ml.has_common(def_list): + return cur_ins + for mem_mop in self._memory_unresolved_mops: + if equal_mops_ignore_size(cur_ins.d, mem_mop): + return cur_ins + cur_ins = cur_ins.prev + return None + + +def get_block_with_multiple_predecessors(var_histories: List[MopHistory]) -> Tuple[Union[None, mblock_t], + Union[None, Dict[int, List[MopHistory]]]]: + for i, var_history in enumerate(var_histories): + pred_blk = var_history.block_path[0] + for block in var_history.block_path[1:]: + tmp_dict = {pred_blk.serial: [var_history]} + for j in range(i + 1, len(var_histories)): + blk_index = get_blk_index(block, var_histories[j].block_path) + if (blk_index - 1) >= 0: + other_pred = var_histories[j].block_path[blk_index - 1] + if other_pred.serial not in tmp_dict.keys(): + tmp_dict[other_pred.serial] = [] + tmp_dict[other_pred.serial].append(var_histories[j]) + if len(tmp_dict) > 1: + return block, tmp_dict + pred_blk = block + return None, None + + +def try_to_duplicate_one_block(var_histories: List[MopHistory]) -> Tuple[int, int]: + nb_duplication = 0 + nb_change = 0 + if (len(var_histories) == 0) or (len(var_histories[0].block_path) == 0): + return nb_duplication, nb_change + mba = var_histories[0].block_path[0].mba + block_to_duplicate, pred_dict = get_block_with_multiple_predecessors(var_histories) + if block_to_duplicate is None: + return nb_duplication, nb_change + logger.debug("Block to duplicate found: {0} with {1} successors" + .format(block_to_duplicate.serial, block_to_duplicate.nsucc())) + i = 0 + for pred_serial, pred_history_group in pred_dict.items(): + # We do not duplicate first group + if i >= 1: + logger.debug(" Before {0}: {1}" + .format(pred_serial, [var_history.block_serial_path for var_history in pred_history_group])) + pred_block = mba.get_mblock(pred_serial) + duplicated_blk_jmp, duplicated_blk_default = duplicate_block(block_to_duplicate) + nb_duplication += 1 if duplicated_blk_jmp is not None else 0 + nb_duplication += 1 if duplicated_blk_default is not None else 0 + logger.debug(" Making {0} goto {1}".format(pred_block.serial, duplicated_blk_jmp.serial)) + if (pred_block.tail is None) or (not is_mcode_jcond(pred_block.tail.opcode)): + change_1way_block_successor(pred_block, duplicated_blk_jmp.serial) + nb_change += 1 + else: + if block_to_duplicate.serial == pred_block.tail.d.b: + change_2way_block_conditional_successor(pred_block, duplicated_blk_jmp.serial) + nb_change += 1 + else: + logger.warning(" not sure this is suppose to happen") + change_1way_block_successor(pred_block.mba.get_mblock(pred_block.serial + 1), + duplicated_blk_jmp.serial) + nb_change += 1 + + block_to_duplicate_default_successor = mba.get_mblock(block_to_duplicate.serial + 1) + logger.debug(" Now, we fix var histories...") + for var_history in pred_history_group: + var_history.replace_block_in_path(block_to_duplicate, duplicated_blk_jmp) + if block_to_duplicate.tail is not None and is_mcode_jcond(block_to_duplicate.tail.opcode): + index_jump_block = get_blk_index(duplicated_blk_jmp, var_history.block_path) + if index_jump_block + 1 < len(var_history.block_path): + original_jump_block_successor = var_history.block_path[index_jump_block + 1] + if original_jump_block_successor.serial == block_to_duplicate_default_successor.serial: + var_history.insert_block_in_path(duplicated_blk_default, index_jump_block + 1) + i += 1 + logger.debug(" After {0}: {1}" + .format(pred_serial, [var_history.block_serial_path for var_history in pred_history_group])) + for i, var_history in enumerate(var_histories): + logger.debug(" internal_pass_end.{0}: {1}".format(i, var_history.block_serial_path)) + return nb_duplication, nb_change + + +def duplicate_histories(var_histories: List[MopHistory], max_nb_pass: int = 10) -> Tuple[int, int]: + cur_pass = 0 + total_nb_duplication = 0 + total_nb_change = 0 + logger.info("Trying to fix new var_history...") + for i, var_history in enumerate(var_histories): + logger.info(" start.{0}: {1}".format(i, var_history.block_serial_path)) + while cur_pass < max_nb_pass: + logger.debug("Current path {0}".format(cur_pass)) + nb_duplication, nb_change = try_to_duplicate_one_block(var_histories) + if nb_change == 0 and nb_duplication == 0: + break + total_nb_duplication += nb_duplication + total_nb_change += nb_change + cur_pass += 1 + for i, var_history in enumerate(var_histories): + logger.info(" end.{0}: {1}".format(i, var_history.block_serial_path)) + return total_nb_duplication, total_nb_change + + +def get_segment_register_indexes(mop_list: List[mop_t]) -> List[int]: + # This is a very dirty and probably buggy + segment_register_indexes = [] + for i, mop in enumerate(mop_list): + if mop.t == mop_r: + formatted_mop = format_mop_t(mop) + if formatted_mop in ["ds.2", "cs.2", "es.2", "ss.2"]: + segment_register_indexes.append(i) + return segment_register_indexes + + +def remove_segment_registers(mop_list: List[mop_t]) -> List[mop_t]: + # TODO: instead of doing that, we should add the segment registers to the (global?) emulation environment + segment_register_indexes = get_segment_register_indexes(mop_list) + if len(segment_register_indexes) == 0: + return mop_list + new_mop_list = [] + for i, mop in enumerate(mop_list): + if i in segment_register_indexes: + pass + else: + new_mop_list.append(mop) + return new_mop_list diff --git a/d810/utils.py b/d810/utils.py new file mode 100644 index 0000000..0f8af3e --- /dev/null +++ b/d810/utils.py @@ -0,0 +1,69 @@ +import ctypes + +from d810.hexrays_helpers import MSB_TABLE + +CTYPE_SIGNED_TABLE = {1: ctypes.c_int8, 2: ctypes.c_int16, 4: ctypes.c_int32, 8: ctypes.c_int64} +CTYPE_UNSIGNED_TABLE = {1: ctypes.c_uint8, 2: ctypes.c_uint16, 4: ctypes.c_uint32, 8: ctypes.c_uint64} + + +def get_all_subclasses(python_class): + python_class.__subclasses__() + + subclasses = set() + check_these = [python_class] + + while check_these: + parent = check_these.pop() + for child in parent.__subclasses__(): + if child not in subclasses: + subclasses.add(child) + check_these.append(child) + + return sorted(subclasses, key=lambda x: x.__name__) + + +def unsigned_to_signed(unsigned_value, nb_bytes): + return CTYPE_SIGNED_TABLE[nb_bytes](unsigned_value).value + + +def signed_to_unsigned(signed_value, nb_bytes): + return CTYPE_UNSIGNED_TABLE[nb_bytes](signed_value).value + + +def get_msb(value, nb_bytes): + return (value & MSB_TABLE[nb_bytes]) >> (nb_bytes * 8 - 1) + + +def get_add_cf(op1, op2, nb_bytes): + res = op1 + op2 + return get_msb((((op1 ^ op2) ^ res) ^ ((op1 ^ res) & (~(op1 ^ op2)))), nb_bytes) + + +def get_add_of(op1, op2, nb_bytes): + res = op1 + op2 + return get_msb(((op1 ^ res) & (~(op1 ^ op2))), nb_bytes) + + +def get_sub_cf(op1, op2, nb_bytes): + res = op1 - op2 + return get_msb((((op1 ^ op2) ^ res) ^ ((op1 ^ res) & (op1 ^ op2))), nb_bytes) + + +def get_sub_of(op1, op2, nb_bytes): + res = op1 - op2 + return get_msb(((op1 ^ res) & (op1 ^ op2)), nb_bytes) + + +def get_parity_flag(op1, op2, nb_bytes): + tmp = CTYPE_UNSIGNED_TABLE[nb_bytes](op1 - op2).value + return (bin(tmp).count("1") + 1) % 2 + + +def ror(x, n, nb_bits=32): + mask = (2 ** n) - 1 + mask_bits = x & mask + return (x >> n) | (mask_bits << (nb_bits - n)) + + +def rol(x, n, nb_bits=32): + return ror(x, nb_bits - n, nb_bits) diff --git a/d810/z3_utils.py b/d810/z3_utils.py new file mode 100644 index 0000000..e910995 --- /dev/null +++ b/d810/z3_utils.py @@ -0,0 +1,158 @@ +import logging +from typing import List, Union +from ida_hexrays import * + +from d810.hexrays_helpers import get_mop_index +from d810.hexrays_formatters import format_minsn_t, opcode_to_string +from d810.ast import mop_to_ast, minsn_to_ast, AstLeaf, AstNode +from d810.errors import D810Z3Exception + +logger = logging.getLogger('D810.plugin') +z3_file_logger = logging.getLogger('D810.z3_test') + +try: + import z3 + Z3_INSTALLED = True +except ImportError: + logger.info("Z3 features disabled. Install Z3 to enable them") + Z3_INSTALLED = False + + +def create_z3_vars(leaf_list: List[AstLeaf]): + if not Z3_INSTALLED: + raise D810Z3Exception("Z3 is not installed") + known_leaf_list = [] + known_leaf_z3_var_list = [] + for leaf in leaf_list: + if not leaf.is_constant(): + leaf_index = get_mop_index(leaf.mop, known_leaf_list) + if leaf_index == -1: + known_leaf_list.append(leaf.mop) + leaf_index = len(known_leaf_list) - 1 + if leaf.mop.size in [1, 2, 4, 8]: + # Normally, we should create variable based on their size + # but for now it can cause issue when instructions like XDU are used, hence this ugly fix + # known_leaf_z3_var_list.append(z3.BitVec("x_{0}".format(leaf_index), 8 * leaf.mop.size)) + known_leaf_z3_var_list.append(z3.BitVec("x_{0}".format(leaf_index), 32)) + pass + else: + known_leaf_z3_var_list.append(z3.BitVec("x_{0}".format(leaf_index), 32)) + leaf.z3_var = known_leaf_z3_var_list[leaf_index] + leaf.z3_var_name = "x_{0}".format(leaf_index) + return known_leaf_z3_var_list + + +def ast_to_z3_expression(ast: Union[AstNode, AstLeaf], use_bitvecval=False): + if not Z3_INSTALLED: + raise D810Z3Exception("Z3 is not installed") + if isinstance(ast, AstLeaf): + if ast.is_constant(): + return z3.BitVecVal(ast.value, 32) + return ast.z3_var + if ast.opcode == m_neg: + return -(ast_to_z3_expression(ast.left, use_bitvecval)) + elif ast.opcode == m_lnot: + return not (ast_to_z3_expression(ast.left, use_bitvecval)) + elif ast.opcode == m_bnot: + return ~(ast_to_z3_expression(ast.left, use_bitvecval)) + elif ast.opcode == m_add: + return (ast_to_z3_expression(ast.left, use_bitvecval)) + (ast_to_z3_expression(ast.right, use_bitvecval)) + elif ast.opcode == m_sub: + return (ast_to_z3_expression(ast.left, use_bitvecval)) - (ast_to_z3_expression(ast.right, use_bitvecval)) + elif ast.opcode == m_mul: + return (ast_to_z3_expression(ast.left, use_bitvecval)) * (ast_to_z3_expression(ast.right, use_bitvecval)) + elif ast.opcode == m_udiv: + return z3.UDiv(ast_to_z3_expression(ast.left, use_bitvecval=True), + ast_to_z3_expression(ast.right, use_bitvecval=True)) + elif ast.opcode == m_sdiv: + return (ast_to_z3_expression(ast.left, use_bitvecval)) / (ast_to_z3_expression(ast.right, use_bitvecval)) + elif ast.opcode == m_umod: + return z3.URem(ast_to_z3_expression(ast.left, use_bitvecval), ast_to_z3_expression(ast.right, use_bitvecval)) + elif ast.opcode == m_smod: + return (ast_to_z3_expression(ast.left, use_bitvecval)) % (ast_to_z3_expression(ast.right, use_bitvecval)) + elif ast.opcode == m_or: + return (ast_to_z3_expression(ast.left, use_bitvecval)) | (ast_to_z3_expression(ast.right, use_bitvecval)) + elif ast.opcode == m_and: + return (ast_to_z3_expression(ast.left, use_bitvecval)) & (ast_to_z3_expression(ast.right, use_bitvecval)) + elif ast.opcode == m_xor: + return (ast_to_z3_expression(ast.left, use_bitvecval)) ^ (ast_to_z3_expression(ast.right, use_bitvecval)) + elif ast.opcode == m_shl: + return (ast_to_z3_expression(ast.left, use_bitvecval)) << (ast_to_z3_expression(ast.right, use_bitvecval)) + elif ast.opcode == m_shr: + return z3.LShR(ast_to_z3_expression(ast.left, use_bitvecval), ast_to_z3_expression(ast.right, use_bitvecval)) + elif ast.opcode == m_sar: + return (ast_to_z3_expression(ast.left, use_bitvecval)) >> (ast_to_z3_expression(ast.right, use_bitvecval)) + elif ast.opcode in [m_xdu, m_xds, m_low, m_high]: + return ast_to_z3_expression(ast.left, use_bitvecval) + raise D810Z3Exception("Z3 evaluation: Unknown opcode {0} for {1}".format(opcode_to_string(ast.opcode), ast)) + + +def mop_list_to_z3_expression_list(mop_list: List[mop_t]): + if not Z3_INSTALLED: + raise D810Z3Exception("Z3 is not installed") + ast_list = [mop_to_ast(mop) for mop in mop_list] + ast_leaf_list = [] + for ast in ast_list: + ast_leaf_list += ast.get_leaf_list() + _ = create_z3_vars(ast_leaf_list) + return [ast_to_z3_expression(ast) for ast in ast_list] + + +def z3_check_mop_equality(mop1: mop_t, mop2: mop_t) -> bool: + if not Z3_INSTALLED: + raise D810Z3Exception("Z3 is not installed") + z3_mop1, z3_mop2 = mop_list_to_z3_expression_list([mop1, mop2]) + s = z3.Solver() + s.add(z3.Not(z3_mop1 == z3_mop2)) + if s.check().r == -1: + return True + return False + + +def z3_check_mop_inequality(mop1: mop_t, mop2: mop_t) -> bool: + if not Z3_INSTALLED: + raise D810Z3Exception("Z3 is not installed") + z3_mop1, z3_mop2 = mop_list_to_z3_expression_list([mop1, mop2]) + s = z3.Solver() + s.add(z3_mop1 == z3_mop2) + if s.check().r == -1: + return True + return False + + +def rename_leafs(leaf_list: List[AstLeaf]) -> List[str]: + if not Z3_INSTALLED: + raise D810Z3Exception("Z3 is not installed") + known_leaf_list = [] + for leaf in leaf_list: + if not leaf.is_constant() and leaf.mop.t != mop_z: + leaf_index = get_mop_index(leaf.mop, known_leaf_list) + if leaf_index == -1: + known_leaf_list.append(leaf.mop) + leaf_index = len(known_leaf_list) - 1 + leaf.z3_var_name = "x_{0}".format(leaf_index) + + return ["x_{0} = BitVec('x_{0}', {1})".format(i, 8 * leaf.size) for i, leaf in enumerate(known_leaf_list)] + + +def log_z3_instructions(original_ins: minsn_t, new_ins: minsn_t): + if not Z3_INSTALLED: + raise D810Z3Exception("Z3 is not installed") + orig_mba_tree = minsn_to_ast(original_ins) + new_mba_tree = minsn_to_ast(new_ins) + if orig_mba_tree is None or new_mba_tree is None: + return None + orig_leaf_list = orig_mba_tree.get_leaf_list() + new_leaf_list = new_mba_tree.get_leaf_list() + + var_def_list = rename_leafs(orig_leaf_list + new_leaf_list) + + z3_file_logger.info("print('Testing: {0} == {1}')".format(format_minsn_t(original_ins), format_minsn_t(new_ins))) + for var_def in var_def_list: + z3_file_logger.info("{0}".format(var_def)) + + removed_xdu = "{0}".format(orig_mba_tree).replace("xdu","") + z3_file_logger.info("original_expr = {0}".format(removed_xdu)) + removed_xdu = "{0}".format(new_mba_tree).replace("xdu","") + z3_file_logger.info("new_expr = {0}".format(removed_xdu)) + z3_file_logger.info("prove(original_expr == new_expr)\n")