diff --git a/CMakeLists.txt b/CMakeLists.txt index 81f9543..7973b84 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -88,12 +88,12 @@ set_property(TARGET ${TARGET} PROPERTY CXX_STANDARD_REQUIRED ON) # CTest stuff include(CTest) -#find_package(Python REQUIRED) enable_testing() +set(ARGUMENTS "--all") if(MSVC) add_test(executable, "${BUILD_DIR}/Release/${TARGET}") else() -add_test(executable, "${BUILD_DIR}/${TARGET}") +add_test(NAME TARGET COMMAND "${BUILD_DIR}/${TARGET}" ${ARGUMENTS}) endif() diff --git a/COPYING b/COPYING new file mode 100644 index 0000000..3877ae0 --- /dev/null +++ b/COPYING @@ -0,0 +1,674 @@ + GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 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 General Public License is a free, copyleft license for +software and other kinds of works. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +the GNU General Public License is 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. We, the Free Software Foundation, use the +GNU General Public License for most of our software; it applies also to +any other work released this way by its authors. You can apply it to +your programs, too. + + 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. + + To protect your rights, we need to prevent others from denying you +these rights or asking you to surrender the rights. Therefore, you have +certain responsibilities if you distribute copies of the software, or if +you modify it: responsibilities to respect the freedom of others. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must pass on to the recipients the same +freedoms that you received. You must make sure that they, too, receive +or can get the source code. And you must show them these terms so they +know their rights. + + Developers that use the GNU GPL protect your rights with two steps: +(1) assert copyright on the software, and (2) offer you this License +giving you legal permission to copy, distribute and/or modify it. + + For the developers' and authors' protection, the GPL clearly explains +that there is no warranty for this free software. For both users' and +authors' sake, the GPL requires that modified versions be marked as +changed, so that their problems will not be attributed erroneously to +authors of previous versions. + + Some devices are designed to deny users access to install or run +modified versions of the software inside them, although the manufacturer +can do so. This is fundamentally incompatible with the aim of +protecting users' freedom to change the software. The systematic +pattern of such abuse occurs in the area of products for individuals to +use, which is precisely where it is most unacceptable. Therefore, we +have designed this version of the GPL to prohibit the practice for those +products. If such problems arise substantially in other domains, we +stand ready to extend this provision to those domains in future versions +of the GPL, as needed to protect the freedom of users. + + Finally, every program is threatened constantly by software patents. +States should not allow patents to restrict development and use of +software on general-purpose computers, but in those that do, we wish to +avoid the special danger that patents applied to a free program could +make it effectively proprietary. To prevent this, the GPL assures that +patents cannot be used to render the program non-free. + + 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 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. Use with the GNU Affero General Public License. + + 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 Affero 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 special requirements of the GNU Affero General Public License, +section 13, concerning interaction through a network will apply to the +combination as such. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU 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 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 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 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 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If the program does terminal interaction, make it output a short +notice like this when it starts in an interactive mode: + + Copyright (C) + This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, your program's commands +might be different; for a GUI interface, you would use an "about box". + + 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 GPL, see +. + + The GNU General Public License does not permit incorporating your program +into proprietary programs. If your program is a subroutine library, you +may consider it more useful to permit linking proprietary applications with +the library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. But first, please read +. diff --git a/LICENSE b/LICENSE index 3877ae0..8966516 100644 --- a/LICENSE +++ b/LICENSE @@ -1,674 +1,21 @@ - GNU GENERAL PUBLIC LICENSE - Version 3, 29 June 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 General Public License is a free, copyleft license for -software and other kinds of works. - - The licenses for most software and other practical works are designed -to take away your freedom to share and change the works. By contrast, -the GNU General Public License is 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. We, the Free Software Foundation, use the -GNU General Public License for most of our software; it applies also to -any other work released this way by its authors. You can apply it to -your programs, too. - - 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. - - To protect your rights, we need to prevent others from denying you -these rights or asking you to surrender the rights. Therefore, you have -certain responsibilities if you distribute copies of the software, or if -you modify it: responsibilities to respect the freedom of others. - - For example, if you distribute copies of such a program, whether -gratis or for a fee, you must pass on to the recipients the same -freedoms that you received. You must make sure that they, too, receive -or can get the source code. And you must show them these terms so they -know their rights. - - Developers that use the GNU GPL protect your rights with two steps: -(1) assert copyright on the software, and (2) offer you this License -giving you legal permission to copy, distribute and/or modify it. - - For the developers' and authors' protection, the GPL clearly explains -that there is no warranty for this free software. For both users' and -authors' sake, the GPL requires that modified versions be marked as -changed, so that their problems will not be attributed erroneously to -authors of previous versions. - - Some devices are designed to deny users access to install or run -modified versions of the software inside them, although the manufacturer -can do so. This is fundamentally incompatible with the aim of -protecting users' freedom to change the software. The systematic -pattern of such abuse occurs in the area of products for individuals to -use, which is precisely where it is most unacceptable. Therefore, we -have designed this version of the GPL to prohibit the practice for those -products. If such problems arise substantially in other domains, we -stand ready to extend this provision to those domains in future versions -of the GPL, as needed to protect the freedom of users. - - Finally, every program is threatened constantly by software patents. -States should not allow patents to restrict development and use of -software on general-purpose computers, but in those that do, we wish to -avoid the special danger that patents applied to a free program could -make it effectively proprietary. To prevent this, the GPL assures that -patents cannot be used to render the program non-free. - - 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 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. Use with the GNU Affero General Public License. - - 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 Affero 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 special requirements of the GNU Affero General Public License, -section 13, concerning interaction through a network will apply to the -combination as such. - - 14. Revised Versions of this License. - - The Free Software Foundation may publish revised and/or new versions of -the GNU 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 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 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 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 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 General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . - -Also add information on how to contact you by electronic and paper mail. - - If the program does terminal interaction, make it output a short -notice like this when it starts in an interactive mode: - - Copyright (C) - This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. - This is free software, and you are welcome to redistribute it - under certain conditions; type `show c' for details. - -The hypothetical commands `show w' and `show c' should show the appropriate -parts of the General Public License. Of course, your program's commands -might be different; for a GUI interface, you would use an "about box". - - 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 GPL, see -. - - The GNU General Public License does not permit incorporating your program -into proprietary programs. If your program is a subroutine library, you -may consider it more useful to permit linking proprietary applications with -the library. If this is what you want to do, use the GNU Lesser General -Public License instead of this License. But first, please read -. +MIT License + +Copyright (c) 2024 kernelwernel + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. \ No newline at end of file diff --git a/README.md b/README.md index a05d1cd..8a68397 100644 --- a/README.md +++ b/README.md @@ -141,7 +141,7 @@ You can view the full docs [here](docs/documentation.md). All the details such a > Hyper-V has an obscure feature where if it's enabled in the host system, the CPU hardware values makes it look like the whole system is running inside Hyper-V, which isn't true. This makes it a challenge to determine whether the hardware values the library is collecting is either a real Hyper-V VM, or just the artifacts of what Hyper-V has left as a consequence of having it enabled in the host system. The reason why this is a problem is because the library might falsely conclude that your the host system is running in Hyper-V, which is a false positive. This is where the **Hyper-X** mechanism comes into play to distinguish between these two. This was designed by Requiem

