mirror of
https://github.com/amnezia-vpn/win-split-tunnel.git
synced 2026-05-17 00:06:00 +03:00
Initial commit
This commit is contained in:
3
.gitignore
vendored
Normal file
3
.gitignore
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
/bin/
|
||||
.vs/
|
||||
*.user
|
||||
675
LICENSE.md
Normal file
675
LICENSE.md
Normal file
@@ -0,0 +1,675 @@
|
||||
### GNU GENERAL PUBLIC LICENSE
|
||||
|
||||
Version 3, 29 June 2007
|
||||
|
||||
Copyright (C) 2007 Free Software Foundation, Inc.
|
||||
<http://fsf.org/>
|
||||
|
||||
Everyone is permitted to copy and distribute verbatim copies of this
|
||||
license document, but changing it is not allowed.
|
||||
|
||||
### Preamble
|
||||
|
||||
The GNU General Public License is a free, copyleft license for
|
||||
software and other kinds of works.
|
||||
|
||||
The licenses for most software and other practical works are designed
|
||||
to take away your freedom to share and change the works. By contrast,
|
||||
the GNU General Public License is intended to guarantee your freedom
|
||||
to share and change all versions of a program--to make sure it remains
|
||||
free software for all its users. We, the Free Software Foundation, use
|
||||
the GNU General Public License for most of our software; it applies
|
||||
also to any other work released this way by its authors. You can apply
|
||||
it to your programs, too.
|
||||
|
||||
When we speak of free software, we are referring to freedom, not
|
||||
price. Our General Public Licenses are designed to make sure that you
|
||||
have the freedom to distribute copies of free software (and charge for
|
||||
them if you wish), that you receive source code or can get it if you
|
||||
want it, that you can change the software or use pieces of it in new
|
||||
free programs, and that you know you can do these things.
|
||||
|
||||
To protect your rights, we need to prevent others from denying you
|
||||
these rights or asking you to surrender the rights. Therefore, you
|
||||
have certain responsibilities if you distribute copies of the
|
||||
software, or if you modify it: responsibilities to respect the freedom
|
||||
of others.
|
||||
|
||||
For example, if you distribute copies of such a program, whether
|
||||
gratis or for a fee, you must pass on to the recipients the same
|
||||
freedoms that you received. You must make sure that they, too, receive
|
||||
or can get the source code. And you must show them these terms so they
|
||||
know their rights.
|
||||
|
||||
Developers that use the GNU GPL protect your rights with two steps:
|
||||
(1) assert copyright on the software, and (2) offer you this License
|
||||
giving you legal permission to copy, distribute and/or modify it.
|
||||
|
||||
For the developers' and authors' protection, the GPL clearly explains
|
||||
that there is no warranty for this free software. For both users' and
|
||||
authors' sake, the GPL requires that modified versions be marked as
|
||||
changed, so that their problems will not be attributed erroneously to
|
||||
authors of previous versions.
|
||||
|
||||
Some devices are designed to deny users access to install or run
|
||||
modified versions of the software inside them, although the
|
||||
manufacturer can do so. This is fundamentally incompatible with the
|
||||
aim of protecting users' freedom to change the software. The
|
||||
systematic pattern of such abuse occurs in the area of products for
|
||||
individuals to use, which is precisely where it is most unacceptable.
|
||||
Therefore, we have designed this version of the GPL to prohibit the
|
||||
practice for those products. If such problems arise substantially in
|
||||
other domains, we stand ready to extend this provision to those
|
||||
domains in future versions of the GPL, as needed to protect the
|
||||
freedom of users.
|
||||
|
||||
Finally, every program is threatened constantly by software patents.
|
||||
States should not allow patents to restrict development and use of
|
||||
software on general-purpose computers, but in those that do, we wish
|
||||
to avoid the special danger that patents applied to a free program
|
||||
could make it effectively proprietary. To prevent this, the GPL
|
||||
assures that patents cannot be used to render the program non-free.
|
||||
|
||||
The precise terms and conditions for copying, distribution and
|
||||
modification follow.
|
||||
|
||||
### TERMS AND CONDITIONS
|
||||
|
||||
#### 0. Definitions.
|
||||
|
||||
"This License" refers to version 3 of the GNU General Public License.
|
||||
|
||||
"Copyright" also means copyright-like laws that apply to other kinds
|
||||
of works, such as semiconductor masks.
|
||||
|
||||
"The Program" refers to any copyrightable work licensed under this
|
||||
License. Each licensee is addressed as "you". "Licensees" and
|
||||
"recipients" may be individuals or organizations.
|
||||
|
||||
To "modify" a work means to copy from or adapt all or part of the work
|
||||
in a fashion requiring copyright permission, other than the making of
|
||||
an exact copy. The resulting work is called a "modified version" of
|
||||
the earlier work or a work "based on" the earlier work.
|
||||
|
||||
A "covered work" means either the unmodified Program or a work based
|
||||
on the Program.
|
||||
|
||||
To "propagate" a work means to do anything with it that, without
|
||||
permission, would make you directly or secondarily liable for
|
||||
infringement under applicable copyright law, except executing it on a
|
||||
computer or modifying a private copy. Propagation includes copying,
|
||||
distribution (with or without modification), making available to the
|
||||
public, and in some countries other activities as well.
|
||||
|
||||
To "convey" a work means any kind of propagation that enables other
|
||||
parties to make or receive copies. Mere interaction with a user
|
||||
through a computer network, with no transfer of a copy, is not
|
||||
conveying.
|
||||
|
||||
An interactive user interface displays "Appropriate Legal Notices" to
|
||||
the extent that it includes a convenient and prominently visible
|
||||
feature that (1) displays an appropriate copyright notice, and (2)
|
||||
tells the user that there is no warranty for the work (except to the
|
||||
extent that warranties are provided), that licensees may convey the
|
||||
work under this License, and how to view a copy of this License. If
|
||||
the interface presents a list of user commands or options, such as a
|
||||
menu, a prominent item in the list meets this criterion.
|
||||
|
||||
#### 1. Source Code.
|
||||
|
||||
The "source code" for a work means the preferred form of the work for
|
||||
making modifications to it. "Object code" means any non-source form of
|
||||
a work.
|
||||
|
||||
A "Standard Interface" means an interface that either is an official
|
||||
standard defined by a recognized standards body, or, in the case of
|
||||
interfaces specified for a particular programming language, one that
|
||||
is widely used among developers working in that language.
|
||||
|
||||
The "System Libraries" of an executable work include anything, other
|
||||
than the work as a whole, that (a) is included in the normal form of
|
||||
packaging a Major Component, but which is not part of that Major
|
||||
Component, and (b) serves only to enable use of the work with that
|
||||
Major Component, or to implement a Standard Interface for which an
|
||||
implementation is available to the public in source code form. A
|
||||
"Major Component", in this context, means a major essential component
|
||||
(kernel, window system, and so on) of the specific operating system
|
||||
(if any) on which the executable work runs, or a compiler used to
|
||||
produce the work, or an object code interpreter used to run it.
|
||||
|
||||
The "Corresponding Source" for a work in object code form means all
|
||||
the source code needed to generate, install, and (for an executable
|
||||
work) run the object code and to modify the work, including scripts to
|
||||
control those activities. However, it does not include the work's
|
||||
System Libraries, or general-purpose tools or generally available free
|
||||
programs which are used unmodified in performing those activities but
|
||||
which are not part of the work. For example, Corresponding Source
|
||||
includes interface definition files associated with source files for
|
||||
the work, and the source code for shared libraries and dynamically
|
||||
linked subprograms that the work is specifically designed to require,
|
||||
such as by intimate data communication or control flow between those
|
||||
subprograms and other parts of the work.
|
||||
|
||||
The Corresponding Source need not include anything that users can
|
||||
regenerate automatically from other parts of the Corresponding Source.
|
||||
|
||||
The Corresponding Source for a work in source code form is that same
|
||||
work.
|
||||
|
||||
#### 2. Basic Permissions.
|
||||
|
||||
All rights granted under this License are granted for the term of
|
||||
copyright on the Program, and are irrevocable provided the stated
|
||||
conditions are met. This License explicitly affirms your unlimited
|
||||
permission to run the unmodified Program. The output from running a
|
||||
covered work is covered by this License only if the output, given its
|
||||
content, constitutes a covered work. This License acknowledges your
|
||||
rights of fair use or other equivalent, as provided by copyright law.
|
||||
|
||||
You may make, run and propagate covered works that you do not convey,
|
||||
without conditions so long as your license otherwise remains in force.
|
||||
You may convey covered works to others for the sole purpose of having
|
||||
them make modifications exclusively for you, or provide you with
|
||||
facilities for running those works, provided that you comply with the
|
||||
terms of this License in conveying all material for which you do not
|
||||
control copyright. Those thus making or running the covered works for
|
||||
you must do so exclusively on your behalf, under your direction and
|
||||
control, on terms that prohibit them from making any copies of your
|
||||
copyrighted material outside their relationship with you.
|
||||
|
||||
Conveying under any other circumstances is permitted solely under the
|
||||
conditions stated below. Sublicensing is not allowed; section 10 makes
|
||||
it unnecessary.
|
||||
|
||||
#### 3. Protecting Users' Legal Rights From Anti-Circumvention Law.
|
||||
|
||||
No covered work shall be deemed part of an effective technological
|
||||
measure under any applicable law fulfilling obligations under article
|
||||
11 of the WIPO copyright treaty adopted on 20 December 1996, or
|
||||
similar laws prohibiting or restricting circumvention of such
|
||||
measures.
|
||||
|
||||
When you convey a covered work, you waive any legal power to forbid
|
||||
circumvention of technological measures to the extent such
|
||||
circumvention is effected by exercising rights under this License with
|
||||
respect to the covered work, and you disclaim any intention to limit
|
||||
operation or modification of the work as a means of enforcing, against
|
||||
the work's users, your or third parties' legal rights to forbid
|
||||
circumvention of technological measures.
|
||||
|
||||
#### 4. Conveying Verbatim Copies.
|
||||
|
||||
You may convey verbatim copies of the Program's source code as you
|
||||
receive it, in any medium, provided that you conspicuously and
|
||||
appropriately publish on each copy an appropriate copyright notice;
|
||||
keep intact all notices stating that this License and any
|
||||
non-permissive terms added in accord with section 7 apply to the code;
|
||||
keep intact all notices of the absence of any warranty; and give all
|
||||
recipients a copy of this License along with the Program.
|
||||
|
||||
You may charge any price or no price for each copy that you convey,
|
||||
and you may offer support or warranty protection for a fee.
|
||||
|
||||
#### 5. Conveying Modified Source Versions.
|
||||
|
||||
You may convey a work based on the Program, or the modifications to
|
||||
produce it from the Program, in the form of source code under the
|
||||
terms of section 4, provided that you also meet all of these
|
||||
conditions:
|
||||
|
||||
- a) The work must carry prominent notices stating that you modified
|
||||
it, and giving a relevant date.
|
||||
- b) The work must carry prominent notices stating that it is
|
||||
released under this License and any conditions added under
|
||||
section 7. This requirement modifies the requirement in section 4
|
||||
to "keep intact all notices".
|
||||
- c) You must license the entire work, as a whole, under this
|
||||
License to anyone who comes into possession of a copy. This
|
||||
License will therefore apply, along with any applicable section 7
|
||||
additional terms, to the whole of the work, and all its parts,
|
||||
regardless of how they are packaged. This License gives no
|
||||
permission to license the work in any other way, but it does not
|
||||
invalidate such permission if you have separately received it.
|
||||
- d) If the work has interactive user interfaces, each must display
|
||||
Appropriate Legal Notices; however, if the Program has interactive
|
||||
interfaces that do not display Appropriate Legal Notices, your
|
||||
work need not make them do so.
|
||||
|
||||
A compilation of a covered work with other separate and independent
|
||||
works, which are not by their nature extensions of the covered work,
|
||||
and which are not combined with it such as to form a larger program,
|
||||
in or on a volume of a storage or distribution medium, is called an
|
||||
"aggregate" if the compilation and its resulting copyright are not
|
||||
used to limit the access or legal rights of the compilation's users
|
||||
beyond what the individual works permit. Inclusion of a covered work
|
||||
in an aggregate does not cause this License to apply to the other
|
||||
parts of the aggregate.
|
||||
|
||||
#### 6. Conveying Non-Source Forms.
|
||||
|
||||
You may convey a covered work in object code form under the terms of
|
||||
sections 4 and 5, provided that you also convey the machine-readable
|
||||
Corresponding Source under the terms of this License, in one of these
|
||||
ways:
|
||||
|
||||
- a) Convey the object code in, or embodied in, a physical product
|
||||
(including a physical distribution medium), accompanied by the
|
||||
Corresponding Source fixed on a durable physical medium
|
||||
customarily used for software interchange.
|
||||
- b) Convey the object code in, or embodied in, a physical product
|
||||
(including a physical distribution medium), accompanied by a
|
||||
written offer, valid for at least three years and valid for as
|
||||
long as you offer spare parts or customer support for that product
|
||||
model, to give anyone who possesses the object code either (1) a
|
||||
copy of the Corresponding Source for all the software in the
|
||||
product that is covered by this License, on a durable physical
|
||||
medium customarily used for software interchange, for a price no
|
||||
more than your reasonable cost of physically performing this
|
||||
conveying of source, or (2) access to copy the Corresponding
|
||||
Source from a network server at no charge.
|
||||
- c) Convey individual copies of the object code with a copy of the
|
||||
written offer to provide the Corresponding Source. This
|
||||
alternative is allowed only occasionally and noncommercially, and
|
||||
only if you received the object code with such an offer, in accord
|
||||
with subsection 6b.
|
||||
- d) Convey the object code by offering access from a designated
|
||||
place (gratis or for a charge), and offer equivalent access to the
|
||||
Corresponding Source in the same way through the same place at no
|
||||
further charge. You need not require recipients to copy the
|
||||
Corresponding Source along with the object code. If the place to
|
||||
copy the object code is a network server, the Corresponding Source
|
||||
may be on a different server (operated by you or a third party)
|
||||
that supports equivalent copying facilities, provided you maintain
|
||||
clear directions next to the object code saying where to find the
|
||||
Corresponding Source. Regardless of what server hosts the
|
||||
Corresponding Source, you remain obligated to ensure that it is
|
||||
available for as long as needed to satisfy these requirements.
|
||||
- e) Convey the object code using peer-to-peer transmission,
|
||||
provided you inform other peers where the object code and
|
||||
Corresponding Source of the work are being offered to the general
|
||||
public at no charge under subsection 6d.
|
||||
|
||||
A separable portion of the object code, whose source code is excluded
|
||||
from the Corresponding Source as a System Library, need not be
|
||||
included in conveying the object code work.
|
||||
|
||||
A "User Product" is either (1) a "consumer product", which means any
|
||||
tangible personal property which is normally used for personal,
|
||||
family, or household purposes, or (2) anything designed or sold for
|
||||
incorporation into a dwelling. In determining whether a product is a
|
||||
consumer product, doubtful cases shall be resolved in favor of
|
||||
coverage. For a particular product received by a particular user,
|
||||
"normally used" refers to a typical or common use of that class of
|
||||
product, regardless of the status of the particular user or of the way
|
||||
in which the particular user actually uses, or expects or is expected
|
||||
to use, the product. A product is a consumer product regardless of
|
||||
whether the product has substantial commercial, industrial or
|
||||
non-consumer uses, unless such uses represent the only significant
|
||||
mode of use of the product.
|
||||
|
||||
"Installation Information" for a User Product means any methods,
|
||||
procedures, authorization keys, or other information required to
|
||||
install and execute modified versions of a covered work in that User
|
||||
Product from a modified version of its Corresponding Source. The
|
||||
information must suffice to ensure that the continued functioning of
|
||||
the modified object code is in no case prevented or interfered with
|
||||
solely because modification has been made.
|
||||
|
||||
If you convey an object code work under this section in, or with, or
|
||||
specifically for use in, a User Product, and the conveying occurs as
|
||||
part of a transaction in which the right of possession and use of the
|
||||
User Product is transferred to the recipient in perpetuity or for a
|
||||
fixed term (regardless of how the transaction is characterized), the
|
||||
Corresponding Source conveyed under this section must be accompanied
|
||||
by the Installation Information. But this requirement does not apply
|
||||
if neither you nor any third party retains the ability to install
|
||||
modified object code on the User Product (for example, the work has
|
||||
been installed in ROM).
|
||||
|
||||
The requirement to provide Installation Information does not include a
|
||||
requirement to continue to provide support service, warranty, or
|
||||
updates for a work that has been modified or installed by the
|
||||
recipient, or for the User Product in which it has been modified or
|
||||
installed. Access to a network may be denied when the modification
|
||||
itself materially and adversely affects the operation of the network
|
||||
or violates the rules and protocols for communication across the
|
||||
network.
|
||||
|
||||
Corresponding Source conveyed, and Installation Information provided,
|
||||
in accord with this section must be in a format that is publicly
|
||||
documented (and with an implementation available to the public in
|
||||
source code form), and must require no special password or key for
|
||||
unpacking, reading or copying.
|
||||
|
||||
#### 7. Additional Terms.
|
||||
|
||||
"Additional permissions" are terms that supplement the terms of this
|
||||
License by making exceptions from one or more of its conditions.
|
||||
Additional permissions that are applicable to the entire Program shall
|
||||
be treated as though they were included in this License, to the extent
|
||||
that they are valid under applicable law. If additional permissions
|
||||
apply only to part of the Program, that part may be used separately
|
||||
under those permissions, but the entire Program remains governed by
|
||||
this License without regard to the additional permissions.
|
||||
|
||||
When you convey a copy of a covered work, you may at your option
|
||||
remove any additional permissions from that copy, or from any part of
|
||||
it. (Additional permissions may be written to require their own
|
||||
removal in certain cases when you modify the work.) You may place
|
||||
additional permissions on material, added by you to a covered work,
|
||||
for which you have or can give appropriate copyright permission.
|
||||
|
||||
Notwithstanding any other provision of this License, for material you
|
||||
add to a covered work, you may (if authorized by the copyright holders
|
||||
of that material) supplement the terms of this License with terms:
|
||||
|
||||
- a) Disclaiming warranty or limiting liability differently from the
|
||||
terms of sections 15 and 16 of this License; or
|
||||
- b) Requiring preservation of specified reasonable legal notices or
|
||||
author attributions in that material or in the Appropriate Legal
|
||||
Notices displayed by works containing it; or
|
||||
- c) Prohibiting misrepresentation of the origin of that material,
|
||||
or requiring that modified versions of such material be marked in
|
||||
reasonable ways as different from the original version; or
|
||||
- d) Limiting the use for publicity purposes of names of licensors
|
||||
or authors of the material; or
|
||||
- e) Declining to grant rights under trademark law for use of some
|
||||
trade names, trademarks, or service marks; or
|
||||
- f) Requiring indemnification of licensors and authors of that
|
||||
material by anyone who conveys the material (or modified versions
|
||||
of it) with contractual assumptions of liability to the recipient,
|
||||
for any liability that these contractual assumptions directly
|
||||
impose on those licensors and authors.
|
||||
|
||||
All other non-permissive additional terms are considered "further
|
||||
restrictions" within the meaning of section 10. If the Program as you
|
||||
received it, or any part of it, contains a notice stating that it is
|
||||
governed by this License along with a term that is a further
|
||||
restriction, you may remove that term. If a license document contains
|
||||
a further restriction but permits relicensing or conveying under this
|
||||
License, you may add to a covered work material governed by the terms
|
||||
of that license document, provided that the further restriction does
|
||||
not survive such relicensing or conveying.
|
||||
|
||||
If you add terms to a covered work in accord with this section, you
|
||||
must place, in the relevant source files, a statement of the
|
||||
additional terms that apply to those files, or a notice indicating
|
||||
where to find the applicable terms.
|
||||
|
||||
Additional terms, permissive or non-permissive, may be stated in the
|
||||
form of a separately written license, or stated as exceptions; the
|
||||
above requirements apply either way.
|
||||
|
||||
#### 8. Termination.
|
||||
|
||||
You may not propagate or modify a covered work except as expressly
|
||||
provided under this License. Any attempt otherwise to propagate or
|
||||
modify it is void, and will automatically terminate your rights under
|
||||
this License (including any patent licenses granted under the third
|
||||
paragraph of section 11).
|
||||
|
||||
However, if you cease all violation of this License, then your license
|
||||
from a particular copyright holder is reinstated (a) provisionally,
|
||||
unless and until the copyright holder explicitly and finally
|
||||
terminates your license, and (b) permanently, if the copyright holder
|
||||
fails to notify you of the violation by some reasonable means prior to
|
||||
60 days after the cessation.
|
||||
|
||||
Moreover, your license from a particular copyright holder is
|
||||
reinstated permanently if the copyright holder notifies you of the
|
||||
violation by some reasonable means, this is the first time you have
|
||||
received notice of violation of this License (for any work) from that
|
||||
copyright holder, and you cure the violation prior to 30 days after
|
||||
your receipt of the notice.
|
||||
|
||||
Termination of your rights under this section does not terminate the
|
||||
licenses of parties who have received copies or rights from you under
|
||||
this License. If your rights have been terminated and not permanently
|
||||
reinstated, you do not qualify to receive new licenses for the same
|
||||
material under section 10.
|
||||
|
||||
#### 9. Acceptance Not Required for Having Copies.
|
||||
|
||||
You are not required to accept this License in order to receive or run
|
||||
a copy of the Program. Ancillary propagation of a covered work
|
||||
occurring solely as a consequence of using peer-to-peer transmission
|
||||
to receive a copy likewise does not require acceptance. However,
|
||||
nothing other than this License grants you permission to propagate or
|
||||
modify any covered work. These actions infringe copyright if you do
|
||||
not accept this License. Therefore, by modifying or propagating a
|
||||
covered work, you indicate your acceptance of this License to do so.
|
||||
|
||||
#### 10. Automatic Licensing of Downstream Recipients.
|
||||
|
||||
Each time you convey a covered work, the recipient automatically
|
||||
receives a license from the original licensors, to run, modify and
|
||||
propagate that work, subject to this License. You are not responsible
|
||||
for enforcing compliance by third parties with this License.
|
||||
|
||||
An "entity transaction" is a transaction transferring control of an
|
||||
organization, or substantially all assets of one, or subdividing an
|
||||
organization, or merging organizations. If propagation of a covered
|
||||
work results from an entity transaction, each party to that
|
||||
transaction who receives a copy of the work also receives whatever
|
||||
licenses to the work the party's predecessor in interest had or could
|
||||
give under the previous paragraph, plus a right to possession of the
|
||||
Corresponding Source of the work from the predecessor in interest, if
|
||||
the predecessor has it or can get it with reasonable efforts.
|
||||
|
||||
You may not impose any further restrictions on the exercise of the
|
||||
rights granted or affirmed under this License. For example, you may
|
||||
not impose a license fee, royalty, or other charge for exercise of
|
||||
rights granted under this License, and you may not initiate litigation
|
||||
(including a cross-claim or counterclaim in a lawsuit) alleging that
|
||||
any patent claim is infringed by making, using, selling, offering for
|
||||
sale, or importing the Program or any portion of it.
|
||||
|
||||
#### 11. Patents.
|
||||
|
||||
A "contributor" is a copyright holder who authorizes use under this
|
||||
License of the Program or a work on which the Program is based. The
|
||||
work thus licensed is called the contributor's "contributor version".
|
||||
|
||||
A contributor's "essential patent claims" are all patent claims owned
|
||||
or controlled by the contributor, whether already acquired or
|
||||
hereafter acquired, that would be infringed by some manner, permitted
|
||||
by this License, of making, using, or selling its contributor version,
|
||||
but do not include claims that would be infringed only as a
|
||||
consequence of further modification of the contributor version. For
|
||||
purposes of this definition, "control" includes the right to grant
|
||||
patent sublicenses in a manner consistent with the requirements of
|
||||
this License.
|
||||
|
||||
Each contributor grants you a non-exclusive, worldwide, royalty-free
|
||||
patent license under the contributor's essential patent claims, to
|
||||
make, use, sell, offer for sale, import and otherwise run, modify and
|
||||
propagate the contents of its contributor version.
|
||||
|
||||
In the following three paragraphs, a "patent license" is any express
|
||||
agreement or commitment, however denominated, not to enforce a patent
|
||||
(such as an express permission to practice a patent or covenant not to
|
||||
sue for patent infringement). To "grant" such a patent license to a
|
||||
party means to make such an agreement or commitment not to enforce a
|
||||
patent against the party.
|
||||
|
||||
If you convey a covered work, knowingly relying on a patent license,
|
||||
and the Corresponding Source of the work is not available for anyone
|
||||
to copy, free of charge and under the terms of this License, through a
|
||||
publicly available network server or other readily accessible means,
|
||||
then you must either (1) cause the Corresponding Source to be so
|
||||
available, or (2) arrange to deprive yourself of the benefit of the
|
||||
patent license for this particular work, or (3) arrange, in a manner
|
||||
consistent with the requirements of this License, to extend the patent
|
||||
license to downstream recipients. "Knowingly relying" means you have
|
||||
actual knowledge that, but for the patent license, your conveying the
|
||||
covered work in a country, or your recipient's use of the covered work
|
||||
in a country, would infringe one or more identifiable patents in that
|
||||
country that you have reason to believe are valid.
|
||||
|
||||
If, pursuant to or in connection with a single transaction or
|
||||
arrangement, you convey, or propagate by procuring conveyance of, a
|
||||
covered work, and grant a patent license to some of the parties
|
||||
receiving the covered work authorizing them to use, propagate, modify
|
||||
or convey a specific copy of the covered work, then the patent license
|
||||
you grant is automatically extended to all recipients of the covered
|
||||
work and works based on it.
|
||||
|
||||
A patent license is "discriminatory" if it does not include within the
|
||||
scope of its coverage, prohibits the exercise of, or is conditioned on
|
||||
the non-exercise of one or more of the rights that are specifically
|
||||
granted under this License. You may not convey a covered work if you
|
||||
are a party to an arrangement with a third party that is in the
|
||||
business of distributing software, under which you make payment to the
|
||||
third party based on the extent of your activity of conveying the
|
||||
work, and under which the third party grants, to any of the parties
|
||||
who would receive the covered work from you, a discriminatory patent
|
||||
license (a) in connection with copies of the covered work conveyed by
|
||||
you (or copies made from those copies), or (b) primarily for and in
|
||||
connection with specific products or compilations that contain the
|
||||
covered work, unless you entered into that arrangement, or that patent
|
||||
license was granted, prior to 28 March 2007.
|
||||
|
||||
Nothing in this License shall be construed as excluding or limiting
|
||||
any implied license or other defenses to infringement that may
|
||||
otherwise be available to you under applicable patent law.
|
||||
|
||||
#### 12. No Surrender of Others' Freedom.
|
||||
|
||||
If conditions are imposed on you (whether by court order, agreement or
|
||||
otherwise) that contradict the conditions of this License, they do not
|
||||
excuse you from the conditions of this License. If you cannot convey a
|
||||
covered work so as to satisfy simultaneously your obligations under
|
||||
this License and any other pertinent obligations, then as a
|
||||
consequence you may not convey it at all. For example, if you agree to
|
||||
terms that obligate you to collect a royalty for further conveying
|
||||
from those to whom you convey the Program, the only way you could
|
||||
satisfy both those terms and this License would be to refrain entirely
|
||||
from conveying the Program.
|
||||
|
||||
#### 13. Use with the GNU Affero General Public License.
|
||||
|
||||
Notwithstanding any other provision of this License, you have
|
||||
permission to link or combine any covered work with a work licensed
|
||||
under version 3 of the GNU Affero General Public License into a single
|
||||
combined work, and to convey the resulting work. The terms of this
|
||||
License will continue to apply to the part which is the covered work,
|
||||
but the special requirements of the GNU Affero General Public License,
|
||||
section 13, concerning interaction through a network will apply to the
|
||||
combination as such.
|
||||
|
||||
#### 14. Revised Versions of this License.
|
||||
|
||||
The Free Software Foundation may publish revised and/or new versions
|
||||
of the GNU General Public License from time to time. Such new versions
|
||||
will be similar in spirit to the present version, but may differ in
|
||||
detail to address new problems or concerns.
|
||||
|
||||
Each version is given a distinguishing version number. If the Program
|
||||
specifies that a certain numbered version of the GNU General Public
|
||||
License "or any later version" applies to it, you have the option of
|
||||
following the terms and conditions either of that numbered version or
|
||||
of any later version published by the Free Software Foundation. If the
|
||||
Program does not specify a version number of the GNU General Public
|
||||
License, you may choose any version ever published by the Free
|
||||
Software Foundation.
|
||||
|
||||
If the Program specifies that a proxy can decide which future versions
|
||||
of the GNU General Public License can be used, that proxy's public
|
||||
statement of acceptance of a version permanently authorizes you to
|
||||
choose that version for the Program.
|
||||
|
||||
Later license versions may give you additional or different
|
||||
permissions. However, no additional obligations are imposed on any
|
||||
author or copyright holder as a result of your choosing to follow a
|
||||
later version.
|
||||
|
||||
#### 15. Disclaimer of Warranty.
|
||||
|
||||
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
|
||||
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
|
||||
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT
|
||||
WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT
|
||||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND
|
||||
PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE
|
||||
DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR
|
||||
CORRECTION.
|
||||
|
||||
#### 16. Limitation of Liability.
|
||||
|
||||
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
|
||||
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR
|
||||
CONVEYS THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
|
||||
INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES
|
||||
ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT
|
||||
NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR
|
||||
LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM
|
||||
TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER
|
||||
PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
|
||||
|
||||
#### 17. Interpretation of Sections 15 and 16.
|
||||
|
||||
If the disclaimer of warranty and limitation of liability provided
|
||||
above cannot be given local legal effect according to their terms,
|
||||
reviewing courts shall apply local law that most closely approximates
|
||||
an absolute waiver of all civil liability in connection with the
|
||||
Program, unless a warranty or assumption of liability accompanies a
|
||||
copy of the Program in return for a fee.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
### How to Apply These Terms to Your New Programs
|
||||
|
||||
If you develop a new program, and you want it to be of the greatest
|
||||
possible use to the public, the best way to achieve this is to make it
|
||||
free software which everyone can redistribute and change under these
|
||||
terms.
|
||||
|
||||
To do so, attach the following notices to the program. It is safest to
|
||||
attach them to the start of each source file to most effectively state
|
||||
the exclusion of warranty; and each file should have at least the
|
||||
"copyright" line and a pointer to where the full notice is found.
|
||||
|
||||
<one line to give the program's name and a brief idea of what it does.>
|
||||
Copyright (C) <year> <name of author>
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
Also add information on how to contact you by electronic and paper
|
||||
mail.
|
||||
|
||||
If the program does terminal interaction, make it output a short
|
||||
notice like this when it starts in an interactive mode:
|
||||
|
||||
<program> Copyright (C) <year> <name of author>
|
||||
This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
|
||||
This is free software, and you are welcome to redistribute it
|
||||
under certain conditions; type `show c' for details.
|
||||
|
||||
The hypothetical commands \`show w' and \`show c' should show the
|
||||
appropriate parts of the General Public License. Of course, your
|
||||
program's commands might be different; for a GUI interface, you would
|
||||
use an "about box".
|
||||
|
||||
You should also get your employer (if you work as a programmer) or
|
||||
school, if any, to sign a "copyright disclaimer" for the program, if
|
||||
necessary. For more information on this, and how to apply and follow
|
||||
the GNU GPL, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
The GNU General Public License does not permit incorporating your
|
||||
program into proprietary programs. If your program is a subroutine
|
||||
library, you may consider it more useful to permit linking proprietary
|
||||
applications with the library. If this is what you want to do, use the
|
||||
GNU Lesser General Public License instead of this License. But first,
|
||||
please read <http://www.gnu.org/philosophy/why-not-lgpl.html>.
|
||||
39
README.md
Normal file
39
README.md
Normal file
@@ -0,0 +1,39 @@
|
||||
# Mullvad split tunnel driver for Windows
|
||||
|
||||
This is a non-PnP KMDF driver suitable for implementing split tunneling in VPN client software. The driver works on Windows 7 through 10.
|
||||
|
||||
Main features:
|
||||
|
||||
- Exclude network traffic from VPN tunnel based on process paths.
|
||||
- Tracking of arriving and departing processes.
|
||||
- Atomic process classifications remove any races that could enable traffic leaks.
|
||||
- Propagation of exclusion flag to child processes.
|
||||
- Dynamic reconfiguration.
|
||||
- Blocking of pre-existing unwanted connections.
|
||||
- Blocking of IPv6 in cases where it would otherwise leak inside the tunnel.
|
||||
|
||||
# Development environment
|
||||
|
||||
Visual Studio 2019, any edition.
|
||||
|
||||
WDK, recent version.
|
||||
|
||||
# Architecture
|
||||
|
||||
The features mentioned above are wholly implemented in the driver. However, the driver needs a user mode agent to initially and continuously provide it with configuration data.
|
||||
|
||||
Specifically, the agent provides a set of application paths that should be excluded from the tunnel. It also communicates the tunnel IPs (IPv4/IPv6) as well as IPs of the primary network interface.
|
||||
|
||||
The agent is required to monitor network interfaces and update the driver with new IPs, as they change.
|
||||
|
||||
The code in `./testing` gives an example of building blocks needed in the agent. This code is mostly useful for manual testing. For an implementation that is more suited for production use, refer to relevant sections of the [Mullvad VPN app](https://github.com/mullvad/mullvadvpn-app)
|
||||
|
||||
# License
|
||||
|
||||
Copyright (C) 2021 Mullvad VPN AB
|
||||
|
||||
This program is free software: you can redistribute it and/or modify it under the terms of the
|
||||
GNU General Public License as published by the Free Software Foundation, either version 3 of
|
||||
the License, or (at your option) any later version.
|
||||
|
||||
For the full license agreement, see the LICENSE.md file
|
||||
108
build.bat
Normal file
108
build.bat
Normal file
@@ -0,0 +1,108 @@
|
||||
@echo off
|
||||
|
||||
if [%1]==[] goto USAGE
|
||||
|
||||
set CERT_THUMBPRINT=%1
|
||||
set CROSSCERT=digicert-high-assurance-ev.crt
|
||||
set TIMESTAMP_SERVER=http://timestamp.digicert.com
|
||||
|
||||
set ROOT=%~dp0
|
||||
|
||||
:: Register "x64 Native Tools" environment
|
||||
|
||||
call "C:\Program Files (x86)\Microsoft Visual Studio\2019\Community\VC\Auxiliary\Build\vcvars64.bat"
|
||||
|
||||
:: Build driver but do not sign it
|
||||
:: It's not possible to control all arguments to signtool through msbuild
|
||||
|
||||
msbuild.exe %ROOT%src\mullvad-split-tunnel.vcxproj /p:Configuration=Release /p:Platform=x64 /p:SignMode=Off
|
||||
|
||||
IF %ERRORLEVEL% NEQ 0 goto ERROR
|
||||
|
||||
:: Sign driver
|
||||
|
||||
signtool sign /tr %TIMESTAMP_SERVER% /td sha256 /fd sha256 /sha1 "%1" /v /ac %ROOT%resources\%CROSSCERT% %ROOT%bin\x64-Release\mullvad-split-tunnel\mullvad-split-tunnel.sys
|
||||
|
||||
IF %ERRORLEVEL% NEQ 0 goto ERROR
|
||||
|
||||
:: Re-generate catalog file now that driver binary has changed
|
||||
|
||||
del %ROOT%bin\x64-Release\mullvad-split-tunnel\mullvad-split-tunnel.cat
|
||||
"%WindowsSdkVerBinPath%x86\inf2cat.exe" /driver:%ROOT%bin\x64-Release\mullvad-split-tunnel /os:"7_x64" /verbose
|
||||
|
||||
IF %ERRORLEVEL% NEQ 0 goto ERROR
|
||||
|
||||
:: Sign catalog
|
||||
|
||||
signtool sign /tr %TIMESTAMP_SERVER% /td sha256 /fd sha256 /sha1 "%1" /v /ac %ROOT%resources\%CROSSCERT% %ROOT%bin\x64-Release\mullvad-split-tunnel\mullvad-split-tunnel.cat
|
||||
|
||||
IF %ERRORLEVEL% NEQ 0 goto ERROR
|
||||
|
||||
:: Copy artifacts
|
||||
|
||||
rmdir /s /q %ROOT%bin\dist
|
||||
|
||||
mkdir %ROOT%bin\dist\legacy
|
||||
copy /b %ROOT%bin\x64-Release\mullvad-split-tunnel\* %ROOT%bin\dist\legacy\
|
||||
|
||||
::
|
||||
:: Build a CAB file for submission to the MS Hardware Dev Center
|
||||
::
|
||||
|
||||
mkdir %ROOT%bin\dist\win10
|
||||
|
||||
>"%ROOT%bin\dist\win10\mullvad-split-tunnel-amd64.ddf" (
|
||||
echo .OPTION EXPLICIT ; Generate errors
|
||||
echo .Set CabinetFileCountThreshold=0
|
||||
echo .Set FolderFileCountThreshold=0
|
||||
echo .Set FolderSizeThreshold=0
|
||||
echo .Set MaxCabinetSize=0
|
||||
echo .Set MaxDiskFileCount=0
|
||||
echo .Set MaxDiskSize=0
|
||||
echo .Set CompressionType=MSZIP
|
||||
echo .Set Cabinet=on
|
||||
echo .Set Compress=on
|
||||
echo .Set CabinetNameTemplate=mullvad-split-tunnel-amd64.cab
|
||||
echo .Set DestinationDir=Package
|
||||
echo .Set DiskDirectoryTemplate=%ROOT%bin\dist\win10
|
||||
echo %ROOT%bin\dist\legacy\mullvad-split-tunnel.cat
|
||||
echo %ROOT%bin\dist\legacy\mullvad-split-tunnel.inf
|
||||
echo %ROOT%bin\dist\legacy\mullvad-split-tunnel.sys
|
||||
echo %ROOT%bin\dist\legacy\WdfCoinstaller01011.dll
|
||||
)
|
||||
|
||||
::
|
||||
:: makecab produces several garbage files
|
||||
:: force current working directory to prevent spreading them out
|
||||
::
|
||||
|
||||
pushd %ROOT%bin\dist\win10
|
||||
|
||||
makecab /f "%ROOT%bin\dist\win10\mullvad-split-tunnel-amd64.ddf"
|
||||
|
||||
popd
|
||||
|
||||
IF %ERRORLEVEL% NEQ 0 goto ERROR
|
||||
|
||||
signtool sign /tr %TIMESTAMP_SERVER% /td sha256 /fd sha256 /sha1 "%1" /v /ac %ROOT%resources\%CROSSCERT% %ROOT%bin\dist\win10\mullvad-split-tunnel-amd64.cab
|
||||
|
||||
IF %ERRORLEVEL% NEQ 0 goto ERROR
|
||||
|
||||
echo;
|
||||
echo BUILD COMPLETED SUCCESSFULLY
|
||||
echo;
|
||||
|
||||
exit /b 0
|
||||
|
||||
:USAGE
|
||||
|
||||
echo Usage: %0 ^<cert_sha1_hash^>
|
||||
exit /b 1
|
||||
|
||||
:ERROR
|
||||
|
||||
echo;
|
||||
echo !!! BUILD FAILED !!!
|
||||
echo;
|
||||
|
||||
exit /b 1
|
||||
30
resources/digicert-high-assurance-ev.crt
Normal file
30
resources/digicert-high-assurance-ev.crt
Normal file
@@ -0,0 +1,30 @@
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIFOzCCAyOgAwIBAgIKYSBNtAAAAAAAJzANBgkqhkiG9w0BAQUFADB/MQswCQYD
|
||||
VQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEe
|
||||
MBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSkwJwYDVQQDEyBNaWNyb3Nv
|
||||
ZnQgQ29kZSBWZXJpZmljYXRpb24gUm9vdDAeFw0xMTA0MTUxOTQ1MzNaFw0yMTA0
|
||||
MTUxOTU1MzNaMGwxCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMx
|
||||
GTAXBgNVBAsTEHd3dy5kaWdpY2VydC5jb20xKzApBgNVBAMTIkRpZ2lDZXJ0IEhp
|
||||
Z2ggQXNzdXJhbmNlIEVWIFJvb3QgQ0EwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAw
|
||||
ggEKAoIBAQDGzOVz5vvUu+UtLTKm3+WBP8nNJUm2cSrD1ZQ0Z6IKHLBfaaZAscS3
|
||||
so/QmKSpQVk609yU1jzbdDikSsxNJYL3SqVTEjju80ltcZF+Y7arpl/DpIT4T2JR
|
||||
vvjF7Ns4kuMG5QiRDMQoQVX7y1qJFX5x6DW/TXIJPb46OFBbdzEbjbPHJEWap6xt
|
||||
ABRaBLe6E+tRCphBQSJOZWGHgUFQpnlcid4ZSlfVLuZdHFMsfpjNGgYWpGhz0DQE
|
||||
E1yhcdNafFXbXmThN4cwVgTlEbQpgBLxeTmIogIRfCdmt4i3ePLKCqg4qwpkwr9m
|
||||
XZWEwaElHoddGlALIBLMQbtuC1E4uEvLAgMBAAGjgcswgcgwEQYDVR0gBAowCDAG
|
||||
BgRVHSAAMAsGA1UdDwQEAwIBhjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBSx
|
||||
PsNpA/i/RwHUmCYaCALvY2QrwzAfBgNVHSMEGDAWgBRi+wohW39DbhHaCVRQa/XS
|
||||
lnHxnjBVBgNVHR8ETjBMMEqgSKBGhkRodHRwOi8vY3JsLm1pY3Jvc29mdC5jb20v
|
||||
cGtpL2NybC9wcm9kdWN0cy9NaWNyb3NvZnRDb2RlVmVyaWZSb290LmNybDANBgkq
|
||||
hkiG9w0BAQUFAAOCAgEAIIzBWe1vnGstwUo+dR1FTEFQHL2A6tmwkosGKhM/Uxae
|
||||
VjlqimO2eCR59X24uUehCpbC9su9omafBuGs0nkJDv083KwCDHCvPxvseH7U60sF
|
||||
YCbZc2GRIe2waGPglxKrb6AS7dmf0tonPLPkVvnR1IEPcb1CfKaJ3M3VvZWiq/GT
|
||||
EX3orDEpqF1mcEGd/HXJ1bMaOSrQhQVQi6yRysSTy3GlnaSUb1gM+m4gxAgxtYWd
|
||||
foH50j3KWxiFbAqG7CIJG6V0NE9/KLyVSqsdtpiwXQmkd3Z+76eOXYT2GCTL0W2m
|
||||
w6GcwhB1gP+dMv3mz0M6gvfOj+FyKptit1/tlRo5XC+UbUi3AV8zL7vcLXM0iQRC
|
||||
ChyLefmj+hfv+qEaEN/gssGV61wMBZc7NT4YiE3bbL8kiY3Ivdifezk6JKDV39Hz
|
||||
ShqX9qZveh+wkKmzrAE5kdNht2TxPlc4A6/OetK1kPWu3DmZ1bY8l+2myxbHfWsq
|
||||
TJCU5kxU/R7NIOzOaJyHWOlhYL7rDsnVGX2f6Xi9DqwhdQePqW7gjGoqa5zj52W8
|
||||
vC08bdwE3GdFNjKvBIG8qABuYUyVxVzUjo6fL8EydL29EWUDB83vt14CV9qG1Boo
|
||||
NK+ISbLPpd2CVm9oqhTiWVT+/+ru7+qScCJggeMlI8CfzA9JsjWqWMM6w9kWlBA=
|
||||
-----END CERTIFICATE-----
|
||||
28
src/containers.h
Normal file
28
src/containers.h
Normal file
@@ -0,0 +1,28 @@
|
||||
#pragma once
|
||||
|
||||
#include <wdm.h>
|
||||
#include <wdf.h>
|
||||
#include "containers/procregistry.h"
|
||||
#include "containers/registeredimage.h"
|
||||
|
||||
//
|
||||
// The single instance of this struct lives in the device context.
|
||||
// But it has to be defined here so it can be shared with other components
|
||||
// in the system that should not be concerned with the full context.
|
||||
//
|
||||
struct PROCESS_REGISTRY_MGMT
|
||||
{
|
||||
WDFSPINLOCK Lock;
|
||||
procregistry::CONTEXT *Instance;
|
||||
};
|
||||
|
||||
//
|
||||
// Same deal as above.
|
||||
//
|
||||
// This instance is replaced from time to time hence wrapping it makes
|
||||
// for a better interface when sharing it.
|
||||
//
|
||||
struct REGISTERED_IMAGE_MGMT
|
||||
{
|
||||
registeredimage::CONTEXT * volatile Instance;
|
||||
};
|
||||
370
src/containers/procregistry.cpp
Normal file
370
src/containers/procregistry.cpp
Normal file
@@ -0,0 +1,370 @@
|
||||
#include <ntifs.h>
|
||||
#include "procregistry.h"
|
||||
#include "../util.h"
|
||||
|
||||
namespace procregistry
|
||||
{
|
||||
|
||||
struct CONTEXT
|
||||
{
|
||||
RTL_AVL_TABLE Tree;
|
||||
ST_PAGEABLE Pageable;
|
||||
};
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
RTL_GENERIC_COMPARE_RESULTS
|
||||
TreeCompareRoutine
|
||||
(
|
||||
__in struct _RTL_AVL_TABLE *Table,
|
||||
__in PVOID FirstStruct,
|
||||
__in PVOID SecondStruct
|
||||
)
|
||||
{
|
||||
UNREFERENCED_PARAMETER(Table);
|
||||
|
||||
auto first = ((PROCESS_REGISTRY_ENTRY*)FirstStruct)->ProcessId;
|
||||
auto second = ((PROCESS_REGISTRY_ENTRY*)SecondStruct)->ProcessId;
|
||||
|
||||
if (first < second)
|
||||
{
|
||||
return GenericLessThan;
|
||||
}
|
||||
|
||||
if (first > second)
|
||||
{
|
||||
return GenericGreaterThan;
|
||||
}
|
||||
|
||||
return GenericEqual;
|
||||
}
|
||||
|
||||
PVOID
|
||||
TreeAllocateRoutineNonPaged
|
||||
(
|
||||
__in struct _RTL_AVL_TABLE *Table,
|
||||
__in CLONG ByteSize
|
||||
)
|
||||
{
|
||||
UNREFERENCED_PARAMETER(Table);
|
||||
|
||||
return ExAllocatePoolWithTag(NonPagedPool, ByteSize, ST_POOL_TAG);
|
||||
}
|
||||
|
||||
PVOID
|
||||
TreeAllocateRoutinePaged
|
||||
(
|
||||
__in struct _RTL_AVL_TABLE *Table,
|
||||
__in CLONG ByteSize
|
||||
)
|
||||
{
|
||||
UNREFERENCED_PARAMETER(Table);
|
||||
|
||||
return ExAllocatePoolWithTag(PagedPool, ByteSize, ST_POOL_TAG);
|
||||
}
|
||||
|
||||
VOID
|
||||
TreeFreeRoutine
|
||||
(
|
||||
__in struct _RTL_AVL_TABLE *Table,
|
||||
__in PVOID Buffer
|
||||
)
|
||||
{
|
||||
UNREFERENCED_PARAMETER(Table);
|
||||
|
||||
ExFreePoolWithTag(Buffer, ST_POOL_TAG);
|
||||
}
|
||||
|
||||
//
|
||||
// ClearDepartingParentLink()
|
||||
//
|
||||
// `Entry` is an enumerated entry in the tree.
|
||||
// `Context` is an entry that's being removed from the tree.
|
||||
//
|
||||
// If `Entry` is a child of `Context` it needs to be updated to indicate that the parent process
|
||||
// is no longer available.
|
||||
//
|
||||
bool
|
||||
NTAPI
|
||||
ClearDepartingParentLink
|
||||
(
|
||||
PROCESS_REGISTRY_ENTRY *Entry,
|
||||
void *Context
|
||||
)
|
||||
{
|
||||
auto parentEntry = (PROCESS_REGISTRY_ENTRY*)Context;
|
||||
|
||||
if (Entry->ParentEntry == parentEntry)
|
||||
{
|
||||
NT_ASSERT(Entry->ParentProcessId == parentEntry->ProcessId);
|
||||
|
||||
Entry->ParentProcessId = 0;
|
||||
Entry->ParentEntry = NULL;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void
|
||||
InnerDeleteEntry
|
||||
(
|
||||
CONTEXT *Context,
|
||||
PROCESS_REGISTRY_ENTRY *Entry
|
||||
)
|
||||
{
|
||||
if (Entry->ImageName.Buffer != NULL)
|
||||
{
|
||||
util::FreeStringBuffer(&Entry->ImageName);
|
||||
}
|
||||
|
||||
RtlDeleteElementGenericTableAvl(&Context->Tree, Entry);
|
||||
}
|
||||
|
||||
} // anonymous namespace
|
||||
|
||||
NTSTATUS
|
||||
Initialize
|
||||
(
|
||||
CONTEXT **Context,
|
||||
ST_PAGEABLE Pageable
|
||||
)
|
||||
{
|
||||
const auto poolType = (Pageable == ST_PAGEABLE::YES) ? PagedPool : NonPagedPool;
|
||||
|
||||
*Context = (CONTEXT*)
|
||||
ExAllocatePoolWithTag(poolType, sizeof(CONTEXT), ST_POOL_TAG);
|
||||
|
||||
if (*Context == NULL)
|
||||
{
|
||||
return STATUS_INSUFFICIENT_RESOURCES;
|
||||
}
|
||||
|
||||
(*Context)->Pageable = Pageable;
|
||||
|
||||
const auto allocRoutine = (Pageable == ST_PAGEABLE::YES)
|
||||
? TreeAllocateRoutinePaged : TreeAllocateRoutineNonPaged;
|
||||
|
||||
RtlInitializeGenericTableAvl(&(*Context)->Tree, TreeCompareRoutine,
|
||||
allocRoutine, TreeFreeRoutine, NULL);
|
||||
|
||||
return STATUS_SUCCESS;
|
||||
}
|
||||
|
||||
void
|
||||
TearDown
|
||||
(
|
||||
CONTEXT **Context
|
||||
)
|
||||
{
|
||||
Reset(*Context);
|
||||
|
||||
ExFreePoolWithTag(*Context, ST_POOL_TAG);
|
||||
|
||||
*Context = NULL;
|
||||
}
|
||||
|
||||
void
|
||||
Reset
|
||||
(
|
||||
CONTEXT *Context
|
||||
)
|
||||
{
|
||||
for (;;)
|
||||
{
|
||||
auto entry = (PROCESS_REGISTRY_ENTRY*)RtlGetElementGenericTableAvl(&Context->Tree, 0);
|
||||
|
||||
if (NULL == entry)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
InnerDeleteEntry(Context, entry);
|
||||
}
|
||||
}
|
||||
|
||||
NTSTATUS
|
||||
InitializeEntry
|
||||
(
|
||||
CONTEXT *Context,
|
||||
HANDLE ParentProcessId,
|
||||
HANDLE ProcessId,
|
||||
ST_PROCESS_SPLIT_STATUS Split,
|
||||
UNICODE_STRING *ImageName,
|
||||
PROCESS_REGISTRY_ENTRY *Entry
|
||||
)
|
||||
{
|
||||
RtlZeroMemory(Entry, sizeof(*Entry));
|
||||
|
||||
if (ImageName != NULL
|
||||
&& ImageName->Length != 0)
|
||||
{
|
||||
LOWER_UNICODE_STRING lowerImageName;
|
||||
|
||||
auto status = util::AllocateCopyDowncaseString(ImageName, &lowerImageName, Context->Pageable);
|
||||
|
||||
if (!NT_SUCCESS(status))
|
||||
{
|
||||
return status;
|
||||
}
|
||||
|
||||
Entry->ImageName = lowerImageName;
|
||||
}
|
||||
|
||||
Entry->ParentProcessId = ParentProcessId;
|
||||
Entry->ProcessId = ProcessId;
|
||||
|
||||
static const PROCESS_REGISTRY_ENTRY_SETTINGS settings =
|
||||
{
|
||||
.Split = ST_PROCESS_SPLIT_STATUS_OFF,
|
||||
.HasFirewallState = false
|
||||
};
|
||||
|
||||
Entry->Settings = { Split, false };
|
||||
Entry->TargetSettings = settings;
|
||||
Entry->PreviousSettings = settings;
|
||||
|
||||
Entry->ParentEntry = NULL;
|
||||
|
||||
return STATUS_SUCCESS;
|
||||
}
|
||||
|
||||
NTSTATUS
|
||||
AddEntry
|
||||
(
|
||||
CONTEXT *Context,
|
||||
PROCESS_REGISTRY_ENTRY *Entry
|
||||
)
|
||||
{
|
||||
//
|
||||
// Insert entry into tree.
|
||||
// This makes a copy of the entry.
|
||||
//
|
||||
|
||||
BOOLEAN newElement;
|
||||
|
||||
auto record = RtlInsertElementGenericTableAvl(&Context->Tree, Entry, (CLONG)sizeof(*Entry), &newElement);
|
||||
|
||||
if (record != NULL && newElement != FALSE)
|
||||
{
|
||||
return STATUS_SUCCESS;
|
||||
}
|
||||
|
||||
//
|
||||
// Handle failure cases.
|
||||
//
|
||||
|
||||
if (record == NULL)
|
||||
{
|
||||
// Allocation of record failed.
|
||||
return STATUS_INSUFFICIENT_RESOURCES;
|
||||
}
|
||||
|
||||
// There's already a record for this PID.
|
||||
return STATUS_DUPLICATE_OBJECTID;
|
||||
}
|
||||
|
||||
void
|
||||
ReleaseEntry
|
||||
(
|
||||
PROCESS_REGISTRY_ENTRY *Entry
|
||||
)
|
||||
{
|
||||
if (Entry->ImageName.Buffer != NULL)
|
||||
{
|
||||
util::FreeStringBuffer(&Entry->ImageName);
|
||||
}
|
||||
}
|
||||
|
||||
PROCESS_REGISTRY_ENTRY*
|
||||
FindEntry
|
||||
(
|
||||
CONTEXT *Context,
|
||||
HANDLE ProcessId
|
||||
)
|
||||
{
|
||||
PROCESS_REGISTRY_ENTRY record = { 0 };
|
||||
|
||||
record.ProcessId = ProcessId;
|
||||
|
||||
return (PROCESS_REGISTRY_ENTRY*)RtlLookupElementGenericTableAvl(&Context->Tree, &record);
|
||||
}
|
||||
|
||||
void
|
||||
DeleteEntry
|
||||
(
|
||||
CONTEXT *Context,
|
||||
PROCESS_REGISTRY_ENTRY *Entry
|
||||
)
|
||||
{
|
||||
ForEach(Context, ClearDepartingParentLink, Entry);
|
||||
|
||||
InnerDeleteEntry(Context, Entry);
|
||||
}
|
||||
|
||||
void
|
||||
DeleteEntryById
|
||||
(
|
||||
CONTEXT *Context,
|
||||
HANDLE ProcessId
|
||||
)
|
||||
{
|
||||
auto entry = FindEntry(Context, ProcessId);
|
||||
|
||||
if (entry != NULL)
|
||||
{
|
||||
DeleteEntry(Context, entry);
|
||||
}
|
||||
}
|
||||
|
||||
bool
|
||||
ForEach
|
||||
(
|
||||
CONTEXT *Context,
|
||||
ST_PR_FOREACH Callback,
|
||||
void *ClientContext
|
||||
)
|
||||
{
|
||||
for (auto entry = RtlEnumerateGenericTableAvl(&Context->Tree, TRUE);
|
||||
entry != NULL;
|
||||
entry = RtlEnumerateGenericTableAvl(&Context->Tree, FALSE))
|
||||
{
|
||||
if (!Callback((PROCESS_REGISTRY_ENTRY*)entry, ClientContext))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
PROCESS_REGISTRY_ENTRY*
|
||||
GetParentEntry
|
||||
(
|
||||
CONTEXT *Context,
|
||||
PROCESS_REGISTRY_ENTRY *Entry
|
||||
)
|
||||
{
|
||||
if (NULL != Entry->ParentEntry)
|
||||
{
|
||||
return Entry->ParentEntry;
|
||||
}
|
||||
|
||||
if (0 == Entry->ParentProcessId)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return (Entry->ParentEntry = FindEntry(Context, Entry->ParentProcessId));
|
||||
}
|
||||
|
||||
bool
|
||||
IsEmpty
|
||||
(
|
||||
CONTEXT *Context
|
||||
)
|
||||
{
|
||||
return NULL == RtlEnumerateGenericTableAvl(&Context->Tree, TRUE);
|
||||
}
|
||||
|
||||
} // namespace procregistry
|
||||
162
src/containers/procregistry.h
Normal file
162
src/containers/procregistry.h
Normal file
@@ -0,0 +1,162 @@
|
||||
#pragma once
|
||||
|
||||
#include <ntddk.h>
|
||||
#include "../defs/types.h"
|
||||
|
||||
namespace procregistry
|
||||
{
|
||||
|
||||
struct PROCESS_REGISTRY_ENTRY_SETTINGS
|
||||
{
|
||||
// Whether traffic should be split.
|
||||
ST_PROCESS_SPLIT_STATUS Split;
|
||||
|
||||
// Whether the process is associated with any firewall filters.
|
||||
bool HasFirewallState;
|
||||
};
|
||||
|
||||
struct PROCESS_REGISTRY_ENTRY
|
||||
{
|
||||
HANDLE ParentProcessId;
|
||||
HANDLE ProcessId;
|
||||
|
||||
PROCESS_REGISTRY_ENTRY_SETTINGS Settings;
|
||||
|
||||
PROCESS_REGISTRY_ENTRY_SETTINGS TargetSettings;
|
||||
|
||||
PROCESS_REGISTRY_ENTRY_SETTINGS PreviousSettings;
|
||||
|
||||
// Device path using all lower-case characters.
|
||||
LOWER_UNICODE_STRING ImageName;
|
||||
|
||||
//
|
||||
// This is management data initialized and updated
|
||||
// by the implementation.
|
||||
//
|
||||
// It would be inconvenient to store it anywhere else.
|
||||
//
|
||||
PROCESS_REGISTRY_ENTRY *ParentEntry;
|
||||
};
|
||||
|
||||
struct CONTEXT;
|
||||
|
||||
NTSTATUS
|
||||
Initialize
|
||||
(
|
||||
CONTEXT **Context,
|
||||
ST_PAGEABLE Pageable
|
||||
);
|
||||
|
||||
void
|
||||
TearDown
|
||||
(
|
||||
CONTEXT **Context
|
||||
);
|
||||
|
||||
void
|
||||
Reset
|
||||
(
|
||||
CONTEXT *Context
|
||||
);
|
||||
|
||||
//
|
||||
// InitializeEntry()
|
||||
//
|
||||
// IRQL <= APC.
|
||||
//
|
||||
// Initializes `Entry` with provided values and initializes a buffer of
|
||||
// the correct backing and format for `Entry->ImageName.Buffer`.
|
||||
//
|
||||
// The provided `Entry` argument is typically allocated on the stack.
|
||||
//
|
||||
NTSTATUS
|
||||
InitializeEntry
|
||||
(
|
||||
CONTEXT *Context,
|
||||
HANDLE ParentProcessId,
|
||||
HANDLE ProcessId,
|
||||
ST_PROCESS_SPLIT_STATUS Split,
|
||||
UNICODE_STRING *ImageName,
|
||||
PROCESS_REGISTRY_ENTRY *Entry
|
||||
);
|
||||
|
||||
//
|
||||
// AddEntry()
|
||||
//
|
||||
// IRQL <= DISPATCH.
|
||||
//
|
||||
// On Success:
|
||||
//
|
||||
// The `Entry` argument will be copied and `Entry->ImageName.Buffer`
|
||||
// is taken ownership of.
|
||||
//
|
||||
// On failure:
|
||||
//
|
||||
// `Entry->ImageName.Buffer` is not taken ownership of.
|
||||
//
|
||||
NTSTATUS
|
||||
AddEntry
|
||||
(
|
||||
CONTEXT *Context,
|
||||
PROCESS_REGISTRY_ENTRY *Entry
|
||||
);
|
||||
|
||||
//
|
||||
// ReleaseEntry()
|
||||
//
|
||||
// Memory backing the imagename string buffer is allocated by InitializeEntry().
|
||||
//
|
||||
// Use this function to release an entry that could not be added, in order to
|
||||
// keep details abstracted.
|
||||
//
|
||||
void
|
||||
ReleaseEntry
|
||||
(
|
||||
PROCESS_REGISTRY_ENTRY *Entry
|
||||
);
|
||||
|
||||
PROCESS_REGISTRY_ENTRY*
|
||||
FindEntry
|
||||
(
|
||||
CONTEXT *Context,
|
||||
HANDLE ProcessId
|
||||
);
|
||||
|
||||
void
|
||||
DeleteEntry
|
||||
(
|
||||
CONTEXT *Context,
|
||||
PROCESS_REGISTRY_ENTRY *Entry
|
||||
);
|
||||
|
||||
void
|
||||
DeleteEntryById
|
||||
(
|
||||
CONTEXT *Context,
|
||||
HANDLE ProcessId
|
||||
);
|
||||
|
||||
typedef bool (NTAPI *ST_PR_FOREACH)(PROCESS_REGISTRY_ENTRY *Entry, void *Context);
|
||||
|
||||
bool
|
||||
ForEach
|
||||
(
|
||||
CONTEXT *Context,
|
||||
ST_PR_FOREACH Callback,
|
||||
void *ClientContext
|
||||
);
|
||||
|
||||
PROCESS_REGISTRY_ENTRY*
|
||||
GetParentEntry
|
||||
(
|
||||
CONTEXT *Context,
|
||||
PROCESS_REGISTRY_ENTRY *Entry
|
||||
);
|
||||
|
||||
bool
|
||||
IsEmpty
|
||||
(
|
||||
CONTEXT *Context
|
||||
);
|
||||
|
||||
} // namespace procregistry
|
||||
327
src/containers/registeredimage.cpp
Normal file
327
src/containers/registeredimage.cpp
Normal file
@@ -0,0 +1,327 @@
|
||||
#include <ntifs.h>
|
||||
#include "registeredimage.h"
|
||||
#include "../util.h"
|
||||
|
||||
namespace registeredimage
|
||||
{
|
||||
|
||||
struct CONTEXT
|
||||
{
|
||||
LIST_ENTRY ListEntry;
|
||||
ST_PAGEABLE Pageable;
|
||||
};
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
//
|
||||
// FindEntry()
|
||||
//
|
||||
// Use at PASSIVE only (APC is OK unless in paging file IO path).
|
||||
// Presumably because character tables are stored in pageable memory.
|
||||
//
|
||||
// Implements case-insensitive comparison.
|
||||
//
|
||||
REGISTERED_IMAGE_ENTRY*
|
||||
FindEntry
|
||||
(
|
||||
CONTEXT *Context,
|
||||
UNICODE_STRING *ImageName
|
||||
)
|
||||
{
|
||||
for (auto entry = Context->ListEntry.Flink;
|
||||
entry != &Context->ListEntry;
|
||||
entry = entry->Flink)
|
||||
{
|
||||
auto candidate = (REGISTERED_IMAGE_ENTRY*)entry;
|
||||
|
||||
if (0 == RtlCompareUnicodeString((UNICODE_STRING*)&candidate->ImageName, ImageName, TRUE))
|
||||
{
|
||||
return candidate;
|
||||
}
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
//
|
||||
// FindEntryExact()
|
||||
//
|
||||
// Use at DISPATCH.
|
||||
// Implements case-sensitive comparison.
|
||||
//
|
||||
REGISTERED_IMAGE_ENTRY*
|
||||
FindEntryExact
|
||||
(
|
||||
CONTEXT *Context,
|
||||
LOWER_UNICODE_STRING *ImageName
|
||||
)
|
||||
{
|
||||
for (auto entry = Context->ListEntry.Flink;
|
||||
entry != &Context->ListEntry;
|
||||
entry = entry->Flink)
|
||||
{
|
||||
auto candidate = (REGISTERED_IMAGE_ENTRY*)entry;
|
||||
|
||||
if (candidate->ImageName.Length != ImageName->Length)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
const auto equalBytes = RtlCompareMemory
|
||||
(
|
||||
candidate->ImageName.Buffer,
|
||||
ImageName->Buffer,
|
||||
ImageName->Length
|
||||
);
|
||||
|
||||
if (equalBytes == ImageName->Length)
|
||||
{
|
||||
return candidate;
|
||||
}
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
NTSTATUS
|
||||
AddEntryInner
|
||||
(
|
||||
CONTEXT *Context,
|
||||
LOWER_UNICODE_STRING *ImageName
|
||||
)
|
||||
{
|
||||
//
|
||||
// Make a single allocation for the struct and string buffer.
|
||||
//
|
||||
|
||||
auto offsetStringBuffer = util::RoundToMultiple(sizeof(REGISTERED_IMAGE_ENTRY), 8);
|
||||
|
||||
auto allocationSize = offsetStringBuffer + ImageName->Length;
|
||||
|
||||
const auto poolType = (Context->Pageable == ST_PAGEABLE::YES) ? PagedPool : NonPagedPool;
|
||||
|
||||
auto record = (REGISTERED_IMAGE_ENTRY*)
|
||||
ExAllocatePoolWithTag(poolType, allocationSize, ST_POOL_TAG);
|
||||
|
||||
if (record == NULL)
|
||||
{
|
||||
return STATUS_INSUFFICIENT_RESOURCES;
|
||||
}
|
||||
|
||||
auto stringBuffer = (WCHAR*)(((CHAR*)record) + offsetStringBuffer);
|
||||
|
||||
InitializeListHead(&record->ListEntry);
|
||||
|
||||
record->ImageName.Length = ImageName->Length;
|
||||
record->ImageName.MaximumLength = ImageName->Length;
|
||||
record->ImageName.Buffer = stringBuffer;
|
||||
|
||||
RtlCopyMemory(stringBuffer, ImageName->Buffer, ImageName->Length);
|
||||
|
||||
InsertTailList(&Context->ListEntry, &record->ListEntry);
|
||||
|
||||
return STATUS_SUCCESS;
|
||||
}
|
||||
|
||||
bool
|
||||
RemoveEntryInner
|
||||
(
|
||||
REGISTERED_IMAGE_ENTRY *Entry
|
||||
)
|
||||
{
|
||||
if (Entry == NULL)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
RemoveEntryList(&Entry->ListEntry);
|
||||
|
||||
ExFreePoolWithTag(Entry, ST_POOL_TAG);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
} // anonymous namespace
|
||||
|
||||
NTSTATUS
|
||||
Initialize
|
||||
(
|
||||
CONTEXT **Context,
|
||||
ST_PAGEABLE Pageable
|
||||
)
|
||||
{
|
||||
const auto poolType = (Pageable == ST_PAGEABLE::YES) ? PagedPool : NonPagedPool;
|
||||
|
||||
*Context = (CONTEXT*)ExAllocatePoolWithTag(poolType, sizeof(CONTEXT), ST_POOL_TAG);
|
||||
|
||||
if (*Context == NULL)
|
||||
{
|
||||
return STATUS_INSUFFICIENT_RESOURCES;
|
||||
}
|
||||
|
||||
InitializeListHead(&(*Context)->ListEntry);
|
||||
(*Context)->Pageable = Pageable;
|
||||
|
||||
return STATUS_SUCCESS;
|
||||
}
|
||||
|
||||
_IRQL_requires_(PASSIVE_LEVEL)
|
||||
NTSTATUS
|
||||
AddEntry
|
||||
(
|
||||
CONTEXT *Context,
|
||||
UNICODE_STRING *ImageName
|
||||
)
|
||||
{
|
||||
NT_ASSERT(KeGetCurrentIrql() == PASSIVE_LEVEL);
|
||||
|
||||
//
|
||||
// Avoid storing duplicates.
|
||||
// FindEntry doesn't care about character casing.
|
||||
//
|
||||
|
||||
if (NULL != FindEntry(Context, ImageName))
|
||||
{
|
||||
return STATUS_SUCCESS;
|
||||
}
|
||||
|
||||
//
|
||||
// Make a lower case string copy.
|
||||
//
|
||||
|
||||
UNICODE_STRING lowerImageName;
|
||||
|
||||
auto status = RtlDowncaseUnicodeString(&lowerImageName, ImageName, TRUE);
|
||||
|
||||
if (!NT_SUCCESS(status))
|
||||
{
|
||||
return status;
|
||||
}
|
||||
|
||||
status = AddEntryInner(Context, (LOWER_UNICODE_STRING*)&lowerImageName);
|
||||
|
||||
RtlFreeUnicodeString(&lowerImageName);
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
NTSTATUS
|
||||
AddEntryExact
|
||||
(
|
||||
CONTEXT *Context,
|
||||
LOWER_UNICODE_STRING *ImageName
|
||||
)
|
||||
{
|
||||
if (NULL != FindEntryExact(Context, ImageName))
|
||||
{
|
||||
return STATUS_SUCCESS;
|
||||
}
|
||||
|
||||
return AddEntryInner(Context, ImageName);
|
||||
}
|
||||
|
||||
bool
|
||||
HasEntry
|
||||
(
|
||||
CONTEXT *Context,
|
||||
UNICODE_STRING *ImageName
|
||||
)
|
||||
{
|
||||
auto record = FindEntry(Context, ImageName);
|
||||
|
||||
return record != NULL;
|
||||
}
|
||||
|
||||
bool
|
||||
HasEntryExact
|
||||
(
|
||||
CONTEXT *Context,
|
||||
LOWER_UNICODE_STRING *ImageName
|
||||
)
|
||||
{
|
||||
auto record = FindEntryExact(Context, ImageName);
|
||||
|
||||
return record != NULL;
|
||||
}
|
||||
|
||||
bool
|
||||
RemoveEntry
|
||||
(
|
||||
CONTEXT *Context,
|
||||
UNICODE_STRING *ImageName
|
||||
)
|
||||
{
|
||||
return RemoveEntryInner(FindEntry(Context, ImageName));
|
||||
}
|
||||
|
||||
bool
|
||||
RemoveEntryExact
|
||||
(
|
||||
CONTEXT *Context,
|
||||
LOWER_UNICODE_STRING *ImageName
|
||||
)
|
||||
{
|
||||
return RemoveEntryInner(FindEntryExact(Context, ImageName));
|
||||
}
|
||||
|
||||
bool
|
||||
ForEach
|
||||
(
|
||||
CONTEXT *Context,
|
||||
ST_RI_FOREACH Callback,
|
||||
void *ClientContext
|
||||
)
|
||||
{
|
||||
for (auto entry = Context->ListEntry.Flink;
|
||||
entry != &Context->ListEntry;
|
||||
entry = entry->Flink)
|
||||
{
|
||||
auto typedEntry = (REGISTERED_IMAGE_ENTRY *)entry;
|
||||
|
||||
if (!Callback(&typedEntry->ImageName, ClientContext))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void
|
||||
Reset
|
||||
(
|
||||
CONTEXT *Context
|
||||
)
|
||||
{
|
||||
while (FALSE == IsListEmpty(&Context->ListEntry))
|
||||
{
|
||||
auto entry = RemoveHeadList(&Context->ListEntry);
|
||||
|
||||
ExFreePoolWithTag(entry, ST_POOL_TAG);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
TearDown
|
||||
(
|
||||
CONTEXT **Context
|
||||
)
|
||||
{
|
||||
Reset(*Context);
|
||||
|
||||
ExFreePoolWithTag(*Context, ST_POOL_TAG);
|
||||
|
||||
*Context = NULL;
|
||||
}
|
||||
|
||||
bool
|
||||
IsEmpty
|
||||
(
|
||||
CONTEXT *Context
|
||||
)
|
||||
{
|
||||
return bool_cast(IsListEmpty(&Context->ListEntry));
|
||||
}
|
||||
|
||||
} // namespace registeredimage
|
||||
139
src/containers/registeredimage.h
Normal file
139
src/containers/registeredimage.h
Normal file
@@ -0,0 +1,139 @@
|
||||
#pragma once
|
||||
|
||||
#include <wdm.h>
|
||||
#include "../defs/types.h"
|
||||
|
||||
namespace registeredimage
|
||||
{
|
||||
|
||||
struct REGISTERED_IMAGE_ENTRY
|
||||
{
|
||||
LIST_ENTRY ListEntry;
|
||||
|
||||
// Device path using all lower-case characters.
|
||||
LOWER_UNICODE_STRING ImageName;
|
||||
};
|
||||
|
||||
struct CONTEXT;
|
||||
|
||||
NTSTATUS
|
||||
Initialize
|
||||
(
|
||||
CONTEXT **Context,
|
||||
ST_PAGEABLE Pageable
|
||||
);
|
||||
|
||||
//
|
||||
// AddEntry()
|
||||
//
|
||||
// IRQL == PASSIVE_LEVEL
|
||||
//
|
||||
// Converts imagename to lower case before creating an entry.
|
||||
//
|
||||
_IRQL_requires_(PASSIVE_LEVEL)
|
||||
NTSTATUS
|
||||
AddEntry
|
||||
(
|
||||
CONTEXT *Context,
|
||||
UNICODE_STRING *ImageName
|
||||
);
|
||||
|
||||
//
|
||||
// AddEntryExact()
|
||||
//
|
||||
// IRQL <= DISPATCH
|
||||
//
|
||||
// Creates a new entry with the `ImageName` argument exactly as passed.
|
||||
//
|
||||
NTSTATUS
|
||||
AddEntryExact
|
||||
(
|
||||
CONTEXT *Context,
|
||||
LOWER_UNICODE_STRING *ImageName
|
||||
);
|
||||
|
||||
//
|
||||
// HasEntry()
|
||||
//
|
||||
// IRQL <= APC
|
||||
//
|
||||
// Compares existing entries against `ImageName` without regard to character casing.
|
||||
//
|
||||
bool
|
||||
HasEntry
|
||||
(
|
||||
CONTEXT *Context,
|
||||
UNICODE_STRING *ImageName
|
||||
);
|
||||
|
||||
//
|
||||
// HasEntryExact()
|
||||
//
|
||||
// IRQL <= DISPATCH
|
||||
//
|
||||
// Compares existing entries against case-sensitive `ImageName` argument.
|
||||
//
|
||||
bool
|
||||
HasEntryExact
|
||||
(
|
||||
CONTEXT *Context,
|
||||
LOWER_UNICODE_STRING *ImageName
|
||||
);
|
||||
|
||||
//
|
||||
// RemoveEntry()
|
||||
//
|
||||
// IRQL <= APC
|
||||
//
|
||||
// Searches for and removes entry matching `ImageName` without regard to character casing.
|
||||
//
|
||||
bool
|
||||
RemoveEntry
|
||||
(
|
||||
CONTEXT *Context,
|
||||
UNICODE_STRING *ImageName
|
||||
);
|
||||
|
||||
//
|
||||
// RemoveEntryExact()
|
||||
//
|
||||
// IRQL <= DISPATCH
|
||||
//
|
||||
// Searches for and removes entry using case-sensitive matching of `ImageName`.
|
||||
//
|
||||
bool
|
||||
RemoveEntryExact
|
||||
(
|
||||
CONTEXT *Context,
|
||||
LOWER_UNICODE_STRING *ImageName
|
||||
);
|
||||
|
||||
typedef bool (NTAPI *ST_RI_FOREACH)(LOWER_UNICODE_STRING *ImageName, void *Context);
|
||||
|
||||
bool
|
||||
ForEach
|
||||
(
|
||||
CONTEXT *Context,
|
||||
ST_RI_FOREACH Callback,
|
||||
void *ClientContext
|
||||
);
|
||||
|
||||
void
|
||||
Reset
|
||||
(
|
||||
CONTEXT *Context
|
||||
);
|
||||
|
||||
void
|
||||
TearDown
|
||||
(
|
||||
CONTEXT **Context
|
||||
);
|
||||
|
||||
bool
|
||||
IsEmpty
|
||||
(
|
||||
CONTEXT *Context
|
||||
);
|
||||
|
||||
} // namespace registeredimage
|
||||
59
src/custom-stampinf.bat
Normal file
59
src/custom-stampinf.bat
Normal file
@@ -0,0 +1,59 @@
|
||||
@echo off
|
||||
|
||||
:: Visual Studio invokes the pre-build event only after it executes stampinf.
|
||||
:: Therefore, any scheme to attempt to set STAMPINF_VERSION (using e.g. setx) during a pre-build event, is doomed to fail.
|
||||
:: You also cannot disable stampinf being called by Visual Studio.
|
||||
:: So what we'll do is run stampinf again, with the correct version data.
|
||||
::
|
||||
|
||||
if [%1]==[] goto ABORT_ARGUMENTS
|
||||
if [%2]==[] goto ABORT_ARGUMENTS
|
||||
if [%3]==[] goto ABORT_ARGUMENTS
|
||||
if [%4]==[] goto ABORT_ARGUMENTS
|
||||
if [%5]==[] goto ABORT_ARGUMENTS
|
||||
|
||||
:: Arguments 1, 4, 5 are quoted strings containing absolute paths.
|
||||
:: This avoids any issues with spaces in paths, quotes, concatenation.
|
||||
|
||||
set INF_BINARY=%1
|
||||
set INF_ARCH=%2
|
||||
set DRIVER_KMDF_VERSION=%3
|
||||
set INTERMEDIATE_DIR_TARGET=%4
|
||||
set OUTPUT_DIR_TARGET=%5
|
||||
|
||||
setlocal enabledelayedexpansion
|
||||
|
||||
:: Import version defines into environment
|
||||
|
||||
for /f "tokens=1-3 delims= " %%i in (%~dp0\version.h) do (
|
||||
if /i "%%i"=="#define" (
|
||||
set %%j=%%k
|
||||
)
|
||||
)
|
||||
|
||||
set DRIVER_VERSION=%DRIVER_VERSION_MAJOR%.%DRIVER_VERSION_MINOR%.%DRIVER_VERSION_PATCH%.%DRIVER_VERSION_BUILD%
|
||||
|
||||
:: Broken actions such as the DriverPackageTarget references the intermediate INF
|
||||
:: So we have to re-stamp the intermediate file and copy it to the output directory, so they're both up-to-date
|
||||
|
||||
echo Stamping INF again with correct version data
|
||||
|
||||
%INF_BINARY% -d "*" -a "%INF_ARCH%" -v "%DRIVER_VERSION%" -k "%DRIVER_KMDF_VERSION%" -f %INTERMEDIATE_DIR_TARGET%
|
||||
|
||||
if %ERRORLEVEL% neq 0 goto FAILED_STAMP
|
||||
|
||||
copy /y /b %INTERMEDIATE_DIR_TARGET% %OUTPUT_DIR_TARGET%
|
||||
|
||||
exit /b 0
|
||||
|
||||
:ABORT_ARGUMENTS
|
||||
|
||||
echo ERROR: %0 invoked without enough arguments
|
||||
|
||||
exit /b 1
|
||||
|
||||
:FAILED_STAMP
|
||||
|
||||
echo ERROR: %0 has failed
|
||||
|
||||
exit /b 1
|
||||
26
src/defs/config.h
Normal file
26
src/defs/config.h
Normal file
@@ -0,0 +1,26 @@
|
||||
#pragma once
|
||||
|
||||
//
|
||||
// Structures related to configuration.
|
||||
//
|
||||
|
||||
typedef struct tag_ST_CONFIGURATION_ENTRY
|
||||
{
|
||||
// Offset into buffer region that follows all entries.
|
||||
// The image name uses the device path.
|
||||
SIZE_T ImageNameOffset;
|
||||
|
||||
// Byte length for non-null terminated wide char string.
|
||||
USHORT ImageNameLength;
|
||||
}
|
||||
ST_CONFIGURATION_ENTRY;
|
||||
|
||||
typedef struct tag_ST_CONFIGURATION_HEADER
|
||||
{
|
||||
// Number of entries immediately following the header.
|
||||
SIZE_T NumEntries;
|
||||
|
||||
// Total byte length: header + entries + string buffer.
|
||||
SIZE_T TotalLength;
|
||||
}
|
||||
ST_CONFIGURATION_HEADER;
|
||||
56
src/defs/events.h
Normal file
56
src/defs/events.h
Normal file
@@ -0,0 +1,56 @@
|
||||
#pragma once
|
||||
|
||||
enum ST_EVENT_ID
|
||||
{
|
||||
ST_EVENT_START_SPLITTING_PROCESS = 0, // ST_SPLITTING_EVENT
|
||||
ST_EVENT_STOP_SPLITTING_PROCESS, // ST_SPLITTING_EVENT
|
||||
|
||||
ST_EVENT_ERROR_FLAG = 0x80000000,
|
||||
|
||||
ST_EVENT_ERROR_START_SPLITTING_PROCESS, // ST_SPLITTING_ERROR_EVENT
|
||||
ST_EVENT_ERROR_STOP_SPLITTING_PROCESS // ST_SPLITTING_ERROR_EVENT
|
||||
};
|
||||
|
||||
typedef struct tag_ST_EVENT_HEADER
|
||||
{
|
||||
ST_EVENT_ID EventId;
|
||||
|
||||
// Size of payload.
|
||||
SIZE_T EventSize;
|
||||
|
||||
// Message defined payload.
|
||||
UCHAR EventData[ANYSIZE_ARRAY];
|
||||
}
|
||||
ST_EVENT_HEADER;
|
||||
|
||||
enum ST_SPLITTING_STATUS_CHANGE_REASON
|
||||
{
|
||||
ST_SPLITTING_REASON_BY_INHERITANCE = 1,
|
||||
ST_SPLITTING_REASON_BY_CONFIG = 2,
|
||||
ST_SPLITTING_REASON_PROCESS_ARRIVING = 4,
|
||||
ST_SPLITTING_REASON_PROCESS_DEPARTING = 8
|
||||
};
|
||||
|
||||
typedef struct tag_ST_SPLITTING_EVENT
|
||||
{
|
||||
HANDLE ProcessId;
|
||||
|
||||
ST_SPLITTING_STATUS_CHANGE_REASON Reason;
|
||||
|
||||
// Byte length for non-null terminated wide char string.
|
||||
USHORT ImageNameLength;
|
||||
|
||||
WCHAR ImageName[ANYSIZE_ARRAY];
|
||||
}
|
||||
ST_SPLITTING_EVENT;
|
||||
|
||||
typedef struct tag_ST_SPLITTING_ERROR_EVENT
|
||||
{
|
||||
HANDLE ProcessId;
|
||||
|
||||
// Byte length for non-null terminated wide char string.
|
||||
USHORT ImageNameLength;
|
||||
|
||||
WCHAR ImageName[ANYSIZE_ARRAY];
|
||||
}
|
||||
ST_SPLITTING_ERROR_EVENT;
|
||||
49
src/defs/ioctl.h
Normal file
49
src/defs/ioctl.h
Normal file
@@ -0,0 +1,49 @@
|
||||
#pragma once
|
||||
|
||||
//
|
||||
// IOCTLs for controlling the driver.
|
||||
//
|
||||
|
||||
#define ST_DEVICE_TYPE 0x8000
|
||||
|
||||
#define IOCTL_ST_INITIALIZE \
|
||||
CTL_CODE(ST_DEVICE_TYPE, 1, METHOD_NEITHER, FILE_ANY_ACCESS)
|
||||
|
||||
#define IOCTL_ST_DEQUEUE_EVENT \
|
||||
CTL_CODE(ST_DEVICE_TYPE, 2, METHOD_BUFFERED, FILE_ANY_ACCESS)
|
||||
|
||||
#define IOCTL_ST_REGISTER_PROCESSES \
|
||||
CTL_CODE(ST_DEVICE_TYPE, 3, METHOD_BUFFERED, FILE_ANY_ACCESS)
|
||||
|
||||
#define IOCTL_ST_REGISTER_IP_ADDRESSES \
|
||||
CTL_CODE(ST_DEVICE_TYPE, 4, METHOD_BUFFERED, FILE_ANY_ACCESS)
|
||||
|
||||
#define IOCTL_ST_GET_IP_ADDRESSES \
|
||||
CTL_CODE(ST_DEVICE_TYPE, 5, METHOD_BUFFERED, FILE_ANY_ACCESS)
|
||||
|
||||
#define IOCTL_ST_SET_CONFIGURATION \
|
||||
CTL_CODE(ST_DEVICE_TYPE, 6, METHOD_BUFFERED, FILE_ANY_ACCESS)
|
||||
|
||||
#define IOCTL_ST_GET_CONFIGURATION \
|
||||
CTL_CODE(ST_DEVICE_TYPE, 7, METHOD_BUFFERED, FILE_ANY_ACCESS)
|
||||
|
||||
#define IOCTL_ST_CLEAR_CONFIGURATION \
|
||||
CTL_CODE(ST_DEVICE_TYPE, 8, METHOD_NEITHER, FILE_ANY_ACCESS)
|
||||
|
||||
#define IOCTL_ST_GET_STATE \
|
||||
CTL_CODE(ST_DEVICE_TYPE, 9, METHOD_BUFFERED, FILE_ANY_ACCESS)
|
||||
|
||||
#define IOCTL_ST_QUERY_PROCESS \
|
||||
CTL_CODE(ST_DEVICE_TYPE, 10, METHOD_BUFFERED, FILE_ANY_ACCESS)
|
||||
|
||||
//
|
||||
// IOCTL_ST_RESET:
|
||||
//
|
||||
// Use before attempting to unload the driver.
|
||||
// Subsystems will be torn down, resources released etc.
|
||||
//
|
||||
// On success, the new state will be ST_DRIVER_STATE_STARTED.
|
||||
// On error, the new state will be ST_DRIVER_STATE_ZOMBIE.
|
||||
//
|
||||
#define IOCTL_ST_RESET \
|
||||
CTL_CODE(ST_DEVICE_TYPE, 11, METHOD_NEITHER, FILE_ANY_ACCESS)
|
||||
29
src/defs/process.h
Normal file
29
src/defs/process.h
Normal file
@@ -0,0 +1,29 @@
|
||||
#pragma once
|
||||
|
||||
//
|
||||
// Structures related to initial process registration.
|
||||
//
|
||||
|
||||
typedef struct tag_ST_PROCESS_DISCOVERY_ENTRY
|
||||
{
|
||||
HANDLE ProcessId;
|
||||
HANDLE ParentProcessId;
|
||||
|
||||
// Offset into buffer region that follows all entries.
|
||||
// The image name uses the device path.
|
||||
SIZE_T ImageNameOffset;
|
||||
|
||||
// Byte length for non-null terminated wide char string.
|
||||
USHORT ImageNameLength;
|
||||
}
|
||||
ST_PROCESS_DISCOVERY_ENTRY;
|
||||
|
||||
typedef struct tag_ST_PROCESS_DISCOVERY_HEADER
|
||||
{
|
||||
// Number of entries immediately following the header.
|
||||
SIZE_T NumEntries;
|
||||
|
||||
// Total byte length: header + entries + string buffer.
|
||||
SIZE_T TotalLength;
|
||||
}
|
||||
ST_PROCESS_DISCOVERY_HEADER;
|
||||
25
src/defs/queryprocess.h
Normal file
25
src/defs/queryprocess.h
Normal file
@@ -0,0 +1,25 @@
|
||||
#pragma once
|
||||
|
||||
//
|
||||
// Structures related to querying process information.
|
||||
//
|
||||
|
||||
typedef struct tag_ST_QUERY_PROCESS
|
||||
{
|
||||
HANDLE ProcessId;
|
||||
}
|
||||
ST_QUERY_PROCESS;
|
||||
|
||||
typedef struct tag_ST_QUERY_PROCESS_RESPONSE
|
||||
{
|
||||
HANDLE ProcessId;
|
||||
HANDLE ParentProcessId;
|
||||
|
||||
BOOLEAN Split;
|
||||
|
||||
// Byte length for non-null terminated wide char string.
|
||||
USHORT ImageNameLength;
|
||||
|
||||
WCHAR ImageName[ANYSIZE_ARRAY];
|
||||
}
|
||||
ST_QUERY_PROCESS_RESPONSE;
|
||||
28
src/defs/state.h
Normal file
28
src/defs/state.h
Normal file
@@ -0,0 +1,28 @@
|
||||
#pragma once
|
||||
|
||||
//
|
||||
// All possible states in the driver.
|
||||
//
|
||||
|
||||
enum ST_DRIVER_STATE
|
||||
{
|
||||
// Default state after being loaded.
|
||||
ST_DRIVER_STATE_NONE = 0,
|
||||
|
||||
// DriverEntry has completed successfully.
|
||||
// Basically only driver and device objects are created at this point.
|
||||
ST_DRIVER_STATE_STARTED = 1,
|
||||
|
||||
// All subsystems are initialized.
|
||||
ST_DRIVER_STATE_INITIALIZED = 2,
|
||||
|
||||
// User mode has registered all processes in the system.
|
||||
ST_DRIVER_STATE_READY = 3,
|
||||
|
||||
// IP addresses are registered.
|
||||
// A valid configuration is registered.
|
||||
ST_DRIVER_STATE_ENGAGED = 4,
|
||||
|
||||
// Driver could not tear down subsystems.
|
||||
ST_DRIVER_STATE_ZOMBIE = 5,
|
||||
};
|
||||
39
src/defs/types.h
Normal file
39
src/defs/types.h
Normal file
@@ -0,0 +1,39 @@
|
||||
#pragma once
|
||||
|
||||
//
|
||||
// types.h
|
||||
//
|
||||
// Miscellaneous types and defines used internally.
|
||||
//
|
||||
|
||||
#define ST_POOL_TAG 'UTPS'
|
||||
|
||||
enum class ST_PAGEABLE
|
||||
{
|
||||
YES = 0,
|
||||
NO
|
||||
};
|
||||
|
||||
//
|
||||
// Type-safety when passing around lower case device paths.
|
||||
// Same definition as UNICODE_STRING so they can be cast between.
|
||||
//
|
||||
typedef struct tag_LOWER_UNICODE_STRING
|
||||
{
|
||||
USHORT Length;
|
||||
USHORT MaximumLength;
|
||||
PWCH Buffer;
|
||||
}
|
||||
LOWER_UNICODE_STRING;
|
||||
|
||||
enum ST_PROCESS_SPLIT_STATUS
|
||||
{
|
||||
// Traffic should be split.
|
||||
ST_PROCESS_SPLIT_STATUS_ON_BY_CONFIG = 0,
|
||||
|
||||
// Traffic should be split.
|
||||
ST_PROCESS_SPLIT_STATUS_ON_BY_INHERITANCE,
|
||||
|
||||
// Traffic should not be split.
|
||||
ST_PROCESS_SPLIT_STATUS_OFF
|
||||
};
|
||||
42
src/devicecontext.h
Normal file
42
src/devicecontext.h
Normal file
@@ -0,0 +1,42 @@
|
||||
#pragma once
|
||||
|
||||
#include <wdf.h>
|
||||
#include "ipaddr.h"
|
||||
#include "containers.h"
|
||||
#include "defs/state.h"
|
||||
#include "firewall/firewall.h"
|
||||
#include "procmgmt/procmgmt.h"
|
||||
#include "eventing/eventing.h"
|
||||
#include "procbroker/procbroker.h"
|
||||
|
||||
struct DRIVER_STATE_MGMT
|
||||
{
|
||||
WDFWAITLOCK Lock;
|
||||
ST_DRIVER_STATE State;
|
||||
};
|
||||
|
||||
typedef struct tag_ST_DEVICE_CONTEXT
|
||||
{
|
||||
DRIVER_STATE_MGMT DriverState;
|
||||
|
||||
// Serialized queue for processing of most IOCTLs.
|
||||
WDFQUEUE IoCtlQueue;
|
||||
|
||||
ST_IP_ADDRESSES IpAddresses;
|
||||
|
||||
PROCESS_REGISTRY_MGMT ProcessRegistry;
|
||||
|
||||
// Protected by state lock.
|
||||
REGISTERED_IMAGE_MGMT RegisteredImage;
|
||||
|
||||
firewall::CONTEXT *Firewall;
|
||||
|
||||
procmgmt::CONTEXT *ProcessMgmt;
|
||||
|
||||
eventing::CONTEXT *Eventing;
|
||||
|
||||
procbroker::CONTEXT *ProcessEventBroker;
|
||||
}
|
||||
ST_DEVICE_CONTEXT;
|
||||
|
||||
WDF_DECLARE_CONTEXT_TYPE_WITH_NAME(ST_DEVICE_CONTEXT, DeviceGetSplitTunnelContext)
|
||||
558
src/driverentry.cpp
Normal file
558
src/driverentry.cpp
Normal file
@@ -0,0 +1,558 @@
|
||||
#include "x64guard.h"
|
||||
|
||||
#include <ntddk.h>
|
||||
#include <wdf.h>
|
||||
#include <wdmsec.h>
|
||||
#include <mstcpip.h>
|
||||
|
||||
#include "devicecontext.h"
|
||||
#include "util.h"
|
||||
#include "ioctl.h"
|
||||
#include "firewall/firewall.h"
|
||||
#include "defs/ioctl.h"
|
||||
#include "eventing/eventing.h"
|
||||
|
||||
extern "C"
|
||||
DRIVER_INITIALIZE DriverEntry;
|
||||
|
||||
extern "C" // Because alloc_text requires this.
|
||||
NTSTATUS
|
||||
StCreateDevice
|
||||
(
|
||||
IN WDFDRIVER WdfDriver
|
||||
);
|
||||
|
||||
EVT_WDF_IO_QUEUE_IO_DEVICE_CONTROL StEvtIoDeviceControl;
|
||||
|
||||
EVT_WDF_IO_QUEUE_IO_DEVICE_CONTROL StEvtIoDeviceControlSerial;
|
||||
|
||||
EVT_WDF_DRIVER_UNLOAD StEvtDriverUnload;
|
||||
|
||||
#pragma alloc_text (INIT, DriverEntry)
|
||||
#pragma alloc_text (INIT, StCreateDevice)
|
||||
|
||||
#if DBG
|
||||
#define ST_DEVICE_SECURITY_DESCRIPTOR SDDL_DEVOBJ_SYS_ALL_ADM_RWX_WORLD_RWX_RES_RWX
|
||||
#else
|
||||
#define ST_DEVICE_SECURITY_DESCRIPTOR SDDL_DEVOBJ_SYS_ALL
|
||||
#endif
|
||||
|
||||
#define ST_DEVICE_NAME_STRING L"\\Device\\MULLVADSPLITTUNNEL"
|
||||
#define ST_SYMBOLIC_NAME_STRING L"\\Global??\\MULLVADSPLITTUNNEL"
|
||||
|
||||
//
|
||||
// DriverEntry
|
||||
//
|
||||
// Creates a single device with associated symbolic link.
|
||||
// Does minimal initialization.
|
||||
//
|
||||
extern "C"
|
||||
NTSTATUS
|
||||
DriverEntry
|
||||
(
|
||||
_In_ PDRIVER_OBJECT DriverObject,
|
||||
_In_ PUNICODE_STRING RegistryPath
|
||||
)
|
||||
{
|
||||
DbgPrint("Loading Mullvad split tunnel driver\n");
|
||||
|
||||
ExInitializeDriverRuntime(DrvRtPoolNxOptIn);
|
||||
|
||||
//
|
||||
// Create WDF driver object.
|
||||
//
|
||||
|
||||
WDF_DRIVER_CONFIG config;
|
||||
|
||||
WDF_DRIVER_CONFIG_INIT(&config, WDF_NO_EVENT_CALLBACK);
|
||||
|
||||
config.DriverInitFlags |= WdfDriverInitNonPnpDriver;
|
||||
config.EvtDriverUnload = StEvtDriverUnload;
|
||||
config.DriverPoolTag = ST_POOL_TAG;
|
||||
|
||||
WDFDRIVER wdfDriver;
|
||||
|
||||
auto status = WdfDriverCreate
|
||||
(
|
||||
DriverObject,
|
||||
RegistryPath,
|
||||
WDF_NO_OBJECT_ATTRIBUTES,
|
||||
&config,
|
||||
&wdfDriver
|
||||
);
|
||||
|
||||
if (!NT_SUCCESS(status))
|
||||
{
|
||||
DbgPrint("WdfDriverCreate() failed 0x%X\n", status);
|
||||
return status;
|
||||
}
|
||||
|
||||
//
|
||||
// Create WDF device object.
|
||||
//
|
||||
|
||||
status = StCreateDevice(wdfDriver);
|
||||
|
||||
if (!NT_SUCCESS(status))
|
||||
{
|
||||
DbgPrint("StCreateDevice() failed 0x%X\n", status);
|
||||
return status;
|
||||
}
|
||||
|
||||
//
|
||||
// All set.
|
||||
//
|
||||
|
||||
DbgPrint("Successfully loaded Mullvad split tunnel driver\n");
|
||||
|
||||
return STATUS_SUCCESS;
|
||||
}
|
||||
|
||||
extern "C"
|
||||
NTSTATUS
|
||||
StCreateDevice
|
||||
(
|
||||
IN WDFDRIVER WdfDriver
|
||||
)
|
||||
{
|
||||
DECLARE_CONST_UNICODE_STRING(deviceName, ST_DEVICE_NAME_STRING);
|
||||
DECLARE_CONST_UNICODE_STRING(symbolicLinkName, ST_SYMBOLIC_NAME_STRING);
|
||||
|
||||
auto deviceInit = WdfControlDeviceInitAllocate
|
||||
(
|
||||
WdfDriver,
|
||||
&ST_DEVICE_SECURITY_DESCRIPTOR
|
||||
);
|
||||
|
||||
if (deviceInit == NULL)
|
||||
{
|
||||
DbgPrint("WdfControlDeviceInitAllocate() failed\n");
|
||||
return STATUS_INSUFFICIENT_RESOURCES;
|
||||
}
|
||||
|
||||
WdfDeviceInitSetExclusive(deviceInit, TRUE);
|
||||
|
||||
auto status = WdfDeviceInitAssignName(deviceInit, &deviceName);
|
||||
|
||||
if (!NT_SUCCESS(status))
|
||||
{
|
||||
DbgPrint("WdfDeviceInitAssignName() failed 0x%X\n", status);
|
||||
goto Cleanup;
|
||||
}
|
||||
|
||||
//
|
||||
// No need to call WdfDeviceInitSetIoType() that configures the I/O type for
|
||||
// read and write requests.
|
||||
//
|
||||
// We're using IOCTL for everything, which have the I/O type encoded.
|
||||
//
|
||||
// ---
|
||||
//
|
||||
// No need to call WdfControlDeviceInitSetShutdownNotification() because
|
||||
// we don't care about the system being shut down.
|
||||
//
|
||||
// ---
|
||||
//
|
||||
// No need to call WdfDeviceInitSetFileObjectConfig() because we're not
|
||||
// interested in receiving events when device handles are created/destroyed.
|
||||
//
|
||||
// --
|
||||
//
|
||||
// No need to call WdfDeviceInitSetIoInCallerContextCallback() because
|
||||
// we're not using METHOD_NEITHER for any buffers.
|
||||
//
|
||||
|
||||
WDF_OBJECT_ATTRIBUTES attributes;
|
||||
|
||||
WDF_OBJECT_ATTRIBUTES_INIT_CONTEXT_TYPE
|
||||
(
|
||||
&attributes,
|
||||
ST_DEVICE_CONTEXT
|
||||
);
|
||||
|
||||
WDFDEVICE wdfDevice;
|
||||
|
||||
status = WdfDeviceCreate
|
||||
(
|
||||
&deviceInit,
|
||||
&attributes,
|
||||
&wdfDevice
|
||||
);
|
||||
|
||||
if (!NT_SUCCESS(status))
|
||||
{
|
||||
DbgPrint("WdfDeviceCreate() failed 0x%X\n", status);
|
||||
goto Cleanup;
|
||||
}
|
||||
|
||||
status = WdfDeviceCreateSymbolicLink
|
||||
(
|
||||
wdfDevice,
|
||||
&symbolicLinkName
|
||||
);
|
||||
|
||||
if (!NT_SUCCESS(status))
|
||||
{
|
||||
DbgPrint("WdfDeviceCreateSymbolicLink() failed 0x%X\n", status);
|
||||
goto Cleanup;
|
||||
}
|
||||
|
||||
//
|
||||
// Create a default request queue.
|
||||
// Only register to handle IOCTL requests.
|
||||
// Use WdfIoQueueDispatchParallel to enable inverted call.
|
||||
//
|
||||
|
||||
WDF_IO_QUEUE_CONFIG queueConfig;
|
||||
|
||||
WDF_IO_QUEUE_CONFIG_INIT_DEFAULT_QUEUE
|
||||
(
|
||||
&queueConfig,
|
||||
WdfIoQueueDispatchParallel
|
||||
);
|
||||
|
||||
queueConfig.EvtIoDeviceControl = StEvtIoDeviceControl;
|
||||
queueConfig.PowerManaged = WdfFalse;
|
||||
|
||||
WDF_OBJECT_ATTRIBUTES_INIT(&attributes);
|
||||
attributes.ExecutionLevel = WdfExecutionLevelPassive;
|
||||
|
||||
status = WdfIoQueueCreate
|
||||
(
|
||||
wdfDevice,
|
||||
&queueConfig,
|
||||
&attributes,
|
||||
WDF_NO_HANDLE
|
||||
);
|
||||
|
||||
if (!NT_SUCCESS(status))
|
||||
{
|
||||
DbgPrint("WdfIoQueueCreate() for default queue failed 0x%X\n", status);
|
||||
goto Cleanup;
|
||||
}
|
||||
|
||||
//
|
||||
// Create a secondary queue that is serialized.
|
||||
// Commands that need to be serialized can then be forwarded to this queue.
|
||||
//
|
||||
|
||||
WDF_IO_QUEUE_CONFIG_INIT
|
||||
(
|
||||
&queueConfig,
|
||||
WdfIoQueueDispatchSequential
|
||||
);
|
||||
|
||||
queueConfig.EvtIoDeviceControl = StEvtIoDeviceControlSerial;
|
||||
queueConfig.PowerManaged = WdfFalse;
|
||||
|
||||
WDF_OBJECT_ATTRIBUTES_INIT(&attributes);
|
||||
attributes.ExecutionLevel = WdfExecutionLevelPassive;
|
||||
|
||||
WDFQUEUE serialQueue;
|
||||
|
||||
status = WdfIoQueueCreate
|
||||
(
|
||||
wdfDevice,
|
||||
&queueConfig,
|
||||
&attributes,
|
||||
&serialQueue
|
||||
);
|
||||
|
||||
if (!NT_SUCCESS(status))
|
||||
{
|
||||
DbgPrint("WdfIoQueueCreate() for secondary queue failed 0x%X\n", status);
|
||||
goto Cleanup;
|
||||
}
|
||||
|
||||
//
|
||||
// Initialize context.
|
||||
//
|
||||
|
||||
auto context = DeviceGetSplitTunnelContext(wdfDevice);
|
||||
|
||||
RtlZeroMemory(context, sizeof(*context));
|
||||
|
||||
status = WdfWaitLockCreate(WDF_NO_OBJECT_ATTRIBUTES, &context->DriverState.Lock);
|
||||
|
||||
if (!NT_SUCCESS(status))
|
||||
{
|
||||
DbgPrint("WdfWaitLockCreate() failed 0x%X\n", status);
|
||||
goto Cleanup;
|
||||
}
|
||||
|
||||
context->DriverState.State = ST_DRIVER_STATE_STARTED;
|
||||
|
||||
context->IoCtlQueue = serialQueue;
|
||||
|
||||
WdfControlFinishInitializing(wdfDevice);
|
||||
|
||||
status = STATUS_SUCCESS;
|
||||
|
||||
Cleanup:
|
||||
|
||||
if (deviceInit != NULL)
|
||||
{
|
||||
WdfDeviceInitFree(deviceInit);
|
||||
}
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
VOID
|
||||
StEvtIoDeviceControl
|
||||
(
|
||||
WDFQUEUE Queue,
|
||||
WDFREQUEST Request,
|
||||
size_t OutputBufferLength,
|
||||
size_t InputBufferLength,
|
||||
ULONG IoControlCode
|
||||
)
|
||||
{
|
||||
UNREFERENCED_PARAMETER(OutputBufferLength);
|
||||
UNREFERENCED_PARAMETER(InputBufferLength);
|
||||
|
||||
auto device = WdfIoQueueGetDevice(Queue);
|
||||
auto context = DeviceGetSplitTunnelContext(device);
|
||||
|
||||
//
|
||||
// Querying the current driver state is always a valid operation.
|
||||
//
|
||||
|
||||
if (IoControlCode == IOCTL_ST_GET_STATE)
|
||||
{
|
||||
ioctl::GetStateComplete(device, Request);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
//
|
||||
// Once the basic initialization is out of the way
|
||||
// it's always valid for the client to attempt to dequeue an event.
|
||||
//
|
||||
// TODO: This approach is slightly broken.
|
||||
//
|
||||
// CollectOne() may enqueue the request in anticipation of an event arriving.
|
||||
// That means the request completion may come at a later time when the asserted
|
||||
// driver state has changed.
|
||||
//
|
||||
// But this probably doesn't matter.
|
||||
//
|
||||
|
||||
if (IoControlCode == IOCTL_ST_DEQUEUE_EVENT)
|
||||
{
|
||||
WdfWaitLockAcquire(context->DriverState.Lock, NULL);
|
||||
|
||||
if (context->DriverState.State >= ST_DRIVER_STATE_INITIALIZED
|
||||
&& context->DriverState.State < ST_DRIVER_STATE_ZOMBIE)
|
||||
{
|
||||
eventing::CollectOne(context->Eventing, Request);
|
||||
}
|
||||
else
|
||||
{
|
||||
DbgPrint("Cannot dequeue event at current driver state\n");
|
||||
|
||||
WdfRequestComplete(Request, STATUS_INVALID_DEVICE_REQUEST);
|
||||
}
|
||||
|
||||
WdfWaitLockRelease(context->DriverState.Lock);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
//
|
||||
// Forward to serialized queue.
|
||||
//
|
||||
|
||||
auto status = WdfRequestForwardToIoQueue(Request, context->IoCtlQueue);
|
||||
|
||||
if (NT_SUCCESS(status))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
DbgPrint("Failed to forward request to serialized queue\n");
|
||||
|
||||
WdfRequestComplete(Request, status);
|
||||
}
|
||||
|
||||
VOID
|
||||
StEvtIoDeviceControlSerial
|
||||
(
|
||||
WDFQUEUE Queue,
|
||||
WDFREQUEST Request,
|
||||
size_t OutputBufferLength,
|
||||
size_t InputBufferLength,
|
||||
ULONG IoControlCode
|
||||
)
|
||||
{
|
||||
UNREFERENCED_PARAMETER(Queue);
|
||||
UNREFERENCED_PARAMETER(OutputBufferLength);
|
||||
UNREFERENCED_PARAMETER(InputBufferLength);
|
||||
|
||||
auto device = WdfIoQueueGetDevice(Queue);
|
||||
|
||||
if (IoControlCode == IOCTL_ST_RESET)
|
||||
{
|
||||
//
|
||||
// Potential state transition here.
|
||||
//
|
||||
ioctl::ResetComplete(device, Request);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
auto context = DeviceGetSplitTunnelContext(device);
|
||||
|
||||
switch (context->DriverState.State)
|
||||
{
|
||||
case ST_DRIVER_STATE_STARTED:
|
||||
{
|
||||
//
|
||||
// Valid controls:
|
||||
//
|
||||
// IOCTL_ST_INITIALIZE
|
||||
//
|
||||
|
||||
if (IoControlCode == IOCTL_ST_INITIALIZE)
|
||||
{
|
||||
//
|
||||
// Definitive state transition here.
|
||||
// No locking needed this early.
|
||||
//
|
||||
WdfRequestComplete(Request, ioctl::Initialize(device));
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
case ST_DRIVER_STATE_INITIALIZED:
|
||||
{
|
||||
//
|
||||
// Valid controls:
|
||||
//
|
||||
// IOCTL_ST_REGISTER_PROCESSES
|
||||
//
|
||||
|
||||
if (IoControlCode == IOCTL_ST_REGISTER_PROCESSES)
|
||||
{
|
||||
//
|
||||
// Definitive state transition here.
|
||||
// No locking needed this early.
|
||||
//
|
||||
WdfRequestComplete(Request, ioctl::RegisterProcesses(device, Request));
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
case ST_DRIVER_STATE_READY:
|
||||
case ST_DRIVER_STATE_ENGAGED:
|
||||
{
|
||||
//
|
||||
// Valid controls:
|
||||
//
|
||||
// IOCTL_ST_REGISTER_IP_ADDRESSES
|
||||
// IOCTL_ST_GET_IP_ADDRESSES
|
||||
// IOCTL_ST_SET_CONFIGURATION
|
||||
// IOCTL_ST_GET_CONFIGURATION
|
||||
// IOCTL_ST_CLEAR_CONFIGURATION
|
||||
// IOCTL_ST_QUERY_PROCESS
|
||||
//
|
||||
|
||||
if (IoControlCode == IOCTL_ST_REGISTER_IP_ADDRESSES)
|
||||
{
|
||||
//
|
||||
// Potential state transition here.
|
||||
//
|
||||
auto status = ioctl::RegisterIpAddresses(device, Request);
|
||||
|
||||
WdfRequestComplete(Request, status);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (IoControlCode == IOCTL_ST_GET_IP_ADDRESSES)
|
||||
{
|
||||
ioctl::GetIpAddressesComplete(device, Request);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (IoControlCode == IOCTL_ST_SET_CONFIGURATION)
|
||||
{
|
||||
registeredimage::CONTEXT *imageset;
|
||||
|
||||
auto status = ioctl::SetConfigurationPrepare(Request, &imageset);
|
||||
|
||||
if (!NT_SUCCESS(status))
|
||||
{
|
||||
WdfRequestComplete(Request, status);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
//
|
||||
// Potential state transition here.
|
||||
//
|
||||
status = ioctl::SetConfiguration(device, imageset);
|
||||
|
||||
WdfRequestComplete(Request, status);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (IoControlCode == IOCTL_ST_GET_CONFIGURATION)
|
||||
{
|
||||
ioctl::GetConfigurationComplete(device, Request);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (IoControlCode == IOCTL_ST_CLEAR_CONFIGURATION)
|
||||
{
|
||||
//
|
||||
// Potential state transition here.
|
||||
//
|
||||
auto status = ioctl::ClearConfiguration(device);
|
||||
|
||||
WdfRequestComplete(Request, status);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (IoControlCode == IOCTL_ST_QUERY_PROCESS)
|
||||
{
|
||||
ioctl::QueryProcessComplete(device, Request);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
case ST_DRIVER_STATE_ZOMBIE:
|
||||
{
|
||||
DbgPrint("Zombie state: Rejecting all requests\n");
|
||||
|
||||
WdfRequestComplete(Request, STATUS_CANCELLED);
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
DbgPrint("Invalid IOCTL or not valid for current driver state\n");
|
||||
|
||||
WdfRequestComplete(Request, STATUS_INVALID_DEVICE_REQUEST);
|
||||
}
|
||||
|
||||
VOID
|
||||
StEvtDriverUnload
|
||||
(
|
||||
IN WDFDRIVER WdfDriver
|
||||
)
|
||||
{
|
||||
UNREFERENCED_PARAMETER(WdfDriver);
|
||||
|
||||
DbgPrint("Unloading Mullvad split tunnel driver\n");
|
||||
}
|
||||
213
src/eventing/builder.cpp
Normal file
213
src/eventing/builder.cpp
Normal file
@@ -0,0 +1,213 @@
|
||||
#include "builder.h"
|
||||
|
||||
namespace eventing
|
||||
{
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
bool
|
||||
BuildSplittingEvent
|
||||
(
|
||||
HANDLE ProcessId,
|
||||
ST_SPLITTING_STATUS_CHANGE_REASON Reason,
|
||||
LOWER_UNICODE_STRING *ImageName,
|
||||
bool Start,
|
||||
void **Buffer,
|
||||
size_t *BufferSize
|
||||
)
|
||||
{
|
||||
auto headerSize = FIELD_OFFSET(ST_EVENT_HEADER, EventData);
|
||||
auto eventSize = FIELD_OFFSET(ST_SPLITTING_EVENT, ImageName) + ImageName->Length;
|
||||
auto allocationSize = headerSize + eventSize;
|
||||
|
||||
auto buffer = ExAllocatePoolWithTag(NonPagedPool, allocationSize, ST_POOL_TAG);
|
||||
|
||||
if (buffer == NULL)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
auto header = (ST_EVENT_HEADER*)buffer;
|
||||
auto evt = (ST_SPLITTING_EVENT*)(((UCHAR*)buffer) + FIELD_OFFSET(ST_EVENT_HEADER, EventData));
|
||||
|
||||
header->EventId = (Start ? ST_EVENT_START_SPLITTING_PROCESS : ST_EVENT_STOP_SPLITTING_PROCESS);
|
||||
header->EventSize = eventSize;
|
||||
|
||||
evt->ProcessId = ProcessId;
|
||||
evt->Reason = Reason;
|
||||
evt->ImageNameLength = ImageName->Length;
|
||||
|
||||
RtlCopyMemory(evt->ImageName, ImageName->Buffer, ImageName->Length);
|
||||
|
||||
*Buffer = buffer;
|
||||
*BufferSize = allocationSize;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool
|
||||
BuildSplittingErrorEvent
|
||||
(
|
||||
HANDLE ProcessId,
|
||||
LOWER_UNICODE_STRING *ImageName,
|
||||
bool Start,
|
||||
void **Buffer,
|
||||
size_t *BufferSize
|
||||
)
|
||||
{
|
||||
auto headerSize = FIELD_OFFSET(ST_EVENT_HEADER, EventData);
|
||||
auto eventSize = FIELD_OFFSET(ST_SPLITTING_ERROR_EVENT, ImageName) + ImageName->Length;
|
||||
auto allocationSize = headerSize + eventSize;
|
||||
|
||||
auto buffer = ExAllocatePoolWithTag(NonPagedPool, allocationSize, ST_POOL_TAG);
|
||||
|
||||
if (buffer == NULL)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
auto header = (ST_EVENT_HEADER*)buffer;
|
||||
auto evt = (ST_SPLITTING_ERROR_EVENT*)(((UCHAR*)buffer) + FIELD_OFFSET(ST_EVENT_HEADER, EventData));
|
||||
|
||||
header->EventId = (Start ? ST_EVENT_ERROR_START_SPLITTING_PROCESS : ST_EVENT_ERROR_STOP_SPLITTING_PROCESS);
|
||||
header->EventSize = eventSize;
|
||||
|
||||
evt->ProcessId = ProcessId;
|
||||
evt->ImageNameLength = ImageName->Length;
|
||||
|
||||
RtlCopyMemory(evt->ImageName, ImageName->Buffer, ImageName->Length);
|
||||
|
||||
*Buffer = buffer;
|
||||
*BufferSize = allocationSize;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
RAW_EVENT*
|
||||
WrapEvent
|
||||
(
|
||||
void *Buffer,
|
||||
size_t BufferSize
|
||||
)
|
||||
{
|
||||
auto evt = (RAW_EVENT*)ExAllocatePoolWithTag(NonPagedPool, sizeof(RAW_EVENT), ST_POOL_TAG);
|
||||
|
||||
if (evt == NULL)
|
||||
{
|
||||
ExFreePoolWithTag(Buffer, ST_POOL_TAG);
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
evt->SListEntry.Next = NULL;
|
||||
evt->Buffer = Buffer;
|
||||
evt->BufferSize = BufferSize;
|
||||
|
||||
return evt;
|
||||
}
|
||||
|
||||
} // anonymous namespace
|
||||
|
||||
RAW_EVENT*
|
||||
BuildStartSplittingEvent
|
||||
(
|
||||
HANDLE ProcessId,
|
||||
ST_SPLITTING_STATUS_CHANGE_REASON Reason,
|
||||
LOWER_UNICODE_STRING *ImageName
|
||||
)
|
||||
{
|
||||
void *buffer;
|
||||
size_t bufferSize;
|
||||
|
||||
auto status = BuildSplittingEvent(ProcessId, Reason, ImageName, true, &buffer, &bufferSize);
|
||||
|
||||
if (!status)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return WrapEvent(buffer, bufferSize);
|
||||
}
|
||||
|
||||
RAW_EVENT*
|
||||
BuildStopSplittingEvent
|
||||
(
|
||||
HANDLE ProcessId,
|
||||
ST_SPLITTING_STATUS_CHANGE_REASON Reason,
|
||||
LOWER_UNICODE_STRING *ImageName
|
||||
)
|
||||
{
|
||||
void *buffer;
|
||||
size_t bufferSize;
|
||||
|
||||
auto status = BuildSplittingEvent(ProcessId, Reason, ImageName, false, &buffer, &bufferSize);
|
||||
|
||||
if (!status)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return WrapEvent(buffer, bufferSize);
|
||||
}
|
||||
|
||||
RAW_EVENT*
|
||||
BuildStartSplittingErrorEvent
|
||||
(
|
||||
HANDLE ProcessId,
|
||||
LOWER_UNICODE_STRING *ImageName
|
||||
)
|
||||
{
|
||||
void *buffer;
|
||||
size_t bufferSize;
|
||||
|
||||
auto status = BuildSplittingErrorEvent(ProcessId, ImageName, false, &buffer, &bufferSize);
|
||||
|
||||
if (!status)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return WrapEvent(buffer, bufferSize);
|
||||
}
|
||||
|
||||
RAW_EVENT*
|
||||
BuildStopSplittingErrorEvent
|
||||
(
|
||||
HANDLE ProcessId,
|
||||
LOWER_UNICODE_STRING *ImageName
|
||||
)
|
||||
{
|
||||
void *buffer;
|
||||
size_t bufferSize;
|
||||
|
||||
auto status = BuildSplittingErrorEvent(ProcessId, ImageName, false, &buffer, &bufferSize);
|
||||
|
||||
if (!status)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return WrapEvent(buffer, bufferSize);
|
||||
}
|
||||
|
||||
void
|
||||
ReleaseEvent
|
||||
(
|
||||
RAW_EVENT **Event
|
||||
)
|
||||
{
|
||||
auto evt = *Event;
|
||||
|
||||
if (evt == NULL)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
*Event = NULL;
|
||||
|
||||
ExFreePoolWithTag(evt->Buffer, ST_POOL_TAG);
|
||||
ExFreePoolWithTag(evt, ST_POOL_TAG);
|
||||
}
|
||||
|
||||
} // namespace eventing
|
||||
47
src/eventing/builder.h
Normal file
47
src/eventing/builder.h
Normal file
@@ -0,0 +1,47 @@
|
||||
#pragma once
|
||||
|
||||
#include <wdm.h>
|
||||
#include "../defs/events.h"
|
||||
#include "../defs/types.h"
|
||||
#include "eventing.h"
|
||||
|
||||
namespace eventing
|
||||
{
|
||||
|
||||
RAW_EVENT*
|
||||
BuildStartSplittingEvent
|
||||
(
|
||||
HANDLE ProcessId,
|
||||
ST_SPLITTING_STATUS_CHANGE_REASON Reason,
|
||||
LOWER_UNICODE_STRING *ImageName
|
||||
);
|
||||
|
||||
RAW_EVENT*
|
||||
BuildStopSplittingEvent
|
||||
(
|
||||
HANDLE ProcessId,
|
||||
ST_SPLITTING_STATUS_CHANGE_REASON Reason,
|
||||
LOWER_UNICODE_STRING *ImageName
|
||||
);
|
||||
|
||||
RAW_EVENT*
|
||||
BuildStartSplittingErrorEvent
|
||||
(
|
||||
HANDLE ProcessId,
|
||||
LOWER_UNICODE_STRING *ImageName
|
||||
);
|
||||
|
||||
RAW_EVENT*
|
||||
BuildStopSplittingErrorEvent
|
||||
(
|
||||
HANDLE ProcessId,
|
||||
LOWER_UNICODE_STRING *ImageName
|
||||
);
|
||||
|
||||
void
|
||||
ReleaseEvent
|
||||
(
|
||||
RAW_EVENT **Event
|
||||
);
|
||||
|
||||
} // namespace eventing
|
||||
19
src/eventing/context.h
Normal file
19
src/eventing/context.h
Normal file
@@ -0,0 +1,19 @@
|
||||
#pragma once
|
||||
|
||||
#include <wdm.h>
|
||||
#include <wdf.h>
|
||||
|
||||
namespace eventing
|
||||
{
|
||||
|
||||
struct CONTEXT
|
||||
{
|
||||
// Pended IOCTL requests for inverted call.
|
||||
WDFQUEUE RequestQueue;
|
||||
|
||||
KSPIN_LOCK EventQueueLock;
|
||||
|
||||
SLIST_HEADER EventQueue;
|
||||
};
|
||||
|
||||
} // namespace eventing
|
||||
239
src/eventing/eventing.cpp
Normal file
239
src/eventing/eventing.cpp
Normal file
@@ -0,0 +1,239 @@
|
||||
#include "eventing.h"
|
||||
#include "context.h"
|
||||
#include "builder.h"
|
||||
#include "../defs/types.h"
|
||||
|
||||
namespace eventing
|
||||
{
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
void CompleteRequestReleaseEvent
|
||||
(
|
||||
WDFREQUEST Request,
|
||||
void *RequestBuffer,
|
||||
RAW_EVENT *Event
|
||||
)
|
||||
{
|
||||
RtlCopyMemory(RequestBuffer, Event->Buffer, Event->BufferSize);
|
||||
|
||||
WdfRequestCompleteWithInformation(Request, STATUS_SUCCESS, Event->BufferSize);
|
||||
|
||||
ReleaseEvent(&Event);
|
||||
}
|
||||
|
||||
} // anonymous namespace
|
||||
|
||||
NTSTATUS
|
||||
Initialize
|
||||
(
|
||||
CONTEXT **Context,
|
||||
WDFDEVICE Device
|
||||
)
|
||||
{
|
||||
*Context = NULL;
|
||||
|
||||
auto context = (CONTEXT*)ExAllocatePoolWithTag(NonPagedPool, sizeof(CONTEXT), ST_POOL_TAG);
|
||||
|
||||
if (NULL == context)
|
||||
{
|
||||
return STATUS_INSUFFICIENT_RESOURCES;
|
||||
}
|
||||
|
||||
RtlZeroMemory(context, sizeof(*context));
|
||||
|
||||
InitializeSListHead(&context->EventQueue);
|
||||
KeInitializeSpinLock(&context->EventQueueLock);
|
||||
|
||||
WDF_IO_QUEUE_CONFIG queueConfig;
|
||||
|
||||
WDF_IO_QUEUE_CONFIG_INIT
|
||||
(
|
||||
&queueConfig,
|
||||
WdfIoQueueDispatchManual
|
||||
);
|
||||
|
||||
queueConfig.PowerManaged = WdfFalse;
|
||||
|
||||
auto status = WdfIoQueueCreate
|
||||
(
|
||||
Device,
|
||||
&queueConfig,
|
||||
WDF_NO_OBJECT_ATTRIBUTES,
|
||||
&context->RequestQueue
|
||||
);
|
||||
|
||||
if (!NT_SUCCESS(status))
|
||||
{
|
||||
DbgPrint("WdfIoQueueCreate() failed 0x%X\n", status);
|
||||
|
||||
ExFreePoolWithTag(context, ST_POOL_TAG);
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
*Context = context;
|
||||
|
||||
return STATUS_SUCCESS;
|
||||
}
|
||||
|
||||
void
|
||||
TearDown
|
||||
(
|
||||
CONTEXT **Context
|
||||
)
|
||||
{
|
||||
auto context = *Context;
|
||||
|
||||
RAW_EVENT *evt = NULL;
|
||||
|
||||
//
|
||||
// Discard and release all queued events.
|
||||
//
|
||||
|
||||
while (NULL != (evt = (RAW_EVENT*)ExInterlockedPopEntrySList(&context->EventQueue, &context->EventQueueLock)))
|
||||
{
|
||||
ReleaseEvent(&evt);
|
||||
}
|
||||
|
||||
//
|
||||
// Cancel all queued requests.
|
||||
//
|
||||
|
||||
WDFREQUEST pendedRequest;
|
||||
|
||||
for (;;)
|
||||
{
|
||||
auto status = WdfIoQueueRetrieveNextRequest(context->RequestQueue, &pendedRequest);
|
||||
|
||||
if (!NT_SUCCESS(status) || pendedRequest == NULL)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
WdfRequestComplete(pendedRequest, STATUS_CANCELLED);
|
||||
}
|
||||
|
||||
WdfObjectDelete(context->RequestQueue);
|
||||
|
||||
//
|
||||
// Release context.
|
||||
//
|
||||
|
||||
ExFreePoolWithTag(context, ST_POOL_TAG);
|
||||
|
||||
*Context = NULL;
|
||||
}
|
||||
|
||||
void
|
||||
Emit
|
||||
(
|
||||
CONTEXT *Context,
|
||||
RAW_EVENT **Event
|
||||
)
|
||||
{
|
||||
auto *evt = *Event;
|
||||
|
||||
if (evt == NULL)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
*Event = NULL;
|
||||
|
||||
WDFREQUEST pendedRequest;
|
||||
|
||||
void *buffer;
|
||||
|
||||
//
|
||||
// Look for a pended request with a correctly sized buffer.
|
||||
//
|
||||
// Fail all requests we encounter that have tiny buffers.
|
||||
// User mode should know better.
|
||||
//
|
||||
|
||||
for (;;)
|
||||
{
|
||||
auto status = WdfIoQueueRetrieveNextRequest(Context->RequestQueue, &pendedRequest);
|
||||
|
||||
if (!NT_SUCCESS(status) || pendedRequest == NULL)
|
||||
{
|
||||
ExInterlockedPushEntrySList(&Context->EventQueue, &evt->SListEntry, &Context->EventQueueLock);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
status = WdfRequestRetrieveOutputBuffer
|
||||
(
|
||||
pendedRequest,
|
||||
evt->BufferSize,
|
||||
&buffer,
|
||||
NULL
|
||||
);
|
||||
|
||||
if (NT_SUCCESS(status))
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
WdfRequestComplete(pendedRequest, status);
|
||||
}
|
||||
|
||||
CompleteRequestReleaseEvent(pendedRequest, buffer, evt);
|
||||
}
|
||||
|
||||
void
|
||||
CollectOne
|
||||
(
|
||||
CONTEXT *Context,
|
||||
WDFREQUEST Request
|
||||
)
|
||||
{
|
||||
auto evt = (RAW_EVENT*)ExInterlockedPopEntrySList(&Context->EventQueue, &Context->EventQueueLock);
|
||||
|
||||
if (evt == NULL)
|
||||
{
|
||||
auto status = WdfRequestForwardToIoQueue(Request, Context->RequestQueue);
|
||||
|
||||
if (!NT_SUCCESS(status))
|
||||
{
|
||||
DbgPrint("Failed to pend event request\n");
|
||||
|
||||
WdfRequestComplete(Request, STATUS_INTERNAL_ERROR);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
//
|
||||
// Acquire and validate request buffer.
|
||||
//
|
||||
|
||||
void *buffer;
|
||||
|
||||
auto status = WdfRequestRetrieveOutputBuffer
|
||||
(
|
||||
Request,
|
||||
evt->BufferSize,
|
||||
&buffer,
|
||||
NULL
|
||||
);
|
||||
|
||||
if (!NT_SUCCESS(status))
|
||||
{
|
||||
WdfRequestComplete(Request, status);
|
||||
|
||||
//
|
||||
// Put the event back.
|
||||
//
|
||||
|
||||
ExInterlockedPushEntrySList(&Context->EventQueue, &evt->SListEntry, &Context->EventQueueLock);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
CompleteRequestReleaseEvent(Request, buffer, evt);
|
||||
}
|
||||
|
||||
} // eventing
|
||||
61
src/eventing/eventing.h
Normal file
61
src/eventing/eventing.h
Normal file
@@ -0,0 +1,61 @@
|
||||
#pragma once
|
||||
|
||||
#include <wdm.h>
|
||||
#include <wdf.h>
|
||||
|
||||
namespace eventing
|
||||
{
|
||||
|
||||
struct CONTEXT;
|
||||
|
||||
NTSTATUS
|
||||
Initialize
|
||||
(
|
||||
CONTEXT **Context,
|
||||
WDFDEVICE Device
|
||||
);
|
||||
|
||||
void
|
||||
TearDown
|
||||
(
|
||||
CONTEXT **Context
|
||||
);
|
||||
|
||||
struct RAW_EVENT
|
||||
{
|
||||
SLIST_ENTRY SListEntry;
|
||||
|
||||
size_t BufferSize;
|
||||
|
||||
void *Buffer;
|
||||
};
|
||||
|
||||
//
|
||||
// Emit()
|
||||
//
|
||||
// Takes ownership of passed event.
|
||||
//
|
||||
// If possible, sends the event to user mode immediately.
|
||||
// Otherwise queues the event for later dispatching.
|
||||
//
|
||||
void
|
||||
Emit
|
||||
(
|
||||
CONTEXT *Context,
|
||||
RAW_EVENT **Evt
|
||||
);
|
||||
|
||||
//
|
||||
// CollectOne()
|
||||
//
|
||||
// Collects a single event and completes the request.
|
||||
// Or pends the request if there are no queued events.
|
||||
//
|
||||
void
|
||||
CollectOne
|
||||
(
|
||||
CONTEXT *Context,
|
||||
WDFREQUEST Request
|
||||
);
|
||||
|
||||
} // namespace eventing
|
||||
310
src/firewall/asyncbind.cpp
Normal file
310
src/firewall/asyncbind.cpp
Normal file
@@ -0,0 +1,310 @@
|
||||
#include "asyncbind.h"
|
||||
#include "../util.h"
|
||||
|
||||
namespace firewall
|
||||
{
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
const ULONGLONG RECORD_MAX_LIFETIME_MS = 10000;
|
||||
|
||||
bool
|
||||
FailBindRequest
|
||||
(
|
||||
HANDLE ProcessId,
|
||||
FWPS_CLASSIFY_OUT0 *ClassifyOut,
|
||||
UINT64 ClassifyHandle,
|
||||
UINT64 FilterId,
|
||||
bool Ipv4
|
||||
)
|
||||
{
|
||||
DbgPrint("Failing bind request from process %p\n", ProcessId);
|
||||
|
||||
//
|
||||
// There doesn't seem to be any support in WFP for blocking a bind request.
|
||||
// Specifying `FWP_ACTION_BLOCK` will just resume request processing.
|
||||
// So the best we can do is rewrite the bind to do as little harm as possible.
|
||||
//
|
||||
|
||||
FWPS_BIND_REQUEST0 *bindRequest = NULL;
|
||||
|
||||
auto status = FwpsAcquireWritableLayerDataPointer0
|
||||
(
|
||||
ClassifyHandle,
|
||||
FilterId,
|
||||
0,
|
||||
(PVOID*)&bindRequest,
|
||||
ClassifyOut
|
||||
);
|
||||
|
||||
if (!NT_SUCCESS(status))
|
||||
{
|
||||
DbgPrint("FwpsAcquireWritableLayerDataPointer0() failed 0x%X\n", status);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
ClassifyOut->actionType = FWP_ACTION_PERMIT;
|
||||
ClassifyOut->rights &= ~FWPS_RIGHT_ACTION_WRITE;
|
||||
|
||||
if (Ipv4)
|
||||
{
|
||||
auto bindTarget = (SOCKADDR_IN*)&(bindRequest->localAddressAndPort);
|
||||
|
||||
IN_ADDR localhost;
|
||||
|
||||
localhost.S_un.S_un_b.s_b1 = 127;
|
||||
localhost.S_un.S_un_b.s_b2 = 0;
|
||||
localhost.S_un.S_un_b.s_b3 = 0;
|
||||
localhost.S_un.S_un_b.s_b4 = 1;
|
||||
|
||||
bindTarget->sin_addr = localhost;
|
||||
}
|
||||
else
|
||||
{
|
||||
auto bindTarget = (SOCKADDR_IN6*)&(bindRequest->localAddressAndPort);
|
||||
|
||||
IN6_ADDR localhost;
|
||||
|
||||
localhost.u.Word[0] = 0;
|
||||
localhost.u.Word[1] = 0;
|
||||
localhost.u.Word[2] = 0;
|
||||
localhost.u.Word[3] = 0;
|
||||
localhost.u.Word[4] = 0;
|
||||
localhost.u.Word[5] = 0;
|
||||
localhost.u.Word[6] = 0;
|
||||
localhost.u.Word[7] = htons(USHORT(1));
|
||||
|
||||
bindTarget->sin6_addr = localhost;
|
||||
}
|
||||
|
||||
FwpsApplyModifiedLayerData0(ClassifyHandle, (PVOID*)&bindRequest, 0);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void
|
||||
ReauthPendedBindRequest
|
||||
(
|
||||
PENDED_BIND *Record
|
||||
)
|
||||
{
|
||||
DbgPrint("Requesting re-auth for bind request from process %p\n", Record->ProcessId);
|
||||
|
||||
FwpsCompleteClassify0(Record->ClassifyHandle, 0, NULL);
|
||||
FwpsReleaseClassifyHandle0(Record->ClassifyHandle);
|
||||
|
||||
ExFreePoolWithTag(Record, ST_POOL_TAG);
|
||||
}
|
||||
|
||||
void
|
||||
FailPendedBindRequest
|
||||
(
|
||||
PENDED_BIND *Record
|
||||
)
|
||||
{
|
||||
const auto status = FailBindRequest(Record->ProcessId, &Record->ClassifyOut,
|
||||
Record->ClassifyHandle, Record->FilterId, Record->Ipv4);
|
||||
|
||||
if (!status)
|
||||
{
|
||||
//
|
||||
// At this point there are basically two options:
|
||||
//
|
||||
// #1 Leak the bind request to prevent it from successfully binding to the tunnel interface.
|
||||
// #2 Request a re-auth of the bind request.
|
||||
//
|
||||
// We choose to implement #2 in order to retry the processing.
|
||||
//
|
||||
|
||||
ReauthPendedBindRequest(Record);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
FwpsCompleteClassify0(Record->ClassifyHandle, 0, &Record->ClassifyOut);
|
||||
FwpsReleaseClassifyHandle0(Record->ClassifyHandle);
|
||||
|
||||
ExFreePoolWithTag(Record, ST_POOL_TAG);
|
||||
}
|
||||
|
||||
} // anonymous namespace
|
||||
|
||||
NTSTATUS
|
||||
PendBindRequest
|
||||
(
|
||||
CONTEXT *Context,
|
||||
HANDLE ProcessId,
|
||||
void *ClassifyContext,
|
||||
UINT64 FilterId,
|
||||
FWPS_CLASSIFY_OUT0 *ClassifyOut,
|
||||
bool Ipv4
|
||||
)
|
||||
{
|
||||
DbgPrint("Pending bind request from process %p\n", ProcessId);
|
||||
|
||||
auto record = (PENDED_BIND*)
|
||||
ExAllocatePoolWithTag(NonPagedPool, sizeof(PENDED_BIND), ST_POOL_TAG);
|
||||
|
||||
if (record == NULL)
|
||||
{
|
||||
return STATUS_INSUFFICIENT_RESOURCES;
|
||||
}
|
||||
|
||||
UINT64 classifyHandle;
|
||||
|
||||
auto status = FwpsAcquireClassifyHandle0(ClassifyContext, 0, &classifyHandle);
|
||||
|
||||
if (!NT_SUCCESS(status))
|
||||
{
|
||||
ExFreePoolWithTag(record, ST_POOL_TAG);
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
status = FwpsPendClassify0(classifyHandle, FilterId, 0, ClassifyOut);
|
||||
|
||||
if (!NT_SUCCESS(status))
|
||||
{
|
||||
FwpsReleaseClassifyHandle0(classifyHandle);
|
||||
|
||||
ExFreePoolWithTag(record, ST_POOL_TAG);
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
record->ProcessId = ProcessId;
|
||||
record->Timestamp = KeQueryInterruptTime();
|
||||
record->ClassifyHandle = classifyHandle;
|
||||
record->ClassifyOut = *ClassifyOut;
|
||||
record->FilterId = FilterId;
|
||||
record->Ipv4 = Ipv4;
|
||||
|
||||
WdfWaitLockAcquire(Context->PendedBinds.Lock, NULL);
|
||||
|
||||
InsertTailList(&Context->PendedBinds.Records, &record->ListEntry);
|
||||
|
||||
WdfWaitLockRelease(Context->PendedBinds.Lock);
|
||||
|
||||
return STATUS_SUCCESS;
|
||||
}
|
||||
|
||||
void
|
||||
FailBindRequest
|
||||
(
|
||||
HANDLE ProcessId,
|
||||
void *ClassifyContext,
|
||||
UINT64 FilterId,
|
||||
FWPS_CLASSIFY_OUT0 *ClassifyOut,
|
||||
bool Ipv4
|
||||
)
|
||||
{
|
||||
UINT64 classifyHandle = 0;
|
||||
|
||||
auto status = FwpsAcquireClassifyHandle0
|
||||
(
|
||||
ClassifyContext,
|
||||
0,
|
||||
&classifyHandle
|
||||
);
|
||||
|
||||
if (!NT_SUCCESS(status))
|
||||
{
|
||||
DbgPrint("FwpsAcquireClassifyHandle0() failed 0x%X\n", status);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
FailBindRequest(ProcessId, ClassifyOut, classifyHandle, FilterId, Ipv4);
|
||||
|
||||
FwpsReleaseClassifyHandle0(classifyHandle);
|
||||
}
|
||||
|
||||
void
|
||||
HandleProcessEvent
|
||||
(
|
||||
HANDLE ProcessId,
|
||||
bool Arriving,
|
||||
void *Context
|
||||
)
|
||||
{
|
||||
auto context = (CONTEXT*)Context;
|
||||
|
||||
auto timeNow = KeQueryInterruptTime();
|
||||
|
||||
static const ULONGLONG MS_TO_100NS_FACTOR = 10000;
|
||||
|
||||
auto maxAge = RECORD_MAX_LIFETIME_MS * MS_TO_100NS_FACTOR;
|
||||
|
||||
//
|
||||
// Iterate over all pended bind requests.
|
||||
//
|
||||
// Fail all requests that are too old.
|
||||
// Re-auth all requests that belong to the arriving process.
|
||||
//
|
||||
|
||||
WdfWaitLockAcquire(context->PendedBinds.Lock, NULL);
|
||||
|
||||
for (auto rawRecord = context->PendedBinds.Records.Flink;
|
||||
rawRecord != &context->PendedBinds.Records;
|
||||
/* no post-condition */)
|
||||
{
|
||||
auto record = (PENDED_BIND*)rawRecord;
|
||||
|
||||
rawRecord = rawRecord->Flink;
|
||||
|
||||
auto timeDelta = timeNow - record->Timestamp;
|
||||
|
||||
if (timeDelta > maxAge)
|
||||
{
|
||||
RemoveEntryList(&record->ListEntry);
|
||||
|
||||
FailPendedBindRequest(record);
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if (record->ProcessId != ProcessId)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
RemoveEntryList(&record->ListEntry);
|
||||
|
||||
if (Arriving)
|
||||
{
|
||||
ReauthPendedBindRequest(record);
|
||||
}
|
||||
else
|
||||
{
|
||||
FailPendedBindRequest(record);
|
||||
}
|
||||
}
|
||||
|
||||
WdfWaitLockRelease(context->PendedBinds.Lock);
|
||||
}
|
||||
|
||||
void
|
||||
FailPendedBinds
|
||||
(
|
||||
CONTEXT *Context
|
||||
)
|
||||
{
|
||||
auto context = (CONTEXT*)Context;
|
||||
|
||||
for (auto rawRecord = context->PendedBinds.Records.Flink;
|
||||
rawRecord != &context->PendedBinds.Records;
|
||||
/* no post-condition */)
|
||||
{
|
||||
auto record = (PENDED_BIND*)rawRecord;
|
||||
|
||||
rawRecord = rawRecord->Flink;
|
||||
|
||||
RemoveEntryList(&record->ListEntry);
|
||||
|
||||
FailPendedBindRequest(record);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace firewall
|
||||
45
src/firewall/asyncbind.h
Normal file
45
src/firewall/asyncbind.h
Normal file
@@ -0,0 +1,45 @@
|
||||
#pragma once
|
||||
|
||||
#include "wfp.h"
|
||||
#include <wdf.h>
|
||||
#include "context.h"
|
||||
|
||||
namespace firewall
|
||||
{
|
||||
|
||||
NTSTATUS
|
||||
PendBindRequest
|
||||
(
|
||||
CONTEXT *Context,
|
||||
HANDLE ProcessId,
|
||||
void *ClassifyContext,
|
||||
UINT64 FilterId,
|
||||
FWPS_CLASSIFY_OUT0 *ClassifyOut,
|
||||
bool Ipv4
|
||||
);
|
||||
|
||||
void
|
||||
FailBindRequest
|
||||
(
|
||||
HANDLE ProcessId,
|
||||
void *ClassifyContext,
|
||||
UINT64 FilterId,
|
||||
FWPS_CLASSIFY_OUT0 *ClassifyOut,
|
||||
bool Ipv4
|
||||
);
|
||||
|
||||
void
|
||||
HandleProcessEvent
|
||||
(
|
||||
HANDLE ProcessId,
|
||||
bool Arriving,
|
||||
void *Context
|
||||
);
|
||||
|
||||
void
|
||||
FailPendedBinds
|
||||
(
|
||||
CONTEXT *Context
|
||||
);
|
||||
|
||||
} // namespace firewall
|
||||
1254
src/firewall/blocking.cpp
Normal file
1254
src/firewall/blocking.cpp
Normal file
File diff suppressed because it is too large
Load Diff
119
src/firewall/blocking.h
Normal file
119
src/firewall/blocking.h
Normal file
@@ -0,0 +1,119 @@
|
||||
#pragma once
|
||||
|
||||
#include <wdm.h>
|
||||
#include <inaddr.h>
|
||||
#include <in6addr.h>
|
||||
#include "../defs/types.h"
|
||||
|
||||
namespace firewall::blocking
|
||||
{
|
||||
|
||||
NTSTATUS
|
||||
Initialize
|
||||
(
|
||||
HANDLE WfpSession,
|
||||
void **Context
|
||||
);
|
||||
|
||||
void
|
||||
TearDown
|
||||
(
|
||||
void **Context
|
||||
);
|
||||
|
||||
//
|
||||
// ResetTx2()
|
||||
//
|
||||
// Remove all app specific blocking filters.
|
||||
// Remove generic IPv6 blocking if active.
|
||||
//
|
||||
// IMPORTANT: This function needs to be running inside a WFP transaction as well as a
|
||||
// local transaction managed by this module.
|
||||
//
|
||||
NTSTATUS
|
||||
ResetTx2
|
||||
(
|
||||
void *Context
|
||||
);
|
||||
|
||||
NTSTATUS
|
||||
TransactionBegin
|
||||
(
|
||||
void *Context
|
||||
);
|
||||
|
||||
void
|
||||
TransactionCommit
|
||||
(
|
||||
void *Context
|
||||
);
|
||||
|
||||
void
|
||||
TransactionAbort
|
||||
(
|
||||
void *Context
|
||||
);
|
||||
|
||||
//
|
||||
// RegisterFilterBlockSplitAppTx2()
|
||||
//
|
||||
// Register WFP filters, with linked callout, that will block connections in the tunnel
|
||||
// from applications being split.
|
||||
//
|
||||
// This is used to block existing connections inside the tunnel for applications that are
|
||||
// just now being split.
|
||||
//
|
||||
// IMPORTANT: These functions need to be running inside a WFP transaction as well as a
|
||||
// local transaction managed by this module.
|
||||
//
|
||||
NTSTATUS
|
||||
RegisterFilterBlockSplitAppTx2
|
||||
(
|
||||
void *Context,
|
||||
const LOWER_UNICODE_STRING *ImageName,
|
||||
const IN_ADDR *TunnelIpv4,
|
||||
const IN6_ADDR *TunnelIpv6
|
||||
);
|
||||
|
||||
NTSTATUS
|
||||
RemoveFilterBlockSplitAppTx2
|
||||
(
|
||||
void *Context,
|
||||
const LOWER_UNICODE_STRING *ImageName
|
||||
);
|
||||
|
||||
//
|
||||
// RegisterFilterBlockSplitAppsTunnelIpv6Tx()
|
||||
//
|
||||
// Block all tunnel IPv6 traffic for applications being split.
|
||||
// To be used when the physical adapter doesn't have an IPv6 interface.
|
||||
//
|
||||
NTSTATUS
|
||||
RegisterFilterBlockSplitAppsIpv6Tx
|
||||
(
|
||||
void *Context
|
||||
);
|
||||
|
||||
NTSTATUS
|
||||
RemoveFilterBlockSplitAppsIpv6Tx
|
||||
(
|
||||
void *Context
|
||||
);
|
||||
|
||||
//
|
||||
// UpdateBlockingFiltersTx2()
|
||||
//
|
||||
// Rewrite filters with updated IP addresses.
|
||||
//
|
||||
// IMPORTANT: This function needs to be running inside a WFP transaction as well as a
|
||||
// local transaction managed by this module.
|
||||
//
|
||||
NTSTATUS
|
||||
UpdateBlockingFiltersTx2
|
||||
(
|
||||
void *Context,
|
||||
const IN_ADDR *TunnelIpv4,
|
||||
const IN6_ADDR *TunnelIpv6
|
||||
);
|
||||
|
||||
} // namespace firewall::blocking
|
||||
740
src/firewall/callouts.cpp
Normal file
740
src/firewall/callouts.cpp
Normal file
@@ -0,0 +1,740 @@
|
||||
#include "wfp.h"
|
||||
#include "firewall.h"
|
||||
#include "context.h"
|
||||
#include "identifiers.h"
|
||||
#include "splitting.h"
|
||||
#include "asyncbind.h"
|
||||
#include "callouts.h"
|
||||
|
||||
namespace firewall
|
||||
{
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
//
|
||||
// NotifyFilterAttach()
|
||||
//
|
||||
// Receive notifications about filters attaching/detaching the callout.
|
||||
//
|
||||
NTSTATUS
|
||||
NotifyFilterAttach
|
||||
(
|
||||
FWPS_CALLOUT_NOTIFY_TYPE notifyType,
|
||||
const GUID *filterKey,
|
||||
FWPS_FILTER1 *filter
|
||||
)
|
||||
{
|
||||
UNREFERENCED_PARAMETER(notifyType);
|
||||
UNREFERENCED_PARAMETER(filterKey);
|
||||
UNREFERENCED_PARAMETER(filter);
|
||||
|
||||
return STATUS_SUCCESS;
|
||||
}
|
||||
|
||||
NTSTATUS
|
||||
RegisterCalloutTx
|
||||
(
|
||||
PDEVICE_OBJECT DeviceObject,
|
||||
HANDLE WfpSession,
|
||||
FWPS_CALLOUT_CLASSIFY_FN1 Callout,
|
||||
const GUID *CalloutKey,
|
||||
const GUID *LayerKey,
|
||||
const wchar_t *CalloutName,
|
||||
const wchar_t* CalloutDescription
|
||||
)
|
||||
{
|
||||
//
|
||||
// Logically, this is the wrong order, but it results in cleaner code.
|
||||
// You're encouraged to first register the callout and then add it.
|
||||
//
|
||||
// However, what's currently here is fully supported:
|
||||
//
|
||||
// `By default filters that reference callouts that have been added
|
||||
// but have not yet registered with the filter engine are treated as Block filters.`
|
||||
//
|
||||
|
||||
FWPM_CALLOUT0 callout;
|
||||
|
||||
RtlZeroMemory(&callout, sizeof(callout));
|
||||
|
||||
callout.calloutKey = *CalloutKey;
|
||||
callout.displayData.name = const_cast<wchar_t *>(CalloutName);
|
||||
callout.displayData.description = const_cast<wchar_t *>(CalloutDescription);
|
||||
callout.flags = FWPM_CALLOUT_FLAG_USES_PROVIDER_CONTEXT;
|
||||
callout.providerKey = const_cast<GUID *>(&ST_FW_PROVIDER_KEY);
|
||||
callout.applicableLayer = *LayerKey;
|
||||
|
||||
auto status = FwpmCalloutAdd0(WfpSession, &callout, NULL, NULL);
|
||||
|
||||
if (!NT_SUCCESS(status))
|
||||
{
|
||||
return status;
|
||||
}
|
||||
|
||||
FWPS_CALLOUT1 aCallout = { 0 };
|
||||
|
||||
aCallout.calloutKey = *CalloutKey;
|
||||
aCallout.classifyFn = Callout;
|
||||
aCallout.notifyFn = NotifyFilterAttach;
|
||||
aCallout.flowDeleteFn = NULL;
|
||||
|
||||
return FwpsCalloutRegister1(DeviceObject, &aCallout, NULL);
|
||||
}
|
||||
|
||||
void
|
||||
ClassifyUnknownBind
|
||||
(
|
||||
CONTEXT *Context,
|
||||
HANDLE ProcessId,
|
||||
UINT64 FilterId,
|
||||
const void *ClassifyContext,
|
||||
FWPS_CLASSIFY_OUT0 *ClassifyOut,
|
||||
bool Ipv4
|
||||
)
|
||||
{
|
||||
//
|
||||
// Pend the bind and wait for process to become known and classified.
|
||||
//
|
||||
|
||||
auto status = PendBindRequest
|
||||
(
|
||||
Context,
|
||||
ProcessId,
|
||||
const_cast<void*>(ClassifyContext),
|
||||
FilterId,
|
||||
ClassifyOut,
|
||||
Ipv4
|
||||
);
|
||||
|
||||
if (NT_SUCCESS(status))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
DbgPrint("Could not pend bind request from process %p, blocking instead\n", ProcessId);
|
||||
|
||||
FailBindRequest
|
||||
(
|
||||
ProcessId,
|
||||
const_cast<void*>(ClassifyContext),
|
||||
FilterId,
|
||||
ClassifyOut,
|
||||
Ipv4
|
||||
);
|
||||
}
|
||||
|
||||
//
|
||||
// CalloutClassifyBind()
|
||||
//
|
||||
// Entry point for splitting traffic.
|
||||
// Check whether the binding process is marked for having its traffic split.
|
||||
//
|
||||
// FWPS_LAYER_ALE_BIND_REDIRECT_V4
|
||||
// FWPS_LAYER_ALE_BIND_REDIRECT_V6
|
||||
//
|
||||
void
|
||||
CalloutClassifyBind
|
||||
(
|
||||
const FWPS_INCOMING_VALUES0 *FixedValues,
|
||||
const FWPS_INCOMING_METADATA_VALUES0 *MetaValues,
|
||||
void *LayerData,
|
||||
const void *ClassifyContext,
|
||||
const FWPS_FILTER1 *Filter,
|
||||
UINT64 FlowContext,
|
||||
FWPS_CLASSIFY_OUT0 *ClassifyOut
|
||||
)
|
||||
{
|
||||
UNREFERENCED_PARAMETER(LayerData);
|
||||
UNREFERENCED_PARAMETER(FlowContext);
|
||||
|
||||
NT_ASSERT
|
||||
(
|
||||
FixedValues->layerId == FWPS_LAYER_ALE_BIND_REDIRECT_V4
|
||||
|| FixedValues->layerId == FWPS_LAYER_ALE_BIND_REDIRECT_V6
|
||||
);
|
||||
|
||||
NT_ASSERT
|
||||
(
|
||||
Filter->providerContext != NULL
|
||||
&& Filter->providerContext->type == FWPM_GENERAL_CONTEXT
|
||||
&& Filter->providerContext->dataBuffer->size == sizeof(CONTEXT*)
|
||||
);
|
||||
|
||||
auto context = *(CONTEXT**)Filter->providerContext->dataBuffer->data;
|
||||
|
||||
if (0 == (ClassifyOut->rights & FWPS_RIGHT_ACTION_WRITE))
|
||||
{
|
||||
DbgPrint("Aborting bind processing because hard permit/block already applied\n");
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (ClassifyOut->actionType == FWP_ACTION_NONE)
|
||||
{
|
||||
ClassifyOut->actionType = FWP_ACTION_CONTINUE;
|
||||
}
|
||||
|
||||
if (!FWPS_IS_METADATA_FIELD_PRESENT(MetaValues, FWPS_METADATA_FIELD_PROCESS_ID))
|
||||
{
|
||||
DbgPrint("Failed to classify bind because PID was not provided\n");
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
const CALLBACKS &callbacks = context->Callbacks;
|
||||
|
||||
const auto verdict = callbacks.QueryProcess(HANDLE(MetaValues->processId), callbacks.Context);
|
||||
|
||||
switch (verdict)
|
||||
{
|
||||
case PROCESS_SPLIT_VERDICT::DO_SPLIT:
|
||||
{
|
||||
RewriteBind
|
||||
(
|
||||
context,
|
||||
FixedValues,
|
||||
MetaValues,
|
||||
Filter->filterId,
|
||||
ClassifyContext,
|
||||
ClassifyOut
|
||||
);
|
||||
|
||||
break;
|
||||
}
|
||||
case PROCESS_SPLIT_VERDICT::UNKNOWN:
|
||||
{
|
||||
ClassifyUnknownBind
|
||||
(
|
||||
context,
|
||||
HANDLE(MetaValues->processId),
|
||||
Filter->filterId,
|
||||
ClassifyContext,
|
||||
ClassifyOut,
|
||||
FixedValues->layerId == FWPS_LAYER_ALE_BIND_REDIRECT_V4
|
||||
);
|
||||
|
||||
break;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
bool IsAleReauthorize
|
||||
(
|
||||
const FWPS_INCOMING_VALUES *FixedValues
|
||||
)
|
||||
{
|
||||
size_t index;
|
||||
|
||||
switch (FixedValues->layerId)
|
||||
{
|
||||
case FWPS_LAYER_ALE_AUTH_CONNECT_V4:
|
||||
{
|
||||
index = FWPS_FIELD_ALE_AUTH_CONNECT_V4_FLAGS;
|
||||
break;
|
||||
}
|
||||
case FWPS_LAYER_ALE_AUTH_RECV_ACCEPT_V4:
|
||||
{
|
||||
index = FWPS_FIELD_ALE_AUTH_RECV_ACCEPT_V4_FLAGS;
|
||||
break;
|
||||
}
|
||||
case FWPS_LAYER_ALE_AUTH_CONNECT_V6:
|
||||
{
|
||||
index = FWPS_FIELD_ALE_AUTH_CONNECT_V6_FLAGS;
|
||||
break;
|
||||
}
|
||||
case FWPS_LAYER_ALE_AUTH_RECV_ACCEPT_V6:
|
||||
{
|
||||
index = FWPS_FIELD_ALE_AUTH_RECV_ACCEPT_V6_FLAGS;
|
||||
break;
|
||||
}
|
||||
default:
|
||||
{
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
const auto flags = FixedValues->incomingValue[index].value.uint32;
|
||||
|
||||
return ((flags & FWP_CONDITION_FLAG_IS_REAUTHORIZE) != 0);
|
||||
}
|
||||
|
||||
//
|
||||
// CalloutPermitSplitApps()
|
||||
//
|
||||
// For processes being split, the bind will have already been moved off the
|
||||
// tunnel interface.
|
||||
//
|
||||
// So now it's only a matter of approving the connection.
|
||||
//
|
||||
// FWPS_LAYER_ALE_AUTH_CONNECT_V4
|
||||
// FWPS_LAYER_ALE_AUTH_CONNECT_V6
|
||||
// FWPS_LAYER_ALE_AUTH_RECV_ACCEPT_V4
|
||||
// FWPS_LAYER_ALE_AUTH_RECV_ACCEPT_V6
|
||||
//
|
||||
void
|
||||
CalloutPermitSplitApps
|
||||
(
|
||||
const FWPS_INCOMING_VALUES0 *FixedValues,
|
||||
const FWPS_INCOMING_METADATA_VALUES0 *MetaValues,
|
||||
void *LayerData,
|
||||
const void *ClassifyContext,
|
||||
const FWPS_FILTER1 *Filter,
|
||||
UINT64 FlowContext,
|
||||
FWPS_CLASSIFY_OUT0 *ClassifyOut
|
||||
)
|
||||
{
|
||||
#if !DBG
|
||||
UNREFERENCED_PARAMETER(FixedValues);
|
||||
#endif
|
||||
UNREFERENCED_PARAMETER(LayerData);
|
||||
UNREFERENCED_PARAMETER(ClassifyContext);
|
||||
UNREFERENCED_PARAMETER(Filter);
|
||||
UNREFERENCED_PARAMETER(FlowContext);
|
||||
|
||||
NT_ASSERT
|
||||
(
|
||||
FixedValues->layerId == FWPS_LAYER_ALE_AUTH_CONNECT_V4
|
||||
|| FixedValues->layerId == FWPS_LAYER_ALE_AUTH_CONNECT_V6
|
||||
|| FixedValues->layerId == FWPS_LAYER_ALE_AUTH_RECV_ACCEPT_V4
|
||||
|| FixedValues->layerId == FWPS_LAYER_ALE_AUTH_RECV_ACCEPT_V6
|
||||
);
|
||||
|
||||
NT_ASSERT
|
||||
(
|
||||
Filter->providerContext != NULL
|
||||
&& Filter->providerContext->type == FWPM_GENERAL_CONTEXT
|
||||
&& Filter->providerContext->dataBuffer->size == sizeof(CONTEXT*)
|
||||
);
|
||||
|
||||
auto context = *(CONTEXT**)Filter->providerContext->dataBuffer->data;
|
||||
|
||||
if (0 == (ClassifyOut->rights & FWPS_RIGHT_ACTION_WRITE))
|
||||
{
|
||||
DbgPrint("Aborting connection processing because hard permit/block already applied\n");
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (ClassifyOut->actionType == FWP_ACTION_NONE)
|
||||
{
|
||||
ClassifyOut->actionType = FWP_ACTION_CONTINUE;
|
||||
}
|
||||
|
||||
if (!FWPS_IS_METADATA_FIELD_PRESENT(MetaValues, FWPS_METADATA_FIELD_PROCESS_ID))
|
||||
{
|
||||
DbgPrint("Failed to classify connection because PID was not provided\n");
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
const CALLBACKS &callbacks = context->Callbacks;
|
||||
|
||||
const auto verdict = callbacks.QueryProcess(HANDLE(MetaValues->processId), callbacks.Context);
|
||||
|
||||
if (verdict == PROCESS_SPLIT_VERDICT::DO_SPLIT)
|
||||
{
|
||||
DbgPrint("APPROVING CONNECTION\n");
|
||||
|
||||
ClassifyOut->actionType = FWP_ACTION_PERMIT;
|
||||
ClassifyOut->rights &= ~FWPS_RIGHT_ACTION_WRITE;
|
||||
}
|
||||
else
|
||||
{
|
||||
#if DBG
|
||||
if (IsAleReauthorize(FixedValues))
|
||||
{
|
||||
DbgPrint("[CalloutPermitSplitApps] Reauthorized connection (PID: %p) is not explicitly "\
|
||||
"approved by callout\n", HANDLE(MetaValues->processId));
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// CalloutBlockSplitApps()
|
||||
//
|
||||
// For processes just now being split, it could be the case that they have existing
|
||||
// long-lived connections inside the tunnel.
|
||||
//
|
||||
// These connections need to be blocked to ensure the process exists on
|
||||
// only one side of the tunnel.
|
||||
//
|
||||
// FWPS_LAYER_ALE_AUTH_CONNECT_V4
|
||||
// FWPS_LAYER_ALE_AUTH_CONNECT_V6
|
||||
// FWPS_LAYER_ALE_AUTH_RECV_ACCEPT_V4
|
||||
// FWPS_LAYER_ALE_AUTH_RECV_ACCEPT_V6
|
||||
//
|
||||
void
|
||||
CalloutBlockSplitApps
|
||||
(
|
||||
const FWPS_INCOMING_VALUES0 *FixedValues,
|
||||
const FWPS_INCOMING_METADATA_VALUES0 *MetaValues,
|
||||
void *LayerData,
|
||||
const void *ClassifyContext,
|
||||
const FWPS_FILTER1 *Filter,
|
||||
UINT64 FlowContext,
|
||||
FWPS_CLASSIFY_OUT0 *ClassifyOut
|
||||
)
|
||||
{
|
||||
#if !DBG
|
||||
UNREFERENCED_PARAMETER(FixedValues);
|
||||
#endif
|
||||
UNREFERENCED_PARAMETER(LayerData);
|
||||
UNREFERENCED_PARAMETER(ClassifyContext);
|
||||
UNREFERENCED_PARAMETER(Filter);
|
||||
UNREFERENCED_PARAMETER(FlowContext);
|
||||
|
||||
NT_ASSERT
|
||||
(
|
||||
FixedValues->layerId == FWPS_LAYER_ALE_AUTH_CONNECT_V4
|
||||
|| FixedValues->layerId == FWPS_LAYER_ALE_AUTH_CONNECT_V6
|
||||
|| FixedValues->layerId == FWPS_LAYER_ALE_AUTH_RECV_ACCEPT_V4
|
||||
|| FixedValues->layerId == FWPS_LAYER_ALE_AUTH_RECV_ACCEPT_V6
|
||||
);
|
||||
|
||||
NT_ASSERT
|
||||
(
|
||||
Filter->providerContext != NULL
|
||||
&& Filter->providerContext->type == FWPM_GENERAL_CONTEXT
|
||||
&& Filter->providerContext->dataBuffer->size == sizeof(CONTEXT*)
|
||||
);
|
||||
|
||||
auto context = *(CONTEXT**)Filter->providerContext->dataBuffer->data;
|
||||
|
||||
if (0 == (ClassifyOut->rights & FWPS_RIGHT_ACTION_WRITE))
|
||||
{
|
||||
DbgPrint("Aborting connection processing because hard permit/block already applied\n");
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (ClassifyOut->actionType == FWP_ACTION_NONE)
|
||||
{
|
||||
ClassifyOut->actionType = FWP_ACTION_CONTINUE;
|
||||
}
|
||||
|
||||
if (!FWPS_IS_METADATA_FIELD_PRESENT(MetaValues, FWPS_METADATA_FIELD_PROCESS_ID))
|
||||
{
|
||||
DbgPrint("Failed to classify connection because PID was not provided\n");
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
const CALLBACKS &callbacks = context->Callbacks;
|
||||
|
||||
const auto verdict = callbacks.QueryProcess(HANDLE(MetaValues->processId), callbacks.Context);
|
||||
|
||||
if (verdict == PROCESS_SPLIT_VERDICT::DO_SPLIT)
|
||||
{
|
||||
DbgPrint("BLOCKING CONNECTION\n");
|
||||
|
||||
ClassifyOut->actionType = FWP_ACTION_BLOCK;
|
||||
ClassifyOut->rights &= ~FWPS_RIGHT_ACTION_WRITE;
|
||||
}
|
||||
else
|
||||
{
|
||||
#if DBG
|
||||
if (IsAleReauthorize(FixedValues))
|
||||
{
|
||||
DbgPrint("[CalloutBlockSplitApps] Reauthorized connection (PID: %p) is not explicitly "\
|
||||
"blocked by callout\n", HANDLE(MetaValues->processId));
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
} // anonymous namespace
|
||||
|
||||
//
|
||||
// RegisterCalloutClassifyBindTx()
|
||||
//
|
||||
// Register callout with WFP. In all applicable layers.
|
||||
//
|
||||
// "Tx" (in transaction) suffix means there is no clean-up in failure paths.
|
||||
//
|
||||
NTSTATUS
|
||||
RegisterCalloutClassifyBindTx
|
||||
(
|
||||
PDEVICE_OBJECT DeviceObject,
|
||||
HANDLE WfpSession
|
||||
)
|
||||
{
|
||||
auto status = RegisterCalloutTx
|
||||
(
|
||||
DeviceObject,
|
||||
WfpSession,
|
||||
CalloutClassifyBind,
|
||||
&ST_FW_CALLOUT_CLASSIFY_BIND_IPV4_KEY,
|
||||
&FWPM_LAYER_ALE_BIND_REDIRECT_V4,
|
||||
L"Mullvad Split Tunnel Bind Redirect Callout (IPv4)",
|
||||
L"Redirects certain binds away from tunnel interface"
|
||||
);
|
||||
|
||||
if (!NT_SUCCESS(status))
|
||||
{
|
||||
return status;
|
||||
}
|
||||
|
||||
status = RegisterCalloutTx
|
||||
(
|
||||
DeviceObject,
|
||||
WfpSession,
|
||||
CalloutClassifyBind,
|
||||
&ST_FW_CALLOUT_CLASSIFY_BIND_IPV6_KEY,
|
||||
&FWPM_LAYER_ALE_BIND_REDIRECT_V6,
|
||||
L"Mullvad Split Tunnel Bind Redirect Callout (IPv6)",
|
||||
L"Redirects certain binds away from tunnel interface"
|
||||
);
|
||||
|
||||
if (!NT_SUCCESS(status))
|
||||
{
|
||||
UnregisterCalloutClassifyBind();
|
||||
}
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
NTSTATUS
|
||||
UnregisterCalloutClassifyBind
|
||||
(
|
||||
)
|
||||
{
|
||||
#define RETURN_IF_FAILED(status) \
|
||||
if (!NT_SUCCESS(status) && status != STATUS_FWP_CALLOUT_NOT_FOUND) \
|
||||
{ \
|
||||
return status; \
|
||||
}
|
||||
|
||||
auto s1 = FwpsCalloutUnregisterByKey0(&ST_FW_CALLOUT_CLASSIFY_BIND_IPV4_KEY);
|
||||
auto s2 = FwpsCalloutUnregisterByKey0(&ST_FW_CALLOUT_CLASSIFY_BIND_IPV6_KEY);
|
||||
|
||||
RETURN_IF_FAILED(s1)
|
||||
RETURN_IF_FAILED(s2)
|
||||
|
||||
return STATUS_SUCCESS;
|
||||
}
|
||||
|
||||
//
|
||||
// RegisterCalloutPermitSplitAppsTx()
|
||||
//
|
||||
// Register callout with WFP. In all applicable layers.
|
||||
//
|
||||
// "Tx" (in transaction) suffix means there is no clean-up in failure paths.
|
||||
//
|
||||
NTSTATUS
|
||||
RegisterCalloutPermitSplitAppsTx
|
||||
(
|
||||
PDEVICE_OBJECT DeviceObject,
|
||||
HANDLE WfpSession
|
||||
)
|
||||
{
|
||||
auto status = RegisterCalloutTx
|
||||
(
|
||||
DeviceObject,
|
||||
WfpSession,
|
||||
CalloutPermitSplitApps,
|
||||
&ST_FW_CALLOUT_PERMIT_SPLIT_APPS_IPV4_CONN_KEY,
|
||||
&FWPM_LAYER_ALE_AUTH_CONNECT_V4,
|
||||
L"Mullvad Split Tunnel Permitting Callout (IPv4)",
|
||||
L"Permits selected connections outside the tunnel"
|
||||
);
|
||||
|
||||
if (!NT_SUCCESS(status))
|
||||
{
|
||||
return status;
|
||||
}
|
||||
|
||||
status = RegisterCalloutTx
|
||||
(
|
||||
DeviceObject,
|
||||
WfpSession,
|
||||
CalloutPermitSplitApps,
|
||||
&ST_FW_CALLOUT_PERMIT_SPLIT_APPS_IPV4_RECV_KEY,
|
||||
&FWPM_LAYER_ALE_AUTH_RECV_ACCEPT_V4,
|
||||
L"Mullvad Split Tunnel Permitting Callout (IPv4)",
|
||||
L"Permits selected connections outside the tunnel"
|
||||
);
|
||||
|
||||
if (!NT_SUCCESS(status))
|
||||
{
|
||||
UnregisterCalloutPermitSplitApps();
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
status = RegisterCalloutTx
|
||||
(
|
||||
DeviceObject,
|
||||
WfpSession,
|
||||
CalloutPermitSplitApps,
|
||||
&ST_FW_CALLOUT_PERMIT_SPLIT_APPS_IPV6_CONN_KEY,
|
||||
&FWPM_LAYER_ALE_AUTH_CONNECT_V6,
|
||||
L"Mullvad Split Tunnel Permitting Callout (IPv6)",
|
||||
L"Permits selected connections outside the tunnel"
|
||||
);
|
||||
|
||||
if (!NT_SUCCESS(status))
|
||||
{
|
||||
UnregisterCalloutPermitSplitApps();
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
status = RegisterCalloutTx
|
||||
(
|
||||
DeviceObject,
|
||||
WfpSession,
|
||||
CalloutPermitSplitApps,
|
||||
&ST_FW_CALLOUT_PERMIT_SPLIT_APPS_IPV6_RECV_KEY,
|
||||
&FWPM_LAYER_ALE_AUTH_RECV_ACCEPT_V6,
|
||||
L"Mullvad Split Tunnel Permitting Callout (IPv6)",
|
||||
L"Permits selected connections outside the tunnel"
|
||||
);
|
||||
|
||||
if (!NT_SUCCESS(status))
|
||||
{
|
||||
UnregisterCalloutPermitSplitApps();
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
return STATUS_SUCCESS;
|
||||
}
|
||||
|
||||
NTSTATUS
|
||||
UnregisterCalloutPermitSplitApps
|
||||
(
|
||||
)
|
||||
{
|
||||
#define RETURN_IF_FAILED(status) \
|
||||
if (!NT_SUCCESS(status) && status != STATUS_FWP_CALLOUT_NOT_FOUND) \
|
||||
{ \
|
||||
return status; \
|
||||
}
|
||||
|
||||
auto s1 = FwpsCalloutUnregisterByKey0(&ST_FW_CALLOUT_PERMIT_SPLIT_APPS_IPV4_CONN_KEY);
|
||||
auto s2 = FwpsCalloutUnregisterByKey0(&ST_FW_CALLOUT_PERMIT_SPLIT_APPS_IPV4_RECV_KEY);
|
||||
auto s3 = FwpsCalloutUnregisterByKey0(&ST_FW_CALLOUT_PERMIT_SPLIT_APPS_IPV6_CONN_KEY);
|
||||
auto s4 = FwpsCalloutUnregisterByKey0(&ST_FW_CALLOUT_PERMIT_SPLIT_APPS_IPV6_RECV_KEY);
|
||||
|
||||
RETURN_IF_FAILED(s1);
|
||||
RETURN_IF_FAILED(s2);
|
||||
RETURN_IF_FAILED(s3);
|
||||
RETURN_IF_FAILED(s4);
|
||||
|
||||
return STATUS_SUCCESS;
|
||||
}
|
||||
|
||||
//
|
||||
// RegisterCalloutBlockSplitAppsTx()
|
||||
//
|
||||
// Register callout with WFP. In all applicable layers.
|
||||
//
|
||||
// "Tx" (in transaction) suffix means there is no clean-up in failure paths.
|
||||
//
|
||||
NTSTATUS
|
||||
RegisterCalloutBlockSplitAppsTx
|
||||
(
|
||||
PDEVICE_OBJECT DeviceObject,
|
||||
HANDLE WfpSession
|
||||
)
|
||||
{
|
||||
auto status = RegisterCalloutTx
|
||||
(
|
||||
DeviceObject,
|
||||
WfpSession,
|
||||
CalloutBlockSplitApps,
|
||||
&ST_FW_CALLOUT_BLOCK_SPLIT_APPS_IPV4_CONN_KEY,
|
||||
&FWPM_LAYER_ALE_AUTH_CONNECT_V4,
|
||||
L"Mullvad Split Tunnel Blocking Callout (IPv4)",
|
||||
L"Blocks unwanted connections in relation to splitting"
|
||||
);
|
||||
|
||||
if (!NT_SUCCESS(status))
|
||||
{
|
||||
return status;
|
||||
}
|
||||
|
||||
status = RegisterCalloutTx
|
||||
(
|
||||
DeviceObject,
|
||||
WfpSession,
|
||||
CalloutBlockSplitApps,
|
||||
&ST_FW_CALLOUT_BLOCK_SPLIT_APPS_IPV4_RECV_KEY,
|
||||
&FWPM_LAYER_ALE_AUTH_RECV_ACCEPT_V4,
|
||||
L"Mullvad Split Tunnel Blocking Callout (IPv4)",
|
||||
L"Blocks unwanted connections in relation to splitting"
|
||||
);
|
||||
|
||||
if (!NT_SUCCESS(status))
|
||||
{
|
||||
UnregisterCalloutBlockSplitApps();
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
status = RegisterCalloutTx
|
||||
(
|
||||
DeviceObject,
|
||||
WfpSession,
|
||||
CalloutBlockSplitApps,
|
||||
&ST_FW_CALLOUT_BLOCK_SPLIT_APPS_IPV6_CONN_KEY,
|
||||
&FWPM_LAYER_ALE_AUTH_CONNECT_V6,
|
||||
L"Mullvad Split Tunnel Blocking Callout (IPv6)",
|
||||
L"Blocks unwanted connections in relation to splitting"
|
||||
);
|
||||
|
||||
if (!NT_SUCCESS(status))
|
||||
{
|
||||
UnregisterCalloutBlockSplitApps();
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
status = RegisterCalloutTx
|
||||
(
|
||||
DeviceObject,
|
||||
WfpSession,
|
||||
CalloutBlockSplitApps,
|
||||
&ST_FW_CALLOUT_BLOCK_SPLIT_APPS_IPV6_RECV_KEY,
|
||||
&FWPM_LAYER_ALE_AUTH_RECV_ACCEPT_V6,
|
||||
L"Mullvad Split Tunnel Blocking Callout (IPv6)",
|
||||
L"Blocks unwanted connections in relation to splitting"
|
||||
);
|
||||
|
||||
if (!NT_SUCCESS(status))
|
||||
{
|
||||
UnregisterCalloutBlockSplitApps();
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
return STATUS_SUCCESS;
|
||||
}
|
||||
|
||||
NTSTATUS
|
||||
UnregisterCalloutBlockSplitApps
|
||||
(
|
||||
)
|
||||
{
|
||||
#define RETURN_IF_FAILED(status) \
|
||||
if (!NT_SUCCESS(status) && status != STATUS_FWP_CALLOUT_NOT_FOUND) \
|
||||
{ \
|
||||
return status; \
|
||||
}
|
||||
|
||||
auto s1 = FwpsCalloutUnregisterByKey0(&ST_FW_CALLOUT_BLOCK_SPLIT_APPS_IPV4_CONN_KEY);
|
||||
auto s2 = FwpsCalloutUnregisterByKey0(&ST_FW_CALLOUT_BLOCK_SPLIT_APPS_IPV4_RECV_KEY);
|
||||
auto s3 = FwpsCalloutUnregisterByKey0(&ST_FW_CALLOUT_BLOCK_SPLIT_APPS_IPV6_CONN_KEY);
|
||||
auto s4 = FwpsCalloutUnregisterByKey0(&ST_FW_CALLOUT_BLOCK_SPLIT_APPS_IPV6_RECV_KEY);
|
||||
|
||||
RETURN_IF_FAILED(s1);
|
||||
RETURN_IF_FAILED(s2);
|
||||
RETURN_IF_FAILED(s3);
|
||||
RETURN_IF_FAILED(s4);
|
||||
|
||||
return STATUS_SUCCESS;
|
||||
}
|
||||
|
||||
} // namespace firewall
|
||||
44
src/firewall/callouts.h
Normal file
44
src/firewall/callouts.h
Normal file
@@ -0,0 +1,44 @@
|
||||
#pragma once
|
||||
|
||||
#include <wdm.h>
|
||||
|
||||
namespace firewall
|
||||
{
|
||||
|
||||
NTSTATUS
|
||||
RegisterCalloutClassifyBindTx
|
||||
(
|
||||
PDEVICE_OBJECT DeviceObject,
|
||||
HANDLE WfpSession
|
||||
);
|
||||
|
||||
NTSTATUS
|
||||
UnregisterCalloutClassifyBind
|
||||
(
|
||||
);
|
||||
|
||||
NTSTATUS
|
||||
RegisterCalloutPermitSplitAppsTx
|
||||
(
|
||||
PDEVICE_OBJECT DeviceObject,
|
||||
HANDLE WfpSession
|
||||
);
|
||||
|
||||
NTSTATUS
|
||||
UnregisterCalloutPermitSplitApps
|
||||
(
|
||||
);
|
||||
|
||||
NTSTATUS
|
||||
RegisterCalloutBlockSplitAppsTx
|
||||
(
|
||||
PDEVICE_OBJECT DeviceObject,
|
||||
HANDLE WfpSession
|
||||
);
|
||||
|
||||
NTSTATUS
|
||||
UnregisterCalloutBlockSplitApps
|
||||
(
|
||||
);
|
||||
|
||||
} // namespace firewall
|
||||
11
src/firewall/constants.h
Normal file
11
src/firewall/constants.h
Normal file
@@ -0,0 +1,11 @@
|
||||
#pragma once
|
||||
|
||||
#include <wdm.h>
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
static const UINT64 ST_MAX_FILTER_WEIGHT = MAXUINT64;
|
||||
static const UINT64 ST_HIGH_FILTER_WEIGHT = MAXUINT64 - 10;
|
||||
|
||||
} // anonymous namespace
|
||||
106
src/firewall/context.h
Normal file
106
src/firewall/context.h
Normal file
@@ -0,0 +1,106 @@
|
||||
#pragma once
|
||||
|
||||
#include <wdm.h>
|
||||
#include <wdf.h>
|
||||
#include "firewall.h"
|
||||
#include "../ipaddr.h"
|
||||
#include "../procbroker/procbroker.h"
|
||||
|
||||
namespace firewall
|
||||
{
|
||||
|
||||
enum class IPV6_ACTION
|
||||
{
|
||||
//
|
||||
// There's an IPv6 address on both of the adapters we're working with.
|
||||
// Split all IPV6 traffic.
|
||||
//
|
||||
SPLIT,
|
||||
|
||||
//
|
||||
// Only the tunnel adapter has an IPV6 address.
|
||||
// Block all IPv6 traffic to avoid it leaking inside the tunnel.
|
||||
//
|
||||
BLOCK,
|
||||
|
||||
//
|
||||
// Only the internet connected adapter has an IPv6 address, or none
|
||||
// of the adapters have one.
|
||||
//
|
||||
// Take no action.
|
||||
//
|
||||
NONE
|
||||
};
|
||||
|
||||
struct IP_ADDRESSES_MGMT
|
||||
{
|
||||
WDFWAITLOCK Lock;
|
||||
ST_IP_ADDRESSES Addresses;
|
||||
IPV6_ACTION Ipv6Action;
|
||||
};
|
||||
|
||||
struct PENDED_BIND
|
||||
{
|
||||
LIST_ENTRY ListEntry;
|
||||
|
||||
// Process that is trying to bind.
|
||||
HANDLE ProcessId;
|
||||
|
||||
// Timestamp when record was created.
|
||||
ULONGLONG Timestamp;
|
||||
|
||||
// Handle used to trigger re-auth or resume request processing.
|
||||
UINT64 ClassifyHandle;
|
||||
|
||||
// Classification data for when we don't want a re-auth
|
||||
// but instead wish to break and deny the bind.
|
||||
FWPS_CLASSIFY_OUT0 ClassifyOut;
|
||||
|
||||
// The filter that triggered the classification.
|
||||
UINT64 FilterId;
|
||||
|
||||
// Whether this is an IPv4 or IPv6 bind.
|
||||
bool Ipv4;
|
||||
};
|
||||
|
||||
struct PENDED_BIND_MGMT
|
||||
{
|
||||
WDFWAITLOCK Lock;
|
||||
LIST_ENTRY Records;
|
||||
};
|
||||
|
||||
struct TRANSACTION_MGMT
|
||||
{
|
||||
// Lock that is held for the duration of a transaction.
|
||||
WDFWAITLOCK Lock;
|
||||
|
||||
// Indicator of active transaction.
|
||||
bool Active;
|
||||
|
||||
// Thread ID of transaction owner.
|
||||
HANDLE OwnerId;
|
||||
};
|
||||
|
||||
struct CONTEXT
|
||||
{
|
||||
bool SplittingEnabled;
|
||||
|
||||
CALLBACKS Callbacks;
|
||||
|
||||
HANDLE WfpSession;
|
||||
|
||||
IP_ADDRESSES_MGMT IpAddresses;
|
||||
|
||||
PENDED_BIND_MGMT PendedBinds;
|
||||
|
||||
procbroker::CONTEXT *ProcessEventBroker;
|
||||
|
||||
TRANSACTION_MGMT Transaction;
|
||||
|
||||
//
|
||||
// Context used with the blocking subsystem.
|
||||
//
|
||||
void *BlockingContext;
|
||||
};
|
||||
|
||||
} // namespace firewall
|
||||
1005
src/firewall/firewall.cpp
Normal file
1005
src/firewall/firewall.cpp
Normal file
File diff suppressed because it is too large
Load Diff
117
src/firewall/firewall.h
Normal file
117
src/firewall/firewall.h
Normal file
@@ -0,0 +1,117 @@
|
||||
#pragma once
|
||||
|
||||
#include <wdm.h>
|
||||
#include "../ipaddr.h"
|
||||
#include "../defs/types.h"
|
||||
#include "../procbroker/procbroker.h"
|
||||
|
||||
namespace firewall
|
||||
{
|
||||
|
||||
struct CONTEXT;
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// Callback definitions.
|
||||
// Client(s) of the firewall subsystem provide the implementations.
|
||||
//
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
enum class PROCESS_SPLIT_VERDICT
|
||||
{
|
||||
DO_SPLIT,
|
||||
DONT_SPLIT,
|
||||
|
||||
// PID is unknown
|
||||
UNKNOWN
|
||||
};
|
||||
|
||||
typedef
|
||||
PROCESS_SPLIT_VERDICT
|
||||
(NTAPI *QUERY_PROCESS_FUNC)
|
||||
(
|
||||
HANDLE ProcessId,
|
||||
void *Context
|
||||
);
|
||||
|
||||
typedef struct tag_CALLBACKS
|
||||
{
|
||||
QUERY_PROCESS_FUNC QueryProcess;
|
||||
void *Context;
|
||||
}
|
||||
CALLBACKS;
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// Public functions.
|
||||
//
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
NTSTATUS
|
||||
Initialize
|
||||
(
|
||||
CONTEXT **Context,
|
||||
PDEVICE_OBJECT DeviceObject,
|
||||
const CALLBACKS *Callbacks,
|
||||
procbroker::CONTEXT *ProcessEventBroker
|
||||
);
|
||||
|
||||
NTSTATUS
|
||||
TearDown
|
||||
(
|
||||
CONTEXT **Context
|
||||
);
|
||||
|
||||
NTSTATUS
|
||||
EnableSplitting
|
||||
(
|
||||
CONTEXT *Context,
|
||||
const ST_IP_ADDRESSES *IpAddresses
|
||||
);
|
||||
|
||||
NTSTATUS
|
||||
DisableSplitting
|
||||
(
|
||||
CONTEXT *Context
|
||||
);
|
||||
|
||||
NTSTATUS
|
||||
RegisterUpdatedIpAddresses
|
||||
(
|
||||
CONTEXT *Context,
|
||||
const ST_IP_ADDRESSES *IpAddresses
|
||||
);
|
||||
|
||||
NTSTATUS
|
||||
TransactionBegin
|
||||
(
|
||||
CONTEXT *Context
|
||||
);
|
||||
|
||||
NTSTATUS
|
||||
TransactionCommit
|
||||
(
|
||||
CONTEXT *Context
|
||||
);
|
||||
|
||||
NTSTATUS
|
||||
TransactionAbort
|
||||
(
|
||||
CONTEXT *Context
|
||||
);
|
||||
|
||||
NTSTATUS
|
||||
RegisterAppBecomingSplitTx2
|
||||
(
|
||||
CONTEXT *Context,
|
||||
const LOWER_UNICODE_STRING *ImageName
|
||||
);
|
||||
|
||||
NTSTATUS
|
||||
RegisterAppBecomingUnsplitTx2
|
||||
(
|
||||
CONTEXT *Context,
|
||||
const LOWER_UNICODE_STRING *ImageName
|
||||
);
|
||||
|
||||
} // namespace firewall
|
||||
97
src/firewall/identifiers.h
Normal file
97
src/firewall/identifiers.h
Normal file
@@ -0,0 +1,97 @@
|
||||
#pragma once
|
||||
|
||||
#include <initguid.h>
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// Identifiers used with WFP.
|
||||
//
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
// {E2C114EE-F32A-4264-A6CB-3FA7996356D9}
|
||||
DEFINE_GUID(ST_FW_PROVIDER_KEY,
|
||||
0xe2c114ee, 0xf32a, 0x4264, 0xa6, 0xcb, 0x3f, 0xa7, 0x99, 0x63, 0x56, 0xd9);
|
||||
|
||||
// {76653805-1972-45D1-B47C-3140AEBABC49}
|
||||
DEFINE_GUID(ST_FW_CALLOUT_CLASSIFY_BIND_IPV4_KEY,
|
||||
0x76653805, 0x1972, 0x45d1, 0xb4, 0x7c, 0x31, 0x40, 0xae, 0xba, 0xbc, 0x49);
|
||||
|
||||
// {53FB3120-B6A4-462B-BFFC-6978AADA1DA2}
|
||||
DEFINE_GUID(ST_FW_CALLOUT_CLASSIFY_BIND_IPV6_KEY,
|
||||
0x53fb3120, 0xb6a4, 0x462b, 0xbf, 0xfc, 0x69, 0x78, 0xaa, 0xda, 0x1d, 0xa2);
|
||||
|
||||
// {33F3EDCC-EB5E-41CF-9250-702C94A28E39}
|
||||
DEFINE_GUID(ST_FW_CALLOUT_PERMIT_SPLIT_APPS_IPV4_CONN_KEY,
|
||||
0x33f3edcc, 0xeb5e, 0x41cf, 0x92, 0x50, 0x70, 0x2c, 0x94, 0xa2, 0x8e, 0x39);
|
||||
|
||||
// {A7A13809-0DE6-48AB-9BB8-20A8BCEC37AB}
|
||||
DEFINE_GUID(ST_FW_CALLOUT_PERMIT_SPLIT_APPS_IPV4_RECV_KEY,
|
||||
0xa7a13809, 0xde6, 0x48ab, 0x9b, 0xb8, 0x20, 0xa8, 0xbc, 0xec, 0x37, 0xab);
|
||||
|
||||
// {7B7E0055-89F5-4760-8928-CCD57C8830AB}
|
||||
DEFINE_GUID(ST_FW_CALLOUT_PERMIT_SPLIT_APPS_IPV6_CONN_KEY,
|
||||
0x7b7e0055, 0x89f5, 0x4760, 0x89, 0x28, 0xcc, 0xd5, 0x7c, 0x88, 0x30, 0xab);
|
||||
|
||||
// {B40B78EF-5642-40EF-AC4D-F9651261F9E7}
|
||||
DEFINE_GUID(ST_FW_CALLOUT_PERMIT_SPLIT_APPS_IPV6_RECV_KEY,
|
||||
0xb40b78ef, 0x5642, 0x40ef, 0xac, 0x4d, 0xf9, 0x65, 0x12, 0x61, 0xf9, 0xe7);
|
||||
|
||||
// {974AA588-397A-483E-AC29-88F4F4112AC2}
|
||||
DEFINE_GUID(ST_FW_CALLOUT_BLOCK_SPLIT_APPS_IPV4_CONN_KEY,
|
||||
0x974aa588, 0x397a, 0x483e, 0xac, 0x29, 0x88, 0xf4, 0xf4, 0x11, 0x2a, 0xc2);
|
||||
|
||||
// {8E314FD7-BDD3-45A4-A712-46036B25B3E1}
|
||||
DEFINE_GUID(ST_FW_CALLOUT_BLOCK_SPLIT_APPS_IPV4_RECV_KEY,
|
||||
0x8e314fd7, 0xbdd3, 0x45a4, 0xa7, 0x12, 0x46, 0x3, 0x6b, 0x25, 0xb3, 0xe1);
|
||||
|
||||
// {466B7800-5EF4-4772-AA79-E0A834328214}
|
||||
DEFINE_GUID(ST_FW_CALLOUT_BLOCK_SPLIT_APPS_IPV6_CONN_KEY,
|
||||
0x466b7800, 0x5ef4, 0x4772, 0xaa, 0x79, 0xe0, 0xa8, 0x34, 0x32, 0x82, 0x14);
|
||||
|
||||
// {D25AFB1B-4645-43CB-B0BE-3794FE487BAC}
|
||||
DEFINE_GUID(ST_FW_CALLOUT_BLOCK_SPLIT_APPS_IPV6_RECV_KEY,
|
||||
0xd25afb1b, 0x4645, 0x43cb, 0xb0, 0xbe, 0x37, 0x94, 0xfe, 0x48, 0x7b, 0xac);
|
||||
|
||||
// {B47D14A7-AEED-48B9-AD4E-5529619F1337}
|
||||
DEFINE_GUID(ST_FW_FILTER_CLASSIFY_BIND_IPV4_KEY,
|
||||
0xb47d14a7, 0xaeed, 0x48b9, 0xad, 0x4e, 0x55, 0x29, 0x61, 0x9f, 0x13, 0x37);
|
||||
|
||||
// {2F607222-B2EB-443C-B6E0-641067375478}
|
||||
DEFINE_GUID(ST_FW_FILTER_CLASSIFY_BIND_IPV6_KEY,
|
||||
0x2f607222, 0xb2eb, 0x443c, 0xb6, 0xe0, 0x64, 0x10, 0x67, 0x37, 0x54, 0x78);
|
||||
|
||||
// {66CED079-C270-4B4D-A45C-D11711C0D600}
|
||||
DEFINE_GUID(ST_FW_FILTER_PERMIT_SPLIT_APPS_IPV4_CONN_KEY,
|
||||
0x66ced079, 0xc270, 0x4b4d, 0xa4, 0x5c, 0xd1, 0x17, 0x11, 0xc0, 0xd6, 0x0);
|
||||
|
||||
// {37972155-EBDB-49FC-9A37-3A0B3B0AA100}
|
||||
DEFINE_GUID(ST_FW_FILTER_PERMIT_SPLIT_APPS_IPV4_RECV_KEY,
|
||||
0x37972155, 0xebdb, 0x49fc, 0x9a, 0x37, 0x3a, 0xb, 0x3b, 0xa, 0xa1, 0x0);
|
||||
|
||||
// {0AFA08E3-B010-4082-9E03-1CC4BE1C6CF8}
|
||||
DEFINE_GUID(ST_FW_FILTER_PERMIT_SPLIT_APPS_IPV6_CONN_KEY,
|
||||
0xafa08e3, 0xb010, 0x4082, 0x9e, 0x3, 0x1c, 0xc4, 0xbe, 0x1c, 0x6c, 0xf8);
|
||||
|
||||
// {7835DFD7-24AE-44F4-8A8A-5E9C766AAE63}
|
||||
DEFINE_GUID(ST_FW_FILTER_PERMIT_SPLIT_APPS_IPV6_RECV_KEY,
|
||||
0x7835dfd7, 0x24ae, 0x44f4, 0x8a, 0x8a, 0x5e, 0x9c, 0x76, 0x6a, 0xae, 0x63);
|
||||
|
||||
// {05CB3C5E-6F64-44F7-81B1-C890563FA280}
|
||||
DEFINE_GUID(ST_FW_FILTER_BLOCK_ALL_SPLIT_APPS_IPV6_CONN_KEY,
|
||||
0x5cb3c5e, 0x6f64, 0x44f7, 0x81, 0xb1, 0xc8, 0x90, 0x56, 0x3f, 0xa2, 0x80);
|
||||
|
||||
// {C854E73A-81C8-4814-9A55-55BAF2C3BD17}
|
||||
DEFINE_GUID(ST_FW_FILTER_BLOCK_ALL_SPLIT_APPS_IPV6_RECV_KEY,
|
||||
0xc854e73a, 0x81c8, 0x4814, 0x9a, 0x55, 0x55, 0xba, 0xf2, 0xc3, 0xbd, 0x17);
|
||||
|
||||
//
|
||||
// This sublayer is defined and registered by `winfw`.
|
||||
// We're going to reuse it to avoid having different sublayers fight over
|
||||
// whether something should be blocked or permitted.
|
||||
//
|
||||
DEFINE_GUID(ST_FW_WINFW_BASELINE_SUBLAYER_KEY,
|
||||
0xc78056ff, 0x2bc1, 0x4211, 0xaa, 0xdd, 0x7f, 0x35, 0x8d, 0xef, 0x20, 0x2d);
|
||||
|
||||
// {FDC95593-04EF-415C-AE68-46BD8B4821A8}
|
||||
DEFINE_GUID(ST_FW_PROVIDER_CONTEXT_KEY,
|
||||
0xfdc95593, 0x4ef, 0x415c, 0xae, 0x68, 0x46, 0xbd, 0x8b, 0x48, 0x21, 0xa8);
|
||||
420
src/firewall/splitting.cpp
Normal file
420
src/firewall/splitting.cpp
Normal file
@@ -0,0 +1,420 @@
|
||||
#include "../util.h"
|
||||
#include "identifiers.h"
|
||||
#include "constants.h"
|
||||
#include "splitting.h"
|
||||
|
||||
namespace firewall
|
||||
{
|
||||
|
||||
//
|
||||
// RewriteBind()
|
||||
//
|
||||
// This is where the splitting happens.
|
||||
// Move socket binds from tunnel interface to the internet connected interface.
|
||||
//
|
||||
void
|
||||
RewriteBind
|
||||
(
|
||||
CONTEXT *Context,
|
||||
const FWPS_INCOMING_VALUES0 *FixedValues,
|
||||
const FWPS_INCOMING_METADATA_VALUES0 *MetaValues,
|
||||
UINT64 FilterId,
|
||||
const void *ClassifyContext,
|
||||
FWPS_CLASSIFY_OUT0 *ClassifyOut
|
||||
)
|
||||
{
|
||||
UNREFERENCED_PARAMETER(MetaValues);
|
||||
|
||||
UINT64 classifyHandle = 0;
|
||||
|
||||
auto status = FwpsAcquireClassifyHandle0
|
||||
(
|
||||
const_cast<void*>(ClassifyContext),
|
||||
0,
|
||||
&classifyHandle
|
||||
);
|
||||
|
||||
if (!NT_SUCCESS(status))
|
||||
{
|
||||
DbgPrint("FwpsAcquireClassifyHandle0() failed 0x%X\n", status);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
FWPS_BIND_REQUEST0 *bindRequest = NULL;
|
||||
|
||||
status = FwpsAcquireWritableLayerDataPointer0
|
||||
(
|
||||
classifyHandle,
|
||||
FilterId,
|
||||
0,
|
||||
(PVOID*)&bindRequest,
|
||||
ClassifyOut
|
||||
);
|
||||
|
||||
if (!NT_SUCCESS(status))
|
||||
{
|
||||
DbgPrint("FwpsAcquireWritableLayerDataPointer0() failed 0x%X\n", status);
|
||||
|
||||
goto Cleanup_handle;
|
||||
}
|
||||
|
||||
//
|
||||
// According to documentation, FwpsAcquireWritableLayerDataPointer0() will update the
|
||||
// `actionType` and `rights` fields with poorly chosen values:
|
||||
//
|
||||
// ```
|
||||
// classifyOut->actionType = FWP_ACTION_BLOCK
|
||||
// classifyOut->rights &= ~FWPS_RIGHT_ACTION_WRITE
|
||||
// ```
|
||||
//
|
||||
// However, in practice it seems to not make any changes to those fields.
|
||||
// But if it did we'd want to ensure the fields have sane values.
|
||||
//
|
||||
|
||||
ClassifyOut->actionType = FWP_ACTION_CONTINUE;
|
||||
ClassifyOut->rights |= FWPS_RIGHT_ACTION_WRITE;
|
||||
|
||||
//
|
||||
// There's a list with redirection history.
|
||||
//
|
||||
// This only ever comes into play if several callouts are fighting to redirect the bind.
|
||||
//
|
||||
// To prevent recursion, we need to check if we're on the list, and abort if so.
|
||||
//
|
||||
|
||||
for (auto history = bindRequest->previousVersion;
|
||||
history != NULL;
|
||||
history = history->previousVersion)
|
||||
{
|
||||
if (history->modifierFilterId == FilterId)
|
||||
{
|
||||
DbgPrint("Aborting bind processing because already redirected by us\n");
|
||||
|
||||
goto Cleanup_data;
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// Rewrite bind as applicable.
|
||||
//
|
||||
|
||||
const bool ipv4 = FixedValues->layerId == FWPS_LAYER_ALE_BIND_REDIRECT_V4;
|
||||
|
||||
WdfWaitLockAcquire(Context->IpAddresses.Lock, NULL);
|
||||
|
||||
if (ipv4)
|
||||
{
|
||||
auto bindTarget = (SOCKADDR_IN*)&(bindRequest->localAddressAndPort);
|
||||
|
||||
DbgPrint("Bind request eligible for splitting: %d.%d.%d.%d:%d\n",
|
||||
bindTarget->sin_addr.S_un.S_un_b.s_b1,
|
||||
bindTarget->sin_addr.S_un.S_un_b.s_b2,
|
||||
bindTarget->sin_addr.S_un.S_un_b.s_b3,
|
||||
bindTarget->sin_addr.S_un.S_un_b.s_b4,
|
||||
ntohs(bindTarget->sin_port)
|
||||
);
|
||||
|
||||
if (IN4_IS_ADDR_UNSPECIFIED(&(bindTarget->sin_addr))
|
||||
|| IN4_ADDR_EQUAL(&(bindTarget->sin_addr), &(Context->IpAddresses.Addresses.TunnelIpv4)))
|
||||
{
|
||||
DbgPrint("SPLITTING\n");
|
||||
|
||||
bindTarget->sin_addr = Context->IpAddresses.Addresses.InternetIpv4;
|
||||
|
||||
ClassifyOut->actionType = FWP_ACTION_PERMIT;
|
||||
ClassifyOut->rights &= ~FWPS_RIGHT_ACTION_WRITE;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
auto bindTarget = (SOCKADDR_IN6*)&(bindRequest->localAddressAndPort);
|
||||
|
||||
DbgPrint("Bind request eligible for splitting: [%X:%X:%X:%X:%X:%X:%X:%X]:%d\n",
|
||||
ntohs(bindTarget->sin6_addr.u.Word[0]),
|
||||
ntohs(bindTarget->sin6_addr.u.Word[1]),
|
||||
ntohs(bindTarget->sin6_addr.u.Word[2]),
|
||||
ntohs(bindTarget->sin6_addr.u.Word[3]),
|
||||
ntohs(bindTarget->sin6_addr.u.Word[4]),
|
||||
ntohs(bindTarget->sin6_addr.u.Word[5]),
|
||||
ntohs(bindTarget->sin6_addr.u.Word[6]),
|
||||
ntohs(bindTarget->sin6_addr.u.Word[7]),
|
||||
ntohs(bindTarget->sin6_port)
|
||||
);
|
||||
|
||||
static const IN6_ADDR IN6_ADDR_ANY = { 0 };
|
||||
|
||||
if (IN6_ADDR_EQUAL(&(bindTarget->sin6_addr), &IN6_ADDR_ANY)
|
||||
|| IN6_ADDR_EQUAL(&(bindTarget->sin6_addr), &(Context->IpAddresses.Addresses.TunnelIpv6)))
|
||||
{
|
||||
DbgPrint("SPLITTING\n");
|
||||
|
||||
bindTarget->sin6_addr = Context->IpAddresses.Addresses.InternetIpv6;
|
||||
|
||||
ClassifyOut->actionType = FWP_ACTION_PERMIT;
|
||||
ClassifyOut->rights &= ~FWPS_RIGHT_ACTION_WRITE;
|
||||
}
|
||||
}
|
||||
|
||||
WdfWaitLockRelease(Context->IpAddresses.Lock);
|
||||
|
||||
Cleanup_data:
|
||||
|
||||
//
|
||||
// Call the "apply" function even in instances where we've made no changes
|
||||
// to the data, because it was deemed not necessary, or aborting for some other reason.
|
||||
//
|
||||
// This is the correct logic according to documentation.
|
||||
//
|
||||
|
||||
FwpsApplyModifiedLayerData0(classifyHandle, (PVOID*)&bindRequest, 0);
|
||||
|
||||
Cleanup_handle:
|
||||
|
||||
FwpsReleaseClassifyHandle0(classifyHandle);
|
||||
}
|
||||
|
||||
//
|
||||
// RegisterFilterBindRedirectTx()
|
||||
//
|
||||
// Register WFP filters that will pass all bind requests through the bind callout
|
||||
// for validation/redirection.
|
||||
//
|
||||
// "Tx" (in transaction) suffix means there is no clean-up in failure paths.
|
||||
//
|
||||
NTSTATUS
|
||||
RegisterFilterBindRedirectTx
|
||||
(
|
||||
HANDLE WfpSession,
|
||||
bool RegisterIpv6
|
||||
)
|
||||
{
|
||||
//
|
||||
// Create filter that references callout.
|
||||
// Not specifying any conditions makes it apply to all traffic.
|
||||
//
|
||||
|
||||
FWPM_FILTER0 filter = { 0 };
|
||||
|
||||
const auto filterName = L"Mullvad Split Tunnel Bind Redirect Filter (IPv4)";
|
||||
const auto filterDescription = L"Redirects certain binds away from tunnel interface";
|
||||
|
||||
filter.filterKey = ST_FW_FILTER_CLASSIFY_BIND_IPV4_KEY;
|
||||
filter.displayData.name = const_cast<wchar_t*>(filterName);
|
||||
filter.displayData.description = const_cast<wchar_t*>(filterDescription);
|
||||
filter.flags = FWPM_FILTER_FLAG_CLEAR_ACTION_RIGHT | FWPM_FILTER_FLAG_HAS_PROVIDER_CONTEXT;
|
||||
filter.providerKey = const_cast<GUID*>(&ST_FW_PROVIDER_KEY);
|
||||
filter.layerKey = FWPM_LAYER_ALE_BIND_REDIRECT_V4;
|
||||
filter.subLayerKey = ST_FW_WINFW_BASELINE_SUBLAYER_KEY;
|
||||
filter.weight.type = FWP_UINT64;
|
||||
filter.weight.uint64 = const_cast<UINT64*>(&ST_MAX_FILTER_WEIGHT);
|
||||
filter.action.type = FWP_ACTION_CALLOUT_UNKNOWN;
|
||||
filter.action.calloutKey = ST_FW_CALLOUT_CLASSIFY_BIND_IPV4_KEY;
|
||||
filter.providerContextKey = ST_FW_PROVIDER_CONTEXT_KEY;
|
||||
|
||||
auto status = FwpmFilterAdd0(WfpSession, &filter, NULL, NULL);
|
||||
|
||||
if (!NT_SUCCESS(status))
|
||||
{
|
||||
return status;
|
||||
}
|
||||
|
||||
if (!RegisterIpv6)
|
||||
{
|
||||
return STATUS_SUCCESS;
|
||||
}
|
||||
|
||||
//
|
||||
// Again, for IPv6 also.
|
||||
//
|
||||
|
||||
const auto filterNameIpv6 = L"Mullvad Split Tunnel Bind Redirect Filter (IPv6)";
|
||||
|
||||
filter.filterKey = ST_FW_FILTER_CLASSIFY_BIND_IPV6_KEY;
|
||||
filter.displayData.name = const_cast<wchar_t*>(filterNameIpv6);
|
||||
filter.layerKey = FWPM_LAYER_ALE_BIND_REDIRECT_V6;
|
||||
filter.action.calloutKey = ST_FW_CALLOUT_CLASSIFY_BIND_IPV6_KEY;
|
||||
|
||||
return FwpmFilterAdd0(WfpSession, &filter, NULL, NULL);
|
||||
}
|
||||
|
||||
//
|
||||
// RemoveFilterBindRedirectTx()
|
||||
//
|
||||
// Remove WFP filters that activate the bind callout.
|
||||
//
|
||||
// "Tx" (in transaction) suffix means there is no clean-up in failure paths.
|
||||
//
|
||||
NTSTATUS
|
||||
RemoveFilterBindRedirectTx
|
||||
(
|
||||
HANDLE WfpSession,
|
||||
bool RemoveIpv6
|
||||
)
|
||||
{
|
||||
auto status = FwpmFilterDeleteByKey0(WfpSession, &ST_FW_FILTER_CLASSIFY_BIND_IPV4_KEY);
|
||||
|
||||
if (!NT_SUCCESS(status))
|
||||
{
|
||||
return status;
|
||||
}
|
||||
|
||||
if (!RemoveIpv6)
|
||||
{
|
||||
return STATUS_SUCCESS;
|
||||
}
|
||||
|
||||
return FwpmFilterDeleteByKey0(WfpSession, &ST_FW_FILTER_CLASSIFY_BIND_IPV6_KEY);
|
||||
}
|
||||
|
||||
//
|
||||
// RegisterFilterPermitSplitAppsTx()
|
||||
//
|
||||
// Register WFP filters that will pass all connection attempts through the
|
||||
// connection callouts for validation.
|
||||
//
|
||||
// "Tx" (in transaction) suffix means there is no clean-up in failure paths.
|
||||
//
|
||||
NTSTATUS
|
||||
RegisterFilterPermitSplitAppsTx
|
||||
(
|
||||
HANDLE WfpSession,
|
||||
const IN_ADDR *TunnelIpv4,
|
||||
const IN6_ADDR *TunnelIpv6
|
||||
)
|
||||
{
|
||||
//
|
||||
// Create filter that references callout.
|
||||
//
|
||||
// The single condition is IP_LOCAL_ADDRESS != Tunnel.
|
||||
//
|
||||
// This ensures the callout is presented only with connections that are
|
||||
// attempted outside the tunnel.
|
||||
//
|
||||
// Ipv4 outbound.
|
||||
//
|
||||
|
||||
FWPM_FILTER0 filter = { 0 };
|
||||
|
||||
const auto filterName = L"Mullvad Split Tunnel Permissive Filter (IPv4)";
|
||||
const auto filterDescription = L"Approves selected connections outside the tunnel";
|
||||
|
||||
filter.filterKey = ST_FW_FILTER_PERMIT_SPLIT_APPS_IPV4_CONN_KEY;
|
||||
filter.displayData.name = const_cast<wchar_t*>(filterName);
|
||||
filter.displayData.description = const_cast<wchar_t*>(filterDescription);
|
||||
filter.flags = FWPM_FILTER_FLAG_CLEAR_ACTION_RIGHT | FWPM_FILTER_FLAG_HAS_PROVIDER_CONTEXT;
|
||||
filter.providerKey = const_cast<GUID*>(&ST_FW_PROVIDER_KEY);
|
||||
filter.layerKey = FWPM_LAYER_ALE_AUTH_CONNECT_V4;
|
||||
filter.subLayerKey = ST_FW_WINFW_BASELINE_SUBLAYER_KEY;
|
||||
filter.weight.type = FWP_UINT64;
|
||||
filter.weight.uint64 = const_cast<UINT64*>(&ST_HIGH_FILTER_WEIGHT);
|
||||
filter.action.type = FWP_ACTION_CALLOUT_UNKNOWN;
|
||||
filter.action.calloutKey = ST_FW_CALLOUT_PERMIT_SPLIT_APPS_IPV4_CONN_KEY;
|
||||
filter.providerContextKey = ST_FW_PROVIDER_CONTEXT_KEY;
|
||||
|
||||
FWPM_FILTER_CONDITION0 cond;
|
||||
|
||||
cond.fieldKey = FWPM_CONDITION_IP_LOCAL_ADDRESS;
|
||||
cond.matchType = FWP_MATCH_NOT_EQUAL;
|
||||
cond.conditionValue.type = FWP_UINT32;
|
||||
cond.conditionValue.uint32 = RtlUlongByteSwap(TunnelIpv4->s_addr);
|
||||
|
||||
filter.filterCondition = &cond;
|
||||
filter.numFilterConditions = 1;
|
||||
|
||||
auto status = FwpmFilterAdd0(WfpSession, &filter, NULL, NULL);
|
||||
|
||||
if (!NT_SUCCESS(status))
|
||||
{
|
||||
return status;
|
||||
}
|
||||
|
||||
//
|
||||
// Ipv4 inbound.
|
||||
//
|
||||
|
||||
filter.filterKey = ST_FW_FILTER_PERMIT_SPLIT_APPS_IPV4_RECV_KEY;
|
||||
filter.layerKey = FWPM_LAYER_ALE_AUTH_RECV_ACCEPT_V4;
|
||||
filter.action.calloutKey = ST_FW_CALLOUT_PERMIT_SPLIT_APPS_IPV4_RECV_KEY;
|
||||
|
||||
status = FwpmFilterAdd0(WfpSession, &filter, NULL, NULL);
|
||||
|
||||
if (!NT_SUCCESS(status))
|
||||
{
|
||||
return status;
|
||||
}
|
||||
|
||||
if (TunnelIpv6 == NULL)
|
||||
{
|
||||
return STATUS_SUCCESS;
|
||||
}
|
||||
|
||||
//
|
||||
// IPv6 outbound.
|
||||
//
|
||||
|
||||
const auto filterNameIpv6 = L"Mullvad Split Tunnel Permissive Filter (IPv6)";
|
||||
|
||||
filter.filterKey = ST_FW_FILTER_PERMIT_SPLIT_APPS_IPV6_CONN_KEY;
|
||||
filter.displayData.name = const_cast<wchar_t*>(filterNameIpv6);
|
||||
filter.layerKey = FWPM_LAYER_ALE_AUTH_CONNECT_V6;
|
||||
filter.action.calloutKey = ST_FW_CALLOUT_PERMIT_SPLIT_APPS_IPV6_CONN_KEY;
|
||||
|
||||
cond.conditionValue.type = FWP_BYTE_ARRAY16_TYPE;
|
||||
cond.conditionValue.byteArray16 = (FWP_BYTE_ARRAY16*)TunnelIpv6->u.Byte;
|
||||
|
||||
status = FwpmFilterAdd0(WfpSession, &filter, NULL, NULL);
|
||||
|
||||
if (!NT_SUCCESS(status))
|
||||
{
|
||||
return status;
|
||||
}
|
||||
|
||||
//
|
||||
// IPv6 inbound.
|
||||
//
|
||||
|
||||
filter.filterKey = ST_FW_FILTER_PERMIT_SPLIT_APPS_IPV6_RECV_KEY;
|
||||
filter.layerKey = FWPM_LAYER_ALE_AUTH_RECV_ACCEPT_V6;
|
||||
filter.action.calloutKey = ST_FW_CALLOUT_PERMIT_SPLIT_APPS_IPV6_RECV_KEY;
|
||||
|
||||
return FwpmFilterAdd0(WfpSession, &filter, NULL, NULL);
|
||||
}
|
||||
|
||||
NTSTATUS
|
||||
RemoveFilterPermitSplitAppsTx
|
||||
(
|
||||
HANDLE WfpSession,
|
||||
bool RemoveIpv6
|
||||
)
|
||||
{
|
||||
auto status = FwpmFilterDeleteByKey0(WfpSession, &ST_FW_FILTER_PERMIT_SPLIT_APPS_IPV4_CONN_KEY);
|
||||
|
||||
if (!NT_SUCCESS(status))
|
||||
{
|
||||
return status;
|
||||
}
|
||||
|
||||
status = FwpmFilterDeleteByKey0(WfpSession, &ST_FW_FILTER_PERMIT_SPLIT_APPS_IPV4_RECV_KEY);
|
||||
|
||||
if (!NT_SUCCESS(status))
|
||||
{
|
||||
return status;
|
||||
}
|
||||
|
||||
if (!RemoveIpv6)
|
||||
{
|
||||
return STATUS_SUCCESS;
|
||||
}
|
||||
|
||||
status = FwpmFilterDeleteByKey0(WfpSession, &ST_FW_FILTER_PERMIT_SPLIT_APPS_IPV6_CONN_KEY);
|
||||
|
||||
if (!NT_SUCCESS(status))
|
||||
{
|
||||
return status;
|
||||
}
|
||||
|
||||
return FwpmFilterDeleteByKey0(WfpSession, &ST_FW_FILTER_PERMIT_SPLIT_APPS_IPV6_RECV_KEY);
|
||||
}
|
||||
|
||||
} // namespace firewall
|
||||
63
src/firewall/splitting.h
Normal file
63
src/firewall/splitting.h
Normal file
@@ -0,0 +1,63 @@
|
||||
#pragma once
|
||||
|
||||
#include "wfp.h"
|
||||
#include "context.h"
|
||||
|
||||
namespace firewall
|
||||
{
|
||||
|
||||
void
|
||||
RewriteBind
|
||||
(
|
||||
CONTEXT *Context,
|
||||
const FWPS_INCOMING_VALUES0 *FixedValues,
|
||||
const FWPS_INCOMING_METADATA_VALUES0 *MetaValues,
|
||||
UINT64 FilterId,
|
||||
const void *ClassifyContext,
|
||||
FWPS_CLASSIFY_OUT0 *ClassifyOut
|
||||
);
|
||||
|
||||
//
|
||||
// RegisterFilterBindRedirectTx()
|
||||
//
|
||||
// Register filters, with linked callout, that rewrites binds for
|
||||
// applications being split.
|
||||
//
|
||||
NTSTATUS
|
||||
RegisterFilterBindRedirectTx
|
||||
(
|
||||
HANDLE WfpSession,
|
||||
bool RegisterIpv6
|
||||
);
|
||||
|
||||
NTSTATUS
|
||||
RemoveFilterBindRedirectTx
|
||||
(
|
||||
HANDLE WfpSession,
|
||||
bool RemoveIpv6
|
||||
);
|
||||
|
||||
//
|
||||
// RegisterFilterPermitSplitAppsTx()
|
||||
//
|
||||
// Register filters, with linked callout, that permits non-tunnel connections
|
||||
// associated with applications being split.
|
||||
//
|
||||
// This ensures winfw filters are not applied to these apps.
|
||||
//
|
||||
NTSTATUS
|
||||
RegisterFilterPermitSplitAppsTx
|
||||
(
|
||||
HANDLE WfpSession,
|
||||
const IN_ADDR *TunnelIpv4,
|
||||
const IN6_ADDR *TunnelIpv6
|
||||
);
|
||||
|
||||
NTSTATUS
|
||||
RemoveFilterPermitSplitAppsTx
|
||||
(
|
||||
HANDLE WfpSession,
|
||||
bool RemoveIpv6
|
||||
);
|
||||
|
||||
} // namespace firewall
|
||||
18
src/firewall/wfp.h
Normal file
18
src/firewall/wfp.h
Normal file
@@ -0,0 +1,18 @@
|
||||
#pragma once
|
||||
|
||||
//
|
||||
// Magical include order with defines etc.
|
||||
// Infuriating.
|
||||
//
|
||||
|
||||
#include <ntddk.h>
|
||||
#include <wdm.h>
|
||||
#include <initguid.h>
|
||||
#pragma warning(push)
|
||||
#pragma warning(disable:4201)
|
||||
#define NDIS630
|
||||
#include <ndis.h>
|
||||
#include <fwpsk.h>
|
||||
#pragma warning(pop)
|
||||
#include <fwpmk.h>
|
||||
#include <mstcpip.h>
|
||||
1734
src/ioctl.cpp
Normal file
1734
src/ioctl.cpp
Normal file
File diff suppressed because it is too large
Load Diff
98
src/ioctl.h
Normal file
98
src/ioctl.h
Normal file
@@ -0,0 +1,98 @@
|
||||
#pragma once
|
||||
|
||||
#include <ntddk.h>
|
||||
#include <wdf.h>
|
||||
#include "containers/registeredimage.h"
|
||||
|
||||
namespace ioctl
|
||||
{
|
||||
|
||||
//
|
||||
// Initialize()
|
||||
//
|
||||
// Initialize subsystems and device context.
|
||||
//
|
||||
NTSTATUS
|
||||
Initialize
|
||||
(
|
||||
WDFDEVICE Device
|
||||
);
|
||||
|
||||
//
|
||||
// SetConfigurationPrepare()
|
||||
//
|
||||
// Parse client buffer into registeredimage instance.
|
||||
//
|
||||
// This should be called at PASSIVE, and the actual updating and
|
||||
// state transition may be performed at DISPATCH.
|
||||
//
|
||||
NTSTATUS
|
||||
SetConfigurationPrepare
|
||||
(
|
||||
WDFREQUEST Request,
|
||||
registeredimage::CONTEXT **Imageset
|
||||
);
|
||||
|
||||
NTSTATUS
|
||||
SetConfiguration
|
||||
(
|
||||
WDFDEVICE Device,
|
||||
registeredimage::CONTEXT *Imageset
|
||||
);
|
||||
|
||||
void
|
||||
GetConfigurationComplete
|
||||
(
|
||||
WDFDEVICE Device,
|
||||
WDFREQUEST Request
|
||||
);
|
||||
|
||||
NTSTATUS
|
||||
ClearConfiguration
|
||||
(
|
||||
WDFDEVICE Device
|
||||
);
|
||||
|
||||
NTSTATUS
|
||||
RegisterProcesses
|
||||
(
|
||||
WDFDEVICE Device,
|
||||
WDFREQUEST Request
|
||||
);
|
||||
|
||||
NTSTATUS
|
||||
RegisterIpAddresses
|
||||
(
|
||||
WDFDEVICE Device,
|
||||
WDFREQUEST Request
|
||||
);
|
||||
|
||||
void
|
||||
GetIpAddressesComplete
|
||||
(
|
||||
WDFDEVICE Device,
|
||||
WDFREQUEST Request
|
||||
);
|
||||
|
||||
void
|
||||
GetStateComplete
|
||||
(
|
||||
WDFDEVICE Device,
|
||||
WDFREQUEST Request
|
||||
);
|
||||
|
||||
void
|
||||
QueryProcessComplete
|
||||
(
|
||||
WDFDEVICE Device,
|
||||
WDFREQUEST Request
|
||||
);
|
||||
|
||||
void
|
||||
ResetComplete
|
||||
(
|
||||
WDFDEVICE Device,
|
||||
WDFREQUEST Request
|
||||
);
|
||||
|
||||
} // namespace ioctl
|
||||
44
src/ipaddr.cpp
Normal file
44
src/ipaddr.cpp
Normal file
@@ -0,0 +1,44 @@
|
||||
#include <wdm.h>
|
||||
#include "ipaddr.h"
|
||||
#include "util.h"
|
||||
|
||||
namespace ip
|
||||
{
|
||||
|
||||
bool
|
||||
ValidTunnelIpv4Address
|
||||
(
|
||||
const ST_IP_ADDRESSES *IpAddresses
|
||||
)
|
||||
{
|
||||
return !util::IsEmptyRange(&IpAddresses->TunnelIpv4, sizeof(IpAddresses->TunnelIpv4));
|
||||
}
|
||||
|
||||
bool
|
||||
ValidInternetIpv4Address
|
||||
(
|
||||
const ST_IP_ADDRESSES *IpAddresses
|
||||
)
|
||||
{
|
||||
return !util::IsEmptyRange(&IpAddresses->InternetIpv4, sizeof(IpAddresses->InternetIpv4));
|
||||
}
|
||||
|
||||
bool
|
||||
ValidTunnelIpv6Address
|
||||
(
|
||||
const ST_IP_ADDRESSES *IpAddresses
|
||||
)
|
||||
{
|
||||
return !util::IsEmptyRange(&IpAddresses->TunnelIpv6, sizeof(IpAddresses->TunnelIpv6));
|
||||
}
|
||||
|
||||
bool
|
||||
ValidInternetIpv6Address
|
||||
(
|
||||
const ST_IP_ADDRESSES *IpAddresses
|
||||
)
|
||||
{
|
||||
return !util::IsEmptyRange(&IpAddresses->InternetIpv6, sizeof(IpAddresses->InternetIpv6));
|
||||
}
|
||||
|
||||
} // namespace ip
|
||||
43
src/ipaddr.h
Normal file
43
src/ipaddr.h
Normal file
@@ -0,0 +1,43 @@
|
||||
#pragma once
|
||||
|
||||
#include <inaddr.h>
|
||||
#include <in6addr.h>
|
||||
|
||||
typedef struct tag_ST_IP_ADDRESSES
|
||||
{
|
||||
IN_ADDR TunnelIpv4;
|
||||
IN_ADDR InternetIpv4;
|
||||
|
||||
IN6_ADDR TunnelIpv6;
|
||||
IN6_ADDR InternetIpv6;
|
||||
}
|
||||
ST_IP_ADDRESSES;
|
||||
|
||||
namespace ip
|
||||
{
|
||||
|
||||
bool
|
||||
ValidTunnelIpv4Address
|
||||
(
|
||||
const ST_IP_ADDRESSES *IpAddresses
|
||||
);
|
||||
|
||||
bool
|
||||
ValidInternetIpv4Address
|
||||
(
|
||||
const ST_IP_ADDRESSES *IpAddresses
|
||||
);
|
||||
|
||||
bool
|
||||
ValidTunnelIpv6Address
|
||||
(
|
||||
const ST_IP_ADDRESSES *IpAddresses
|
||||
);
|
||||
|
||||
bool
|
||||
ValidInternetIpv6Address
|
||||
(
|
||||
const ST_IP_ADDRESSES *IpAddresses
|
||||
);
|
||||
|
||||
} // namespace ip
|
||||
86
src/mullvad-split-tunnel.inf
Normal file
86
src/mullvad-split-tunnel.inf
Normal file
@@ -0,0 +1,86 @@
|
||||
;
|
||||
; mullvad-split-tunnel.inf
|
||||
;
|
||||
|
||||
[Version]
|
||||
Signature="$WINDOWS NT$"
|
||||
Class=WFPCALLOUTS
|
||||
ClassGuid={57465043-616C-6C6F-7574-5F636C617373}
|
||||
Provider=%ManufacturerName%
|
||||
CatalogFile=mullvad-split-tunnel.cat
|
||||
DriverVer=
|
||||
|
||||
[DestinationDirs]
|
||||
DefaultDestDir = 12
|
||||
mullvad-split-tunnel_Device_CoInstaller_CopyFiles = 11
|
||||
|
||||
; ================= Class section =====================
|
||||
|
||||
[ClassInstall32]
|
||||
AddReg=SplitTunnelClassReg
|
||||
|
||||
[SplitTunnelClassReg]
|
||||
HKR,,,0,%ClassName%
|
||||
HKR,,Icon,,-5
|
||||
|
||||
[SourceDisksNames]
|
||||
1 = %DiskName%,,,""
|
||||
|
||||
[SourceDisksFiles]
|
||||
mullvad-split-tunnel.sys = 1,,
|
||||
WdfCoInstaller$KMDFCOINSTALLERVERSION$.dll=1
|
||||
|
||||
;*****************************************
|
||||
; Install Section
|
||||
;*****************************************
|
||||
|
||||
[Manufacturer]
|
||||
%ManufacturerName%=Standard,NT$ARCH$
|
||||
|
||||
[Standard.NT$ARCH$]
|
||||
%mullvad-split-tunnel.DeviceDesc%=mullvad-split-tunnel_Device, Root\mullvad-split-tunnel
|
||||
|
||||
[mullvad-split-tunnel_Device.NT]
|
||||
CopyFiles=Drivers_Dir
|
||||
|
||||
[Drivers_Dir]
|
||||
mullvad-split-tunnel.sys
|
||||
|
||||
;-------------- Service installation
|
||||
[mullvad-split-tunnel_Device.NT.Services]
|
||||
AddService = mullvad-split-tunnel,%SPSVCINST_ASSOCSERVICE%, mullvad-split-tunnel_Service_Inst
|
||||
|
||||
; -------------- mullvad-split-tunnel driver install sections
|
||||
[mullvad-split-tunnel_Service_Inst]
|
||||
DisplayName = %mullvad-split-tunnel.SVCDESC%
|
||||
ServiceType = 1 ; SERVICE_KERNEL_DRIVER
|
||||
StartType = 3 ; SERVICE_DEMAND_START
|
||||
ErrorControl = 1 ; SERVICE_ERROR_NORMAL
|
||||
ServiceBinary = %12%\mullvad-split-tunnel.sys
|
||||
|
||||
;
|
||||
;--- mullvad-split-tunnel_Device Coinstaller installation ------
|
||||
;
|
||||
|
||||
[mullvad-split-tunnel_Device.NT.CoInstallers]
|
||||
AddReg=mullvad-split-tunnel_Device_CoInstaller_AddReg
|
||||
CopyFiles=mullvad-split-tunnel_Device_CoInstaller_CopyFiles
|
||||
|
||||
[mullvad-split-tunnel_Device_CoInstaller_AddReg]
|
||||
HKR,,CoInstallers32,0x00010000, "WdfCoInstaller$KMDFCOINSTALLERVERSION$.dll,WdfCoInstaller"
|
||||
|
||||
[mullvad-split-tunnel_Device_CoInstaller_CopyFiles]
|
||||
WdfCoInstaller$KMDFCOINSTALLERVERSION$.dll
|
||||
|
||||
[mullvad-split-tunnel_Device.NT.Wdf]
|
||||
KmdfService = mullvad-split-tunnel, mullvad-split-tunnel_wdfsect
|
||||
[mullvad-split-tunnel_wdfsect]
|
||||
KmdfLibraryVersion = $KMDFVERSION$
|
||||
|
||||
[Strings]
|
||||
SPSVCINST_ASSOCSERVICE= 0x00000002
|
||||
ManufacturerName="Mullvad AB"
|
||||
ClassName="Mullvad Split Tunnel"
|
||||
DiskName = "Mullvad Split Tunnel Installation Disk"
|
||||
mullvad-split-tunnel.DeviceDesc = "Mullvad Split Tunnel Device"
|
||||
mullvad-split-tunnel.SVCDESC = "Mullvad Split Tunnel Service"
|
||||
35
src/mullvad-split-tunnel.sln
Normal file
35
src/mullvad-split-tunnel.sln
Normal file
@@ -0,0 +1,35 @@
|
||||
|
||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
# Visual Studio Version 16
|
||||
VisualStudioVersion = 16.0.29609.76
|
||||
MinimumVisualStudioVersion = 10.0.40219.1
|
||||
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "mullvad-split-tunnel", "mullvad-split-tunnel.vcxproj", "{5B2A6B2C-D052-43DA-8181-EACB5F93E5A9}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|ARM64 = Debug|ARM64
|
||||
Debug|x64 = Debug|x64
|
||||
Release|ARM64 = Release|ARM64
|
||||
Release|x64 = Release|x64
|
||||
EndGlobalSection
|
||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||
{5B2A6B2C-D052-43DA-8181-EACB5F93E5A9}.Debug|ARM64.ActiveCfg = Debug|ARM64
|
||||
{5B2A6B2C-D052-43DA-8181-EACB5F93E5A9}.Debug|ARM64.Build.0 = Debug|ARM64
|
||||
{5B2A6B2C-D052-43DA-8181-EACB5F93E5A9}.Debug|ARM64.Deploy.0 = Debug|ARM64
|
||||
{5B2A6B2C-D052-43DA-8181-EACB5F93E5A9}.Debug|x64.ActiveCfg = Debug|x64
|
||||
{5B2A6B2C-D052-43DA-8181-EACB5F93E5A9}.Debug|x64.Build.0 = Debug|x64
|
||||
{5B2A6B2C-D052-43DA-8181-EACB5F93E5A9}.Debug|x64.Deploy.0 = Debug|x64
|
||||
{5B2A6B2C-D052-43DA-8181-EACB5F93E5A9}.Release|ARM64.ActiveCfg = Release|ARM64
|
||||
{5B2A6B2C-D052-43DA-8181-EACB5F93E5A9}.Release|ARM64.Build.0 = Release|ARM64
|
||||
{5B2A6B2C-D052-43DA-8181-EACB5F93E5A9}.Release|ARM64.Deploy.0 = Release|ARM64
|
||||
{5B2A6B2C-D052-43DA-8181-EACB5F93E5A9}.Release|x64.ActiveCfg = Release|x64
|
||||
{5B2A6B2C-D052-43DA-8181-EACB5F93E5A9}.Release|x64.Build.0 = Release|x64
|
||||
{5B2A6B2C-D052-43DA-8181-EACB5F93E5A9}.Release|x64.Deploy.0 = Release|x64
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
EndGlobalSection
|
||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||
SolutionGuid = {E03BD6C9-77C0-4B56-8A56-2A430043D615}
|
||||
EndGlobalSection
|
||||
EndGlobal
|
||||
260
src/mullvad-split-tunnel.vcxproj
Normal file
260
src/mullvad-split-tunnel.vcxproj
Normal file
@@ -0,0 +1,260 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project DefaultTargets="Build" ToolsVersion="12.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<ItemGroup Label="ProjectConfigurations">
|
||||
<ProjectConfiguration Include="Debug|x64">
|
||||
<Configuration>Debug</Configuration>
|
||||
<Platform>x64</Platform>
|
||||
</ProjectConfiguration>
|
||||
<ProjectConfiguration Include="Release|x64">
|
||||
<Configuration>Release</Configuration>
|
||||
<Platform>x64</Platform>
|
||||
</ProjectConfiguration>
|
||||
<ProjectConfiguration Include="Debug|ARM64">
|
||||
<Configuration>Debug</Configuration>
|
||||
<Platform>ARM64</Platform>
|
||||
</ProjectConfiguration>
|
||||
<ProjectConfiguration Include="Release|ARM64">
|
||||
<Configuration>Release</Configuration>
|
||||
<Platform>ARM64</Platform>
|
||||
</ProjectConfiguration>
|
||||
</ItemGroup>
|
||||
<PropertyGroup Label="Globals">
|
||||
<ProjectGuid>{5B2A6B2C-D052-43DA-8181-EACB5F93E5A9}</ProjectGuid>
|
||||
<TemplateGuid>{1bc93793-694f-48fe-9372-81e2b05556fd}</TemplateGuid>
|
||||
<TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
|
||||
<MinimumVisualStudioVersion>12.0</MinimumVisualStudioVersion>
|
||||
<Configuration>Debug</Configuration>
|
||||
<Platform Condition="'$(Platform)' == ''">Win32</Platform>
|
||||
<RootNamespace>mullvad_split_tunnel</RootNamespace>
|
||||
<WindowsTargetPlatformVersion>$(LatestTargetPlatformVersion)</WindowsTargetPlatformVersion>
|
||||
</PropertyGroup>
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="Configuration">
|
||||
<TargetVersion>Windows7</TargetVersion>
|
||||
<UseDebugLibraries>true</UseDebugLibraries>
|
||||
<PlatformToolset>WindowsKernelModeDriver10.0</PlatformToolset>
|
||||
<ConfigurationType>Driver</ConfigurationType>
|
||||
<DriverType>KMDF</DriverType>
|
||||
<DriverTargetPlatform>Desktop</DriverTargetPlatform>
|
||||
<KMDF_VERSION_MAJOR>1</KMDF_VERSION_MAJOR>
|
||||
<KMDF_VERSION_MINOR>11</KMDF_VERSION_MINOR>
|
||||
<KMDF_MINIMUM_VERSION_REQUIRED>
|
||||
</KMDF_MINIMUM_VERSION_REQUIRED>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="Configuration">
|
||||
<TargetVersion>Windows7</TargetVersion>
|
||||
<UseDebugLibraries>false</UseDebugLibraries>
|
||||
<PlatformToolset>WindowsKernelModeDriver10.0</PlatformToolset>
|
||||
<ConfigurationType>Driver</ConfigurationType>
|
||||
<DriverType>KMDF</DriverType>
|
||||
<DriverTargetPlatform>Desktop</DriverTargetPlatform>
|
||||
<KMDF_VERSION_MAJOR>1</KMDF_VERSION_MAJOR>
|
||||
<KMDF_VERSION_MINOR>11</KMDF_VERSION_MINOR>
|
||||
<KMDF_MINIMUM_VERSION_REQUIRED>
|
||||
</KMDF_MINIMUM_VERSION_REQUIRED>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|ARM64'" Label="Configuration">
|
||||
<TargetVersion>Windows7</TargetVersion>
|
||||
<UseDebugLibraries>true</UseDebugLibraries>
|
||||
<PlatformToolset>WindowsKernelModeDriver10.0</PlatformToolset>
|
||||
<ConfigurationType>Driver</ConfigurationType>
|
||||
<DriverType>KMDF</DriverType>
|
||||
<DriverTargetPlatform>Desktop</DriverTargetPlatform>
|
||||
<KMDF_VERSION_MAJOR>1</KMDF_VERSION_MAJOR>
|
||||
<KMDF_VERSION_MINOR>11</KMDF_VERSION_MINOR>
|
||||
<KMDF_MINIMUM_VERSION_REQUIRED>
|
||||
</KMDF_MINIMUM_VERSION_REQUIRED>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|ARM64'" Label="Configuration">
|
||||
<TargetVersion>Windows7</TargetVersion>
|
||||
<UseDebugLibraries>false</UseDebugLibraries>
|
||||
<PlatformToolset>WindowsKernelModeDriver10.0</PlatformToolset>
|
||||
<ConfigurationType>Driver</ConfigurationType>
|
||||
<DriverType>KMDF</DriverType>
|
||||
<DriverTargetPlatform>Desktop</DriverTargetPlatform>
|
||||
<KMDF_VERSION_MAJOR>1</KMDF_VERSION_MAJOR>
|
||||
<KMDF_VERSION_MINOR>11</KMDF_VERSION_MINOR>
|
||||
<KMDF_MINIMUM_VERSION_REQUIRED>
|
||||
</KMDF_MINIMUM_VERSION_REQUIRED>
|
||||
</PropertyGroup>
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
|
||||
<ImportGroup Label="ExtensionSettings">
|
||||
</ImportGroup>
|
||||
<ImportGroup Label="PropertySheets">
|
||||
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
|
||||
</ImportGroup>
|
||||
<PropertyGroup Label="UserMacros" />
|
||||
<PropertyGroup />
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
|
||||
<DebuggerFlavor>DbgengKernelDebugger</DebuggerFlavor>
|
||||
<OutDir>$(SolutionDir)..\bin\$(Platform)-$(Configuration)\</OutDir>
|
||||
<IntDir>$(SolutionDir)..\bin\temp\$(Platform)-$(Configuration)\$(ProjectName)\</IntDir>
|
||||
<Inf2CatUseLocalTime>true</Inf2CatUseLocalTime>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
|
||||
<DebuggerFlavor>DbgengKernelDebugger</DebuggerFlavor>
|
||||
<OutDir>$(SolutionDir)..\bin\$(Platform)-$(Configuration)\</OutDir>
|
||||
<IntDir>$(SolutionDir)..\bin\temp\$(Platform)-$(Configuration)\$(ProjectName)\</IntDir>
|
||||
<Inf2CatUseLocalTime>true</Inf2CatUseLocalTime>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|ARM64'">
|
||||
<DebuggerFlavor>DbgengKernelDebugger</DebuggerFlavor>
|
||||
<OutDir>$(SolutionDir)..\bin\$(Platform)-$(Configuration)\</OutDir>
|
||||
<IntDir>$(SolutionDir)..\bin\temp\$(Platform)-$(Configuration)\$(ProjectName)\</IntDir>
|
||||
<Inf2CatUseLocalTime>true</Inf2CatUseLocalTime>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|ARM64'">
|
||||
<DebuggerFlavor>DbgengKernelDebugger</DebuggerFlavor>
|
||||
<OutDir>$(SolutionDir)..\bin\$(Platform)-$(Configuration)\</OutDir>
|
||||
<IntDir>$(SolutionDir)..\bin\temp\$(Platform)-$(Configuration)\$(ProjectName)\</IntDir>
|
||||
<Inf2CatUseLocalTime>true</Inf2CatUseLocalTime>
|
||||
</PropertyGroup>
|
||||
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
|
||||
<ClCompile>
|
||||
<PreprocessorDefinitions>POOL_NX_OPTIN=1;_WIN64;_AMD64_;AMD64;DEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<LanguageStandard>stdcpplatest</LanguageStandard>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<AdditionalDependencies>%(AdditionalDependencies);$(KernelBufferOverflowLib);$(DDK_LIB_PATH)ntoskrnl.lib;$(DDK_LIB_PATH)hal.lib;$(DDK_LIB_PATH)wmilib.lib;$(KMDF_LIB_PATH)$(KMDF_VER_PATH)\WdfLdr.lib;$(KMDF_LIB_PATH)$(KMDF_VER_PATH)\WdfDriverEntry.lib;$(DDK_LIB_PATH)\wdmsec.lib;Fwpkclnt.lib</AdditionalDependencies>
|
||||
<AdditionalOptions>/INTEGRITYCHECK %(AdditionalOptions)</AdditionalOptions>
|
||||
</Link>
|
||||
<Inf>
|
||||
<TimeStamp>0.0.0.1</TimeStamp>
|
||||
</Inf>
|
||||
<PostBuildEvent>
|
||||
<Command>copy /y $(SolutionDir)..\bin\$(Platform)-$(Configuration)\mullvad-split-tunnel.pdb $(SolutionDir)..\bin\$(Platform)-$(Configuration)\mullvad-split-tunnel\mullvad-split-tunnel.pdb</Command>
|
||||
</PostBuildEvent>
|
||||
<PreBuildEvent>
|
||||
<Command>custom-stampinf.bat "$(InfToolPath)stampinf.exe" $(InfArch) $(KMDF_VERSION_MAJOR).$(KMDF_VERSION_MINOR) "$(IntDir)mullvad-split-tunnel.inf" "$(OutDir)mullvad-split-tunnel.inf"</Command>
|
||||
</PreBuildEvent>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|ARM64'">
|
||||
<ClCompile>
|
||||
<PreprocessorDefinitions>_ARM64_;ARM64;_USE_DECLSPECS_FOR_SAL=1;STD_CALL;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<LanguageStandard>stdcpp17</LanguageStandard>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<AdditionalDependencies>%(AdditionalDependencies);$(KernelBufferOverflowLib);$(DDK_LIB_PATH)ntoskrnl.lib;$(DDK_LIB_PATH)hal.lib;$(DDK_LIB_PATH)wmilib.lib;$(KMDF_LIB_PATH)$(KMDF_VER_PATH)\WdfLdr.lib;$(KMDF_LIB_PATH)$(KMDF_VER_PATH)\WdfDriverEntry.lib;$(DDK_LIB_PATH)\wdmsec.lib;Fwpkclnt.lib</AdditionalDependencies>
|
||||
<AdditionalOptions>/INTEGRITYCHECK %(AdditionalOptions)</AdditionalOptions>
|
||||
</Link>
|
||||
<PostBuildEvent>
|
||||
<Command>copy /y $(SolutionDir)..\bin\$(Platform)-$(Configuration)\mullvad-split-tunnel.pdb $(SolutionDir)..\bin\$(Platform)-$(Configuration)\mullvad-split-tunnel\mullvad-split-tunnel.pdb</Command>
|
||||
</PostBuildEvent>
|
||||
<PreBuildEvent>
|
||||
<Command>custom-stampinf.bat "$(InfToolPath)stampinf.exe" $(InfArch) $(KMDF_VERSION_MAJOR).$(KMDF_VERSION_MINOR) "$(IntDir)mullvad-split-tunnel.inf" "$(OutDir)mullvad-split-tunnel.inf"</Command>
|
||||
</PreBuildEvent>
|
||||
<Inf>
|
||||
<TimeStamp>0.0.0.1</TimeStamp>
|
||||
</Inf>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|ARM64'">
|
||||
<Link>
|
||||
<AdditionalDependencies>%(AdditionalDependencies);$(KernelBufferOverflowLib);$(DDK_LIB_PATH)ntoskrnl.lib;$(DDK_LIB_PATH)hal.lib;$(DDK_LIB_PATH)wmilib.lib;$(KMDF_LIB_PATH)$(KMDF_VER_PATH)\WdfLdr.lib;$(KMDF_LIB_PATH)$(KMDF_VER_PATH)\WdfDriverEntry.lib;$(DDK_LIB_PATH)\wdmsec.lib;Fwpkclnt.lib</AdditionalDependencies>
|
||||
<AdditionalOptions>/INTEGRITYCHECK %(AdditionalOptions)</AdditionalOptions>
|
||||
</Link>
|
||||
<ClCompile>
|
||||
<LanguageStandard>stdcpp17</LanguageStandard>
|
||||
</ClCompile>
|
||||
<PostBuildEvent>
|
||||
<Command>copy /y $(SolutionDir)..\bin\$(Platform)-$(Configuration)\mullvad-split-tunnel.pdb $(SolutionDir)..\bin\$(Platform)-$(Configuration)\mullvad-split-tunnel\mullvad-split-tunnel.pdb</Command>
|
||||
</PostBuildEvent>
|
||||
<PreBuildEvent>
|
||||
<Command>custom-stampinf.bat "$(InfToolPath)stampinf.exe" $(InfArch) $(KMDF_VERSION_MAJOR).$(KMDF_VERSION_MINOR) "$(IntDir)mullvad-split-tunnel.inf" "$(OutDir)mullvad-split-tunnel.inf"</Command>
|
||||
</PreBuildEvent>
|
||||
<Inf>
|
||||
<TimeStamp>0.0.0.1</TimeStamp>
|
||||
</Inf>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
|
||||
<Link>
|
||||
<AdditionalDependencies>%(AdditionalDependencies);$(KernelBufferOverflowLib);$(DDK_LIB_PATH)ntoskrnl.lib;$(DDK_LIB_PATH)hal.lib;$(DDK_LIB_PATH)wmilib.lib;$(KMDF_LIB_PATH)$(KMDF_VER_PATH)\WdfLdr.lib;$(KMDF_LIB_PATH)$(KMDF_VER_PATH)\WdfDriverEntry.lib;$(DDK_LIB_PATH)\wdmsec.lib;Fwpkclnt.lib</AdditionalDependencies>
|
||||
<AdditionalOptions>/INTEGRITYCHECK %(AdditionalOptions)</AdditionalOptions>
|
||||
</Link>
|
||||
<ClCompile>
|
||||
<LanguageStandard>stdcpplatest</LanguageStandard>
|
||||
<PreprocessorDefinitions>POOL_NX_OPTIN=1;_WIN64;_AMD64_;AMD64;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
</ClCompile>
|
||||
<Inf>
|
||||
<TimeStamp>0.0.0.1</TimeStamp>
|
||||
</Inf>
|
||||
<PostBuildEvent>
|
||||
<Command>copy /y $(SolutionDir)..\bin\$(Platform)-$(Configuration)\mullvad-split-tunnel.pdb $(SolutionDir)..\bin\$(Platform)-$(Configuration)\mullvad-split-tunnel\mullvad-split-tunnel.pdb</Command>
|
||||
</PostBuildEvent>
|
||||
<PreBuildEvent>
|
||||
<Command>custom-stampinf.bat "$(InfToolPath)stampinf.exe" $(InfArch) $(KMDF_VERSION_MAJOR).$(KMDF_VERSION_MINOR) "$(IntDir)mullvad-split-tunnel.inf" "$(OutDir)mullvad-split-tunnel.inf"</Command>
|
||||
</PreBuildEvent>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemGroup>
|
||||
<FilesToPackage Include="$(TargetPath)" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClCompile Include="containers\procregistry.cpp" />
|
||||
<ClCompile Include="containers\registeredimage.cpp" />
|
||||
<ClCompile Include="driverentry.cpp" />
|
||||
<ClCompile Include="eventing\builder.cpp" />
|
||||
<ClCompile Include="eventing\eventing.cpp" />
|
||||
<ClCompile Include="firewall\asyncbind.cpp" />
|
||||
<ClCompile Include="firewall\blocking.cpp" />
|
||||
<ClCompile Include="firewall\callouts.cpp" />
|
||||
<ClCompile Include="firewall\firewall.cpp" />
|
||||
<ClCompile Include="firewall\splitting.cpp" />
|
||||
<ClCompile Include="ioctl.cpp" />
|
||||
<ClCompile Include="ipaddr.cpp" />
|
||||
<ClCompile Include="procbroker\procbroker.cpp" />
|
||||
<ClCompile Include="procmgmt\procmgmt.cpp" />
|
||||
<ClCompile Include="procmon\procmon.cpp" />
|
||||
<ClCompile Include="util.cpp" />
|
||||
<ClCompile Include="validation.cpp" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Inf Include="mullvad-split-tunnel.inf" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClInclude Include="containers\procregistry.h" />
|
||||
<ClInclude Include="containers\registeredimage.h" />
|
||||
<ClInclude Include="defs\config.h" />
|
||||
<ClInclude Include="defs\events.h" />
|
||||
<ClInclude Include="defs\ioctl.h" />
|
||||
<ClInclude Include="defs\process.h" />
|
||||
<ClInclude Include="defs\queryprocess.h" />
|
||||
<ClInclude Include="defs\state.h" />
|
||||
<ClInclude Include="defs\types.h" />
|
||||
<ClInclude Include="devicecontext.h" />
|
||||
<ClInclude Include="eventing\builder.h" />
|
||||
<ClInclude Include="eventing\context.h" />
|
||||
<ClInclude Include="eventing\eventing.h" />
|
||||
<ClInclude Include="firewall\asyncbind.h" />
|
||||
<ClInclude Include="firewall\blocking.h" />
|
||||
<ClInclude Include="firewall\callouts.h" />
|
||||
<ClInclude Include="firewall\constants.h" />
|
||||
<ClInclude Include="firewall\context.h" />
|
||||
<ClInclude Include="firewall\firewall.h" />
|
||||
<ClInclude Include="firewall\identifiers.h" />
|
||||
<ClInclude Include="firewall\splitting.h" />
|
||||
<ClInclude Include="firewall\wfp.h" />
|
||||
<ClInclude Include="ioctl.h" />
|
||||
<ClInclude Include="ipaddr.h" />
|
||||
<ClInclude Include="procbroker\context.h" />
|
||||
<ClInclude Include="procbroker\procbroker.h" />
|
||||
<ClInclude Include="containers.h" />
|
||||
<ClInclude Include="procmgmt\callbacks.h" />
|
||||
<ClInclude Include="procmgmt\context.h" />
|
||||
<ClInclude Include="procmgmt\procmgmt.h" />
|
||||
<ClInclude Include="procmon\context.h" />
|
||||
<ClInclude Include="procmon\procmon.h" />
|
||||
<ClInclude Include="public.h" />
|
||||
<ClInclude Include="util.h" />
|
||||
<ClInclude Include="validation.h" />
|
||||
<ClInclude Include="version.h" />
|
||||
<ClInclude Include="x64guard.h" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ResourceCompile Include="resource.rc" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Include="custom-stampinf.bat" />
|
||||
</ItemGroup>
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
|
||||
<ImportGroup Label="ExtensionTargets">
|
||||
</ImportGroup>
|
||||
</Project>
|
||||
173
src/mullvad-split-tunnel.vcxproj.filters
Normal file
173
src/mullvad-split-tunnel.vcxproj.filters
Normal file
@@ -0,0 +1,173 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<ItemGroup>
|
||||
<ClCompile Include="driverentry.cpp" />
|
||||
<ClCompile Include="util.cpp" />
|
||||
<ClCompile Include="ioctl.cpp" />
|
||||
<ClCompile Include="validation.cpp" />
|
||||
<ClCompile Include="firewall\blocking.cpp">
|
||||
<Filter>firewall</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="firewall\callouts.cpp">
|
||||
<Filter>firewall</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="firewall\firewall.cpp">
|
||||
<Filter>firewall</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="firewall\splitting.cpp">
|
||||
<Filter>firewall</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="ipaddr.cpp" />
|
||||
<ClCompile Include="procmon\procmon.cpp">
|
||||
<Filter>procmon</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="procmgmt\procmgmt.cpp">
|
||||
<Filter>procmgmt</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="eventing\eventing.cpp">
|
||||
<Filter>eventing</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="eventing\builder.cpp">
|
||||
<Filter>eventing</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="containers\registeredimage.cpp">
|
||||
<Filter>containers</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="containers\procregistry.cpp">
|
||||
<Filter>containers</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="procbroker\procbroker.cpp">
|
||||
<Filter>procbroker</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="firewall\asyncbind.cpp">
|
||||
<Filter>firewall</Filter>
|
||||
</ClCompile>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Inf Include="mullvad-split-tunnel.inf" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClInclude Include="public.h" />
|
||||
<ClInclude Include="util.h" />
|
||||
<ClInclude Include="ioctl.h" />
|
||||
<ClInclude Include="validation.h" />
|
||||
<ClInclude Include="firewall\blocking.h">
|
||||
<Filter>firewall</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="firewall\identifiers.h">
|
||||
<Filter>firewall</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="firewall\constants.h">
|
||||
<Filter>firewall</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="firewall\callouts.h">
|
||||
<Filter>firewall</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="firewall\firewall.h">
|
||||
<Filter>firewall</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="firewall\context.h">
|
||||
<Filter>firewall</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="firewall\splitting.h">
|
||||
<Filter>firewall</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="firewall\wfp.h">
|
||||
<Filter>firewall</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="ipaddr.h" />
|
||||
<ClInclude Include="defs\config.h">
|
||||
<Filter>defs</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="defs\ioctl.h">
|
||||
<Filter>defs</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="defs\process.h">
|
||||
<Filter>defs</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="defs\queryprocess.h">
|
||||
<Filter>defs</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="defs\state.h">
|
||||
<Filter>defs</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="defs\types.h">
|
||||
<Filter>defs</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="procmon\procmon.h">
|
||||
<Filter>procmon</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="procmon\context.h">
|
||||
<Filter>procmon</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="procmgmt\procmgmt.h">
|
||||
<Filter>procmgmt</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="procmgmt\context.h">
|
||||
<Filter>procmgmt</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="devicecontext.h" />
|
||||
<ClInclude Include="eventing\eventing.h">
|
||||
<Filter>eventing</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="eventing\context.h">
|
||||
<Filter>eventing</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="eventing\builder.h">
|
||||
<Filter>eventing</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="defs\events.h">
|
||||
<Filter>defs</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="containers\registeredimage.h">
|
||||
<Filter>containers</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="containers\procregistry.h">
|
||||
<Filter>containers</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="procbroker\context.h">
|
||||
<Filter>procbroker</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="procbroker\procbroker.h">
|
||||
<Filter>procbroker</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="firewall\asyncbind.h">
|
||||
<Filter>firewall</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="procmgmt\callbacks.h">
|
||||
<Filter>procmgmt</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="containers.h" />
|
||||
<ClInclude Include="x64guard.h" />
|
||||
<ClInclude Include="version.h" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Filter Include="firewall">
|
||||
<UniqueIdentifier>{928a5c2c-1bf7-4e3c-acc6-8ba6d886c732}</UniqueIdentifier>
|
||||
</Filter>
|
||||
<Filter Include="defs">
|
||||
<UniqueIdentifier>{290a2ff5-e1cf-4d3c-acc0-4ca5cdb2df6a}</UniqueIdentifier>
|
||||
</Filter>
|
||||
<Filter Include="procmon">
|
||||
<UniqueIdentifier>{acf27993-d281-4696-855e-8d5ce53aa007}</UniqueIdentifier>
|
||||
</Filter>
|
||||
<Filter Include="procmgmt">
|
||||
<UniqueIdentifier>{115be191-5ceb-46b8-b6ae-69469f030f45}</UniqueIdentifier>
|
||||
</Filter>
|
||||
<Filter Include="eventing">
|
||||
<UniqueIdentifier>{8014a3a4-3238-4a49-9289-c6f34abf23dc}</UniqueIdentifier>
|
||||
</Filter>
|
||||
<Filter Include="containers">
|
||||
<UniqueIdentifier>{59fed122-0778-4ba5-96a5-35a86e1467d2}</UniqueIdentifier>
|
||||
</Filter>
|
||||
<Filter Include="procbroker">
|
||||
<UniqueIdentifier>{1d2963d7-a048-4609-b8e7-812f4c6ed7a9}</UniqueIdentifier>
|
||||
</Filter>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ResourceCompile Include="resource.rc" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Include="custom-stampinf.bat" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
15
src/mullvad-split-tunnel.vcxproj.user
Normal file
15
src/mullvad-split-tunnel.vcxproj.user
Normal file
@@ -0,0 +1,15 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project ToolsVersion="Current" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
|
||||
<SignMode>Off</SignMode>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
|
||||
<SignMode>Off</SignMode>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|ARM64'">
|
||||
<SignMode>Off</SignMode>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|ARM64'">
|
||||
<SignMode>Off</SignMode>
|
||||
</PropertyGroup>
|
||||
</Project>
|
||||
22
src/procbroker/context.h
Normal file
22
src/procbroker/context.h
Normal file
@@ -0,0 +1,22 @@
|
||||
#pragma once
|
||||
|
||||
#include <wdf.h>
|
||||
#include "procbroker.h"
|
||||
|
||||
namespace procbroker
|
||||
{
|
||||
|
||||
struct SUBSCRIPTION
|
||||
{
|
||||
LIST_ENTRY ListEntry;
|
||||
ST_PB_CALLBACK Callback;
|
||||
void *ClientContext;
|
||||
};
|
||||
|
||||
struct CONTEXT
|
||||
{
|
||||
WDFWAITLOCK SubscriptionsLock;
|
||||
LIST_ENTRY Subscriptions;
|
||||
};
|
||||
|
||||
} // namespace procbroker
|
||||
138
src/procbroker/procbroker.cpp
Normal file
138
src/procbroker/procbroker.cpp
Normal file
@@ -0,0 +1,138 @@
|
||||
#include "procbroker.h"
|
||||
#include "context.h"
|
||||
#include "../defs/types.h"
|
||||
|
||||
namespace procbroker
|
||||
{
|
||||
|
||||
NTSTATUS
|
||||
Initialize
|
||||
(
|
||||
CONTEXT **Context
|
||||
)
|
||||
{
|
||||
auto context = (CONTEXT*)ExAllocatePoolWithTag(PagedPool, sizeof(CONTEXT), ST_POOL_TAG);
|
||||
|
||||
if (NULL == context)
|
||||
{
|
||||
return STATUS_INSUFFICIENT_RESOURCES;
|
||||
}
|
||||
|
||||
RtlZeroMemory(context, sizeof(*context));
|
||||
|
||||
auto status = WdfWaitLockCreate(WDF_NO_OBJECT_ATTRIBUTES, &context->SubscriptionsLock);
|
||||
|
||||
if (!NT_SUCCESS(status))
|
||||
{
|
||||
DbgPrint("WdfWaitLockCreate() failed 0x%X\n", status);
|
||||
|
||||
ExFreePoolWithTag(context, ST_POOL_TAG);
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
InitializeListHead(&context->Subscriptions);
|
||||
|
||||
*Context = context;
|
||||
|
||||
return STATUS_SUCCESS;
|
||||
}
|
||||
|
||||
void
|
||||
TearDown
|
||||
(
|
||||
CONTEXT **Context
|
||||
)
|
||||
{
|
||||
auto context = *Context;
|
||||
|
||||
LIST_ENTRY *record;
|
||||
|
||||
while ((record = RemoveHeadList(&context->Subscriptions)) != &context->Subscriptions)
|
||||
{
|
||||
ExFreePoolWithTag(record, ST_POOL_TAG);
|
||||
}
|
||||
|
||||
WdfObjectDelete(context->SubscriptionsLock);
|
||||
|
||||
ExFreePoolWithTag(context, ST_POOL_TAG);
|
||||
|
||||
*Context = NULL;
|
||||
}
|
||||
|
||||
NTSTATUS
|
||||
Subscribe
|
||||
(
|
||||
CONTEXT *Context,
|
||||
ST_PB_CALLBACK Callback,
|
||||
void *ClientContext
|
||||
)
|
||||
{
|
||||
auto sub = (SUBSCRIPTION*)ExAllocatePoolWithTag(PagedPool, sizeof(SUBSCRIPTION), ST_POOL_TAG);
|
||||
|
||||
if (NULL == sub)
|
||||
{
|
||||
return STATUS_INSUFFICIENT_RESOURCES;
|
||||
}
|
||||
|
||||
RtlZeroMemory(sub, sizeof(SUBSCRIPTION));
|
||||
|
||||
sub->Callback = Callback;
|
||||
sub->ClientContext = ClientContext;
|
||||
|
||||
WdfWaitLockAcquire(Context->SubscriptionsLock, NULL);
|
||||
|
||||
InsertTailList(&Context->Subscriptions, &sub->ListEntry);
|
||||
|
||||
WdfWaitLockRelease(Context->SubscriptionsLock);
|
||||
|
||||
return STATUS_SUCCESS;
|
||||
}
|
||||
|
||||
void
|
||||
CancelSubscription
|
||||
(
|
||||
CONTEXT *Context,
|
||||
ST_PB_CALLBACK Callback
|
||||
)
|
||||
{
|
||||
WdfWaitLockAcquire(Context->SubscriptionsLock, NULL);
|
||||
|
||||
for (auto entry = Context->Subscriptions.Flink;
|
||||
entry != &Context->Subscriptions;
|
||||
entry = entry->Flink)
|
||||
{
|
||||
if (((SUBSCRIPTION*)entry)->Callback == Callback)
|
||||
{
|
||||
RemoveEntryList(entry);
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
WdfWaitLockRelease(Context->SubscriptionsLock);
|
||||
}
|
||||
|
||||
void
|
||||
Publish
|
||||
(
|
||||
CONTEXT *Context,
|
||||
HANDLE ProcessId,
|
||||
bool Arriving
|
||||
)
|
||||
{
|
||||
WdfWaitLockAcquire(Context->SubscriptionsLock, NULL);
|
||||
|
||||
for (auto entry = Context->Subscriptions.Flink;
|
||||
entry != &Context->Subscriptions;
|
||||
entry = entry->Flink)
|
||||
{
|
||||
auto sub = (SUBSCRIPTION*)entry;
|
||||
|
||||
sub->Callback(ProcessId, Arriving, sub->ClientContext);
|
||||
}
|
||||
|
||||
WdfWaitLockRelease(Context->SubscriptionsLock);
|
||||
}
|
||||
|
||||
} // namespace procbroker
|
||||
56
src/procbroker/procbroker.h
Normal file
56
src/procbroker/procbroker.h
Normal file
@@ -0,0 +1,56 @@
|
||||
#pragma once
|
||||
|
||||
#include <wdm.h>
|
||||
|
||||
//
|
||||
// Process event broker.
|
||||
//
|
||||
// Distributes events in the system to notify subsystems when
|
||||
// processes arrive and depart.
|
||||
//
|
||||
// Introduced to break the dependency between "procmgmt" and "firewall".
|
||||
//
|
||||
|
||||
namespace procbroker
|
||||
{
|
||||
|
||||
struct CONTEXT;
|
||||
|
||||
NTSTATUS
|
||||
Initialize
|
||||
(
|
||||
CONTEXT **Context
|
||||
);
|
||||
|
||||
void
|
||||
TearDown
|
||||
(
|
||||
CONTEXT **Context
|
||||
);
|
||||
|
||||
typedef void (NTAPI *ST_PB_CALLBACK)(HANDLE ProcessId, bool Arriving, void *Context);
|
||||
|
||||
NTSTATUS
|
||||
Subscribe
|
||||
(
|
||||
CONTEXT *Context,
|
||||
ST_PB_CALLBACK Callback,
|
||||
void *ClientContext
|
||||
);
|
||||
|
||||
void
|
||||
CancelSubscription
|
||||
(
|
||||
CONTEXT *Context,
|
||||
ST_PB_CALLBACK Callback
|
||||
);
|
||||
|
||||
void
|
||||
Publish
|
||||
(
|
||||
CONTEXT *Context,
|
||||
HANDLE ProcessId,
|
||||
bool Arriving
|
||||
);
|
||||
|
||||
} // namespace procbroker
|
||||
12
src/procmgmt/callbacks.h
Normal file
12
src/procmgmt/callbacks.h
Normal file
@@ -0,0 +1,12 @@
|
||||
#pragma once
|
||||
|
||||
#include <wdf.h>
|
||||
|
||||
namespace procmgmt
|
||||
{
|
||||
|
||||
typedef void (NTAPI *ACQUIRE_STATE_LOCK_FN)(void *context);
|
||||
typedef void (NTAPI *RELEASE_STATE_LOCK_FN)(void *context);
|
||||
typedef bool (NTAPI *ENGAGED_STATE_ACTIVE_FN)(void *context);
|
||||
|
||||
} // namespace procmgmt
|
||||
34
src/procmgmt/context.h
Normal file
34
src/procmgmt/context.h
Normal file
@@ -0,0 +1,34 @@
|
||||
#pragma once
|
||||
|
||||
#include "../procmon/procmon.h"
|
||||
#include "../procbroker/procbroker.h"
|
||||
#include "../containers.h"
|
||||
#include "../eventing/eventing.h"
|
||||
#include "../firewall/firewall.h"
|
||||
#include "callbacks.h"
|
||||
|
||||
namespace procmgmt
|
||||
{
|
||||
|
||||
struct CONTEXT
|
||||
{
|
||||
procmon::CONTEXT *ProcessMonitor;
|
||||
|
||||
procbroker::CONTEXT *ProcessEventBroker;
|
||||
|
||||
PROCESS_REGISTRY_MGMT *ProcessRegistry;
|
||||
|
||||
REGISTERED_IMAGE_MGMT *RegisteredImage;
|
||||
|
||||
eventing::CONTEXT *Eventing;
|
||||
|
||||
firewall::CONTEXT *Firewall;
|
||||
|
||||
ACQUIRE_STATE_LOCK_FN AcquireStateLock;
|
||||
RELEASE_STATE_LOCK_FN ReleaseStateLock;
|
||||
ENGAGED_STATE_ACTIVE_FN EngagedStateActive;
|
||||
|
||||
void *CallbackContext;
|
||||
};
|
||||
|
||||
} // namespace procmgmt
|
||||
536
src/procmgmt/procmgmt.cpp
Normal file
536
src/procmgmt/procmgmt.cpp
Normal file
@@ -0,0 +1,536 @@
|
||||
#include "procmgmt.h"
|
||||
#include "context.h"
|
||||
#include "../util.h"
|
||||
#include "../defs/events.h"
|
||||
#include "../eventing/builder.h"
|
||||
|
||||
namespace procmgmt
|
||||
{
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
//
|
||||
// ValidateCollision()
|
||||
//
|
||||
// Find and validate existing entry in process registry that prevented the insertion
|
||||
// of a a new entry, because they share the same PID.
|
||||
//
|
||||
bool
|
||||
ValidateCollision
|
||||
(
|
||||
CONTEXT *Context,
|
||||
const procregistry::PROCESS_REGISTRY_ENTRY *newEntry
|
||||
)
|
||||
{
|
||||
auto processRegistry = Context->ProcessRegistry;
|
||||
|
||||
const auto existingEntry = procregistry::FindEntry(processRegistry->Instance, newEntry->ProcessId);
|
||||
|
||||
if (existingEntry == NULL)
|
||||
{
|
||||
DbgPrint("Validate PR collision - could not look up existing entry\n");
|
||||
|
||||
goto Abort_unlock_break;
|
||||
}
|
||||
|
||||
if (existingEntry->ParentProcessId != newEntry->ParentProcessId)
|
||||
{
|
||||
DbgPrint("Validate PR collision - different parent process\n");
|
||||
|
||||
goto Abort_unlock_break;
|
||||
}
|
||||
|
||||
if (existingEntry->ImageName.Length == 0)
|
||||
{
|
||||
if (newEntry->ImageName.Length != 0)
|
||||
{
|
||||
DbgPrint("Validate PR collision - "\
|
||||
"registered entry is without image name but proposed entry is not\n");
|
||||
|
||||
goto Abort_unlock_break;
|
||||
}
|
||||
|
||||
goto Approved;
|
||||
}
|
||||
|
||||
//
|
||||
// Both the existing entry and the proposed entry will have lower-case
|
||||
// imagenames so it's straight forward to compare the strings.
|
||||
//
|
||||
|
||||
const auto equalBytes = RtlCompareMemory
|
||||
(
|
||||
existingEntry->ImageName.Buffer,
|
||||
newEntry->ImageName.Buffer,
|
||||
newEntry->ImageName.Length
|
||||
);
|
||||
|
||||
if (equalBytes != newEntry->ImageName.Length)
|
||||
{
|
||||
DbgPrint("Validate PR collision - mismatched image name\n");
|
||||
|
||||
goto Abort_unlock_break;
|
||||
}
|
||||
|
||||
Approved:
|
||||
|
||||
DbgPrint("Process registry collision validation has succeeded\n");
|
||||
|
||||
return true;
|
||||
|
||||
Abort_unlock_break:
|
||||
|
||||
DbgPrint("Process registry collision validation has failed\n");
|
||||
|
||||
DbgPrint("Existing entry at %p\n", existingEntry);
|
||||
DbgPrint("New proposed entry at %p\n", newEntry);
|
||||
|
||||
util::StopIfDebugBuild();
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
struct ArrivalEvent
|
||||
{
|
||||
UINT32 SplittingReason;
|
||||
bool EmitEvent;
|
||||
|
||||
//
|
||||
// Successfully adding a new entry in the process registry makes the
|
||||
// registry take ownership of the imagename buffer passed.
|
||||
//
|
||||
// Therefore, if we need to emit a splitting event for a successful addition,
|
||||
// we have to duplicate the imagename here to preserve it.
|
||||
//
|
||||
LOWER_UNICODE_STRING Imagename;
|
||||
};
|
||||
|
||||
void
|
||||
EvaluateSplitting
|
||||
(
|
||||
CONTEXT *Context,
|
||||
procregistry::PROCESS_REGISTRY_ENTRY *RegistryEntry,
|
||||
ArrivalEvent *ArrivalEvent
|
||||
)
|
||||
{
|
||||
auto registeredImage = Context->RegisteredImage->Instance;
|
||||
|
||||
if (registeredimage::HasEntryExact(registeredImage, &RegistryEntry->ImageName))
|
||||
{
|
||||
RegistryEntry->Settings.Split = ST_PROCESS_SPLIT_STATUS_ON_BY_CONFIG;
|
||||
ArrivalEvent->SplittingReason |= ST_SPLITTING_REASON_BY_CONFIG;
|
||||
|
||||
goto Duplicate_imagename;
|
||||
}
|
||||
|
||||
//
|
||||
// Note that we're providing an entry which is not yet added to the registry.
|
||||
// This may seem wrong but is totally fine.
|
||||
//
|
||||
auto processRegistry = Context->ProcessRegistry;
|
||||
|
||||
auto parent = procregistry::GetParentEntry(processRegistry->Instance, RegistryEntry);
|
||||
|
||||
if (parent == NULL || !util::SplittingEnabled(parent->Settings.Split))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
RegistryEntry->Settings.Split = ST_PROCESS_SPLIT_STATUS_ON_BY_INHERITANCE;
|
||||
ArrivalEvent->SplittingReason |= ST_SPLITTING_REASON_BY_INHERITANCE;
|
||||
|
||||
Duplicate_imagename:
|
||||
|
||||
ArrivalEvent->EmitEvent = true;
|
||||
|
||||
auto status = util::DuplicateString
|
||||
(
|
||||
&RegistryEntry->ImageName,
|
||||
&ArrivalEvent->Imagename,
|
||||
ST_PAGEABLE::NO
|
||||
);
|
||||
|
||||
if (NT_SUCCESS(status))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
DbgPrint("Cannot emit splitting event for arriving process due to resource exhaustion\n");
|
||||
|
||||
ArrivalEvent->EmitEvent = false;
|
||||
}
|
||||
|
||||
void
|
||||
HandleProcessArriving
|
||||
(
|
||||
CONTEXT *Context,
|
||||
const procmon::PROCESS_EVENT *Record
|
||||
)
|
||||
{
|
||||
//DbgPrint("Process arriving: 0x%X\n", Record->ProcessId);
|
||||
//DbgPrint(" Parent: 0x%X\n", Record->Details->ParentProcessId);
|
||||
//DbgPrint(" Path: %wZ\n", Record->Details->Path);
|
||||
|
||||
//
|
||||
// State lock is held and is locking out IOCTL handlers.
|
||||
//
|
||||
// The process registry lock will be required for updating the process registry.
|
||||
// The configuration lock won't be required.
|
||||
//
|
||||
|
||||
auto processRegistry = Context->ProcessRegistry;
|
||||
|
||||
procregistry::PROCESS_REGISTRY_ENTRY registryEntry = { 0 };
|
||||
|
||||
auto status = procregistry::InitializeEntry
|
||||
(
|
||||
processRegistry->Instance,
|
||||
Record->Details->ParentProcessId,
|
||||
Record->ProcessId,
|
||||
ST_PROCESS_SPLIT_STATUS_OFF,
|
||||
&(Record->Details->ImageName),
|
||||
®istryEntry
|
||||
);
|
||||
|
||||
if (!NT_SUCCESS(status))
|
||||
{
|
||||
DbgPrint("Failed to initialize entry for arriving process: status 0x%X\n", status);
|
||||
DbgPrint(" PID of arriving process %p\n", Record->ProcessId);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
ArrivalEvent arrivalEvent =
|
||||
{
|
||||
.SplittingReason = ST_SPLITTING_REASON_PROCESS_ARRIVING,
|
||||
.EmitEvent = false
|
||||
};
|
||||
|
||||
if (Context->EngagedStateActive(Context->CallbackContext))
|
||||
{
|
||||
EvaluateSplitting(Context, ®istryEntry, &arrivalEvent);
|
||||
}
|
||||
|
||||
//
|
||||
// Insert entry into registry.
|
||||
//
|
||||
|
||||
WdfSpinLockAcquire(processRegistry->Lock);
|
||||
|
||||
status = procregistry::AddEntry(processRegistry->Instance, ®istryEntry);
|
||||
|
||||
WdfSpinLockRelease(processRegistry->Lock);
|
||||
|
||||
if (NT_SUCCESS(status))
|
||||
{
|
||||
//
|
||||
// Entry was successfully added and we no longer own the imagename buffer
|
||||
// referenced by the registry entry.
|
||||
//
|
||||
|
||||
if (arrivalEvent.EmitEvent)
|
||||
{
|
||||
auto splittingEvent = eventing::BuildStartSplittingEvent
|
||||
(
|
||||
Record->ProcessId,
|
||||
(ST_SPLITTING_STATUS_CHANGE_REASON)arrivalEvent.SplittingReason,
|
||||
&arrivalEvent.Imagename
|
||||
);
|
||||
|
||||
eventing::Emit(Context->Eventing, &splittingEvent);
|
||||
}
|
||||
}
|
||||
else if (status == STATUS_DUPLICATE_OBJECTID)
|
||||
{
|
||||
//
|
||||
// During driver initialization it may happen that the process registry is
|
||||
// populated with processes that are also queued to the current function.
|
||||
//
|
||||
// This is usually fine, but has to be verified to ensure it's an exact duplicate
|
||||
// and not just a PID collision.
|
||||
//
|
||||
// The latter would indicate that events are not being queued in an orderly fashion
|
||||
// or went missing alltogether.
|
||||
//
|
||||
// In case the collision is approved - Do NOT emit an event since the corresponding
|
||||
// event will already have been emitted.
|
||||
//
|
||||
|
||||
auto validationStatus = ValidateCollision(Context, ®istryEntry);
|
||||
|
||||
if (!validationStatus && arrivalEvent.EmitEvent)
|
||||
{
|
||||
auto splittingErrorEvent = eventing::BuildStartSplittingErrorEvent
|
||||
(
|
||||
Record->ProcessId,
|
||||
®istryEntry.ImageName
|
||||
);
|
||||
|
||||
eventing::Emit(Context->Eventing, &splittingErrorEvent);
|
||||
}
|
||||
|
||||
procregistry::ReleaseEntry(®istryEntry);
|
||||
}
|
||||
else
|
||||
{
|
||||
//
|
||||
// General error handling.
|
||||
//
|
||||
|
||||
DbgPrint("Failed to add entry for arriving process: status 0x%X.\n", status);
|
||||
DbgPrint(" PID of arriving process %p\n", Record->ProcessId);
|
||||
|
||||
if (arrivalEvent.EmitEvent)
|
||||
{
|
||||
auto splittingErrorEvent = eventing::BuildStartSplittingErrorEvent
|
||||
(
|
||||
Record->ProcessId,
|
||||
®istryEntry.ImageName
|
||||
);
|
||||
|
||||
eventing::Emit(Context->Eventing, &splittingErrorEvent);
|
||||
}
|
||||
|
||||
procregistry::ReleaseEntry(®istryEntry);
|
||||
}
|
||||
|
||||
//
|
||||
// Clean up event data.
|
||||
//
|
||||
|
||||
if (arrivalEvent.Imagename.Buffer != NULL)
|
||||
{
|
||||
util::FreeStringBuffer(&arrivalEvent.Imagename);
|
||||
}
|
||||
|
||||
//
|
||||
// No need to update the firewall because the arriving process won't
|
||||
// have any existing connections.
|
||||
//
|
||||
}
|
||||
|
||||
NTSTATUS
|
||||
UpdateFirewallDepartingProcess
|
||||
(
|
||||
CONTEXT *Context,
|
||||
procregistry::PROCESS_REGISTRY_ENTRY *registryEntry
|
||||
)
|
||||
{
|
||||
//
|
||||
// It's inferred that we're in the engaged state.
|
||||
// Because we found a process record that has firewall state.
|
||||
// But leave this assert here for now.
|
||||
//
|
||||
NT_ASSERT(Context->EngagedStateActive(Context->CallbackContext));
|
||||
|
||||
auto status = firewall::TransactionBegin(Context->Firewall);
|
||||
|
||||
if (!NT_SUCCESS(status))
|
||||
{
|
||||
DbgPrint("Failed to create firewall transaction: 0x%X\n", status);
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
status = firewall::RegisterAppBecomingUnsplitTx2(Context->Firewall, ®istryEntry->ImageName);
|
||||
|
||||
if (!NT_SUCCESS(status))
|
||||
{
|
||||
DbgPrint("Failed to update firewall: 0x%X\n", status);
|
||||
|
||||
auto s2 = firewall::TransactionAbort(Context->Firewall);
|
||||
|
||||
if (!NT_SUCCESS(s2))
|
||||
{
|
||||
DbgPrint("Failed to abort firewall transaction: 0x%X\n", s2);
|
||||
}
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
status = firewall::TransactionCommit(Context->Firewall);
|
||||
|
||||
if (!NT_SUCCESS(status))
|
||||
{
|
||||
DbgPrint("Failed to commit firewall transaction: 0x%X\n", status);
|
||||
|
||||
auto s2 = firewall::TransactionAbort(Context->Firewall);
|
||||
|
||||
if (!NT_SUCCESS(s2))
|
||||
{
|
||||
DbgPrint("Failed to abort firewall transaction: 0x%X\n", s2);
|
||||
}
|
||||
}
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
void
|
||||
HandleProcessDeparting
|
||||
(
|
||||
CONTEXT *Context,
|
||||
const procmon::PROCESS_EVENT *Record
|
||||
)
|
||||
{
|
||||
//DbgPrint("Process departing: 0x%X\n", Record->ProcessId);
|
||||
|
||||
//
|
||||
// We're still at PASSIVE_LEVEL and the state lock is held.
|
||||
// IOCTL handlers are locked out.
|
||||
//
|
||||
// Complete all processing and acquire the spin lock only when
|
||||
// updating the process tree.
|
||||
//
|
||||
|
||||
auto processRegistry = Context->ProcessRegistry;
|
||||
|
||||
auto registryEntry = procregistry::FindEntry(processRegistry->Instance, Record->ProcessId);
|
||||
|
||||
if (NULL == registryEntry)
|
||||
{
|
||||
DbgPrint("Received process-departing event for unknown PID\n");
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (registryEntry->Settings.HasFirewallState)
|
||||
{
|
||||
auto status = UpdateFirewallDepartingProcess(Context, registryEntry);
|
||||
|
||||
eventing::RAW_EVENT *evt = NULL;
|
||||
|
||||
if (NT_SUCCESS(status))
|
||||
{
|
||||
evt = eventing::BuildStopSplittingEvent(registryEntry->ProcessId,
|
||||
ST_SPLITTING_REASON_PROCESS_DEPARTING, ®istryEntry->ImageName);
|
||||
}
|
||||
else
|
||||
{
|
||||
evt = eventing::BuildStopSplittingErrorEvent(registryEntry->ProcessId,
|
||||
®istryEntry->ImageName);
|
||||
}
|
||||
|
||||
eventing::Emit(Context->Eventing, &evt);
|
||||
}
|
||||
else if (util::SplittingEnabled(registryEntry->Settings.Split))
|
||||
{
|
||||
auto splittingEvent = eventing::BuildStopSplittingEvent(Record->ProcessId,
|
||||
ST_SPLITTING_REASON_PROCESS_DEPARTING, ®istryEntry->ImageName);
|
||||
|
||||
eventing::Emit(Context->Eventing, &splittingEvent);
|
||||
}
|
||||
|
||||
WdfSpinLockAcquire(processRegistry->Lock);
|
||||
|
||||
procregistry::DeleteEntry(processRegistry->Instance, registryEntry);
|
||||
|
||||
WdfSpinLockRelease(processRegistry->Lock);
|
||||
}
|
||||
|
||||
void
|
||||
NTAPI
|
||||
ProcessEventSink
|
||||
(
|
||||
const procmon::PROCESS_EVENT *Event,
|
||||
void *Context
|
||||
)
|
||||
{
|
||||
auto context = (CONTEXT*)Context;
|
||||
|
||||
const auto arriving = (Event->Details != NULL);
|
||||
|
||||
context->AcquireStateLock(context->CallbackContext);
|
||||
|
||||
if (arriving)
|
||||
{
|
||||
HandleProcessArriving(context, Event);
|
||||
}
|
||||
else
|
||||
{
|
||||
HandleProcessDeparting(context, Event);
|
||||
}
|
||||
|
||||
context->ReleaseStateLock(context->CallbackContext);
|
||||
|
||||
procbroker::Publish(context->ProcessEventBroker, Event->ProcessId, arriving);
|
||||
}
|
||||
|
||||
} // anonymous namespace
|
||||
|
||||
NTSTATUS
|
||||
Initialize
|
||||
(
|
||||
CONTEXT **Context,
|
||||
procbroker::CONTEXT *ProcessEventBroker,
|
||||
PROCESS_REGISTRY_MGMT *ProcessRegistry,
|
||||
REGISTERED_IMAGE_MGMT *RegisteredImage,
|
||||
eventing::CONTEXT *Eventing,
|
||||
firewall::CONTEXT *Firewall,
|
||||
ACQUIRE_STATE_LOCK_FN AcquireStateLock,
|
||||
RELEASE_STATE_LOCK_FN ReleaseStateLock,
|
||||
ENGAGED_STATE_ACTIVE_FN EngagedStateActive,
|
||||
void *CallbackContext
|
||||
)
|
||||
{
|
||||
auto context = (CONTEXT*)ExAllocatePoolWithTag(NonPagedPool, sizeof(CONTEXT), ST_POOL_TAG);
|
||||
|
||||
if (NULL == context)
|
||||
{
|
||||
return STATUS_INSUFFICIENT_RESOURCES;
|
||||
}
|
||||
|
||||
RtlZeroMemory(context, sizeof(*context));
|
||||
|
||||
auto status = procmon::Initialize(&context->ProcessMonitor, ProcessEventSink, context);
|
||||
|
||||
if (!NT_SUCCESS(status))
|
||||
{
|
||||
DbgPrint("procmon::Initialize() failed 0x%X\n", status);
|
||||
|
||||
ExFreePoolWithTag(context, ST_POOL_TAG);
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
context->ProcessEventBroker = ProcessEventBroker;
|
||||
context->ProcessRegistry = ProcessRegistry;
|
||||
context->RegisteredImage = RegisteredImage;
|
||||
context->Eventing = Eventing;
|
||||
context->Firewall = Firewall;
|
||||
|
||||
context->AcquireStateLock = AcquireStateLock;
|
||||
context->ReleaseStateLock = ReleaseStateLock;
|
||||
context->EngagedStateActive = EngagedStateActive;
|
||||
context->CallbackContext = CallbackContext;
|
||||
|
||||
*Context = context;
|
||||
|
||||
return STATUS_SUCCESS;
|
||||
}
|
||||
|
||||
void
|
||||
TearDown
|
||||
(
|
||||
CONTEXT **Context
|
||||
)
|
||||
{
|
||||
auto context = *Context;
|
||||
|
||||
procmon::TearDown(&context->ProcessMonitor);
|
||||
|
||||
ExFreePoolWithTag(context, ST_POOL_TAG);
|
||||
|
||||
*Context = NULL;
|
||||
}
|
||||
|
||||
void
|
||||
Activate
|
||||
(
|
||||
CONTEXT *Context
|
||||
)
|
||||
{
|
||||
procmon::EnableDispatching(Context->ProcessMonitor);
|
||||
}
|
||||
|
||||
} // namespace procmgmt
|
||||
49
src/procmgmt/procmgmt.h
Normal file
49
src/procmgmt/procmgmt.h
Normal file
@@ -0,0 +1,49 @@
|
||||
#pragma once
|
||||
|
||||
#include <ntddk.h>
|
||||
#include <wdf.h>
|
||||
#include "../procbroker/procbroker.h"
|
||||
#include "../containers.h"
|
||||
#include "../eventing/eventing.h"
|
||||
#include "../firewall/firewall.h"
|
||||
#include "callbacks.h"
|
||||
|
||||
namespace procmgmt
|
||||
{
|
||||
|
||||
struct CONTEXT;
|
||||
|
||||
NTSTATUS
|
||||
Initialize
|
||||
(
|
||||
CONTEXT **Context,
|
||||
procbroker::CONTEXT *ProcessEventBroker,
|
||||
PROCESS_REGISTRY_MGMT *ProcessRegistry,
|
||||
REGISTERED_IMAGE_MGMT *RegisteredImage,
|
||||
eventing::CONTEXT *Eventing,
|
||||
firewall::CONTEXT *Firewall,
|
||||
ACQUIRE_STATE_LOCK_FN AcquireStateLock,
|
||||
RELEASE_STATE_LOCK_FN ReleaseStateLock,
|
||||
ENGAGED_STATE_ACTIVE_FN EngagedStateActive,
|
||||
void *CallbackContext
|
||||
);
|
||||
|
||||
void
|
||||
TearDown
|
||||
(
|
||||
CONTEXT **Context
|
||||
);
|
||||
|
||||
//
|
||||
// Activate()
|
||||
//
|
||||
// Until after you call Activate(), all process events are queued.
|
||||
// Call Activate() after the process registry is populated.
|
||||
//
|
||||
void
|
||||
Activate
|
||||
(
|
||||
CONTEXT *Context
|
||||
);
|
||||
|
||||
} // namespace procmgmt
|
||||
47
src/procmon/context.h
Normal file
47
src/procmon/context.h
Normal file
@@ -0,0 +1,47 @@
|
||||
#pragma once
|
||||
|
||||
#include <wdm.h>
|
||||
#include <wdf.h>
|
||||
#include "procmon.h"
|
||||
|
||||
namespace procmon
|
||||
{
|
||||
|
||||
struct CONTEXT
|
||||
{
|
||||
// The thread that services queued process events.
|
||||
PETHREAD DispatchWorker;
|
||||
|
||||
// Lock to coordinate work on the queue.
|
||||
WDFWAITLOCK QueueLock;
|
||||
|
||||
// Queue of incoming process events.
|
||||
LIST_ENTRY EventQueue;
|
||||
|
||||
// Event that signals worker should exit.
|
||||
KEVENT ExitWorker;
|
||||
|
||||
// Event that signals a new process event has been queued.
|
||||
KEVENT WakeUpWorker;
|
||||
|
||||
//
|
||||
// Initially events are not dispatched.
|
||||
//
|
||||
// This variable controls whether an event should only be queued or if the queue should be
|
||||
// signalled as well.
|
||||
//
|
||||
bool DispatchingEnabled;
|
||||
|
||||
//
|
||||
// Client callback function that receives process events.
|
||||
// Single client only in this layer.
|
||||
//
|
||||
PROCESS_EVENT_SINK ProcessEventSink;
|
||||
|
||||
//
|
||||
// Context to pass along when making the callback.
|
||||
//
|
||||
void *SinkContext;
|
||||
};
|
||||
|
||||
} // namespace procmon
|
||||
399
src/procmon/procmon.cpp
Normal file
399
src/procmon/procmon.cpp
Normal file
@@ -0,0 +1,399 @@
|
||||
#include <ntddk.h>
|
||||
#include "procmon.h"
|
||||
#include "context.h"
|
||||
#include "../util.h"
|
||||
#include "../defs/types.h"
|
||||
|
||||
namespace procmon
|
||||
{
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
//
|
||||
// PsSetCreateProcessNotifyRoutineEx() is broken so you can't pass context.
|
||||
//
|
||||
// This isn't ideal, especially considering creating more than once instance of this "class" will
|
||||
// send all events to the most recently registered sink.
|
||||
//
|
||||
// But... There should never be more than one instance.
|
||||
// And this lets us keep a familiar interface towards clients, so just roll with it.
|
||||
//
|
||||
CONTEXT *g_Context = NULL;
|
||||
|
||||
void
|
||||
SystemProcessEvent
|
||||
(
|
||||
PEPROCESS Process,
|
||||
HANDLE ProcessId,
|
||||
PPS_CREATE_NOTIFY_INFO CreateInfo
|
||||
)
|
||||
{
|
||||
//
|
||||
// We want to offload the system thread this is being sent on.
|
||||
// Build a self-contained event record and queue it to a dedicated thread.
|
||||
//
|
||||
|
||||
PROCESS_EVENT *record = NULL;
|
||||
|
||||
if (CreateInfo != NULL)
|
||||
{
|
||||
//
|
||||
// Process is arriving.
|
||||
//
|
||||
// First, get the filename so we can determine the size of the final
|
||||
// buffer that needs to be allocated.
|
||||
//
|
||||
|
||||
UNICODE_STRING *imageName;
|
||||
|
||||
auto status = util::GetDevicePathImageName(Process, &imageName);
|
||||
|
||||
if (!NT_SUCCESS(status))
|
||||
{
|
||||
DbgPrint("Dropping process event\n");
|
||||
DbgPrint(" Could not determine image filename, status: 0x%X\n", status);
|
||||
DbgPrint(" PID of arriving process %p\n", ProcessId);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
auto offsetDetails = util::RoundToMultiple(sizeof(PROCESS_EVENT), TYPE_ALIGNMENT(PROCESS_EVENT_DETAILS));
|
||||
auto offsetStringBuffer = util::RoundToMultiple(offsetDetails + sizeof(PROCESS_EVENT_DETAILS), TYPE_ALIGNMENT(WCHAR));
|
||||
|
||||
auto allocationSize = offsetStringBuffer + imageName->Length;
|
||||
|
||||
record = (PROCESS_EVENT *)ExAllocatePoolWithTag(PagedPool, allocationSize, ST_POOL_TAG);
|
||||
|
||||
if (record == NULL)
|
||||
{
|
||||
DbgPrint("Dropping process event\n");
|
||||
DbgPrint(" Failed to allocate memory\n");
|
||||
DbgPrint(" Imagename of arriving process %wZ\n", imageName);
|
||||
DbgPrint(" PID of arriving process %p\n", ProcessId);
|
||||
|
||||
ExFreePoolWithTag(imageName, ST_POOL_TAG);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
auto details = (PROCESS_EVENT_DETAILS*)(((CHAR*)record) + offsetDetails);
|
||||
auto stringBuffer = (WCHAR*)(((CHAR*)record) + offsetStringBuffer);
|
||||
|
||||
InitializeListHead(&record->ListEntry);
|
||||
record->ProcessId = ProcessId;
|
||||
record->Details = details;
|
||||
|
||||
details->ParentProcessId = CreateInfo->ParentProcessId;
|
||||
details->ImageName.Length = imageName->Length;
|
||||
details->ImageName.MaximumLength = imageName->Length;
|
||||
details->ImageName.Buffer = stringBuffer;
|
||||
|
||||
RtlCopyMemory(stringBuffer, imageName->Buffer, imageName->Length);
|
||||
ExFreePoolWithTag(imageName, ST_POOL_TAG);
|
||||
}
|
||||
else
|
||||
{
|
||||
//
|
||||
// Process is departing.
|
||||
//
|
||||
|
||||
record = (PROCESS_EVENT *)ExAllocatePoolWithTag(PagedPool, sizeof(PROCESS_EVENT), ST_POOL_TAG);
|
||||
|
||||
if (record == NULL)
|
||||
{
|
||||
DbgPrint("Dropping process event\n");
|
||||
DbgPrint(" Failed to allocate memory\n");
|
||||
DbgPrint(" PID of departing process %p\n", ProcessId);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
InitializeListHead(&record->ListEntry);
|
||||
record->ProcessId = ProcessId;
|
||||
record->Details = NULL;
|
||||
}
|
||||
|
||||
//
|
||||
// Queue to worker thread.
|
||||
//
|
||||
|
||||
WdfWaitLockAcquire(g_Context->QueueLock, NULL);
|
||||
|
||||
InsertTailList(&g_Context->EventQueue, &record->ListEntry);
|
||||
|
||||
if (g_Context->DispatchingEnabled)
|
||||
{
|
||||
KeSetEvent(&g_Context->WakeUpWorker, 0, FALSE);
|
||||
}
|
||||
|
||||
WdfWaitLockRelease(g_Context->QueueLock);
|
||||
}
|
||||
|
||||
void
|
||||
DispatchWorker
|
||||
(
|
||||
PVOID StartContext
|
||||
)
|
||||
{
|
||||
auto context = (CONTEXT *)StartContext;
|
||||
|
||||
for (;;)
|
||||
{
|
||||
KeWaitForSingleObject(&context->WakeUpWorker, Executive, KernelMode, FALSE, NULL);
|
||||
|
||||
WdfWaitLockAcquire(context->QueueLock, NULL);
|
||||
|
||||
if (0 != KeReadStateEvent(&context->ExitWorker))
|
||||
{
|
||||
WdfWaitLockRelease(context->QueueLock);
|
||||
|
||||
PsTerminateSystemThread(STATUS_SUCCESS);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
//
|
||||
// Reparent the queue in order to release the lock sooner.
|
||||
//
|
||||
|
||||
LIST_ENTRY queue;
|
||||
|
||||
util::ReparentList(&queue, &context->EventQueue);
|
||||
|
||||
KeClearEvent(&context->WakeUpWorker);
|
||||
|
||||
WdfWaitLockRelease(context->QueueLock);
|
||||
|
||||
//
|
||||
// There are one or more records queued.
|
||||
// Process all available records.
|
||||
//
|
||||
|
||||
LIST_ENTRY *record;
|
||||
|
||||
while ((record = RemoveHeadList(&queue)) != &queue)
|
||||
{
|
||||
context->ProcessEventSink((PROCESS_EVENT*)record, context->SinkContext);
|
||||
|
||||
ExFreePoolWithTag(record, ST_POOL_TAG);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // anonymous namespace
|
||||
|
||||
NTSTATUS
|
||||
Initialize
|
||||
(
|
||||
CONTEXT **Context,
|
||||
PROCESS_EVENT_SINK ProcessEventSink,
|
||||
void *SinkContext
|
||||
)
|
||||
{
|
||||
*Context = NULL;
|
||||
|
||||
bool notifyRoutineRegistered = false;
|
||||
|
||||
auto context = (CONTEXT*)ExAllocatePoolWithTag(NonPagedPool, sizeof(CONTEXT), ST_POOL_TAG);
|
||||
|
||||
if (NULL == context)
|
||||
{
|
||||
return STATUS_INSUFFICIENT_RESOURCES;
|
||||
}
|
||||
|
||||
RtlZeroMemory(context, sizeof(*context));
|
||||
|
||||
context->ProcessEventSink = ProcessEventSink;
|
||||
context->SinkContext = SinkContext;
|
||||
|
||||
InitializeListHead(&context->EventQueue);
|
||||
|
||||
KeInitializeEvent(&context->ExitWorker, NotificationEvent, FALSE);
|
||||
KeInitializeEvent(&context->WakeUpWorker, NotificationEvent, FALSE);
|
||||
|
||||
auto status = WdfWaitLockCreate(WDF_NO_OBJECT_ATTRIBUTES, &context->QueueLock);
|
||||
|
||||
if (!NT_SUCCESS(status))
|
||||
{
|
||||
DbgPrint("WdfWaitLockCreate() failed 0x%X\n", status);
|
||||
|
||||
context->QueueLock = NULL;
|
||||
|
||||
goto Abort;
|
||||
}
|
||||
|
||||
g_Context = context;
|
||||
|
||||
//
|
||||
// It's alright to register for notifications before starting the worker thread.
|
||||
//
|
||||
// Events that come in before the thread is created are queued.
|
||||
// So no event will be lost.
|
||||
//
|
||||
// Also, the thread doesn't own the queued events so nothing is leaked even
|
||||
// if the thread fails to process events in a timely manner, or at all.
|
||||
//
|
||||
// Also, clean-up is simpler if thread creation is the last fallible operation.
|
||||
//
|
||||
|
||||
status = PsSetCreateProcessNotifyRoutineEx(SystemProcessEvent, FALSE);
|
||||
|
||||
if (!NT_SUCCESS(status))
|
||||
{
|
||||
DbgPrint("PsSetCreateProcessNotifyRoutineEx() failed 0x%X\n", status);
|
||||
|
||||
goto Abort;
|
||||
}
|
||||
|
||||
notifyRoutineRegistered = true;
|
||||
|
||||
//
|
||||
// Create the thread that will be servicing events.
|
||||
//
|
||||
|
||||
OBJECT_ATTRIBUTES threadAttributes;
|
||||
|
||||
InitializeObjectAttributes(&threadAttributes, NULL, OBJ_KERNEL_HANDLE, NULL, NULL);
|
||||
|
||||
HANDLE threadHandle;
|
||||
|
||||
status = PsCreateSystemThread
|
||||
(
|
||||
&threadHandle,
|
||||
THREAD_ALL_ACCESS,
|
||||
&threadAttributes,
|
||||
NULL,
|
||||
NULL,
|
||||
DispatchWorker,
|
||||
context
|
||||
);
|
||||
|
||||
if (!NT_SUCCESS(status))
|
||||
{
|
||||
DbgPrint("PsCreateSystemThread() failed 0x%X\n", status);
|
||||
DbgPrint("Could not create process monitoring thread\n");
|
||||
|
||||
goto Abort;
|
||||
}
|
||||
|
||||
//
|
||||
// ObReference... will never fail if the handle is valid.
|
||||
//
|
||||
|
||||
status = ObReferenceObjectByHandle
|
||||
(
|
||||
threadHandle,
|
||||
THREAD_ALL_ACCESS,
|
||||
NULL,
|
||||
KernelMode,
|
||||
(PVOID *)&context->DispatchWorker,
|
||||
NULL
|
||||
);
|
||||
|
||||
ZwClose(threadHandle);
|
||||
|
||||
*Context = context;
|
||||
|
||||
return STATUS_SUCCESS;
|
||||
|
||||
Abort:
|
||||
|
||||
if (notifyRoutineRegistered)
|
||||
{
|
||||
PsSetCreateProcessNotifyRoutineEx(SystemProcessEvent, TRUE);
|
||||
|
||||
//
|
||||
// Drain event queue to avoid leaking events.
|
||||
//
|
||||
|
||||
LIST_ENTRY *record;
|
||||
|
||||
while ((record = RemoveHeadList(&context->EventQueue)) != &context->EventQueue)
|
||||
{
|
||||
ExFreePoolWithTag(record, ST_POOL_TAG);
|
||||
}
|
||||
}
|
||||
|
||||
if (context->QueueLock != NULL)
|
||||
{
|
||||
WdfObjectDelete(context->QueueLock);
|
||||
}
|
||||
|
||||
ExFreePoolWithTag(context, ST_POOL_TAG);
|
||||
g_Context = NULL;
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
void
|
||||
TearDown
|
||||
(
|
||||
CONTEXT **Context
|
||||
)
|
||||
{
|
||||
auto context = *Context;
|
||||
|
||||
//
|
||||
// Deregister notify routine so we stop queuing events.
|
||||
// This can never fail according to documentation.
|
||||
//
|
||||
|
||||
PsSetCreateProcessNotifyRoutineEx(SystemProcessEvent, TRUE);
|
||||
|
||||
//
|
||||
// Tell worker thread to exit and wait for it to happen.
|
||||
//
|
||||
|
||||
WdfWaitLockAcquire(context->QueueLock, NULL);
|
||||
|
||||
KeSetEvent(&context->ExitWorker, 0, FALSE);
|
||||
KeSetEvent(&context->WakeUpWorker, 1, FALSE);
|
||||
|
||||
WdfWaitLockRelease(context->QueueLock);
|
||||
|
||||
KeWaitForSingleObject(context->DispatchWorker, Executive, KernelMode, FALSE, NULL);
|
||||
|
||||
ObDereferenceObject(context->DispatchWorker);
|
||||
|
||||
//
|
||||
// Drain event queue to avoid leaking events.
|
||||
//
|
||||
|
||||
LIST_ENTRY *record;
|
||||
|
||||
while ((record = RemoveHeadList(&context->EventQueue)) != &context->EventQueue)
|
||||
{
|
||||
ExFreePoolWithTag(record, ST_POOL_TAG);
|
||||
}
|
||||
|
||||
//
|
||||
// Release remaining resources.
|
||||
//
|
||||
|
||||
WdfObjectDelete(context->QueueLock);
|
||||
|
||||
ExFreePoolWithTag(context, ST_POOL_TAG);
|
||||
|
||||
*Context = NULL;
|
||||
}
|
||||
|
||||
void
|
||||
EnableDispatching
|
||||
(
|
||||
CONTEXT *Context
|
||||
)
|
||||
{
|
||||
WdfWaitLockAcquire(Context->QueueLock, NULL);
|
||||
|
||||
Context->DispatchingEnabled = true;
|
||||
|
||||
if (!IsListEmpty(&Context->EventQueue))
|
||||
{
|
||||
KeSetEvent(&Context->WakeUpWorker, 0, FALSE);
|
||||
}
|
||||
|
||||
WdfWaitLockRelease(Context->QueueLock);
|
||||
}
|
||||
|
||||
}
|
||||
55
src/procmon/procmon.h
Normal file
55
src/procmon/procmon.h
Normal file
@@ -0,0 +1,55 @@
|
||||
#pragma once
|
||||
|
||||
#include <wdm.h>
|
||||
|
||||
namespace procmon
|
||||
{
|
||||
|
||||
typedef struct tag_PROCESS_EVENT_DETAILS
|
||||
{
|
||||
HANDLE ParentProcessId;
|
||||
|
||||
// Device path using mixed case characters.
|
||||
UNICODE_STRING ImageName;
|
||||
}
|
||||
PROCESS_EVENT_DETAILS;
|
||||
|
||||
typedef struct tag_PROCESS_EVENT
|
||||
{
|
||||
LIST_ENTRY ListEntry;
|
||||
|
||||
HANDLE ProcessId;
|
||||
|
||||
//
|
||||
// `Details` will be present and valid for processes that are arriving.
|
||||
// If a process is departing, this field is set to NULL.
|
||||
//
|
||||
PROCESS_EVENT_DETAILS *Details;
|
||||
}
|
||||
PROCESS_EVENT;
|
||||
|
||||
typedef void (NTAPI *PROCESS_EVENT_SINK)(const PROCESS_EVENT *Event, void *Context);
|
||||
|
||||
struct CONTEXT;
|
||||
|
||||
NTSTATUS
|
||||
Initialize
|
||||
(
|
||||
CONTEXT **Context,
|
||||
PROCESS_EVENT_SINK ProcessEventSink,
|
||||
void *SinkContext
|
||||
);
|
||||
|
||||
void
|
||||
TearDown
|
||||
(
|
||||
CONTEXT **Context
|
||||
);
|
||||
|
||||
void
|
||||
EnableDispatching
|
||||
(
|
||||
CONTEXT *Context
|
||||
);
|
||||
|
||||
} // namespace procmon
|
||||
10
src/public.h
Normal file
10
src/public.h
Normal file
@@ -0,0 +1,10 @@
|
||||
#pragma once
|
||||
|
||||
#include "x64guard.h"
|
||||
#include "ipaddr.h"
|
||||
#include "defs/state.h"
|
||||
#include "defs/ioctl.h"
|
||||
#include "defs/config.h"
|
||||
#include "defs/process.h"
|
||||
#include "defs/queryprocess.h"
|
||||
#include "defs/events.h"
|
||||
34
src/resource.rc
Normal file
34
src/resource.rc
Normal file
@@ -0,0 +1,34 @@
|
||||
#include "version.h"
|
||||
|
||||
#define STRINGIFY(X) #X
|
||||
#define EXPANDSTR(X) STRINGIFY(X)
|
||||
#define MAKE_VERSION_STR(A,B,C,D) EXPANDSTR(A) "." EXPANDSTR(B) "." EXPANDSTR(C) "." EXPANDSTR(D)
|
||||
#define CALL(A,B) A B
|
||||
#define DRIVER_VERSION_STR_HELPER(X) CALL(MAKE_VERSION_STR,(X))
|
||||
|
||||
#define DRIVER_VERSION DRIVER_VERSION_MAJOR,DRIVER_VERSION_MINOR,DRIVER_VERSION_PATCH,DRIVER_VERSION_BUILD
|
||||
#define DRIVER_VERSION_STR DRIVER_VERSION_STR_HELPER(DRIVER_VERSION)
|
||||
|
||||
1 VERSIONINFO
|
||||
FILEVERSION DRIVER_VERSION
|
||||
PRODUCTVERSION DRIVER_VERSION
|
||||
BEGIN
|
||||
BLOCK "StringFileInfo"
|
||||
BEGIN
|
||||
BLOCK "040904E4"
|
||||
BEGIN
|
||||
VALUE "CompanyName", "Mullvad VPN AB"
|
||||
VALUE "FileDescription", "Split Tunnel Kernel Driver"
|
||||
VALUE "FileVersion", DRIVER_VERSION_STR
|
||||
VALUE "InternalName", "mullvad-split-tunnel"
|
||||
VALUE "LegalCopyright", "(c) 2021 Mullvad VPN AB"
|
||||
VALUE "OriginalFilename", "mullvad-split-tunnel.sys"
|
||||
VALUE "ProductName", "Mullvad VPN"
|
||||
VALUE "ProductVersion", DRIVER_VERSION_STR
|
||||
END
|
||||
END
|
||||
BLOCK "VarFileInfo"
|
||||
BEGIN
|
||||
VALUE "Translation", 0x409, 1252
|
||||
END
|
||||
END
|
||||
339
src/util.cpp
Normal file
339
src/util.cpp
Normal file
@@ -0,0 +1,339 @@
|
||||
#include <ntifs.h>
|
||||
#include "util.h"
|
||||
|
||||
namespace util
|
||||
{
|
||||
|
||||
void
|
||||
ReparentList(LIST_ENTRY *dest, LIST_ENTRY *src)
|
||||
{
|
||||
//
|
||||
// If it's an empty list there is nothing to reparent.
|
||||
//
|
||||
|
||||
if (src->Flink == src)
|
||||
{
|
||||
InitializeListHead(dest);
|
||||
return;
|
||||
}
|
||||
|
||||
//
|
||||
// Replace root node.
|
||||
//
|
||||
|
||||
*dest = *src;
|
||||
|
||||
//
|
||||
// Update links on first and last entry.
|
||||
//
|
||||
|
||||
dest->Flink->Blink = dest;
|
||||
dest->Blink->Flink = dest;
|
||||
|
||||
//
|
||||
// Reinitialize original root node.
|
||||
//
|
||||
|
||||
InitializeListHead(src);
|
||||
}
|
||||
|
||||
typedef NTSTATUS (*QUERY_INFO_PROCESS) (
|
||||
__in HANDLE ProcessHandle,
|
||||
__in PROCESSINFOCLASS ProcessInformationClass,
|
||||
__out_bcount(ProcessInformationLength) PVOID ProcessInformation,
|
||||
__in ULONG ProcessInformationLength,
|
||||
__out_opt PULONG ReturnLength
|
||||
);
|
||||
|
||||
extern "C"
|
||||
NTSTATUS
|
||||
GetDevicePathImageName
|
||||
(
|
||||
PEPROCESS Process,
|
||||
UNICODE_STRING **ImageName
|
||||
)
|
||||
{
|
||||
*ImageName = NULL;
|
||||
|
||||
HANDLE processHandle;
|
||||
|
||||
auto status = ObOpenObjectByPointer
|
||||
(
|
||||
Process,
|
||||
OBJ_KERNEL_HANDLE,
|
||||
NULL,
|
||||
GENERIC_READ,
|
||||
NULL,
|
||||
KernelMode,
|
||||
&processHandle
|
||||
);
|
||||
|
||||
if (!NT_SUCCESS(status))
|
||||
{
|
||||
return status;
|
||||
}
|
||||
|
||||
static QUERY_INFO_PROCESS QueryFunction = NULL;
|
||||
|
||||
if (QueryFunction == NULL)
|
||||
{
|
||||
DECLARE_CONST_UNICODE_STRING(queryName, L"ZwQueryInformationProcess");
|
||||
|
||||
QueryFunction = (QUERY_INFO_PROCESS)
|
||||
MmGetSystemRoutineAddress((UNICODE_STRING*)&queryName);
|
||||
|
||||
if (NULL == QueryFunction)
|
||||
{
|
||||
// TODO: Use more appropriate error code
|
||||
status = STATUS_NOT_CAPABLE;
|
||||
|
||||
goto Failure;
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// Determine required size of name buffer.
|
||||
//
|
||||
|
||||
ULONG bufferLength;
|
||||
|
||||
status = QueryFunction
|
||||
(
|
||||
processHandle,
|
||||
ProcessImageFileName,
|
||||
NULL,
|
||||
0,
|
||||
&bufferLength
|
||||
);
|
||||
|
||||
if (status != STATUS_INFO_LENGTH_MISMATCH)
|
||||
{
|
||||
goto Failure;
|
||||
}
|
||||
|
||||
//
|
||||
// Allocate name buffer.
|
||||
//
|
||||
|
||||
*ImageName = (UNICODE_STRING*)ExAllocatePoolWithTag(PagedPool, bufferLength, ST_POOL_TAG);
|
||||
|
||||
if (NULL == *ImageName)
|
||||
{
|
||||
status = STATUS_INSUFFICIENT_RESOURCES;
|
||||
|
||||
goto Failure;
|
||||
}
|
||||
|
||||
//
|
||||
// Retrieve filename.
|
||||
//
|
||||
|
||||
status = QueryFunction
|
||||
(
|
||||
processHandle,
|
||||
ProcessImageFileName,
|
||||
*ImageName,
|
||||
bufferLength,
|
||||
&bufferLength
|
||||
);
|
||||
|
||||
if (NT_SUCCESS(status))
|
||||
{
|
||||
goto Cleanup;
|
||||
}
|
||||
|
||||
Failure:
|
||||
|
||||
if (*ImageName != NULL)
|
||||
{
|
||||
ExFreePoolWithTag(*ImageName, ST_POOL_TAG);
|
||||
}
|
||||
|
||||
Cleanup:
|
||||
|
||||
ZwClose(processHandle);
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
bool
|
||||
ValidateBufferRange
|
||||
(
|
||||
void *Buffer,
|
||||
void *BufferEnd,
|
||||
SIZE_T RangeOffset,
|
||||
SIZE_T RangeLength
|
||||
)
|
||||
{
|
||||
if (RangeLength == 0)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
auto range = (UCHAR*)Buffer + RangeOffset;
|
||||
auto rangeEnd = range + RangeLength;
|
||||
|
||||
if (range < (UCHAR*)Buffer
|
||||
|| range >= (UCHAR*)BufferEnd
|
||||
|| rangeEnd < range
|
||||
|| rangeEnd > BufferEnd)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool
|
||||
IsEmptyRange
|
||||
(
|
||||
const void *Buffer,
|
||||
SIZE_T Length
|
||||
)
|
||||
{
|
||||
//
|
||||
// TODO
|
||||
//
|
||||
// Assuming x64, round down `Length` and read QWORDs from the buffer.
|
||||
// Then read the last few bytes in this silly byte-by-byte manner.
|
||||
//
|
||||
|
||||
for (auto b = (const UCHAR*)Buffer; Length != 0; ++b, --Length)
|
||||
{
|
||||
if (*b != 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
NTSTATUS
|
||||
AllocateCopyDowncaseString
|
||||
(
|
||||
const UNICODE_STRING * const In,
|
||||
LOWER_UNICODE_STRING *Out,
|
||||
ST_PAGEABLE Pageable
|
||||
)
|
||||
{
|
||||
//
|
||||
// Unfortunately, there is no way to determine the required buffer size.
|
||||
//
|
||||
// It would be possible to allocate e.g. `In.Length * 1.5` bytes, and waste memory.
|
||||
//
|
||||
// We opt for the slightly less time efficient method of allocating an exact size
|
||||
// twice and copying the string.
|
||||
//
|
||||
|
||||
UNICODE_STRING lower;
|
||||
|
||||
auto status = RtlDowncaseUnicodeString(&lower, In, TRUE);
|
||||
|
||||
if (!NT_SUCCESS(status))
|
||||
{
|
||||
return status;
|
||||
}
|
||||
|
||||
const auto poolType = (Pageable == ST_PAGEABLE::YES) ? PagedPool : NonPagedPool;
|
||||
|
||||
auto finalBuffer = (PWCH)ExAllocatePoolWithTag(poolType, lower.Length, ST_POOL_TAG);
|
||||
|
||||
if (finalBuffer == NULL)
|
||||
{
|
||||
RtlFreeUnicodeString(&lower);
|
||||
|
||||
return STATUS_INSUFFICIENT_RESOURCES;
|
||||
}
|
||||
|
||||
RtlCopyMemory(finalBuffer, lower.Buffer, lower.Length);
|
||||
|
||||
Out->Length = lower.Length;
|
||||
Out->MaximumLength = lower.Length;
|
||||
Out->Buffer = finalBuffer;
|
||||
|
||||
RtlFreeUnicodeString(&lower);
|
||||
|
||||
return STATUS_SUCCESS;
|
||||
}
|
||||
|
||||
void
|
||||
FreeStringBuffer
|
||||
(
|
||||
UNICODE_STRING *String
|
||||
)
|
||||
{
|
||||
ExFreePoolWithTag(String->Buffer, ST_POOL_TAG);
|
||||
|
||||
String->Length = 0;
|
||||
String->MaximumLength = 0;
|
||||
String->Buffer = NULL;
|
||||
}
|
||||
|
||||
void
|
||||
FreeStringBuffer
|
||||
(
|
||||
LOWER_UNICODE_STRING *String
|
||||
)
|
||||
{
|
||||
return FreeStringBuffer((UNICODE_STRING*)String);
|
||||
}
|
||||
|
||||
NTSTATUS
|
||||
DuplicateString
|
||||
(
|
||||
const UNICODE_STRING *Src,
|
||||
UNICODE_STRING *Dest,
|
||||
ST_PAGEABLE Pageable
|
||||
)
|
||||
{
|
||||
const auto poolType = (Pageable == ST_PAGEABLE::YES) ? PagedPool : NonPagedPool;
|
||||
|
||||
auto buffer = (PWCH)ExAllocatePoolWithTag(poolType, Src->Length, ST_POOL_TAG);
|
||||
|
||||
if (NULL == buffer)
|
||||
{
|
||||
return STATUS_INSUFFICIENT_RESOURCES;
|
||||
}
|
||||
|
||||
RtlCopyMemory(buffer, Src->Buffer, Src->Length);
|
||||
|
||||
Dest->Length = Src->Length;
|
||||
Dest->MaximumLength = Src->Length;
|
||||
Dest->Buffer = buffer;
|
||||
|
||||
return STATUS_SUCCESS;
|
||||
}
|
||||
|
||||
NTSTATUS
|
||||
DuplicateString
|
||||
(
|
||||
const LOWER_UNICODE_STRING *Src,
|
||||
LOWER_UNICODE_STRING *Dest,
|
||||
ST_PAGEABLE Pageable
|
||||
)
|
||||
{
|
||||
return DuplicateString((const UNICODE_STRING*)Src, (UNICODE_STRING*)Dest, Pageable);
|
||||
}
|
||||
|
||||
void
|
||||
StopIfDebugBuild
|
||||
(
|
||||
)
|
||||
{
|
||||
#ifdef DEBUG
|
||||
DbgBreakPoint();
|
||||
#endif
|
||||
}
|
||||
|
||||
bool
|
||||
SplittingEnabled
|
||||
(
|
||||
ST_PROCESS_SPLIT_STATUS Status
|
||||
)
|
||||
{
|
||||
return (Status == ST_PROCESS_SPLIT_STATUS_ON_BY_CONFIG
|
||||
|| Status == ST_PROCESS_SPLIT_STATUS_ON_BY_INHERITANCE);
|
||||
}
|
||||
|
||||
} // namespace util
|
||||
121
src/util.h
Normal file
121
src/util.h
Normal file
@@ -0,0 +1,121 @@
|
||||
#pragma once
|
||||
|
||||
#include <wdm.h>
|
||||
#include "defs/types.h"
|
||||
|
||||
#define bswapw(s) (((s & 0xFF) << 8) | ((s >> 8) & 0xFF))
|
||||
|
||||
#define ntohs(s) bswapw(s)
|
||||
#define htons(s) bswapw(s)
|
||||
|
||||
template<typename T>
|
||||
bool bool_cast(T t)
|
||||
{
|
||||
return t != 0;
|
||||
}
|
||||
|
||||
namespace util
|
||||
{
|
||||
|
||||
//
|
||||
// N.B. m has to be a power of two.
|
||||
//
|
||||
inline SIZE_T RoundToMultiple(SIZE_T v, SIZE_T m)
|
||||
{
|
||||
return ((v + m - 1) & ~(m - 1));
|
||||
}
|
||||
|
||||
void
|
||||
ReparentList(LIST_ENTRY *dest, LIST_ENTRY *src);
|
||||
|
||||
//
|
||||
// GetDevicePathImageName()
|
||||
//
|
||||
// Returns the device path of the process binary.
|
||||
// I.e. the returned path begins with `\Device\HarddiskVolumeX\`
|
||||
// rather than a symbolic link of the form `\??\C:\`.
|
||||
//
|
||||
// A UNICODE_STRING structure and an associated filename buffer
|
||||
// is allocated and returned.
|
||||
//
|
||||
// TODO: The type PEPROCESS seems to require C-linkage on any function
|
||||
// that uses it as an argument. Fix, maybe.
|
||||
//
|
||||
extern "C"
|
||||
NTSTATUS
|
||||
GetDevicePathImageName
|
||||
(
|
||||
PEPROCESS Process,
|
||||
UNICODE_STRING **ImageName
|
||||
);
|
||||
|
||||
bool
|
||||
ValidateBufferRange
|
||||
(
|
||||
void *Buffer,
|
||||
void *BufferEnd,
|
||||
SIZE_T RangeOffset,
|
||||
SIZE_T RangeLength
|
||||
);
|
||||
|
||||
bool
|
||||
IsEmptyRange
|
||||
(
|
||||
const void *Buffer,
|
||||
SIZE_T Length
|
||||
);
|
||||
|
||||
//
|
||||
// AllocateCopyDowncaseString()
|
||||
//
|
||||
// Make a lower case copy of the string.
|
||||
// `Out->Buffer` is allocated and assigned.
|
||||
//
|
||||
NTSTATUS
|
||||
AllocateCopyDowncaseString
|
||||
(
|
||||
const UNICODE_STRING * const In,
|
||||
LOWER_UNICODE_STRING *Out,
|
||||
ST_PAGEABLE Pageable
|
||||
);
|
||||
|
||||
void
|
||||
FreeStringBuffer
|
||||
(
|
||||
UNICODE_STRING *String
|
||||
);
|
||||
|
||||
void
|
||||
FreeStringBuffer
|
||||
(
|
||||
LOWER_UNICODE_STRING *String
|
||||
);
|
||||
|
||||
NTSTATUS
|
||||
DuplicateString
|
||||
(
|
||||
const UNICODE_STRING *Src,
|
||||
UNICODE_STRING *Dest,
|
||||
ST_PAGEABLE Pageable
|
||||
);
|
||||
|
||||
NTSTATUS
|
||||
DuplicateString
|
||||
(
|
||||
const LOWER_UNICODE_STRING *Src,
|
||||
LOWER_UNICODE_STRING *Dest,
|
||||
ST_PAGEABLE Pageable
|
||||
);
|
||||
|
||||
void
|
||||
StopIfDebugBuild
|
||||
(
|
||||
);
|
||||
|
||||
bool
|
||||
SplittingEnabled
|
||||
(
|
||||
ST_PROCESS_SPLIT_STATUS Status
|
||||
);
|
||||
|
||||
} // namespace util
|
||||
108
src/validation.cpp
Normal file
108
src/validation.cpp
Normal file
@@ -0,0 +1,108 @@
|
||||
#include "validation.h"
|
||||
#include "defs/config.h"
|
||||
#include "defs/process.h"
|
||||
#include "util.h"
|
||||
|
||||
bool
|
||||
ValidateUserBufferConfiguration
|
||||
(
|
||||
void *Buffer,
|
||||
size_t BufferLength
|
||||
)
|
||||
{
|
||||
auto bufferEnd = (UCHAR*)Buffer + BufferLength;
|
||||
|
||||
if (BufferLength < sizeof(ST_CONFIGURATION_HEADER)
|
||||
|| bufferEnd < (UCHAR*)Buffer)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
auto header = (ST_CONFIGURATION_HEADER*)Buffer;
|
||||
|
||||
if (header->TotalLength != BufferLength)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
auto stringBuffer = (UCHAR*)Buffer
|
||||
+ sizeof(ST_CONFIGURATION_HEADER)
|
||||
+ (sizeof(ST_CONFIGURATION_ENTRY) * header->NumEntries);
|
||||
|
||||
if (stringBuffer < (UCHAR*)Buffer
|
||||
|| stringBuffer >= bufferEnd)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
//
|
||||
// Verify that all strings reside within the string buffer.
|
||||
//
|
||||
|
||||
auto entry = (ST_CONFIGURATION_ENTRY*)(header + 1);
|
||||
|
||||
for (auto i = 0; i < header->NumEntries; ++i, ++entry)
|
||||
{
|
||||
const auto valid = util::ValidateBufferRange(stringBuffer, bufferEnd,
|
||||
entry->ImageNameOffset, entry->ImageNameLength);
|
||||
|
||||
if (!valid)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool
|
||||
ValidateUserBufferProcesses
|
||||
(
|
||||
void *Buffer,
|
||||
size_t BufferLength
|
||||
)
|
||||
{
|
||||
auto bufferEnd = (UCHAR*)Buffer + BufferLength;
|
||||
|
||||
if (BufferLength < sizeof(ST_PROCESS_DISCOVERY_HEADER)
|
||||
|| bufferEnd < (UCHAR*)Buffer)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
auto header = (ST_PROCESS_DISCOVERY_HEADER*)Buffer;
|
||||
|
||||
if (header->TotalLength != BufferLength)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
auto stringBuffer = (UCHAR*)Buffer
|
||||
+ sizeof(ST_PROCESS_DISCOVERY_HEADER)
|
||||
+ (sizeof(ST_PROCESS_DISCOVERY_ENTRY) * header->NumEntries);
|
||||
|
||||
if (stringBuffer < (UCHAR*)Buffer
|
||||
|| stringBuffer >= bufferEnd)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
//
|
||||
// Verify that all strings reside within the string buffer.
|
||||
//
|
||||
|
||||
auto entry = (ST_PROCESS_DISCOVERY_ENTRY*)(header + 1);
|
||||
|
||||
for (auto i = 0; i < header->NumEntries; ++i, ++entry)
|
||||
{
|
||||
const auto valid = util::ValidateBufferRange(stringBuffer, bufferEnd,
|
||||
entry->ImageNameOffset, entry->ImageNameLength);
|
||||
|
||||
if (!valid)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
27
src/validation.h
Normal file
27
src/validation.h
Normal file
@@ -0,0 +1,27 @@
|
||||
#pragma once
|
||||
|
||||
#include <wdm.h>
|
||||
|
||||
//
|
||||
// ValidateUserBufferConfiguration()
|
||||
//
|
||||
// Validates configuration data sent by user mode.
|
||||
//
|
||||
bool
|
||||
ValidateUserBufferConfiguration
|
||||
(
|
||||
void *Buffer,
|
||||
size_t BufferLength
|
||||
);
|
||||
|
||||
//
|
||||
// ValidateUserBufferProcesses()
|
||||
//
|
||||
// Validates process data sent by user mode.
|
||||
//
|
||||
bool
|
||||
ValidateUserBufferProcesses
|
||||
(
|
||||
void *Buffer,
|
||||
size_t BufferLength
|
||||
);
|
||||
6
src/version.h
Normal file
6
src/version.h
Normal file
@@ -0,0 +1,6 @@
|
||||
#pragma once
|
||||
|
||||
#define DRIVER_VERSION_MAJOR 0
|
||||
#define DRIVER_VERSION_MINOR 1
|
||||
#define DRIVER_VERSION_PATCH 0
|
||||
#define DRIVER_VERSION_BUILD 0
|
||||
11
src/x64guard.h
Normal file
11
src/x64guard.h
Normal file
@@ -0,0 +1,11 @@
|
||||
#pragma once
|
||||
|
||||
#ifdef NTDDI_VERSION // kernel
|
||||
#ifndef _AMD64_
|
||||
#error The only supported compilation target is x64
|
||||
#endif
|
||||
#else // user
|
||||
#ifdef WIN32
|
||||
#error The only supported compilation target is x64
|
||||
#endif
|
||||
#endif
|
||||
280
testing/proc.cpp
Normal file
280
testing/proc.cpp
Normal file
@@ -0,0 +1,280 @@
|
||||
#include <vector>
|
||||
#include <cstdint>
|
||||
#include <windows.h>
|
||||
#include <tlhelp32.h>
|
||||
#include <libcommon/error.h>
|
||||
#include <libcommon/memory.h>
|
||||
#include <map>
|
||||
|
||||
#include "../src/public.h"
|
||||
|
||||
#define PSAPI_VERSION 2
|
||||
#include <psapi.h>
|
||||
|
||||
#include "proc.h"
|
||||
|
||||
using common::memory::UniqueHandle;
|
||||
|
||||
struct ProcessInfo
|
||||
{
|
||||
DWORD ProcessId;
|
||||
DWORD ParentProcessId;
|
||||
FILETIME CreationTime;
|
||||
std::wstring DevicePath;
|
||||
};
|
||||
|
||||
FILETIME GetProcessCreationTime(HANDLE processHandle)
|
||||
{
|
||||
FILETIME creationTime, dummy;
|
||||
|
||||
const auto status = GetProcessTimes(processHandle, &creationTime, &dummy, &dummy, &dummy);
|
||||
|
||||
if (FALSE == status)
|
||||
{
|
||||
THROW_WINDOWS_ERROR(GetLastError(), "GetProcessTimes");
|
||||
}
|
||||
|
||||
return creationTime;
|
||||
}
|
||||
|
||||
std::wstring GetProcessDevicePath(HANDLE processHandle)
|
||||
{
|
||||
size_t bufferSize = 512;
|
||||
std::vector<wchar_t> buffer;
|
||||
|
||||
for (;;)
|
||||
{
|
||||
buffer.resize(bufferSize);
|
||||
|
||||
const auto charsWritten = K32GetProcessImageFileNameW(processHandle,
|
||||
&buffer[0], static_cast<DWORD>(buffer.size()));
|
||||
|
||||
if (0 == charsWritten)
|
||||
{
|
||||
if (ERROR_INSUFFICIENT_BUFFER == GetLastError())
|
||||
{
|
||||
bufferSize *= 2;
|
||||
continue;
|
||||
}
|
||||
|
||||
THROW_WINDOWS_ERROR(GetLastError(), "K32GetProcessImageFileNameW");
|
||||
}
|
||||
|
||||
//
|
||||
// K32GetProcessImageFileNameW writes a null terminator
|
||||
// but doesn't account for it in the return value.
|
||||
//
|
||||
|
||||
return std::wstring(&buffer[0], &buffer[0] + charsWritten);
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// CompileProcessInfo()
|
||||
//
|
||||
// Returns a set including all processes in the system.
|
||||
//
|
||||
// The return value uses the vector container type since it's perceived
|
||||
// the set will not be searched.
|
||||
//
|
||||
std::vector<ProcessInfo> CompileProcessInfo()
|
||||
{
|
||||
auto snapshot = UniqueHandle(new HANDLE(
|
||||
(HANDLE)CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0)));
|
||||
|
||||
if (INVALID_HANDLE_VALUE == *snapshot)
|
||||
{
|
||||
THROW_WINDOWS_ERROR(GetLastError(), "Snapshot processes");
|
||||
}
|
||||
|
||||
PROCESSENTRY32W processEntry { .dwSize = sizeof(PROCESSENTRY32W) };
|
||||
|
||||
if (FALSE == Process32First(*snapshot, &processEntry))
|
||||
{
|
||||
THROW_WINDOWS_ERROR(GetLastError(), "Initiate process enumeration");
|
||||
}
|
||||
|
||||
std::map<DWORD, ProcessInfo> processes;
|
||||
|
||||
//
|
||||
// Discover all processes.
|
||||
//
|
||||
|
||||
do
|
||||
{
|
||||
auto handle = UniqueHandle(new HANDLE(OpenProcess(
|
||||
PROCESS_QUERY_LIMITED_INFORMATION, FALSE, processEntry.th32ProcessID)));
|
||||
|
||||
if (NULL == *handle)
|
||||
{
|
||||
//THROW_WINDOWS_ERROR(GetLastError(), "Open process");
|
||||
continue;
|
||||
}
|
||||
|
||||
ProcessInfo pi;
|
||||
|
||||
pi.ProcessId = processEntry.th32ProcessID;
|
||||
pi.ParentProcessId = processEntry.th32ParentProcessID;
|
||||
|
||||
try
|
||||
{
|
||||
pi.CreationTime = GetProcessCreationTime(*handle);
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
pi.CreationTime = { 0 };
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
pi.DevicePath = GetProcessDevicePath(*handle);
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
//
|
||||
// Including a process without a path might seem useless.
|
||||
// But it enables ancestor discovery.
|
||||
//
|
||||
|
||||
pi.DevicePath = L"";
|
||||
}
|
||||
|
||||
processes.insert(std::make_pair(pi.ProcessId, pi));
|
||||
}
|
||||
while (FALSE != Process32NextW(*snapshot, &processEntry));
|
||||
|
||||
//
|
||||
// Find instances of PID recycling.
|
||||
//
|
||||
// This can be done by checking the creation time of the parent
|
||||
// process and discovering that the age of the claimed parent process
|
||||
// is lower than that of the child process.
|
||||
//
|
||||
|
||||
for (auto& [pid, process] : processes)
|
||||
{
|
||||
auto parentIter = processes.find(process.ParentProcessId);
|
||||
|
||||
if (parentIter != processes.end())
|
||||
{
|
||||
ULARGE_INTEGER parentTime { .LowPart = parentIter->second.CreationTime.dwLowDateTime,
|
||||
.HighPart = parentIter->second.CreationTime.dwHighDateTime };
|
||||
|
||||
ULARGE_INTEGER processTime { .LowPart = process.CreationTime.dwLowDateTime,
|
||||
.HighPart = process.CreationTime.dwHighDateTime };
|
||||
|
||||
if (0 != parentTime.QuadPart
|
||||
&& parentTime.QuadPart < processTime.QuadPart)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
process.ParentProcessId = 0;
|
||||
}
|
||||
|
||||
//
|
||||
// Store process records into vector.
|
||||
//
|
||||
|
||||
std::vector<ProcessInfo> output;
|
||||
|
||||
output.reserve(processes.size());
|
||||
|
||||
std::transform(processes.begin(), processes.end(), std::back_inserter(output),
|
||||
[](const std::map<DWORD, ProcessInfo>::value_type &entry)
|
||||
{
|
||||
return entry.second;
|
||||
});
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
//
|
||||
// MakeHandle()
|
||||
//
|
||||
// For some reason a PID is of type HANDLE in the kernel.
|
||||
// Casting to HANDLE, which is a pointer type, requires some sourcery.
|
||||
//
|
||||
HANDLE MakeHandle(DWORD h)
|
||||
{
|
||||
return reinterpret_cast<HANDLE>(static_cast<size_t>(h));
|
||||
}
|
||||
|
||||
std::vector<uint8_t> PackageProcessInfo(const std::vector<ProcessInfo> &processes)
|
||||
{
|
||||
if (processes.empty())
|
||||
{
|
||||
THROW_ERROR("Invalid set of processes (empty set)");
|
||||
}
|
||||
|
||||
//
|
||||
// Determine required byte length for string buffer.
|
||||
//
|
||||
|
||||
size_t stringBufferLength = 0;
|
||||
|
||||
for (const auto &process : processes)
|
||||
{
|
||||
stringBufferLength += (process.DevicePath.size() * sizeof(wchar_t));
|
||||
}
|
||||
|
||||
size_t bufferLength = sizeof(ST_PROCESS_DISCOVERY_HEADER)
|
||||
+ (sizeof(ST_PROCESS_DISCOVERY_ENTRY) * processes.size())
|
||||
+ stringBufferLength;
|
||||
|
||||
std::vector<uint8_t> buffer(bufferLength);
|
||||
|
||||
//
|
||||
// Create pointers to various buffer areas.
|
||||
//
|
||||
|
||||
auto header = reinterpret_cast<ST_PROCESS_DISCOVERY_HEADER*>(&buffer[0]);
|
||||
auto entry = reinterpret_cast<ST_PROCESS_DISCOVERY_ENTRY*>(header + 1);
|
||||
auto stringBuffer = reinterpret_cast<uint8_t *>(entry + processes.size());
|
||||
|
||||
//
|
||||
// Serialize into buffer.
|
||||
//
|
||||
|
||||
SIZE_T stringOffset = 0;
|
||||
|
||||
for (const auto &process : processes)
|
||||
{
|
||||
entry->ProcessId = MakeHandle(process.ProcessId);
|
||||
entry->ParentProcessId = MakeHandle(process.ParentProcessId);
|
||||
|
||||
if (process.DevicePath.empty())
|
||||
{
|
||||
entry->ImageNameOffset = 0;
|
||||
entry->ImageNameLength = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
const auto imageNameLength = process.DevicePath.size() * sizeof(wchar_t);
|
||||
|
||||
entry->ImageNameOffset = stringOffset;
|
||||
entry->ImageNameLength = static_cast<USHORT>(imageNameLength);
|
||||
|
||||
RtlCopyMemory(stringBuffer + stringOffset, &process.DevicePath[0], imageNameLength);
|
||||
|
||||
stringOffset += imageNameLength;
|
||||
}
|
||||
|
||||
++entry;
|
||||
}
|
||||
|
||||
//
|
||||
// Finalize header.
|
||||
//
|
||||
|
||||
header->NumEntries = processes.size();
|
||||
header->TotalLength = bufferLength;
|
||||
|
||||
return buffer;
|
||||
}
|
||||
|
||||
std::vector<uint8_t> BuildRegisterProcessesPayload()
|
||||
{
|
||||
return PackageProcessInfo(CompileProcessInfo());
|
||||
}
|
||||
6
testing/proc.h
Normal file
6
testing/proc.h
Normal file
@@ -0,0 +1,6 @@
|
||||
#pragma once
|
||||
|
||||
#include <vector>
|
||||
#include <cstdint>
|
||||
|
||||
std::vector<uint8_t> BuildRegisterProcessesPayload();
|
||||
909
testing/stconsole.cpp
Normal file
909
testing/stconsole.cpp
Normal file
@@ -0,0 +1,909 @@
|
||||
#include <libcommon/network/adapters.h>
|
||||
#include <libcommon/string.h>
|
||||
#include <libcommon/error.h>
|
||||
|
||||
#include <iostream>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <windows.h>
|
||||
#include <conio.h>
|
||||
#include <ip2string.h>
|
||||
#include <winternl.h>
|
||||
#include <ws2ipdef.h>
|
||||
#include <process.h>
|
||||
#include "proc.h"
|
||||
|
||||
#include "../src/public.h"
|
||||
|
||||
#pragma comment(lib, "iphlpapi.lib")
|
||||
|
||||
|
||||
static const wchar_t DriverSymbolicName[] = L"\\\\.\\MULLVADSPLITTUNNEL";
|
||||
HANDLE g_DriverHandle = INVALID_HANDLE_VALUE;
|
||||
|
||||
std::vector<std::wstring> g_imagenames;
|
||||
|
||||
bool g_DisplayEvents = false;
|
||||
|
||||
bool SendIoControl(DWORD code, void *inBuffer, DWORD inBufferSize,
|
||||
void *outBuffer, DWORD outBufferSize, DWORD *bytesReturned)
|
||||
{
|
||||
OVERLAPPED o = { 0 };
|
||||
|
||||
//
|
||||
// Event should not be created on-the-fly.
|
||||
//
|
||||
// Create an event for each thread that needs to send a request
|
||||
// and keep the event around.
|
||||
//
|
||||
o.hEvent = CreateEventW(nullptr, TRUE, FALSE, nullptr);
|
||||
|
||||
auto status = DeviceIoControl(g_DriverHandle, code,
|
||||
inBuffer, inBufferSize, outBuffer, outBufferSize, bytesReturned, &o);
|
||||
|
||||
if (FALSE != status)
|
||||
{
|
||||
CloseHandle(o.hEvent);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
if (ERROR_IO_PENDING != GetLastError())
|
||||
{
|
||||
//THROW_ERROR("Could not post request to driver");
|
||||
|
||||
CloseHandle(o.hEvent);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
DWORD tempBytesReturned = 0;
|
||||
|
||||
status = GetOverlappedResult(g_DriverHandle, &o, &tempBytesReturned, TRUE);
|
||||
|
||||
CloseHandle(o.hEvent);
|
||||
|
||||
if (FALSE == status)
|
||||
{
|
||||
//THROW_ERROR("Failed to wait on driver to complete request");
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
*bytesReturned = tempBytesReturned;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
ST_DRIVER_STATE GetDriverState()
|
||||
{
|
||||
if (INVALID_HANDLE_VALUE == g_DriverHandle)
|
||||
{
|
||||
THROW_ERROR("Not connected to driver");
|
||||
}
|
||||
|
||||
DWORD bytesReturned;
|
||||
|
||||
SIZE_T buffer;
|
||||
|
||||
auto status = SendIoControl((DWORD)IOCTL_ST_GET_STATE, nullptr, 0, &buffer, sizeof(buffer), &bytesReturned);
|
||||
|
||||
if (!status)
|
||||
{
|
||||
THROW_ERROR("Failed to request state info from driver");
|
||||
}
|
||||
|
||||
return static_cast<ST_DRIVER_STATE>(buffer);
|
||||
}
|
||||
|
||||
std::wstring MapDriverState(ST_DRIVER_STATE state)
|
||||
{
|
||||
switch (state)
|
||||
{
|
||||
case ST_DRIVER_STATE_STARTED: return L"ST_DRIVER_STATE_STARTED";
|
||||
case ST_DRIVER_STATE_INITIALIZED: return L"ST_DRIVER_STATE_INITIALIZED";
|
||||
case ST_DRIVER_STATE_READY: return L"ST_DRIVER_STATE_READY";
|
||||
case ST_DRIVER_STATE_ENGAGED: return L"ST_DRIVER_STATE_ENGAGED";
|
||||
case ST_DRIVER_STATE_ZOMBIE: return L"ST_DRIVER_STATE_ZOMBIE";
|
||||
default:
|
||||
{
|
||||
THROW_ERROR("Unknown driver state");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ProcessConnect()
|
||||
{
|
||||
g_DriverHandle = CreateFileW(DriverSymbolicName, GENERIC_READ | GENERIC_WRITE,
|
||||
0, nullptr, OPEN_EXISTING, FILE_FLAG_OVERLAPPED, nullptr);
|
||||
|
||||
if (INVALID_HANDLE_VALUE == g_DriverHandle)
|
||||
{
|
||||
THROW_WINDOWS_ERROR(GetLastError(), "Connect to driver");
|
||||
}
|
||||
|
||||
std::wcout << L"Driver state: " << MapDriverState(GetDriverState()) << std::endl;
|
||||
|
||||
std::wcout << L"Successfully connected to driver" << std::endl;
|
||||
}
|
||||
|
||||
void ProcessInitialize()
|
||||
{
|
||||
DWORD bytesReturned;
|
||||
|
||||
auto status = SendIoControl((DWORD)IOCTL_ST_INITIALIZE, nullptr, 0, nullptr, 0, &bytesReturned);
|
||||
|
||||
if (!status)
|
||||
{
|
||||
THROW_ERROR("Initialization command failed");
|
||||
}
|
||||
|
||||
std::wcout << L"Driver state: " << MapDriverState(GetDriverState()) << std::endl;
|
||||
|
||||
std::wcout << L"Successfully initialized driver" << std::endl;
|
||||
}
|
||||
|
||||
std::vector<uint8_t> MakeConfiguration(const std::vector<std::wstring> &imageNames)
|
||||
{
|
||||
size_t totalStringLength = 0;
|
||||
|
||||
for (const auto &imageName : imageNames)
|
||||
{
|
||||
totalStringLength += imageName.size() * sizeof(wchar_t);
|
||||
}
|
||||
|
||||
size_t totalBufferSize = sizeof(ST_CONFIGURATION_HEADER)
|
||||
+ (sizeof(ST_CONFIGURATION_ENTRY) * imageNames.size())
|
||||
+ totalStringLength;
|
||||
|
||||
std::vector<uint8_t> buffer(totalBufferSize);
|
||||
|
||||
auto header = (ST_CONFIGURATION_HEADER*)&buffer[0];
|
||||
auto entry = (ST_CONFIGURATION_ENTRY*)(header + 1);
|
||||
|
||||
auto stringDest = &buffer[0] + sizeof(ST_CONFIGURATION_HEADER)
|
||||
+ (sizeof(ST_CONFIGURATION_ENTRY) * imageNames.size());
|
||||
|
||||
SIZE_T stringOffset = 0;
|
||||
|
||||
for (const auto &imageName : imageNames)
|
||||
{
|
||||
auto stringLength = imageName.size() * sizeof(wchar_t);
|
||||
|
||||
entry->ImageNameLength = (USHORT)stringLength;
|
||||
entry->ImageNameOffset = stringOffset;
|
||||
|
||||
memcpy(stringDest, imageName.c_str(), stringLength);
|
||||
|
||||
++entry;
|
||||
stringDest += stringLength;
|
||||
stringOffset += stringLength;
|
||||
}
|
||||
|
||||
header->NumEntries = imageNames.size();
|
||||
header->TotalLength = totalBufferSize;
|
||||
|
||||
return buffer;
|
||||
}
|
||||
|
||||
void ProcessSetConfig(const std::vector<std::wstring> &imageNames)
|
||||
{
|
||||
if (INVALID_HANDLE_VALUE == g_DriverHandle)
|
||||
{
|
||||
THROW_ERROR("Not connected to driver");
|
||||
}
|
||||
|
||||
std::wcout << L"Sending the following config to driver:" << std::endl;
|
||||
|
||||
for (const auto &imagename : imageNames)
|
||||
{
|
||||
std::wcout << L" " << imagename << std::endl;
|
||||
}
|
||||
|
||||
auto blob = MakeConfiguration(imageNames);
|
||||
|
||||
DWORD bytesReturned;
|
||||
|
||||
auto status = SendIoControl((DWORD)IOCTL_ST_SET_CONFIGURATION,
|
||||
&blob[0], (DWORD)blob.size(), nullptr, 0, &bytesReturned);
|
||||
|
||||
if (!status)
|
||||
{
|
||||
THROW_ERROR("Set configuration");
|
||||
}
|
||||
|
||||
std::wcout << L"Driver state: " << MapDriverState(GetDriverState()) << std::endl;
|
||||
|
||||
std::wcout << L"Successfully set configuration" << std::endl;
|
||||
}
|
||||
|
||||
void ProcessAddConfig(const std::wstring &imageName)
|
||||
{
|
||||
auto tempNames = g_imagenames;
|
||||
|
||||
tempNames.push_back(imageName);
|
||||
|
||||
ProcessSetConfig(tempNames);
|
||||
|
||||
// Persist data now that the above call did not throw.
|
||||
g_imagenames = tempNames;
|
||||
}
|
||||
|
||||
void ProcessClearConfig();
|
||||
|
||||
void ProcessRemoveConfig(const std::wstring &imageName)
|
||||
{
|
||||
auto iterMatch = std::find_if(g_imagenames.begin(), g_imagenames.end(), [&imageName](const std::wstring &candidate)
|
||||
{
|
||||
return 0 == _wcsicmp(candidate.c_str(), imageName.c_str());
|
||||
});
|
||||
|
||||
if (iterMatch == g_imagenames.end())
|
||||
{
|
||||
THROW_ERROR("Specified imagename was not previously registered");
|
||||
}
|
||||
|
||||
auto indexMatch = std::distance(g_imagenames.begin(), iterMatch);
|
||||
|
||||
auto tempNames = g_imagenames;
|
||||
|
||||
tempNames.erase(tempNames.begin() + indexMatch);
|
||||
|
||||
if (tempNames.empty())
|
||||
{
|
||||
ProcessClearConfig();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
ProcessSetConfig(tempNames);
|
||||
|
||||
// Persist data now that the above call did not throw.
|
||||
g_imagenames = tempNames;
|
||||
}
|
||||
|
||||
void ProcessGetConfig()
|
||||
{
|
||||
if (INVALID_HANDLE_VALUE == g_DriverHandle)
|
||||
{
|
||||
THROW_ERROR("Not connected to driver");
|
||||
}
|
||||
|
||||
DWORD bytesReturned;
|
||||
|
||||
SIZE_T requiredBufferSize;
|
||||
|
||||
auto status = SendIoControl((DWORD)IOCTL_ST_GET_CONFIGURATION,
|
||||
nullptr, 0, &requiredBufferSize, sizeof(requiredBufferSize), &bytesReturned);
|
||||
|
||||
if (!status || 0 == bytesReturned)
|
||||
{
|
||||
THROW_ERROR("Get configuration");
|
||||
}
|
||||
|
||||
std::vector<uint8_t> buffer(requiredBufferSize, 0);
|
||||
|
||||
status = SendIoControl((DWORD)IOCTL_ST_GET_CONFIGURATION,
|
||||
nullptr, 0, &buffer[0], (DWORD)buffer.size(), &bytesReturned);
|
||||
|
||||
if (!status || bytesReturned != buffer.size())
|
||||
{
|
||||
THROW_ERROR("Get configuration");
|
||||
}
|
||||
|
||||
auto header = (ST_CONFIGURATION_HEADER*)&buffer[0];
|
||||
auto entry = (ST_CONFIGURATION_ENTRY*)(header + 1);
|
||||
|
||||
auto stringBuffer = (uint8_t *)(entry + header->NumEntries);
|
||||
|
||||
std::vector<std::wstring> imageNames;
|
||||
|
||||
for (auto i = 0; i < header->NumEntries; ++i, ++entry)
|
||||
{
|
||||
imageNames.emplace_back
|
||||
(
|
||||
(wchar_t*)(stringBuffer + entry->ImageNameOffset),
|
||||
(wchar_t*)(stringBuffer + entry->ImageNameOffset + entry->ImageNameLength)
|
||||
);
|
||||
}
|
||||
|
||||
std::wcout << L"Successfully got configuration" << std::endl;
|
||||
|
||||
std::wcout << L"Image names in config:" << std::endl;
|
||||
|
||||
for (const auto &imageName : imageNames)
|
||||
{
|
||||
std::wcout << L" " << imageName << std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
void ProcessClearConfig()
|
||||
{
|
||||
if (INVALID_HANDLE_VALUE == g_DriverHandle)
|
||||
{
|
||||
THROW_ERROR("Not connected to driver");
|
||||
}
|
||||
|
||||
DWORD bytesReturned;
|
||||
|
||||
auto status = SendIoControl((DWORD)IOCTL_ST_CLEAR_CONFIGURATION,
|
||||
nullptr, 0, nullptr, 0, &bytesReturned);
|
||||
|
||||
if (!status)
|
||||
{
|
||||
THROW_ERROR("Clear configuration");
|
||||
}
|
||||
|
||||
g_imagenames.clear();
|
||||
|
||||
std::wcout << L"Driver state: " << MapDriverState(GetDriverState()) << std::endl;
|
||||
|
||||
std::wcout << L"Successfully cleared configuration" << std::endl;
|
||||
}
|
||||
|
||||
void ProcessRegisterProcesses()
|
||||
{
|
||||
if (INVALID_HANDLE_VALUE == g_DriverHandle)
|
||||
{
|
||||
THROW_ERROR("Not connected to driver");
|
||||
}
|
||||
|
||||
auto blob = BuildRegisterProcessesPayload();
|
||||
|
||||
DWORD bytesReturned;
|
||||
|
||||
auto status = SendIoControl((DWORD)IOCTL_ST_REGISTER_PROCESSES,
|
||||
&blob[0], (DWORD)blob.size(), nullptr, 0, &bytesReturned);
|
||||
|
||||
if (!status)
|
||||
{
|
||||
THROW_ERROR("Register processes");
|
||||
}
|
||||
|
||||
std::wcout << L"Driver state: " << MapDriverState(GetDriverState()) << std::endl;
|
||||
|
||||
std::wcout << L"Successfully registered processes" << std::endl;
|
||||
}
|
||||
|
||||
void GetAdapterAddresses(const std::wstring &adapterName, IN_ADDR &ipv4, IN6_ADDR &ipv6)
|
||||
{
|
||||
const DWORD flags = GAA_FLAG_SKIP_ANYCAST | GAA_FLAG_SKIP_MULTICAST | GAA_FLAG_SKIP_DNS_SERVER;
|
||||
|
||||
common::network::Adapters adapters(AF_INET, flags);
|
||||
|
||||
bool ipv4Done = false;
|
||||
|
||||
for (auto adapter = adapters.next(); adapter != NULL; adapter = adapters.next())
|
||||
{
|
||||
if (0 != _wcsicmp(adapter->FriendlyName, adapterName.c_str()))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (adapter->Ipv4Enabled == 0
|
||||
|| adapter->FirstUnicastAddress == nullptr)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
auto sa = (SOCKADDR_IN*)adapter->FirstUnicastAddress->Address.lpSockaddr;
|
||||
ipv4 = sa->sin_addr;
|
||||
|
||||
ipv4Done = true;
|
||||
break;
|
||||
}
|
||||
|
||||
if (!ipv4Done)
|
||||
{
|
||||
throw std::runtime_error("Could not determine adapter IPv4 address");
|
||||
}
|
||||
|
||||
common::network::Adapters adapters6(AF_INET6, flags);
|
||||
|
||||
bool ipv6Done = false;
|
||||
|
||||
for (auto adapter = adapters6.next(); adapter != NULL; adapter = adapters6.next())
|
||||
{
|
||||
if (0 != _wcsicmp(adapter->FriendlyName, adapterName.c_str()))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (adapter->Ipv6Enabled == 0
|
||||
|| adapter->FirstUnicastAddress == nullptr)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
auto sa = (SOCKADDR_IN6*)adapter->FirstUnicastAddress->Address.lpSockaddr;
|
||||
ipv6 = sa->sin6_addr;
|
||||
|
||||
ipv6Done = true;
|
||||
break;
|
||||
}
|
||||
|
||||
if (!ipv6Done)
|
||||
{
|
||||
throw std::runtime_error("Could not determine adapter IPv6 address");
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<uint8_t> BuildRegisterIpsPayload()
|
||||
{
|
||||
std::vector<uint8_t> payload(sizeof(ST_IP_ADDRESSES));
|
||||
|
||||
auto ip = reinterpret_cast<ST_IP_ADDRESSES*>(&payload[0]);
|
||||
|
||||
GetAdapterAddresses(L"Ethernet", ip->InternetIpv4, ip->InternetIpv6);
|
||||
GetAdapterAddresses(L"Mullvad", ip->TunnelIpv4, ip->TunnelIpv6);
|
||||
|
||||
wchar_t stringBuffer[100];
|
||||
|
||||
std::wcout << L"Internet addresses" << std::endl;
|
||||
|
||||
RtlIpv4AddressToStringW(&(ip->InternetIpv4), stringBuffer);
|
||||
std::wcout << L" Ipv4: " << stringBuffer << std::endl;
|
||||
|
||||
RtlIpv6AddressToStringW(&(ip->InternetIpv6), stringBuffer);
|
||||
std::wcout << L" Ipv6: " << stringBuffer << std::endl;
|
||||
|
||||
std::wcout << L"Tunnel addresses" << std::endl;
|
||||
|
||||
RtlIpv4AddressToStringW(&(ip->TunnelIpv4), stringBuffer);
|
||||
std::wcout << L" Ipv4: " << stringBuffer << std::endl;
|
||||
|
||||
RtlIpv6AddressToStringW(&(ip->TunnelIpv6), stringBuffer);
|
||||
std::wcout << L" Ipv6: " << stringBuffer << std::endl;
|
||||
|
||||
//ip->InternetIpv4.S_un.S_addr = 0x0f02000a;
|
||||
//ip->InternetIpv6.u.Byte[0] = 0;
|
||||
//ip->TunnelIpv4.S_un.S_addr = 0x0c00080a;
|
||||
//ip->TunnelIpv6.u.Byte[0] = 0;
|
||||
|
||||
return payload;
|
||||
}
|
||||
|
||||
void ProcessRegisterIps()
|
||||
{
|
||||
if (INVALID_HANDLE_VALUE == g_DriverHandle)
|
||||
{
|
||||
THROW_ERROR("Not connected to driver");
|
||||
}
|
||||
|
||||
auto blob = BuildRegisterIpsPayload();
|
||||
|
||||
DWORD bytesReturned;
|
||||
|
||||
auto status = SendIoControl((DWORD)IOCTL_ST_REGISTER_IP_ADDRESSES,
|
||||
&blob[0], (DWORD)blob.size(), nullptr, 0, &bytesReturned);
|
||||
|
||||
if (!status)
|
||||
{
|
||||
THROW_ERROR("Register IP addresses");
|
||||
}
|
||||
|
||||
std::wcout << L"Driver state: " << MapDriverState(GetDriverState()) << std::endl;
|
||||
|
||||
std::wcout << L"Successfully registered IP addresses" << std::endl;
|
||||
}
|
||||
|
||||
void ProcessGetIps()
|
||||
{
|
||||
ST_IP_ADDRESSES ips = { 0 };
|
||||
|
||||
DWORD bytesReturned;
|
||||
|
||||
auto status = SendIoControl((DWORD)IOCTL_ST_GET_IP_ADDRESSES,
|
||||
nullptr, 0, &ips, (DWORD)sizeof(ips), &bytesReturned);
|
||||
|
||||
if (!status || bytesReturned != sizeof(ips))
|
||||
{
|
||||
THROW_ERROR("Register IP addresses");
|
||||
}
|
||||
|
||||
std::wcout << L"Internet IPv4: " << common::string::FormatIpv4(ips.InternetIpv4.S_un.S_addr) << std::endl;
|
||||
std::wcout << L"Internet IPv6: " << common::string::FormatIpv6(ips.InternetIpv6.u.Byte) << std::endl;
|
||||
std::wcout << L"Tunnel IPv4: " << common::string::FormatIpv4(ips.TunnelIpv4.S_un.S_addr) << std::endl;
|
||||
std::wcout << L"Tunnel IPv6: " << common::string::FormatIpv6(ips.TunnelIpv6.u.Byte) << std::endl;
|
||||
}
|
||||
|
||||
//
|
||||
// This is duplicated from proc.cpp
|
||||
//
|
||||
HANDLE XxxMakeHandle(DWORD h)
|
||||
{
|
||||
return reinterpret_cast<HANDLE>(static_cast<size_t>(h));
|
||||
}
|
||||
|
||||
DWORD MakeDword(HANDLE h)
|
||||
{
|
||||
return static_cast<DWORD>(reinterpret_cast<size_t>(h));
|
||||
}
|
||||
|
||||
void ProcessQueryProcess(const std::wstring &processId)
|
||||
{
|
||||
ST_QUERY_PROCESS q = { 0 };
|
||||
|
||||
q.ProcessId = XxxMakeHandle(_wtoi(processId.c_str()));
|
||||
|
||||
std::vector<uint8_t> buffer(1024);
|
||||
|
||||
DWORD bytesReturned;
|
||||
|
||||
auto status = SendIoControl((DWORD)IOCTL_ST_QUERY_PROCESS,
|
||||
&q, sizeof(q), &buffer[0], (DWORD)buffer.size(), &bytesReturned);
|
||||
|
||||
if (!status)
|
||||
{
|
||||
THROW_ERROR("Query process");
|
||||
}
|
||||
|
||||
//
|
||||
// Dump retrieved information.
|
||||
//
|
||||
|
||||
buffer.push_back(0);
|
||||
buffer.push_back(0);
|
||||
|
||||
auto r = (ST_QUERY_PROCESS_RESPONSE *)&buffer[0];
|
||||
|
||||
std::wcout << L"Process id: " << MakeDword(r->ProcessId) << std::endl;
|
||||
std::wcout << L"Parent process id: " << MakeDword(r->ParentProcessId) << std::endl;
|
||||
std::wcout << L"Split: " << r->Split << std::endl;
|
||||
std::wcout << L"Imagename: " << r->ImageName << std::endl;
|
||||
}
|
||||
|
||||
void ProcessDisplayEvents()
|
||||
{
|
||||
g_DisplayEvents = !g_DisplayEvents;
|
||||
|
||||
std::wcout << L"Displaying events: " << std::boolalpha << g_DisplayEvents << std::endl;
|
||||
}
|
||||
|
||||
void DisplaySplittingEvent(const ST_SPLITTING_EVENT *evt, size_t /*eventSize*/)
|
||||
{
|
||||
std::wcout << L"Process id: " << MakeDword(evt->ProcessId) << std::endl;
|
||||
|
||||
std::wcout << L"Flags:" << std::endl;
|
||||
|
||||
if ((evt->Reason & ST_SPLITTING_REASON_BY_INHERITANCE) != 0)
|
||||
{
|
||||
std::wcout << L" ST_SPLITTING_REASON_BY_INHERITANCE" << std::endl;
|
||||
}
|
||||
|
||||
if ((evt->Reason & ST_SPLITTING_REASON_BY_CONFIG) != 0)
|
||||
{
|
||||
std::wcout << L" ST_SPLITTING_REASON_BY_CONFIG" << std::endl;
|
||||
}
|
||||
|
||||
if ((evt->Reason & ST_SPLITTING_REASON_PROCESS_ARRIVING) != 0)
|
||||
{
|
||||
std::wcout << L" ST_SPLITTING_REASON_PROCESS_ARRIVING" << std::endl;
|
||||
}
|
||||
|
||||
if ((evt->Reason & ST_SPLITTING_REASON_PROCESS_DEPARTING) != 0)
|
||||
{
|
||||
std::wcout << L" ST_SPLITTING_REASON_PROCESS_DEPARTING" << std::endl;
|
||||
}
|
||||
|
||||
std::wstring imageName(&evt->ImageName[0], &evt->ImageName[0] + (evt->ImageNameLength / sizeof(wchar_t)));
|
||||
|
||||
std::wcout << L"Imagename: " << imageName << std::endl;
|
||||
}
|
||||
|
||||
void DisplaySplittingErrorEvent(const ST_SPLITTING_ERROR_EVENT *evt, size_t /*eventSize*/)
|
||||
{
|
||||
std::wcout << L"Process id: " << MakeDword(evt->ProcessId) << std::endl;
|
||||
|
||||
std::wstring imageName(&evt->ImageName[0], &evt->ImageName[0] + (evt->ImageNameLength / sizeof(wchar_t)));
|
||||
|
||||
std::wcout << L"Imagename: " << imageName << std::endl;
|
||||
}
|
||||
|
||||
void ParseDisplayEvent(const uint8_t *evt, size_t eventSize)
|
||||
{
|
||||
if (!g_DisplayEvents)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
std::wcout << L"Event received, " << eventSize << " bytes" << std::endl;
|
||||
|
||||
auto header = (ST_EVENT_HEADER *)evt;
|
||||
|
||||
std::wcout << L"Payload size, " << header->EventSize << " bytes" << std::endl;
|
||||
|
||||
switch (header->EventId)
|
||||
{
|
||||
case ST_EVENT_START_SPLITTING_PROCESS:
|
||||
{
|
||||
std::wcout << L"Type: ST_EVENT_START_SPLITTING_PROCESS" << std::endl;
|
||||
|
||||
DisplaySplittingEvent((ST_SPLITTING_EVENT*)&header->EventData[0], header->EventSize);
|
||||
|
||||
break;
|
||||
}
|
||||
case ST_EVENT_STOP_SPLITTING_PROCESS:
|
||||
{
|
||||
std::wcout << L"Type: ST_EVENT_STOP_SPLITTING_PROCESS" << std::endl;
|
||||
|
||||
DisplaySplittingEvent((ST_SPLITTING_EVENT*)&header->EventData[0], header->EventSize);
|
||||
|
||||
break;
|
||||
}
|
||||
case ST_EVENT_ERROR_START_SPLITTING_PROCESS:
|
||||
{
|
||||
std::wcout << L"Type: ST_EVENT_ERROR_START_SPLITTING_PROCESS" << std::endl;
|
||||
|
||||
DisplaySplittingErrorEvent((ST_SPLITTING_ERROR_EVENT*)&header->EventData[0], header->EventSize);
|
||||
|
||||
break;
|
||||
}
|
||||
case ST_EVENT_ERROR_STOP_SPLITTING_PROCESS:
|
||||
{
|
||||
std::wcout << L"Type: ST_EVENT_ERROR_STOP_SPLITTING_PROCESS" << std::endl;
|
||||
|
||||
DisplaySplittingErrorEvent((ST_SPLITTING_ERROR_EVENT*)&header->EventData[0], header->EventSize);
|
||||
|
||||
break;
|
||||
}
|
||||
default:
|
||||
{
|
||||
std::wcout << L"Unsupported event" << std::endl;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
unsigned __stdcall EventThread(void * /*rawContext*/)
|
||||
{
|
||||
//
|
||||
// Wait for connect
|
||||
//
|
||||
|
||||
for (;;)
|
||||
{
|
||||
if (g_DriverHandle == INVALID_HANDLE_VALUE)
|
||||
{
|
||||
Sleep(1000);
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
//
|
||||
// Continously issue event requests
|
||||
//
|
||||
|
||||
std::vector<uint8_t> buffer(2048);
|
||||
|
||||
for (;;)
|
||||
{
|
||||
DWORD bytesReturned;
|
||||
|
||||
auto status = SendIoControl((DWORD)IOCTL_ST_DEQUEUE_EVENT, nullptr, 0,
|
||||
&buffer[0], static_cast<DWORD>(buffer.size()), &bytesReturned);
|
||||
|
||||
if (!status)
|
||||
{
|
||||
//std::wcout << L"Failed to dequeue event from driver" << std::endl;
|
||||
|
||||
Sleep(1000);
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
ParseDisplayEvent(&buffer[0], bytesReturned);
|
||||
}
|
||||
}
|
||||
|
||||
void ResetDriver()
|
||||
{
|
||||
DWORD bytesReturned;
|
||||
|
||||
auto status = SendIoControl((DWORD)IOCTL_ST_RESET,
|
||||
nullptr, 0, nullptr, 0, &bytesReturned);
|
||||
|
||||
if (!status)
|
||||
{
|
||||
THROW_ERROR("Request to reset driver has failed");
|
||||
}
|
||||
|
||||
std::wcout << L"Driver state: " << MapDriverState(GetDriverState()) << std::endl;
|
||||
}
|
||||
|
||||
bool CreateEventThread()
|
||||
{
|
||||
auto t = _beginthreadex(nullptr, 0, EventThread, nullptr, 0, nullptr);
|
||||
|
||||
auto threadHandle = reinterpret_cast<HANDLE>(t);
|
||||
|
||||
if (0 == threadHandle)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
CloseHandle(threadHandle);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
int main()
|
||||
{
|
||||
std::wcout << L"Testing console for split tunnel driver" << std::endl;
|
||||
|
||||
if (!CreateEventThread())
|
||||
{
|
||||
std::wcout << L"Failed to create event thread" << std::endl;
|
||||
return 1;
|
||||
}
|
||||
|
||||
for (;;)
|
||||
{
|
||||
std::wcout << L"cmd> ";
|
||||
|
||||
std::wstring request;
|
||||
std::getline(std::wcin, request);
|
||||
|
||||
auto tokens = common::string::Tokenize(request, L" ");
|
||||
|
||||
if (tokens.empty())
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (0 == _wcsicmp(tokens[0].c_str(), L"quit"))
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
if (0 == _wcsicmp(tokens[0].c_str(), L"connect"))
|
||||
{
|
||||
ProcessConnect();
|
||||
continue;
|
||||
}
|
||||
|
||||
if (0 == _wcsicmp(tokens[0].c_str(), L"initialize"))
|
||||
{
|
||||
ProcessInitialize();
|
||||
continue;
|
||||
}
|
||||
|
||||
if (0 == _wcsicmp(tokens[0].c_str(), L"get-state"))
|
||||
{
|
||||
std::wcout << L"Driver state: " << MapDriverState(GetDriverState()) << std::endl;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (0 == _wcsicmp(tokens[0].c_str(), L"add-config"))
|
||||
{
|
||||
//
|
||||
// tokens[1] will be a partial path if the path contains spaces.
|
||||
// reuse the source for "tokens" instead.
|
||||
//
|
||||
|
||||
ProcessAddConfig(request.substr(sizeof("add-config")));
|
||||
continue;
|
||||
}
|
||||
|
||||
if (0 == _wcsicmp(tokens[0].c_str(), L"remove-config"))
|
||||
{
|
||||
ProcessRemoveConfig(request.substr(sizeof("remove-config")));
|
||||
continue;
|
||||
}
|
||||
|
||||
if (0 == _wcsicmp(tokens[0].c_str(), L"get-config"))
|
||||
{
|
||||
ProcessGetConfig();
|
||||
continue;
|
||||
}
|
||||
|
||||
if (0 == _wcsicmp(tokens[0].c_str(), L"clear-config"))
|
||||
{
|
||||
ProcessClearConfig();
|
||||
continue;
|
||||
}
|
||||
|
||||
if (0 == _wcsicmp(tokens[0].c_str(), L"register-processes"))
|
||||
{
|
||||
ProcessRegisterProcesses();
|
||||
continue;
|
||||
}
|
||||
|
||||
if (0 == _wcsicmp(tokens[0].c_str(), L"register-ips"))
|
||||
{
|
||||
ProcessRegisterIps();
|
||||
continue;
|
||||
}
|
||||
|
||||
if (0 == _wcsicmp(tokens[0].c_str(), L"get-ips"))
|
||||
{
|
||||
ProcessGetIps();
|
||||
continue;
|
||||
}
|
||||
|
||||
if (0 == _wcsicmp(tokens[0].c_str(), L"dry-run-ips"))
|
||||
{
|
||||
BuildRegisterIpsPayload();
|
||||
continue;
|
||||
}
|
||||
|
||||
if (0 == _wcsicmp(tokens[0].c_str(), L"query-process"))
|
||||
{
|
||||
ProcessQueryProcess(tokens[1]);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (0 == _wcsicmp(tokens[0].c_str(), L"quick"))
|
||||
{
|
||||
if (g_DriverHandle != INVALID_HANDLE_VALUE)
|
||||
{
|
||||
std::wcout << L"Already initialized" << std::endl;
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
ProcessConnect();
|
||||
ProcessInitialize();
|
||||
ProcessRegisterProcesses();
|
||||
ProcessRegisterIps();
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if (0 == _wcsicmp(tokens[0].c_str(), L"display-events"))
|
||||
{
|
||||
ProcessDisplayEvents();
|
||||
continue;
|
||||
}
|
||||
|
||||
if (0 == _wcsicmp(tokens[0].c_str(), L"split-firefox"))
|
||||
{
|
||||
static bool split = false;
|
||||
|
||||
const auto targetSplit = !split;
|
||||
|
||||
std::wcout << L"Splitting firefox: " << std::boolalpha << targetSplit << std::endl;
|
||||
|
||||
const std::wstring path = L"\\Device\\HarddiskVolume2\\Program Files (x86)\\Mozilla Firefox\\firefox.exe";
|
||||
|
||||
if (targetSplit)
|
||||
{
|
||||
ProcessAddConfig(path);
|
||||
}
|
||||
else
|
||||
{
|
||||
ProcessRemoveConfig(path);
|
||||
}
|
||||
|
||||
// Safe to update now since above calls did not throw.
|
||||
split = targetSplit;
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if (0 == _wcsicmp(tokens[0].c_str(), L"reset"))
|
||||
{
|
||||
ResetDriver();
|
||||
continue;
|
||||
}
|
||||
}
|
||||
catch (const std::exception &ex)
|
||||
{
|
||||
std::cout << "Error: " << ex.what() << std::endl;
|
||||
continue;
|
||||
}
|
||||
|
||||
std::wcout << L"invalid command" << std::endl;
|
||||
}
|
||||
|
||||
if (g_DriverHandle != INVALID_HANDLE_VALUE)
|
||||
{
|
||||
CloseHandle(g_DriverHandle);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
31
testing/stconsole.sln
Normal file
31
testing/stconsole.sln
Normal file
@@ -0,0 +1,31 @@
|
||||
|
||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
# Visual Studio Version 16
|
||||
VisualStudioVersion = 16.0.29609.76
|
||||
MinimumVisualStudioVersion = 10.0.40219.1
|
||||
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "stconsole", "stconsole.vcxproj", "{8BFBC284-F915-43CF-9315-E99FAE9BA511}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|x64 = Debug|x64
|
||||
Debug|x86 = Debug|x86
|
||||
Release|x64 = Release|x64
|
||||
Release|x86 = Release|x86
|
||||
EndGlobalSection
|
||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||
{8BFBC284-F915-43CF-9315-E99FAE9BA511}.Debug|x64.ActiveCfg = Debug|x64
|
||||
{8BFBC284-F915-43CF-9315-E99FAE9BA511}.Debug|x64.Build.0 = Debug|x64
|
||||
{8BFBC284-F915-43CF-9315-E99FAE9BA511}.Debug|x86.ActiveCfg = Debug|Win32
|
||||
{8BFBC284-F915-43CF-9315-E99FAE9BA511}.Debug|x86.Build.0 = Debug|Win32
|
||||
{8BFBC284-F915-43CF-9315-E99FAE9BA511}.Release|x64.ActiveCfg = Release|x64
|
||||
{8BFBC284-F915-43CF-9315-E99FAE9BA511}.Release|x64.Build.0 = Release|x64
|
||||
{8BFBC284-F915-43CF-9315-E99FAE9BA511}.Release|x86.ActiveCfg = Release|Win32
|
||||
{8BFBC284-F915-43CF-9315-E99FAE9BA511}.Release|x86.Build.0 = Release|Win32
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
EndGlobalSection
|
||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||
SolutionGuid = {161EC718-4B2C-4D05-8155-C4CEAF549C49}
|
||||
EndGlobalSection
|
||||
EndGlobal
|
||||
191
testing/stconsole.vcxproj
Normal file
191
testing/stconsole.vcxproj
Normal file
@@ -0,0 +1,191 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<ItemGroup Label="ProjectConfigurations">
|
||||
<ProjectConfiguration Include="Debug|Win32">
|
||||
<Configuration>Debug</Configuration>
|
||||
<Platform>Win32</Platform>
|
||||
</ProjectConfiguration>
|
||||
<ProjectConfiguration Include="Release|Win32">
|
||||
<Configuration>Release</Configuration>
|
||||
<Platform>Win32</Platform>
|
||||
</ProjectConfiguration>
|
||||
<ProjectConfiguration Include="Debug|x64">
|
||||
<Configuration>Debug</Configuration>
|
||||
<Platform>x64</Platform>
|
||||
</ProjectConfiguration>
|
||||
<ProjectConfiguration Include="Release|x64">
|
||||
<Configuration>Release</Configuration>
|
||||
<Platform>x64</Platform>
|
||||
</ProjectConfiguration>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClCompile Include="stconsole.cpp" />
|
||||
<ClCompile Include="proc.cpp" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClInclude Include="proc.h" />
|
||||
</ItemGroup>
|
||||
<PropertyGroup Label="Globals">
|
||||
<VCProjectVersion>16.0</VCProjectVersion>
|
||||
<ProjectGuid>{8BFBC284-F915-43CF-9315-E99FAE9BA511}</ProjectGuid>
|
||||
<Keyword>Win32Proj</Keyword>
|
||||
<RootNamespace>stconsole</RootNamespace>
|
||||
<WindowsTargetPlatformVersion>10.0</WindowsTargetPlatformVersion>
|
||||
</PropertyGroup>
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration">
|
||||
<ConfigurationType>Application</ConfigurationType>
|
||||
<UseDebugLibraries>true</UseDebugLibraries>
|
||||
<PlatformToolset>v142</PlatformToolset>
|
||||
<CharacterSet>Unicode</CharacterSet>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="Configuration">
|
||||
<ConfigurationType>Application</ConfigurationType>
|
||||
<UseDebugLibraries>false</UseDebugLibraries>
|
||||
<PlatformToolset>v142</PlatformToolset>
|
||||
<WholeProgramOptimization>true</WholeProgramOptimization>
|
||||
<CharacterSet>Unicode</CharacterSet>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="Configuration">
|
||||
<ConfigurationType>Application</ConfigurationType>
|
||||
<UseDebugLibraries>true</UseDebugLibraries>
|
||||
<PlatformToolset>v142</PlatformToolset>
|
||||
<CharacterSet>Unicode</CharacterSet>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="Configuration">
|
||||
<ConfigurationType>Application</ConfigurationType>
|
||||
<UseDebugLibraries>false</UseDebugLibraries>
|
||||
<PlatformToolset>v142</PlatformToolset>
|
||||
<WholeProgramOptimization>true</WholeProgramOptimization>
|
||||
<CharacterSet>Unicode</CharacterSet>
|
||||
</PropertyGroup>
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
|
||||
<ImportGroup Label="ExtensionSettings">
|
||||
</ImportGroup>
|
||||
<ImportGroup Label="Shared">
|
||||
</ImportGroup>
|
||||
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
|
||||
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
|
||||
</ImportGroup>
|
||||
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
|
||||
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
|
||||
</ImportGroup>
|
||||
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
|
||||
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
|
||||
</ImportGroup>
|
||||
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
|
||||
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
|
||||
</ImportGroup>
|
||||
<PropertyGroup Label="UserMacros" />
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
|
||||
<LinkIncremental>true</LinkIncremental>
|
||||
<IncludePath>$(VC_IncludePath);$(WindowsSDK_IncludePath);C:\Program Files (x86)\Windows Kits\10\Include\wdf\kmdf\1.11;C:\Program Files (x86)\Windows Kits\10\Include\10.0.18362.0\km;C:\Program Files (x86)\Windows Kits\10\Include\10.0.18362.0\shared</IncludePath>
|
||||
<OutDir>$(SolutionDir)..\bin\$(Platform)-$(Configuration)\</OutDir>
|
||||
<IntDir>$(SolutionDir)..\bin\temp\$(Platform)-$(Configuration)\$(ProjectName)\</IntDir>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
|
||||
<LinkIncremental>true</LinkIncremental>
|
||||
<IncludePath>$(VC_IncludePath);$(WindowsSDK_IncludePath);C:\Program Files (x86)\Windows Kits\10\Include\wdf\kmdf\1.11;C:\Program Files (x86)\Windows Kits\10\Include\10.0.18362.0\km;C:\Program Files (x86)\Windows Kits\10\Include\10.0.18362.0\shared</IncludePath>
|
||||
<OutDir>$(SolutionDir)..\bin\$(Platform)-$(Configuration)\</OutDir>
|
||||
<IntDir>$(SolutionDir)..\bin\temp\$(Platform)-$(Configuration)\$(ProjectName)\</IntDir>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
|
||||
<LinkIncremental>false</LinkIncremental>
|
||||
<IncludePath>$(VC_IncludePath);$(WindowsSDK_IncludePath);C:\Program Files (x86)\Windows Kits\10\Include\wdf\kmdf\1.11;C:\Program Files (x86)\Windows Kits\10\Include\10.0.18362.0\km;C:\Program Files (x86)\Windows Kits\10\Include\10.0.18362.0\shared</IncludePath>
|
||||
<OutDir>$(SolutionDir)..\bin\$(Platform)-$(Configuration)\</OutDir>
|
||||
<IntDir>$(SolutionDir)..\bin\temp\$(Platform)-$(Configuration)\$(ProjectName)\</IntDir>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
|
||||
<LinkIncremental>false</LinkIncremental>
|
||||
<IncludePath>$(VC_IncludePath);$(WindowsSDK_IncludePath);C:\Program Files (x86)\Windows Kits\10\Include\wdf\kmdf\1.11;C:\Program Files (x86)\Windows Kits\10\Include\10.0.18362.0\km;C:\Program Files (x86)\Windows Kits\10\Include\10.0.18362.0\shared</IncludePath>
|
||||
<OutDir>$(SolutionDir)..\bin\$(Platform)-$(Configuration)\</OutDir>
|
||||
<IntDir>$(SolutionDir)..\bin\temp\$(Platform)-$(Configuration)\$(ProjectName)\</IntDir>
|
||||
</PropertyGroup>
|
||||
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
|
||||
<ClCompile>
|
||||
<PrecompiledHeader>
|
||||
</PrecompiledHeader>
|
||||
<WarningLevel>Level4</WarningLevel>
|
||||
<SDLCheck>true</SDLCheck>
|
||||
<PreprocessorDefinitions>_DEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<ConformanceMode>true</ConformanceMode>
|
||||
<AdditionalIncludeDirectories>$(ProjectDir)..\..\mullvadvpn-app\windows\windows-libraries\src\</AdditionalIncludeDirectories>
|
||||
<LanguageStandard>stdcpplatest</LanguageStandard>
|
||||
<RuntimeLibrary>MultiThreadedDebug</RuntimeLibrary>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<SubSystem>Console</SubSystem>
|
||||
<GenerateDebugInformation>true</GenerateDebugInformation>
|
||||
<AdditionalDependencies>libcommon.lib;kernel32.lib;user32.lib;gdi32.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies)</AdditionalDependencies>
|
||||
<AdditionalLibraryDirectories>$(ProjectDir)..\..\mullvadvpn-app\windows\windows-libraries\bin\$(Platform)-$(Configuration)\</AdditionalLibraryDirectories>
|
||||
</Link>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
|
||||
<ClCompile>
|
||||
<PrecompiledHeader>
|
||||
</PrecompiledHeader>
|
||||
<WarningLevel>Level4</WarningLevel>
|
||||
<SDLCheck>true</SDLCheck>
|
||||
<PreprocessorDefinitions>WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<ConformanceMode>true</ConformanceMode>
|
||||
<AdditionalIncludeDirectories>$(ProjectDir)..\..\mullvadvpn-app\windows\windows-libraries\src\</AdditionalIncludeDirectories>
|
||||
<LanguageStandard>stdcpplatest</LanguageStandard>
|
||||
<RuntimeLibrary>MultiThreadedDebug</RuntimeLibrary>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<SubSystem>Console</SubSystem>
|
||||
<GenerateDebugInformation>true</GenerateDebugInformation>
|
||||
<AdditionalDependencies>libcommon.lib;kernel32.lib;user32.lib;gdi32.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies)</AdditionalDependencies>
|
||||
<AdditionalLibraryDirectories>$(ProjectDir)..\..\mullvadvpn-app\windows\windows-libraries\bin\$(Platform)-$(Configuration)\</AdditionalLibraryDirectories>
|
||||
</Link>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
|
||||
<ClCompile>
|
||||
<PrecompiledHeader>
|
||||
</PrecompiledHeader>
|
||||
<WarningLevel>Level4</WarningLevel>
|
||||
<FunctionLevelLinking>true</FunctionLevelLinking>
|
||||
<IntrinsicFunctions>true</IntrinsicFunctions>
|
||||
<SDLCheck>true</SDLCheck>
|
||||
<PreprocessorDefinitions>WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<ConformanceMode>true</ConformanceMode>
|
||||
<AdditionalIncludeDirectories>$(ProjectDir)..\..\mullvadvpn-app\windows\windows-libraries\src\</AdditionalIncludeDirectories>
|
||||
<LanguageStandard>stdcpplatest</LanguageStandard>
|
||||
<RuntimeLibrary>MultiThreaded</RuntimeLibrary>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<SubSystem>Console</SubSystem>
|
||||
<EnableCOMDATFolding>true</EnableCOMDATFolding>
|
||||
<OptimizeReferences>true</OptimizeReferences>
|
||||
<GenerateDebugInformation>true</GenerateDebugInformation>
|
||||
<AdditionalDependencies>libcommon.lib;kernel32.lib;user32.lib;gdi32.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies)</AdditionalDependencies>
|
||||
<AdditionalLibraryDirectories>$(ProjectDir)..\..\mullvadvpn-app\windows\windows-libraries\bin\$(Platform)-$(Configuration)\</AdditionalLibraryDirectories>
|
||||
</Link>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
|
||||
<ClCompile>
|
||||
<PrecompiledHeader>
|
||||
</PrecompiledHeader>
|
||||
<WarningLevel>Level4</WarningLevel>
|
||||
<FunctionLevelLinking>true</FunctionLevelLinking>
|
||||
<IntrinsicFunctions>true</IntrinsicFunctions>
|
||||
<SDLCheck>true</SDLCheck>
|
||||
<PreprocessorDefinitions>NDEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<ConformanceMode>true</ConformanceMode>
|
||||
<AdditionalIncludeDirectories>$(ProjectDir)..\..\mullvadvpn-app\windows\windows-libraries\src\</AdditionalIncludeDirectories>
|
||||
<LanguageStandard>stdcpplatest</LanguageStandard>
|
||||
<RuntimeLibrary>MultiThreaded</RuntimeLibrary>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<SubSystem>Console</SubSystem>
|
||||
<EnableCOMDATFolding>true</EnableCOMDATFolding>
|
||||
<OptimizeReferences>true</OptimizeReferences>
|
||||
<GenerateDebugInformation>true</GenerateDebugInformation>
|
||||
<AdditionalDependencies>libcommon.lib;kernel32.lib;user32.lib;gdi32.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies)</AdditionalDependencies>
|
||||
<AdditionalLibraryDirectories>$(ProjectDir)..\..\mullvadvpn-app\windows\windows-libraries\bin\$(Platform)-$(Configuration)\</AdditionalLibraryDirectories>
|
||||
</Link>
|
||||
</ItemDefinitionGroup>
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
|
||||
<ImportGroup Label="ExtensionTargets">
|
||||
</ImportGroup>
|
||||
</Project>
|
||||
10
testing/stconsole.vcxproj.filters
Normal file
10
testing/stconsole.vcxproj.filters
Normal file
@@ -0,0 +1,10 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<ItemGroup>
|
||||
<ClCompile Include="stconsole.cpp" />
|
||||
<ClCompile Include="proc.cpp" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClInclude Include="proc.h" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
Reference in New Issue
Block a user