pax_global_header00006660000000000000000000000064130605614120014510gustar00rootroot0000000000000052 comment=8f1d88ac472634f23875154bd93d7a78b8526621 winregfs-0.7/000077500000000000000000000000001306056141200132025ustar00rootroot00000000000000winregfs-0.7/.gitignore000066400000000000000000000004571306056141200152000ustar00rootroot00000000000000# # Build ignores # .* *.o *.o.* *.a *.s *.1.gz Kbuild Config.in # # Never ignore these # !.gitignore # # Normal output and testing dirs # /foo /fsck.winregfs /mount.winregfs /debug.log /*.?.gz # # Backups / patches # *~ *.orig *.rej /*.patch # # debugging stuff # core .gdb_history .gdbinit *.hiv winregfs-0.7/INSTALL000066400000000000000000000011131306056141200142270ustar00rootroot00000000000000Just type "make" and then "make install". DESTDIR may be specified to install elsewhere: make DESTDIR=/pkg install If your compiler doesn't support LTO, remove "-flto" from Makefile and try compiling again. ntreg still contains code that winregfs may not use and LTO removes this dead code from the final program. A script "chroot_build.sh" is provided which will perform builds in your own local chroot build environments and makes tar.xz package files for each one specified at the top of the script. This is handy for supporting multiple C libraries and varying machine bit sizes. winregfs-0.7/LGPL.txt000066400000000000000000000636421306056141200145140ustar00rootroot00000000000000 GNU LESSER GENERAL PUBLIC LICENSE Version 2.1, February 1999 Copyright (C) 1991, 1999 Free Software Foundation, Inc. 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. [This is the first released version of the Lesser GPL. It also counts as the successor of the GNU Library Public License, version 2, hence the version number 2.1.] Preamble The licenses for most software are designed to take away your freedom to share and change it. By contrast, the GNU General Public Licenses are intended to guarantee your freedom to share and change free software--to make sure the software is free for all its users. This license, the Lesser General Public License, applies to some specially designated software packages--typically libraries--of the Free Software Foundation and other authors who decide to use it. You can use it too, but we suggest you first think carefully about whether this license or the ordinary General Public License is the better strategy to use in any particular case, based on the explanations below. When we speak of free software, we are referring to freedom of use, 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 this service if you wish); that you receive source code or can get it if you want it; that you can change the software and use pieces of it in new free programs; and that you are informed that you can do these things. To protect your rights, we need to make restrictions that forbid distributors to deny you these rights or to ask you to surrender these rights. These restrictions translate to certain responsibilities for you if you distribute copies of the library or if you modify it. For example, if you distribute copies of the library, whether gratis or for a fee, you must give the recipients all the rights that we gave you. You must make sure that they, too, receive or can get the source code. If you link other code with the library, you must provide complete object files to the recipients, so that they can relink them with the library after making changes to the library and recompiling it. And you must show them these terms so they know their rights. We protect your rights with a two-step method: (1) we copyright the library, and (2) we offer you this license, which gives you legal permission to copy, distribute and/or modify the library. To protect each distributor, we want to make it very clear that there is no warranty for the free library. Also, if the library is modified by someone else and passed on, the recipients should know that what they have is not the original version, so that the original author's reputation will not be affected by problems that might be introduced by others. Finally, software patents pose a constant threat to the existence of any free program. We wish to make sure that a company cannot effectively restrict the users of a free program by obtaining a restrictive license from a patent holder. Therefore, we insist that any patent license obtained for a version of the library must be consistent with the full freedom of use specified in this license. Most GNU software, including some libraries, is covered by the ordinary GNU General Public License. This license, the GNU Lesser General Public License, applies to certain designated libraries, and is quite different from the ordinary General Public License. We use this license for certain libraries in order to permit linking those libraries into non-free programs. When a program is linked with a library, whether statically or using a shared library, the combination of the two is legally speaking a combined work, a derivative of the original library. The ordinary General Public License therefore permits such linking only if the entire combination fits its criteria of freedom. The Lesser General Public License permits more lax criteria for linking other code with the library. We call this license the "Lesser" General Public License because it does Less to protect the user's freedom than the ordinary General Public License. It also provides other free software developers Less of an advantage over competing non-free programs. These disadvantages are the reason we use the ordinary General Public License for many libraries. However, the Lesser license provides advantages in certain special circumstances. For example, on rare occasions, there may be a special need to encourage the widest possible use of a certain library, so that it becomes a de-facto standard. To achieve this, non-free programs must be allowed to use the library. A more frequent case is that a free library does the same job as widely used non-free libraries. In this case, there is little to gain by limiting the free library to free software only, so we use the Lesser General Public License. In other cases, permission to use a particular library in non-free programs enables a greater number of people to use a large body of free software. For example, permission to use the GNU C Library in non-free programs enables many more people to use the whole GNU operating system, as well as its variant, the GNU/Linux operating system. Although the Lesser General Public License is Less protective of the users' freedom, it does ensure that the user of a program that is linked with the Library has the freedom and the wherewithal to run that program using a modified version of the Library. The precise terms and conditions for copying, distribution and modification follow. Pay close attention to the difference between a "work based on the library" and a "work that uses the library". The former contains code derived from the library, whereas the latter must be combined with the library in order to run. GNU LESSER GENERAL PUBLIC LICENSE TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 0. This License Agreement applies to any software library or other program which contains a notice placed by the copyright holder or other authorized party saying it may be distributed under the terms of this Lesser General Public License (also called "this License"). Each licensee is addressed as "you". A "library" means a collection of software functions and/or data prepared so as to be conveniently linked with application programs (which use some of those functions and data) to form executables. The "Library", below, refers to any such software library or work which has been distributed under these terms. A "work based on the Library" means either the Library or any derivative work under copyright law: that is to say, a work containing the Library or a portion of it, either verbatim or with modifications and/or translated straightforwardly into another language. (Hereinafter, translation is included without limitation in the term "modification".) "Source code" for a work means the preferred form of the work for making modifications to it. For a library, complete source code means all the source code for all modules it contains, plus any associated interface definition files, plus the scripts used to control compilation and installation of the library. Activities other than copying, distribution and modification are not covered by this License; they are outside its scope. The act of running a program using the Library is not restricted, and output from such a program is covered only if its contents constitute a work based on the Library (independent of the use of the Library in a tool for writing it). Whether that is true depends on what the Library does and what the program that uses the Library does. 1. You may copy and distribute verbatim copies of the Library's complete source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice and disclaimer of warranty; keep intact all the notices that refer to this License and to the absence of any warranty; and distribute a copy of this License along with the Library. You may charge a fee for the physical act of transferring a copy, and you may at your option offer warranty protection in exchange for a fee. 2. You may modify your copy or copies of the Library or any portion of it, thus forming a work based on the Library, and copy and distribute such modifications or work under the terms of Section 1 above, provided that you also meet all of these conditions: a) The modified work must itself be a software library. b) You must cause the files modified to carry prominent notices stating that you changed the files and the date of any change. c) You must cause the whole of the work to be licensed at no charge to all third parties under the terms of this License. d) If a facility in the modified Library refers to a function or a table of data to be supplied by an application program that uses the facility, other than as an argument passed when the facility is invoked, then you must make a good faith effort to ensure that, in the event an application does not supply such function or table, the facility still operates, and performs whatever part of its purpose remains meaningful. (For example, a function in a library to compute square roots has a purpose that is entirely well-defined independent of the application. Therefore, Subsection 2d requires that any application-supplied function or table used by this function must be optional: if the application does not supply it, the square root function must still compute square roots.) These requirements apply to the modified work as a whole. If identifiable sections of that work are not derived from the Library, and can be reasonably considered independent and separate works in themselves, then this License, and its terms, do not apply to those sections when you distribute them as separate works. But when you distribute the same sections as part of a whole which is a work based on the Library, the distribution of the whole must be on the terms of this License, whose permissions for other licensees extend to the entire whole, and thus to each and every part regardless of who wrote it. Thus, it is not the intent of this section to claim rights or contest your rights to work written entirely by you; rather, the intent is to exercise the right to control the distribution of derivative or collective works based on the Library. In addition, mere aggregation of another work not based on the Library with the Library (or with a work based on the Library) on a volume of a storage or distribution medium does not bring the other work under the scope of this License. 3. You may opt to apply the terms of the ordinary GNU General Public License instead of this License to a given copy of the Library. To do this, you must alter all the notices that refer to this License, so that they refer to the ordinary GNU General Public License, version 2, instead of to this License. (If a newer version than version 2 of the ordinary GNU General Public License has appeared, then you can specify that version instead if you wish.) Do not make any other change in these notices. Once this change is made in a given copy, it is irreversible for that copy, so the ordinary GNU General Public License applies to all subsequent copies and derivative works made from that copy. This option is useful when you wish to copy part of the code of the Library into a program that is not a library. 4. You may copy and distribute the Library (or a portion or derivative of it, under Section 2) in object code or executable form under the terms of Sections 1 and 2 above provided that you accompany it with the complete corresponding machine-readable source code, which must be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange. If distribution of object code is made by offering access to copy from a designated place, then offering equivalent access to copy the source code from the same place satisfies the requirement to distribute the source code, even though third parties are not compelled to copy the source along with the object code. 5. A program that contains no derivative of any portion of the Library, but is designed to work with the Library by being compiled or linked with it, is called a "work that uses the Library". Such a work, in isolation, is not a derivative work of the Library, and therefore falls outside the scope of this License. However, linking a "work that uses the Library" with the Library creates an executable that is a derivative of the Library (because it contains portions of the Library), rather than a "work that uses the library". The executable is therefore covered by this License. Section 6 states terms for distribution of such executables. When a "work that uses the Library" uses material from a header file that is part of the Library, the object code for the work may be a derivative work of the Library even though the source code is not. Whether this is true is especially significant if the work can be linked without the Library, or if the work is itself a library. The threshold for this to be true is not precisely defined by law. If such an object file uses only numerical parameters, data structure layouts and accessors, and small macros and small inline functions (ten lines or less in length), then the use of the object file is unrestricted, regardless of whether it is legally a derivative work. (Executables containing this object code plus portions of the Library will still fall under Section 6.) Otherwise, if the work is a derivative of the Library, you may distribute the object code for the work under the terms of Section 6. Any executables containing that work also fall under Section 6, whether or not they are linked directly with the Library itself. 6. As an exception to the Sections above, you may also combine or link a "work that uses the Library" with the Library to produce a work containing portions of the Library, and distribute that work under terms of your choice, provided that the terms permit modification of the work for the customer's own use and reverse engineering for debugging such modifications. You must give prominent notice with each copy of the work that the Library is used in it and that the Library and its use are covered by this License. You must supply a copy of this License. If the work during execution displays copyright notices, you must include the copyright notice for the Library among them, as well as a reference directing the user to the copy of this License. Also, you must do one of these things: a) Accompany the work with the complete corresponding machine-readable source code for the Library including whatever changes were used in the work (which must be distributed under Sections 1 and 2 above); and, if the work is an executable linked with the Library, with the complete machine-readable "work that uses the Library", as object code and/or source code, so that the user can modify the Library and then relink to produce a modified executable containing the modified Library. (It is understood that the user who changes the contents of definitions files in the Library will not necessarily be able to recompile the application to use the modified definitions.) b) Use a suitable shared library mechanism for linking with the Library. A suitable mechanism is one that (1) uses at run time a copy of the library already present on the user's computer system, rather than copying library functions into the executable, and (2) will operate properly with a modified version of the library, if the user installs one, as long as the modified version is interface-compatible with the version that the work was made with. c) Accompany the work with a written offer, valid for at least three years, to give the same user the materials specified in Subsection 6a, above, for a charge no more than the cost of performing this distribution. d) If distribution of the work is made by offering access to copy from a designated place, offer equivalent access to copy the above specified materials from the same place. e) Verify that the user has already received a copy of these materials or that you have already sent this user a copy. For an executable, the required form of the "work that uses the Library" must include any data and utility programs needed for reproducing the executable from it. However, as a special exception, the materials to be distributed need not include anything that is normally distributed (in either source or binary form) with the major components (compiler, kernel, and so on) of the operating system on which the executable runs, unless that component itself accompanies the executable. It may happen that this requirement contradicts the license restrictions of other proprietary libraries that do not normally accompany the operating system. Such a contradiction means you cannot use both them and the Library together in an executable that you distribute. 7. You may place library facilities that are a work based on the Library side-by-side in a single library together with other library facilities not covered by this License, and distribute such a combined library, provided that the separate distribution of the work based on the Library and of the other library facilities is otherwise permitted, and provided that you do these two things: a) Accompany the combined library with a copy of the same work based on the Library, uncombined with any other library facilities. This must be distributed under the terms of the Sections above. b) Give prominent notice with the combined library of the fact that part of it is a work based on the Library, and explaining where to find the accompanying uncombined form of the same work. 8. You may not copy, modify, sublicense, link with, or distribute the Library except as expressly provided under this License. Any attempt otherwise to copy, modify, sublicense, link with, or distribute the Library is void, and will automatically terminate your rights under this License. However, parties who have received copies, or rights, from you under this License will not have their licenses terminated so long as such parties remain in full compliance. 9. You are not required to accept this License, since you have not signed it. However, nothing else grants you permission to modify or distribute the Library or its derivative works. These actions are prohibited by law if you do not accept this License. Therefore, by modifying or distributing the Library (or any work based on the Library), you indicate your acceptance of this License to do so, and all its terms and conditions for copying, distributing or modifying the Library or works based on it. 10. Each time you redistribute the Library (or any work based on the Library), the recipient automatically receives a license from the original licensor to copy, distribute, link with or modify the Library subject to these terms and conditions. You may not impose any further restrictions on the recipients' exercise of the rights granted herein. You are not responsible for enforcing compliance by third parties with this License. 11. If, as a consequence of a court judgment or allegation of patent infringement or for any other reason (not limited to patent issues), 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 distribute so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not distribute the Library at all. For example, if a patent license would not permit royalty-free redistribution of the Library by all those who receive copies directly or indirectly through you, then the only way you could satisfy both it and this License would be to refrain entirely from distribution of the Library. If any portion of this section is held invalid or unenforceable under any particular circumstance, the balance of the section is intended to apply, and the section as a whole is intended to apply in other circumstances. It is not the purpose of this section to induce you to infringe any patents or other property right claims or to contest validity of any such claims; this section has the sole purpose of protecting the integrity of the free software distribution system which is implemented by public license practices. Many people have made generous contributions to the wide range of software distributed through that system in reliance on consistent application of that system; it is up to the author/donor to decide if he or she is willing to distribute software through any other system and a licensee cannot impose that choice. This section is intended to make thoroughly clear what is believed to be a consequence of the rest of this License. 12. If the distribution and/or use of the Library is restricted in certain countries either by patents or by copyrighted interfaces, the original copyright holder who places the Library under this License may add an explicit geographical distribution limitation excluding those countries, so that distribution is permitted only in or among countries not thus excluded. In such case, this License incorporates the limitation as if written in the body of this License. 13. The Free Software Foundation may publish revised and/or new versions of the Lesser 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 Library specifies a version number of this License which applies to it and "any later version", you have the option of following the terms and conditions either of that version or of any later version published by the Free Software Foundation. If the Library does not specify a license version number, you may choose any version ever published by the Free Software Foundation. 14. If you wish to incorporate parts of the Library into other free programs whose distribution conditions are incompatible with these, write to the author to ask for permission. For software which is copyrighted by the Free Software Foundation, write to the Free Software Foundation; we sometimes make exceptions for this. Our decision will be guided by the two goals of preserving the free status of all derivatives of our free software and of promoting the sharing and reuse of software generally. NO WARRANTY 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE LIBRARY "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 LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE THE LIBRARY 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 LIBRARY (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 LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. END OF TERMS AND CONDITIONS How to Apply These Terms to Your New Libraries If you develop a new library, and you want it to be of the greatest possible use to the public, we recommend making it free software that everyone can redistribute and change. You can do so by permitting redistribution under these terms (or, alternatively, under the terms of the ordinary General Public License). To apply these terms, attach the following notices to the library. It is safest to attach them to the start of each source file to most effectively convey 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 library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This library 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 Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA Also add information on how to contact you by electronic and paper mail. You should also get your employer (if you work as a programmer) or your school, if any, to sign a "copyright disclaimer" for the library, if necessary. Here is a sample; alter the names: Yoyodyne, Inc., hereby disclaims all copyright interest in the library `Frob' (a library for tweaking knobs) written by James Random Hacker. , 1 April 1990 Ty Coon, President of Vice That's all there is to it! winregfs-0.7/LICENSE000066400000000000000000000430761306056141200142210ustar00rootroot00000000000000 GNU GENERAL PUBLIC LICENSE Version 2, June 1991 Copyright (C) 1989, 1991 Free Software Foundation, Inc. 675 Mass Ave, Cambridge, MA 02139, USA Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. Preamble The licenses for most software are designed to take away your freedom to share and change it. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change free software--to make sure the software is free for all its users. This General Public License applies to most of the Free Software Foundation's software and to any other program whose authors commit to using it. (Some other Free Software Foundation software is covered by the GNU Library General Public License instead.) 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 this service 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 make restrictions that forbid anyone to deny you these rights or to ask you to surrender the rights. These restrictions translate to certain responsibilities for you if you distribute copies of the software, or if you modify it. For example, if you distribute copies of such a program, whether gratis or for a fee, you must give the recipients all the rights that you have. 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. We protect your rights with two steps: (1) copyright the software, and (2) offer you this license which gives you legal permission to copy, distribute and/or modify the software. Also, for each author's protection and ours, we want to make certain that everyone understands that there is no warranty for this free software. If the software is modified by someone else and passed on, we want its recipients to know that what they have is not the original, so that any problems introduced by others will not reflect on the original authors' reputations. Finally, any free program is threatened constantly by software patents. We wish to avoid the danger that redistributors of a free program will individually obtain patent licenses, in effect making the program proprietary. To prevent this, we have made it clear that any patent must be licensed for everyone's free use or not licensed at all. The precise terms and conditions for copying, distribution and modification follow. GNU GENERAL PUBLIC LICENSE TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 0. This License applies to any program or other work which contains a notice placed by the copyright holder saying it may be distributed under the terms of this General Public License. The "Program", below, refers to any such program or work, and a "work based on the Program" means either the Program or any derivative work under copyright law: that is to say, a work containing the Program or a portion of it, either verbatim or with modifications and/or translated into another language. (Hereinafter, translation is included without limitation in the term "modification".) Each licensee is addressed as "you". Activities other than copying, distribution and modification are not covered by this License; they are outside its scope. The act of running the Program is not restricted, and the output from the Program is covered only if its contents constitute a work based on the Program (independent of having been made by running the Program). Whether that is true depends on what the Program does. 1. You may copy and distribute 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 and disclaimer of warranty; keep intact all the notices that refer to this License and to the absence of any warranty; and give any other recipients of the Program a copy of this License along with the Program. You may charge a fee for the physical act of transferring a copy, and you may at your option offer warranty protection in exchange for a fee. 2. You may modify your copy or copies of the Program or any portion of it, thus forming a work based on the Program, and copy and distribute such modifications or work under the terms of Section 1 above, provided that you also meet all of these conditions: a) You must cause the modified files to carry prominent notices stating that you changed the files and the date of any change. b) You must cause any work that you distribute or publish, that in whole or in part contains or is derived from the Program or any part thereof, to be licensed as a whole at no charge to all third parties under the terms of this License. c) If the modified program normally reads commands interactively when run, you must cause it, when started running for such interactive use in the most ordinary way, to print or display an announcement including an appropriate copyright notice and a notice that there is no warranty (or else, saying that you provide a warranty) and that users may redistribute the program under these conditions, and telling the user how to view a copy of this License. (Exception: if the Program itself is interactive but does not normally print such an announcement, your work based on the Program is not required to print an announcement.) These requirements apply to the modified work as a whole. If identifiable sections of that work are not derived from the Program, and can be reasonably considered independent and separate works in themselves, then this License, and its terms, do not apply to those sections when you distribute them as separate works. But when you distribute the same sections as part of a whole which is a work based on the Program, the distribution of the whole must be on the terms of this License, whose permissions for other licensees extend to the entire whole, and thus to each and every part regardless of who wrote it. Thus, it is not the intent of this section to claim rights or contest your rights to work written entirely by you; rather, the intent is to exercise the right to control the distribution of derivative or collective works based on the Program. In addition, mere aggregation of another work not based on the Program with the Program (or with a work based on the Program) on a volume of a storage or distribution medium does not bring the other work under the scope of this License. 3. You may copy and distribute the Program (or a work based on it, under Section 2) in object code or executable form under the terms of Sections 1 and 2 above provided that you also do one of the following: a) Accompany it with the complete corresponding machine-readable source code, which must be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, b) Accompany it with a written offer, valid for at least three years, to give any third party, for a charge no more than your cost of physically performing source distribution, a complete machine-readable copy of the corresponding source code, to be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, c) Accompany it with the information you received as to the offer to distribute corresponding source code. (This alternative is allowed only for noncommercial distribution and only if you received the program in object code or executable form with such an offer, in accord with Subsection b above.) The source code for a work means the preferred form of the work for making modifications to it. For an executable work, complete source code means all the source code for all modules it contains, plus any associated interface definition files, plus the scripts used to control compilation and installation of the executable. However, as a special exception, the source code distributed need not include anything that is normally distributed (in either source or binary form) with the major components (compiler, kernel, and so on) of the operating system on which the executable runs, unless that component itself accompanies the executable. If distribution of executable or object code is made by offering access to copy from a designated place, then offering equivalent access to copy the source code from the same place counts as distribution of the source code, even though third parties are not compelled to copy the source along with the object code. 4. You may not copy, modify, sublicense, or distribute the Program except as expressly provided under this License. Any attempt otherwise to copy, modify, sublicense or distribute the Program is void, and will automatically terminate your rights under this License. However, parties who have received copies, or rights, from you under this License will not have their licenses terminated so long as such parties remain in full compliance. 5. You are not required to accept this License, since you have not signed it. However, nothing else grants you permission to modify or distribute the Program or its derivative works. These actions are prohibited by law if you do not accept this License. Therefore, by modifying or distributing the Program (or any work based on the Program), you indicate your acceptance of this License to do so, and all its terms and conditions for copying, distributing or modifying the Program or works based on it. 6. Each time you redistribute the Program (or any work based on the Program), the recipient automatically receives a license from the original licensor to copy, distribute or modify the Program subject to these terms and conditions. You may not impose any further restrictions on the recipients' exercise of the rights granted herein. You are not responsible for enforcing compliance by third parties to this License. 7. If, as a consequence of a court judgment or allegation of patent infringement or for any other reason (not limited to patent issues), 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 distribute so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not distribute the Program at all. For example, if a patent license would not permit royalty-free redistribution of the Program by all those who receive copies directly or indirectly through you, then the only way you could satisfy both it and this License would be to refrain entirely from distribution of the Program. If any portion of this section is held invalid or unenforceable under any particular circumstance, the balance of the section is intended to apply and the section as a whole is intended to apply in other circumstances. It is not the purpose of this section to induce you to infringe any patents or other property right claims or to contest validity of any such claims; this section has the sole purpose of protecting the integrity of the free software distribution system, which is implemented by public license practices. Many people have made generous contributions to the wide range of software distributed through that system in reliance on consistent application of that system; it is up to the author/donor to decide if he or she is willing to distribute software through any other system and a licensee cannot impose that choice. This section is intended to make thoroughly clear what is believed to be a consequence of the rest of this License. 8. If the distribution and/or use of the Program is restricted in certain countries either by patents or by copyrighted interfaces, the original copyright holder who places the Program under this License may add an explicit geographical distribution limitation excluding those countries, so that distribution is permitted only in or among countries not thus excluded. In such case, this License incorporates the limitation as if written in the body of this License. 9. The Free Software Foundation may publish revised and/or new versions of the 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 a version number of this License which applies to it and "any later version", you have the option of following the terms and conditions either of that version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of this License, you may choose any version ever published by the Free Software Foundation. 10. If you wish to incorporate parts of the Program into other free programs whose distribution conditions are different, write to the author to ask for permission. For software which is copyrighted by the Free Software Foundation, write to the Free Software Foundation; we sometimes make exceptions for this. Our decision will be guided by the two goals of preserving the free status of all derivatives of our free software and of promoting the sharing and reuse of software generally. NO WARRANTY 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, 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. 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE 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. END OF TERMS AND CONDITIONS Appendix: 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 convey 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) 19yy 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 2 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, write to the Free Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. Also add information on how to contact you by electronic and paper mail. If the program is interactive, make it output a short notice like this when it starts in an interactive mode: Gnomovision version 69, Copyright (C) 19yy name of author Gnomovision 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, the commands you use may be called something other than `show w' and `show c'; they could even be mouse-clicks or menu items--whatever suits your program. You should also get your employer (if you work as a programmer) or your school, if any, to sign a "copyright disclaimer" for the program, if necessary. Here is a sample; alter the names: Yoyodyne, Inc., hereby disclaims all copyright interest in the program `Gnomovision' (which makes passes at compilers) written by James Hacker. , 1 April 1989 Ty Coon, President of Vice This 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 Library General Public License instead of this License. winregfs-0.7/Makefile000066400000000000000000000042001306056141200146360ustar00rootroot00000000000000CC=gcc CFLAGS=-O2 -g #CFLAGS=-O2 -flto -ffunction-sections -fdata-sections -fno-unwind-tables -fno-asynchronous-unwind-tables #CFLAGS=-Og -g3 BUILD_CFLAGS = -std=gnu99 -I. -D_FILE_OFFSET_BITS=64 -pipe -fstrict-aliasing #BUILD_CFLAGS += -Wall -Wextra -Wstrict-aliasing -Wcast-align -pedantic -Wno-unused-parameter BUILD_CFLAGS += -Wall -Wextra -Wwrite-strings -Wcast-align -Wstrict-aliasing -pedantic -Wstrict-overflow -Wstrict-prototypes -Wpointer-arith -Wundef BUILD_CFLAGS += -Wshadow -Wfloat-equal -Wstrict-overflow=5 -Waggregate-return -Wcast-qual -Wswitch-default -Wswitch-enum -Wunreachable-code -Wformat=2 -Winit-self #LDFLAGS=-s #LDFLAGS=-flto -s -Wl,--gc-sections LDFLAGS= FUSE_CFLAGS=$(shell pkg-config fuse --cflags) FUSE_LDFLAGS=$(shell pkg-config fuse --libs) FUSE_LIBS=-lfuse prefix=/usr exec_prefix=${prefix} bindir=${exec_prefix}/bin mandir=${prefix}/man datarootdir=${prefix}/share datadir=${datarootdir} sysconfdir=${prefix}/etc OBJS_LIB=ntreg.o jody_string.o OBJS_FSCK=fsck_winregfs.o $(OBJS_LIB) OBJS_MOUNT=winregfs.o jody_hash.o $(OBJS_LIB) BUILD_CFLAGS += $(CFLAGS_EXTRA) all: mount.winregfs fsck.winregfs manual mount.winregfs: $(OBJS_MOUNT) $(CC) $(CFLAGS) $(LDFLAGS) $(FUSE_CFLAGS) $(BUILD_CFLAGS) $(FUSE_LDFLAGS) -o mount.winregfs $(OBJS_MOUNT) $(FUSE_LIBS) fsck.winregfs: $(OBJS_FSCK) $(CC) $(CFLAGS) $(LDFLAGS) $(FUSE_CFLAGS) $(BUILD_CFLAGS) $(FUSE_LDFLAGS) -o fsck.winregfs $(OBJS_FSCK) manual: gzip -9 < mount.winregfs.8 > mount.winregfs.8.gz gzip -9 < fsck.winregfs.8 > fsck.winregfs.8.gz .c.o: $(CC) -c $(BUILD_CFLAGS) $(FUSE_CFLAGS) $(CFLAGS) $< clean: rm -f *.o *~ mount.winregfs fsck.winregfs debug.log *.?.gz distclean: rm -f *.o *~ mount.winregfs fsck.winregfs debug.log *.?.gz winregfs*.pkg.tar.* install: all install -D -o root -g root -m 0644 mount.winregfs.8.gz $(DESTDIR)/$(mandir)/man8/mount.winregfs.8.gz install -D -o root -g root -m 0644 fsck.winregfs.8.gz $(DESTDIR)/$(mandir)/man8/fsck.winregfs.8.gz install -D -o root -g root -m 0755 -s mount.winregfs $(DESTDIR)/$(bindir)/mount.winregfs install -D -o root -g root -m 0755 -s fsck.winregfs $(DESTDIR)/$(bindir)/fsck.winregfs package: +./chroot_build.sh winregfs-0.7/README000066400000000000000000000142221306056141200140630ustar00rootroot00000000000000 THE WINDOWS REGISTRY FUSE FILESYSTEM ==================================== If you have any questions, comments, or patches, send me an email: jody@jodybruchon.com One of the most difficult things to deal with in years of writing Linux utilities to work with and repair Windows PCs is the Windows registry. While many excellent tools exist to work with NTFS filesystems and to change and remove passwords from user accounts, the ability to work with the registry has always been severely lacking. Included in the excellent chntpw package is a primitive registry editor "reged" which has largely been quite helpful and I have been grateful for its existence, but it suffers from a very limited interface and a complete lack of scriptability that presents a major hurdle for anyone wanting to do more with the registry than wipe out a password or change the "Start" flag of a system service. Because of the serious limitations of "reged," the only practical way to do anything registry-oriented with a shell script was to export an ENTIRE HIVE to a .reg file, crudely parse the file for what you want, create a .reg file from the script to import the changes, and import them. Needless to say, the process is slow, complicated, and frustrating. I even wrote a tool called "read_inf_section" to help my scripts parse INF/INI/REG files faster because of this need (but also for an unrelated need to read .inf files from driver packages.) This complexity became too excessive, so I came up with a much better way to tweak the registry from shell scripts and programs. Thus, the Windows Registry FUSE Filesystem "winregfs" was born. chntpw ( http://pogostick.net/~pnh/ntpasswd/ ) has an excellent library for working with Windows NT registry hive files, distributed under the LGPL. winregfs is essentially a glue layer between ntreg.c and FUSE, translating Windows registry keys and values into ordinary directories and files. Features include: * Full write support (value size limited to 8192 bytes) * Case-insensitivity in key/value name matching * Automatic forward-slash escaping (gets around the Linux pathname limitation) * "Wildcard" name matching on reads, i.e. "cat foo/bar" matches "foo/bar.sz" * Friendly DWORD editing in hexadecimal ASCII text rather than raw data Also included is a tool called "fsck.winregfs" which will perform a basic check of the integrity of a registry hive. It recursively follows all possible keys and values, checking for errors in offsets, reading data, and value types. To use it, type "fsck.winregfs [hivename]" and when the scan completes a detailed list of statistics and error counts will be produced. The exit status can be checked (i.e. in a script) for success or failure of the check. A few keys and value names in the Windows registry such as MIME types contain forward slash characters; winregfs substitutes "_SLASH_" where a forward slash appears in names. To use winregfs, make a directory to mount on and point it to the registry hive of interest: --- $ mkdir reg $ mount.winregfs /mnt/sdc2/Windows/System32/config/software reg/ --- Now, you can see everything in that hive under "reg": --- $ ls reg 7-Zip/ Google/ Policies/ AVAST Software/ InstalledOptions/ Program Groups/ Adobe/ Intel/ RegisteredApplications/ Analog Devices/ LibreOffice/ S3/ C07ft5Y/ Macromedia/ Schlumberger/ Classes/ Microsoft/ Secure/ Clients/ Mozilla/ Sigmatel/ Diskeeper Corporation/ MozillaPlugins/ The Document Foundation/ GNU/ NVIDIA Corporation/ Windows 3.1 Migration Status/ Gabest/ ODBC/ mozilla.org/ Gemplus/ Piriform/ --- Let's say you want to see some things that automatically run during startup. --- $ ls -l reg/Microsoft/Windows/CurrentVersion/Run total 0 -r--r--r-- 1 root root 118 Dec 31 1969 Adobe ARM.sz -r--r--r-- 1 root root 124 Dec 31 1969 DiskeeperSystray.sz -r--r--r-- 1 root root 60 Dec 31 1969 HotKeysCmds.sz -r--r--r-- 1 root root 66 Dec 31 1969 IgfxTray.sz -r--r--r-- 1 root root 70 Dec 31 1969 KernelFaultCheck.esz -r--r--r-- 1 root root 66 Dec 31 1969 Persistence.sz -r--r--r-- 1 root root 100 Dec 31 1969 SoundMAXPnP.sz -r--r--r-- 1 root root 118 Dec 31 1969 avast.sz --- You want to see what these values contain. --- $ for X in reg/Microsoft/Windows/CurrentVersion/Run/* > do echo -en "$X\n "; cat "$X"; echo; done reg/Microsoft/Windows/CurrentVersion/Run/Adobe ARM.sz "C:\Program Files\Common Files\Adobe\ARM\1.0\AdobeARM.exe" reg/Microsoft/Windows/CurrentVersion/Run/DiskeeperSystray.sz "C:\Program Files\Diskeeper Corporation\Diskeeper\DkIcon.exe" reg/Microsoft/Windows/CurrentVersion/Run/HotKeysCmds.sz C:\WINDOWS\system32\hkcmd.exe reg/Microsoft/Windows/CurrentVersion/Run/IgfxTray.sz C:\WINDOWS\system32\igfxtray.exe reg/Microsoft/Windows/CurrentVersion/Run/KernelFaultCheck.esz %systemroot%\system32\dumprep 0 -k reg/Microsoft/Windows/CurrentVersion/Run/Persistence.sz C:\WINDOWS\system32\igfxpers.exe reg/Microsoft/Windows/CurrentVersion/Run/SoundMAXPnP.sz C:\Program Files\Analog Devices\Core\smax4pnp.exe reg/Microsoft/Windows/CurrentVersion/Run/avast.sz "C:\Program Files\AVAST Software\Avast\avastUI.exe" /nogui --- Has anything hijacked the Windows "shell" value that runs explorer.exe? --- $ cat reg/Microsoft/Windows\ NT/CurrentVersion/Winlogon/Shell.sz Explorer.exe --- How about the userinit.exe value? --- $ cat reg/Microsoft/Windows\ NT/CurrentVersion/Winlogon/Userinit.sz C:\WINDOWS\system32\userinit.exe, --- Perhaps check if some system policies are set (note that REG_DWORD values now work as friendly hexadecimal text files instead of raw data): --- $ cat \ > reg/Policies/Microsoft/Windows/System/Allow-LogonScript-NetbiosDisabled.dw 00000001 You can probably figure out what to do with it from here. ;-) LICENSING NOTICE ---------------- ntreg.c and ntreg.h are distributed under the terms of the LGPL as described in LGPL.txt while all other winregfs components are licensed under the terms of the GNU GPL as described in LICENSE. winregfs-0.7/TODO000066400000000000000000000014351306056141200136750ustar00rootroot00000000000000 Windows Registry FUSE Filesystem TODO list ------------------------------------------ * Fix 8 KiB file write limit issue (may really be in ntreg.c) For now an error is issued on attempts to write >8192 bytes since there are extremely few values that contain this much data; the XP compatibility shim cache is pretty much the only value of such a size (all others are <6000 bytes) * Allow arbitrary value types using a hexadecimal extension (used in SAM and some MS Click-to-Run registry keys) * Unicode and non-ASCII character support This is not only useful for non-Latin characters, it also can be used to find novel registry-resident malware such as Poweliks, which uses a non-ASCII name to block the Windows registry editor from being able to touch it. winregfs-0.7/chroot_build.sh000077500000000000000000000041431306056141200162200ustar00rootroot00000000000000#!/bin/sh # Jody's generic chroot build script # Version 1.0 ARCHES="i386 x86-64 uclibc-i386 uclibc-x86-64" test -z "$NAME" && NAME="$(basename "$(pwd)")" test -e "version.h" && VER="$(grep '#define VER ' version.h | tr -d \\\" | cut -d' ' -f3)" test -z "$VER" && VER=0 export NAME export VER export CHROOT_BASE=/chroots export WD="$(pwd)" export PKG="pkg" echo "chroot builder: building '$NAME' version '$VER'" trap clean_exit INT QUIT ABRT HUP clean_exit () { umount $CHROOT/proc $CHROOT/sys $CHROOT/tmp $CHROOT/dev $CHROOT/usr/src $CHROOT/home } do_build () { test -z "$WD" && echo "WD not set, aborting" && exit 1 test -z "$PKG" && echo "PKG not set, aborting" && exit 1 make clean if ! make -j$JOBS all then echo "Build failed"; exit 1 else echo "WD/PKG: $WD/$PKG" test -d $WD/$PKG && rm -rf $WD/$PKG mkdir $WD/$PKG make DESTDIR=$WD/$PKG install && \ tar -C pkg -c usr | xz -e > ${NAME}_$VER-$ARCH.pkg.tar.xz fi } if [ "$(id -u)" != "0" ] then echo "You must be root to auto-build chroot packages." exit 1 fi if [ "$DO_CHROOT_BUILD" = "1" ] then test -z "$1" && echo "No arch specified" && exit 1 test ! -d "$1" && echo "Not a directory: $1" && exit 1 cd $1 export WD="$1" do_build echo "finished: $1" exit else echo baz export DO_CHROOT_BUILD=1 for ARCH in $ARCHES do export ARCH export CHROOT="$CHROOT_BASE/$ARCH" test ! -d $CHROOT && echo "$CHROOT not present, not building $ARCH package." && continue echo "Performing package build for $CHROOT" test ! -x $CHROOT/bin/sh && echo "$CHROOT does not seem to be a chroot; aborting." && exit 1 mount --bind /dev $CHROOT/dev || clean_exit mount --bind /usr/src $CHROOT/usr/src || clean_exit mount --bind /home $CHROOT/home || clean_exit mount -t proc proc $CHROOT/proc || clean_exit mount -t sysfs sysfs $CHROOT/sys || clean_exit mount -t tmpfs tmpfs $CHROOT/tmp || clean_exit if echo "$ARCH" | grep -q "i386" then linux32 chroot $CHROOT $WD/$0 $WD else chroot $CHROOT $WD/$0 $WD fi umount $CHROOT/proc $CHROOT/sys $CHROOT/tmp $CHROOT/dev $CHROOT/usr/src $CHROOT/home test -d $WD/$PKG && rm -rf $WD/$PKG done fi winregfs-0.7/config.h000066400000000000000000000005001306056141200146130ustar00rootroot00000000000000#ifndef CONFIG_H #define CONFIG_H /* Threaded mode suffers from decreased performance */ #define ENABLE_THREADED 0 #define ENABLE_LOGGING 0 #define ENABLE_DEBUG_LOGGING 0 #define ENABLE_DEBUG_PRINTF 0 #define ENABLE_NKOFS_CACHE 1 #define ENABLE_NKOFS_CACHE_STATS 0 #define NKOFS_CACHE_ITEMS 64 #endif /* CONFIG_H */ winregfs-0.7/fsck.winregfs.8000066400000000000000000000016641306056141200160530ustar00rootroot00000000000000.\" Copyright (c) 2014-2017 Jody Bruchon .\" Licensed under the GNU General Public License v2 .\" .TH winregfs 8 "20 May 2014" "fsck.winregfs" .SH NAME fsck.winregfs \- Check a Windows registry file .SH SYNOPSIS .B fsck.winregfs .I [-v] hivefile .SH DESCRIPTION \fBfsck.winregfs\fP scans a Windows registry hive file for problems that indicate the hive has been damaged by hardware or software issues. If the \fI-v\fP option is specified, the program will output detailed information about the problems it finds. .SH LIMITATIONS \fBfsck.winregfs\fP works by recursively reading the key and value data structures in the registry hive. Currently it does not read the actual data stored in a value, and due to the nature of the ntreg library's interfaces the checks are currently limited to reporting that errors exist; the details of the error conditions encountered are not broken down in the final output. .SH SEE ALSO .BR mount.winregfs (8) winregfs-0.7/fsck_winregfs.c000066400000000000000000000131131306056141200161770ustar00rootroot00000000000000/* * Windows registry "filesystem checker" * Reads all the keys in a hive and reports any errors triggered * * Copyright (C) 2014-2017 by Jody Bruchon * * Licensed under GNU GPL v2. See LICENSE and README for details. * */ #define FSCK_WINREGFS #include #include #include #include #include #include #include #include #include #include #include "ntreg.h" #include "jody_string.h" #include "winregfs.h" #define UPDATE_INTERVAL 300 struct winregfs_data wd; struct fsck_stat { int e_travpath; int e_nkofs; int e_read_key; int e_read_val; int w_type; int keys; int values; int maxdepth; int update_delay; }; void invalidate_nk_cache(void) { return; } /* Converts a path to the required formats for keypath/nodepath usage */ static inline int sanitize_path(const char * const restrict path, char * const restrict keypath, char * const restrict node) { strncpy(keypath, path, ABSPATHLEN); strncpy(node, path, ABSPATHLEN); dirname(keypath); /* need to read the root key */ strncpy(node, basename(node), ABSPATHLEN); return EXIT_SUCCESS; } void show_progress(struct fsck_stat * const restrict stats) { if (stats->update_delay > 0) { stats->update_delay--; return; } else stats->update_delay = UPDATE_INTERVAL; printf("Keys: %d Values: %d \r", stats->keys, stats->values); return; } static int process_key(struct fsck_stat * const restrict stats, const char * const restrict path, int depth, int verbose) { /* * For keys, run process_key again recursively * For values, just check that the value exists for now */ struct nk_key *key; int nkofs, i; struct ex_data ex; struct vex_data vex; int count = 0, countri = 0, error_count = 0; char filename[ABSPATHLEN]; char keypath[ABSPATHLEN]; depth++; if (stats->maxdepth < depth) stats->maxdepth = depth; stats->keys++; show_progress(stats); strncpy(keypath, path, ABSPATHLEN); nkofs = trav_path(wd.hive, 0, keypath, TPF_NK_EXACT); if (!nkofs) { if (verbose) printf("\rPath traversal failure: %s\n", keypath); stats->e_travpath++; return -1; } nkofs += 4; if(nkofs > wd.hive->size) { if (verbose) printf("\rNK offset too large: %s\n", keypath); stats->e_nkofs++; return -1; } key = (struct nk_key *)(wd.hive->buffer + nkofs); if (key->no_subkeys) { while ((i = ex_next_n(wd.hive, nkofs, &count, &countri, &ex)) > 0) { strncpy(filename, keypath, ABSPATHLEN); if(strncmp(keypath, "\\", 3)) strncat(filename, "\\", ABSPATHLEN); strncat(filename, ex.name, ABSPATHLEN); error_count += process_key(stats, filename, depth, verbose); } if (i < 0) { if (verbose) printf("\rKey read failure: %s\n", keypath); stats->e_read_key++; show_progress(stats); } } count = 0; if (key->no_values) { while ((i = ex_next_v(wd.hive, nkofs, &count, &vex)) > 0) { stats->values++; show_progress(stats); if (vex.type > REG_MAX) { if (verbose) printf("\rValue type 0x%x is an unknown type: %s\n", vex.type, keypath); stats->w_type++; } strncpy(filename, keypath, ABSPATHLEN); strncat(filename, "\\", ABSPATHLEN); if (strlen(vex.name) == 0) strncpy(filename, "@", 2); else strncat(filename, vex.name, ABSPATHLEN); } if (i < 0) { if (verbose) printf("\rValue read failure: %s\n", keypath); stats->e_read_val++; show_progress(stats); } } return EXIT_SUCCESS; } int main(int argc, char *argv[]) { char file[ABSPATHLEN]; char path[ABSPATHLEN]; int error_count, warn_count, verbose = 0; struct fsck_stat stats; if (argc == 2 && !strcaseeq(argv[1], "-v")) { fprintf(stderr, "Windows Registry Hive File Checker %s (%s)\n", VER, VERDATE); return EXIT_SUCCESS; } if ((argc < 2) || (argv[argc-1][0] == '-')) { fprintf(stderr, "Windows Registry Hive File Checker %s (%s)\n", VER, VERDATE); fprintf(stderr, "\nUsage: %s [options] hivename\n\n", argv[0]); return EXIT_FAILURE; } if (!strncmp(argv[1], "-v", 3)) { printf("Verbose mode enabled\n"); verbose = 1; } /* Pull hive file name from command line */ strncpy(file, argv[argc-1], ABSPATHLEN); /* malloc() and initialize cache pointers/data */ wd.hive = open_hive(file, HMODE_RW); if (!wd.hive) { fprintf(stderr, "Error: couldn't open %s\n", file); return EXIT_FAILURE; } stats.e_travpath = 0; stats.e_nkofs = 0; stats.e_read_key = 0; stats.e_read_val = 0; stats.w_type = 0; stats.keys = 0; stats.values = 0; stats.maxdepth = 0; stats.update_delay = 0; /* Start at the hive root */ path[0] = '\\'; path[1] = '\0'; process_key(&stats, path, -1, verbose); close_hive(wd.hive); error_count = (stats.e_travpath + stats.e_nkofs + stats.e_read_key + stats.e_read_val); warn_count = (stats.w_type); /* Show final stats for everything */ printf("Keys: %d Values: %d Max key depth: %d\n", stats.keys, stats.values, stats.maxdepth); if (stats.e_travpath) printf("\nPath traversal errors: %d\n", stats.e_travpath); if (stats.e_nkofs) printf("\n'nk' offset errors: %d\n", stats.e_nkofs); if (stats.e_read_key) printf("\nKey read errors: %d\n", stats.e_read_key); if (stats.e_read_val) printf("\nValue read errors: %d\n", stats.e_read_val); if (stats.w_type) printf("\nValue type warnings: %d\n", stats.w_type); if (error_count || warn_count) { printf("\nHive %s has ", file); if (error_count) { printf("%d total errors", error_count); if (warn_count) printf(" and "); } if (warn_count) printf("%d total warnings", warn_count); printf("\n\n"); } else printf("Hive %s is clean.\n\n", file); return (error_count ? EXIT_FAILURE : EXIT_SUCCESS); } winregfs-0.7/jody_hash.c000066400000000000000000000066341306056141200153270ustar00rootroot00000000000000/* Jody Bruchon's fast hashing function * * This function was written to generate a fast hash that also has a * fairly low collision rate. The collision rate is much higher than * a secure hash algorithm, but the calculation is drastically simpler * and faster. * * Copyright (C) 2014-2017 by Jody Bruchon * Released under The MIT License */ #include #include #include "jody_hash.h" /* DO NOT modify the shift unless you know what you're doing. * This shift was decided upon after lots of testing and * changing it will likely cause lots of hash collisions. */ #ifndef JODY_HASH_SHIFT #define JODY_HASH_SHIFT 11 #endif /* The salt value's purpose is to cause each byte in the * hash_t word to have a positionally dependent variation. * It is injected into the calculation to prevent a string of * identical bytes from easily producing an identical hash. */ /* The tail mask table is used for block sizes that are * indivisible by the width of a hash_t. It is ANDed with the * final hash_t-sized element to zero out data in the buffer * that is not part of the data to be hashed. */ /* Set hash parameters based on requested hash width */ #if JODY_HASH_WIDTH == 64 #define JODY_HASH_CONSTANT 0x1f3d5b79U static const hash_t tail_mask[] = { 0x0000000000000000, 0x00000000000000ff, 0x000000000000ffff, 0x0000000000ffffff, 0x00000000ffffffff, 0x000000ffffffffff, 0x0000ffffffffffff, 0x00ffffffffffffff, 0xffffffffffffffff }; #endif /* JODY_HASH_WIDTH == 64 */ #if JODY_HASH_WIDTH == 32 #define JODY_HASH_CONSTANT 0x1f3d5b79U static const hash_t tail_mask[] = { 0x00000000, 0x000000ff, 0x0000ffff, 0x00ffffff, 0xffffffff, }; #endif /* JODY_HASH_WIDTH == 32 */ #if JODY_HASH_WIDTH == 16 #define JODY_HASH_CONSTANT 0x1f5bU static const hash_t tail_mask[] = { 0x0000, 0x00ff, 0xffff, }; #endif /* JODY_HASH_WIDTH == 16 */ /* Hash a block of arbitrary size; must be divisible by sizeof(hash_t) * The first block should pass a start_hash of zero. * All blocks after the first should pass start_hash as the value * returned by the last call to this function. This allows hashing * of any amount of data. If data is not divisible by the size of * hash_t, it is MANDATORY that the caller provide a data buffer * which is divisible by sizeof(hash_t). */ extern hash_t jody_block_hash(const hash_t * restrict data, const hash_t start_hash, const size_t count) { hash_t hash = start_hash; hash_t element; hash_t partial_salt; size_t len; /* Don't bother trying to hash a zero-length block */ if (count == 0) return hash; len = count / sizeof(hash_t); for (; len > 0; len--) { element = *data; hash += element; hash += JODY_HASH_CONSTANT; hash = (hash << JODY_HASH_SHIFT) | hash >> (sizeof(hash_t) * 8 - JODY_HASH_SHIFT); hash ^= element; hash = (hash << JODY_HASH_SHIFT) | hash >> (sizeof(hash_t) * 8 - JODY_HASH_SHIFT); hash ^= JODY_HASH_CONSTANT; hash += element; data++; } /* Handle data tail (for blocks indivisible by sizeof(hash_t)) */ len = count & (sizeof(hash_t) - 1); if (len) { partial_salt = JODY_HASH_CONSTANT & tail_mask[len]; element = *data & tail_mask[len]; hash += element; hash += partial_salt; hash = (hash << JODY_HASH_SHIFT) | hash >> (sizeof(hash_t) * 8 - JODY_HASH_SHIFT); hash ^= element; hash = (hash << JODY_HASH_SHIFT) | hash >> (sizeof(hash_t) * 8 - JODY_HASH_SHIFT); hash ^= partial_salt; hash += element; } return hash; } winregfs-0.7/jody_hash.h000066400000000000000000000015001306056141200153170ustar00rootroot00000000000000/* Jody Bruchon's fast hashing function (headers) * See jody_hash.c for license information */ #ifndef JODY_HASH_H #define JODY_HASH_H #ifdef __cplusplus extern "C" { #endif /* Required for uint64_t */ #include /* Width of a jody_hash. Changing this will also require * changing the width of tail masks and endian conversion */ #ifndef JODY_HASH_WIDTH #define JODY_HASH_WIDTH 64 #endif #if JODY_HASH_WIDTH == 64 typedef uint64_t hash_t; #endif #if JODY_HASH_WIDTH == 32 typedef uint32_t hash_t; #endif #if JODY_HASH_WIDTH == 16 typedef uint16_t hash_t; #endif /* Version increments when algorithm changes incompatibly */ #define JODY_HASH_VERSION 4 extern hash_t jody_block_hash(const hash_t * restrict data, const hash_t start_hash, const size_t count); #ifdef __cplusplus } #endif #endif /* JODY_HASH_H */ winregfs-0.7/jody_string.c000066400000000000000000000033751306056141200157110ustar00rootroot00000000000000/* * Jody Bruchon's string function library * Copyright (C) 2015-2017 * Distributed under the GNU General Public License version 2 */ #include #include /* Like strncasecmp() but only tests for equality */ extern int strncaseeq(const char *s1, const char *s2, size_t len) { size_t i = 0; while (i < len) { if (*s1 != *s2) { unsigned char c1, c2; c1 = *(const unsigned char *)s1; c2 = *(const unsigned char *)s2; /* Transform upper case to lower case */ if (c1 == 0 || c2 == 0) return 1; if (c1 >= 'A' && c1 <= 'Z') c1 |= 0x20; if (c2 >= 'A' && c2 <= 'Z') c2 |= 0x20; if (c1 != c2) return 1; } else { if (*s1 == 0) return 0; } s1++; s2++; i++; } return 0; } /* Like strcasecmp() but only tests for equality */ extern int strcaseeq(const char *s1, const char *s2) { while (1) { if (*s1 != *s2) { unsigned char c1, c2; c1 = *(const unsigned char *)s1; c2 = *(const unsigned char *)s2; /* Transform upper case to lower case */ if (c1 == 0 || c2 == 0) return 1; if (c1 >= 'A' && c1 <= 'Z') c1 |= 0x20; if (c2 >= 'A' && c2 <= 'Z') c2 |= 0x20; if (c1 != c2) return 1; } else { if (*s1 == 0) return 0; } s1++; s2++; } return 1; } /* Like strncmp() but only tests for equality */ extern int strneq(const char *s1, const char *s2, size_t len) { size_t i = 0; if (!len) return 0; while (*s1 != '\0' && *s2 != '\0') { if (*s1 != *s2) return 1; s1++; s2++; i++; if (i == len) return 0; } if (*s1 != *s2) return 1; return 0; } /* Like strcmp() but only tests for equality */ extern int streq(const char *s1, const char *s2) { while (*s1 != '\0' && *s2 != '\0') { if (*s1 != *s2) return 1; s1++; s2++; } if (*s1 != *s2) return 1; return 0; } winregfs-0.7/jody_string.h000066400000000000000000000014411306056141200157060ustar00rootroot00000000000000/* * Jody Bruchon's string function library * Copyright (C) 2015-2017 * Distributed under the GNU General Public License version 2 */ #ifndef JODY_STRING_H #define JODY_STRING_H #ifdef __cplusplus extern "C" { #endif #include #include extern int strncaseeq(const char *s1, const char *s2, size_t len); extern int strcaseeq(const char *s1, const char *s2); extern int strneq(const char *s1, const char *s2, size_t len); extern int streq(const char *s1, const char *s2); /* Inline strcpy() */ inline void xstrcpy(char * restrict dest, const char * restrict src) { while (*src != '\0') { *dest = *src; dest++; src++; } *dest = '\0'; } #ifdef __cplusplus } #endif #endif /* JODY_STRING_H */ winregfs-0.7/mount.winregfs.8000066400000000000000000000044631306056141200162670ustar00rootroot00000000000000.\" Copyright (c) 2014-2017 Jody Bruchon .\" Licensed under the GNU General Public License v2 .\" .TH winregfs 8 "10 Mar 2017" "mount.winregfs" .SH NAME winregfs \- Windows registry FUSE filesystem .SH SYNOPSIS .B mount.winregfs \fB[\fIfuse_options\fP\fB[,...]]\fR .I hivefile mountpoint .SH DESCRIPTION \fBwinregfs\fR is a FUSE-based filesystem driver that enables accessing of Windows registry hive files as ordinary filesystems. Registry hive file editing can be performed with ordinary shell scripts and command-line tools once mounted. Registry values are shown as files with a file extension corresponding to the data type of the value. These are generally named after the REG_* types seen in the Windows registry editor. For example, \fBdw\fP is a DWORD, \fBsz\fP is a string, \fBesz\fP is an expanding string, \fBbin\fP is a raw binary value, and \fBmsz\fP is a multi-line string. Types different from the "standard" types may appear in rare instances. Because registry keys and values can have forward slashes in their names, there is a special word \fB_SLASH_\fP that is used in place of any real slashes and is converted back-and-forth automatically. Opening a value's name without the extension is supported, but you should always use the extension if possible to avoid ambiguity issues. .SH KNOWN ISSUES \fBwinregfs\fP does not currently support writing value data greater than 8,192 bytes (8 KiB) in size. If such a write occurs, the data will be truncated and an error will be returned. Unicode data is not currently supported, partly due to a lack of support in the ntreg library. Incorrect or unrecognized structures in a registry file may cause this program to crash. It is not recommended for use on important data and while most operations generally work as expected, you use it at your own risk. Please report any bugs you encounter. \fBwinregfs\fP loads the entire hive file into one large chunk of memory. This is a side effect of adopting the low-level registry library from the program \fBchntpw\fP. There is a silent delay as the hive is loaded and if the file is large enough there is a chance of failure due to being unable to allocate enough contiguous memory. Changing this behavior is a very difficult task due to how the ntreg library works with the data. .SH SEE ALSO .BR fusermount (8), .BR fsck.winregfs (8) winregfs-0.7/ntreg.c000066400000000000000000002332271306056141200144760ustar00rootroot00000000000000/* * ntreg.c - Windows (NT and up) Registry Hive access library * should be able to handle most basic functions: * iterate, add&delete keys and values, read stuff, change stuff etc * no rename of keys or values yet.. * also contains some minor utility functions (string handling etc) for now * ***** * * NTREG - Window registry file reader / writer library * Copyright (c) 1997-2014 Petter Nordahl-Hagen. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; * version 2.1 of the License. * * This library 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 * Lesser General Public License for more details. * See file LGPL.txt for the full license. * * Modified for Windows Registry FUSE filesystem project "winregfs" * by Jody Bruchon since 2014-04-16 * */ #include #include #include #include #include #include #include #include #include #include #include #include "jody_string.h" #include "winregfs.h" #include "ntreg.h" #if ENABLE_DEBUG_PRINTF #undef LOG #define LOG(...) printf(__VA_ARGS__) #undef DLOG #define DLOG(...) printf(__VA_ARGS__) #endif static char *blank = '\0'; static inline int de_escape(char *s, int wide); const char *val_types[REG_MAX+1] = { "REG_NONE", "REG_SZ", "REG_EXPAND_SZ", "REG_BINARY", "REG_DWORD", /* 0 - 4 */ "REG_DWORD_BIG_ENDIAN", "REG_LINK", /* 5 - 6 */ "REG_MULTI_SZ", "REG_RESOUCE_LIST", "REG_FULL_RES_DESC", "REG_RES_REQ", /* 7 - 10 */ "REG_QWORD", /* 11 */ }; static char *string_prog2regw(void *string, int len, int* out_len); /* Utility routines */ /* toupper() table for registry hashing functions, so we don't have to * dependent upon external locale lib files */ static const unsigned char reg_touppertable[] = { /* ISO 8859-1 is probably not the one.. */ 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, /* 0x00-0x07 */ 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, /* 0x08-0x0f */ 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, /* 0x10-0x17 */ 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, /* 0x18-0x1f */ 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, /* 0x20-0x27 */ 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, /* 0x28-0x2f */ 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, /* 0x30-0x37 */ 0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f, /* 0x38-0x3f */ 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, /* 0x40-0x47 */ 0x48, 0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f, /* 0x48-0x4f */ 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, /* 0x50-0x57 */ 0x58, 0x59, 0x5a, 0x5b, 0x5c, 0x5d, 0x5e, 0x5f, /* 0x58-0x5f */ 0x60, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, /* 0x60-0x67 */ 0x48, 0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f, /* 0x68-0x6f */ 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, /* 0x70-0x77 */ 0x58, 0x59, 0x5a, 0x7b, 0x7c, 0x7d, 0x7e, 0x7f, /* 0x78-0x7f */ 0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, /* 0x80-0x87 */ 0x88, 0x89, 0x8a, 0x8b, 0x8c, 0x8d, 0x8e, 0x8f, /* 0x88-0x8f */ 0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, /* 0x90-0x97 */ 0x98, 0x99, 0x9a, 0x9b, 0x9c, 0x9d, 0x9e, 0x9f, /* 0x98-0x9f */ 0xa0, 0xa1, 0xa2, 0xa3, 0xa4, 0xa5, 0xa6, 0xa7, /* 0xa0-0xa7 */ 0xa8, 0xa9, 0xaa, 0xab, 0xac, 0xad, 0xae, 0xaf, /* 0xa8-0xaf */ 0xb0, 0xb1, 0xb2, 0xb3, 0xb4, 0x00, 0xb6, 0xb7, /* 0xb0-0xb7 */ 0xb8, 0xb9, 0xba, 0xbb, 0xbc, 0xbd, 0xbe, 0xbf, /* 0xb8-0xbf */ 0xc0, 0xc1, 0xc2, 0xc3, 0xc4, 0xc5, 0xc6, 0xc7, /* 0xc0-0xc7 */ 0xc8, 0xc9, 0xca, 0xcb, 0xcc, 0xcd, 0xce, 0xcf, /* 0xc8-0xcf */ 0xd0, 0xd1, 0xd2, 0xd3, 0xd4, 0xd5, 0xd6, 0xd7, /* 0xd0-0xd7 */ 0xd8, 0xd9, 0xda, 0xdb, 0xdc, 0xdd, 0xde, 0xdf, /* 0xd8-0xdf */ 0xc0, 0xc1, 0xc2, 0xc3, 0xc4, 0xc5, 0xc6, 0xc7, /* 0xe0-0xe7 */ 0xc8, 0xc9, 0xca, 0xcb, 0xcc, 0xcd, 0xce, 0xcf, /* 0xe8-0xef */ 0xd0, 0xd1, 0xd2, 0xd3, 0xd4, 0xd5, 0xd6, 0xf7, /* 0xf0-0xf7 */ 0xd8, 0xd9, 0xda, 0xdb, 0xdc, 0xdd, 0xde, 0x00, /* 0xf8-0xff */ }; /* Use table above in strcasecmp else add_key may put names in wrong order and windows actually verifies that on hive load!! or at least it finds out in some cases.. */ static int strn_casecmp(const char *s1, const char *s2, size_t n) { int r; while (*s1 && *s2 && n) { r = reg_touppertable[(unsigned char)*s1] - reg_touppertable[(unsigned char)*s2]; if (r) return r; n--; s1++; s2++; } if ((!*s1 && !*s2) || !n) return 0; if (!*s1) return -1; return 1; } static inline char *str_dup(const char * const restrict str) { char *str_new; int len; if (!str) return 0; len = strlen(str) + 1; str_new = (char *)malloc(len); strncpy(str_new, str, len); return str_new; } /* Optimized non-terminated string copy * * Inline code that accepts a non-terminated string and a length and * copies the string, null-terminating it. Attempts to use word-size * operations when possible. */ static inline void str_memcpy(char *dest, const char *src, int len) { unsigned long *ldest = (unsigned long *)dest; const unsigned long *lsrc = (const unsigned long *)src; int remain = len; /* Alignment check - don't use word-sized copy for unaligned objects */ if (len < (int)sizeof(unsigned long)) goto bytewise_copy; if ((uintptr_t)dest & (sizeof(unsigned long) - 1)) goto bytewise_copy; if ((uintptr_t)src & (sizeof(unsigned long) - 1)) goto bytewise_copy; /* Word copy */ while (remain > (int)sizeof(unsigned long)) { *ldest = *lsrc; ldest++; lsrc++; remain -= sizeof(unsigned long); } bytewise_copy: dest = (char *)ldest; src = (const char *)lsrc; while (remain > 0) { *dest = *src; dest++; src++; remain--; } *dest = '\0'; } /* Get INTEGER from memory. This is probably low-endian specific? */ /* This is totally unnecessary on i386/x86_64, so macro it out! */ #define get_int32(a) *(int32_t *)(a) #if 0 static inline int get_int32(char *array) { return ((array[0]&0xff) + ((array[1]<<8)&0xff00) + ((array[2]<<16)&0xff0000) + ((array[3]<<24)&0xff000000)); } #endif /* Quick and dirty UNICODE to std. ascii */ void cheap_uni2ascii(const char *src, char *dest, int l) { for (; l > 0; l -=2) { *dest = *src; dest++; src +=2; } *dest = 0; } /* Quick and dirty ascii to unicode */ void cheap_ascii2uni(const char *src, char *dest, int l) { for (; l > 0; l--) { *dest++ = *src++; *dest++ = 0; } } /* Flush pages marked as dirty to the hive file */ int flush_dirty_pages(struct hive *hdesc) { int i = 0; int offset; int ret; LOAD_WD_LOGONLY(); DLOG("flush_dirty_pages called (%u pages)\n", hdesc->dirty_entries); while (i < hdesc->dirty_entries) { offset = hdesc->dirty[i] << 12; fseek(hdesc->fp, offset, SEEK_SET); ret = fwrite(hdesc->buffer + offset, 0x1000, 1, hdesc->fp); if (!ret) goto write_error; i++; } hdesc->dirty_entries = 0; return 0; write_error: LOG("error: flush_dirty_pages: write failed @ offset 0x%x\n", offset); return -1; } /* Mark 4K pages as dirty for writing */ int mark_pages_dirty(struct hive *hdesc, int start, int end) { int startpage; int endpage; int i; LOAD_WD_LOGONLY(); startpage = (start >> 12); endpage = (end >> 12); if ((startpage < 0) || (startpage > hdesc->pages)) goto bad_start; if ((endpage < 0) || (endpage > hdesc->pages)) goto bad_end; /* Swap start and end if they are backwards */ if (startpage > endpage) { i = endpage; endpage = startpage; startpage = i; } /* Add each page to the dirty list */ while (startpage <= endpage) { i = 0; if (hdesc->dirty_entries >= MAX_DIRTY) { i = flush_dirty_pages(hdesc); if (i) return i; } while (i < hdesc->dirty_entries) { /* Don't add pages already present */ if (hdesc->dirty[i] == startpage) { i = -1; /* needed for 'continue' later */ startpage++; break; } i++; } /* Skip pages already present */ if (i < 0) continue; /* Store this page number in the dirty list */ hdesc->dirty_entries++; hdesc->dirty[i] = startpage; startpage++; } return 0; bad_start: LOG("error: mark_pages_dirty: bad start page %d (0x%x)\n", startpage, start); return -1; bad_end: LOG("error: mark_pages_dirty: bad end page %d (0x%x)\n", endpage, end); return -1; } /* Parse the datablock * vofs = offset into struct (after size linkage) */ inline int parse_block(struct hive *hdesc, int vofs) { /* unsigned short id; */ int seglen; LOAD_WD_LOGONLY(); seglen = get_int32(hdesc->buffer + vofs); if (seglen == 0) { LOG("parse_block: Zero data block size (not registry or corrupt file?)\n"); return 0; } if (seglen < 0) { seglen = -seglen; hdesc->usetot += seglen; hdesc->useblk++; } else { hdesc->unusetot += seglen; hdesc->unuseblk++; } return seglen; } /* ================================================================ */ /* Scan and allocation routines */ /* Find start of page given a current pointer into the buffer * hdesc = hive * vofs = offset pointer into buffer * returns: offset to start of page (and page header) */ int find_page_start(struct hive * const hdesc, const int vofs) { int r, prev; struct hbin_page *h; LOAD_WD_LOGONLY(); /* Again, assume start at 0x1000 */ r = 0x1000; while (r < hdesc->size) { prev = r; h = (struct hbin_page *)(hdesc->buffer + r); if (h->id != 0x6E696268) return 0; if (h->ofs_next == 0) { LOG("find_page_start: zero len or ofs_next found in page at 0x%x\n",r); return 0; } r += h->ofs_next; if (r > vofs) return prev; } return 0; } /* Find free space in page * size = requested size in bytes * pofs = offset to start of actual page header * returns: offset to free block, or 0 for error */ int find_free_blk(struct hive * const hdesc, const int pofs, const int size) { int vofs = pofs + 0x20; int seglen; struct hbin_page *p; LOAD_WD_LOGONLY(); p = (struct hbin_page *)(hdesc->buffer + pofs); while ((vofs - pofs) < (p->ofs_next - HBIN_ENDFILL)) { seglen = get_int32(hdesc->buffer + vofs); if (seglen == 0) { LOG("find_free_blk: Zero data block size; block at offset %0x\n",vofs); if ((vofs - pofs) == (p->ofs_next - 4)) { LOG("find_free_blk: at exact end of hbin, do not care.\n"); return 0; } return 0; } if (seglen < 0) { seglen = -seglen; } else { if (seglen >= size) { return vofs; } } vofs += seglen; } return 0; } /* Search pages from start to find free block * hdesc - hive * size - space requested, in bytes * returns: offset to free block, 0 if not found or error */ int find_free(struct hive * const hdesc, int size) { unsigned int r, blk; struct hbin_page *h; LOAD_WD_LOGONLY(); /* Align to 8 byte boundary */ if (size & 7) size += (8 - (size & 7)); /* Again, assume start at 0x1000 */ r = 0x1000; while (r < hdesc->endofs) { h = (struct hbin_page *)(hdesc->buffer + r); if (h->id != 0x6E696268) { LOG("find_free: Hive block not of type HBIN at %p", (hdesc->buffer + r)); return 0; } if (h->ofs_next == 0) { LOG("find_free: zero len or ofs_next found in page at 0x%x\n", r); return 0; } blk = find_free_blk(hdesc, r, size); if (blk) return blk; r += h->ofs_next; } LOG("find_free: No free block of size %d found\n", size); return 0; } /* Add new hbin to end of file. If file contains data at end * that is not in a hbin, include that too * hdesc - hive as usual * size - minimum size (will be rounded up to next 0x1000 alignment) * returns offset to first block in new hbin */ int add_bin(struct hive * const hdesc, int size) { unsigned int r; unsigned int newsize; unsigned int newbinofs; struct hbin_page *newbin; struct regf_header *hdr; LOAD_WD_LOGONLY(); if (hdesc->state & HMODE_NOEXPAND) goto error_noexpand; /* Add header and link, round up to page boundary, usually 0x1000 */ r = ((size + 0x20 + 4) & ~0xfff) + HBIN_PAGESIZE; newbinofs = hdesc->endofs; /* We must allocate more buffer */ if ((newbinofs + r) >= hdesc->size) { /* File normally multiple of 0x40000 bytes */ newsize = ((newbinofs + r) & (unsigned int)~(REGF_FILEDIVISOR - 1)) + REGF_FILEDIVISOR; hdesc->buffer = realloc(hdesc->buffer, newsize); if (!hdesc->buffer) goto error_realloc; /* New allocated space needs to be written to disk */ hdesc->pages = newsize >> 12; if (mark_pages_dirty(hdesc, hdesc->size, newsize)) goto error_dirty; hdesc->size = newsize; invalidate_nk_cache(); /* winregfs cache must be wiped if buffer is reallocated */ LOG("winregfs cache invalidated by ntreg\n"); } /* At this point, we have large enough space at end of file */ newbin = (struct hbin_page *)(hdesc->buffer + newbinofs); memset((void *)newbin, 0, r); /* zero out new hbin, easier to debug too */ newbin->id = 0x6E696268; /* 'hbin' */ newbin->ofs_self = newbinofs - 0x1000; /* Point to ourselves minus regf. Seem to be that.. */ newbin->ofs_next = r; /* size of this new bin */ /* Wonder if anything else in the hbin header matters? */ /* Set whole hbin to be one contious unused block */ newbin->firstlink = (r - 0x20 - 0); /* Positive linkage = unused */ /* Update REGF header */ hdr = (struct regf_header *) hdesc->buffer; hdr->filesize = newbinofs + r - 0x1000; /* Point header to new end of data */ /* Update state */ if (mark_pages_dirty(hdesc, 0, 0)) goto error_dirty; if (mark_pages_dirty(hdesc, newbinofs, newbinofs)) goto error_dirty; hdesc->state |= HMODE_DIDEXPAND; hdesc->lastbin = newbinofs; /* Last bin */ hdesc->endofs = newbinofs + r; /* New data end */ return (newbinofs + 0x20); error_noexpand: LOG("add_bin: %s cannot be expanded as required (NOEXPAND is set)\n", hdesc->filename); return 0; error_realloc: LOG("add_bin: realloc to size %u failed\n", newsize); return 0; error_dirty: LOG("add_bin: mark_pages_dirty returned an error\n"); return -1; } /* Allocate a block of requested size if possible * hdesc - hive * pofs - If >0 will try this page first (ptr may be inside page) * size - number of bytes to allocate * returns: 0 - failed, else pointer to allocated block. * WARNING: Will realloc() buffer if it has to be expanded! * ALL POINTERS TO BUFFER IS INVALID AFTER THAT. (offsets are still correct) * Guess I'd better switch to mmap() one day.. * This function WILL CHANGE THE HIVE (change block linkage) if it * succeeds. */ int alloc_block(struct hive *hdesc, int ofs, int size) { int pofs = 0; int blk = 0; int newbin; int trail, trailsize, oldsz; LOAD_WD_LOGONLY(); if (hdesc->state & HMODE_NOALLOC) { LOG("\nalloc_block: Hive <%s> is in no allocation safe mode," "new space not allocated. Operation will fail!\n", hdesc->filename); return 0; } size += 4; /* Add linkage */ if (size & 7) size += (8 - (size & 7)); /* Check current page first */ if (ofs) { pofs = find_page_start(hdesc, ofs); blk = find_free_blk(hdesc, pofs, size); } /* Then check whole hive */ if (!blk) { blk = find_free(hdesc, size); } if (blk) { /* Got the space */ oldsz = get_int32(hdesc->buffer+blk); trailsize = oldsz - size; if (trailsize == 4) { trailsize = 0; size += 4; } if (trailsize & 7) { /* Trail must be 8 aligned */ trailsize -= (8 - (trailsize & 7)); size += (8 - (trailsize & 7)); } if (trailsize == 4) { trailsize = 0; size += 4; } /* Now change pointers on this to reflect new size */ *(int *)((hdesc->buffer)+blk) = -(size); if (mark_pages_dirty(hdesc, blk, blk + size)) return -1; /* If the fit was exact (unused block was same size as wee need) * there is no need for more, else make free block after end * of newly allocated one */ hdesc->useblk++; hdesc->unuseblk--; hdesc->usetot += size; hdesc->unusetot -= size; if (trailsize) { trail = blk + size; *(int *)((hdesc->buffer)+trail) = (int)trailsize; if (mark_pages_dirty(hdesc, trail, trail)) return -1; hdesc->useblk++; /* This will keep blockcount */ hdesc->unuseblk--; hdesc->usetot += 4; /* But account for more linkage bytes */ hdesc->unusetot -= 4; } return blk; } else { LOG("alloc_block: failed to alloc %d bytes, trying to expand hive.\n",size); newbin = add_bin(hdesc,size); if (newbin) return alloc_block(hdesc, newbin, size); /* Nasty... recall ourselves. */ /* Fallthrough to fail if add_bin fails */ } LOG("alloc_block: failed to expand hive"); return 0; } /* Free a block in registry * hdesc - hive * blk - offset of block, MUST POINT TO THE LINKAGE! * returns bytes freed (incl linkage bytes) or 0 if fail * Will CHANGE HIVE IF SUCCESSFUL (changes linkage) */ int free_block(struct hive * const hdesc, int blk) { int pofs,vofs,seglen,next,nextsz,prevsz,size; int prev = 0; struct hbin_page *p; LOAD_WD_LOGONLY(); if (hdesc->state & HMODE_NOALLOC) { LOG("free_block: %s cannot free blocks as needed (NOALLOC set)\n", hdesc->filename); return 0; } size = get_int32(hdesc->buffer+blk); if (size >= 0) { LOG("free_block: trying to free already free block: %x\n",blk); return 0; } size = -size; /* So, we must find start of the block BEFORE us */ pofs = find_page_start(hdesc, blk); if (!pofs) { LOG("free_block: find_page_start failed: offset %d\n", blk); return 0; } p = (struct hbin_page *)(hdesc->buffer + pofs); vofs = pofs + 0x20; prevsz = -32; if (vofs != blk) { /* Block is not at start of page? */ while ((vofs - pofs) < (p->ofs_next - HBIN_ENDFILL)) { seglen = get_int32(hdesc->buffer+vofs); if (seglen == 0) { LOG("free_block: Zero data block size (not registry or corrupt file?)\n"); return 0; } if (seglen < 0) { seglen = -seglen; } prev = vofs; vofs += seglen; if (vofs == blk) break; } if (vofs != blk) { LOG("free_block: Ran off end of page. Error in chains? vofs %x, pofs %x, blk %x\n",vofs,pofs,blk); return 0; } prevsz = get_int32(hdesc->buffer+prev); } /* We also need details on next block (unless at end of page) */ next = blk + size; nextsz = 0; if ((next - pofs) < (p->ofs_next - HBIN_ENDFILL)) nextsz = get_int32(hdesc->buffer+next); /* Now check if next block is free, if so merge it with the one to be freed */ if (nextsz > 0) { size += nextsz; /* Swallow it in current block */ hdesc->useblk--; hdesc->usetot -= 4; hdesc->unusetot -= 4; /* FIXME !??!?? */ } /* Now free the block (possibly with ajusted size as above) */ *(int *)((hdesc->buffer)+blk) = (int)size; hdesc->usetot -= size; hdesc->unusetot -= size; /* FIXME !?!? */ hdesc->unuseblk--; if (mark_pages_dirty(hdesc, blk, blk)) return -1; /* Check if previous block is also free, if so, merge.. */ if (prevsz > 0) { hdesc->usetot -= prevsz; hdesc->unusetot += prevsz; prevsz += size; /* And swallow current.. */ *(int *)((hdesc->buffer)+prev) = (int)prevsz; if (mark_pages_dirty(hdesc, prev, prev)) return -1; hdesc->useblk--; return prevsz; } return size; } /* ================================================================ */ /* ** Registry manipulation routines ** */ /* "directory scan", return next name/pointer of a subkey on each call * nkofs = offset to directory to scan * lfofs = pointer to int to hold the current scan position, * set position to 0 to start. * sptr = pointer to struct to hold a single result * returns: -1 = error. 0 = end of key. 1 = more subkeys to scan */ int ex_next_n(const struct hive * const hdesc, int nkofs, int * const restrict count, int * const restrict countri, struct ex_data * const restrict sptr) { struct nk_key *key, *newnkkey; int newnkofs; struct lf_key *lfkey; struct li_key *likey; struct ri_key *rikey; LOAD_WD_LOGONLY(); if (!nkofs) return -1; if (!sptr) return -1; DLOG("ex_next_n: nkofs %d, count %d, countri %d, sptr 0x%p\n", nkofs, *count, *countri, (void *)sptr); key = (struct nk_key *)(hdesc->buffer + nkofs); if (key->id != 0x6b6e) goto error_not_nk; lfkey = (struct lf_key *)(hdesc->buffer + key->ofs_lf + 0x1004); rikey = (struct ri_key *)(hdesc->buffer + key->ofs_lf + 0x1004); if (hdesc->size < ((intptr_t)lfkey - (intptr_t)hdesc->buffer)) goto error_lf_eof; if (hdesc->size < ((intptr_t)rikey - (intptr_t)hdesc->buffer)) goto error_ri_eof; if (rikey->id == 0x6972) { /* Is it extended 'ri'-block? */ if (*countri < 0 || *countri >= rikey->no_lis) { /* End of ri's? */ return 0; } /* Get the li of lf-struct that's current based on countri */ likey = (struct li_key *)(hdesc->buffer + rikey->hash[*countri].ofs_li + 0x1004) ; if (hdesc->size < ((intptr_t)likey - (intptr_t)hdesc->buffer)) goto error_li_eof; if (likey->id == 0x696c) { newnkofs = likey->hash[*count].ofs_nk + 0x1000; } else { lfkey = (struct lf_key *)(hdesc->buffer + rikey->hash[*countri].ofs_li + 0x1004) ; newnkofs = lfkey->h.hash[*count].ofs_nk + 0x1000; if (hdesc->size < newnkofs) goto error_nk_eof; } /* Check if current li/lf is exhausted */ if (*count >= likey->no_keys-1) { /* Last legal entry in li list? */ (*countri)++; /* Bump up ri count so we take next ri entry next time */ (*count) = -1; /* Reset li traverse counter for next round, not used later here */ } } else { /* Plain handler */ if (key->no_subkeys <= 0 || *count >= key->no_subkeys) return 0; if (lfkey->id == 0x696c) { /* Is it 3.x 'li' instead? */ likey = (struct li_key *)(hdesc->buffer + key->ofs_lf + 0x1004); newnkofs = likey->hash[*count].ofs_nk + 0x1000; } else { newnkofs = lfkey->h.hash[*count].ofs_nk + 0x1000; } } if (hdesc->size < newnkofs) goto error_nk_eof; sptr->nkoffs = newnkofs; newnkkey = (struct nk_key *)(hdesc->buffer + newnkofs + 4); sptr->nk = newnkkey; DLOG("ex_next_n: sptr->nk 0x%p, newnkkey 0x%p, newnkofs 0x%d\n", (void *)sptr->nk, (void *)newnkkey, newnkofs); /* Verify it's an NK */ if (newnkkey->id != 0x6b6e) goto error_not_nk2; else { if (newnkkey->len_name <= 0) { LOG("ex_next_n: nk at 0x%0x has no name\n",newnkofs); } else if (newnkkey->type & 0x20) { str_memcpy(sptr->name, newnkkey->keyname, newnkkey->len_name); } else { string_regw2prog(sptr->name, newnkkey->keyname, newnkkey->len_name); } } (*count)++; return 1; /* return( *count <= key->no_subkeys); */ error_not_nk: LOG("ex_next_n: error: Not a 'nk' node at 0x%0x\n", nkofs); return -1; error_not_nk2: LOG("ex_next_n: error: Not a 'nk' node at 0x%0x\n", newnkofs); return -1; error_lf_eof: LOG("ex_next_n: error: 'lf' key pointer beyond end of file"); return -1; error_ri_eof: LOG("ex_next_n: error: 'ri' key pointer beyond end of file"); return -1; error_li_eof: LOG("ex_next_n: error: 'li' key pointer beyond end of file"); return -1; error_nk_eof: LOG("ex_next_n: error: new 'nk' pointer beyond end of file\n"); return -1; } /* "directory scan" for VALUES, return next name/pointer of a value on each call * nkofs = offset to directory to scan * lfofs = pointer to int to hold the current scan position, * set position to 0 to start. * sptr = pointer to struct to hold a single result * returns: -1 = error. 0 = end of key. 1 = more values to scan * NOTE: caller must free the name-buffer (struct vex_data *name) */ int ex_next_v(struct hive * const hdesc, int nkofs, int * const restrict count, struct vex_data * const restrict sptr) { struct nk_key *key; int32_t vkofs, vlistofs; int32_t *vlistkey; struct vk_key *vkkey; LOAD_WD_LOGONLY(); if (!nkofs) goto error_nkofs_null; if (!sptr) goto error_sptr_null; if ((int)(nkofs + sizeof(struct nk_key)) > hdesc->size) goto error_nk_eof; key = (struct nk_key *)(hdesc->buffer + nkofs); if (key->id != 0x6b6e) goto error_not_nk; if (key->no_values <= 0 || *count >= key->no_values) return 0; vlistofs = key->ofs_vallist + 0x1004; vlistkey = (int32_t *)(hdesc->buffer + vlistofs); if (hdesc->size < vlistofs) goto error_vlistofs_size; vkofs = vlistkey[*count] + 0x1004; if ((int)(vkofs + sizeof(struct vk_key)) > hdesc->size) goto error_key_offset_size; vkkey = (struct vk_key *)(hdesc->buffer + vkofs); if (vkkey->id != 0x6b76) goto error_not_vk; sptr->vk = vkkey; sptr->vkoffs = vkofs; sptr->name[0] = '\0'; sptr->size = (vkkey->len_data & 0x7fffffff); if (vkkey->len_name >0) { if (vkkey->flag & 1) { str_memcpy(sptr->name, vkkey->keyname, vkkey->len_name); } else { string_regw2prog(sptr->name, vkkey->keyname, vkkey->len_name); } } else { strncpy(sptr->name, "", 2); } sptr->type = vkkey->val_type; if (sptr->size) { if (vkkey->val_type == REG_DWORD) { if (vkkey->len_data & 0x80000000) { sptr->val = (int)(vkkey->ofs_data); } } } (*count)++; return (*count <= key->no_values); error_nkofs_null: LOG("ex_next_v: nkofs is NULL\n"); return -1; error_sptr_null: LOG("ex_next_v: sptr is NULL\n"); return -1; error_nk_eof: LOG("ex_next_v: 'nk' offset beyond end of file\n"); return -1; error_not_nk: LOG("ex_next_v: not a 'nk' node at 0x%0x\n",nkofs); return -1; error_vlistofs_size: LOG("ex_next_v: value list offset too large\n"); return -1; error_key_offset_size: LOG("ex_next_v: value key offset too large\n"); return -1; error_not_vk: LOG("ex_next_v: hit non-VK node during scan at offset 0x%0x\n",vkofs); return -1; } /* traceback - trace nk's back to root, * building path string as we go. * nkofs = offset to nk-node * path = pointer to pathstring-buffer * maxlen = max length of path-buffer * return: length of path string */ int get_abs_path(struct hive *hdesc, int nkofs, char *path, int maxlen) { /* int newnkofs; */ struct nk_key *key; char tmp[ABSPATHLEN+1]; char keyname[ABSPATHLEN]; int len_name; LOAD_WD_LOGONLY(); maxlen = (maxlen < ABSPATHLEN ? maxlen : ABSPATHLEN); key = (struct nk_key *)(hdesc->buffer + nkofs); if (key->id != 0x6b6e) { LOG("get_abs_path: Not a 'nk' node\n"); return 0; } if (key->type == KEY_ROOT) { /* We're at the root */ return strlen(path); } strncpy(tmp, path, ABSPATHLEN-1); if (key->type & 0x20) { str_memcpy(keyname, key->keyname, key->len_name); } else { string_regw2prog(keyname, key->keyname, key->len_name); } len_name = strlen(keyname); if ((int)(strlen(path) + len_name) >= maxlen - 6) { snprintf(path,maxlen,"(...)%s",tmp); return strlen(path); /* Stop trace when string exhausted */ } *path = '\\'; memcpy(path + 1, keyname, len_name); strncpy(path + len_name + 1, tmp, maxlen - 6 - len_name); return get_abs_path(hdesc, key->ofs_parent + 0x1004, path, maxlen); /* go back one more */ } /* Value index table lookup * hdesc - hive as usual * vlistofs - offset of table * name - value name to look for * returns index into table or -1 if err */ static int vlist_find(const struct hive * const restrict hdesc, int vlistofs, int numval, const char * const restrict name, int type) { struct vk_key *vkkey; int i, vkofs, len; int32_t *vlistkey; int approx = -1; #if ENABLE_DEBUG_LOGGING LOAD_WD_LOGONLY(); #endif len = strlen(name); vlistkey = (int32_t *)(hdesc->buffer + vlistofs); DLOG("vlist_find: vofs 0x%x, values %d, type 0x%x, name (%d) '%s'\n", vlistofs, numval, type, len, name); for (i = 0; i < numval; i++) { vkofs = vlistkey[i] + 0x1004; vkkey = (struct vk_key *)(hdesc->buffer + vkofs); DLOG("vlist_find: key len %d\n", vkkey->len_name); if (vkkey->len_name == 0 && *name == '@' && len == 1) { /* @ is alias for nameless value */ DLOG("vlist_find: at-value detected, returning %d\n", i); return i; } if ((type & TPF_EXACT) && vkkey->len_name != len) continue; /* Skip if exact match and not exact size */ if (vkkey->len_name >= len) { /* Only check for names that are longer or equal than we seek */ if (!strncaseeq(name, vkkey->keyname, len)) { /* Name match */ /* XXX: winregfs */ if (vkkey->len_name == len) return i; /* Exact match always best, returns */ if (approx == -1) approx = i; /* Else remember first partial match */ } } } return approx; } /* Recursevely follow 'nk'-nodes based on a path-string, * returning offset of last 'nk' or 'vk' * vofs - offset to start node * path - null-terminated pathname (relative to vofs, \ is separator) * type - type to return TPF_??, see ntreg.h * return: offset to nk or vk (or NULL if not found) */ int trav_path(struct hive * const hdesc, int vofs, const char * restrict path, int type) { const struct nk_key *key, *newnkkey; const struct lf_key *lfkey; const struct li_key *likey; const struct ri_key *rikey; const int32_t *vlistkey; int newnkofs, i, lfofs, vlistofs, r, ricnt, subs; int plen = 0; int adjust = 0; char *buf; char part[ABSPATHLEN+1]; char *partptr = NULL; char *partw = NULL; int partw_len, part_len; int cmp; LOAD_WD_LOGONLY(); if (!hdesc) { LOG("trav_path: hive pointer is NULL\n"); return 0; } buf = hdesc->buffer; DLOG("trav_path: vofs 0x%x, type 0x%x, path '%s'\n", vofs, type, path); if (!vofs) vofs = hdesc->rootofs + 4; /* No current key given, so start at root */ if (!(type & TPF_ABS) && *path == '\\' && *(path + 1) != '\\') { /* Start from root if path starts with \ */ path++; vofs = hdesc->rootofs+4; } key = (struct nk_key *)(buf + vofs); if (key->id != 0x6b6e) { LOG("trav_path: Not a 'nk' node\n"); return 0; } if (!(type & TPF_ABS)) { /* Only traverse path if not absolute literal value name passed */ /* TODO: Need to rethink this.. */ /* Find \ delimiter or end of string, copying to name part buffer as we go, rewriting double \\s */ partptr = part; for (plen = 0; path[plen] && (path[plen] != '\\' || path[plen+1] == '\\'); plen++) { if (path[plen] == '\\' && path[plen+1] == '\\') plen++; /* Skip one if double */ *partptr++ = path[plen]; } *partptr = '\0'; if (plen == 0) return (vofs - 4); /* Path has no length - we're there! */ adjust = (path[plen] == '\\') ? 1 : 0; if ((plen == 1) && (*(path + 1) && *path == '.') && !(type & TPF_EXACT)) { /* Handle '.' current dir */ return trav_path(hdesc, vofs, path + plen + adjust, type); } if (!(type & TPF_EXACT) && (plen == 2) && !strcaseeq("..", path)) { /* Get parent key */ newnkofs = key->ofs_parent + 0x1004; /* Return parent (or only root if at the root) */ return trav_path(hdesc, (key->type == KEY_ROOT ? vofs : newnkofs), path + plen + adjust, type); } } /* at last name of path, and we want vk, and the nk has values */ if ((type & TPF_VK_ABS) || (!path[plen] && (type & TPF_VK) && key->no_values)) { if (type & TPF_ABS) { strcpy(part, path); plen = de_escape(part, 0); partptr = part + plen; } vlistofs = key->ofs_vallist + 0x1004; vlistkey = (int32_t *)(buf + vlistofs); i = vlist_find(hdesc, vlistofs, key->no_values, part, type); if (i != -1) { return (vlistkey[i] + 0x1000); } } if (key->no_subkeys > 0) { /* If it has subkeys, loop through the hash */ lfofs = key->ofs_lf + 0x1004; /* lf (hash) record */ lfkey = (struct lf_key *)(buf + lfofs); if (lfkey->id == 0x6972) { /* ri struct need special parsing */ /* Prime loop state */ rikey = (const struct ri_key *)lfkey; ricnt = rikey->no_lis; r = 0; likey = (struct li_key *)(hdesc->buffer + rikey->hash[r].ofs_li + 0x1004); subs = likey->no_keys; if (likey->id != 0x696c) { /* Bwah, not li anyway, XP uses lh usually which is actually smarter */ lfkey = (struct lf_key *)(hdesc->buffer + rikey->hash[r].ofs_li + 0x1004); likey = NULL; } } else { if (lfkey->id == 0x696c) { /* li? */ likey = (struct li_key *)(buf + lfofs); } else { likey = NULL; } rikey = NULL; ricnt = 0; r = 0; subs = key->no_subkeys; } partw = string_prog2regw(part, partptr-part, &partw_len); part_len = strlen(part); do { for(i = 0; i < subs; i++) { if (likey) newnkofs = likey->hash[i].ofs_nk + 0x1004; else newnkofs = lfkey->h.hash[i].ofs_nk + 0x1004; newnkkey = (struct nk_key *)(buf + newnkofs); if (newnkkey->id != 0x6b6e) { LOG("trav_path: error: not a 'nk' node\n"); } else { if (newnkkey->len_name <= 0) { LOG("trav_path: warning: no name\n"); } else if ( ((part_len <= newnkkey->len_name) && !(type & TPF_EXACT)) || ((part_len == newnkkey->len_name) && (type & TPF_EXACT))) { /* Can't match if name is shorter than we look for */ if (newnkkey->type & 0x20) cmp = strncaseeq(part, newnkkey->keyname, part_len); else cmp = memcmp(partw, newnkkey->keyname, partw_len); if (!cmp) { free(partw); return trav_path(hdesc, newnkofs, path + plen + adjust, type); } } } /* if id OK */ } /* hash loop */ r++; if (ricnt && r < ricnt) { newnkofs = rikey->hash[r].ofs_li; likey = (struct li_key *)(hdesc->buffer + newnkofs + 0x1004) ; subs = likey->no_keys; if (likey->id != 0x696c) { /* Bwah, not li anyway, XP uses lh usually which is actually smarter */ lfkey = (struct lf_key *)(hdesc->buffer + rikey->hash[r].ofs_li + 0x1004) ; likey = NULL; } } } while (r < ricnt && ricnt); free(partw); } /* if subkeys */ LOG("trav_path: not found: %s\n", path); return 0; } /* This function only remains as an example for reading a key. */ /* void nk_ls(struct hive *hdesc, char *path, int vofs, int type) { struct nk_key *key; int nkofs; struct ex_data ex; struct vex_data vex; int count = 0, countri = 0; nkofs = trav_path(hdesc, vofs, path, 0); if(!nkofs) { printf("nk_ls: Key <%s> not found\n",path); return; } nkofs += 4; key = (struct nk_key *)(hdesc->buffer + nkofs); if (key->id != 0x6b6e) printf("Error: Not a 'nk' node at offset %x!\n",nkofs); printf("Node has %d subkeys and %d values",key->no_subkeys,key->no_values); if (key->len_classnam) printf(", and class-data of %d bytes",key->len_classnam); printf("\n"); if (key->no_subkeys) { printf(" key name\n"); while ((ex_next_n(hdesc, nkofs, &count, &countri, &ex) > 0)) { printf("[%6x] %c <%s>\n", ex.nkoffs, (ex.nk->len_classnam)?'*':' ',ex.name); free(ex.name); } } count = 0; if (key->no_values) { printf(" size type value name [value if type DWORD]\n"); while ((ex_next_v(hdesc, nkofs, &count, &vex) > 0)) { printf("%6d %x %-16s <%s>", vex.size, vex.type, (vex.type < REG_MAX ? val_types[vex.type] : "(unknown)"), vex.name); if (vex.type == REG_DWORD) printf(" %*d [0x%x]",25-(int)strlen(vex.name),vex.val , vex.val); printf("\n"); free(vex.name); } } } */ /* Get the type of a value */ int get_val_type(struct hive *hdesc, int vofs, char *path, int exact) { struct vk_key *vkkey; int vkofs; LOAD_WD_LOGONLY(); vkofs = trav_path(hdesc, vofs,path,exact | TPF_VK_EXACT); if (!vkofs) { LOG("get_val_type: trav_path failed: %s offset %d\n", path, vofs); return -1; } vkofs += 4; vkkey = (struct vk_key *)(hdesc->buffer + vkofs); return vkkey->val_type; } /* Set/change the type of a value. Strangely we need this in some situation in SAM * and delete + add value is a bit overkill */ int set_val_type(struct hive *hdesc, int vofs, char *path, int exact, int type) { struct vk_key *vkkey; int vkofs; LOAD_WD_LOGONLY(); vkofs = trav_path(hdesc, vofs,path,exact | TPF_VK_EXACT); if (!vkofs) { LOG("set_val_type: trav_path failed: %s offset %d\n", path, vofs); return -1; } vkofs += 4; vkkey = (struct vk_key *)(hdesc->buffer + vkofs); vkkey->val_type = type; return vkkey->val_type; } /* Get len of a value, given current key + path */ int get_val_len(struct hive *hdesc, int vofs, char *path, int exact) { struct vk_key *vkkey; int vkofs; int len; LOAD_WD_LOGONLY(); vkofs = trav_path(hdesc, vofs,path,exact | TPF_VK_EXACT); if (!vkofs) { LOG("get_val_len: trav_path failed: %s offset %d\n", path, vofs); return -1; } vkofs +=4; if (vkofs > hdesc->size) { LOG("get_val_len: error: 'vk' offset too large: %x\n", vkofs); return -1; } vkkey = (struct vk_key *)(hdesc->buffer + vkofs); if (vkkey->id != 0x6b76) { LOG("get_val_len: Cell ID is not 'vk': offset 0x%x, subpath \\%s\n", vkofs, path); return -1; } len = vkkey->len_data & 0x7fffffff; if (vkkey->len_data == 0x80000000 && (exact & TPF_VK_SHORT)) { /* Special inline case, return size of 4 (dword) */ len = 4; } return len; } /* Get void-pointer to value-data, also if inline. * If val_type != 0 a check for correct value type is done * Caller must keep track of value's length (call function above to get it) */ void *get_val_data(struct hive *hdesc, int vofs, char *path, int val_type, int exact) { struct vk_key *vkkey; int vkofs; LOAD_WD_LOGONLY(); vkofs = trav_path(hdesc,vofs,path,exact | TPF_VK); if (!vkofs) { LOG("get_val_data: not found: %s\n", path); return NULL; } vkofs +=4; vkkey = (struct vk_key *)(hdesc->buffer + vkofs); if (vkkey->id != 0x6b76) { LOG("get_val_data: Cell ID is not 'vk': offset 0x%x, subpath \\%s\n", vkofs, path); return NULL; } if (vkkey->len_data == 0) { LOG("get_val_data: zero-length value data: %s\n", path); return NULL; } if (vkkey->len_data == 0x80000000 && (exact & TPF_VK_SHORT)) { /* Special inline case (len = 0x80000000) */ return &vkkey->val_type; /* Data (4 bytes?) in type field */ } if (val_type && vkkey->val_type && (vkkey->val_type) != val_type) { LOG("get_val_data: not of correct type: %s\n",path); return NULL; } /* Negative len is inline, return ptr to offset-field which in * this case contains the data itself */ if (vkkey->len_data & 0x80000000) return &vkkey->ofs_data; /* Normal return, return data pointer */ return (hdesc->buffer + vkkey->ofs_data + 0x1004); } /* Get and copy key data (if any) to buffer * if kv==NULL will allocate needed return struct & buffer * else will use buffer allocated for it (if it fits) * return len+data or NULL if not found (or other error) * NOTE: caller must deallocate buffer! a simple free(keyval) will suffice. */ struct keyval *get_val2buf(struct hive *hdesc, struct keyval *kv, int vofs, char *path, int type, int exact) { int l, i, parts, list, blockofs, blocksize, point, copylen, restlen; struct keyval *kr; void *keydataptr; struct db_key *db; void *addr; LOAD_WD_LOGONLY(); l = get_val_len(hdesc, vofs, path, exact); DLOG("get_val2buf: value length for 'vk' offset %x subpath '\\%s' = %x\n", vofs, path, l); if (l == -1) { LOG("get_val2buf: get_val_len error: %s offset %d\n", path, vofs); return NULL; } if (kv && (kv->len < l)) { LOG("get_val2buf: Buffer overflow\n"); return NULL; } keydataptr = get_val_data(hdesc, vofs, path, type, exact); if (!keydataptr) { LOG("get_val2buf: error reading value data\n"); return NULL; } /* Allocate space for data + header, or use supplied buffer */ if (kv) { kr = kv; } else { ALLOC(kr, 1, 2 * sizeof(struct keyval)); ALLOC(kr->data, 1, l * sizeof(int32_t)); } kr->len = l; if (l > VAL_DIRECT_LIMIT) { /* Where do the db indirects start? seems to be around 16k */ DLOG("get_val2buf: notice: kr->len > VAL_DIRECT_LIMIT: %x\n", l); db = (struct db_key *)keydataptr; if (db->id != 0x6264) { LOG("get_val2buf: data block expected but not found: 0x%" PRIxPTR "\n", (intptr_t)db - (intptr_t)hdesc->buffer); if(!kv) { free(kr->data); free(kr); } return NULL; } parts = db->no_part; list = db->ofs_data + 0x1004; LOG("get_val2buf: Long value: parts %d, list %x\n", parts, list); point = 0; restlen = l; for (i = 0; i < parts; i++) { blockofs = get_int32(hdesc->buffer + list + (i << 2)) + 0x1000; blocksize = -get_int32(hdesc->buffer + blockofs) - 8; /* Copy this part, up to size of block or rest lenght in last block */ copylen = (blocksize > restlen) ? restlen : blocksize; DLOG("get_val2buf: Datablock %d offset %x, size %x (%d)\n",i,blockofs,blocksize,blocksize); DLOG(" : Point = %x, restlen = %x, copylen = %x\n",point,restlen,copylen); addr = (void *)((int *)kr->data + point); memcpy(addr, hdesc->buffer + blockofs + 4, copylen); point += copylen; restlen -= copylen; } } else { if (l && kr && keydataptr) memcpy(kr->data, keydataptr, l); } return kr; } /* Sanity checker when transferring data into a block * ofs = offset to data block, point to start of actual datablock linkage * data = data to copy * size = size of data to copy */ int fill_block(struct hive *hdesc, int ofs, void *data, int size) { uint32_t blksize; LOAD_WD_LOGONLY(); blksize = get_int32(hdesc->buffer + ofs); blksize = -blksize; /* if (blksize < size || ( (ofs & 0xfffff000) != ((ofs+size) & 0xfffff000) )) { */ if (blksize < (unsigned int)size) goto error_too_small; memcpy(hdesc->buffer + ofs + 4, data, size); if (mark_pages_dirty(hdesc, ofs + 4, ofs + size + 4)) goto error_dirty; return 0; error_too_small: LOG("fill_block: too small for data: ofs %x, size %x, blksize %x\n",ofs,size,blksize); return -1; error_dirty: LOG("fill_block: mark_pages_dirty returned an error\n"); return -1; } /* Free actual data of a value, and update value descriptor * hdesc - hive * vofs - current value offset */ int free_val_data(struct hive *hdesc, int vkofs) { struct vk_key *vkkey; struct db_key *db; int len,i,blockofs,parts,list; #if LOG_IS_USED int blocksize; #endif LOAD_WD_LOGONLY(); vkkey = (struct vk_key *)(hdesc->buffer + vkofs); len = vkkey->len_data; if (!(len & 0x80000000)) { /* Check for inline, if so, skip it, nothing to do */ if (len > VAL_DIRECT_LIMIT) { /* Where do the db indirects start? seems to be around 16k */ db = (struct db_key *)(hdesc->buffer + vkkey->ofs_data + 0x1004); if (db->id != 0x6264) { LOG("free_val_data: db id incorrect\n"); return -1; } parts = db->no_part; list = db->ofs_data + 0x1004; DLOG("free_val_data: Long value: parts %d, list %x\n", parts, list); for (i = 0; i < parts; i++) { blockofs = get_int32(hdesc->buffer + list + (i << 2)) + 0x1000; #if LOG_IS_USED blocksize = -get_int32(hdesc->buffer + blockofs); LOG("free_val_data: Freeing long block %d: offset %x, size %x (%d)\n",i, blockofs, blocksize, blocksize); #endif free_block(hdesc, blockofs); } DLOG("free_val_data: Freeing indirect list at %x\n", list-4); free_block(hdesc, list - 4); DLOG("free_val_data: Freeing db structure at %x\n", vkkey->ofs_data + 0x1000); } /* Fall through to regular which deallocs data or db block ofs_data point to */ if (len) free_block(hdesc, vkkey->ofs_data + 0x1000); } /* inline check */ vkkey->len_data = 0; vkkey->ofs_data = 0; mark_pages_dirty(hdesc, vkofs, vkofs); return vkofs; } /* Allocate data for value. Frees old data (if any) which will be destroyed * hdesc - hive * vofs - current key * path - path to value * size - size of data * Returns: 0 - error, >0 pointer to actual dataspace */ int alloc_val_data(struct hive *hdesc, int vofs, char *path, int size,int exact) { struct vk_key *vkkey; struct db_key *db; int vkofs,dbofs,listofs,blockofs,blocksize,parts; int datablk, i, j; int *ptr; LOAD_WD_LOGONLY(); vkofs = trav_path(hdesc,vofs,path,exact); if (!vkofs) goto error_trav_path; vkofs +=4; vkkey = (struct vk_key *)(hdesc->buffer + vkofs); /* Get rid of old data if any */ if (free_val_data(hdesc, vkofs) < 0) goto error_free; /* Allocate space for new data */ if (size > 4) { if (size > VAL_DIRECT_LIMIT) { /* We must allocate indirect stuff *sigh* */ parts = size / VAL_DIRECT_LIMIT + 1; DLOG("alloc_val_data: large key: size %x (%d), parts %d\n", size, size, parts); dbofs = alloc_block(hdesc, vkofs, sizeof(struct db_key)); /* Alloc db structure */ db = (struct db_key *)(hdesc->buffer + dbofs + 4); db->id = 0x6264; db->no_part = parts; listofs = alloc_block(hdesc, vkofs, 4 * parts); /* block offset list */ db = (struct db_key *)(hdesc->buffer + dbofs + 4); db->ofs_data = listofs - 0x1000; if (mark_pages_dirty(hdesc, dbofs, dbofs + 4)) goto error_dirty; LOG("alloc_val_data: dbofs %x, listofs %x\n", dbofs, listofs); for (i = 0; i < parts; i++) { blocksize = VAL_DIRECT_LIMIT; /* Windows seem to alway allocate the whole block */ blockofs = alloc_block(hdesc, vkofs, blocksize); LOG("alloc_val_data: block %d, blockofs %x\n",i,blockofs); j = listofs + 4 + (i << 2); ptr = (int *)(hdesc->buffer + j); *ptr = blockofs - 0x1000; if (mark_pages_dirty(hdesc, j, j)) goto error_dirty; } datablk = dbofs; } else { /* Regular size < 16 k direct alloc */ datablk = alloc_block(hdesc, vkofs, size); } } else { /* 4 bytes or less are inlined */ /* datablk = vkofs + (int32_t)&(vkkey->ofs_data) - (int32_t)vkkey; */ datablk = vkofs + (int)(&(vkkey->ofs_data) - (int *)vkkey); size |= 0x80000000; } if (!datablk) goto error_db_ptr; vkkey = (struct vk_key *)(hdesc->buffer + vkofs); /* alloc_block may move pointer, realloc() buf */ /* Link in new datablock */ if (!(size & 0x80000000)) vkkey->ofs_data = datablk - 0x1000; vkkey->len_data = size; if (mark_pages_dirty(hdesc, vkofs, vkofs)) goto error_dirty; return (datablk + 4); error_trav_path: LOG("alloc_val_data: trav_path failed: %s offset %d\n", path, vofs); return 0; error_free: LOG("alloc_val_data: error freeing old data\n"); return 0; error_db_ptr: LOG("alloc_val_data: data block pointer is NULL\n"); return 0; error_dirty: LOG("alloc_val_data: mark_pages_dirty returned an error\n"); return 0; } /* Add a value to a key. * Just add the metadata (empty value), to put data into it, use * put_buf2val afterwards * hdesc - hive * nkofs - current key * name - name of value * type - type of value * returns: 0 err, >0 offset to value metadata */ struct vk_key *add_value(struct hive *hdesc, int nkofs, char *name, int type) { struct nk_key *nk; int oldvlist = 0, newvlist, newvkofs, i; struct vk_key *newvkkey; LOAD_WD_LOGONLY(); if (!name || !*name) goto error_null_name; nk = (struct nk_key *)(hdesc->buffer + nkofs); if (nk->id != 0x6b6e) goto error_not_nk; if (vlist_find(hdesc, nk->ofs_vallist + 0x1004, nk->no_values, name, TPF_EXACT) != -1) { if(!del_value(hdesc, nkofs, name)) goto error_del_value; } if (!streq(name, "@")) name = blank; if (nk->no_values) oldvlist = nk->ofs_vallist; newvlist = alloc_block(hdesc, nkofs, nk->no_values * 4 + 4); if (!newvlist) goto error_vlist_alloc; nk = (struct nk_key *)(hdesc->buffer + nkofs); /* In case buffer was moved.. */ if (oldvlist) { /* Copy old data if any */ memcpy(hdesc->buffer + newvlist + 4, hdesc->buffer + oldvlist + 0x1004, nk->no_values * 4 + 4); if (mark_pages_dirty(hdesc, newvlist + 4, newvlist + 8 + nk->no_values * 4)) goto error_dirty; } /* Allocate value descriptor including its name */ newvkofs = alloc_block(hdesc, newvlist, sizeof(struct vk_key) + strlen(name)); if (!newvkofs) goto error_vkofs_alloc; nk = (struct nk_key *)(hdesc->buffer + nkofs); /* In case buffer was moved.. */ /* Success, now fill in the metadata */ newvkkey = (struct vk_key *)(hdesc->buffer + newvkofs + 4); /* Add pointer in value list */ i = newvlist + 4 + (nk->no_values * 4); *(int *)(hdesc->buffer + i) = newvkofs - 0x1000; if (mark_pages_dirty(hdesc, i, i)) goto error_dirty; /* Fill in vk struct */ newvkkey->id = 0x6b76; newvkkey->len_name = strlen(name); if (type == REG_DWORD || type == REG_DWORD_BIG_ENDIAN) { newvkkey->len_data = 0x80000004; /* Prime the DWORD inline stuff */ } else { newvkkey->len_data = 0x80000000; /* Default inline zero size */ } newvkkey->ofs_data = 0; newvkkey->val_type = type; newvkkey->flag = newvkkey->len_name ? 1 : 0; /* Seems to be 1, but 0 for no name default value */ newvkkey->dummy1 = 0; memcpy((char *)&newvkkey->keyname, name, newvkkey->len_name); /* And copy name */ /* Finally update the key and free the old valuelist */ nk->no_values++; nk->ofs_vallist = newvlist - 0x1000; if (oldvlist) free_block(hdesc,oldvlist + 0x1000); if (mark_pages_dirty(hdesc, newvkofs + 4, newvkofs + 4)) goto error_dirty; return newvkkey; error_null_name: LOG("add_value: Null or empty value name\n"); return NULL; error_not_nk: LOG("add_value: Key pointer not to 'nk' node\n"); return NULL; error_del_value: LOG("add_value: could not remove old value: %s\n", name); return NULL; error_dirty: LOG("add_block: mark_pages_dirty returned an error\n"); return NULL; error_vkofs_alloc: free_block(hdesc, newvlist); LOG("add_block: alloc_block failed to allocate value descriptor\n"); return NULL; error_vlist_alloc: LOG("add_value: failed to allocate new value list\n"); return NULL; } /* Remove a vk-struct incl dataspace if any * Mostly for use by higher level stuff * hdesc - hive * vkofs - offset to vk */ int del_vk(struct hive *hdesc, int vkofs) { struct vk_key *vk; LOAD_WD_LOGONLY(); vk = (struct vk_key *)(hdesc->buffer + vkofs); if (vk->id != 0x6b76) { LOG("del_vk: Key pointer not to 'vk' node\n"); return 1; } if (!(vk->len_data & 0x80000000) && vk->ofs_data) { if(free_val_data(hdesc, vkofs) < 0) { LOG("del_vk: error freeing value data\n"); return -1; } } free_block(hdesc, vkofs - 4); return 0; } /* Delete single value from key * hdesc - yer usual hive * nkofs - current keyoffset * name - name of value to delete * returns: 0 - ok, 1 - failed */ int del_value(struct hive *hdesc, int nkofs, char *name) { int vlistofs, slot, o, n, vkofs, newlistofs; int32_t *vlistkey, *tmplist, *newlistkey; struct nk_key *nk; LOAD_WD_LOGONLY(); if (!name || !*name) { LOG("del_value: Null or empty name given\n"); return 1; } if (!streq(name, "@")) name = blank; nk = (struct nk_key *)(hdesc->buffer + nkofs); if (nk->id != 0x6b6e) { LOG("del_value: Key pointer not to 'nk' node: %s\n", name); return 1; } if (!nk->no_values) { LOG("del_value: Key has no values: %s\n", name); return 1; } vlistofs = nk->ofs_vallist + 0x1004; vlistkey = (int32_t *)(hdesc->buffer + vlistofs); slot = vlist_find(hdesc, vlistofs, nk->no_values, name, TPF_VK_EXACT); if (slot == -1) { LOG("del_value: not found: %s\n", name); return 1; } /* Delete vk and data */ vkofs = vlistkey[slot] + 0x1004; del_vk(hdesc, vkofs); /* Copy out old index list */ CREATE(tmplist,int32_t,nk->no_values); memcpy(tmplist, vlistkey, nk->no_values * sizeof(int32_t)); free_block(hdesc,vlistofs-4); /* Get rid of old list */ nk->no_values--; if (nk->no_values) { newlistofs = alloc_block(hdesc, vlistofs, nk->no_values * sizeof(int32_t)); if (!newlistofs) { LOG("del_value: Was not able to alloc new index list\n"); return 1; } nk = (struct nk_key *)(hdesc->buffer + nkofs); /* In case buffer was moved */ /* Now copy over, omitting deleted entry */ newlistkey = (int32_t *)(hdesc->buffer + newlistofs + 4); for (n = 0, o = 0; n < nk->no_values; o++, n++) { if (o == slot) o++; newlistkey[n] = tmplist[o]; } nk->ofs_vallist = newlistofs - 0x1000; } else { nk->ofs_vallist = -1; } return 0; } /* Add a subkey to a key * hdesc - usual.. * nkofs - offset of current nk * name - name of key to add * return: ptr to new keystruct, or NULL */ struct nk_key *add_key(struct hive *hdesc, int nkofs, char *name) { int slot, newlfofs = 0, oldlfofs = 0, newliofs = 0; int oldliofs = 0; int o, n, i, onkofs, newnkofs, cmp; int rimax, rislot, riofs, namlen; struct ri_key *ri = NULL; struct lf_key *newlf = NULL, *oldlf; struct li_key *newli = NULL, *oldli; struct nk_key *key, *newnk, *onk; int32_t hash; LOAD_WD_LOGONLY(); key = (struct nk_key *)(hdesc->buffer + nkofs); if (key->id != 0x6b6e) goto error_not_nk; namlen = strlen(name); slot = -1; if (key->no_subkeys) { /* It already has subkeys */ oldlfofs = key->ofs_lf; oldliofs = key->ofs_lf; oldlf = (struct lf_key *)(hdesc->buffer + oldlfofs + 0x1004); if (oldlf->id != 0x666c && oldlf->id != 0x686c && oldlf->id != 0x696c && oldlf->id != 0x697) goto error_bad_index_type; rimax = 0; ri = NULL; riofs = 0; rislot = -1; if (oldlf->id == 0x6972) { /* Indirect index 'ri', init loop */ riofs = key->ofs_lf; ri = (struct ri_key *)(hdesc->buffer + riofs + 0x1004); rimax = ri->no_lis-1; oldliofs = ri->hash[rislot+1].ofs_li; oldlfofs = ri->hash[rislot+1].ofs_li; } do { /* 'ri' loop, at least run once if no 'ri' deep index */ if (ri) { /* Do next 'ri' slot */ rislot++; oldliofs = ri->hash[rislot].ofs_li; oldlfofs = ri->hash[rislot].ofs_li; oldli = (struct li_key *)(hdesc->buffer + oldliofs + 0x1004); oldlf = (struct lf_key *)(hdesc->buffer + oldlfofs + 0x1004); } oldli = (struct li_key *)(hdesc->buffer + oldliofs + 0x1004); oldlf = (struct lf_key *)(hdesc->buffer + oldlfofs + 0x1004); slot = -1; if (oldli->id == 0x696c) { /* li */ free(newli); ALLOC(newli, 8 + 4*oldli->no_keys + 4, 1); newli->no_keys = oldli->no_keys; newli->id = oldli->id; /* Now copy old, checking where to insert (alphabetically) */ for (o = 0, n = 0; o < oldli->no_keys; o++,n++) { onkofs = oldli->hash[o].ofs_nk; onk = (struct nk_key *)(onkofs + hdesc->buffer + 0x1004); if (slot == -1) { cmp = strn_casecmp(name, onk->keyname, (namlen > onk->len_name) ? namlen : onk->len_name); if (!cmp) goto error_key_exists; if (cmp < 0) { slot = o; rimax = rislot; /* Cause end of 'ri' search, too */ n++; } } newli->hash[n].ofs_nk = oldli->hash[o].ofs_nk; } if (slot == -1) slot = oldli->no_keys; } else { /* lf or lh */ oldlf = (struct lf_key *)(hdesc->buffer + oldlfofs + 0x1004); free(newlf); ALLOC(newlf, 8 + 8*oldlf->no_keys + 8, 1); newlf->no_keys = oldlf->no_keys; newlf->id = oldlf->id; /* Now copy old, checking where to insert (alphabetically) */ for (o = 0, n = 0; o < oldlf->no_keys; o++,n++) { onkofs = oldlf->h.hash[o].ofs_nk; onk = (struct nk_key *)(onkofs + hdesc->buffer + 0x1004); if (slot == -1) { cmp = strn_casecmp(name, onk->keyname, (namlen > onk->len_name) ? namlen : onk->len_name); if (!cmp) goto error_key_exists; if (cmp < 0) { slot = o; rimax = rislot; /* Cause end of 'ri' search, too */ n++; } } newlf->h.hash[n].ofs_nk = oldlf->h.hash[o].ofs_nk; newlf->h.hash[n].name[0] = oldlf->h.hash[o].name[0]; newlf->h.hash[n].name[1] = oldlf->h.hash[o].name[1]; newlf->h.hash[n].name[2] = oldlf->h.hash[o].name[2]; newlf->h.hash[n].name[3] = oldlf->h.hash[o].name[3]; } if (slot == -1) slot = oldlf->no_keys; } /* li else check */ } while ((rislot < rimax) && (rimax > 0)); /* 'ri' wrapper loop */ } else { /* Parent was empty, make new index block */ ALLOC(newlf, 8 + 8, 1); newlf->no_keys = 0 ; /* Will increment to 1 when filling in the offset later */ /* Use ID (lf, lh or li) we fetched from root node, so we use same as rest of hive */ newlf->id = hdesc->nkindextype; slot = 0; } /* if has keys before */ /* Make and fill in new nk */ newnkofs = alloc_block(hdesc, nkofs, sizeof(struct nk_key) + strlen(name)); if (!newnkofs) goto error_newnkofs; key = (struct nk_key *)(hdesc->buffer + nkofs); /* In case buffer moved */ newnk = (struct nk_key *)(hdesc->buffer + newnkofs + 4); newnk->id = 0x6b6e; newnk->type = KEY_NORMAL; /* Some versions use 0x1020 a lot.. */ newnk->ofs_parent = nkofs - 0x1004; newnk->no_subkeys = 0; newnk->ofs_lf = -1; newnk->no_values = 0; newnk->ofs_vallist = -1; newnk->ofs_sk = key->ofs_sk; /* Get parents for now. 0 or -1 here crashes XP */ newnk->ofs_classnam = -1; newnk->len_name = strlen(name); newnk->len_classnam = 0; memcpy(newnk->keyname, name, newnk->len_name); if (newli) { /* Handle li */ /* And put its offset into parents index list */ newli->hash[slot].ofs_nk = newnkofs - 0x1000; newli->no_keys++; /* Allocate space for our new li list and copy it into reg */ newliofs = alloc_block(hdesc, nkofs, 8 + 4*newli->no_keys); if (!newliofs) goto error_newliofs; key = (struct nk_key *)(hdesc->buffer + nkofs); /* In case buffer moved */ newnk = (struct nk_key *)(hdesc->buffer + newnkofs + 4); /* memcpy(hdesc->buffer + newliofs + 4, newli, 8 + 4*newli->no_keys); */ if (fill_block(hdesc, newliofs, newli, 8 + 4*newli->no_keys)) goto error_fill_block; } else { /* lh or lf */ /* And put its offset into parents index list */ newlf->h.hash[slot].ofs_nk = newnkofs - 0x1000; newlf->no_keys++; if (newlf->id == 0x666c) { /* lf hash */ newlf->h.hash[slot].name[0] = 0; newlf->h.hash[slot].name[1] = 0; newlf->h.hash[slot].name[2] = 0; newlf->h.hash[slot].name[3] = 0; strncpy(newlf->h.hash[slot].name, name, 4); } else if (newlf->id == 0x686c) { /* lh. XP uses this. hashes whole name */ for (i = 0, hash = 0; i < (int)strlen(name); i++) { hash *= 37; hash += reg_touppertable[(unsigned char)name[i]]; } newlf->h.lh_hash[slot].hash = hash; } /* Allocate space for our new lf list and copy it into reg */ newlfofs = alloc_block(hdesc, nkofs, 8 + 8*newlf->no_keys); if (!newlfofs) goto error_alloc_index; key = (struct nk_key *)(hdesc->buffer + nkofs); /* In case buffer moved */ newnk = (struct nk_key *)(hdesc->buffer + newnkofs + 4); /* memcpy(hdesc->buffer + newlfofs + 4, newlf, 8 + 8*newlf->no_keys); */ if(fill_block(hdesc, newlfofs, newlf, 8 + 8*newlf->no_keys)) goto error_fill_block; } /* li else */ /* Update parent, and free old lf list */ key->no_subkeys++; if (ri) { /* ri index */ ri->hash[rislot].ofs_li = (newlf ? newlfofs : newliofs) - 0x1000; } else { /* Parent key */ key->ofs_lf = (newlf ? newlfofs : newliofs) - 0x1000; } if (newlf && oldlfofs) free_block(hdesc,oldlfofs + 0x1000); if (newli && oldliofs) free_block(hdesc,oldliofs + 0x1000); free(newlf); free(newli); return newnk; error_not_nk: LOG("add_key: current pointer not 'nk'\n"); return NULL; error_bad_index_type: LOG("add_key: index type not supported: 0x%04x\n",oldlf->id); return NULL; error_key_exists: free(newlf); LOG("add_key: key %s already exists!\n", name); return NULL; error_newnkofs: free(newlf); free(newli); LOG("add_key: can't alloc space for key descriptor for %s\n", name); return NULL; error_newliofs: free(newli); free_block(hdesc,newnkofs); LOG("add_key: can't alloc space for index table for %s\n", name); return NULL; error_alloc_index: free(newlf); free_block(hdesc,newnkofs); LOG("add_key: can't alloc space for index table for %s\n", name); return NULL; error_fill_block: LOG("add_key: fill_block failed\n"); return NULL; } /* Delete a subkey from a key * hdesc - usual.. * nkofs - offset of current nk * name - name of key to delete (must match exactly, also case) * return: 1 - err, 0 - ok */ int del_key(struct hive *hdesc, int nkofs, char *name) { int slot = 0, newlfofs = 0, oldlfofs = 0, o, n, onkofs; int delnkofs = 0; int oldliofs = 0, no_keys = 0, newriofs = 0; int namlen; int rimax, riofs, rislot; struct ri_key *ri, *newri = NULL; struct lf_key *newlf = NULL, *oldlf = NULL; struct li_key *newli = NULL, *oldli = NULL; struct nk_key *key, *onk, *delnk = NULL; char fullpath[501]; LOAD_WD_LOGONLY(); key = (struct nk_key *)(hdesc->buffer + nkofs); namlen = strlen(name); if (key->id != 0x6b6e) goto error_not_nk; slot = -1; if (!key->no_subkeys) goto error_no_subkeys; oldlfofs = key->ofs_lf; oldliofs = key->ofs_lf; oldlf = (struct lf_key *)(hdesc->buffer + oldlfofs + 0x1004); if (oldlf->id != 0x666c && oldlf->id != 0x686c && oldlf->id != 0x696c && oldlf->id != 0x6972) goto error_index; rimax = 0; ri = NULL; riofs = 0; rislot = 0; if (oldlf->id == 0x6972) { /* Indirect index 'ri', init loop */ riofs = key->ofs_lf; ri = (struct ri_key *)(hdesc->buffer + riofs + 0x1004); rimax = ri->no_lis-1; rislot = -1; /* Starts at slot 0 below */ } do { /* 'ri' loop, at least run once if no 'ri' deep index */ if (ri) { /* Do next 'ri' slot */ rislot++; oldliofs = ri->hash[rislot].ofs_li; oldlfofs = ri->hash[rislot].ofs_li; } oldli = (struct li_key *)(hdesc->buffer + oldliofs + 0x1004); oldlf = (struct lf_key *)(hdesc->buffer + oldlfofs + 0x1004); slot = -1; if (oldlf->id == 0x696c) { /* 'li' handler */ free(newli); ALLOC(newli, 8 + 4*oldli->no_keys - 4, 1); newli->no_keys = oldli->no_keys - 1; no_keys = newli->no_keys; newli->id = oldli->id; /* Now copy old, checking where to delete */ for (o = 0, n = 0; o < oldli->no_keys; o++,n++) { onkofs = oldli->hash[o].ofs_nk; onk = (struct nk_key *)(onkofs + hdesc->buffer + 0x1004); if (slot == -1 && onk->len_name == namlen && !strneq(name, onk->keyname, (onk->len_name > namlen) ? onk->len_name : namlen)) { slot = o; delnkofs = onkofs; delnk = onk; rimax = rislot; o++; } newli->hash[n].ofs_nk = oldli->hash[o].ofs_nk; } if (mark_pages_dirty(hdesc, 4 + 4*oldli->no_keys, 4 + 4*oldli->no_keys)) goto error_dirty; } else { /* 'lf' or 'lh' are similar */ free(newlf); ALLOC(newlf, 8 + 8*oldlf->no_keys - 8, 1); newlf->no_keys = oldlf->no_keys - 1; no_keys = newlf->no_keys; newlf->id = oldlf->id; /* Now copy old, checking where to delete */ for (o = 0, n = 0; o < oldlf->no_keys; o++,n++) { onkofs = oldlf->h.hash[o].ofs_nk; onk = (struct nk_key *)(onkofs + hdesc->buffer + 0x1004); if (slot == -1 && (onk->len_name == namlen) && !strneq(name, onk->keyname, onk->len_name)) { slot = o; delnkofs = onkofs; delnk = onk; rimax = rislot; o++; } if (n < newlf->no_keys) { /* Only store if not last index in old */ newlf->h.hash[n].ofs_nk = oldlf->h.hash[o].ofs_nk; newlf->h.hash[n].name[0] = oldlf->h.hash[o].name[0]; newlf->h.hash[n].name[1] = oldlf->h.hash[o].name[1]; newlf->h.hash[n].name[2] = oldlf->h.hash[o].name[2]; newlf->h.hash[n].name[3] = oldlf->h.hash[o].name[3]; } } if (mark_pages_dirty(hdesc, 8*oldlf->no_keys, 8*oldlf->no_keys)) goto error_dirty; } /* else lh or lf */ } while (rislot < rimax); /* ri traverse loop */ if (slot == -1) goto error_subkey_not_found; if (delnk->no_values || delnk->no_subkeys) goto error_not_empty; /* Allocate space for our new lf list and copy it into reg */ if (no_keys && (newlf || newli)) { newlfofs = alloc_block(hdesc, nkofs, 8 + (newlf ? 8 : 4) * no_keys); /* alloc_block may invalidate pointers if hive expanded. Recalculate this one. * Thanks to Jacky To for reporting it here, and suggesting a fix * (better would of course be for me to redesign stuff :) */ if (delnkofs) delnk = (struct nk_key *)(delnkofs + hdesc->buffer + 0x1004); if (!newlfofs) goto error_alloc_key; key = (struct nk_key *)(hdesc->buffer + nkofs); oldli = (struct li_key *)(hdesc->buffer + oldliofs + 0x1004); oldlf = (struct lf_key *)(hdesc->buffer + oldlfofs + 0x1004); /* memcpy(hdesc->buffer + newlfofs + 4, ((void *)newlf ? (void *)newlf : (void *)newli), 8 + (newlf ? 8 : 4) * no_keys); */ if(fill_block(hdesc, newlfofs, ((void *)newlf ? (void *)newlf : (void *)newli), 8 + (newlf ? 8 : 4) * no_keys)) goto error_fill_block; } else { /* Last deleted, will throw away index */ newlfofs = 0xfff; /* We subtract 0x1000 later */ } if (newlfofs < 0xfff) goto error_newlfofs; /* Check for CLASS data, if so, deallocate it too */ if (delnk != NULL && delnk->len_classnam) { free_block(hdesc, delnk->ofs_classnam + 0x1000); } /* Now it's safe to zap the nk */ free_block(hdesc, delnkofs + 0x1000); /* And the old index list */ free_block(hdesc, (oldlfofs ? oldlfofs : oldliofs) + 0x1000); /* Update parent */ key->no_subkeys--; key = (struct nk_key *)(hdesc->buffer + nkofs); oldli = (struct li_key *)(hdesc->buffer + oldliofs + 0x1004); oldlf = (struct lf_key *)(hdesc->buffer + oldlfofs + 0x1004); if (ri) { ri = (struct ri_key *)(hdesc->buffer + riofs + 0x1004); /* In case realloc */ if (newlfofs == 0xfff) { *fullpath = 0; get_abs_path(hdesc, nkofs, fullpath, 480); if (ri->no_lis > 1) { /* We have subindiceblocks left? */ /* Delete from array */ ALLOC(newri, 8 + 4*ri->no_lis - 4, 1); newri->no_lis = ri->no_lis - 1; newri->id = ri->id; for (o = 0, n = 0; o < ri->no_lis; o++,n++) { if (n == rislot) o++; newri->hash[n].ofs_li = ri->hash[o].ofs_li; } newriofs = alloc_block(hdesc, nkofs, 8 + newri->no_lis*4); if (!newriofs) goto error_alloc_ri_index; key = (struct nk_key *)(hdesc->buffer + nkofs); oldli = (struct li_key *)(hdesc->buffer + oldliofs + 0x1004); oldlf = (struct lf_key *)(hdesc->buffer + oldlfofs + 0x1004); if(fill_block(hdesc, newriofs, newri, 8 + newri->no_lis * 4)) goto error_fill_block; free_block(hdesc, riofs + 0x1000); key->ofs_lf = newriofs - 0x1000; free(newri); } else { /* Last entry in ri was deleted, get rid of it, key is empty */ free_block(hdesc, riofs + 0x1000); key->ofs_lf = -1; } } else { ri->hash[rislot].ofs_li = newlfofs - 0x1000; } } else { key->ofs_lf = newlfofs - 0x1000; } free(newlf); return 0; error_not_nk: LOG("del_key: current pointer not 'nk'\n"); return 1; error_no_subkeys: LOG("del_key: key has no subkeys\n"); return 1; error_index: LOG("del_key: index other than 'lf', 'li' or 'lh' not supported: 0x%04x\n", oldlf->id); return 1; error_subkey_not_found: LOG("del_key: subkey not found: %s\n", name); free(newlf); free(newli); return 1; error_not_empty: LOG("del_key: subkey %s has subkeys or values. Not deleted.\n",name); free(newlf); free(newli); return 1; error_alloc_key: free(newlf); LOG("del_key: not deleted: can't alloc space for new key descriptor for %s\n", name); return 1; error_newlfofs: LOG("del_key: error: newlfofs = %x\n", newlfofs); return 1; error_alloc_ri_index: free(newlf); free(newri); LOG("del_key: not deleted: unable to allocate space for ri-index for %s\n", name); return 1; error_dirty: LOG("del_key: mark_pages_dirty returned an error\n"); return 1; error_fill_block: LOG("del_key: fill_block failed\n"); return 1; } /* Write to registry value. * If same size as existing, copy back in place to avoid changing too much * otherwise allocate new dataspace, then free the old * Thus enough space to hold both new and old data is needed * Pass in buffer with data len as first DWORD (as routines above) * returns: 0 - error, len - OK (len of data) */ int put_buf2val(struct hive *hdesc, struct keyval *kv, int vofs, char *path, int type, int exact) { int l; void *keydataptr, *addr; struct db_key *db; int32_t copylen, blockofs, blocksize, restlen, point, list, parts; int i; LOAD_WD_LOGONLY(); if (!kv) goto error_null_kv; l = get_val_len(hdesc, vofs, path, exact); if (l == -1) goto error_get_val_len; if (kv->len != l) { /* Realloc data block if not same size as existing */ if (!alloc_val_data(hdesc, vofs, path, kv->len, exact)) { LOG("put_buf2val: alloc_val_data failed: %s\n", path); return 0; } } keydataptr = get_val_data(hdesc, vofs, path, type, exact); if (!keydataptr) { LOG("put_buf2val: get_val_data failed: %s\n",path); return 0; } if (kv->len > VAL_DIRECT_LIMIT) { /* Where do the db indirects start? seems to be around 16k */ db = (struct db_key *)keydataptr; if (db->id != 0x6264) goto error_db_not_found; parts = db->no_part; list = db->ofs_data + 0x1004; LOG("put_buf2val: long value: parts %d, list %x\n", parts, list); point = 0; restlen = kv->len; for (i = 0; i < parts; i++) { blockofs = get_int32(hdesc->buffer + list + (i << 2)) + 0x1000; blocksize = -get_int32(hdesc->buffer + blockofs) - 8; /* Copy this part, up to size of block or rest length in last block */ copylen = (blocksize > restlen) ? restlen : blocksize; addr = (char *)(kv->data) + point; if(fill_block(hdesc, blockofs, addr, copylen)) goto error_fill_block; point += copylen; restlen -= copylen; } } else { memcpy(keydataptr, kv->data, kv->len); i = (uintptr_t)keydataptr - (uintptr_t)(hdesc->buffer); if (mark_pages_dirty(hdesc, i, i + kv->len)) return -1; } if(flush_dirty_pages(hdesc)) return 0; return (kv->len); error_fill_block: LOG("put_buf2val: fill_block failed\n"); return 0; error_db_not_found: LOG("put_buf2val: data block expected but not found: 0x%" PRIxPTR "\n", (intptr_t)db - (intptr_t)hdesc->buffer); return 0; error_get_val_len: LOG("put_buf2val: get_val_len failed for %s offset %d\n", path, vofs); return 0; error_null_kv: LOG("put_buf2val: NULL key value pointer given\n"); return 0; } /* And, yer basic DWORD write */ /*int put_dword(struct hive *hdesc, int vofs, char *path, int exact, int dword) { struct keyval *kr; int r; ALLOC(kr,1,16); kr->len = 4; *(kr->data) = dword; r = put_buf2val(hdesc, kr, vofs, path, REG_DWORD, exact); free(kr); return r; }*/ /* ================================================================ */ /* * converts a value string from an registry entry into a c string. It does not * use any encoding functions. * It works very primitive by just taking every second char. * The caller must free the resulting string, that was allocated with malloc. * * cstring: the destination string * string: string where every second char is \0 * len: length of the string * return: the converted string as char* */ void string_regw2prog(char * const restrict cstring, const void * const restrict string, int len) { int i, k; int out_len = 0; for (i = 0; i < len; i += 2) { unsigned v = ((const unsigned char *)string)[i] + ((const unsigned char *)string)[i+1] * 256u; if (v < 128) out_len += 1; else if (v < 0x800) out_len += 2; else out_len += 3; } for (i = 0, k = 0; i < len; i += 2) { unsigned v = ((const unsigned char *)string)[i] + ((const unsigned char *)string)[i+1] * 256u; if (v < 128) cstring[k++] = v; else if (v < 0x800) { cstring[k++] = 0xc0 | (v >> 6); cstring[k++] = 0x80 | (v & 0x3f); } else { cstring[k++] = 0xe0 | (v >> 12); cstring[k++] = 0x80 | ((v >> 6) & 0x3f); cstring[k++] = 0x80 | (v & 0x3f); } } cstring[out_len] = '\0'; return; } /* Utility functions used elsewhere in the library */ static char *string_prog2regw(void *string, int len, int *out_len) { unsigned char *regw = (unsigned char*) malloc(len*2+2); unsigned char *out = regw; unsigned char *in = (unsigned char*) string; for (;len>0; ++in, --len) { if (!(in[0] & 0x80)) { *out++ = *in; *out++ = 0; } else if ((in[0] & 0xe0) == 0xc0 && len >= 2) { *out++ = (in[0] & 0x1f) << 6 | (in[1] & 0x3f); *out++ = (in[0] & 0x1f) >> 2; ++in, --len; } else if (len >= 3) { /* assume 3 byte*/ *out++ = (in[1] & 0xf) << 6 | (in[2] & 0x3f); *out++ = (in[0] & 0xf) << 4 | ((in[1] & 0x3f) >> 2); in += 2; len -= 2; } } *out_len = out - regw; out[0] = out[1] = 0; return (char *) regw; } static inline int de_escape(char *s, int wide) { int src = 0; int dst = 0; if (wide) { while (*(s + src) || *(s + src +1)) { if (*(s + src) == '\\' && *(s + src + 1) == 0) src += 2; /* Skip over backslash */ *(s + dst) = *(s + src); *(s + dst + 1) = *(s + src + 1); dst += 2; src += 2; } *(s + dst) = 0; *(s + dst + 1) = 0; dst += 2; } else { while (*(s + src)) { if (*(s + src) == '\\') src++; *(s + dst) = *(s + src); dst++; src++; } *(s + dst) = 0; dst++; } return dst; } /* ================================================================ */ /* Hive control (load/save/close) etc */ void close_hive(struct hive *hdesc) { if (hdesc->state & HMODE_OPEN) { fclose(hdesc->fp); } free(hdesc->filename); free(hdesc->buffer); free(hdesc); } /* Compute checksum of REGF header page * hdesc = hive * returns checksum value, 32 bit int */ static inline int32_t calc_regfsum(struct hive * const hdesc) { int32_t checksum = 0; struct regf_header * restrict hdr; int i; hdr = (struct regf_header *) hdesc->buffer; for (i = 0; i < 0x1fc/4; ++i) checksum ^= ((int32_t *) hdr)[i]; return checksum; } /* Write the hive back to disk (only if dirty & not readonly) */ int write_hive(struct hive *hdesc) { struct regf_header *hdr; LOAD_WD_LOGONLY(); if (hdesc->state & HMODE_RO) goto error_ro; if (!(hdesc->state & HMODE_OPEN)) goto error_closed; /* Seek to beginning of file */ fseek(hdesc->fp, 0L, SEEK_SET); /* compute new checksum */ hdr = (struct regf_header *) hdesc->buffer; hdr->checksum = calc_regfsum(hdesc); if (mark_pages_dirty(hdesc, 0, 0)) goto error_dirty; if (flush_dirty_pages(hdesc)) goto error_flush; return 0; error_ro: LOG("write_hive: Hive is read-only, cannot write\n"); return 0; error_closed: LOG("write_hive: cannot write to closed hive: %s\n", hdesc->filename); return 1; error_dirty: LOG("write_hive: mark_pages_dirty returned an error\n"); return -1; error_flush: LOG("write_hive: flush_dirty_pages returned an error\n"); return -1; } struct hive *open_hive(char *filename, int mode) { struct hive *hdesc; int r,vofs; char fmode[] = "r+"; struct stat sbuf; uint32_t pofs; int32_t checksum; struct hbin_page *p = NULL; struct regf_header *hdr; struct nk_key *nk; struct ri_key *rikey; LOAD_WD_LOGONLY(); if (!filename || !*filename) { LOG("open_hive: Null or empty filename given\n"); return NULL; } CREATE(hdesc, struct hive, 1); hdesc->filename = str_dup(filename); hdesc->state = 0; hdesc->size = 0; hdesc->buffer = NULL; hdesc->dirty_entries = 0; /* Disable writing if read-only mode requested */ if (mode & HMODE_RO) fmode[1] = '\0'; if (stat(hdesc->filename, &sbuf)) { LOG("open_hive: cannot stat hive %s\n", hdesc->filename); return NULL; } hdesc->fp = fopen(hdesc->filename, fmode); if (hdesc->fp == NULL) { LOG("open_hive: failed: %s: %s; trying read-only\n",strerror(errno),hdesc->filename); fmode[1] = '\0'; mode |= HMODE_RO; hdesc->fp = fopen(hdesc->filename, fmode); if (hdesc->fp == NULL) { LOG("open_hive: read-only failed: %s: %s\n",strerror(errno),hdesc->filename); close_hive(hdesc); return NULL; } } hdesc->size = sbuf.st_size; hdesc->state = mode | HMODE_OPEN; /* Read the whole file */ ALLOC(hdesc->buffer, 1, hdesc->size); errno = 0; r = fread(hdesc->buffer, hdesc->size, 1, hdesc->fp); if (!r) { LOG("open_hive: read failed: %s: %s\n", strerror(errno), hdesc->filename); close_hive(hdesc); return NULL; } /* Now run through file, tallying all pages */ /* NOTE/KLUDGE: Assume first page starts at offset 0x1000 */ pofs = 0x1000; hdesc->pages = hdesc->size >> 12; hdr = (struct regf_header *)hdesc->buffer; if (hdr->id != 0x66676572) { LOG("open_hive: not a registry file: %s\n",filename); return hdesc; } checksum = calc_regfsum(hdesc); if (checksum != hdr->checksum) { LOG("open_hive: header checksum mismatch: calc %08x, hdr %08x\n", checksum, hdr->checksum); } hdesc->rootofs = hdr->ofs_rootkey + 0x1000; /* Cache the roots subkey index type (li,lf,lh) so we can use the correct * one when creating the first subkey in a key */ nk = (struct nk_key *)(hdesc->buffer + hdesc->rootofs + 4); if (nk->id == 0x6b6e) { rikey = (struct ri_key *)(hdesc->buffer + nk->ofs_lf + 0x1004); hdesc->nkindextype = rikey->id; if (hdesc->nkindextype == 0x6972) { /* Gee, big root, must check indirectly */ rikey = (struct ri_key *)(hdesc->buffer + rikey->hash[0].ofs_li + 0x1004); hdesc->nkindextype = rikey->id; } if (hdesc->nkindextype != 0x666c && hdesc->nkindextype != 0x686c && hdesc->nkindextype != 0x696c) { hdesc->nkindextype = 0x666c; } } else { LOG("open_hive: Root key is not a key (not of type 'nk')\n"); } while (pofs < (unsigned int)(hdr->filesize + 0x1000)) { /* Loop through hbins until end according to regf header */ p = (struct hbin_page *)(hdesc->buffer + pofs); if (p->id != 0x6E696268) { break; } if (p->ofs_next == 0) { LOG("open_hive: Corrupt file: zero-size page at %x\n", pofs); return hdesc; } vofs = pofs + 0x20; /* Skip page header, and run through blocks in hbin */ while ((int)(vofs - pofs) < p->ofs_next && vofs < hdesc->size) { vofs += parse_block(hdesc, vofs); } pofs += p->ofs_next; } /* hbin loop */ hdesc->endofs = hdr->filesize + 0x1000; hdesc->lastbin = pofs - p->ofs_next; /* Compensate for loop that added at end above */ return hdesc; } winregfs-0.7/ntreg.h000066400000000000000000000374421306056141200145040ustar00rootroot00000000000000/* * ntreg.h - NT Registry Hive access library, constants & structures * * NOTE: defines are not frozen. It can and will change every release. * ***** * * NTREG - Window registry file reader / writer library * Copyright (c) 1997-2013 Petter Nordahl-Hagen. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; * version 2.1 of the License. * * This library 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 * Lesser General Public License for more details. * See file LGPL.txt for the full license. * * Modified for Windows Registry FUSE filesystem project "winregfs" * by Jody Bruchon since 2014-04-16 * */ #ifndef NTREG_H #define NTREG_H #ifdef __cplusplus extern "C" { #endif #define KEY_ROOT 0x2c /* Type ID of ROOT key node */ #define KEY_NORMAL 0x20 /* Normal nk key */ #define ABSPATHLEN 4096 /* hbin page size. hbins are minimum this, and always multiple of this */ #define HBIN_PAGESIZE 0x1000 /* Hive filesize seems to always be multiple of this */ //#define REGF_FILEDIVISOR 0x40000 #define REGF_FILEDIVISOR 0x1000 /* Larger than this, and values seems split into several blocks */ #define VAL_DIRECT_LIMIT 0x3fd0 /* Datatypes of the values in the registry */ #define REG_NONE 0 /* No value type */ #define REG_SZ 1 /* Unicode nul terminated string */ #define REG_EXPAND_SZ 2 /* Unicode nul terminated string + env */ #define REG_BINARY 3 /* Free form binary */ #define REG_DWORD 4 /* 32-bit number */ #define REG_DWORD_BIG_ENDIAN 5 /* 32-bit number */ #define REG_LINK 6 /* Symbolic Link (unicode) */ #define REG_MULTI_SZ 7 /* Multiple Unicode strings */ #define REG_RESOURCE_LIST 8 /* Resource list in the resource map */ #define REG_FULL_RESOURCE_DESCRIPTOR 9 /* Resource list in the hardware description */ #define REG_RESOURCE_REQUIREMENTS_LIST 10 /* Uh? Rait.. */ #define REG_QWORD 11 /* Quad word 64 bit, little endian */ #define REG_MAX 12 /* The first page of the registry file is some kind of header, lot of * it's contents is unknown, and seems to be mostly NULLs anyway. * Note also, that this is the only place in the registry I've been * able to find _any_ kind of checksumming */ struct regf_header { int32_t id; /* 0x00000000 D-Word ID: ASCII-"regf" = 0x66676572 */ int32_t unknown1; /* 0x00000004 D-Word ???? Mount count */ int32_t unknown2; /* 0x00000008 D-Word ???? Always the same value as at 0x00000004 */ char timestamp[8]; /* 0x0000000C Q-Word last modify date in WinNT date-format */ int32_t unknown3; /* 0x00000014 D-Word 1 */ int32_t unknown4; /* 0x00000018 D-Word 3 - probably version #. 2 in NT3.51 */ int32_t unknown5; /* 0x0000001C D-Word 0 */ int32_t unknown6; /* 0x00000020 D-Word 1 */ int32_t ofs_rootkey; /* 0x00000024 D-Word Offset of 1st key record */ int32_t filesize; /* 0x00000028 D-Word Offset of first non-used data at end of file */ int32_t unknown7; /* 0x0000002C D-Word 1 */ char name[0x1fc-0x30]; /* 0x00000030 Seems like the hive's name is buried here, max len unknown */ int32_t checksum; /* 0x000001FC D-Word Xor sum of all D-Words from 0x00000000 to 0x000001FB */ }; /* The page header, I don't know if the 14 "dummy" bytes has a meaning, * they seem to be mostly NULLS */ struct hbin_page { int32_t id; /* 0x0000 D-Word ID: ASCII-"hbin" = 0x6E696268 */ int32_t ofs_self; /* 0x0004 D-Word Offset to itself, could be for sanity check */ int32_t ofs_next; /* 0x0008 D-Word Relative offset to next hbin (practically length of this one) */ char dummy1[14]; /* 0x14 to 0x001b may be timestamp in some windows versions, at least in first hbin */ int32_t len_page; /* 0x001C D-Word Block-size??? Don't look like it. Most often zero. */ int32_t firstlink; /* 0x0020 First data block likage */ /* char data[1]; 0x0020 First data block starts here */ }; /* Minimum block size utilized at end of hbin * Make routines accept 0 size block when at end */ #define HBIN_ENDFILL 0 /* Security descriptor. I know how it's linked, but don't know how the real security data is constructed, it may as well be like the higher level security structs defined by MS in its includes & NT docs. Currently, I have no use for it. Note that keys sharing the exact same security settings will most likely point to the same security descriptor, thus saving space and making it fast to make objects inherit settings (is inheritance supported? they speak of security inheritance as a "new" feature in the filesystem on NT5, even though I think it was also supported by the lower levels in the earlier versions) */ struct sk_key { int16_t id; /* 0x0000 Word ID: ASCII-"sk" = 0x6B73 */ int16_t dummy1; /* 0x0002 Word Unused */ int32_t ofs_prevsk; /* 0x0004 D-Word Offset of previous "sk"-Record */ int32_t ofs_nextsk; /* 0x0008 D-Word Offset of next "sk"-Record */ int32_t no_usage; /* 0x000C D-Word usage-counter */ int32_t len_sk; /* 0x0010 D-Word Size of "sk"-record in bytes */ char data[4]; /* Security data up to len_sk bytes */ }; /* This is the subkeylist/hash structure. NT4.0+. * ID + count, then count number of offset/4byte "hash". (not true hash) * Probably changed from the 3.x version to make it faster to * traverse the registry if you're looking for a specific name * (saves lookups in 'nk's that have the first 4 name chars different) */ struct lf_key { int16_t id; /* 0x0000 Word ID: ASCII-"lf" = 0x666C or "lh" = 0x686c */ int16_t no_keys; /* 0x0002 Word number of keys */ /* 0x0004 ???? Hash-Records */ union { struct lf_hash { int32_t ofs_nk; /* 0x0000 D-Word Offset of corresponding "nk"-Record */ char name[4]; /* 0x0004 D-Word ASCII: the first 4 characters of the key-name, */ } hash[1]; /* WinXP uses a more real hash instead (base 37 of uppercase name chars) */ /* padded with 0's. Case sensitiv! */ struct lh_hash { int32_t ofs_nk; /* 0x0000 D-Word Offset of corresponding "nk"-Record */ int32_t hash; /* 0x0004 D-Word ASCII: the first 4 characters of the key-name, */ } lh_hash[1]; } h; }; /* 3.x version of the above, contains only offset table, NOT * any start of names "hash". Thus needs 'nk' lookups for searches. */ struct li_key { int16_t id; /* 0x0000 Word ID: ASCII-"li" = 0x696C */ int16_t no_keys; /* 0x0002 Word number of keys */ /* 0x0004 ???? Hash-Records */ struct li_hash { int32_t ofs_nk; /* 0x0000 D-Word Offset of corresponding "nk"-Record */ } hash[1]; }; /* Indirect pointer list for value data, vk points to this instead of values data directly * Seems to be used when value data is large, maybe larger than 3-4k. */ struct db_key { int16_t id; /* 0x0000 Word ID: ASCII-"li" = 0x6462 */ int16_t no_part; /* 0x0002 Word number of data parts */ /* 0x0004 ???? Pointers to data */ int32_t ofs_data; /* 0x0000 D-Word Offset to list of data blocks */ /* Something else seems to follow here, 4 bytes at least */ /* and why not list the data block in here ???? why make another list?? */ }; /* This is a list of pointers to struct li_key, ie * an extention record if many li's. * This happens in NT4&5 when the lf hashlist grows larger * than about 400-500 entries/subkeys??, then the nk_key->ofs_lf points to this * instead of directly to an lf. * The sub-indices this points to seems to be li (yes!) in NT4 and 2k. * In XP and newer they point to lh which is more efficient. * Likely to happen in HKLM\Software\classes (file extention list) and * in SAM when many users. */ struct ri_key { int16_t id; /* 0x0000 Word ID: ASCII-"ri" = 0x6972 */ int16_t no_lis; /* 0x0002 Word number of pointers to li */ /* 0x0004 ???? Hash-Records */ struct ri_hash { int32_t ofs_li; /* 0x0000 D-Word Offset of corresponding "li"-Record */ } hash[1]; }; /* This is the value descriptor. * If the sign bit (31st bit) in the length field is set, the value is * stored inline this struct, and not in a seperate data chunk - * the data itself is then in the ofs_data field, happens for DWORD all the time * If the name size == 0, then the struct is probably cut short right * after the val_type or flag. * The flag meaning is rather unknown. */ struct vk_key { /* Offset Size Contents */ int16_t id; /* 0x0000 Word ID: ASCII-"vk" = 0x6B76 */ int16_t len_name; /* 0x0002 Word name length */ uint32_t len_data; /* 0x0004 D-Word length of the data */ int32_t ofs_data; /* 0x0008 D-Word Offset of Data */ int32_t val_type; /* 0x000C D-Word Type of value */ int16_t flag; /* 0x0010 Word Flag 0x1 ANSI encoding */ int16_t dummy1; /* 0x0012 Word Unused (data-trash) */ char keyname[1]; /* 0x0014 ???? Name */ }; /* This is the key node (ie directory) descriptor, can contain subkeys and/or values. * Note that for values, the count is stored here, but for subkeys * there's a count both here and in the offset-table (lf or li struct). * What happens if these mismatch is not known. * What's the classname thingy? Can't remember seeing that used in * anything I've looked at. */ struct nk_key { /* Offset Size Contents */ int16_t id; /* 0x0000 Word ID: ASCII-"nk" = 0x6B6E */ int16_t type; /* 0x0002 Word for the root-key: 0x2C, otherwise 0x20 */ /* 0x20 seems a flag for ANSI encoding */ /* 0x1000 is used in some places in Vista and newer*/ char timestamp[12]; /* 0x0004 Q-Word write-date/time in windows nt notation */ int32_t ofs_parent; /* 0x0010 D-Word Offset of Owner/Parent key */ int32_t no_subkeys; /* 0x0014 D-Word number of sub-Keys */ int32_t dummy1; int32_t ofs_lf; /* 0x001C D-Word Offset of the sub-key lf-Records */ int32_t dummy2; int32_t no_values; /* 0x0024 D-Word number of values */ int32_t ofs_vallist; /* 0x0028 D-Word Offset of the Value-List */ int32_t ofs_sk; /* 0x002C D-Word Offset of the sk-Record */ int32_t ofs_classnam; /* 0x0030 D-Word Offset of the Class-Name */ int32_t dummy3; /* 0x0034 unknown some of these may be used by vista */ int32_t dummy4; /* 0x0038 unknown and newer ?? */ int32_t dummy5; /* 0x003c unknown */ int32_t dummy6; /* 0x0040 unknown */ int32_t dummy7; /* 0x0044 unknown */ int16_t len_name; /* 0x0048 Word name-length */ int16_t len_classnam; /* 0x004A Word class-name length */ char keyname[1]; /* 0x004C ???? key-name */ }; /*********************************************************************************/ /* Structure defines for my routines */ struct ex_data { int nkoffs; struct nk_key *nk; char name[ABSPATHLEN]; }; struct vex_data { int vkoffs; struct vk_key *vk; int type; /* Value type REG_??? */ int size; /* Values size (normalized, inline accounted for) */ int32_t val; /* Actual value itself if type==REG_DWORD */ char name[ABSPATHLEN]; }; struct keyval { int len; /* Length of databuffer */ void *data; /* Modified for winregfs: use pointers, not inline data */ }; /* Types to trav_path() */ #define TPF_NK 0 #define TPF_VK 1 #define TPF_ABS 64 #define TPF_EXACT 128 #define TPF_VK_SHORT 256 /* To get type field instead of data field, used in SAM */ #define TPF_NK_EXACT (TPF_NK | TPF_EXACT) #define TPF_VK_EXACT (TPF_VK | TPF_EXACT) #define TPF_VK_ABS (TPF_VK | TPF_ABS) /* Name is literal, not a path */ /* Hive open modes */ #define HMODE_RW 0 #define HMODE_RO 0x1 #define HMODE_OPEN 0x2 #define HMODE_DIRTY 0x4 #define HMODE_NOALLOC 0x8 /* Don't allocate new blocks */ #define HMODE_NOEXPAND 0x10 /* Don't expand file with new hbin */ #define HMODE_DIDEXPAND 0x20 /* File has been expanded */ /* Suggested type of hive loaded, guessed by library, but not used by it */ #define HTYPE_UNKNOWN 0 #define HTYPE_SAM 1 #define HTYPE_SYSTEM 2 #define HTYPE_SECURITY 3 #define HTYPE_SOFTWARE 4 /* Maximum number of dirty pages before forced commit */ #define MAX_DIRTY 32 /* Hive definition, allocated by open_hive(), dealloc by close_hive() * contains state data, must be passed in all functions */ struct hive { char *filename; /* Hives filename */ FILE *fp; /* File pointer (only valid if state == OPEN) */ int state; /* Current state of hive */ int pages; /* Number of pages, total */ int useblk; /* Total # of used blocks */ int unuseblk; /* Total # of unused blocks */ int usetot; /* total # of bytes in useblk */ int unusetot; /* total # of bytes in unuseblk */ off_t size; /* Hives size (filesize) in bytes, incl regf header */ int rootofs; /* Offset of root-node */ int lastbin; /* Offset to last HBIN */ unsigned int endofs; /* Offset of first non HBIN page, we can expand from here */ int dirty[MAX_DIRTY];/* Dirty page list */ int dirty_entries; /* Number of dirty pages */ int16_t nkindextype; /* Subkey-indextype the root key uses */ char *buffer; /* Files raw contents */ }; /***************************************************/ /* Various nice macros */ #define CREATE(result, type, number)\ { \ if (!((result) = (type *) calloc ((number), sizeof(type)))) { \ abort(); \ } \ } #define ALLOC(result, size, number)\ { \ if (!((result) = (void *) calloc ((number), (size)))) { \ abort(); \ } \ } /******* Function prototypes **********/ void cheap_uni2ascii(const char *src, char *dest, int l); void cheap_ascii2uni(const char *src, char *dest, int l); int flush_dirty_pages(struct hive *hdesc); int parse_block(struct hive *hdesc, int vofs); int ex_next_n(const struct hive * const hdesc, int nkofs, int * const restrict count, int * const restrict countri, struct ex_data * const restrict sptr); int ex_next_v(struct hive *hdesc, int nkofs, int *count, struct vex_data *sptr); int get_abs_path(struct hive *hdesc, int nkofs, char *path, int maxlen); int trav_path(struct hive *hdesc, int vofs, const char * restrict path, int type); int get_val_type(struct hive *hdesc, int vofs, char *path, int exact); int set_val_type(struct hive *hdesc, int vofs, char *path, int exact, int type); int get_val_len(struct hive *hdesc, int vofs, char *path, int exact); void *get_val_data(struct hive *hdesc, int vofs, char *path, int val_type, int exact); struct keyval *get_val2buf(struct hive *hdesc, struct keyval *kv, int vofs, char *path, int type, int exact ); int get_dword(struct hive *hdesc, int vofs, char *path, int exact); int put_buf2val(struct hive *hdesc, struct keyval *kv, int vofs, char *path, int type, int exact ); int put_dword(struct hive *hdesc, int vofs, char *path, int exact, int dword); void export_key(struct hive *hdesc, int nkofs, char *name, char *filename, char *prefix); void close_hive(struct hive *hdesc); int write_hive(struct hive *hdesc); struct hive *open_hive(char *filename, int mode); struct vk_key *add_value(struct hive *hdesc, int nkofs, char *name, int type); int del_allvalues(struct hive *hdesc, int nkofs); int del_value(struct hive *hdesc, int nkofs, char *name); struct nk_key *add_key(struct hive *hdesc, int nkofs, char *name); int del_key(struct hive *hdesc, int nkofs, char *name); int add_bin(struct hive *hdesc, int size); void string_regw2prog(char * const restrict cstring, const void * const restrict string, int len); #ifdef __cplusplus } #endif #endif /* NTREG_H */ winregfs-0.7/version.h000066400000000000000000000003251306056141200150400ustar00rootroot00000000000000/* * Windows Registry FUSE Filesystem * * Version number definitions * */ #ifndef WINREGFS_VERSION_H #define WINREGFS_VERSION_H #define VER "0.7" #define VERDATE "2017-03-10" #endif /* WINREGFS_VERSION_H */ winregfs-0.7/winregfs.c000066400000000000000000001074151306056141200152020ustar00rootroot00000000000000/* * Windows Registry FUSE Filesystem * * Copyright (C) 2014-2017 Jody Bruchon * * Mounts a Windows registry hive file as a filesystem using FUSE * Registry keys become directories and values become files * Value files have an extension based on the value type * (blah.sz = REG_SZ string; blah.dw = REG_DWORD 32-bit number) * * Licensed under GNU GPL v2. See LICENSE and README for details. * */ #include #include #include #include #include #include #include #include #include #include #include #include #include "ntreg.h" #include "jody_hash.h" #include "jody_string.h" #include "winregfs.h" /* Avoid str[n]cmp calls by doing this simple check directly */ #define PATH_IS_ROOT(a) (a[0] == '/' && a[1] == '\0') /* Use Jody's block hash algorithm for the cache */ #define cache_hash(a) jody_block_hash((const hash_t *)a, 0, strlen(a)) /* Value type file extensions */ const char *ext[REG_MAX + 1] = { "none", "sz", "esz", "bin", "dw", "dwbe", "lnk", "msz", "reslist", "fullres", "res_req", "qw", NULL }; const char slash[] = "_SLASH_"; const int ss = sizeof(slash) - 1; /*** Non-FUSE helper functions ***/ /* NT time to UNIX time */ static time_t nttime_to_unixtime(char *timestamp) { int64_t newstamp; memcpy(&newstamp, timestamp, sizeof(int64_t)); newstamp /= 10000000LL; if (newstamp <= 11644473600LL) return 0; newstamp -= 11644473600LL; return newstamp; } /* Return offset to the first non-hexadecimal char in string */ static int find_nonhex(const char * const restrict string, int len) { unsigned char q; int offset; if (*string == 0) return -1; for (offset = 0; offset < len; offset++) { if (*(string + offset) == 0) return offset; q = *(string + offset) - 48; /* ASCII 0-9 to real 0-9 */ if (q > 48) q -= 39; if (q > 15) return offset; } return -1; } /* Convert hex string to integer */ static int convert_hex(const char * const restrict string, uint64_t * const restrict dest, int len) { unsigned char q; int offset; *dest = 0; if (*string == 0) return -1; for (offset = 0; offset < len; offset++) { if (*(string + offset) == 0) return 0; *dest <<= 4; /* Shift for each character processed */ q = *(string + offset) - 48; /* ASCII 0-9 to real 0-9 */ if (q > 48) q -= 39; if (q > 15) return -1; *dest += q; } return 0; } /* Convert value to hex string, return string length */ static int bytes2hex(char * const restrict string, const void * const restrict data, const int bytes) { int i, j = 0; unsigned char c; for (i = bytes - 1; i >= 0; i--) { c = *((const char *)data + i); string[j] = (c >> 4) + 48; if (string[j] > 57) string[j] += 39; j++; string[j] = (c & 15) + 48; if (string[j] > 57) string[j] += 39; j++; } string[j] = '\n'; j++; string[j] = '\0'; return (bytes << 1) + 2; } /* Remove extension and return numeric value for value type */ static int process_ext(char * const node) { char * restrict str_ext; int i = 0; str_ext = strrchr(node, '.'); /* Check for no-extension case */ if (str_ext == NULL) return -1; *str_ext = '\0'; str_ext++; for (; i < REG_MAX; i++) { if (!strcaseeq(str_ext, ext[i])) return i; } return -1; } /* Add the type extension to the registry value name */ static int add_val_ext(char * const restrict filename, const struct vex_data * const restrict vex) { LOAD_WD_LOGONLY(); if (vex->type > REG_MAX) { LOG("add_val_ext: error: value type out of range: %s\n", filename); return 1; } xstrcpy(filename, vex->name); strncat(filename, ".", ABSPATHLEN); strncat(filename, ext[vex->type], ABSPATHLEN); return 0; } /* Convert slashes to backslashes */ static inline void slash_fix(char * const restrict path) { int i; for (i = strlen(path); i >= 0; i--) { if (path[i] == '/') path[i] = '\\'; } } /* Forward slashes cannot appear in pathname components */ static int escape_fwdslash(char * const path) { int pos = 0; const char *p; char *q; char temp[ABSPATHLEN]; LOAD_WD_LOGONLY(); /* Avoid likely unnecessary work */ if (!strchr(path, '/')) return 0; p = path; q = temp; for (; pos < ABSPATHLEN; pos++) { if (*p == '/') { xstrcpy(q, slash); q += (ss - 1); pos = (pos + ss - 2); } else *q = *p; if (*p == '\0') break; q++; p++; } if (pos == ABSPATHLEN) { LOG("escape_fwdslash: maximum path length reached\n"); return -ENAMETOOLONG; } xstrcpy(path, temp); return 0; } /* Reverse escape_fwdslash */ static int unescape_fwdslash(char *path) { int pos = 0; const char *p; char *q; char temp[ABSPATHLEN]; LOAD_WD_LOGONLY(); /* Avoid likely unnecessary work */ if (!strchr(path, slash[0])) return 0; p = path; q = temp; for (; pos < ABSPATHLEN; pos++) { if (!strncmp(p, slash, ss)) { *q = '/'; p += (ss - 1); pos += (ss - 1); } else *q = *p; if (*p == '\0') break; q++; p++; } if (pos == ABSPATHLEN) { LOG("unescape_fwdslash: maximum path length reached\n"); return -ENAMETOOLONG; } xstrcpy(path, temp); return 0; } #if ENABLE_NKOFS_CACHE_STATS # if ENABLE_LOGGING /* Log current cache stats every 100th cache event */ static void log_nk_cache_stats(struct winregfs_data * const restrict wd) { float c, h; /* c, h are checked to prevent divide by zero errors */ if (wd->delay--) return; wd->delay = 100; c = wd->nk_cache_miss + wd->nk_cache_hit; h = wd->nk_hash_miss + wd->nk_hash_hit; LOG("cache: at pos %d, %d miss, %d hit (%3.2f%%); ", wd->nk_cache_pos, wd->nk_cache_miss, wd->nk_cache_hit, ((wd->nk_cache_hit * 100) / ((c > 0) ? c : 1))); LOG("hash: %d miss, %d hit (%3.2f%%), %d fail (%3.2f%%)\n", wd->nk_hash_miss, wd->nk_hash_hit, ((wd->nk_hash_hit * 100) / ((h > 0) ? h : 1)), wd->nk_hash_fail, ((wd->nk_hash_fail * 100) / ((h > 0) ? h : 1))); } # endif /* ENABLE_LOGGING */ /* Collect information on NK offset cache success/failure */ static void nk_cache_stats(struct winregfs_data * const restrict wd, char hit) { switch (hit) { case CACHE_HIT: wd->nk_cache_hit++; break; case CACHE_MISS: wd->nk_cache_miss++; break; case HASH_HIT: wd->nk_hash_hit++; break; case HASH_MISS: wd->nk_hash_miss++; break; case HASH_FAIL: wd->nk_hash_fail++; break; default: break; } # if ENABLE_LOGGING log_nk_cache_stats(wd); # endif } #endif /* NKOFS_CACHE_STATS */ /* Clear all cache elements (used when hive buffer is invalidated) */ void invalidate_nk_cache(void) { int i; LOAD_WD(); DLOG("winregfs cache invalidated\n"); LOCK(); for (i=0; i < NKOFS_CACHE_ITEMS; i++) wd->nk_hash[i] = '\0'; UNLOCK(); return; } /* Caching offset fetcher. If update_cache is nonzero, the * function call will refresh the cache entry for the path * and stop (useful for things that modify directories) */ static int get_path_nkofs(struct winregfs_data * const restrict wd, const char * const restrict keypath, struct nk_key ** const key, int update_cache) { int nkofs; #if ENABLE_NKOFS_CACHE int i; hash_t hash; /* Check the cached path to avoid extra traversals */ hash = cache_hash(keypath); LOCK(); /* Work backwards in the hash cache ring until we come back * where we started or encounter a zeroed (non-existent) hash */ i = wd->nk_cache_pos; while (1) { if (!wd->nk_hash[i]) break; /* 0 = end of recorded hashes */ if (wd->nk_hash[i] == hash) { nk_cache_stats(wd, HASH_HIT); if (!strcaseeq(wd->nk_last_path[i], keypath)) { if (!update_cache) { nkofs = wd->nk_last_nkofs[i]; *key = wd->nk_last_key[i]; nk_cache_stats(wd, CACHE_HIT); UNLOCK(); return nkofs; } else { nkofs = trav_path(wd->hive, 0, keypath, TPF_NK_EXACT); if (!nkofs) { LOG("get_path_nkofs: trav_path failed: %s\n", keypath); return 0; } nkofs += 4; wd->nk_last_key[i] = (struct nk_key *)(wd->hive->buffer + nkofs); wd->nk_last_nkofs[i] = nkofs; UNLOCK(); return nkofs; } } else { nk_cache_stats(wd, HASH_FAIL); } } else { nk_cache_stats(wd, HASH_MISS); } if (update_cache) return 0; /* If we've hit item 0, return the cache ring position to the end of the ring */ if (!i) i = NKOFS_CACHE_ITEMS; i--; if (i == wd->nk_cache_pos) break; } UNLOCK(); nk_cache_stats(wd, CACHE_MISS); #endif /* NKOFS_CACHE */ /* Cached path didn't match (or cache disabled), traverse and get offset */ nkofs = trav_path(wd->hive, 0, keypath, TPF_NK_EXACT); if (!nkofs) { LOG("get_path_nkofs: trav_path failed: %s\n", keypath); return 0; } nkofs += 4; *key = (struct nk_key *)(wd->hive->buffer + nkofs); #if ENABLE_NKOFS_CACHE /* Increment cache ring position, place new cache item */ LOCK(); if (++wd->nk_cache_pos >= NKOFS_CACHE_ITEMS) wd->nk_cache_pos = 0; xstrcpy(wd->nk_last_path[wd->nk_cache_pos], keypath); wd->nk_last_nkofs[wd->nk_cache_pos] = nkofs; wd->nk_last_key[wd->nk_cache_pos] = *key; wd->nk_hash[wd->nk_cache_pos] = cache_hash(keypath); UNLOCK(); #endif return nkofs; } /* Converts a path to the required formats for keypath/nodepath usage */ static inline int sanitize_path(const char * const restrict path, char * const restrict keypath, char * const restrict node) { char temp[ABSPATHLEN]; xstrcpy(keypath, path); xstrcpy(temp, path); dirname(keypath); /* need to read the root key */ xstrcpy(node, basename(temp)); slash_fix(keypath); unescape_fwdslash(node); unescape_fwdslash(keypath); /* DLOG("sanitize_path: path %s, keypath %s, node %s\n", path, keypath, node); */ return 0; } /*** End helper functions ***/ /*** FUSE functions ***/ /* Check if access to a particular file is allowed */ static int winregfs_access(const char * const restrict path, int mode) { struct nk_key *key; int nkofs; struct ex_data ex; struct vex_data vex; int count = 0, countri = 0; char filename[ABSPATHLEN]; char keypath[ABSPATHLEN]; char node[ABSPATHLEN]; LOAD_WD(); DLOG("access: %s (%d)\n", path, mode); /* Read-only support (possible future feature) */ /* if (mode & W_OK) { LOG("access: write requested for read-only filesystem\n"); errno = EROFS; return -1; } */ if (PATH_IS_ROOT(path)) return 0; sanitize_path(path, keypath, node); nkofs = get_path_nkofs(wd, keypath, &key, 0); if (!nkofs) { LOG("access: no offset: %s\n", keypath); return -ENOENT; } if (key->no_subkeys) { while (ex_next_n(wd->hive, nkofs, &count, &countri, &ex) > 0) { if (!strcaseeq(node, ex.name)) { DLOG("access: ex_n: %p size %d c %d cri %d\n", path, ex.nk->no_subkeys, count, countri); DLOG("access: directory OK: %s\n", node); return 0; } } } count = 0; if (key->no_values) { while (ex_next_v(wd->hive, nkofs, &count, &vex) > 0) { if (strlen(vex.name) == 0) xstrcpy(filename, "@.sz"); else add_val_ext(filename, &vex); if (!strcaseeq(node, filename)) { if (!(mode & X_OK)) { DLOG("access: OK: ex_v: nkofs %x vkofs %x size %d c %d\n", nkofs, vex.vkoffs, vex.size, count); return 0; } else { DLOG("access: exec not allowed: ex_v: nkofs %x vkofs %x size %d c %d\n", nkofs, vex.vkoffs, vex.size, count); return -EACCES; } } } } LOG("access: not found: %s\n", path); return -ENOENT; } /* Flush dirty data */ static int winregfs_flush(const char *path, struct fuse_file_info *fi) { LOAD_WD(); (void)path; (void)fi; DLOG("flush: writing dirty pages to disk\n"); return flush_dirty_pages(wd->hive); } /* Flush dirty data */ static int winregfs_fsync(const char *unused1, int unused2, struct fuse_file_info *fi) { LOAD_WD(); (void)unused1; (void)unused2; (void)fi; DLOG("fsync: writing dirty pages to disk\n"); return flush_dirty_pages(wd->hive); } /* Get file attributes; size is adjusted for conversions we perform transparently */ static int winregfs_getattr(const char * const restrict path, struct stat * const restrict stbuf) { struct nk_key *key; int nkofs; struct ex_data ex; struct vex_data vex; int count = 0, countri = 0; char filename[ABSPATHLEN]; char keypath[ABSPATHLEN]; char node[ABSPATHLEN]; char check1[ABSPATHLEN]; char check2[ABSPATHLEN]; char *token; unsigned int attrmask = 0777; LOAD_WD(); DLOG("getattr: %s\n", path); if (wd->ro) attrmask = 0555; if (PATH_IS_ROOT(path)) { stbuf->st_mode = S_IFDIR | (0777 & attrmask); stbuf->st_nlink = 2; stbuf->st_size = 1; stbuf->st_atime = 0; stbuf->st_mtime = 0; stbuf->st_ctime = 0; return 0; } sanitize_path(path, keypath, node); memset(stbuf, 0, sizeof(struct stat)); nkofs = get_path_nkofs(wd, keypath, &key, 0); if (!nkofs) { LOG("getattr: no offset: %s\n", keypath); return -ENOENT; } DLOG("getattr: key->no_subkeys = %d\n", key->no_subkeys); if (key->no_subkeys) { while (ex_next_n(wd->hive, nkofs, &count, &countri, &ex) > 0) { if (!strcaseeq(node, ex.name)) { stbuf->st_mode = S_IFDIR | (0777 & attrmask); stbuf->st_nlink = 2; stbuf->st_size = ex.nk->no_subkeys; stbuf->st_mtime = nttime_to_unixtime(ex.nk->timestamp); stbuf->st_ctime = stbuf->st_mtime; stbuf->st_atime = stbuf->st_mtime; DLOG("getattr: ex_n: %s size %d c %d cri %d\n", path, ex.nk->no_subkeys, count, countri); return 0; } } } count = 0; DLOG("getattr: key->no_values = %d\n", key->no_values); if (key->no_values) { while (ex_next_v(wd->hive, nkofs, &count, &vex) > 0) { if (strlen(vex.name) == 0) xstrcpy(filename, "@.sz"); else add_val_ext(filename, &vex); /* Wildcard accesses with no extension */ if (!strrchr(node, '.')) { xstrcpy(check1, node); xstrcpy(check2, filename); token = strrchr(check2, '.'); *token = '\0'; if (!strcaseeq(check1, check2)) { LOG("getattr: wildcard found for %s\n", path); goto getattr_wildcard; } else { continue; } } if (!strcaseeq(node, filename)) { getattr_wildcard: stbuf->st_mode = S_IFREG | (0666 & attrmask); stbuf->st_nlink = 1; switch (vex.type) { case REG_QWORD: stbuf->st_size = 17; break; case REG_DWORD: stbuf->st_size = 9; break; case REG_SZ: case REG_EXPAND_SZ: case REG_MULTI_SZ: stbuf->st_size = vex.size >> 1; break; default: stbuf->st_size = vex.size; } DLOG("getattr: ex_v: nkofs %x vkofs %x size %d c %d\n", nkofs, vex.vkoffs, vex.size, count); return 0; } /* Prevent creation of conflicting files */ xstrcpy(check1, node); token = strrchr(check1, '.'); *token = '\0'; xstrcpy(check2, filename); token = strrchr(check2, '.'); *token = '\0'; if (!strcaseeq(check1, check2)) { stbuf->st_mode = S_IFREG | 0000; stbuf->st_nlink = 1; stbuf->st_size = 0; DLOG("getattr: blocking file %s\n", path); return 0; } } } else { DLOG("getattr: no values for key: %p\n", (void *)key); } LOG("getattr: not found: %s\n", path); return -ENOENT; } static int winregfs_readdir(const char * const restrict path, void *buf, fuse_fill_dir_t filler, off_t offset, struct fuse_file_info *fi) { struct nk_key *key; int nkofs; struct ex_data ex; struct vex_data vex; int count = 0, countri = 0; char filename[ABSPATHLEN]; char keypath[ABSPATHLEN]; LOAD_WD(); DLOG("readdir: %s (+%d)\n", path, (int)offset); (void)fi; (void)offset; xstrcpy(keypath, path); slash_fix(keypath); unescape_fwdslash(keypath); nkofs = get_path_nkofs(wd, keypath, &key, 0); if (!nkofs) { LOG("readdir: get_path_nkofs failed: %s\n", keypath); return -ENOENT; } filler(buf, ".", NULL, 0); filler(buf, "..", NULL, 0); DLOG("readdir: key->no_subkeys = %d\n", key->no_subkeys); if (key->no_subkeys) { while (ex_next_n(wd->hive, nkofs, &count, &countri, &ex) > 0) { DLOG("readdir: n_filler: %s\n", ex.name); xstrcpy(filename, ex.name); escape_fwdslash(filename); filler(buf, filename, NULL, 0); } } count = 0; DLOG("readdir: key->no_values = %d\n", key->no_values); if (key->no_values) { while (ex_next_v(wd->hive, nkofs, &count, &vex) > 0) { if (strlen(vex.name) == 0) { xstrcpy(filename, "@.sz"); DLOG("readdir: v_filler: %s\n", filename); filler(buf, filename, NULL, 0); } else { if (!add_val_ext(filename, &vex)) { escape_fwdslash(filename); DLOG("readdir: v_filler: %s\n", filename); filler(buf, filename, NULL, 0); } else { LOG("readdir: error reading %s/%s\n", path, filename); } } } } return 0; } /* This really doesn't do anything now, but in the future we should * implement a proper open/write/release cycle and proper locking. * The chances of this code NEEDING this are very slim. Most users * are likely to access data in a very serialized manner. */ static int winregfs_open(const char * const restrict path, struct fuse_file_info *fi) { struct nk_key *key; int nkofs; struct ex_data ex; struct vex_data vex; int count = 0, countri = 0; char filename[ABSPATHLEN]; char keypath[ABSPATHLEN]; char node[ABSPATHLEN]; char check1[ABSPATHLEN]; char check2[ABSPATHLEN]; char *token; LOAD_WD(); DLOG("open: %s\n", path); (void)fi; /* Read-only support (possible future feature) */ /* if ((fi->flags & 3) != O_RDONLY) { LOG("open: Read-only: %s\n", path); return -EACCES; } */ if (PATH_IS_ROOT(path)) { LOG("open: Is a directory: %s\n", path); return -EISDIR; } sanitize_path(path, keypath, node); nkofs = get_path_nkofs(wd, keypath, &key, 0); if (!nkofs) { LOG("open: get_path_nkofs failed: %s\n", keypath); return -ENOENT; } if (key->no_subkeys) { while (ex_next_n(wd->hive, nkofs, &count, &countri, &ex) > 0) { if (!strcaseeq(node, ex.name)) { /* remove leading slash */ LOG("open: Is a directory: %s\n", node); return -EISDIR; } } } count = 0; if (key->no_values) { while (ex_next_v(wd->hive, nkofs, &count, &vex) > 0) { if (strlen(vex.name) == 0) xstrcpy(filename, "@.sz"); else add_val_ext(filename, &vex); /* Wildcard accesses with no extension */ if (!strrchr(node, '.')) { xstrcpy(check1, node); xstrcpy(check2, filename); token = strrchr(check2, '.'); *token = '\0'; if (!strcaseeq(check1, check2)) { LOG("open: wildcard found for %s\n", path); return 0; } else continue; } if (!strcaseeq(node, filename)) return 0; } } LOG("open: No such file or directory for %s\n", path); return -ENOENT; } static int winregfs_read(const char * const restrict path, char * const restrict buf, size_t size, off_t offset, struct fuse_file_info *fi) { int nkofs; char filename[ABSPATHLEN]; char node[ABSPATHLEN]; char keypath[ABSPATHLEN]; struct nk_key *key; void *data; int len; int i, type, count; char string[4096]; char *sptr = string; struct keyval *kv = NULL; struct vex_data vex; LOAD_WD(); DLOG("read: %s (%d + %d)\n", path, (int)size, (int)offset); (void)fi; sanitize_path(path, keypath, node); nkofs = get_path_nkofs(wd, keypath, &key, 0); if (!nkofs) goto error_nkofs; /* Extract type information, remove extension from name */ if (process_ext(node) < 0) { count = 0; if (key->no_values) { while (ex_next_v(wd->hive, nkofs, &count, &vex) > 0) { if (strlen(vex.name) == 0) xstrcpy(filename, "@"); else xstrcpy(filename, vex.name); if (!strcaseeq(node, filename)) goto read_wildcard; } } goto error_invalid_ext; } read_wildcard: type = get_val_type(wd->hive, nkofs, node, TPF_VK_EXACT); if (type == -1) goto error_no_value; len = get_val_len(wd->hive, nkofs, node, TPF_VK_EXACT); if (len < 0) goto error_read_value; kv = get_val2buf(wd->hive, NULL, nkofs, node, 0, TPF_VK_EXACT); if (!kv) goto error_read_value; data = (void *)(kv->data); switch (type) { case REG_SZ: case REG_EXPAND_SZ: case REG_MULTI_SZ: if (!len) break; /* UTF-16 to ASCII, nulls to newlines */ string_regw2prog(string, data, len); for (i = 0; i < (len >> 1) - 1; i++) { if (string[i] == 0) string[i] = '\n'; if (type == REG_SZ) break; } len = strlen(string) + 1; string[len - 1] = '\n'; break; case REG_QWORD: len = bytes2hex(string, data, 8); break; case REG_DWORD: len = bytes2hex(string, data, 4); break; default: LOG("read: Unknown type %d, treating as REG_BINARY\n", type); case REG_BINARY: sptr = data; break; } if (offset < len) { if (offset + size > (unsigned int)len) size = len - offset; memcpy(buf, sptr + offset, size); } else size = 0; free(kv->data); free(kv); return size; error_nkofs: LOG("read: get_path_nkofs failed: %s\n", keypath); return -ENOENT; error_invalid_ext: LOG("read: invalid type extension: %s\n", path); return -EINVAL; error_read_value: if (*node == '\0') xstrcpy(node, "@"); LOG("read: Can't read value '%s'\n", node); return -EINVAL; error_no_value: if (*node == '\0') xstrcpy(node, "@"); LOG("read: No such value '%s'\n", node); return -EINVAL; } /* This function makes it painfully clear how ugly dealing with the * registry can be. REG_MULTI_SZ data types are the worst thing ever * designed and a special place in Hell exists with that data type. * * In the future, this function has the potential to become more * efficient if the "read-to-write" behavior could be eliminated. * Right now, any write at an offset other than zero will require * reading the existing value into the buffer, updating it, then * writing it back, which is grossly inefficient but is also the only * way to do this easily (and it's still reasonably quick too.) * * I wrote this entire program with just read-only support in about * two days, but THIS function single-handedly ate three days of my * life for testing and debugging...and it's still not where I want * it to be. * * Writes are limited to 8192 bytes maximum because somewhere between * here and ntreg.c we're losing the data. ntreg will allocate all the * needed blocks and fill with zeros, and 8192 bytes of data will be * placed, followed by zeroes until the end. Since the chances of ever * needing to write such a large value size are very slim, I decided * to cap writes at 8192 bytes and worry about this much later in the * future. */ static int winregfs_write(const char * const restrict path, const char * const restrict buf, size_t size, off_t offset, struct fuse_file_info *fi) { int nkofs; char node[ABSPATHLEN]; char keypath[ABSPATHLEN]; struct nk_key *key; int i, type; int newsize; char *newbuf = NULL; char string[4096]; uint64_t val; /* DWORD/QWORD hex string value */ struct keyval * restrict kv = NULL; struct keyval * restrict newkv = NULL; LOAD_WD(); DLOG("write: %s (%d + %d)\n", path, (int)size, (int)offset); (void)fi; if (wd->ro) goto error_rofs; sanitize_path(path, keypath, node); /* Extract type information, remove extension from name */ type = process_ext(node); if (type < 0) goto error_invalid_type; if (*node == '@') *node = '\0'; nkofs = get_path_nkofs(wd, keypath, &key, 0); if (!nkofs) goto error_nkofs; kv = get_val2buf(wd->hive, NULL, nkofs, node, type, TPF_VK_EXACT); if (!kv) goto error_metadata; newsize = offset + size; if (newsize > 8192 || kv->len > 8192) goto error_8k_limit; switch (type) { case REG_DWORD: if (offset != 0 && kv->len > newsize) newsize = kv->len; if (offset > kv->len) goto error_long_write; i = find_nonhex(buf, 9); if (i < 1) goto error_bad_dword; i = convert_hex(buf, &val, i); if (i == -1) goto error_bad_dword; kv->len = 4; *((uint32_t *)kv->data) = (uint32_t)val; i = put_buf2val(wd->hive, kv, nkofs, node, type, TPF_VK_EXACT); if (!i) goto error_write; break; case REG_QWORD: if (offset != 0 && kv->len > newsize) newsize = kv->len; if (offset > kv->len) goto error_long_write; i = find_nonhex(buf, 17); if (i < 1) goto error_bad_qword; i = convert_hex(buf, &val, i); if (i == -1) goto error_bad_qword; kv->len = 8; *((uint64_t *)kv->data) = val; i = put_buf2val(wd->hive, kv, nkofs, node, type, TPF_VK_EXACT); if (!i) goto error_write; break; case REG_BINARY: if (offset != 0 && kv->len > newsize) newsize = kv->len; if (offset > kv->len) goto error_long_write; newkv = (struct keyval *)malloc(sizeof(struct keyval)); if (!newkv) goto error_oom; ALLOC(newkv->data, 1, newsize); if (!newkv->data) goto error_oom; memcpy(newkv->data, kv->data, kv->len); memcpy(((char *)newkv->data + offset), buf, size); newkv->len = newsize; i = put_buf2val(wd->hive, newkv, nkofs, node, type, TPF_VK_EXACT); free(newkv->data); free(newkv); free(kv->data); free(kv); if (!i) goto error_write; break; case REG_SZ: case REG_EXPAND_SZ: case REG_MULTI_SZ: /* Handling offsets as well as loading existing data makes this complex */ if (offset != 0 && (kv->len >> 1) > newsize) newsize = kv->len >> 1; if (offset > kv->len) goto error_long_write; newbuf = (char *)malloc(sizeof(char) * (newsize + 1)); if (!newbuf) goto error_oom; /* Copy old message into buffer if offset is specified */ if (offset != 0) { string_regw2prog(string, kv->data, kv->len); for (i = 0; i < (kv->len >> 1); i++) { if (string[i] == 0) string[i] = '\n'; if (type == REG_SZ) break; } memcpy(newbuf, string, kv->len >> 1); } newkv = (struct keyval *)malloc(sizeof(struct keyval)); if (!newkv) goto error_oom; /* Extra byte for MULTI_SZ null termination */ ALLOC(newkv->data, 1, (newsize + 1) << 1); memcpy((newbuf + offset), buf, size); *(newbuf + offset + size) = 0; newkv->len = newsize << 1; /* Truncate string at first newline */ if (type != REG_MULTI_SZ) { for (i = 0; i < newsize; i++) { if (newbuf[i] == '\n' || newbuf[i] == '\r') { newkv->len = (i + 1) << 1; break; } } } else { /* MULTI_SZ is icky. Newlines become nulls! */ for (i = 0; i < newsize; i++) { if (newbuf[i] == '\n' || newbuf[i] == '\r') newbuf[i] = '\0'; } } cheap_ascii2uni(newbuf, (char *)newkv->data, newsize); free(newbuf); i = put_buf2val(wd->hive, newkv, nkofs, node, type, TPF_VK_EXACT); if (i != newkv->len) goto error_short_write; free(newkv->data); free(newkv); free(kv->data); free(kv); if (!i) goto error_write; break; default: goto error_type_not_sup; break; } if (write_hive(wd->hive)) { LOG("write: error writing changes to hive\n"); return -EPERM; } return size; error_rofs: LOG("write: read-only filesystem\n"); return -EROFS; error_invalid_type: LOG("read: invalid type extension: %s\n", path); return -EINVAL; error_nkofs: LOG("read: get_path_nkofs failed: %s\n", keypath); return -ENOENT; error_metadata: LOG("write: metadata missing for %s\n", path); return -ENOENT; error_8k_limit: LOG("write: 8 KiB value size limit exceeded: %s\n", path); free(kv->data); free(kv); return -EFBIG; error_long_write: LOG("write: attempt to write beyond end of file: %s\n", path); free(kv->data); free(kv); return -EINVAL; error_bad_dword: LOG("write: bad DWORD file format: %s\n", path); free(kv->data); free(kv); return -EINVAL; error_write: LOG("write: error writing file: %s\n", path); free(kv->data); free(kv); return -EINVAL; error_bad_qword: LOG("write: bad QWORD file format: %s\n", path); free(kv->data); free(kv); return -EINVAL; error_oom: LOG("write: failed to allocate memory\n"); free(kv->data); free(kv); return -ENOMEM; error_short_write: LOG("write: short write: %s (%d/%d)\n", path, i, (int)newkv->len); free(newkv->data); free(newkv); free(kv->data); free(kv); return -ENOSPC; error_type_not_sup: LOG("write: type %d not supported: %s\n", type, path); free(kv->data); free(kv); return -EINVAL; } /* Create a new empty file (registry value) */ static int winregfs_mknod(const char * const restrict path, mode_t mode, dev_t dev) { struct nk_key *key; int nkofs, ktype; char keypath[ABSPATHLEN]; char node[ABSPATHLEN]; LOAD_WD(); DLOG("mknod: %s\n", path); (void)dev; if (wd->ro) { LOG("mknod: read-only filesystem\n"); return -EROFS; } /* There are quite a few errors to watch out for */ /* FUSE already handles the "already exists" case */ if (!(mode & S_IFREG)) { LOG("mknod: special files are not allowed\n"); return -EPERM; } if (PATH_IS_ROOT(path)) { LOG("mknod: no path specified\n"); return -EEXIST; } sanitize_path(path, keypath, node); ktype = process_ext(node); if (ktype < 0) { LOG("mknod: bad extension: %s\n", path); return -EPERM; } nkofs = get_path_nkofs(wd, keypath, &key, 0); if (!nkofs) { LOG("mknod: no offset: %s\n", keypath); return -ENOSPC; } if (!add_value(wd->hive, nkofs, node, ktype)) { LOG("mknod: error creating value: %s\n", path); return -ENOSPC; } if (write_hive(wd->hive)) { LOG("mknod: error writing changes to hive\n"); return -EPERM; } #if ENABLE_NKOFS_CACHE get_path_nkofs(wd, keypath, &key, 1); #endif return 0; } /* Remove a file (registry value) */ static int winregfs_unlink(const char * const restrict path) { struct nk_key *key; int nkofs; char node[ABSPATHLEN]; char keypath[ABSPATHLEN]; LOAD_WD(); DLOG("unlink: %s\n", path); if (wd->ro) { LOG("unlink: read-only filesystem\n"); return -EROFS; } sanitize_path(path, keypath, node); process_ext(node); nkofs = get_path_nkofs(wd, keypath, &key, 0); if (!nkofs) { LOG("unlink: no offset: %s\n", keypath); return -ENOENT; } if (del_value(wd->hive, nkofs, node)) { LOG("unlink: cannot delete: %s\n", path); return -ENOENT; } if (write_hive(wd->hive)) { LOG("unlink: error writing changes to hive\n"); return -EPERM; } #if ENABLE_NKOFS_CACHE get_path_nkofs(wd, keypath, &key, 1); #endif return 0; } /* Make a key (directory); creation mode is ignored */ static int winregfs_mkdir(const char * const restrict path, mode_t mode) { struct nk_key *key; int nkofs; char node[ABSPATHLEN]; char keypath[ABSPATHLEN]; LOAD_WD(); DLOG("mkdir: %s\n", path); (void)mode; if (wd->ro) { LOG("mkdir: read-only filesystem\n"); return -EROFS; } sanitize_path(path, keypath, node); nkofs = get_path_nkofs(wd, keypath, &key, 0); if (!nkofs) { LOG("mkdir: no offset: %s\n", keypath); return -ENOENT; } if (add_key(wd->hive, nkofs, node) == NULL) { LOG("mkdir: cannot add key: %s\n", path); return -ENOENT; } if (write_hive(wd->hive)) { LOG("mkdir: error writing changes to hive\n"); return -EPERM; } #if ENABLE_NKOFS_CACHE get_path_nkofs(wd, keypath, &key, 1); #endif return 0; } /* Remove a key (directory) */ static int winregfs_rmdir(const char * const restrict path) { struct nk_key *key; int nkofs; char node[ABSPATHLEN]; char keypath[ABSPATHLEN]; LOAD_WD(); DLOG("rmdir: %s\n", path); if (wd->ro) { LOG("rmdir: read-only filesystem\n"); return -EROFS; } sanitize_path(path, keypath, node); nkofs = get_path_nkofs(wd, keypath, &key, 0); if (!nkofs) { LOG("rmdir: no offset: %s\n", keypath); return -ENOENT; } if (del_key(wd->hive, nkofs, node)) { LOG("rmdir: cannot delete key: %s\n", path); return -ENOENT; } if (write_hive(wd->hive)) { LOG("rmdir: error writing changes to hive\n"); return -EPERM; } #if ENABLE_NKOFS_CACHE get_path_nkofs(wd, keypath, &key, 1); #endif return 0; } /* Timestamps not supported, just return success */ static int winregfs_utimens(const char * const restrict path, const struct timespec tv[2]) { LOAD_WD_LOGONLY(); (void)path; (void)tv; LOG("Called but not implemented: utimens\n"); return 0; } /* Truncate is stupid anyway */ static int winregfs_truncate(const char * const restrict path, off_t len) { LOAD_WD_LOGONLY(); (void)path; (void)len; LOG("Called but not implemented: truncate (len %d)\n", (int)len); return 0; } /* FUSE debugging placeholders for when things get "fun" */ /* static int winregfs_readlink(void) { LOAD_WD(); LOG("ERROR: Not implemented: readlink\n"); return -1; } static int winregfs_symlink(void) { LOAD_WD(); LOG("ERROR: Not implemented: symlink\n"); return -1; } static int winregfs_rename(void) { LOAD_WD(); LOG("ERROR: Not implemented: rename\n"); return -1; } static int winregfs_link(void) { LOAD_WD(); LOG("ERROR: Not implemented: link\n"); return -1; } static int winregfs_chmod(void) { LOAD_WD(); LOG("ERROR: Not implemented: chmod\n"); return -1; } static int winregfs_chown(void) { LOAD_WD(); LOG("ERROR: Not implemented: chown\n"); return -1; } static int winregfs_statfs(void) { LOAD_WD(); LOG("ERROR: Not implemented: statfs\n"); return -1; } static int winregfs_release(void) { LOAD_WD(); LOG("ERROR: Not implemented: release\n"); return -1; } static int winregfs_releasedir(void) { LOAD_WD(); LOG("ERROR: Not implemented: releasedir\n"); return -1; } static int winregfs_fsyncdir(void) { LOAD_WD(); LOG("ERROR: Not implemented: fsyncdir\n"); return -1; } static int winregfs_ftruncate(void) { LOAD_WD(); LOG("ERROR: Not implemented: ftruncate\n"); return -1; } static int winregfs_fgetattr(void) { LOAD_WD(); LOG("ERROR: Not implemented: fgetattr\n"); return -1; } */ /* Required for FUSE to use these functions */ static struct fuse_operations winregfs_oper = { .getattr = winregfs_getattr, /* .readlink = winregfs_readlink, */ .mknod = winregfs_mknod, .readdir = winregfs_readdir, .mkdir = winregfs_mkdir, .unlink = winregfs_unlink, .rmdir = winregfs_rmdir, /* .symlink = winregfs_symlink, .rename = winregfs_rename, .link = winregfs_link, .chmod = winregfs_chmod, .chown = winregfs_chown, */ .truncate = winregfs_truncate, .open = winregfs_open, .read = winregfs_read, .write = winregfs_write, .access = winregfs_access, .utimens = winregfs_utimens, /* .statfs = winregfs_statfs, */ .fsync = winregfs_fsync, .flush = winregfs_flush, /* .release = winregfs_release, .releasedir = winregfs_releasedir, .fsyncdir = winregfs_fsyncdir, .ftruncate = winregfs_ftruncate, .fgetattr = winregfs_fgetattr, .lock = winregfs_lock, */ }; int main(int argc, char *argv[]) { struct winregfs_data * restrict wd; char file[ABSPATHLEN]; int i; /* Show version and return successfully if requested */ if (argc == 2 && !strcaseeq(argv[1], "-v")) { fprintf(stderr, "Windows Registry Filesystem %s (%s)\n", VER, VERDATE); return EXIT_SUCCESS; } if ((argc < 3) || (argv[argc-2][0] == '-') || (argv[argc-1][0] == '-')) { fprintf(stderr, "Windows Registry Filesystem %s (%s)\n", VER, VERDATE); fprintf(stderr, "\nUsage: %s [-o ro] [fuse_options] hivename mountpoint\n\n", argv[0]); return EXIT_FAILURE; } /* Pull hive file name from command line and pass to FUSE */ xstrcpy(file, argv[argc-2]); #if ENABLE_THREADED argv[argc-2] = argv[argc-1]; argv[argc-1] = NULL; argc--; #else /* Add single-threaded mode switch "-s" to args */ argv[argc-2] = (char *) malloc(3); if (!argv[argc-2]) goto oom; xstrcpy(argv[argc-2], "-s"); #endif wd = (struct winregfs_data *) malloc(sizeof(struct winregfs_data)); if (!wd) goto oom; /* Set read-only mode if applicable */ wd->ro = 0; if (argc > 4 && !strncmp(argv[1], "-o", 2)&& !strncmp(argv[2], "ro", 2)) wd->ro = 1; #if ENABLE_LOGGING wd->log = fopen("debug.log", "w"); if (!wd->log) { fprintf(stderr, "Error: couldn't open log file\n"); return EXIT_FAILURE; } #endif #if ENABLE_NKOFS_CACHE /* malloc() and initialize cache pointers/data */ wd->nk_last_path[0] = (char *)malloc(sizeof(char) * ABSPATHLEN * NKOFS_CACHE_ITEMS); if (!wd->nk_last_path[0]) goto oom; for (i = 0; i < NKOFS_CACHE_ITEMS; i++) { wd->nk_last_path[i] = (wd->nk_last_path[0] + (ABSPATHLEN * i)); *wd->nk_last_path[i] = '\0'; wd->nk_hash[i] = 0; } # if ENABLE_THREADED wd->lock = (pthread_mutex_t *) malloc(sizeof(pthread_mutex_t)); if (!wd->lock) goto oom; pthread_mutex_init(wd->lock, NULL); # endif /* THREADED */ wd->nk_cache_pos = NKOFS_CACHE_ITEMS - 1; # if ENABLE_NKOFS_CACHE_STATS wd->nk_cache_miss = 0; wd->nk_cache_hit = 0; wd->nk_hash_miss = 0; wd->nk_hash_hit = 0; wd->nk_hash_fail = 0; wd->delay = 1; # endif #endif /* NKOFS_CACHE */ if (!wd->ro) wd->hive = open_hive(file, HMODE_RW); else wd->hive = open_hive(file, HMODE_RO); if (!wd->hive) { fprintf(stderr, "Error: couldn't open %s\n", file); return EXIT_FAILURE; } #if ENABLE_LOGGING LOG("winregfs %s (%s) started for hive %s\n", VER, VERDATE, file); #endif i = fuse_main(argc, argv, &winregfs_oper, wd); close_hive(wd->hive); #if ENABLE_LOGGING LOG("winregfs terminated OK\n"); fclose(wd->log); #endif #if ENABLE_NKOFS_CACHE free(wd->nk_last_path[0]); # if ENABLE_THREADED free(wd->lock); # endif #endif free(wd); return i; oom: fprintf(stderr, "Error: out of memory\n"); return EXIT_FAILURE; } winregfs-0.7/winregfs.h000066400000000000000000000064411306056141200152040ustar00rootroot00000000000000/* * Windows Registry FUSE Filesystem * * Copyright (C) 2014-2017 by Jody Bruchon * * Licensed under GNU GPL v2. See LICENSE and README for details. * */ #ifndef WINREGFS_H #define WINREGFS_H #include "version.h" #ifndef FSCK_WINREGFS #define FUSE_USE_VERSION 26 #include #endif #include "jody_hash.h" #include "config.h" /*** Check config.h settings for sanity ***/ #if ENABLE_NKOFS_CACHE_STATS # if !ENABLE_NKOFS_CACHE # warning ENABLE_NKOFS_CACHE_STATS requires ENABLE_NKOFS_CACHE # undef ENABLE_NKOFS_CACHE # define ENABLE_NKOFS_CACHE 1 # endif #endif #if ENABLE_DEBUG_LOGGING # if !ENABLE_LOGGING # warning ENABLE_DEBUG_LOGGING requires ENABLE_LOGGING # undef ENABLE_LOGGING # define ENABLE_LOGGING 1 # endif #endif #if ENABLE_NKOFS_CACHE # if !NKOFS_CACHE_ITEMS # error ENABLE_NKOFS_CACHE enabled; NKOFS_CACHE_ITEMS must be set and non-zero # endif #endif #ifdef FSCK_WINREGFS #undef ENABLE_LOGGING #define ENABLE_LOGGING 0 #undef ENABLE_THREADED #define ENABLE_THREADED 0 #endif /*** End sanity checks ***/ #if ENABLE_NKOFS_CACHE_STATS #define CACHE_HIT 0 #define CACHE_MISS 1 #define HASH_HIT 2 #define HASH_MISS 3 #define HASH_FAIL 4 #else #define nk_cache_stats(a,b) #endif /* NKOFS_CACHE_STATS */ /* Data structures */ struct winregfs_data { struct hive *hive; int ro; #if ENABLE_LOGGING FILE *log; #endif #if ENABLE_NKOFS_CACHE /* Cache previous nkofs/path/key sets up to NKOFS_CACHE_ITEMS */ int nk_cache_pos; char *nk_last_path[NKOFS_CACHE_ITEMS]; int nk_last_nkofs[NKOFS_CACHE_ITEMS]; struct nk_key *nk_last_key[NKOFS_CACHE_ITEMS]; hash_t nk_hash[NKOFS_CACHE_ITEMS]; # if ENABLE_NKOFS_CACHE_STATS int delay; /* Cache log throttling interval */ int nk_cache_miss; int nk_cache_hit; int nk_hash_miss; int nk_hash_hit; int nk_hash_fail; # endif # if ENABLE_THREADED pthread_mutex_t *lock; # endif #endif }; /* Shortcut to pull winregfs_data structure into a function This MUST BE PLACED between variable declarations and code in ANY function that uses winregfs logging or data */ #define LOAD_WD() struct winregfs_data *wd; \ wd = fuse_get_context()->private_data; /* Enable/disable logging We check wd for non-NULL before logging since wd may be unallocated during startup before fuse_main() */ #if ENABLE_LOGGING # define LOG_IS_USED 1 # define LOG(...) if (wd) { \ fprintf(wd->log, __VA_ARGS__); fflush(wd->log); \ } else printf(__VA_ARGS__); # define LOAD_WD_LOGONLY() struct winregfs_data *wd; \ wd = fuse_get_context()->private_data; #else # define LOAD_WD_LOGONLY() # if ENABLE_DEBUG_PRINTF # define LOG_IS_USED 1 # define LOG(...) printf(__VA_ARGS__) # else # define LOG(...) # endif #endif /* Use DLOG for places where logging may be high-volume */ #if ENABLE_DEBUG_LOGGING # define LOG_IS_USED 1 # define DLOG(...) if (wd) { \ fprintf(wd->log, __VA_ARGS__); fflush(wd->log); \ } else printf(__VA_ARGS__); #else # if ENABLE_DEBUG_PRINTF # define LOG_IS_USED 1 # define DLOG(...) printf(__VA_ARGS__); # else # define DLOG(...) # endif #endif #ifndef LOG_IS_USED #define LOG_IS_USED 0 #endif /* Threaded mode mutex */ #if ENABLE_THREADED #define LOCK() pthread_mutex_lock(wd->lock) #define UNLOCK() pthread_mutex_unlock(wd->lock) #else #define LOCK() #define UNLOCK() #endif void invalidate_nk_cache(void); #endif /* WINREGFS_H */