- +
diff --git a/TODO.md b/TODO.md index e123e49..8b5bfa8 100644 --- a/TODO.md +++ b/TODO.md @@ -43,10 +43,17 @@ - [ ] make a medium post about it - [ ] test the VM::modify_score() function - [ ] check if bios date in /sys/class/dmi/id/ could be useful under QEMU -- [ ] make the cli demo in the readme for the 1.8 version -- [ ] fix the percentage thing for the disabled techniques -- [ ] adopt the firmware technique from the vmprotect source code leak +- [ ] make the cli demo in the readme for the 1.9 version +- [X] fix the percentage thing for the disabled techniques +- [X] adopt the firmware technique from the vmprotect source code leak - [ ] update the Hyper-X graph with the cpu manufacturer part +- [ ] add a .so, .dll, and .dylib shared object files in the release +- [X] make a struct version as an alternative +- [X] add the license style like in ffmpeg https://github.com/FFmpeg/FFmpeg/tree/master?tab=License-1-ov-file +- [ ] fix the issue of VM::QEMU_USB being ultra slow +- [X] make a MIT transformer python script from GPL to MIT +- [ ] /sys/class/dmi/id/product_name check this in qemu + # Distant plans - add the library to conan.io when released diff --git a/assets/Hyper-X_version_2.drawio b/assets/Hyper-X_version_2.drawio new file mode 100644 index 0000000..dc53209 --- /dev/null +++ b/assets/Hyper-X_version_2.drawio @@ -0,0 +1,242 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/assets/Hyper-X_version_2.png b/assets/Hyper-X_version_2.png new file mode 100644 index 0000000..afaefad Binary files /dev/null and b/assets/Hyper-X_version_2.png differ diff --git a/assets/demo.jpg b/assets/demo.jpg index 939621d..f7e1ec9 100644 Binary files a/assets/demo.jpg and b/assets/demo.jpg differ diff --git a/auxiliary/arg_checks.py b/auxiliary/arg_checks.py deleted file mode 100644 index 65ba21c..0000000 --- a/auxiliary/arg_checks.py +++ /dev/null @@ -1,183 +0,0 @@ -# -# ██╗ ██╗███╗ ███╗ █████╗ ██╗ ██╗ █████╗ ██████╗ ███████╗ -# ██║ ██║████╗ ████║██╔══██╗██║ ██║██╔══██╗██╔══██╗██╔════╝ -# ██║ ██║██╔████╔██║███████║██║ █╗ ██║███████║██████╔╝█████╗ -# ╚██╗ ██╔╝██║╚██╔╝██║██╔══██║██║███╗██║██╔══██║██╔══██╗██╔══╝ -# ╚████╔╝ ██║ ╚═╝ ██║██║ ██║╚███╔███╔╝██║ ██║██║ ██║███████╗ -# ╚═══╝ ╚═╝ ╚═╝╚═╝ ╚═╝ ╚══╝╚══╝ ╚═╝ ╚═╝╚═╝ ╚═╝╚══════╝ -# -# C++ VM detection library -# -# ============================================================= -# -# This is just an internal script for CI/CD. The main goal is to -# check whether all of the techniques are actually updated since -# keeping track of the docs, the cli, and the table isn't easy, -# so I'm automating the checks in case I forget to update any. -# -# =============================================================== -# -# - Made by: @kernelwernel (https://github.com/kernelwernel) -# - Repository: https://github.com/kernelwernel/VMAware -# - License: GPL 3.0 - -import sys -import re - -def fetch(): - # fetch file content - with open("../src/vmaware.hpp", 'r') as vmaware: - header_content = vmaware.readlines() - - # reversed since the table is at the very end of the vmaware.hpp file - header_content.reverse() - - # breakpoint - keyword = "const std::map VM::core::technique_table = {" - - # fetch index of breakpoint - index_of_keyword = next((i for i, line in enumerate(header_content) if keyword in line), None) - - # remove everything before the breakpoint for simplification - if index_of_keyword is not None: - header_content = header_content[:index_of_keyword + 1] - - return header_content - - - -def filter(raw_content): - trimmed_content = [] - - # filter - trimmed_content = [s for s in raw_content if not ( - s.isspace() or - "//" in s or - ";" in s or - "VM::core::technique" in s - )] - - # strip all whitespace - trimmed_content = [s.strip() for s in trimmed_content] - - return trimmed_content - - - -def tokenize(trimmed_content): - flag_array = [] - - # pattern for VM::FLAG_EXAMPLE1 - pattern = r'\bVM::([A-Z0-9_]+)\b' - - # match and push to flag_array[] - for line in trimmed_content: - match = re.search(pattern, line) - - if match: - flag_array.append(match.group(0)) - else: - print("Unable to find flag variable for " + line) - sys.exit(1) - - return flag_array - - - -def check_docs(flag_array): - # fetch docs content - with open("../docs/documentation.md", 'r') as docs: - docs_content = docs.readlines() - - # strip whitespace - docs_content = [s.strip() for s in docs_content] - - # find indices - start = "# Flag table" - end = "# Non-technique flags" - - # extract the indexes - try: - start_index = docs_content.index(start) - end_index = docs_content.index(end) - except ValueError: - print("Couldn't find range index point \"# Flag table\" or \"# Non-technique flags\"") - start_index = end_index = None - sys.exit(1) - - # extract the range between the aforementioned indexes - if start_index is not None and end_index is not None: - extracted_range = docs_content[start_index + 1:end_index] - docs_content = extracted_range - - # filter elements with whitespace - docs_content = [s for s in docs_content if not s.isspace() and s and "VM::" in s] - - # extract flag string for every line - docs_flags = [] - pattern = r'`([^`]+)`' - for line in docs_content: - match = re.search(pattern, line) - - if match: - docs_flags.append(match.group(1)) - else: - print("Pattern not found in the line \"" + line + "\"") - sys.exit(1) - - set1 = set(docs_flags) - set2 = set(flag_array) - - # Check if every element in set1 has a corresponding element in set2 - all_elements_have_pair = set1.issubset(set2) and set2.issubset(set1) - - not_paired = set1.symmetric_difference(set2) - - if not_paired: - print("Mismatched elements found in documentation.md and vmaware.hpp, make sure to include the technique in both files") - print("Elements without a pair:", not_paired) - sys.exit(1) - - - - -def check_cli(flag_array): - # fetch docs content - with open("../src/cli.cpp", 'r') as cli: - cli_content = cli.readlines() - - # strip whitespace - cli_content = [s.strip() for s in cli_content] - - # filter elements with whitespace - cli_content = [s for s in cli_content if ("checker(" in s)] - - # extract the flags - cli_flags = [] - pattern = r'checker\((.*?),' - for line in cli_content: - match = re.search(pattern, line) - - if match: - cli_flags.append(match.group(1).strip()) - else: - print("Pattern not found in the string.") - - set1 = set(cli_flags) - set2 = set(flag_array) - - # check if every element in set1 has a corresponding element in set2 - not_paired = set1.symmetric_difference(set2) - - if not_paired: - print("Mismatched elements found in cli.cpp and vmaware.hpp, make sure to include the technique in both files") - print("Elements without a pair:", not_paired) - sys.exit(1) - - -raw_content = fetch() -trimmed_content = filter(raw_content) -flags = tokenize(trimmed_content) - -check_docs(flags) -check_cli(flags) \ No newline at end of file diff --git a/auxiliary/cpuid_fuzzer.c b/auxiliary/cpuid_fuzzer.c index 92a804a..6548009 100644 --- a/auxiliary/cpuid_fuzzer.c +++ b/auxiliary/cpuid_fuzzer.c @@ -27,7 +27,6 @@ #include #include #include -#include #if (defined(_MSC_VER) || defined(_WIN32) || defined(_WIN64) || defined(__MINGW32__)) #define MSVC 1 @@ -40,11 +39,13 @@ #endif #if (LINUX) - #define _GNU_SOURCE #include #include #include + #include #include +#else + #include #endif // branching macros @@ -76,6 +77,10 @@ #define proc_trace 0x00000014 // ecx = 0 #define aes 0x00000019 #define avx10 0x00000024 // ecx = 0 +#define vm0 0x40000000 +#define vm1 0x40000001 +#define vm2 0x40000002 +#define vm3 0x40000003 #define extended_proc_info 0x80000001 #define hypervisor 0x40000000 #define max_leaf 0x80000000 @@ -108,114 +113,6 @@ #define breakpoint 10000000 -typedef struct { - void (*taskFunction)(void*); // Function pointer to the task - void* arg; // Argument to the task function -} Task; - -typedef struct { - pthread_t* threads; // Array of thread IDs - Task* taskQueue; // Array to hold tasks - int queueSize; // Size of the task queue - int nextTaskIndex; // Index to insert the next task - int shutdown; // Flag to indicate pool shutdown - pthread_mutex_t mutex; // Mutex for synchronization - pthread_cond_t condition; // Condition variable for task availability -} ThreadPool; - -// function executed by each thread in the pool -void* threadFunction(void* arg) { - ThreadPool* pool = (ThreadPool*)arg; - - while (1) { - pthread_mutex_lock(&pool->mutex); - - while (pool->nextTaskIndex == 0 && !pool->shutdown) { - pthread_cond_wait(&pool->condition, &pool->mutex); - } - - if (pool->shutdown) { - pthread_mutex_unlock(&pool->mutex); - pthread_exit(NULL); - } - - Task task = pool->taskQueue[--pool->nextTaskIndex]; - - pthread_mutex_unlock(&pool->mutex); - - task.taskFunction(task.arg); - } - - return NULL; -} - -// initialize the thread pool -ThreadPool* initializeThreadPool(int poolSize) { - ThreadPool* pool = (ThreadPool*)malloc(sizeof(ThreadPool)); - if (!pool) { - perror("Error creating thread pool"); - exit(EXIT_FAILURE); - } - - pool->threads = (pthread_t*)malloc(poolSize * sizeof(pthread_t)); - pool->taskQueue = (Task*)malloc(poolSize * sizeof(Task)); - pool->queueSize = poolSize; - pool->nextTaskIndex = 0; - pool->shutdown = 0; - - pthread_mutex_init(&pool->mutex, NULL); - pthread_cond_init(&pool->condition, NULL); - - for (int i = 0; i < poolSize; i++) { - if (pthread_create(&pool->threads[i], NULL, threadFunction, (void*)pool) != 0) { - perror("Error creating thread"); - exit(EXIT_FAILURE); - } - } - - return pool; -} - -// submit a task to the thread pool -void submitTask(ThreadPool* pool, void (*taskFunction)(void*), void* arg) { - pthread_mutex_lock(&pool->mutex); - - if (pool->nextTaskIndex == pool->queueSize) { - fprintf(stderr, "Task queue is full. Task not submitted.\n"); - pthread_mutex_unlock(&pool->mutex); - return; - } - - pool->taskQueue[pool->nextTaskIndex].taskFunction = taskFunction; - pool->taskQueue[pool->nextTaskIndex].arg = arg; - pool->nextTaskIndex++; - - pthread_cond_signal(&pool->condition); - - pthread_mutex_unlock(&pool->mutex); -} - -// shutdown the thread pool -void shutdownThreadPool(ThreadPool* pool) { - pthread_mutex_lock(&pool->mutex); - - pool->shutdown = 1; - - pthread_cond_broadcast(&pool->condition); - - pthread_mutex_unlock(&pool->mutex); - - for (int i = 0; i < pool->queueSize; i++) { - pthread_join(pool->threads[i], NULL); - } - - free(pool->threads); - free(pool->taskQueue); - free(pool); -} - - - // basic cpuid wrapper static void cpuid ( @@ -240,7 +137,7 @@ static uint32_t get_highest_leaf() { // scan for predetermined leafs void leaf_mode_fuzzer(const uint64_t p_max_leaf) { uint32_t reg[4]; - const uint32_t leafs[36] = { + const uint32_t leafs[40] = { manufacturer, proc_info, cache_tlb, serial, topology, topology2, management, extended, extended2, @@ -252,10 +149,11 @@ void leaf_mode_fuzzer(const uint64_t p_max_leaf) { brand2, brand3, L1_cache, L2_cache, capabilities, virtual, svm, enc_mem_cap, ext_info2, - amd_easter_egg, centaur_ext, centaur_feature + amd_easter_egg, centaur_ext, centaur_feature, + vm0, vm1, vm2, vm3 }; - const size_t leaf_arr_size = (sizeof(leafs) / sizeof(leafs[0])); + const size_t leaf_arr_size = (sizeof(leafs) / sizeof(leafs[0])); for (int i = 0; i < leaf_arr_size; i++) { if (leafs[i] >= p_max_leaf) { @@ -271,83 +169,14 @@ void leaf_mode_fuzzer(const uint64_t p_max_leaf) { reg[edx] )) { printf("leaf = %d\n", i); - printf("eax = %d\n", reg[eax]); - printf("ebx = %d\n", reg[ebx]); - printf("ecx = %d\n", reg[ecx]); - printf("edx = %d\n\n", reg[edx]); + printf("eax = 0x%0X\n", reg[eax]); + printf("ebx = 0x%0X\n", reg[ebx]); + printf("ecx = 0x%0X\n", reg[ecx]); + printf("edx = 0x%0X\n\n", reg[edx]); } } } -/* -atomic_int counter; - -void scan_mode_worker() { - for (int i = 0; i < divisor; i++) { - int x = start; - - for (; x < limit; x++) { - uint32_t reg[4]; - cpuid(reg, x, null_leaf); - - if (unlikely( - reg[eax] || \ - reg[ebx] || \ - reg[ecx] || \ - reg[edx] - )) { - printf("leaf = %d\n", i); - printf("eax = %d\n", reg[eax]); - printf("ebx = %d\n", reg[ebx]); - printf("ecx = %d\n", reg[ecx]); - printf("edx = %d\n\n", reg[edx]); - fprintf(file, "%s\n", logMessage); - } - } - - const int percent = (((i + 1) * 100) / p_max_leaf); - - printf("[LOG] Reached eax leaf %d (%d%%)\n", atomic_load(&counter), percent); - limit += breakpoint; - start += breakpoint; - } - -} - -// scan mode fuzzer -void scan_mode_fuzzer(const uint64_t p_max_leaf, const int32_t thread_count) { - uint32_t limit = breakpoint; - uint32_t start = 0; - - const int32_t threads = get_nprocs(); - - const uint32_t divisor = (uint32_t)(p_max_leaf / breakpoint); - printf("divisor = %d\n", divisor); - - atomic_init(&counter, 0); - ThreadPool* pool = initializeThreadPool(8); - - // Submit example tasks to the thread pool - for (int i = 0; i < 10; i++) { - int* taskNumber = (int*)malloc(sizeof(int)); - *taskNumber = i; - submitTask(pool, exampleTask, (void*)taskNumber); - } - - // Sleep to allow tasks to complete - sleep(2); - - // Shutdown the thread pool - shutdownThreadPool(pool); -} - - -void exampleTask(void* low_bound, void* upper_bound) { - int taskNumber = *(int*)arg; - printf("Task %d executed by thread %lu\n", taskNumber, pthread_self()); -} -*/ - int main(int argc, char *argv[]) { uint8_t flags = 0; @@ -357,8 +186,6 @@ int main(int argc, char *argv[]) { } else if (argc == 2) { if (strcmp(argv[2], "--leaf") == 0) { flags |= leaf_mode; - } else if (strcmp(argv[2], "--scan") == 0) { - flags |= scan_mode; } else { printf("%s", "Unknown flag provided, aborting\n"); return 1; diff --git a/auxiliary/updater.py b/auxiliary/updater.py index 36abeba..d0fadb1 100644 --- a/auxiliary/updater.py +++ b/auxiliary/updater.py @@ -10,10 +10,21 @@ # # =============================================================== # -# This is an internal script to update the VMAware -# header file's banner automatically and much more reliably. -# For example, it'll update the line numbers for the sections -# header, and other basic information. +# This is an internal script to update various stuff of the project: +# 1. Check whether all of the techniques are actually updated since +# keeping track of the docs, the CLI, and the table isn't easy, +# so I'm automating the checks in case I forget to update any. +# +# 2. Update the line numbers for the sections header based on what +# line they are located, so it's a (tiny) bit easier to understand +# the structure of the headers for anybody reading it for the first +# time, it's more of a guide to point which parts are this and that. +# +# 3. Convert the GPL file (vmaware.hpp) into an MIT file (vmaware_MIT.hpp). +# In other words, it'll remove all the GPL code so that it qualifies +# as MIT compliant. +# +# 4. Update the dates in the banner, example: "1.9 (Septmber 2024)" # # =============================================================== # @@ -22,8 +33,231 @@ # - License: GPL 3.0 +import sys +import re +from datetime import datetime + + +def arg_check(): + def fetch(): + # fetch file content + with open("../src/vmaware.hpp", 'r') as vmaware: + header_content = vmaware.readlines() + + # reversed since the table is at the very end of the vmaware.hpp file + header_content.reverse() + + # breakpoint + keyword = "const std::map VM::core::technique_table = {" + + # fetch index of breakpoint + index_of_keyword = next((i for i, line in enumerate(header_content) if keyword in line), None) + + # remove everything before the breakpoint for simplification + if index_of_keyword is not None: + header_content = header_content[:index_of_keyword + 1] + + return header_content + + + + def filter(raw_content): + trimmed_content = [] + + # filter + trimmed_content = [s for s in raw_content if not ( + s.isspace() or + "//" in s or + ";" in s or + "VM::core::technique" in s + )] + + # strip all whitespace + trimmed_content = [s.strip() for s in trimmed_content] + + return trimmed_content + + + + def tokenize(trimmed_content): + flag_array = [] + + # pattern for VM::FLAG_EXAMPLE1 + pattern = r'\bVM::([A-Z0-9_]+)\b' + + # match and push to flag_array[] + for line in trimmed_content: + match = re.search(pattern, line) + + if match: + flag_array.append(match.group(0)) + else: + print("Unable to find flag variable for " + line) + sys.exit(1) + + return flag_array + + + + def check_docs(flag_array): + # fetch docs content + with open("../docs/documentation.md", 'r') as docs: + docs_content = docs.readlines() + + # strip whitespace + docs_content = [s.strip() for s in docs_content] + + # find indices + start = "# Flag table" + end = "# Non-technique flags" + + # extract the indexes + try: + start_index = docs_content.index(start) + end_index = docs_content.index(end) + except ValueError: + print("Couldn't find range index point \"# Flag table\" or \"# Non-technique flags\"") + start_index = end_index = None + sys.exit(1) + + # extract the range between the aforementioned indexes + if start_index is not None and end_index is not None: + extracted_range = docs_content[start_index + 1:end_index] + docs_content = extracted_range + + # filter elements with whitespace + docs_content = [s for s in docs_content if not s.isspace() and s and "VM::" in s] + + # extract flag string for every line + docs_flags = [] + pattern = r'`([^`]+)`' + for line in docs_content: + match = re.search(pattern, line) + + if match: + docs_flags.append(match.group(1)) + else: + print("Pattern not found in the line \"" + line + "\"") + sys.exit(1) + + set1 = set(docs_flags) + set2 = set(flag_array) + + # Check if every element in set1 has a corresponding element in set2 + all_elements_have_pair = set1.issubset(set2) and set2.issubset(set1) + + not_paired = set1.symmetric_difference(set2) + + if not_paired: + print("Mismatched elements found in documentation.md and vmaware.hpp, make sure to include the technique in both files") + print("Elements without a pair:", not_paired) + sys.exit(1) + + + + + def check_cli(flag_array): + # fetch docs content + with open("../src/cli.cpp", 'r') as cli: + cli_content = cli.readlines() + + # strip whitespace + cli_content = [s.strip() for s in cli_content] + + # filter elements with whitespace + cli_content = [s for s in cli_content if ("checker(" in s)] + + # extract the flags + cli_flags = [] + pattern = r'checker\((.*?),' + for line in cli_content: + match = re.search(pattern, line) + + if match: + cli_flags.append(match.group(1).strip()) + else: + print("Pattern not found in the string.") + + set1 = set(cli_flags) + set2 = set(flag_array) + + # check if every element in set1 has a corresponding element in set2 + not_paired = set1.symmetric_difference(set2) + + if not_paired: + print("Mismatched elements found in cli.cpp and vmaware.hpp, make sure to include the technique in both files") + print("Elements without a pair:", not_paired) + sys.exit(1) + + + raw_content = fetch() + trimmed_content = filter(raw_content) + flags = tokenize(trimmed_content) + + check_docs(flags) + check_cli(flags) + + + + + + + + + + + +def update_MIT(): + original = '../src/vmaware.hpp' + mit = '../src/vmaware_MIT.hpp' + gpl_string = '/* GPL */' + license_string = ' * - License: GPL-3.0 (https://www.gnu.org/licenses/gpl-3.0.html)' + mit_full_license = ''' * - License: MIT + * + * MIT License + * + * Copyright (c) 2024 kernelwernel + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. +''' + + with open(original, 'r') as file: + lines = file.readlines() + + add_string_added = False + + filtered_lines = [] + for line in lines: + if gpl_string in line: + # skip + continue + if license_string in line: + filtered_lines.append(mit_full_license) + else: + filtered_lines.append(line) + + with open(mit, 'w') as file: + file.writelines(filtered_lines) + + -def update(filename): +def update_sections(filename): with open(filename, 'r') as vmaware_read: header_content = vmaware_read.readlines() @@ -120,5 +354,73 @@ def update(filename): file.writelines(header_content) -update("../src/vmaware.hpp") -update("../src/vmaware_MIT.hpp") \ No newline at end of file + +def update_date(filename): + # fetch the first arg, which is supposed to be the new version number for a new release + args = sys.argv + first_arg = args[1] if len(args) > 1 else None + + + with open(filename, 'r') as file: + header_content = file.readlines() + + index = 0 + banner_line = " * ╚═══╝ ╚═╝ ╚═╝╚═╝ ╚═╝ ╚══╝╚══╝ ╚═╝ ╚═╝╚═╝ ╚═╝╚══════╝ " + + # fetch the index of where the line should be updated + for line in header_content: + if (banner_line not in line): + index += 1 + else: + break + + # find "X.X", where X is an integral + def find_pattern(base_str): + pattern = r'\d+\.\d+' + + # Search for the pattern in the text + match = re.search(pattern, base_str) + + # find match + if match: + return match.group() + print("match found") + else: + print(f"Version number not found for {base_str}, aborting") + sys.exit(1) + + + # fetch the new version + header_version = find_pattern(header_content[index]) + if first_arg == None: + arg_version = header_version + else: + arg_version = find_pattern(first_arg) + + new_version = "" + new_date = "" + + # set the version and date + new_version = arg_version + new_date = datetime.now().strftime("%B") + " " + str(datetime.now().year) + + # this will be the new content + new_content = banner_line + new_version + " (" + new_date + ")" + + if 0 < index <= len(header_content): + header_content[index] = new_content + '\n' + else: + print(f"Line number {line_number} is out of range.") + sys.exit(1) + + with open(filename, 'w') as file: + file.writelines(header_content) + + + +arg_check() +update_MIT() +update_sections("../src/vmaware.hpp") +update_sections("../src/vmaware_MIT.hpp") +update_date("../src/vmaware.hpp") +update_date("../src/vmaware_MIT.hpp") \ No newline at end of file diff --git a/docs/documentation.md b/docs/documentation.md index 50586c4..9ad869f 100644 --- a/docs/documentation.md +++ b/docs/documentation.md @@ -266,10 +266,6 @@ int main() { } ``` -
- - -
## `VM::add_custom()` @@ -315,6 +311,74 @@ VM::add_custom(50, new_technique);
+## `VM::type()` +This will return the VM type as a `std::string` based on the brand found. The possible return values can be: +- `Hypervisor (type 1)` +- `Hypervisor (type 2)` +- `Sandbox` +- `Emulator` +- `Emulator/Hypervisor (type 2)` +- `Partitioning Hypervisor` +- `Container` +- `Hypervisor (either type 1 or 2)` +- `Hypervisor (unknown type)` +- `Compatibility layer` +- `Paravirtualised/Hypervisor (type 2)` +- `Hybrid Hyper-V (type 1 and 2)` +- `Binary Translation Layer/Emulator` +- `Unknown` + +
+ +## `VM::conclusion()` +This will return the "conclusion" message of what the overall result is as a `std::string`. The `[brand]` part might contain a brand or may as well be empty, depending on whether a brand has been found. +- `Running in baremetal` +- `Very unlikely a [brand] VM` +- `Unlikely a [brand] VM` +- `Potentially a [brand] VM` +- `Might be a [brand] VM` +- `Likely a [brand] VM` +- `Very likely a [brand] VM` +- `Running inside a [brand] VM` + +
+ +# vmaware struct +If you prefer having an object to store all the relevant information about the program's environment, you can use the `VM::vmaware` struct: + +```cpp +struct vmaware { + bool is_vm; + std::uint8_t percentage; + std::uint8_t detected_count; + std::uint8_t technique_count; + std::string brand; + std::string type; + std::string conclusion; +}; +``` + +example: +```cpp +#include "vmaware.hpp" +#include + +int main() { + VM::vmaware vm; + + std::cout << "Is this a VM? = " << vm.is_vm << "\n"; + std::cout << "How many techniques detected a VM? = " << vm.detected_count << "%\n"; + std::cout << "What's the overview in a human-readable message?" << vm.conclusion << "\n"; +} +``` + +> [!NOTE] +> the flag system is compatible for the struct constructor. + + +
+ + # Flag table VMAware provides a convenient way to not only check for VMs, but also have the flexibility and freedom for the end-user to choose what techniques are used with complete control over what gets executed or not. This is handled with a flag system. @@ -405,7 +469,7 @@ VMAware provides a convenient way to not only check for VMs, but also have the f | `VM::MUTEX` | Check for mutex strings of VM brands | Windows | 85% | | | | | | | `VM::UPTIME` | Check if uptime is less than or equal to 2 minutes | | 10% | | | | Spoofable | | | `VM::ODD_CPU_THREADS` | Check for odd CPU threads, usually a sign of modification through VM setting because 99% of CPUs have even numbers of threads | | 80% | | | | | | -| `VM::INTEL_THREAD_MISMATCH` | Check for Intel CPU thread count database if it matches the system's thread count | | 85% | | | | | | +| `VM::INTEL_THREAD_MISMATCH` | Check for Intel CPU thread count database if it matches the system's thread count | | 60% | | | | | | | `VM::XEON_THREAD_MISMATCH` | Same as above, but for Xeon Intel CPUs | | 85% | | | | | | | `VM::NETTITUDE_VM_MEMORY` | Check for memory regions to detect VM-specific brands | Windows | 75% | | | | | | | `VM::CPUID_BITSET` | Check for CPUID technique by checking whether all the bits equate to more than 4000 | | 20% | | | | | | @@ -435,6 +499,8 @@ VMAware provides a convenient way to not only check for VMs, but also have the f | `VM::SMBIOS_VM_BIT` | Check for the VM bit in the SMBIOS data | Linux | 50% | | | | | | | `VM::PODMAN_FILE` | Check for podman file in /run/ | Linux | 15% | | | | Spoofable | | | `VM::WSL_PROC` | Check for WSL or microsoft indications in /proc/ subdirectories | Linux | 30% | | | | | | +| `VM::ANYRUN_DRIVER` | Check for any.run driver presence | Windows | 65% | | | | | | +| `VM::ANYRUN_DIRECTORY` | Check for any.run directory and handle the status code | Windows | 35% | | | | | |
diff --git a/src/cli.cpp b/src/cli.cpp index 1ab2ee3..2f40036 100644 --- a/src/cli.cpp +++ b/src/cli.cpp @@ -55,7 +55,10 @@ constexpr const char* red_orange = "\x1B[38;2;247;127;40m"; constexpr const char* green_orange = "\x1B[38;2;174;197;59m"; constexpr const char* grey = "\x1B[38;2;108;108;108m"; -enum arg_enum : std::uint8_t { +using u8 = std::uint8_t; +using u32 = std::uint32_t; + +enum arg_enum : u8 { HELP, VERSION, ALL, @@ -73,7 +76,7 @@ enum arg_enum : std::uint8_t { }; std::bitset<14> arg_bitset; -const std::uint8_t max_bits = static_cast(VM::MULTIPLE) + 1; +const u8 max_bits = static_cast(VM::MULTIPLE) + 1; #if (MSVC) class win_ansi_enabler_t @@ -108,9 +111,6 @@ class win_ansi_enabler_t }; #endif -// for the technique counts -std::uint8_t detected_count = 0; - [[noreturn]] void help(void) { std::cout << @@ -124,10 +124,10 @@ R"(Usage: -a | --all run the result with ALL the techniques enabled (might contain false positives) -d | --detect returns the result as a boolean (1 = VM, 0 = baremetal) -s | --stdout returns either 0 or 1 to STDOUT without any text output (0 = VM, 1 = baremetal) - -b | --brand returns the VM brand string (consult documentation for full output list) + -b | --brand returns the VM brand string + -l | --brand-list returns all the possible VM brand string values -p | --percent returns the VM percentage between 0 and 100 -c | --conclusion returns the conclusion message string - -l | --brand-list returns all the possible VM brand string values -n | --number returns the number of VM detection techniques it performs -t | --type returns the VM type (if a VM was found) @@ -150,7 +150,7 @@ R"(Usage: std::exit(0); } -const char* color(const std::uint8_t score) { +const char* color(const u8 score) { if (score == 0) { return red; } else if (score <= 12) { return red; } else if (score <= 25) { return red_orange; } @@ -163,36 +163,6 @@ const char* color(const std::uint8_t score) { return ""; } -std::string message(const std::uint8_t score, const std::string &brand) { - constexpr const char* baremetal = "Running in baremetal"; - constexpr const char* very_unlikely = "Very unlikely a VM"; - constexpr const char* unlikely = "Unlikely a VM"; - - std::string potentially = "Potentially a VM"; - std::string might = "Might be a VM"; - std::string likely = "Likely a VM"; - std::string very_likely = "Very likely a VM"; - std::string inside_vm = "Running inside a VM"; - - if (brand != "Unknown") { - potentially = "Potentially a " + brand + " VM"; - might = "Might be a " + brand + " VM"; - likely = "Likely a " + brand + " VM"; - very_likely = "Very likely a " + brand + " VM"; - inside_vm = "Running inside a " + brand + " VM"; - } - - if (score == 0) { return baremetal; } - else if (score <= 20) { return very_unlikely; } - else if (score <= 35) { return unlikely; } - else if (score < 50) { return potentially; } - else if (score <= 62) { return might; } - else if (score <= 75) { return likely; } - else if (score < 100) { return very_likely; } - else if (score == 100) { return inside_vm; } - - return "Unknown error"; -} [[noreturn]] void brand_list() { std::cout << @@ -257,87 +227,6 @@ ANY.RUN std::exit(0); } -std::string type(const std::string &brand_str) { - if (brand_str.find(" or ") != std::string::npos) { - return "Unknown"; - } - - const std::map type_table { - // type 1 - { "Xen HVM", "Hypervisor (type 1)" }, - { "VMware ESX", "Hypervisor (type 1)" }, - { "ACRN", "Hypervisor (type 1)" }, - { "QNX hypervisor", "Hypervisor (type 1)" }, - { "Microsoft Hyper-V", "Hypervisor (type 1)" }, - { "Microsoft Azure Hyper-V", "Hypervisor (type 1)" }, - { "Xbox NanoVisor (Hyper-V)", "Hypervisor (type 1)" }, - { "KVM ", "Hypervisor (type 1)" }, - { "bhyve", "Hypervisor (type 1)" }, - { "KVM Hyper-V Enlightenment", "Hypervisor (type 1)" }, - { "QEMU+KVM Hyper-V Enlightenment", "Hypervisor (type 1)" }, - { "QEMU+KVM", "Hypervisor (type 1)" }, - { "Intel HAXM", "Hypervisor (type 1)" }, - { "Intel KGT (Trusty)", "Hypervisor (type 1)" }, - { "SimpleVisor", "Hypervisor (type 1)" }, - { "Google Compute Engine (KVM)", "Hypervisor (type 1)" }, - { "OpenStack (KVM)", "Hypervisor (type 1)" }, - { "KubeVirt (KVM)", "Hypervisor (type 1)" }, - { "IBM PowerVM", "Hypervisor (type 1)" }, - { "AWS Nitro System EC2 (KVM-based)", "Hypervisor (type 1)" }, - - // type 2 - { "VirtualBox", "Hypervisor (type 2)" }, - { "VMware", "Hypervisor (type 2)" }, - { "VMware Express", "Hypervisor (type 2)" }, - { "VMware GSX", "Hypervisor (type 2)" }, - { "VMware Workstation", "Hypervisor (type 2)" }, - { "VMware Fusion", "Hypervisor (type 2)" }, - { "Parallels", "Hypervisor (type 2)" }, - { "Virtual PC", "Hypervisor (type 2)" }, - { "NetBSD NVMM", "Hypervisor (type 2)" }, - { "OpenBSD VMM", "Hypervisor (type 2)" }, - { "User-mode Linux", "Hypervisor (type 2)" }, - - // sandbox - { "Cuckoo", "Sandbox" }, - { "Sandboxie", "Sandbox" }, - { "Hybrid Analysis", "Sandbox" }, - { "CWSandbox", "Sandbox" }, - { "JoeBox", "Sandbox" }, - { "Anubis", "Sandbox" }, - { "Comodo", "Sandbox" }, - { "ThreatExpert", "Sandbox" }, - { "ANY.RUN", "Sandbox"}, - - // misc - { "Bochs", "Emulator" }, - { "BlueStacks", "Emulator" }, - { "Microsoft x86-to-ARM", "Emulator" }, - { "QEMU", "Emulator" }, - { "Jailhouse", "Partitioning Hypervisor" }, - { "Unisys s-Par", "Partitioning Hypervisor" }, - { "Docker", "Container" }, - { "Podman", "Container" }, - { "OpenVZ", "Container" }, - { "Microsoft Virtual PC/Hyper-V", "Hypervisor (either type 1 or 2)" }, - { "Lockheed Martin LMHS", "Hypervisor (unknown type)" }, - { "Wine", "Compatibility layer" }, - { "Apple VZ", "Unknown" }, - { "Hyper-V artifact (not an actual VM)", "No VM" }, - { "User-mode Linux", "Paravirtualised" }, - { "WSL", "Hybrid Hyper-V (type 1 and 2)" }, // debatable tbh - { "Apple Rosetta 2", "Binary Translation Layer/Emulator" }, - }; - - auto it = type_table.find(brand_str); - - if (it != type_table.end()) { - return it->second; - } - - return "Unknown"; -} - bool is_spoofable(const VM::enum_flags flag) { if (arg_bitset.test(ALL)) { return false; @@ -460,7 +349,8 @@ std::bitset settings() { void general() { const std::string detected = ("[ " + std::string(green) + "DETECTED" + std::string(ansi_exit) + " ]"); const std::string not_detected = ("[" + std::string(red) + "NOT DETECTED" + std::string(ansi_exit) + "]"); - const std::string spoofable = ("[" + std::string(red) + " SPOOFABLE " + std::string(ansi_exit) + "]"); + //const std::string spoofable = ("[" + std::string(red) + " SPOOFABLE " + std::string(ansi_exit) + "]"); + const std::string spoofable = ("[" + std::string(red) + " EASY SPOOF " + std::string(ansi_exit) + "]"); const std::string note = ("[ NOTE ]"); const std::string no_perms = ("[" + std::string(grey) + " NO PERMS " + std::string(ansi_exit) + "]"); const std::string disabled = ("[" + std::string(grey) + " DISABLED " + std::string(ansi_exit) + "]"); @@ -469,7 +359,7 @@ void general() { auto checker = [&](const VM::enum_flags flag, const char* message) -> void { if (is_spoofable(flag)) { if (!arg_bitset.test(SPOOFABLE)) { - std::cout << spoofable << " Skipped " << message << "\n"; + std::cout << spoofable << " Skipped " << message << "\n"; return; } } @@ -488,7 +378,6 @@ void general() { if (VM::check(flag)) { std::cout << detected << " Checking " << message << "...\n"; - detected_count++; } else { std::cout << not_detected << " Checking " << message << "...\n"; } @@ -631,90 +520,104 @@ void general() { std::cout << "[DEBUG] theoretical maximum points: " << VM::total_points << "\n"; #endif - std::string brand = VM::brand(VM::MULTIPLE, settings()); + // struct containing the whole overview of the VM data + VM::vmaware vm(VM::MULTIPLE, settings()); - std::cout << "VM brand: " << ((brand == "Unknown") || (brand == "Hyper-V artifact (not an actual VM)") ? red : green) << brand << ansi_exit << "\n"; - // meaning "if there's no brand conflicts" - if (brand.find(" or ") == std::string::npos) { - const std::string type_value = type(brand); + // brand manager + { + std::cout << "VM brand: " << ((vm.brand == "Unknown") || (vm.brand == "Hyper-V artifact (not an actual VM)") ? red : green) << vm.brand << ansi_exit << "\n"; + } - std::cout << "VM type: "; - std::string color = ""; - - if (type_value == "Unknown" || type_value == "No VM") { - color = red; - } else { - color = green; - } + // type manager + { + if (vm.brand.find(" or ") == std::string::npos) { // meaning "if there's no brand conflicts" + std::cout << "VM type: "; - std::cout << color << type_value << ansi_exit << "\n"; - } + std::string color = ""; - const char* percent_color = ""; - const std::uint8_t percent = VM::percentage(settings()); + if (vm.type == "Unknown") { + color = red; + } else { + color = green; + } - if (percent == 0) { percent_color = red; } - else if (percent < 25) { percent_color = red_orange; } - else if (percent < 50) { percent_color = orange; } - else if (percent < 75) { percent_color = green_orange; } - else { percent_color = green; } + std::cout << color << vm.type << ansi_exit << "\n"; + } + } - std::cout << "VM likeliness: " << percent_color << static_cast(percent) << "%" << ansi_exit << "\n"; - const bool is_detected = VM::detect(settings()); + // percentage manager + { + const char* percent_color = ""; - std::cout << "VM confirmation: " << (is_detected ? green : red) << std::boolalpha << is_detected << std::noboolalpha << ansi_exit << "\n"; + if (vm.percentage == 0) { percent_color = red; } + else if (vm.percentage < 25) { percent_color = red_orange; } + else if (vm.percentage < 50) { percent_color = orange; } + else if (vm.percentage < 75) { percent_color = green_orange; } + else { percent_color = green; } + + std::cout << "VM likeliness: " << percent_color << static_cast(vm.percentage) << "%" << ansi_exit << "\n"; + } - const char* count_color = ""; - switch (detected_count) { - case 0: count_color = red; break; - case 1: count_color = red_orange; break; - case 2: count_color = orange; break; - case 3: count_color = orange; break; - case 4: count_color = green_orange; break; - default: - // anything over 4 is green - count_color = green; + // VM confirmation manager + { + std::cout << "VM confirmation: " << (vm.is_vm ? green : red) << std::boolalpha << vm.is_vm << std::noboolalpha << ansi_exit << "\n"; } - std::cout << - "VM detections: " << - count_color << - static_cast(detected_count) << - "/" << - static_cast(VM::technique_count) << - ansi_exit << - "\n\n"; -#if (MSVC) - using brand_score_t = std::int32_t; -#else - using brand_score_t = std::uint8_t; -#endif + // detection count manager + { + const char* count_color = ""; + + switch (vm.detected_count) { + case 0: count_color = red; break; + case 1: count_color = red_orange; break; + case 2: count_color = orange; break; + case 3: count_color = orange; break; + case 4: count_color = green_orange; break; + default: + // anything over 4 is green + count_color = green; + } + + std::cout << + "VM detections: " << + count_color << + static_cast(vm.detected_count) << + "/" << + static_cast(vm.technique_count) << + ansi_exit << + "\n\n"; + } - std::map brand_map = VM::brand_map(); - const char* conclusion_color = color(percent); - std::string conclusion_message = message(percent, brand); + // conclusion manager + { + const char* conclusion_color = color(vm.percentage); + + std::cout + << bold + << "====== CONCLUSION: " + << ansi_exit + << conclusion_color << vm.conclusion << " " << ansi_exit + << bold + << "======" + << ansi_exit + << "\n\n"; + } + - std::cout - << bold - << "====== CONCLUSION: " - << ansi_exit - << conclusion_color << conclusion_message << " " << ansi_exit - << bold - << "======" - << ansi_exit - << "\n\n"; + // finishing touches with notes + if (notes_enabled) { + if ((vm.brand == "Hyper-V artifact (not an actual VM)")) { + std::cout << note << " The result means that the CLI has found Hyper-V, but as an artifact instead of an actual VM. This means that although the hardware values in fact match with Hyper-V due to how it's designed by Microsoft, the CLI has determined you are NOT in a Hyper-V VM.\n\n"; + } - if ((brand == "Hyper-V artifact (not an actual VM)") && notes_enabled) { - std::cout << note << " The result means that the CLI has found Hyper-V, but as an artifact instead of an actual VM. This means that although the hardware values in fact match with Hyper-V due to how it's designed by Microsoft, the CLI has determined you are NOT in a Hyper-V VM.\n\n"; - } else if (notes_enabled) { if (!arg_bitset.test(SPOOFABLE)) { - std::cout << tip << "To enable spoofable techniques, run with the \"--spoofable\" argument\n\n"; + std::cout << tip << "To enable easily spoofable techniques, run with the \"--spoofable\" argument\n\n"; } else { std::cout << note << " If you found a false positive, please make sure to create an issue at https://github.com/kernelwernel/VMAware/issues\n\n"; } @@ -728,7 +631,7 @@ int main(int argc, char* argv[]) { #endif const std::vector args(argv + 1, argv + argc); // easier this way - const std::uint32_t arg_count = argc - 1; + const u32 arg_count = argc - 1; if (arg_count == 0) { general(); @@ -797,19 +700,19 @@ int main(int argc, char* argv[]) { } if (arg_bitset.test(NUMBER)) { - std::cout << static_cast(VM::technique_count) << "\n"; + std::cout << static_cast(VM::technique_count) << "\n"; return 0; } // critical returners - const std::uint32_t returners = ( - static_cast(arg_bitset.test(STDOUT)) + - static_cast(arg_bitset.test(PERCENT)) + - static_cast(arg_bitset.test(DETECT)) + - static_cast(arg_bitset.test(BRAND)) + - static_cast(arg_bitset.test(TYPE)) + - static_cast(arg_bitset.test(CONCLUSION)) + const u32 returners = ( + static_cast(arg_bitset.test(STDOUT)) + + static_cast(arg_bitset.test(PERCENT)) + + static_cast(arg_bitset.test(DETECT)) + + static_cast(arg_bitset.test(BRAND)) + + static_cast(arg_bitset.test(TYPE)) + + static_cast(arg_bitset.test(CONCLUSION)) ); if (returners > 0) { // at least one of the options are set @@ -823,7 +726,7 @@ int main(int argc, char* argv[]) { } if (arg_bitset.test(PERCENT)) { - std::cout << static_cast(VM::percentage(VM::NO_MEMO, settings())) << "\n"; + std::cout << static_cast(VM::percentage(VM::NO_MEMO, settings())) << "\n"; return 0; } @@ -838,18 +741,12 @@ int main(int argc, char* argv[]) { } if (arg_bitset.test(TYPE)) { - const std::string brand = VM::brand(VM::NO_MEMO, VM::MULTIPLE, settings()); - std::cout << type(brand) << "\n"; + std::cout << VM::type(VM::NO_MEMO, VM::MULTIPLE, settings()) << "\n"; return 0; } if (arg_bitset.test(CONCLUSION)) { - std::uint8_t percent = 0; - - percent = VM::percentage(VM::NO_MEMO, settings()); - - const std::string brand = VM::brand(VM::MULTIPLE, settings()); - std::cout << message(percent, brand) << "\n"; + std::cout << VM::conclusion(VM::NO_MEMO, VM::MULTIPLE, settings()) << "\n"; return 0; } } diff --git a/src/vmaware.hpp b/src/vmaware.hpp index 2dd01f6..fc37c98 100644 --- a/src/vmaware.hpp +++ b/src/vmaware.hpp @@ -23,14 +23,14 @@ * * * ================================ SECTIONS ================================== - * - enums for publicly accessible techniques => line 322 - * - struct for internal cpu operations => line 581 - * - struct for internal memoization => line 1007 - * - struct for internal utility functions => line 1134 - * - struct for internal core components => line 9152 - * - start of internal VM detection techniques => line 2409 - * - start of public VM detection functions => line 9495 - * - start of externally defined variables => line 10095 + * - enums for publicly accessible techniques => line 324 + * - struct for internal cpu operations => line 588 + * - struct for internal memoization => line 1014 + * - struct for internal utility functions => line 1142 + * - struct for internal core components => line 9157 + * - start of internal VM detection techniques => line 2438 + * - start of public VM detection functions => line 9519 + * - start of externally defined variables => line 10357 * * * ================================ EXAMPLE ================================== @@ -49,6 +49,8 @@ * } */ +#pragma once + #if (defined(_MSC_VER) || defined(_WIN32) || defined(_WIN64) || defined(__MINGW32__)) #define MSVC 1 #define LINUX 0 @@ -349,18 +351,18 @@ struct VM { DISK_SIZE, VBOX_DEFAULT, VBOX_NETWORK, - COMPUTER_NAME, // GPL - WINE_CHECK, // GPL - HOSTNAME, // GPL - MEMORY, // GPL - VBOX_WINDOW_CLASS, // GPL - LOADED_DLLS, // GPL - KVM_REG, // GPL - KVM_DRIVERS, // GPL - KVM_DIRS, // GPL - AUDIO, // GPL - QEMU_DIR, // GPL - MOUSE_DEVICE, // GPL +/* GPL */ COMPUTER_NAME, +/* GPL */ WINE_CHECK, +/* GPL */ HOSTNAME, +/* GPL */ MEMORY, +/* GPL */ VBOX_WINDOW_CLASS, +/* GPL */ LOADED_DLLS, +/* GPL */ KVM_REG, +/* GPL */ KVM_DRIVERS, +/* GPL */ KVM_DIRS, +/* GPL */ AUDIO, +/* GPL */ QEMU_DIR, +/* GPL */ MOUSE_DEVICE, VM_PROCESSES, LINUX_USER_HOST, GAMARUE, @@ -438,7 +440,7 @@ struct VM { ANYRUN_DRIVER, ANYRUN_DIRECTORY, - // start of non-technique flags (THE ORDERING IS VERY SPECIFIC HERE AND MIGHT BREAK SOMETHING IF RE-ORDERED) + // start of settings technique flags (THE ORDERING IS VERY SPECIFIC HERE AND MIGHT BREAK SOMETHING IF RE-ORDERED) NO_MEMO, HIGH_THRESHOLD, NULL_ARG, // does nothing, just a placeholder flag mainly for the CLI @@ -448,7 +450,7 @@ struct VM { private: static constexpr u8 enum_size = MULTIPLE; // get enum size through value of last element - static constexpr u8 non_technique_count = MULTIPLE - NO_MEMO + 1; // get number of non-technique flags like VM::NO_MEMO for example + static constexpr u8 non_technique_count = MULTIPLE - NO_MEMO + 1; // get number of settings technique flags like VM::NO_MEMO for example static constexpr u8 INVALID = 255; // explicit invalid technique macro static constexpr u16 maximum_points = 4765; // theoretical total points if all VM detections returned true (which is practically impossible) static constexpr u16 high_threshold_score = 300; // new threshold score from 100 to 350 if VM::HIGH_THRESHOLD flag is enabled @@ -463,6 +465,11 @@ struct VM { static constexpr u8 non_technique_begin = NO_MEMO; static constexpr u8 non_technique_end = enum_end; + + // this is specifically meant for VM::detected_count() to + // get the total number of techniques that detected a VM + static u8 detected_count_num; + public: static constexpr u8 technique_count = NO_MEMO; // get total number of techniques static std::vector technique_vector; @@ -763,6 +770,14 @@ struct VM { u32 sig_reg[3] = { 0 }; + if ( + (sig_reg[0] == 0) && + (sig_reg[1] == 0) && + (sig_reg[2] == 0) + ) { + return { "", "" }; + } + if (!cpuid_thingy(p_leaf, sig_reg, 1)) { return { "", "" }; } @@ -772,6 +787,11 @@ struct VM { return str; }; + // the reason why there's 2 is because depending on the leaf, + // the last 4 characters might be switched with the middle + // characters for some fuckin reason, idk why this is even a thing + // so this function basically returns the same string but with + // the 4~8 and 8~12 characters switched for one, and the other isn't. std::stringstream ss; std::stringstream ss2; @@ -1131,6 +1151,7 @@ struct VM { }; }; + // miscellaneous functionalities struct util { #if (LINUX) @@ -1163,7 +1184,7 @@ struct VM { } std::vector buffer((std::istreambuf_iterator(file)), - std::istreambuf_iterator()); + std::istreambuf_iterator()); file.close(); @@ -1213,7 +1234,7 @@ struct VM { return ( (uid != euid) || (euid == 0) - ); + ); #elif (MSVC) BOOL is_admin = FALSE; HANDLE hToken = NULL; @@ -1671,7 +1692,16 @@ struct VM { /** * @brief Checks whether Hyper-V host artifacts are present instead of an actual Hyper-V VM - * @note idea and credits to Requiem (https://github.com/NotRequiem) + * @note Hyper-V has an obscure feature where if it's enabled in the host system, the CPU + * hardware values makes it look like the whole system is running inside Hyper-V, + * which isn't true. This makes it a challenge to determine whether the hardware + * values the library is collecting is either a real Hyper-V VM, or just the artifacts + * of what Hyper-V has left as a consequence of having it enabled in the host system. + * The reason why this is a problem is because the library might falsely conclude that + * your the host system is running in Hyper-V, which is a false positive. This is where + * the Hyper-X mechanism comes into play to distinguish between these two. + * @author idea by Requiem (https://github.com/NotRequiem) + * @link graph to explain how this works: https://github.com/kernelwernel/VMAware/blob/main/assets/Hyper-X.png */ [[nodiscard]] static bool hyper_x() { #if (!MSVC) @@ -1682,73 +1712,81 @@ struct VM { return memo::hyperv::fetch(); } - auto add = [](const bool result) -> bool { - memo::hyperv::store(result); - if (result == true) { - core::add(HYPERV_ARTIFACT); - } - - return result; + auto root_partition = []() -> bool { + u32 ebx, unused = 0; + cpu::cpuid(unused, ebx, unused, unused, 0x40000003); + return (ebx & 1); }; - char out[sizeof(int32_t) * 4 + 1] = { 0 }; // e*x size + number of e*x registers + null terminator - cpu::cpuid((int*)out, cpu::leaf::hypervisor); + auto eax = []() -> bool { + char out[sizeof(int32_t) * 4 + 1] = { 0 }; // e*x size + number of e*x registers + null terminator + cpu::cpuid((int*)out, cpu::leaf::hypervisor); - const u32 eax = static_cast(out[0]); + const u32 eax = static_cast(out[0]); - core_debug("HYPER_X: eax = ", eax); + core_debug("HYPER_X: eax = ", eax); - const bool is_eax_valid = ((eax == 11) || (eax == 12)); + return ((eax == 11) || (eax == 12)); + }; - const std::array cpu = cpu::cpu_manufacturer(cpu::leaf::hypervisor); + auto cpu_vmid = []() -> bool { + const auto cpu = cpu::cpu_manufacturer(cpu::leaf::hypervisor); - const bool is_cpu_hyperv = ( - (cpu.at(0) == "Microsoft Hv") || - (cpu.at(1) == "Microsoft Hv") - ); - - if (is_eax_valid || is_cpu_hyperv) { - // SMBIOS check - const std::string smbios = SMBIOS_string(); + return ( + (cpu.at(0) == "Microsoft Hv") || + (cpu.at(1) == "Microsoft Hv") + ); + }; - core_debug("HYPER_X: SMBIOS string = ", smbios); + // must require at least 2 to continue + const u8 points = (root_partition() + eax() + cpu_vmid()); - if (smbios == "VIRTUAL MACHINE") { - return add(false); - } + if (points >= 2) { + // SMBIOS check + auto is_smbios_hyperv = []() -> bool { + const std::string smbios = SMBIOS_string(); + core_debug("HYPER_X: SMBIOS string = ", smbios); + return (smbios == "VIRTUAL MACHINE"); + }; // motherboard check - const bool motherboard = motherboard_string(L"Microsoft Corporation"); - - core_debug("HYPER_X: motherboard string = ", motherboard); - - if (motherboard) { - return add(false); - } + auto is_motherboard_hyperv = []() -> bool { + const bool motherboard = motherboard_string(L"Microsoft Corporation"); + core_debug("HYPER_X: motherboard string match = ", motherboard); + return motherboard; + }; // event log check (slow, so in last place) - std::wstring logName = L"Microsoft-Windows-Kernel-PnP/Configuration"; - std::vector searchStrings = { L"Virtual_Machine", L"VMBUS" }; - - const bool event_log = util::query_event_logs(logName, searchStrings); + auto is_event_log_hyperv = []() -> bool { + std::wstring logName = L"Microsoft-Windows-Kernel-PnP/Configuration"; + std::vector searchStrings = { L"Virtual_Machine", L"VMBUS" }; + + return (util::query_event_logs(logName, searchStrings)); + }; - if (event_log) { - return add(false); - } + // "if it's hyper-v and NOT an artifact" + const bool is_hyperv = ( + is_smbios_hyperv() || + is_motherboard_hyperv() || + is_event_log_hyperv() + ); - // at this point, it's fair to assume it's Hyper-V artifacts on - // host since none of the "VM-only" techniques returned true - return add(true); - //} else if () { - // actual Hyper-V VM, might do something within this scope in the future idk - //return add(false); - } else { - return add(false); + memo::hyperv::store(is_hyperv); + + if (is_hyperv) { + return true; + } else { + core::add(HYPERV_ARTIFACT); + return false; + } } + + memo::hyperv::store(false); + return false; #endif } @@ -3611,485 +3649,461 @@ struct VM { } - /** - * @brief Check if the computer name (not username to be clear) is VM-specific - * @category Windows - * @author InviZzzible project - * @copyright GPL-3.0 - */ - [[nodiscard]] static bool computer_name_match() try { -#if (!MSVC) - return false; -#else - auto out_length = MAX_PATH; - std::vector comp_name(static_cast(out_length), 0); - GetComputerNameA((LPSTR)comp_name.data(), (LPDWORD)&out_length); - - auto compare = [&](const std::string& s) -> bool { - return (std::strcmp((LPCSTR)comp_name.data(), s.c_str()) == 0); - }; - - debug("COMPUTER_NAME: fetched = ", (LPCSTR)comp_name.data()); - - if (compare("InsideTm") || compare("TU-4NH09SMCG1HC")) { // anubis - debug("COMPUTER_NAME: detected Anubis"); - return core::add(ANUBIS); - } - - if (compare("klone_x64-pc") || compare("tequilaboomboom")) { // general - debug("COMPUTER_NAME: detected general (VM but unknown)"); - return true; - } - - return false; -#endif - } - catch (...) { - debug("COMPUTER_NAME: caught error, returned false"); - return false; - } - - - /** - * @brief Check wine_get_unix_file_name file for Wine - * @author pafish project - * @link https://github.com/a0rtega/pafish/blob/master/pafish/wine.c - * @category Windows - * @copyright GPL-3.0 - */ - [[nodiscard]] static bool wine() try { -#if (!MSVC) - return false; -#else - HMODULE k32; - k32 = GetModuleHandle(TEXT("kernel32.dll")); - - if (k32 != NULL) { - if (GetProcAddress(k32, "wine_get_unix_file_name") != NULL) { - return core::add(WINE); - } - } - - return false; -#endif - } - catch (...) { - debug("WINE_CHECK: caught error, returned false"); - return false; - } - - - /** - * @brief Check if hostname is specific - * @author InviZzzible project - * @category Windows - * @copyright GPL-3.0 - */ - [[nodiscard]] static bool hostname_match() try { -#if (!MSVC) - return false; -#else - auto out_length = MAX_PATH; - std::vector dns_host_name(static_cast(out_length), 0); - GetComputerNameExA(ComputerNameDnsHostname, (LPSTR)dns_host_name.data(), (LPDWORD)&out_length); - - debug("HOSTNAME: ", (LPCSTR)dns_host_name.data()); - - return (!lstrcmpiA((LPCSTR)dns_host_name.data(), "SystemIT")); -#endif - } - catch (...) { - debug("HOSTNAME: caught error, returned false"); - return false; - } - - - /** - * @brief Check if memory space is far too low for a physical machine - * @author Al-Khaser project - * @category x86? - * @copyright GPL-3.0 - */ - [[nodiscard]] static bool low_memory_space() try { - constexpr u64 min_ram_1gb = (1024LL * (1024LL * (1024LL * 1LL))); - const u64 ram = util::get_memory_space(); - - debug("MEMORY: ram size (GB) = ", ram); - debug("MEMORY: minimum ram size (GB) = ", min_ram_1gb); - - return (ram < min_ram_1gb); - } - catch (...) { - debug("MEMORY: caught error, returned false"); - return false; - } - - - /** - * @brief Check for the window class for VirtualBox - * @category Windows - * @author Al-Khaser Project - * @copyright GPL-3.0 - */ - [[nodiscard]] static bool vbox_window_class() try { -#if (!MSVC) - return false; -#else - HWND hClass = FindWindow(_T("VBoxTrayToolWndClass"), NULL); - HWND hWindow = FindWindow(NULL, _T("VBoxTrayToolWnd")); - - if (hClass || hWindow) { - return core::add(VBOX); - } - - return false; -#endif - } - catch (...) { - debug("VBOX_WINDOW_CLASS: caught error, returned false"); - return false; - } - - - /** - * @brief Check for loaded DLLs in the process - * @category Windows - * @author LordNoteworthy - * @note modified code from Al-Khaser project - * @link https://github.com/LordNoteworthy/al-khaser/blob/c68fbd7ba0ba46315e819b490a2c782b80262fcd/al-khaser/Anti%20VM/Generic.cpp - */ - [[nodiscard]] static bool loaded_dlls() try { -#if (!MSVC) - return false; -#else - HMODULE hDll; - - constexpr std::array szDlls = {{ - "avghookx.dll", // AVG - "avghooka.dll", // AVG - "snxhk.dll", // Avast - "sbiedll.dll", // Sandboxie - "dbghelp.dll", // WindBG - "api_log.dll", // iDefense Lab - "dir_watch.dll", // iDefense Lab - "pstorec.dll", // SunBelt CWSandbox - "vmcheck.dll", // Virtual PC - "wpespy.dll", // WPE Pro - "cmdvrt64.dll", // Comodo Container - "cmdvrt32.dll" // Comodo Container - }}; - - for (const auto& key : szDlls) { - const char* dll = key; - - hDll = GetModuleHandleA(dll); // Use GetModuleHandleA for ANSI strings - - if (hDll != NULL && dll != NULL) { - if (strcmp(dll, "sbiedll.dll") == 0) { return core::add(SANDBOXIE); } - if (strcmp(dll, "pstorec.dll") == 0) { return core::add(CWSANDBOX); } - if (strcmp(dll, "vmcheck.dll") == 0) { return core::add(VPC); } - if (strcmp(dll, "cmdvrt32.dll") == 0) { return core::add(COMODO); } - if (strcmp(dll, "cmdvrt64.dll") == 0) { return core::add(COMODO); } - - return true; - } - } - - return false; -#endif - } - catch (...) { - debug("LOADED_DLLS:", "caught error, returned false"); - return false; - } - - - /** - * @brief Check for KVM-specific registry strings - * @category Windows - * @note idea is from Al-Khaser, slightly modified code - * @author LordNoteWorthy - * @link https://github.com/LordNoteworthy/al-khaser/blob/0f31a3866bafdfa703d2ed1ee1a242ab31bf5ef0/al-khaser/AntiVM/KVM.cpp - */ - [[nodiscard]] static bool kvm_registry() try { -#if (!MSVC) - return false; -#else - auto registry_exists = [](const TCHAR* key) -> bool { - HKEY keyHandle; - - if (RegOpenKeyEx(HKEY_LOCAL_MACHINE, key, 0, KEY_QUERY_VALUE, &keyHandle) == ERROR_SUCCESS) { - RegCloseKey(keyHandle); - return true; - } - - return false; - }; - - constexpr std::array keys = {{ - _T("SYSTEM\\ControlSet001\\Services\\vioscsi"), - _T("SYSTEM\\ControlSet001\\Services\\viostor"), - _T("SYSTEM\\ControlSet001\\Services\\VirtIO-FS Service"), - _T("SYSTEM\\ControlSet001\\Services\\VirtioSerial"), - _T("SYSTEM\\ControlSet001\\Services\\BALLOON"), - _T("SYSTEM\\ControlSet001\\Services\\BalloonService"), - _T("SYSTEM\\ControlSet001\\Services\\netkvm"), - }}; - - for (const auto& key : keys) { - if (registry_exists(key)) { - return core::add(KVM); - } - } - - return false; -#endif - } - catch (...) { - debug("KVM_REG: ", "caught error, returned false"); - return false; - } - - - /** - * @brief Check for KVM-specific .sys files in system driver directory - * @category Windows - * @note idea is from Al-Khaser, slightly modified code - * @author LordNoteWorthy - * @link https://github.com/LordNoteworthy/al-khaser/blob/0f31a3866bafdfa703d2ed1ee1a242ab31bf5ef0/al-khaser/AntiVM/KVM.cpp - */ - [[nodiscard]] static bool kvm_drivers() try { -#if (!MSVC) - return false; -#else - constexpr std::array keys = { { - _T("System32\\drivers\\balloon.sys"), - _T("System32\\drivers\\netkvm.sys"), - _T("System32\\drivers\\pvpanic.sys"), - _T("System32\\drivers\\viofs.sys"), - _T("System32\\drivers\\viogpudo.sys"), - _T("System32\\drivers\\vioinput.sys"), - _T("System32\\drivers\\viorng.sys"), - _T("System32\\drivers\\vioscsi.sys"), - _T("System32\\drivers\\vioser.sys"), - _T("System32\\drivers\\viostor.sys") - } }; - - TCHAR szWinDir[MAX_PATH] = _T(""); - TCHAR szPath[MAX_PATH] = _T(""); - PVOID OldValue = NULL; - - if (GetWindowsDirectory(szWinDir, MAX_PATH) == 0) { - return false; - } - - if (util::is_wow64()) { - Wow64DisableWow64FsRedirection(&OldValue); - } - - bool is_vm = false; - - for (const auto& key : keys) { - PathCombine(szPath, szWinDir, key); - if (util::exists(szPath)) { - is_vm = true; - break; - } - } - - if (util::is_wow64()) { - Wow64RevertWow64FsRedirection(&OldValue); - } - - return is_vm; -#endif - } - catch (...) { - debug("KVM_DRIVERS: ", "caught error, returned false"); - return false; - } - - - /** - * @brief Check for KVM directory "Virtio-Win" - * @category Windows - * @author LordNoteWorthy - * @note from Al-Khaser project - * @link https://github.com/LordNoteworthy/al-khaser/blob/0f31a3866bafdfa703d2ed1ee1a242ab31bf5ef0/al-khaser/AntiVM/KVM.cpp - */ - [[nodiscard]] static bool kvm_directories() try { -#if (!MSVC) - return false; -#else - TCHAR szProgramFile[MAX_PATH]; - TCHAR szPath[MAX_PATH] = _T(""); - TCHAR szTarget[MAX_PATH] = _T("Virtio-Win\\"); - - if (util::is_wow64()) { - ExpandEnvironmentStrings(_T("%ProgramW6432%"), szProgramFile, ARRAYSIZE(szProgramFile)); - } else { - SHGetSpecialFolderPath(NULL, szProgramFile, CSIDL_PROGRAM_FILES, FALSE); - } - - PathCombine(szPath, szProgramFile, szTarget); - return util::exists(szPath); -#endif - } - catch (...) { - debug("KVM_DIRS: ", "caught error, returned false"); - return false; - } - - - /** - * @brief Check if audio device is present - * @category Windows - * @author CheckPointSW (InviZzzible project) - * @link https://github.com/CheckPointSW/InviZzzible/blob/master/SandboxEvasion/helper.cpp - * @copyright GPL-3.0 - */ - [[nodiscard]] static bool check_audio() try { -#if (!MSVC) - return false; -#else - PCWSTR wszfilterName = L"audio_device_random_name"; - - if (FAILED(CoInitialize(NULL))) - return false; - - IGraphBuilder* pGraph = nullptr; - if (FAILED(CoCreateInstance(CLSID_FilterGraph, NULL, CLSCTX_INPROC_SERVER, IID_IGraphBuilder, (void**)&pGraph))) - return false; - - // First anti-emulation check: If AddFilter is called with NULL as a first argument it should return the E_POINTER error code. - // Some emulators may implement unknown COM interfaces in a generic way, so they will probably fail here. - if (E_POINTER != pGraph->AddFilter(NULL, wszfilterName)) - return true; - - // Initializes a simple Audio Renderer, error code is not checked, - // but pBaseFilter will be set to NULL upon failure and the code will eventually fail later. - IBaseFilter* pBaseFilter = nullptr; - - HRESULT hr = CoCreateInstance(CLSID_AudioRender, NULL, CLSCTX_INPROC_SERVER, IID_IBaseFilter, (void**)&pBaseFilter); - if (FAILED(hr)) { - return false; - } - - // Adds the previously created Audio Renderer to the Filter Graph, no error checks - pGraph->AddFilter(pBaseFilter, wszfilterName); - - // Tries to find the filter that was just added; in case of any previously not checked error (or wrong emulation) - // this function won't find the filter and the sandbox/emulator will be successfully detected. - IBaseFilter* pBaseFilter2 = nullptr; - pGraph->FindFilterByName(wszfilterName, &pBaseFilter2); - if (nullptr == pBaseFilter2) - return true; - - // Checks if info.achName is equal to the previously added filterName, if not - poor API emulation - FILTER_INFO info = { 0 }; - pBaseFilter2->QueryFilterInfo(&info); - if (0 != wcscmp(info.achName, wszfilterName)) - return false; - - // Checks if the API sets a proper IReferenceClock pointer - IReferenceClock* pClock = nullptr; - if (0 != pBaseFilter2->GetSyncSource(&pClock)) - return false; - if (0 != pClock) - return false; - - // Checks if CLSID is different from 0 - CLSID clsID = { 0 }; - pBaseFilter2->GetClassID(&clsID); - if (clsID.Data1 == 0) - return true; - - if (nullptr == pBaseFilter2) - return true; - - // Just checks if the call was successful - IEnumPins* pEnum = nullptr; - if (0 != pBaseFilter2->EnumPins(&pEnum)) - return true; - - // The reference count returned by AddRef has to be higher than 0 - if (0 == pBaseFilter2->AddRef()) - return true; - - return false; -#endif - } - catch (...) { - debug("AUDIO: ", "caught error, returned false"); - return false; - } - - - /** - * @brief Check for QEMU-specific blacklisted directories - * @author LordNoteworthy - * @link https://github.com/LordNoteworthy/al-khaser/blob/master/al-khaser/AntiVM/Qemu.cpp - * @category Windows - * @note from al-khaser project - * @copyright GPL-3.0 - */ - [[nodiscard]] static bool qemu_dir() try { -#if (!MSVC) - return false; -#else - TCHAR szProgramFile[MAX_PATH]; - TCHAR szPath[MAX_PATH] = _T(""); - - const TCHAR* szDirectories[] = { - _T("qemu-ga"), // QEMU guest agent. - _T("SPICE Guest Tools"), // SPICE guest tools. - }; - - WORD iLength = sizeof(szDirectories) / sizeof(szDirectories[0]); - for (int i = 0; i < iLength; i++) { - TCHAR msg[256] = _T(""); - - if (util::is_wow64()) - ExpandEnvironmentStrings(_T("%ProgramW6432%"), szProgramFile, ARRAYSIZE(szProgramFile)); - else - SHGetSpecialFolderPath(NULL, szProgramFile, CSIDL_PROGRAM_FILES, FALSE); - - PathCombine(szPath, szProgramFile, szDirectories[i]); - - if (util::exists(szPath)) - return core::add(QEMU); - } - - return false; -#endif - } - catch (...) { - debug("QEMU_DIR: ", "caught error, returned false"); - return false; - } - - - /** - * @brief Check for the presence of a mouse device - * @category Windows - * @author a0rtega - * @link https://github.com/a0rtega/pafish/blob/master/pafish/rtt.c - * @note from pafish project - * @copyright GPL - */ - [[nodiscard]] static bool mouse_device() try { -#if (!MSVC) - return false; -#else - int res; - res = GetSystemMetrics(SM_MOUSEPRESENT); - return (res == 0); -#endif - } - catch (...) { - debug("MOUSE_DEVICE: caught error, returned false"); - return false; - } +/* GPL */ // @brief Check if the computer name (not username to be clear) is VM-specific +/* GPL */ // @category Windows +/* GPL */ // @author InviZzzible project +/* GPL */ // @copyright GPL-3.0 +/* GPL */ [[nodiscard]] static bool computer_name_match() try { +/* GPL */ #if (!MSVC) +/* GPL */ return false; +/* GPL */ #else +/* GPL */ auto out_length = MAX_PATH; +/* GPL */ std::vector comp_name(static_cast(out_length), 0); +/* GPL */ GetComputerNameA((LPSTR)comp_name.data(), (LPDWORD)&out_length); +/* GPL */ +/* GPL */ auto compare = [&](const std::string& s) -> bool { +/* GPL */ return (std::strcmp((LPCSTR)comp_name.data(), s.c_str()) == 0); +/* GPL */ }; +/* GPL */ +/* GPL */ debug("COMPUTER_NAME: fetched = ", (LPCSTR)comp_name.data()); +/* GPL */ +/* GPL */ if (compare("InsideTm") || compare("TU-4NH09SMCG1HC")) { // anubis +/* GPL */ debug("COMPUTER_NAME: detected Anubis"); +/* GPL */ return core::add(ANUBIS); +/* GPL */ } +/* GPL */ +/* GPL */ if (compare("klone_x64-pc") || compare("tequilaboomboom")) { // general +/* GPL */ debug("COMPUTER_NAME: detected general (VM but unknown)"); +/* GPL */ return true; +/* GPL */ } +/* GPL */ +/* GPL */ return false; +/* GPL */ #endif +/* GPL */ } +/* GPL */ catch (...) { +/* GPL */ debug("COMPUTER_NAME: caught error, returned false"); +/* GPL */ return false; +/* GPL */ } +/* GPL */ +/* GPL */ +/* GPL */ // @brief Check wine_get_unix_file_name file for Wine +/* GPL */ // @author pafish project +/* GPL */ // @link https://github.com/a0rtega/pafish/blob/master/pafish/wine.c +/* GPL */ // @category Windows +/* GPL */ // @copyright GPL-3.0 +/* GPL */ [[nodiscard]] static bool wine() try { +/* GPL */ #if (!MSVC) +/* GPL */ return false; +/* GPL */ #else +/* GPL */ HMODULE k32; +/* GPL */ k32 = GetModuleHandle(TEXT("kernel32.dll")); +/* GPL */ +/* GPL */ if (k32 != NULL) { +/* GPL */ if (GetProcAddress(k32, "wine_get_unix_file_name") != NULL) { +/* GPL */ return core::add(WINE); +/* GPL */ } +/* GPL */ } +/* GPL */ +/* GPL */ return false; +/* GPL */ #endif +/* GPL */ } +/* GPL */ catch (...) { +/* GPL */ debug("WINE_CHECK: caught error, returned false"); +/* GPL */ return false; +/* GPL */ } +/* GPL */ +/* GPL */ +/* GPL */ // @brief Check if hostname is specific +/* GPL */ // @author InviZzzible project +/* GPL */ // @category Windows +/* GPL */ // @copyright GPL-3.0 +/* GPL */ [[nodiscard]] static bool hostname_match() try { +/* GPL */ #if (!MSVC) +/* GPL */ return false; +/* GPL */ #else +/* GPL */ auto out_length = MAX_PATH; +/* GPL */ std::vector dns_host_name(static_cast(out_length), 0); +/* GPL */ GetComputerNameExA(ComputerNameDnsHostname, (LPSTR)dns_host_name.data(), (LPDWORD)&out_length); +/* GPL */ +/* GPL */ debug("HOSTNAME: ", (LPCSTR)dns_host_name.data()); +/* GPL */ +/* GPL */ return (!lstrcmpiA((LPCSTR)dns_host_name.data(), "SystemIT")); +/* GPL */ #endif +/* GPL */ } +/* GPL */ catch (...) { +/* GPL */ debug("HOSTNAME: caught error, returned false"); +/* GPL */ return false; +/* GPL */ } +/* GPL */ +/* GPL */ +/* GPL */ // @brief Check if memory space is far too low for a physical machine +/* GPL */ // @author Al-Khaser project +/* GPL */ // @category x86? +/* GPL */ // @copyright GPL-3.0 +/* GPL */ [[nodiscard]] static bool low_memory_space() try { +/* GPL */ constexpr u64 min_ram_1gb = (1024LL * (1024LL * (1024LL * 1LL))); +/* GPL */ const u64 ram = util::get_memory_space(); +/* GPL */ +/* GPL */ debug("MEMORY: ram size (GB) = ", ram); +/* GPL */ debug("MEMORY: minimum ram size (GB) = ", min_ram_1gb); +/* GPL */ +/* GPL */ return (ram < min_ram_1gb); +/* GPL */ } +/* GPL */ catch (...) { +/* GPL */ debug("MEMORY: caught error, returned false"); +/* GPL */ return false; +/* GPL */ } +/* GPL */ +/* GPL */ +/* GPL */ // @brief Check for the window class for VirtualBox +/* GPL */ // @category Windows +/* GPL */ // @author Al-Khaser Project +/* GPL */ // @copyright GPL-3.0 +/* GPL */ [[nodiscard]] static bool vbox_window_class() try { +/* GPL */ #if (!MSVC) +/* GPL */ return false; +/* GPL */ #else +/* GPL */ HWND hClass = FindWindow(_T("VBoxTrayToolWndClass"), NULL); +/* GPL */ HWND hWindow = FindWindow(NULL, _T("VBoxTrayToolWnd")); +/* GPL */ +/* GPL */ if (hClass || hWindow) { +/* GPL */ return core::add(VBOX); +/* GPL */ } +/* GPL */ +/* GPL */ return false; +/* GPL */ #endif +/* GPL */ } +/* GPL */ catch (...) { +/* GPL */ debug("VBOX_WINDOW_CLASS: caught error, returned false"); +/* GPL */ return false; +/* GPL */ } +/* GPL */ +/* GPL */ +/* GPL */ // @brief Check for loaded DLLs in the process +/* GPL */ // @category Windows +/* GPL */ // @author LordNoteworthy +/* GPL */ // @note modified code from Al-Khaser project +/* GPL */ // @link https://github.com/LordNoteworthy/al-khaser/blob/c68fbd7ba0ba46315e819b490a2c782b80262fcd/al-khaser/Anti%20VM/Generic.cpp +/* GPL */ [[nodiscard]] static bool loaded_dlls() try { +/* GPL */ #if (!MSVC) +/* GPL */ return false; +/* GPL */ #else +/* GPL */ HMODULE hDll; +/* GPL */ +/* GPL */ constexpr std::array szDlls = {{ +/* GPL */ "avghookx.dll", // AVG +/* GPL */ "avghooka.dll", // AVG +/* GPL */ "snxhk.dll", // Avast +/* GPL */ "sbiedll.dll", // Sandboxie +/* GPL */ "dbghelp.dll", // WindBG +/* GPL */ "api_log.dll", // iDefense Lab +/* GPL */ "dir_watch.dll", // iDefense Lab +/* GPL */ "pstorec.dll", // SunBelt CWSandbox +/* GPL */ "vmcheck.dll", // Virtual PC +/* GPL */ "wpespy.dll", // WPE Pro +/* GPL */ "cmdvrt64.dll", // Comodo Container +/* GPL */ "cmdvrt32.dll" // Comodo Container +/* GPL */ }}; +/* GPL */ +/* GPL */ for (const auto& key : szDlls) { +/* GPL */ const char* dll = key; +/* GPL */ +/* GPL */ hDll = GetModuleHandleA(dll); // Use GetModuleHandleA for ANSI strings +/* GPL */ +/* GPL */ if (hDll != NULL && dll != NULL) { +/* GPL */ if (strcmp(dll, "sbiedll.dll") == 0) { return core::add(SANDBOXIE); } +/* GPL */ if (strcmp(dll, "pstorec.dll") == 0) { return core::add(CWSANDBOX); } +/* GPL */ if (strcmp(dll, "vmcheck.dll") == 0) { return core::add(VPC); } +/* GPL */ if (strcmp(dll, "cmdvrt32.dll") == 0) { return core::add(COMODO); } +/* GPL */ if (strcmp(dll, "cmdvrt64.dll") == 0) { return core::add(COMODO); } +/* GPL */ +/* GPL */ return true; +/* GPL */ } +/* GPL */ } +/* GPL */ +/* GPL */ return false; +/* GPL */ #endif +/* GPL */ } +/* GPL */ catch (...) { +/* GPL */ debug("LOADED_DLLS:", "caught error, returned false"); +/* GPL */ return false; +/* GPL */ } +/* GPL */ +/* GPL */ +/* GPL */ // @brief Check for KVM-specific registry strings +/* GPL */ // @category Windows +/* GPL */ // @note idea is from Al-Khaser, slightly modified code +/* GPL */ // @author LordNoteWorthy +/* GPL */ // @link https://github.com/LordNoteworthy/al-khaser/blob/0f31a3866bafdfa703d2ed1ee1a242ab31bf5ef0/al-khaser/AntiVM/KVM.cpp +/* GPL */ [[nodiscard]] static bool kvm_registry() try { +/* GPL */ #if (!MSVC) +/* GPL */ return false; +/* GPL */ #else +/* GPL */ auto registry_exists = [](const TCHAR* key) -> bool { +/* GPL */ HKEY keyHandle; +/* GPL */ +/* GPL */ if (RegOpenKeyEx(HKEY_LOCAL_MACHINE, key, 0, KEY_QUERY_VALUE, &keyHandle) == ERROR_SUCCESS) { +/* GPL */ RegCloseKey(keyHandle); +/* GPL */ return true; +/* GPL */ } +/* GPL */ +/* GPL */ return false; +/* GPL */ }; +/* GPL */ +/* GPL */ constexpr std::array keys = {{ +/* GPL */ _T("SYSTEM\\ControlSet001\\Services\\vioscsi"), +/* GPL */ _T("SYSTEM\\ControlSet001\\Services\\viostor"), +/* GPL */ _T("SYSTEM\\ControlSet001\\Services\\VirtIO-FS Service"), +/* GPL */ _T("SYSTEM\\ControlSet001\\Services\\VirtioSerial"), +/* GPL */ _T("SYSTEM\\ControlSet001\\Services\\BALLOON"), +/* GPL */ _T("SYSTEM\\ControlSet001\\Services\\BalloonService"), +/* GPL */ _T("SYSTEM\\ControlSet001\\Services\\netkvm"), +/* GPL */ }}; +/* GPL */ +/* GPL */ for (const auto& key : keys) { +/* GPL */ if (registry_exists(key)) { +/* GPL */ return core::add(KVM); +/* GPL */ } +/* GPL */ } +/* GPL */ +/* GPL */ return false; +/* GPL */ #endif +/* GPL */ } +/* GPL */ catch (...) { +/* GPL */ debug("KVM_REG: ", "caught error, returned false"); +/* GPL */ return false; +/* GPL */ } +/* GPL */ +/* GPL */ +/* GPL */ // @brief Check for KVM-specific .sys files in system driver directory +/* GPL */ // @category Windows +/* GPL */ // @note idea is from Al-Khaser, slightly modified code +/* GPL */ // @author LordNoteWorthy +/* GPL */ // @link https://github.com/LordNoteworthy/al-khaser/blob/0f31a3866bafdfa703d2ed1ee1a242ab31bf5ef0/al-khaser/AntiVM/KVM.cpp +/* GPL */ [[nodiscard]] static bool kvm_drivers() try { +/* GPL */ #if (!MSVC) +/* GPL */ return false; +/* GPL */ #else +/* GPL */ constexpr std::array keys = { { +/* GPL */ _T("System32\\drivers\\balloon.sys"), +/* GPL */ _T("System32\\drivers\\netkvm.sys"), +/* GPL */ _T("System32\\drivers\\pvpanic.sys"), +/* GPL */ _T("System32\\drivers\\viofs.sys"), +/* GPL */ _T("System32\\drivers\\viogpudo.sys"), +/* GPL */ _T("System32\\drivers\\vioinput.sys"), +/* GPL */ _T("System32\\drivers\\viorng.sys"), +/* GPL */ _T("System32\\drivers\\vioscsi.sys"), +/* GPL */ _T("System32\\drivers\\vioser.sys"), +/* GPL */ _T("System32\\drivers\\viostor.sys") +/* GPL */ } }; +/* GPL */ +/* GPL */ TCHAR szWinDir[MAX_PATH] = _T(""); +/* GPL */ TCHAR szPath[MAX_PATH] = _T(""); +/* GPL */ PVOID OldValue = NULL; +/* GPL */ +/* GPL */ if (GetWindowsDirectory(szWinDir, MAX_PATH) == 0) { +/* GPL */ return false; +/* GPL */ } +/* GPL */ +/* GPL */ if (util::is_wow64()) { +/* GPL */ Wow64DisableWow64FsRedirection(&OldValue); +/* GPL */ } +/* GPL */ +/* GPL */ bool is_vm = false; +/* GPL */ +/* GPL */ for (const auto& key : keys) { +/* GPL */ PathCombine(szPath, szWinDir, key); +/* GPL */ if (util::exists(szPath)) { +/* GPL */ is_vm = true; +/* GPL */ break; +/* GPL */ } +/* GPL */ } +/* GPL */ +/* GPL */ if (util::is_wow64()) { +/* GPL */ Wow64RevertWow64FsRedirection(&OldValue); +/* GPL */ } +/* GPL */ +/* GPL */ return is_vm; +/* GPL */ #endif +/* GPL */ } +/* GPL */ catch (...) { +/* GPL */ debug("KVM_DRIVERS: ", "caught error, returned false"); +/* GPL */ return false; +/* GPL */ } +/* GPL */ +/* GPL */ +/* GPL */ // @brief Check for KVM directory "Virtio-Win" +/* GPL */ // @category Windows +/* GPL */ // @author LordNoteWorthy +/* GPL */ // @note from Al-Khaser project +/* GPL */ // @link https://github.com/LordNoteworthy/al-khaser/blob/0f31a3866bafdfa703d2ed1ee1a242ab31bf5ef0/al-khaser/AntiVM/KVM.cpp +/* GPL */ [[nodiscard]] static bool kvm_directories() try { +/* GPL */ #if (!MSVC) +/* GPL */ return false; +/* GPL */ #else +/* GPL */ TCHAR szProgramFile[MAX_PATH]; +/* GPL */ TCHAR szPath[MAX_PATH] = _T(""); +/* GPL */ TCHAR szTarget[MAX_PATH] = _T("Virtio-Win\\"); +/* GPL */ +/* GPL */ if (util::is_wow64()) { +/* GPL */ ExpandEnvironmentStrings(_T("%ProgramW6432%"), szProgramFile, ARRAYSIZE(szProgramFile)); +/* GPL */ } else { +/* GPL */ SHGetSpecialFolderPath(NULL, szProgramFile, CSIDL_PROGRAM_FILES, FALSE); +/* GPL */ } +/* GPL */ +/* GPL */ PathCombine(szPath, szProgramFile, szTarget); +/* GPL */ return util::exists(szPath); +/* GPL */ #endif +/* GPL */ } +/* GPL */ catch (...) { +/* GPL */ debug("KVM_DIRS: ", "caught error, returned false"); +/* GPL */ return false; +/* GPL */ } +/* GPL */ +/* GPL */ +/* GPL */ // @brief Check if audio device is present +/* GPL */ // @category Windows +/* GPL */ // @author CheckPointSW (InviZzzible project) +/* GPL */ // @link https://github.com/CheckPointSW/InviZzzible/blob/master/SandboxEvasion/helper.cpp +/* GPL */ // @copyright GPL-3.0 +/* GPL */ [[nodiscard]] static bool check_audio() try { +/* GPL */ #if (!MSVC) +/* GPL */ return false; +/* GPL */ #else +/* GPL */ PCWSTR wszfilterName = L"audio_device_random_name"; +/* GPL */ +/* GPL */ if (FAILED(CoInitialize(NULL))) +/* GPL */ return false; +/* GPL */ +/* GPL */ IGraphBuilder* pGraph = nullptr; +/* GPL */ if (FAILED(CoCreateInstance(CLSID_FilterGraph, NULL, CLSCTX_INPROC_SERVER, IID_IGraphBuilder, (void**)&pGraph))) +/* GPL */ return false; +/* GPL */ +/* GPL */ // First anti-emulation check: If AddFilter is called with NULL as a first argument it should return the E_POINTER error code. +/* GPL */ // Some emulators may implement unknown COM interfaces in a generic way, so they will probably fail here. +/* GPL */ if (E_POINTER != pGraph->AddFilter(NULL, wszfilterName)) +/* GPL */ return true; +/* GPL */ +/* GPL */ // Initializes a simple Audio Renderer, error code is not checked, +/* GPL */ // but pBaseFilter will be set to NULL upon failure and the code will eventually fail later. +/* GPL */ IBaseFilter* pBaseFilter = nullptr; +/* GPL */ +/* GPL */ HRESULT hr = CoCreateInstance(CLSID_AudioRender, NULL, CLSCTX_INPROC_SERVER, IID_IBaseFilter, (void**)&pBaseFilter); +/* GPL */ if (FAILED(hr)) { +/* GPL */ return false; +/* GPL */ } +/* GPL */ +/* GPL */ // Adds the previously created Audio Renderer to the Filter Graph, no error checks +/* GPL */ pGraph->AddFilter(pBaseFilter, wszfilterName); +/* GPL */ +/* GPL */ // Tries to find the filter that was just added; in case of any previously not checked error (or wrong emulation) +/* GPL */ // this function won't find the filter and the sandbox/emulator will be successfully detected. +/* GPL */ IBaseFilter* pBaseFilter2 = nullptr; +/* GPL */ pGraph->FindFilterByName(wszfilterName, &pBaseFilter2); +/* GPL */ if (nullptr == pBaseFilter2) +/* GPL */ return true; +/* GPL */ +/* GPL */ // Checks if info.achName is equal to the previously added filterName, if not - poor API emulation +/* GPL */ FILTER_INFO info = { 0 }; +/* GPL */ pBaseFilter2->QueryFilterInfo(&info); +/* GPL */ if (0 != wcscmp(info.achName, wszfilterName)) +/* GPL */ return false; +/* GPL */ +/* GPL */ // Checks if the API sets a proper IReferenceClock pointer +/* GPL */ IReferenceClock* pClock = nullptr; +/* GPL */ if (0 != pBaseFilter2->GetSyncSource(&pClock)) +/* GPL */ return false; +/* GPL */ if (0 != pClock) +/* GPL */ return false; +/* GPL */ +/* GPL */ // Checks if CLSID is different from 0 +/* GPL */ CLSID clsID = { 0 }; +/* GPL */ pBaseFilter2->GetClassID(&clsID); +/* GPL */ if (clsID.Data1 == 0) +/* GPL */ return true; +/* GPL */ +/* GPL */ if (nullptr == pBaseFilter2) +/* GPL */ return true; +/* GPL */ +/* GPL */ // Just checks if the call was successful +/* GPL */ IEnumPins* pEnum = nullptr; +/* GPL */ if (0 != pBaseFilter2->EnumPins(&pEnum)) +/* GPL */ return true; +/* GPL */ +/* GPL */ // The reference count returned by AddRef has to be higher than 0 +/* GPL */ if (0 == pBaseFilter2->AddRef()) +/* GPL */ return true; +/* GPL */ +/* GPL */ return false; +/* GPL */ #endif +/* GPL */ } +/* GPL */ catch (...) { +/* GPL */ debug("AUDIO: ", "caught error, returned false"); +/* GPL */ return false; +/* GPL */ } +/* GPL */ +/* GPL */ +/* GPL */ // @brief Check for QEMU-specific blacklisted directories +/* GPL */ // @author LordNoteworthy +/* GPL */ // @link https://github.com/LordNoteworthy/al-khaser/blob/master/al-khaser/AntiVM/Qemu.cpp +/* GPL */ // @category Windows +/* GPL */ // @note from al-khaser project +/* GPL */ // @copyright GPL-3.0 +/* GPL */ [[nodiscard]] static bool qemu_dir() try { +/* GPL */ #if (!MSVC) +/* GPL */ return false; +/* GPL */ #else +/* GPL */ TCHAR szProgramFile[MAX_PATH]; +/* GPL */ TCHAR szPath[MAX_PATH] = _T(""); +/* GPL */ +/* GPL */ const TCHAR* szDirectories[] = { +/* GPL */ _T("qemu-ga"), // QEMU guest agent. +/* GPL */ _T("SPICE Guest Tools"), // SPICE guest tools. +/* GPL */ }; +/* GPL */ +/* GPL */ WORD iLength = sizeof(szDirectories) / sizeof(szDirectories[0]); +/* GPL */ for (int i = 0; i < iLength; i++) { +/* GPL */ TCHAR msg[256] = _T(""); +/* GPL */ +/* GPL */ if (util::is_wow64()) +/* GPL */ ExpandEnvironmentStrings(_T("%ProgramW6432%"), szProgramFile, ARRAYSIZE(szProgramFile)); +/* GPL */ else +/* GPL */ SHGetSpecialFolderPath(NULL, szProgramFile, CSIDL_PROGRAM_FILES, FALSE); +/* GPL */ +/* GPL */ PathCombine(szPath, szProgramFile, szDirectories[i]); +/* GPL */ +/* GPL */ if (util::exists(szPath)) +/* GPL */ return core::add(QEMU); +/* GPL */ } +/* GPL */ +/* GPL */ return false; +/* GPL */ #endif +/* GPL */ } +/* GPL */ catch (...) { +/* GPL */ debug("QEMU_DIR: ", "caught error, returned false"); +/* GPL */ return false; +/* GPL */ } +/* GPL */ +/* GPL */ +/* GPL */ // @brief Check for the presence of a mouse device +/* GPL */ // @category Windows +/* GPL */ // @author a0rtega +/* GPL */ // @link https://github.com/a0rtega/pafish/blob/master/pafish/rtt.c +/* GPL */ // @note from pafish project +/* GPL */ // @copyright GPL +/* GPL */ [[nodiscard]] static bool mouse_device() try { +/* GPL */ #if (!MSVC) +/* GPL */ return false; +/* GPL */ #else +/* GPL */ int res; +/* GPL */ res = GetSystemMetrics(SM_MOUSEPRESENT); +/* GPL */ return (res == 0); +/* GPL */ #endif +/* GPL */ } +/* GPL */ catch (...) { +/* GPL */ debug("MOUSE_DEVICE: caught error, returned false"); +/* GPL */ return false; +/* GPL */ } /** @@ -8022,7 +8036,7 @@ struct VM { cmp("Malware") || cmp("malsand") || cmp("ClonePC") - ) { + ) { return true; } @@ -9155,7 +9169,7 @@ struct VM { struct technique { u8 points = 0; // this is the certainty score between 0 and 100 std::function run; // this is the technique function itself - bool spoofable = false; // this is to indicate that the technique can be very easily spoofed (not guaranteed) + bool is_spoofable = false; // this is to indicate that the technique can be very easily spoofed (not guaranteed) }; struct custom_technique { @@ -9213,7 +9227,7 @@ struct VM { return false; } - [[nodiscard]] static bool is_non_technique_set(const flagset& flags) { + [[nodiscard]] static bool is_setting_flag_set(const flagset& flags) { for (std::size_t i = non_technique_begin; i < non_technique_end; i++) { if (flags.test(i)) { return true; @@ -9239,11 +9253,11 @@ struct VM { return; } - if (!core::is_non_technique_set(flags)) { + if (!core::is_setting_flag_set(flags)) { throw std::invalid_argument("Invalid flag option for function parameter found, either leave it empty or add the VM::DEFAULT flag"); } - // at this stage, only non-technique flags are asserted to be set + // at this stage, only settings technique flags are asserted to be set if ( flags.test(NO_MEMO) || flags.test(HIGH_THRESHOLD) || @@ -9260,22 +9274,26 @@ struct VM { // run every VM detection mechanism in the technique table static u16 run_all(const flagset& flags, const bool shortcut = false) { u16 points = 0; + const bool memo_enabled = core::is_disabled(flags, NO_MEMO); const u16 threshold_points = (core::is_enabled(flags, HIGH_THRESHOLD) ? high_threshold_score : 200); - // for main technique table + // loop through technique table, where all the techniques are stored for (const auto& tmp : technique_table) { const enum_flags technique_macro = tmp.first; - const technique tuple = tmp.second; + const technique technique_data = tmp.second; - // check if it's disabled + // check if the technique is disabled if (core::is_disabled(flags, technique_macro)) { continue; } // check if it's spoofable, and whether it's enabled - if (tuple.spoofable && core::is_disabled(flags, SPOOFABLE)) { + if ( + technique_data.is_spoofable && + core::is_disabled(flags, SPOOFABLE) + ) { continue; } @@ -9291,25 +9309,27 @@ struct VM { } // run the technique - const bool result = tuple.run(); + const bool result = technique_data.run(); // accumulate the points if technique detected a VM if (result) { - points += tuple.points; - } + points += technique_data.points; - /** - * for things like VM::detect() and VM::percentage(), - * a score of 200+ is guaranteed to be a VM, so - * there's no point in running the rest of the techniques - */ + // this is specific to VM::detected_count() which returns + // the number of techniques that returned a positive + detected_count_num++; + } + + // for things like VM::detect() and VM::percentage(), + // a score of 200+ is guaranteed to be a VM, so + // there's no point in running the rest of the techniques if (shortcut && points >= threshold_points) { return points; } // store the current technique result to the cache if (memo_enabled) { - memo::cache_store(technique_macro, result, tuple.points); + memo::cache_store(technique_macro, result, technique_data.points); } } @@ -9330,13 +9350,20 @@ struct VM { * basically what this entire template fuckery does is manage the * variadic arguments being given through the arg_handler function, * which could either be a std::bitset, a uint8_t, or a combination - * of both of them. Thisz will handle both argument types and implement + * of both of them. This will handle both argument types and implement * them depending on what their types are. If it's a std::bitset, - * do the |= operation. If it's a uint8_t, simply .set() that into - * the flag_collector bitset. That's the gist of it. + * do the |= operation on flag_collector. If it's a uint8_t, simply + * .set() that into the flag_collector. That's the gist of it. * * Also I won't even deny, the majority of this section was 90% generated * by chatgpt. Can't be arsed with this C++ templatisation shit. + * Like is it really my fault that I have a hard time understanging C++'s + * god awful metaprogramming designs? And don't even get me started on SNIFAE. + * + * You don't need an IQ of 3 digits to realise how dogshit this language + * is, when you end up in situations where there's a few correct solutions + * to a problem, but with a billion ways you can do the same thing but in + * the "wrong" way. I genuinely can't wait for Carbon to come out. */ private: static flagset flag_collector; @@ -9454,18 +9481,23 @@ struct VM { #endif public: - // Function template to test variadic arguments + // fetch the flags, could be an enum value OR a std::bitset. + // This will then generate a different std::bitset as the + // return value by enabling the bits based on the argument. template static flagset arg_handler(Args&&... args) { - if VMAWARE_CONSTEXPR(is_empty()) { + if VMAWARE_CONSTEXPR (is_empty()) { return DEFAULT; } flag_collector.reset(); global_flags.reset(); + // set the bits in the flag, can take in + // either an enum value or a std::bitset handleArgs(std::forward(args)...); + // handle edgecases core::flag_sanitizer(flag_collector); global_flags = flag_collector; @@ -9478,14 +9510,15 @@ struct VM { static flagset disabled_arg_handler(Args&&... args) { flag_collector.reset(); - if VMAWARE_CONSTEXPR(is_empty()) { - throw std::invalid_argument("VM::DISABLE must contain a flag"); + if VMAWARE_CONSTEXPR (is_empty()) { + throw std::invalid_argument("VM::DISABLE() must contain a flag"); } handle_disabled_args(std::forward(args)...); - if (core::is_non_technique_set(flag_collector)) { - throw std::invalid_argument("VM::DISABLE must not contain a non-technique flag, they are disabled by default anyway"); + // check if a settings flag is set, which is not valid + if (core::is_setting_flag_set(flag_collector)) { + throw std::invalid_argument("VM::DISABLE() must not contain a settings flag, they are disabled by default anyway"); } return flag_collector; @@ -9522,7 +9555,7 @@ struct VM { throw_error("Flag argument must be a valid"); } - // check if the bit is a non-technique flag, which shouldn't be allowed + // check if the bit is a settings flag, which shouldn't be allowed if ( (flag_bit == NO_MEMO) || (flag_bit == HIGH_THRESHOLD) || @@ -9552,6 +9585,10 @@ struct VM { const core::technique& pair = it->second; const bool result = pair.run(); + if (result) { + detected_count_num++; + } + #ifdef __VMAWARE_DEBUG__ total_points += pair.points; #endif @@ -9573,15 +9610,17 @@ struct VM { [[nodiscard]] static std::string brand(Args ...args) { flagset flags = core::arg_handler(args...); + // is the multiple setting flag enabled? (meaning multiple + // brand strings will be outputted if there's a conflict) const bool is_multiple = core::is_enabled(flags, MULTIPLE); - // are all the techiques already run? if not, run all of them to get the necessary info to fetch the brand + // are all the techiques already run? if not, run them + // to fetch the necessary info to determine the brand if (!memo::all_present() || core::is_enabled(flags, NO_MEMO)) { - u16 tmp = core::run_all(flags); - UNUSED(tmp); + core::run_all(flags); } - // check if it's already cached and return that instead + // check if the result is already cached and return that instead if (core::is_disabled(flags, NO_MEMO)) { if (is_multiple) { if (memo::multi_brand::is_cached()) { @@ -9596,7 +9635,8 @@ struct VM { } } - // goofy ass C++11 and C++14 linker error workaround + // goofy ass C++11 and C++14 linker error workaround, + // and yes, this does look indeed stupid. #if (CPP <= 14) constexpr const char* TMP_QEMU = "QEMU"; constexpr const char* TMP_KVM = "KVM"; @@ -9639,27 +9679,42 @@ struct VM { constexpr const char* TMP_HYPERV_ARTIFACT = HYPERV_ARTIFACT; #endif + // this is where all the RELEVANT brands are stored. + // The ones with no points will be filtered out. std::map brands; + // add the relevant brands with at least 1 point for (const auto &element : core::brand_scoreboard) { if (element.second > 0) { brands.insert(std::make_pair(element.first, element.second)); } } - // if no brand had a single point, return "Unknown" + // if all brands had a point of 0, return + // "Unknown" (no relevant brands were found) if (brands.empty()) { return "Unknown"; } + // if there's only a single brand, return it. + // This will skip the rest of the function + // where it will process and merge certain + // brands if (brands.size() == 1) { return brands.begin()->first; - } else if (brands.size() > 1) { + } + + // remove Hyper-V artifacts if found with other + // brands, because that's not a VM. It's added + // only for the sake of information cuz of the + // fucky wucky Hyper-V problem (see Hyper-X) + if (brands.size() > 1) { if (brands.find(TMP_HYPERV_ARTIFACT) != brands.end()) { brands.erase(TMP_HYPERV_ARTIFACT); } } + // merge 2 brands, and make a single brand out of it. auto merger = [&](const char* a, const char* b, const char* result) -> void { if ( (brands.count(a) > 0) && @@ -9671,6 +9726,7 @@ struct VM { } }; + // same as above, but for 3 auto triple_merger = [&](const char* a, const char* b, const char* c, const char* result) -> void { if ( (brands.count(a) > 0) && @@ -9684,6 +9740,8 @@ struct VM { } }; + // some edgecase handling for Hyper-V and VirtualPC since + // they're very similar, and they're both from Microsoft (ew) if ((brands.count(TMP_HYPERV) > brands.count(TMP_VPC))) { brands.erase(TMP_VPC); } else if (brands.count(TMP_HYPERV) < brands.count(TMP_VPC)) { @@ -9695,9 +9753,19 @@ struct VM { merger(TMP_VPC, TMP_HYPERV, TMP_HYPERV_VPC); } - merger(TMP_HYPERV, TMP_HYPERV_ARTIFACT, TMP_HYPERV_ARTIFACT); - merger(TMP_VPC, TMP_HYPERV_ARTIFACT, TMP_HYPERV_ARTIFACT); - merger(TMP_HYPERV_VPC, TMP_HYPERV_ARTIFACT, TMP_HYPERV_ARTIFACT); + + // this is the section where brand post-processing will be done. + // The reason why this part is necessary is because it will + // output a more accurate picture on the VM brand. For example, + // Azure's cloud is based on Hyper-V, but Hyper-V may have + // a higher score due to the prevalence of it in a practical + // setting, which will put Azure to the side. This is stupid + // because there should be an indication that Azure is involved + // since it's a better idea to let the end-user know that the + // brand is "Azure Hyper-V" instead of just "Hyper-V". So what + // this section does is "merge" the brands together to form + // a more accurate idea of the brand(s) involved. + merger(TMP_AZURE, TMP_HYPERV, TMP_AZURE); merger(TMP_AZURE, TMP_VPC, TMP_AZURE); @@ -9723,8 +9791,11 @@ struct VM { merger(TMP_VMWARE, TMP_GSX, TMP_GSX); merger(TMP_VMWARE, TMP_WORKSTATION, TMP_WORKSTATION); + // the brand element, which stores the NAME (const char*) and the SCORE (u8) using brand_element_t = std::pair; + // sort the "brands" map so that the brands with the + // highest score appears first in descending order auto sorter = [&]() -> std::vector { std::vector vec(brands.begin(), brands.end()); @@ -9741,11 +9812,15 @@ struct VM { std::vector vec = sorter(); std::string ret_str = "Unknown"; + // if the multiple setting flag is NOT set, return the + // brand with the highest score. Else, return a std::string + // of the brand message (i.e. "VirtualBox or VMware"). + // See VM::MULTIPLE flag in docs for more information. if (!is_multiple) { ret_str = vec.front().first; } else { std::stringstream ss; - u8 i = 1; + std::size_t i = 1; ss << vec.front().first; for (; i < vec.size(); i++) { @@ -9755,6 +9830,7 @@ struct VM { ret_str = ss.str(); } + // cache the result if memoization is enabled if (core::is_disabled(flags, NO_MEMO)) { if (is_multiple) { core_debug("VM::brand(): cached multiple brand string"); @@ -9765,7 +9841,7 @@ struct VM { } } - // this gets annoying really fast + // debug stuff to see the brand scoreboard, ignore this #ifdef __VMAWARE_DEBUG__ for (const auto p : brands) { core_debug("scoreboard: ", (int)p.second, " : ", p.first); @@ -9784,23 +9860,26 @@ struct VM { */ template static bool detect(Args ...args) { + // fetch all the flags in a std::bitset flagset flags = core::arg_handler(args...); + // run all the techniques based on the + // flags above, and get a total score const u16 points = core::run_all(flags, SHORTCUT); #if (CPP >= 23) [[assume(points < maximum_points)]]; #endif - bool result = false; + u16 threshold = 150; + // if high threshold is set, the points + // will be 300. If not, leave it as 150. if (core::is_enabled(flags, HIGH_THRESHOLD)) { - result = (points >= high_threshold_score); - } else { - result = (points >= 150); + threshold = high_threshold_score; } - return result; + return (points >= threshold); } @@ -9812,21 +9891,29 @@ struct VM { */ template static u8 percentage(Args ...args) { + // fetch all the flags in a std::bitset const flagset flags = core::arg_handler(args...); + // run all the techniques based on the + // flags above, and get a total score const u16 points = core::run_all(flags, SHORTCUT); - u8 percent = 0; #if (CPP >= 23) [[assume(points < maximum_points)]]; #endif + u8 percent = 0; u16 threshold = 150; + // set to 300 if high threshold is enabled if (core::is_enabled(flags, HIGH_THRESHOLD)) { threshold = high_threshold_score; } + // the percentage will be set to 99%, because a score + // of 100 is not entirely robust. 150 is more robust + // in my opinion, which is why you need a score of + // above 150 to get to 100% if (points >= threshold) { percent = 100; } else if (points >= 100) { @@ -9853,6 +9940,7 @@ struct VM { , const std::source_location& loc = std::source_location::current() #endif ) { + // lambda to throw the error auto throw_error = [&](const char* text) -> void { std::stringstream ss; #if (CPP >= 20 && !CLANG) @@ -9870,11 +9958,13 @@ struct VM { [[assume(percent > 0 && percent <= 100)]]; #endif + // generate the custom technique struct core::custom_technique query{ percent, detection_func }; + // push it to the custome_table vector core::custom_table.emplace_back(query); } @@ -9887,6 +9977,8 @@ struct VM { */ template static flagset DISABLE(Args ...args) { + // basically core::arg_handler but in reverse, + // it'll clear the bits of the provided flags flagset flags = core::disabled_arg_handler(args...); flags.flip(); @@ -9902,7 +9994,7 @@ struct VM { /** * @brief This will convert the technique flag into a string, which will correspond to the technique name * @param single technique flag in VM structure - * @warning ⚠️ FOR DEVELOPMENT USAGE ONLY, NOT MEANT FOR PUBLIC USE ⚠️ + * @warning ⚠️ FOR DEVELOPMENT USAGE ONLY, NOT MEANT FOR PUBLIC USE FOR NOW ⚠️ */ [[nodiscard]] static std::string flag_to_string(const enum_flags flag) { switch (flag) { @@ -9934,18 +10026,18 @@ struct VM { case DISK_SIZE: return "DISK_SIZE"; case VBOX_DEFAULT: return "VBOX_DEFAULT"; case VBOX_NETWORK: return "VBOX_NETWORK"; - case COMPUTER_NAME: return "COMPUTER_NAME"; - case WINE_CHECK: return "WINE_CHECK"; - case HOSTNAME: return "HOSTNAME"; - case MEMORY: return "MEMORY"; - case VBOX_WINDOW_CLASS: return "VBOX_WINDOW_CLASS"; - case LOADED_DLLS: return "LOADED_DLLS"; - case KVM_REG: return "KVM_REG"; - case KVM_DRIVERS: return "KVM_DRIVERS"; - case KVM_DIRS: return "KVM_DIRS"; - case AUDIO: return "AUDIO"; - case QEMU_DIR: return "QEMU_DIR"; - case MOUSE_DEVICE: return "MOUSE_DEVICE"; +/* GPL */ case COMPUTER_NAME: return "COMPUTER_NAME"; +/* GPL */ case WINE_CHECK: return "WINE_CHECK"; +/* GPL */ case HOSTNAME: return "HOSTNAME"; +/* GPL */ case MEMORY: return "MEMORY"; +/* GPL */ case VBOX_WINDOW_CLASS: return "VBOX_WINDOW_CLASS"; +/* GPL */ case LOADED_DLLS: return "LOADED_DLLS"; +/* GPL */ case KVM_REG: return "KVM_REG"; +/* GPL */ case KVM_DRIVERS: return "KVM_DRIVERS"; +/* GPL */ case KVM_DIRS: return "KVM_DIRS"; +/* GPL */ case AUDIO: return "AUDIO"; +/* GPL */ case QEMU_DIR: return "QEMU_DIR"; +/* GPL */ case MOUSE_DEVICE: return "MOUSE_DEVICE"; case VM_PROCESSES: return "VM_PROCESSES"; case LINUX_USER_HOST: return "LINUX_USER_HOST"; case GAMARUE: return "GAMARUE"; @@ -10028,9 +10120,9 @@ struct VM { /** - * @brief return a vector of detected brand strings (DEVELOPMENT FUNCTION, NOT MEANT FOR PUBLIC USE) + * @brief return a vector of detected brand strings * @param any flag combination in VM structure or nothing - * @warning ⚠️ FOR DEVELOPMENT USAGE ONLY, NOT MEANT FOR PUBLIC USE ⚠️ + * @warning ⚠️ FOR DEVELOPMENT USAGE ONLY, NOT MEANT FOR PUBLIC USE FOR NOW ⚠️ */ template static std::map brand_map(Args ...args) { @@ -10038,8 +10130,7 @@ struct VM { // are all the techiques already run? if not, run all of them to get the necessary info to fetch the brand if (!memo::all_present() || core::is_enabled(flags, NO_MEMO)) { - u16 tmp = core::run_all(flags); - UNUSED(tmp); + core::run_all(flags); } return core::brand_scoreboard; @@ -10050,16 +10141,17 @@ struct VM { * @brief Change the certainty score of a technique * @param technique flag, then the new percentage score to overwite * @return void - * @warning ⚠️ FOR DEVELOPMENT USAGE ONLY, NOT MEANT FOR PUBLIC USE ⚠️ + * @warning ⚠️ FOR DEVELOPMENT USAGE ONLY, NOT MEANT FOR PUBLIC USE FOR NOW ⚠️ */ static void modify_score( const enum_flags flag, - const std::uint8_t percent + const u8 percent // clang doesn't support std::source_location for some reason #if (CPP >= 20 && !CLANG) , const std::source_location& loc = std::source_location::current() #endif ) { + // lambda to throw the error auto throw_error = [&](const char* text) -> void { std::stringstream ss; #if (CPP >= 20 && !CLANG) @@ -10077,19 +10169,196 @@ struct VM { [[assume(percent <= 100)]]; #endif + // check if the flag provided is a setting flag, which isn't valid. if (static_cast(flag) >= technique_end) { throw_error("The flag is not a technique flag"); } + // replica type alias of the technique table using table_t = std::map; auto modify = [](table_t &table, const enum_flags flag, const u8 percent) -> void { core::technique &tmp = table.at(flag); - table[flag] = { percent, tmp.run, tmp.spoofable }; + table[flag] = { percent, tmp.run, tmp.is_spoofable }; }; modify(const_cast(core::technique_table), flag, percent); } + + + /** + * @brief Fetch the total number of detected techniques + * @param any flag combination in VM structure or nothing + * @return std::uint8_t + */ + template + static u8 detected_count(Args ...args) { + flagset flags = core::arg_handler(args...); + + // run all the techniques, which will set the detected_count variable + core::run_all(flags); + + return detected_count_num; + } + + + /** + * @brief Fetch the total number of detected techniques + * @param any flag combination in VM structure or nothing + * @return std::uint8_t + */ + template + static std::string type(Args ...args) { + flagset flags = core::arg_handler(args...); + + const std::string brand_str = brand(flags); + + // if multiple brands were found, return unknown + if (util::find(brand_str, " or ")) { + return "Unknown"; + } + + const std::map type_table { + // type 1 + { XEN, "Hypervisor (type 1)" }, + { VMWARE_ESX, "Hypervisor (type 1)" }, + { ACRN, "Hypervisor (type 1)" }, + { QNX, "Hypervisor (type 1)" }, + { HYPERV, "Hypervisor (type 1)" }, + { AZURE_HYPERV, "Hypervisor (type 1)" }, + { NANOVISOR, "Hypervisor (type 1)" }, + { KVM, "Hypervisor (type 1)" }, + { BHYVE, "Hypervisor (type 1)" }, + { KVM_HYPERV, "Hypervisor (type 1)" }, + { QEMU_KVM_HYPERV, "Hypervisor (type 1)" }, + { QEMU_KVM, "Hypervisor (type 1)" }, + { INTEL_HAXM, "Hypervisor (type 1)" }, + { INTEL_KGT, "Hypervisor (type 1)" }, + { SIMPLEVISOR, "Hypervisor (type 1)" }, + { GCE, "Hypervisor (type 1)" }, + { OPENSTACK, "Hypervisor (type 1)" }, + { KUBEVIRT, "Hypervisor (type 1)" }, + { POWERVM, "Hypervisor (type 1)" }, + { AWS_NITRO, "Hypervisor (type 1)" }, + + // type 2 + { VBOX, "Hypervisor (type 2)" }, + { VMWARE, "Hypervisor (type 2)" }, + { VMWARE_EXPRESS, "Hypervisor (type 2)" }, + { VMWARE_GSX, "Hypervisor (type 2)" }, + { VMWARE_WORKSTATION, "Hypervisor (type 2)" }, + { VMWARE_FUSION, "Hypervisor (type 2)" }, + { PARALLELS, "Hypervisor (type 2)" }, + { VPC, "Hypervisor (type 2)" }, + { NVMM, "Hypervisor (type 2)" }, + { BSD_VMM, "Hypervisor (type 2)" }, + + // sandbox + { CUCKOO, "Sandbox" }, + { SANDBOXIE, "Sandbox" }, + { HYBRID, "Sandbox" }, + { CWSANDBOX, "Sandbox" }, + { JOEBOX, "Sandbox" }, + { ANUBIS, "Sandbox" }, + { COMODO, "Sandbox" }, + { THREATEXPERT, "Sandbox" }, + { ANYRUN, "Sandbox"}, + + // misc + { BOCHS, "Emulator" }, + { BLUESTACKS, "Emulator" }, + { MSXTA, "Emulator" }, + { QEMU, "Emulator/Hypervisor (type 2)" }, + { JAILHOUSE, "Partitioning Hypervisor" }, + { UNISYS, "Partitioning Hypervisor" }, + { DOCKER, "Container" }, + { PODMAN, "Container" }, + { OPENVZ, "Container" }, + { HYPERV_VPC, "Hypervisor (either type 1 or 2)" }, + { LMHS, "Hypervisor (unknown type)" }, + { WINE, "Compatibility layer" }, + { APPLE_VZ, "Unknown" }, + { HYPERV_ARTIFACT, "Unknown" }, + { UML, "Paravirtualised/Hypervisor (type 2)" }, + { WSL, "Hybrid Hyper-V (type 1 and 2)" }, // debatable tbh + { APPLE_ROSETTA, "Binary Translation Layer/Emulator" }, + }; + + auto it = type_table.find(brand_str.c_str()); + + if (it != type_table.end()) { + return it->second; + } + + return "Unknown"; + } + + + /** + * @brief Fetch the conclusion message based on the brand and percentage + * @param any flag combination in VM structure or nothing + * @return std::string + */ + template + static std::string conclusion(Args ...args) { + flagset flags = core::arg_handler(args...); + + const std::string brand_tmp = brand(flags); + const u8 percent_tmp = percentage(flags); + + constexpr const char* baremetal = "Running in baremetal"; + constexpr const char* very_unlikely = "Very unlikely a VM"; + constexpr const char* unlikely = "Unlikely a VM"; + + std::string potentially = "Potentially a VM"; + std::string might = "Might be a VM"; + std::string likely = "Likely a VM"; + std::string very_likely = "Very likely a VM"; + std::string inside_vm = "Running inside a VM"; + + if (brand_tmp != "Unknown") { + potentially = "Potentially a " + brand_tmp + " VM"; + might = "Might be a " + brand_tmp + " VM"; + likely = "Likely a " + brand_tmp + " VM"; + very_likely = "Very likely a " + brand_tmp + " VM"; + inside_vm = "Running inside a " + brand_tmp + " VM"; + } + + if (percent_tmp == 0) { return baremetal; } + else if (percent_tmp <= 20) { return very_unlikely; } + else if (percent_tmp <= 35) { return unlikely; } + else if (percent_tmp < 50) { return potentially; } + else if (percent_tmp <= 62) { return might; } + else if (percent_tmp <= 75) { return likely; } + else if (percent_tmp < 100) { return very_likely; } + else { return inside_vm; } + } + + + struct vmaware { + std::string brand; + std::string type; + std::string conclusion; + bool is_vm; + u8 percentage; + u8 detected_count; + u8 technique_count; + + vmaware() = default; + + template + vmaware(Args ...args) { + flagset flags = core::arg_handler(args...); + + brand = VM::brand(flags); + type = VM::type(flags); + conclusion = VM::conclusion(flags); + is_vm = VM::detect(flags); + percentage = VM::percentage(flags); + detected_count = VM::detected_count(flags); + technique_count = VM::technique_count; + } + }; }; MSVC_ENABLE_WARNING(ASSIGNMENT_OPERATOR NO_INLINE_FUNC SPECTRE) @@ -10174,12 +10443,22 @@ bool VM::memo::hyperv::is_stored = false; VM::u16 VM::total_points = 0; #endif -// not even sure how to explain honestly, just pretend these don't exist idfk +// these are basically the base values for the core::arg_handler function. +// It's like a bucket that will collect all the bits enabled. If for example +// VM::detect(VM::HIGH_THRESHOLD) is passed, the HIGH_THRESHOLD bit will be +// collected in this flagset (std::bitset) variable, and eventually be the +// return value for actual end-user functions like VM::detect() to rely +// and work on. VM::global_flags is just a copy of the flags but visible +// globally throughout the whole VM struct, as the name implies. VM::flagset VM::core::flag_collector; VM::flagset VM::global_flags; + +VM::u8 VM::detected_count_num = 0; + + // default flags -VM::flagset VM::DEFAULT = []() -> flagset { +VM::flagset VM::DEFAULT = []() noexcept -> flagset { flagset tmp; // set all bits to 1 @@ -10190,7 +10469,7 @@ VM::flagset VM::DEFAULT = []() -> flagset { tmp.flip(RDTSC); tmp.flip(RDTSC_VMEXIT); - // disable all the non-technique flags + // disable all the settings flags tmp.flip(NO_MEMO); tmp.flip(HIGH_THRESHOLD); tmp.flip(SPOOFABLE); @@ -10201,13 +10480,13 @@ VM::flagset VM::DEFAULT = []() -> flagset { // flag to enable every technique -VM::flagset VM::ALL = []() -> flagset { +VM::flagset VM::ALL = []() noexcept -> flagset { flagset tmp; // set all bits to 1 tmp.set(); - // disable all the non-technique flags (except SPOOFABLE) + // disable all the settings technique flags (except SPOOFABLE) tmp.flip(NO_MEMO); tmp.flip(HIGH_THRESHOLD); tmp.flip(MULTIPLE); @@ -10252,10 +10531,9 @@ std::vector VM::core::custom_table = { }; - // the 0~100 points are debatable, but I think it's fine how it is. Feel free to disagree. const std::map VM::core::technique_table = { - // FORMAT: VM:: = { certainty%, function pointer, is spoofable? } + // FORMAT: { VM::, { certainty%, function pointer, is spoofable? } }, { VM::VMID, { 100, VM::vmid, false } }, { VM::CPU_BRAND, { 50, VM::cpu_brand, false } }, @@ -10285,18 +10563,18 @@ const std::map VM::core::technique_table = { VM::DISK_SIZE, { 60, VM::disk_size, false } }, { VM::VBOX_DEFAULT, { 55, VM::vbox_default_specs, false } }, { VM::VBOX_NETWORK, { 70, VM::vbox_network_share, false } }, - { VM::COMPUTER_NAME, { 15, VM::computer_name_match, true } }, // GPL - { VM::WINE_CHECK, { 85, VM::wine, false } }, // GPL - { VM::HOSTNAME, { 25, VM::hostname_match, true } }, // GPL - { VM::MEMORY, { 35, VM::low_memory_space, false } }, // GPL - { VM::VBOX_WINDOW_CLASS, { 10, VM::vbox_window_class, false } }, // GPL - { VM::LOADED_DLLS, { 75, VM::loaded_dlls, true } }, // GPL - { VM::KVM_REG, { 75, VM::kvm_registry, true } }, // GPL - { VM::KVM_DRIVERS, { 55, VM::kvm_drivers, true } }, // GPL - { VM::KVM_DIRS, { 55, VM::kvm_directories, true } }, // GPL - { VM::AUDIO, { 35, VM::check_audio, false } }, // GPL - { VM::QEMU_DIR, { 45, VM::qemu_dir, true } }, // GPL - { VM::MOUSE_DEVICE, { 20, VM::mouse_device, true } }, // GPL +/* GPL */ { VM::COMPUTER_NAME, { 15, VM::computer_name_match, true } }, +/* GPL */ { VM::WINE_CHECK, { 85, VM::wine, false } }, +/* GPL */ { VM::HOSTNAME, { 25, VM::hostname_match, true } }, +/* GPL */ { VM::MEMORY, { 35, VM::low_memory_space, false } }, +/* GPL */ { VM::VBOX_WINDOW_CLASS, { 10, VM::vbox_window_class, false } }, +/* GPL */ { VM::LOADED_DLLS, { 75, VM::loaded_dlls, true } }, +/* GPL */ { VM::KVM_REG, { 75, VM::kvm_registry, true } }, +/* GPL */ { VM::KVM_DRIVERS, { 55, VM::kvm_drivers, true } }, +/* GPL */ { VM::KVM_DIRS, { 55, VM::kvm_directories, true } }, +/* GPL */ { VM::AUDIO, { 35, VM::check_audio, false } }, +/* GPL */ { VM::QEMU_DIR, { 45, VM::qemu_dir, true } }, +/* GPL */ { VM::MOUSE_DEVICE, { 20, VM::mouse_device, true } }, { VM::VM_PROCESSES, { 30, VM::vm_processes, true } }, { VM::LINUX_USER_HOST, { 25, VM::linux_user_host, true } }, { VM::GAMARUE, { 40, VM::gamarue, true } }, @@ -10341,7 +10619,7 @@ const std::map VM::core::technique_table = { VM::MUTEX, { 85, VM::mutex, false } }, { VM::UPTIME, { 10, VM::uptime, true } }, { VM::ODD_CPU_THREADS, { 80, VM::odd_cpu_threads, false } }, - { VM::INTEL_THREAD_MISMATCH, { 85, VM::intel_thread_mismatch, false } }, + { VM::INTEL_THREAD_MISMATCH, { 60, VM::intel_thread_mismatch, false } }, { VM::XEON_THREAD_MISMATCH, { 85, VM::xeon_thread_mismatch, false } }, { VM::NETTITUDE_VM_MEMORY, { 75, VM::nettitude_vm_memory, false } }, { VM::CPUID_BITSET, { 20, VM::cpuid_bitset, false } }, diff --git a/src/vmaware_MIT.hpp b/src/vmaware_MIT.hpp index 2d19e84..b9aa722 100644 --- a/src/vmaware_MIT.hpp +++ b/src/vmaware_MIT.hpp @@ -45,14 +45,14 @@ * * * ================================ SECTIONS ================================== - * - enums for publicly accessible techniques => line 322 - * - struct for internal cpu operations => line 581 - * - struct for internal memoization => line 1007 - * - struct for internal utility functions => line 1134 - * - struct for internal core components => line 9152 - * - start of internal VM detection techniques => line 2409 - * - start of public VM detection functions => line 9495 - * - start of externally defined variables => line 10095 + * - enums for publicly accessible techniques => line 346 + * - struct for internal cpu operations => line 598 + * - struct for internal memoization => line 1024 + * - struct for internal utility functions => line 1152 + * - struct for internal core components => line 8712 + * - start of internal VM detection techniques => line 2448 + * - start of public VM detection functions => line 9074 + * - start of externally defined variables => line 9900 * * * ================================ EXAMPLE ================================== @@ -71,6 +71,8 @@ * } */ +#pragma once + #if (defined(_MSC_VER) || defined(_WIN32) || defined(_WIN64) || defined(__MINGW32__)) #define MSVC 1 #define LINUX 0 @@ -448,7 +450,7 @@ struct VM { ANYRUN_DRIVER, ANYRUN_DIRECTORY, - // start of non-technique flags (THE ORDERING IS VERY SPECIFIC HERE AND MIGHT BREAK SOMETHING IF RE-ORDERED) + // start of settings technique flags (THE ORDERING IS VERY SPECIFIC HERE AND MIGHT BREAK SOMETHING IF RE-ORDERED) NO_MEMO, HIGH_THRESHOLD, NULL_ARG, // does nothing, just a placeholder flag mainly for the CLI @@ -458,7 +460,7 @@ struct VM { private: static constexpr u8 enum_size = MULTIPLE; // get enum size through value of last element - static constexpr u8 non_technique_count = MULTIPLE - NO_MEMO + 1; // get number of non-technique flags like VM::NO_MEMO for example + static constexpr u8 non_technique_count = MULTIPLE - NO_MEMO + 1; // get number of settings technique flags like VM::NO_MEMO for example static constexpr u8 INVALID = 255; // explicit invalid technique macro static constexpr u16 maximum_points = 4765; // theoretical total points if all VM detections returned true (which is practically impossible) static constexpr u16 high_threshold_score = 300; // new threshold score from 100 to 350 if VM::HIGH_THRESHOLD flag is enabled @@ -473,6 +475,11 @@ struct VM { static constexpr u8 non_technique_begin = NO_MEMO; static constexpr u8 non_technique_end = enum_end; + + // this is specifically meant for VM::detected_count() to + // get the total number of techniques that detected a VM + static u8 detected_count_num; + public: static constexpr u8 technique_count = NO_MEMO; // get total number of techniques static std::vector technique_vector; @@ -1141,6 +1148,7 @@ struct VM { }; }; + // miscellaneous functionalities struct util { #if (LINUX) @@ -1173,7 +1181,7 @@ struct VM { } std::vector buffer((std::istreambuf_iterator(file)), - std::istreambuf_iterator()); + std::istreambuf_iterator()); file.close(); @@ -1223,7 +1231,7 @@ struct VM { return ( (uid != euid) || (euid == 0) - ); + ); #elif (MSVC) BOOL is_admin = FALSE; HANDLE hToken = NULL; @@ -1681,7 +1689,16 @@ struct VM { /** * @brief Checks whether Hyper-V host artifacts are present instead of an actual Hyper-V VM - * @note idea and credits to Requiem (https://github.com/NotRequiem) + * @note Hyper-V has an obscure feature where if it's enabled in the host system, the CPU + * hardware values makes it look like the whole system is running inside Hyper-V, + * which isn't true. This makes it a challenge to determine whether the hardware + * values the library is collecting is either a real Hyper-V VM, or just the artifacts + * of what Hyper-V has left as a consequence of having it enabled in the host system. + * The reason why this is a problem is because the library might falsely conclude that + * your the host system is running in Hyper-V, which is a false positive. This is where + * the Hyper-X mechanism comes into play to distinguish between these two. + * @author idea by Requiem (https://github.com/NotRequiem) + * @link graph to explain how this works: https://github.com/kernelwernel/VMAware/blob/main/assets/Hyper-X.png */ [[nodiscard]] static bool hyper_x() { #if (!MSVC) @@ -1702,23 +1719,35 @@ struct VM { return result; }; - char out[sizeof(int32_t) * 4 + 1] = { 0 }; // e*x size + number of e*x registers + null terminator - cpu::cpuid((int*)out, cpu::leaf::hypervisor); + auto root_partition = []() -> bool { + u32 ebx, unused = 0; + cpu::cpuid(unused, ebx, unused, unused, 0x40000003); + return (ebx & 1); + }; - const u32 eax = static_cast(out[0]); + auto eax = []() -> bool { + char out[sizeof(int32_t) * 4 + 1] = { 0 }; // e*x size + number of e*x registers + null terminator + cpu::cpuid((int*)out, cpu::leaf::hypervisor); - core_debug("HYPER_X: eax = ", eax); + const u32 eax = static_cast(out[0]); - const bool is_eax_valid = ((eax == 11) || (eax == 12)); + core_debug("HYPER_X: eax = ", eax); - const std::array cpu = cpu::cpu_manufacturer(cpu::leaf::hypervisor); + return ((eax == 11) || (eax == 12)); + }; - const bool is_cpu_hyperv = ( - (cpu.at(0) == "Microsoft Hv") || - (cpu.at(1) == "Microsoft Hv") - ); - - if (is_eax_valid || is_cpu_hyperv) { + auto cpu_vmid = []() -> bool { + const std::array cpu = cpu::cpu_manufacturer(cpu::leaf::hypervisor); + + return ( + (cpu.at(0) == "Microsoft Hv") || + (cpu.at(1) == "Microsoft Hv") + ); + }; + + const u8 points = (root_partition() + eax() + cpu_vmid()); + + if (points >= 2) { // SMBIOS check const std::string smbios = SMBIOS_string(); @@ -2006,27 +2035,27 @@ struct VM { typedef NTSTATUS(WINAPI* RtlGetVersionFunc)(PRTL_OSVERSIONINFOW); const std::map windowsVersions = { - { 6002, 6 }, // windows vista, technically no number but this function is just for great than operations anyway so it doesn't matter - { 7601, 7 }, - { 9200, 8 }, - { 9600, 8 }, - { 10240, 10 }, - { 10586, 10 }, - { 14393, 10 }, - { 15063, 10 }, - { 16299, 10 }, - { 17134, 10 }, - { 17763, 10 }, - { 18362, 10 }, - { 18363, 10 }, - { 19041, 10 }, - { 19042, 10 }, - { 19043, 10 }, - { 19044, 10 }, - { 19045, 10 }, - { 22000, 11 }, - { 22621, 11 }, - { 22631, 11 } + { 6002, static_cast(6) }, // windows vista, technically no number but this function is just for great than operations anyway so it doesn't matter + { 7601, static_cast(7) }, + { 9200, static_cast(8) }, + { 9600, static_cast(8) }, + { 10240, static_cast(10) }, + { 10586, static_cast(10) }, + { 14393, static_cast(10) }, + { 15063, static_cast(10) }, + { 16299, static_cast(10) }, + { 17134, static_cast(10) }, + { 17763, static_cast(10) }, + { 18362, static_cast(10) }, + { 18363, static_cast(10) }, + { 19041, static_cast(10) }, + { 19042, static_cast(10) }, + { 19043, static_cast(10) }, + { 19044, static_cast(10) }, + { 19045, static_cast(10) }, + { 22000, static_cast(11) }, + { 22621, static_cast(11) }, + { 22631, static_cast(11) } }; HMODULE ntdll = LoadLibraryW(L"ntdll.dll"); @@ -3621,6 +3650,8 @@ struct VM { } + + /** * @brief Check for any VM processes that are active * @category Windows @@ -7551,7 +7582,7 @@ struct VM { cmp("Malware") || cmp("malsand") || cmp("ClonePC") - ) { + ) { return true; } @@ -8684,7 +8715,7 @@ struct VM { struct technique { u8 points = 0; // this is the certainty score between 0 and 100 std::function run; // this is the technique function itself - bool spoofable = false; // this is to indicate that the technique can be very easily spoofed (not guaranteed) + bool is_spoofable = false; // this is to indicate that the technique can be very easily spoofed (not guaranteed) }; struct custom_technique { @@ -8742,7 +8773,7 @@ struct VM { return false; } - [[nodiscard]] static bool is_non_technique_set(const flagset& flags) { + [[nodiscard]] static bool is_setting_flag_set(const flagset& flags) { for (std::size_t i = non_technique_begin; i < non_technique_end; i++) { if (flags.test(i)) { return true; @@ -8768,11 +8799,11 @@ struct VM { return; } - if (!core::is_non_technique_set(flags)) { + if (!core::is_setting_flag_set(flags)) { throw std::invalid_argument("Invalid flag option for function parameter found, either leave it empty or add the VM::DEFAULT flag"); } - // at this stage, only non-technique flags are asserted to be set + // at this stage, only settings technique flags are asserted to be set if ( flags.test(NO_MEMO) || flags.test(HIGH_THRESHOLD) || @@ -8789,22 +8820,26 @@ struct VM { // run every VM detection mechanism in the technique table static u16 run_all(const flagset& flags, const bool shortcut = false) { u16 points = 0; + const bool memo_enabled = core::is_disabled(flags, NO_MEMO); const u16 threshold_points = (core::is_enabled(flags, HIGH_THRESHOLD) ? high_threshold_score : 200); - // for main technique table + // loop through technique table, where all the techniques are stored for (const auto& tmp : technique_table) { const enum_flags technique_macro = tmp.first; - const technique tuple = tmp.second; + const technique technique_data = tmp.second; - // check if it's disabled + // check if the technique is disabled if (core::is_disabled(flags, technique_macro)) { continue; } // check if it's spoofable, and whether it's enabled - if (tuple.spoofable && core::is_disabled(flags, SPOOFABLE)) { + if ( + technique_data.is_spoofable && + core::is_disabled(flags, SPOOFABLE) + ) { continue; } @@ -8820,25 +8855,27 @@ struct VM { } // run the technique - const bool result = tuple.run(); + const bool result = technique_data.run(); // accumulate the points if technique detected a VM if (result) { - points += tuple.points; - } + points += technique_data.points; - /** - * for things like VM::detect() and VM::percentage(), - * a score of 200+ is guaranteed to be a VM, so - * there's no point in running the rest of the techniques - */ + // this is specific to VM::detected_count() which returns + // the number of techniques that returned a positive + detected_count_num++; + } + + // for things like VM::detect() and VM::percentage(), + // a score of 200+ is guaranteed to be a VM, so + // there's no point in running the rest of the techniques if (shortcut && points >= threshold_points) { return points; } // store the current technique result to the cache if (memo_enabled) { - memo::cache_store(technique_macro, result, tuple.points); + memo::cache_store(technique_macro, result, technique_data.points); } } @@ -8859,13 +8896,20 @@ struct VM { * basically what this entire template fuckery does is manage the * variadic arguments being given through the arg_handler function, * which could either be a std::bitset, a uint8_t, or a combination - * of both of them. Thisz will handle both argument types and implement + * of both of them. This will handle both argument types and implement * them depending on what their types are. If it's a std::bitset, - * do the |= operation. If it's a uint8_t, simply .set() that into - * the flag_collector bitset. That's the gist of it. + * do the |= operation on flag_collector. If it's a uint8_t, simply + * .set() that into the flag_collector. That's the gist of it. * * Also I won't even deny, the majority of this section was 90% generated * by chatgpt. Can't be arsed with this C++ templatisation shit. + * Like is it really my fault that I have a hard time understanging C++'s + * god awful metaprogramming designs? And don't even get me started on SNIFAE. + * + * You don't need an IQ of 3 digits to realise how dogshit this language + * is, when you end up in situations where there's a few correct solutions + * to a problem, but with a billion ways you can do the same thing but in + * the "wrong" way. I genuinely can't wait for Carbon to come out. */ private: static flagset flag_collector; @@ -8983,18 +9027,23 @@ struct VM { #endif public: - // Function template to test variadic arguments + // fetch the flags, could be an enum value OR a std::bitset. + // This will then generate a different std::bitset as the + // return value by enabling the bits based on the argument. template static flagset arg_handler(Args&&... args) { - if VMAWARE_CONSTEXPR(is_empty()) { + if VMAWARE_CONSTEXPR (is_empty()) { return DEFAULT; } flag_collector.reset(); global_flags.reset(); + // set the bits in the flag, can take in + // either an enum value or a std::bitset handleArgs(std::forward(args)...); + // handle edgecases core::flag_sanitizer(flag_collector); global_flags = flag_collector; @@ -9007,14 +9056,15 @@ struct VM { static flagset disabled_arg_handler(Args&&... args) { flag_collector.reset(); - if VMAWARE_CONSTEXPR(is_empty()) { - throw std::invalid_argument("VM::DISABLE must contain a flag"); + if VMAWARE_CONSTEXPR (is_empty()) { + throw std::invalid_argument("VM::DISABLE() must contain a flag"); } handle_disabled_args(std::forward(args)...); - if (core::is_non_technique_set(flag_collector)) { - throw std::invalid_argument("VM::DISABLE must not contain a non-technique flag, they are disabled by default anyway"); + // check if a settings flag is set, which is not valid + if (core::is_setting_flag_set(flag_collector)) { + throw std::invalid_argument("VM::DISABLE() must not contain a settings flag, they are disabled by default anyway"); } return flag_collector; @@ -9051,7 +9101,7 @@ struct VM { throw_error("Flag argument must be a valid"); } - // check if the bit is a non-technique flag, which shouldn't be allowed + // check if the bit is a settings flag, which shouldn't be allowed if ( (flag_bit == NO_MEMO) || (flag_bit == HIGH_THRESHOLD) || @@ -9081,6 +9131,10 @@ struct VM { const core::technique& pair = it->second; const bool result = pair.run(); + if (result) { + detected_count_num++; + } + #ifdef __VMAWARE_DEBUG__ total_points += pair.points; #endif @@ -9102,15 +9156,17 @@ struct VM { [[nodiscard]] static std::string brand(Args ...args) { flagset flags = core::arg_handler(args...); + // is the multiple setting flag enabled? (meaning multiple + // brand strings will be outputted if there's a conflict) const bool is_multiple = core::is_enabled(flags, MULTIPLE); - // are all the techiques already run? if not, run all of them to get the necessary info to fetch the brand + // are all the techiques already run? if not, run them + // to fetch the necessary info to determine the brand if (!memo::all_present() || core::is_enabled(flags, NO_MEMO)) { - u16 tmp = core::run_all(flags); - UNUSED(tmp); + core::run_all(flags); } - // check if it's already cached and return that instead + // check if the result is already cached and return that instead if (core::is_disabled(flags, NO_MEMO)) { if (is_multiple) { if (memo::multi_brand::is_cached()) { @@ -9125,7 +9181,8 @@ struct VM { } } - // goofy ass C++11 and C++14 linker error workaround + // goofy ass C++11 and C++14 linker error workaround, + // and yes, this does look indeed stupid. #if (CPP <= 14) constexpr const char* TMP_QEMU = "QEMU"; constexpr const char* TMP_KVM = "KVM"; @@ -9168,27 +9225,42 @@ struct VM { constexpr const char* TMP_HYPERV_ARTIFACT = HYPERV_ARTIFACT; #endif + // this is where all the RELEVANT brands are stored. + // The ones with no points will be filtered out. std::map brands; + // add the relevant brands with at least 1 point for (const auto &element : core::brand_scoreboard) { if (element.second > 0) { brands.insert(std::make_pair(element.first, element.second)); } } - // if no brand had a single point, return "Unknown" + // if all brands had a point of 0, return + // "Unknown" (no relevant brands were found) if (brands.empty()) { return "Unknown"; } + // if there's only a single brand, return it. + // This will skip the rest of the function + // where it will process and merge certain + // brands if (brands.size() == 1) { return brands.begin()->first; - } else if (brands.size() > 1) { + } + + // remove Hyper-V artifacts if found with other + // brands, because that's not a VM. It's added + // only for the sake of information cuz of the + // fucky wucky Hyper-V problem (see Hyper-X) + if (brands.size() > 1) { if (brands.find(TMP_HYPERV_ARTIFACT) != brands.end()) { brands.erase(TMP_HYPERV_ARTIFACT); } } + // merge 2 brands, and make a single brand out of it. auto merger = [&](const char* a, const char* b, const char* result) -> void { if ( (brands.count(a) > 0) && @@ -9200,6 +9272,7 @@ struct VM { } }; + // same as above, but for 3 auto triple_merger = [&](const char* a, const char* b, const char* c, const char* result) -> void { if ( (brands.count(a) > 0) && @@ -9213,6 +9286,8 @@ struct VM { } }; + // some edgecase handling for Hyper-V and VirtualPC since + // they're very similar, and they're both from Microsoft (ew) if ((brands.count(TMP_HYPERV) > brands.count(TMP_VPC))) { brands.erase(TMP_VPC); } else if (brands.count(TMP_HYPERV) < brands.count(TMP_VPC)) { @@ -9224,9 +9299,19 @@ struct VM { merger(TMP_VPC, TMP_HYPERV, TMP_HYPERV_VPC); } - merger(TMP_HYPERV, TMP_HYPERV_ARTIFACT, TMP_HYPERV_ARTIFACT); - merger(TMP_VPC, TMP_HYPERV_ARTIFACT, TMP_HYPERV_ARTIFACT); - merger(TMP_HYPERV_VPC, TMP_HYPERV_ARTIFACT, TMP_HYPERV_ARTIFACT); + + // this is the section where brand post-processing will be done. + // The reason why this part is necessary is because it will + // output a more accurate picture on the VM brand. For example, + // Azure's cloud is based on Hyper-V, but Hyper-V may have + // a higher score due to the prevalence of it in a practical + // setting, which will put Azure to the side. This is stupid + // because there should be an indication that Azure is involved + // since it's a better idea to let the end-user know that the + // brand is "Azure Hyper-V" instead of just "Hyper-V". So what + // this section does is "merge" the brands together to form + // a more accurate idea of the brand(s) involved. + merger(TMP_AZURE, TMP_HYPERV, TMP_AZURE); merger(TMP_AZURE, TMP_VPC, TMP_AZURE); @@ -9252,8 +9337,11 @@ struct VM { merger(TMP_VMWARE, TMP_GSX, TMP_GSX); merger(TMP_VMWARE, TMP_WORKSTATION, TMP_WORKSTATION); + // the brand element, which stores the NAME (const char*) and the SCORE (u8) using brand_element_t = std::pair; + // sort the "brands" map so that the brands with the + // highest score appears first in descending order auto sorter = [&]() -> std::vector { std::vector vec(brands.begin(), brands.end()); @@ -9270,11 +9358,15 @@ struct VM { std::vector vec = sorter(); std::string ret_str = "Unknown"; + // if the multiple setting flag is NOT set, return the + // brand with the highest score. Else, return a std::string + // of the brand message (i.e. "VirtualBox or VMware"). + // See VM::MULTIPLE flag in docs for more information. if (!is_multiple) { ret_str = vec.front().first; } else { std::stringstream ss; - u8 i = 1; + std::size_t i = 1; ss << vec.front().first; for (; i < vec.size(); i++) { @@ -9284,6 +9376,7 @@ struct VM { ret_str = ss.str(); } + // cache the result if memoization is enabled if (core::is_disabled(flags, NO_MEMO)) { if (is_multiple) { core_debug("VM::brand(): cached multiple brand string"); @@ -9294,7 +9387,7 @@ struct VM { } } - // this gets annoying really fast + // debug stuff to see the brand scoreboard, ignore this #ifdef __VMAWARE_DEBUG__ for (const auto p : brands) { core_debug("scoreboard: ", (int)p.second, " : ", p.first); @@ -9313,23 +9406,26 @@ struct VM { */ template static bool detect(Args ...args) { + // fetch all the flags in a std::bitset flagset flags = core::arg_handler(args...); + // run all the techniques based on the + // flags above, and get a total score const u16 points = core::run_all(flags, SHORTCUT); #if (CPP >= 23) [[assume(points < maximum_points)]]; #endif - bool result = false; + u16 threshold = 150; + // if high threshold is set, the points + // will be 300. If not, leave it as 150. if (core::is_enabled(flags, HIGH_THRESHOLD)) { - result = (points >= high_threshold_score); - } else { - result = (points >= 150); + threshold = high_threshold_score; } - return result; + return (points >= threshold); } @@ -9341,21 +9437,29 @@ struct VM { */ template static u8 percentage(Args ...args) { + // fetch all the flags in a std::bitset const flagset flags = core::arg_handler(args...); + // run all the techniques based on the + // flags above, and get a total score const u16 points = core::run_all(flags, SHORTCUT); - u8 percent = 0; #if (CPP >= 23) [[assume(points < maximum_points)]]; #endif + u8 percent = 0; u16 threshold = 150; + // set to 300 if high threshold is enabled if (core::is_enabled(flags, HIGH_THRESHOLD)) { threshold = high_threshold_score; } + // the percentage will be set to 99%, because a score + // of 100 is not entirely robust. 150 is more robust + // in my opinion, which is why you need a score of + // above 150 to get to 100% if (points >= threshold) { percent = 100; } else if (points >= 100) { @@ -9382,6 +9486,7 @@ struct VM { , const std::source_location& loc = std::source_location::current() #endif ) { + // lambda to throw the error auto throw_error = [&](const char* text) -> void { std::stringstream ss; #if (CPP >= 20 && !CLANG) @@ -9399,11 +9504,13 @@ struct VM { [[assume(percent > 0 && percent <= 100)]]; #endif + // generate the custom technique struct core::custom_technique query{ percent, detection_func }; + // push it to the custome_table vector core::custom_table.emplace_back(query); } @@ -9416,6 +9523,8 @@ struct VM { */ template static flagset DISABLE(Args ...args) { + // basically core::arg_handler but in reverse, + // it'll clear the bits of the provided flags flagset flags = core::disabled_arg_handler(args...); flags.flip(); @@ -9431,7 +9540,7 @@ struct VM { /** * @brief This will convert the technique flag into a string, which will correspond to the technique name * @param single technique flag in VM structure - * @warning ⚠️ FOR DEVELOPMENT USAGE ONLY, NOT MEANT FOR PUBLIC USE ⚠️ + * @warning ⚠️ FOR DEVELOPMENT USAGE ONLY, NOT MEANT FOR PUBLIC USE FOR NOW ⚠️ */ [[nodiscard]] static std::string flag_to_string(const enum_flags flag) { switch (flag) { @@ -9545,9 +9654,9 @@ struct VM { /** - * @brief return a vector of detected brand strings (DEVELOPMENT FUNCTION, NOT MEANT FOR PUBLIC USE) + * @brief return a vector of detected brand strings * @param any flag combination in VM structure or nothing - * @warning ⚠️ FOR DEVELOPMENT USAGE ONLY, NOT MEANT FOR PUBLIC USE ⚠️ + * @warning ⚠️ FOR DEVELOPMENT USAGE ONLY, NOT MEANT FOR PUBLIC USE FOR NOW ⚠️ */ template static std::map brand_map(Args ...args) { @@ -9555,8 +9664,7 @@ struct VM { // are all the techiques already run? if not, run all of them to get the necessary info to fetch the brand if (!memo::all_present() || core::is_enabled(flags, NO_MEMO)) { - u16 tmp = core::run_all(flags); - UNUSED(tmp); + core::run_all(flags); } return core::brand_scoreboard; @@ -9567,16 +9675,17 @@ struct VM { * @brief Change the certainty score of a technique * @param technique flag, then the new percentage score to overwite * @return void - * @warning ⚠️ FOR DEVELOPMENT USAGE ONLY, NOT MEANT FOR PUBLIC USE ⚠️ + * @warning ⚠️ FOR DEVELOPMENT USAGE ONLY, NOT MEANT FOR PUBLIC USE FOR NOW ⚠️ */ static void modify_score( const enum_flags flag, - const std::uint8_t percent + const u8 percent // clang doesn't support std::source_location for some reason #if (CPP >= 20 && !CLANG) , const std::source_location& loc = std::source_location::current() #endif ) { + // lambda to throw the error auto throw_error = [&](const char* text) -> void { std::stringstream ss; #if (CPP >= 20 && !CLANG) @@ -9594,19 +9703,196 @@ struct VM { [[assume(percent <= 100)]]; #endif + // check if the flag provided is a setting flag, which isn't valid. if (static_cast(flag) >= technique_end) { throw_error("The flag is not a technique flag"); } + // replica type alias of the technique table using table_t = std::map; auto modify = [](table_t &table, const enum_flags flag, const u8 percent) -> void { core::technique &tmp = table.at(flag); - table[flag] = { percent, tmp.run, tmp.spoofable }; + table[flag] = { percent, tmp.run, tmp.is_spoofable }; }; modify(const_cast(core::technique_table), flag, percent); } + + + /** + * @brief Fetch the total number of detected techniques + * @param any flag combination in VM structure or nothing + * @return std::uint8_t + */ + template + static u8 detected_count(Args ...args) { + flagset flags = core::arg_handler(args...); + + // run all the techniques, which will set the detected_count variable + core::run_all(flags); + + return detected_count_num; + } + + + /** + * @brief Fetch the total number of detected techniques + * @param any flag combination in VM structure or nothing + * @return std::uint8_t + */ + template + static std::string type(Args ...args) { + flagset flags = core::arg_handler(args...); + + const std::string brand_str = brand(flags); + + // if multiple brands were found, return unknown + if (util::find(brand_str, " or ")) { + return "Unknown"; + } + + const std::map type_table { + // type 1 + { XEN, "Hypervisor (type 1)" }, + { VMWARE_ESX, "Hypervisor (type 1)" }, + { ACRN, "Hypervisor (type 1)" }, + { QNX, "Hypervisor (type 1)" }, + { HYPERV, "Hypervisor (type 1)" }, + { AZURE_HYPERV, "Hypervisor (type 1)" }, + { NANOVISOR, "Hypervisor (type 1)" }, + { KVM, "Hypervisor (type 1)" }, + { BHYVE, "Hypervisor (type 1)" }, + { KVM_HYPERV, "Hypervisor (type 1)" }, + { QEMU_KVM_HYPERV, "Hypervisor (type 1)" }, + { QEMU_KVM, "Hypervisor (type 1)" }, + { INTEL_HAXM, "Hypervisor (type 1)" }, + { INTEL_KGT, "Hypervisor (type 1)" }, + { SIMPLEVISOR, "Hypervisor (type 1)" }, + { GCE, "Hypervisor (type 1)" }, + { OPENSTACK, "Hypervisor (type 1)" }, + { KUBEVIRT, "Hypervisor (type 1)" }, + { POWERVM, "Hypervisor (type 1)" }, + { AWS_NITRO, "Hypervisor (type 1)" }, + + // type 2 + { VBOX, "Hypervisor (type 2)" }, + { VMWARE, "Hypervisor (type 2)" }, + { VMWARE_EXPRESS, "Hypervisor (type 2)" }, + { VMWARE_GSX, "Hypervisor (type 2)" }, + { VMWARE_WORKSTATION, "Hypervisor (type 2)" }, + { VMWARE_FUSION, "Hypervisor (type 2)" }, + { PARALLELS, "Hypervisor (type 2)" }, + { VPC, "Hypervisor (type 2)" }, + { NVMM, "Hypervisor (type 2)" }, + { BSD_VMM, "Hypervisor (type 2)" }, + + // sandbox + { CUCKOO, "Sandbox" }, + { SANDBOXIE, "Sandbox" }, + { HYBRID, "Sandbox" }, + { CWSANDBOX, "Sandbox" }, + { JOEBOX, "Sandbox" }, + { ANUBIS, "Sandbox" }, + { COMODO, "Sandbox" }, + { THREATEXPERT, "Sandbox" }, + { ANYRUN, "Sandbox"}, + + // misc + { BOCHS, "Emulator" }, + { BLUESTACKS, "Emulator" }, + { MSXTA, "Emulator" }, + { QEMU, "Emulator/Hypervisor (type 2)" }, + { JAILHOUSE, "Partitioning Hypervisor" }, + { UNISYS, "Partitioning Hypervisor" }, + { DOCKER, "Container" }, + { PODMAN, "Container" }, + { OPENVZ, "Container" }, + { HYPERV_VPC, "Hypervisor (either type 1 or 2)" }, + { LMHS, "Hypervisor (unknown type)" }, + { WINE, "Compatibility layer" }, + { APPLE_VZ, "Unknown" }, + { HYPERV_ARTIFACT, "Unknown" }, + { UML, "Paravirtualised/Hypervisor (type 2)" }, + { WSL, "Hybrid Hyper-V (type 1 and 2)" }, // debatable tbh + { APPLE_ROSETTA, "Binary Translation Layer/Emulator" }, + }; + + auto it = type_table.find(brand_str.c_str()); + + if (it != type_table.end()) { + return it->second; + } + + return "Unknown"; + } + + + /** + * @brief Fetch the conclusion message based on the brand and percentage + * @param any flag combination in VM structure or nothing + * @return std::string + */ + template + static std::string conclusion(Args ...args) { + flagset flags = core::arg_handler(args...); + + const std::string brand_tmp = brand(flags); + const u8 percent_tmp = percentage(flags); + + constexpr const char* baremetal = "Running in baremetal"; + constexpr const char* very_unlikely = "Very unlikely a VM"; + constexpr const char* unlikely = "Unlikely a VM"; + + std::string potentially = "Potentially a VM"; + std::string might = "Might be a VM"; + std::string likely = "Likely a VM"; + std::string very_likely = "Very likely a VM"; + std::string inside_vm = "Running inside a VM"; + + if (brand_tmp != "Unknown") { + potentially = "Potentially a " + brand_tmp + " VM"; + might = "Might be a " + brand_tmp + " VM"; + likely = "Likely a " + brand_tmp + " VM"; + very_likely = "Very likely a " + brand_tmp + " VM"; + inside_vm = "Running inside a " + brand_tmp + " VM"; + } + + if (percent_tmp == 0) { return baremetal; } + else if (percent_tmp <= 20) { return very_unlikely; } + else if (percent_tmp <= 35) { return unlikely; } + else if (percent_tmp < 50) { return potentially; } + else if (percent_tmp <= 62) { return might; } + else if (percent_tmp <= 75) { return likely; } + else if (percent_tmp < 100) { return very_likely; } + else { return inside_vm; } + } + + + struct vmaware { + bool is_vm; + u8 percentage; + u8 detected_count; + u8 technique_count; + std::string brand; + std::string type; + std::string conclusion; + + vmaware() = default; + + template + vmaware(Args ...args) { + flagset flags = core::arg_handler(args...); + + is_vm = VM::detect(flags); + percentage = VM::percentage(flags); + detected_count = VM::detected_count(flags); + technique_count = VM::technique_count; + brand = VM::brand(flags); + type = VM::type(flags); + conclusion = VM::conclusion(flags); + } + }; }; MSVC_ENABLE_WARNING(ASSIGNMENT_OPERATOR NO_INLINE_FUNC SPECTRE) @@ -9691,12 +9977,22 @@ bool VM::memo::hyperv::is_stored = false; VM::u16 VM::total_points = 0; #endif -// not even sure how to explain honestly, just pretend these don't exist idfk +// these are basically the base values for the core::arg_handler function. +// It's like a bucket that will collect all the bits enabled. If for example +// VM::detect(VM::HIGH_THRESHOLD) is passed, the HIGH_THRESHOLD bit will be +// collected in this flagset (std::bitset) variable, and eventually be the +// return value for actual end-user functions like VM::detect() to rely +// and work on. VM::global_flags is just a copy of the flags but visible +// globally throughout the whole VM struct, as the name implies. VM::flagset VM::core::flag_collector; VM::flagset VM::global_flags; + +VM::u8 VM::detected_count_num = 0; + + // default flags -VM::flagset VM::DEFAULT = []() -> flagset { +VM::flagset VM::DEFAULT = []() noexcept -> flagset { flagset tmp; // set all bits to 1 @@ -9707,7 +10003,7 @@ VM::flagset VM::DEFAULT = []() -> flagset { tmp.flip(RDTSC); tmp.flip(RDTSC_VMEXIT); - // disable all the non-technique flags + // disable all the settings flags tmp.flip(NO_MEMO); tmp.flip(HIGH_THRESHOLD); tmp.flip(SPOOFABLE); @@ -9718,13 +10014,13 @@ VM::flagset VM::DEFAULT = []() -> flagset { // flag to enable every technique -VM::flagset VM::ALL = []() -> flagset { +VM::flagset VM::ALL = []() noexcept -> flagset { flagset tmp; // set all bits to 1 tmp.set(); - // disable all the non-technique flags (except SPOOFABLE) + // disable all the settings technique flags (except SPOOFABLE) tmp.flip(NO_MEMO); tmp.flip(HIGH_THRESHOLD); tmp.flip(MULTIPLE); @@ -9769,10 +10065,9 @@ std::vector VM::core::custom_table = { }; - // the 0~100 points are debatable, but I think it's fine how it is. Feel free to disagree. const std::map VM::core::technique_table = { - // FORMAT: VM:: = { certainty%, function pointer, is spoofable? } + // FORMAT: { VM::, { certainty%, function pointer, is spoofable? } }, { VM::VMID, { 100, VM::vmid, false } }, { VM::CPU_BRAND, { 50, VM::cpu_brand, false } }, @@ -9846,7 +10141,7 @@ const std::map VM::core::technique_table = { VM::MUTEX, { 85, VM::mutex, false } }, { VM::UPTIME, { 10, VM::uptime, true } }, { VM::ODD_CPU_THREADS, { 80, VM::odd_cpu_threads, false } }, - { VM::INTEL_THREAD_MISMATCH, { 85, VM::intel_thread_mismatch, false } }, + { VM::INTEL_THREAD_MISMATCH, { 60, VM::intel_thread_mismatch, false } }, { VM::XEON_THREAD_MISMATCH, { 85, VM::xeon_thread_mismatch, false } }, { VM::NETTITUDE_VM_MEMORY, { 75, VM::nettitude_vm_memory, false } }, { VM::CPUID_BITSET, { 20, VM::cpuid_bitset, false } }, @@ -9878,4 +10173,4 @@ const std::map VM::core::technique_table = { VM::WSL_PROC, { 30, VM::wsl_proc_subdir, false } }, { VM::ANYRUN_DRIVER, { 65, VM::anyrun_driver, false } }, { VM::ANYRUN_DIRECTORY, { 35, VM::anyrun_directory, false } } -}; \ No newline at end of file +};