Compare commits
14 commits
main
...
concurent_
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
68c6e3cccf | ||
|
|
a6cff22592 | ||
| 82c1140eae | |||
|
|
01c496aaac | ||
|
|
250f85700e | ||
|
|
74583b230b | ||
| e78a67d1d1 | |||
|
|
232b906db1 | ||
|
|
609e6c69cd | ||
| d28968c2e3 | |||
|
|
3a2f45b28b | ||
|
|
bfa957ad45 | ||
|
|
7e63a523d7 | ||
|
|
71fc0d2398 |
66 changed files with 37686 additions and 672056 deletions
1129
Cargo.lock
generated
1129
Cargo.lock
generated
File diff suppressed because it is too large
Load diff
|
|
@ -4,6 +4,5 @@ members = [
|
||||||
"apk_frauder",
|
"apk_frauder",
|
||||||
"androscalpel_serializer",
|
"androscalpel_serializer",
|
||||||
"androscalpel_serializer_derive",
|
"androscalpel_serializer_derive",
|
||||||
"androscalpel", "androscalpel_platform_api_list",
|
"androscalpel",
|
||||||
]
|
]
|
||||||
resolver = "2"
|
|
||||||
|
|
|
||||||
661
LICENSE.txt
661
LICENSE.txt
|
|
@ -1,661 +0,0 @@
|
||||||
GNU AFFERO GENERAL PUBLIC LICENSE
|
|
||||||
Version 3, 19 November 2007
|
|
||||||
|
|
||||||
Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
|
|
||||||
Everyone is permitted to copy and distribute verbatim copies
|
|
||||||
of this license document, but changing it is not allowed.
|
|
||||||
|
|
||||||
Preamble
|
|
||||||
|
|
||||||
The GNU Affero General Public License is a free, copyleft license for
|
|
||||||
software and other kinds of works, specifically designed to ensure
|
|
||||||
cooperation with the community in the case of network server software.
|
|
||||||
|
|
||||||
The licenses for most software and other practical works are designed
|
|
||||||
to take away your freedom to share and change the works. By contrast,
|
|
||||||
our General Public Licenses are intended to guarantee your freedom to
|
|
||||||
share and change all versions of a program--to make sure it remains free
|
|
||||||
software for all its users.
|
|
||||||
|
|
||||||
When we speak of free software, we are referring to freedom, not
|
|
||||||
price. Our General Public Licenses are designed to make sure that you
|
|
||||||
have the freedom to distribute copies of free software (and charge for
|
|
||||||
them if you wish), that you receive source code or can get it if you
|
|
||||||
want it, that you can change the software or use pieces of it in new
|
|
||||||
free programs, and that you know you can do these things.
|
|
||||||
|
|
||||||
Developers that use our General Public Licenses protect your rights
|
|
||||||
with two steps: (1) assert copyright on the software, and (2) offer
|
|
||||||
you this License which gives you legal permission to copy, distribute
|
|
||||||
and/or modify the software.
|
|
||||||
|
|
||||||
A secondary benefit of defending all users' freedom is that
|
|
||||||
improvements made in alternate versions of the program, if they
|
|
||||||
receive widespread use, become available for other developers to
|
|
||||||
incorporate. Many developers of free software are heartened and
|
|
||||||
encouraged by the resulting cooperation. However, in the case of
|
|
||||||
software used on network servers, this result may fail to come about.
|
|
||||||
The GNU General Public License permits making a modified version and
|
|
||||||
letting the public access it on a server without ever releasing its
|
|
||||||
source code to the public.
|
|
||||||
|
|
||||||
The GNU Affero General Public License is designed specifically to
|
|
||||||
ensure that, in such cases, the modified source code becomes available
|
|
||||||
to the community. It requires the operator of a network server to
|
|
||||||
provide the source code of the modified version running there to the
|
|
||||||
users of that server. Therefore, public use of a modified version, on
|
|
||||||
a publicly accessible server, gives the public access to the source
|
|
||||||
code of the modified version.
|
|
||||||
|
|
||||||
An older license, called the Affero General Public License and
|
|
||||||
published by Affero, was designed to accomplish similar goals. This is
|
|
||||||
a different license, not a version of the Affero GPL, but Affero has
|
|
||||||
released a new version of the Affero GPL which permits relicensing under
|
|
||||||
this license.
|
|
||||||
|
|
||||||
The precise terms and conditions for copying, distribution and
|
|
||||||
modification follow.
|
|
||||||
|
|
||||||
TERMS AND CONDITIONS
|
|
||||||
|
|
||||||
0. Definitions.
|
|
||||||
|
|
||||||
"This License" refers to version 3 of the GNU Affero General Public License.
|
|
||||||
|
|
||||||
"Copyright" also means copyright-like laws that apply to other kinds of
|
|
||||||
works, such as semiconductor masks.
|
|
||||||
|
|
||||||
"The Program" refers to any copyrightable work licensed under this
|
|
||||||
License. Each licensee is addressed as "you". "Licensees" and
|
|
||||||
"recipients" may be individuals or organizations.
|
|
||||||
|
|
||||||
To "modify" a work means to copy from or adapt all or part of the work
|
|
||||||
in a fashion requiring copyright permission, other than the making of an
|
|
||||||
exact copy. The resulting work is called a "modified version" of the
|
|
||||||
earlier work or a work "based on" the earlier work.
|
|
||||||
|
|
||||||
A "covered work" means either the unmodified Program or a work based
|
|
||||||
on the Program.
|
|
||||||
|
|
||||||
To "propagate" a work means to do anything with it that, without
|
|
||||||
permission, would make you directly or secondarily liable for
|
|
||||||
infringement under applicable copyright law, except executing it on a
|
|
||||||
computer or modifying a private copy. Propagation includes copying,
|
|
||||||
distribution (with or without modification), making available to the
|
|
||||||
public, and in some countries other activities as well.
|
|
||||||
|
|
||||||
To "convey" a work means any kind of propagation that enables other
|
|
||||||
parties to make or receive copies. Mere interaction with a user through
|
|
||||||
a computer network, with no transfer of a copy, is not conveying.
|
|
||||||
|
|
||||||
An interactive user interface displays "Appropriate Legal Notices"
|
|
||||||
to the extent that it includes a convenient and prominently visible
|
|
||||||
feature that (1) displays an appropriate copyright notice, and (2)
|
|
||||||
tells the user that there is no warranty for the work (except to the
|
|
||||||
extent that warranties are provided), that licensees may convey the
|
|
||||||
work under this License, and how to view a copy of this License. If
|
|
||||||
the interface presents a list of user commands or options, such as a
|
|
||||||
menu, a prominent item in the list meets this criterion.
|
|
||||||
|
|
||||||
1. Source Code.
|
|
||||||
|
|
||||||
The "source code" for a work means the preferred form of the work
|
|
||||||
for making modifications to it. "Object code" means any non-source
|
|
||||||
form of a work.
|
|
||||||
|
|
||||||
A "Standard Interface" means an interface that either is an official
|
|
||||||
standard defined by a recognized standards body, or, in the case of
|
|
||||||
interfaces specified for a particular programming language, one that
|
|
||||||
is widely used among developers working in that language.
|
|
||||||
|
|
||||||
The "System Libraries" of an executable work include anything, other
|
|
||||||
than the work as a whole, that (a) is included in the normal form of
|
|
||||||
packaging a Major Component, but which is not part of that Major
|
|
||||||
Component, and (b) serves only to enable use of the work with that
|
|
||||||
Major Component, or to implement a Standard Interface for which an
|
|
||||||
implementation is available to the public in source code form. A
|
|
||||||
"Major Component", in this context, means a major essential component
|
|
||||||
(kernel, window system, and so on) of the specific operating system
|
|
||||||
(if any) on which the executable work runs, or a compiler used to
|
|
||||||
produce the work, or an object code interpreter used to run it.
|
|
||||||
|
|
||||||
The "Corresponding Source" for a work in object code form means all
|
|
||||||
the source code needed to generate, install, and (for an executable
|
|
||||||
work) run the object code and to modify the work, including scripts to
|
|
||||||
control those activities. However, it does not include the work's
|
|
||||||
System Libraries, or general-purpose tools or generally available free
|
|
||||||
programs which are used unmodified in performing those activities but
|
|
||||||
which are not part of the work. For example, Corresponding Source
|
|
||||||
includes interface definition files associated with source files for
|
|
||||||
the work, and the source code for shared libraries and dynamically
|
|
||||||
linked subprograms that the work is specifically designed to require,
|
|
||||||
such as by intimate data communication or control flow between those
|
|
||||||
subprograms and other parts of the work.
|
|
||||||
|
|
||||||
The Corresponding Source need not include anything that users
|
|
||||||
can regenerate automatically from other parts of the Corresponding
|
|
||||||
Source.
|
|
||||||
|
|
||||||
The Corresponding Source for a work in source code form is that
|
|
||||||
same work.
|
|
||||||
|
|
||||||
2. Basic Permissions.
|
|
||||||
|
|
||||||
All rights granted under this License are granted for the term of
|
|
||||||
copyright on the Program, and are irrevocable provided the stated
|
|
||||||
conditions are met. This License explicitly affirms your unlimited
|
|
||||||
permission to run the unmodified Program. The output from running a
|
|
||||||
covered work is covered by this License only if the output, given its
|
|
||||||
content, constitutes a covered work. This License acknowledges your
|
|
||||||
rights of fair use or other equivalent, as provided by copyright law.
|
|
||||||
|
|
||||||
You may make, run and propagate covered works that you do not
|
|
||||||
convey, without conditions so long as your license otherwise remains
|
|
||||||
in force. You may convey covered works to others for the sole purpose
|
|
||||||
of having them make modifications exclusively for you, or provide you
|
|
||||||
with facilities for running those works, provided that you comply with
|
|
||||||
the terms of this License in conveying all material for which you do
|
|
||||||
not control copyright. Those thus making or running the covered works
|
|
||||||
for you must do so exclusively on your behalf, under your direction
|
|
||||||
and control, on terms that prohibit them from making any copies of
|
|
||||||
your copyrighted material outside their relationship with you.
|
|
||||||
|
|
||||||
Conveying under any other circumstances is permitted solely under
|
|
||||||
the conditions stated below. Sublicensing is not allowed; section 10
|
|
||||||
makes it unnecessary.
|
|
||||||
|
|
||||||
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
|
|
||||||
|
|
||||||
No covered work shall be deemed part of an effective technological
|
|
||||||
measure under any applicable law fulfilling obligations under article
|
|
||||||
11 of the WIPO copyright treaty adopted on 20 December 1996, or
|
|
||||||
similar laws prohibiting or restricting circumvention of such
|
|
||||||
measures.
|
|
||||||
|
|
||||||
When you convey a covered work, you waive any legal power to forbid
|
|
||||||
circumvention of technological measures to the extent such circumvention
|
|
||||||
is effected by exercising rights under this License with respect to
|
|
||||||
the covered work, and you disclaim any intention to limit operation or
|
|
||||||
modification of the work as a means of enforcing, against the work's
|
|
||||||
users, your or third parties' legal rights to forbid circumvention of
|
|
||||||
technological measures.
|
|
||||||
|
|
||||||
4. Conveying Verbatim Copies.
|
|
||||||
|
|
||||||
You may convey verbatim copies of the Program's source code as you
|
|
||||||
receive it, in any medium, provided that you conspicuously and
|
|
||||||
appropriately publish on each copy an appropriate copyright notice;
|
|
||||||
keep intact all notices stating that this License and any
|
|
||||||
non-permissive terms added in accord with section 7 apply to the code;
|
|
||||||
keep intact all notices of the absence of any warranty; and give all
|
|
||||||
recipients a copy of this License along with the Program.
|
|
||||||
|
|
||||||
You may charge any price or no price for each copy that you convey,
|
|
||||||
and you may offer support or warranty protection for a fee.
|
|
||||||
|
|
||||||
5. Conveying Modified Source Versions.
|
|
||||||
|
|
||||||
You may convey a work based on the Program, or the modifications to
|
|
||||||
produce it from the Program, in the form of source code under the
|
|
||||||
terms of section 4, provided that you also meet all of these conditions:
|
|
||||||
|
|
||||||
a) The work must carry prominent notices stating that you modified
|
|
||||||
it, and giving a relevant date.
|
|
||||||
|
|
||||||
b) The work must carry prominent notices stating that it is
|
|
||||||
released under this License and any conditions added under section
|
|
||||||
7. This requirement modifies the requirement in section 4 to
|
|
||||||
"keep intact all notices".
|
|
||||||
|
|
||||||
c) You must license the entire work, as a whole, under this
|
|
||||||
License to anyone who comes into possession of a copy. This
|
|
||||||
License will therefore apply, along with any applicable section 7
|
|
||||||
additional terms, to the whole of the work, and all its parts,
|
|
||||||
regardless of how they are packaged. This License gives no
|
|
||||||
permission to license the work in any other way, but it does not
|
|
||||||
invalidate such permission if you have separately received it.
|
|
||||||
|
|
||||||
d) If the work has interactive user interfaces, each must display
|
|
||||||
Appropriate Legal Notices; however, if the Program has interactive
|
|
||||||
interfaces that do not display Appropriate Legal Notices, your
|
|
||||||
work need not make them do so.
|
|
||||||
|
|
||||||
A compilation of a covered work with other separate and independent
|
|
||||||
works, which are not by their nature extensions of the covered work,
|
|
||||||
and which are not combined with it such as to form a larger program,
|
|
||||||
in or on a volume of a storage or distribution medium, is called an
|
|
||||||
"aggregate" if the compilation and its resulting copyright are not
|
|
||||||
used to limit the access or legal rights of the compilation's users
|
|
||||||
beyond what the individual works permit. Inclusion of a covered work
|
|
||||||
in an aggregate does not cause this License to apply to the other
|
|
||||||
parts of the aggregate.
|
|
||||||
|
|
||||||
6. Conveying Non-Source Forms.
|
|
||||||
|
|
||||||
You may convey a covered work in object code form under the terms
|
|
||||||
of sections 4 and 5, provided that you also convey the
|
|
||||||
machine-readable Corresponding Source under the terms of this License,
|
|
||||||
in one of these ways:
|
|
||||||
|
|
||||||
a) Convey the object code in, or embodied in, a physical product
|
|
||||||
(including a physical distribution medium), accompanied by the
|
|
||||||
Corresponding Source fixed on a durable physical medium
|
|
||||||
customarily used for software interchange.
|
|
||||||
|
|
||||||
b) Convey the object code in, or embodied in, a physical product
|
|
||||||
(including a physical distribution medium), accompanied by a
|
|
||||||
written offer, valid for at least three years and valid for as
|
|
||||||
long as you offer spare parts or customer support for that product
|
|
||||||
model, to give anyone who possesses the object code either (1) a
|
|
||||||
copy of the Corresponding Source for all the software in the
|
|
||||||
product that is covered by this License, on a durable physical
|
|
||||||
medium customarily used for software interchange, for a price no
|
|
||||||
more than your reasonable cost of physically performing this
|
|
||||||
conveying of source, or (2) access to copy the
|
|
||||||
Corresponding Source from a network server at no charge.
|
|
||||||
|
|
||||||
c) Convey individual copies of the object code with a copy of the
|
|
||||||
written offer to provide the Corresponding Source. This
|
|
||||||
alternative is allowed only occasionally and noncommercially, and
|
|
||||||
only if you received the object code with such an offer, in accord
|
|
||||||
with subsection 6b.
|
|
||||||
|
|
||||||
d) Convey the object code by offering access from a designated
|
|
||||||
place (gratis or for a charge), and offer equivalent access to the
|
|
||||||
Corresponding Source in the same way through the same place at no
|
|
||||||
further charge. You need not require recipients to copy the
|
|
||||||
Corresponding Source along with the object code. If the place to
|
|
||||||
copy the object code is a network server, the Corresponding Source
|
|
||||||
may be on a different server (operated by you or a third party)
|
|
||||||
that supports equivalent copying facilities, provided you maintain
|
|
||||||
clear directions next to the object code saying where to find the
|
|
||||||
Corresponding Source. Regardless of what server hosts the
|
|
||||||
Corresponding Source, you remain obligated to ensure that it is
|
|
||||||
available for as long as needed to satisfy these requirements.
|
|
||||||
|
|
||||||
e) Convey the object code using peer-to-peer transmission, provided
|
|
||||||
you inform other peers where the object code and Corresponding
|
|
||||||
Source of the work are being offered to the general public at no
|
|
||||||
charge under subsection 6d.
|
|
||||||
|
|
||||||
A separable portion of the object code, whose source code is excluded
|
|
||||||
from the Corresponding Source as a System Library, need not be
|
|
||||||
included in conveying the object code work.
|
|
||||||
|
|
||||||
A "User Product" is either (1) a "consumer product", which means any
|
|
||||||
tangible personal property which is normally used for personal, family,
|
|
||||||
or household purposes, or (2) anything designed or sold for incorporation
|
|
||||||
into a dwelling. In determining whether a product is a consumer product,
|
|
||||||
doubtful cases shall be resolved in favor of coverage. For a particular
|
|
||||||
product received by a particular user, "normally used" refers to a
|
|
||||||
typical or common use of that class of product, regardless of the status
|
|
||||||
of the particular user or of the way in which the particular user
|
|
||||||
actually uses, or expects or is expected to use, the product. A product
|
|
||||||
is a consumer product regardless of whether the product has substantial
|
|
||||||
commercial, industrial or non-consumer uses, unless such uses represent
|
|
||||||
the only significant mode of use of the product.
|
|
||||||
|
|
||||||
"Installation Information" for a User Product means any methods,
|
|
||||||
procedures, authorization keys, or other information required to install
|
|
||||||
and execute modified versions of a covered work in that User Product from
|
|
||||||
a modified version of its Corresponding Source. The information must
|
|
||||||
suffice to ensure that the continued functioning of the modified object
|
|
||||||
code is in no case prevented or interfered with solely because
|
|
||||||
modification has been made.
|
|
||||||
|
|
||||||
If you convey an object code work under this section in, or with, or
|
|
||||||
specifically for use in, a User Product, and the conveying occurs as
|
|
||||||
part of a transaction in which the right of possession and use of the
|
|
||||||
User Product is transferred to the recipient in perpetuity or for a
|
|
||||||
fixed term (regardless of how the transaction is characterized), the
|
|
||||||
Corresponding Source conveyed under this section must be accompanied
|
|
||||||
by the Installation Information. But this requirement does not apply
|
|
||||||
if neither you nor any third party retains the ability to install
|
|
||||||
modified object code on the User Product (for example, the work has
|
|
||||||
been installed in ROM).
|
|
||||||
|
|
||||||
The requirement to provide Installation Information does not include a
|
|
||||||
requirement to continue to provide support service, warranty, or updates
|
|
||||||
for a work that has been modified or installed by the recipient, or for
|
|
||||||
the User Product in which it has been modified or installed. Access to a
|
|
||||||
network may be denied when the modification itself materially and
|
|
||||||
adversely affects the operation of the network or violates the rules and
|
|
||||||
protocols for communication across the network.
|
|
||||||
|
|
||||||
Corresponding Source conveyed, and Installation Information provided,
|
|
||||||
in accord with this section must be in a format that is publicly
|
|
||||||
documented (and with an implementation available to the public in
|
|
||||||
source code form), and must require no special password or key for
|
|
||||||
unpacking, reading or copying.
|
|
||||||
|
|
||||||
7. Additional Terms.
|
|
||||||
|
|
||||||
"Additional permissions" are terms that supplement the terms of this
|
|
||||||
License by making exceptions from one or more of its conditions.
|
|
||||||
Additional permissions that are applicable to the entire Program shall
|
|
||||||
be treated as though they were included in this License, to the extent
|
|
||||||
that they are valid under applicable law. If additional permissions
|
|
||||||
apply only to part of the Program, that part may be used separately
|
|
||||||
under those permissions, but the entire Program remains governed by
|
|
||||||
this License without regard to the additional permissions.
|
|
||||||
|
|
||||||
When you convey a copy of a covered work, you may at your option
|
|
||||||
remove any additional permissions from that copy, or from any part of
|
|
||||||
it. (Additional permissions may be written to require their own
|
|
||||||
removal in certain cases when you modify the work.) You may place
|
|
||||||
additional permissions on material, added by you to a covered work,
|
|
||||||
for which you have or can give appropriate copyright permission.
|
|
||||||
|
|
||||||
Notwithstanding any other provision of this License, for material you
|
|
||||||
add to a covered work, you may (if authorized by the copyright holders of
|
|
||||||
that material) supplement the terms of this License with terms:
|
|
||||||
|
|
||||||
a) Disclaiming warranty or limiting liability differently from the
|
|
||||||
terms of sections 15 and 16 of this License; or
|
|
||||||
|
|
||||||
b) Requiring preservation of specified reasonable legal notices or
|
|
||||||
author attributions in that material or in the Appropriate Legal
|
|
||||||
Notices displayed by works containing it; or
|
|
||||||
|
|
||||||
c) Prohibiting misrepresentation of the origin of that material, or
|
|
||||||
requiring that modified versions of such material be marked in
|
|
||||||
reasonable ways as different from the original version; or
|
|
||||||
|
|
||||||
d) Limiting the use for publicity purposes of names of licensors or
|
|
||||||
authors of the material; or
|
|
||||||
|
|
||||||
e) Declining to grant rights under trademark law for use of some
|
|
||||||
trade names, trademarks, or service marks; or
|
|
||||||
|
|
||||||
f) Requiring indemnification of licensors and authors of that
|
|
||||||
material by anyone who conveys the material (or modified versions of
|
|
||||||
it) with contractual assumptions of liability to the recipient, for
|
|
||||||
any liability that these contractual assumptions directly impose on
|
|
||||||
those licensors and authors.
|
|
||||||
|
|
||||||
All other non-permissive additional terms are considered "further
|
|
||||||
restrictions" within the meaning of section 10. If the Program as you
|
|
||||||
received it, or any part of it, contains a notice stating that it is
|
|
||||||
governed by this License along with a term that is a further
|
|
||||||
restriction, you may remove that term. If a license document contains
|
|
||||||
a further restriction but permits relicensing or conveying under this
|
|
||||||
License, you may add to a covered work material governed by the terms
|
|
||||||
of that license document, provided that the further restriction does
|
|
||||||
not survive such relicensing or conveying.
|
|
||||||
|
|
||||||
If you add terms to a covered work in accord with this section, you
|
|
||||||
must place, in the relevant source files, a statement of the
|
|
||||||
additional terms that apply to those files, or a notice indicating
|
|
||||||
where to find the applicable terms.
|
|
||||||
|
|
||||||
Additional terms, permissive or non-permissive, may be stated in the
|
|
||||||
form of a separately written license, or stated as exceptions;
|
|
||||||
the above requirements apply either way.
|
|
||||||
|
|
||||||
8. Termination.
|
|
||||||
|
|
||||||
You may not propagate or modify a covered work except as expressly
|
|
||||||
provided under this License. Any attempt otherwise to propagate or
|
|
||||||
modify it is void, and will automatically terminate your rights under
|
|
||||||
this License (including any patent licenses granted under the third
|
|
||||||
paragraph of section 11).
|
|
||||||
|
|
||||||
However, if you cease all violation of this License, then your
|
|
||||||
license from a particular copyright holder is reinstated (a)
|
|
||||||
provisionally, unless and until the copyright holder explicitly and
|
|
||||||
finally terminates your license, and (b) permanently, if the copyright
|
|
||||||
holder fails to notify you of the violation by some reasonable means
|
|
||||||
prior to 60 days after the cessation.
|
|
||||||
|
|
||||||
Moreover, your license from a particular copyright holder is
|
|
||||||
reinstated permanently if the copyright holder notifies you of the
|
|
||||||
violation by some reasonable means, this is the first time you have
|
|
||||||
received notice of violation of this License (for any work) from that
|
|
||||||
copyright holder, and you cure the violation prior to 30 days after
|
|
||||||
your receipt of the notice.
|
|
||||||
|
|
||||||
Termination of your rights under this section does not terminate the
|
|
||||||
licenses of parties who have received copies or rights from you under
|
|
||||||
this License. If your rights have been terminated and not permanently
|
|
||||||
reinstated, you do not qualify to receive new licenses for the same
|
|
||||||
material under section 10.
|
|
||||||
|
|
||||||
9. Acceptance Not Required for Having Copies.
|
|
||||||
|
|
||||||
You are not required to accept this License in order to receive or
|
|
||||||
run a copy of the Program. Ancillary propagation of a covered work
|
|
||||||
occurring solely as a consequence of using peer-to-peer transmission
|
|
||||||
to receive a copy likewise does not require acceptance. However,
|
|
||||||
nothing other than this License grants you permission to propagate or
|
|
||||||
modify any covered work. These actions infringe copyright if you do
|
|
||||||
not accept this License. Therefore, by modifying or propagating a
|
|
||||||
covered work, you indicate your acceptance of this License to do so.
|
|
||||||
|
|
||||||
10. Automatic Licensing of Downstream Recipients.
|
|
||||||
|
|
||||||
Each time you convey a covered work, the recipient automatically
|
|
||||||
receives a license from the original licensors, to run, modify and
|
|
||||||
propagate that work, subject to this License. You are not responsible
|
|
||||||
for enforcing compliance by third parties with this License.
|
|
||||||
|
|
||||||
An "entity transaction" is a transaction transferring control of an
|
|
||||||
organization, or substantially all assets of one, or subdividing an
|
|
||||||
organization, or merging organizations. If propagation of a covered
|
|
||||||
work results from an entity transaction, each party to that
|
|
||||||
transaction who receives a copy of the work also receives whatever
|
|
||||||
licenses to the work the party's predecessor in interest had or could
|
|
||||||
give under the previous paragraph, plus a right to possession of the
|
|
||||||
Corresponding Source of the work from the predecessor in interest, if
|
|
||||||
the predecessor has it or can get it with reasonable efforts.
|
|
||||||
|
|
||||||
You may not impose any further restrictions on the exercise of the
|
|
||||||
rights granted or affirmed under this License. For example, you may
|
|
||||||
not impose a license fee, royalty, or other charge for exercise of
|
|
||||||
rights granted under this License, and you may not initiate litigation
|
|
||||||
(including a cross-claim or counterclaim in a lawsuit) alleging that
|
|
||||||
any patent claim is infringed by making, using, selling, offering for
|
|
||||||
sale, or importing the Program or any portion of it.
|
|
||||||
|
|
||||||
11. Patents.
|
|
||||||
|
|
||||||
A "contributor" is a copyright holder who authorizes use under this
|
|
||||||
License of the Program or a work on which the Program is based. The
|
|
||||||
work thus licensed is called the contributor's "contributor version".
|
|
||||||
|
|
||||||
A contributor's "essential patent claims" are all patent claims
|
|
||||||
owned or controlled by the contributor, whether already acquired or
|
|
||||||
hereafter acquired, that would be infringed by some manner, permitted
|
|
||||||
by this License, of making, using, or selling its contributor version,
|
|
||||||
but do not include claims that would be infringed only as a
|
|
||||||
consequence of further modification of the contributor version. For
|
|
||||||
purposes of this definition, "control" includes the right to grant
|
|
||||||
patent sublicenses in a manner consistent with the requirements of
|
|
||||||
this License.
|
|
||||||
|
|
||||||
Each contributor grants you a non-exclusive, worldwide, royalty-free
|
|
||||||
patent license under the contributor's essential patent claims, to
|
|
||||||
make, use, sell, offer for sale, import and otherwise run, modify and
|
|
||||||
propagate the contents of its contributor version.
|
|
||||||
|
|
||||||
In the following three paragraphs, a "patent license" is any express
|
|
||||||
agreement or commitment, however denominated, not to enforce a patent
|
|
||||||
(such as an express permission to practice a patent or covenant not to
|
|
||||||
sue for patent infringement). To "grant" such a patent license to a
|
|
||||||
party means to make such an agreement or commitment not to enforce a
|
|
||||||
patent against the party.
|
|
||||||
|
|
||||||
If you convey a covered work, knowingly relying on a patent license,
|
|
||||||
and the Corresponding Source of the work is not available for anyone
|
|
||||||
to copy, free of charge and under the terms of this License, through a
|
|
||||||
publicly available network server or other readily accessible means,
|
|
||||||
then you must either (1) cause the Corresponding Source to be so
|
|
||||||
available, or (2) arrange to deprive yourself of the benefit of the
|
|
||||||
patent license for this particular work, or (3) arrange, in a manner
|
|
||||||
consistent with the requirements of this License, to extend the patent
|
|
||||||
license to downstream recipients. "Knowingly relying" means you have
|
|
||||||
actual knowledge that, but for the patent license, your conveying the
|
|
||||||
covered work in a country, or your recipient's use of the covered work
|
|
||||||
in a country, would infringe one or more identifiable patents in that
|
|
||||||
country that you have reason to believe are valid.
|
|
||||||
|
|
||||||
If, pursuant to or in connection with a single transaction or
|
|
||||||
arrangement, you convey, or propagate by procuring conveyance of, a
|
|
||||||
covered work, and grant a patent license to some of the parties
|
|
||||||
receiving the covered work authorizing them to use, propagate, modify
|
|
||||||
or convey a specific copy of the covered work, then the patent license
|
|
||||||
you grant is automatically extended to all recipients of the covered
|
|
||||||
work and works based on it.
|
|
||||||
|
|
||||||
A patent license is "discriminatory" if it does not include within
|
|
||||||
the scope of its coverage, prohibits the exercise of, or is
|
|
||||||
conditioned on the non-exercise of one or more of the rights that are
|
|
||||||
specifically granted under this License. You may not convey a covered
|
|
||||||
work if you are a party to an arrangement with a third party that is
|
|
||||||
in the business of distributing software, under which you make payment
|
|
||||||
to the third party based on the extent of your activity of conveying
|
|
||||||
the work, and under which the third party grants, to any of the
|
|
||||||
parties who would receive the covered work from you, a discriminatory
|
|
||||||
patent license (a) in connection with copies of the covered work
|
|
||||||
conveyed by you (or copies made from those copies), or (b) primarily
|
|
||||||
for and in connection with specific products or compilations that
|
|
||||||
contain the covered work, unless you entered into that arrangement,
|
|
||||||
or that patent license was granted, prior to 28 March 2007.
|
|
||||||
|
|
||||||
Nothing in this License shall be construed as excluding or limiting
|
|
||||||
any implied license or other defenses to infringement that may
|
|
||||||
otherwise be available to you under applicable patent law.
|
|
||||||
|
|
||||||
12. No Surrender of Others' Freedom.
|
|
||||||
|
|
||||||
If conditions are imposed on you (whether by court order, agreement or
|
|
||||||
otherwise) that contradict the conditions of this License, they do not
|
|
||||||
excuse you from the conditions of this License. If you cannot convey a
|
|
||||||
covered work so as to satisfy simultaneously your obligations under this
|
|
||||||
License and any other pertinent obligations, then as a consequence you may
|
|
||||||
not convey it at all. For example, if you agree to terms that obligate you
|
|
||||||
to collect a royalty for further conveying from those to whom you convey
|
|
||||||
the Program, the only way you could satisfy both those terms and this
|
|
||||||
License would be to refrain entirely from conveying the Program.
|
|
||||||
|
|
||||||
13. Remote Network Interaction; Use with the GNU General Public License.
|
|
||||||
|
|
||||||
Notwithstanding any other provision of this License, if you modify the
|
|
||||||
Program, your modified version must prominently offer all users
|
|
||||||
interacting with it remotely through a computer network (if your version
|
|
||||||
supports such interaction) an opportunity to receive the Corresponding
|
|
||||||
Source of your version by providing access to the Corresponding Source
|
|
||||||
from a network server at no charge, through some standard or customary
|
|
||||||
means of facilitating copying of software. This Corresponding Source
|
|
||||||
shall include the Corresponding Source for any work covered by version 3
|
|
||||||
of the GNU General Public License that is incorporated pursuant to the
|
|
||||||
following paragraph.
|
|
||||||
|
|
||||||
Notwithstanding any other provision of this License, you have
|
|
||||||
permission to link or combine any covered work with a work licensed
|
|
||||||
under version 3 of the GNU General Public License into a single
|
|
||||||
combined work, and to convey the resulting work. The terms of this
|
|
||||||
License will continue to apply to the part which is the covered work,
|
|
||||||
but the work with which it is combined will remain governed by version
|
|
||||||
3 of the GNU General Public License.
|
|
||||||
|
|
||||||
14. Revised Versions of this License.
|
|
||||||
|
|
||||||
The Free Software Foundation may publish revised and/or new versions of
|
|
||||||
the GNU Affero General Public License from time to time. Such new versions
|
|
||||||
will be similar in spirit to the present version, but may differ in detail to
|
|
||||||
address new problems or concerns.
|
|
||||||
|
|
||||||
Each version is given a distinguishing version number. If the
|
|
||||||
Program specifies that a certain numbered version of the GNU Affero General
|
|
||||||
Public License "or any later version" applies to it, you have the
|
|
||||||
option of following the terms and conditions either of that numbered
|
|
||||||
version or of any later version published by the Free Software
|
|
||||||
Foundation. If the Program does not specify a version number of the
|
|
||||||
GNU Affero General Public License, you may choose any version ever published
|
|
||||||
by the Free Software Foundation.
|
|
||||||
|
|
||||||
If the Program specifies that a proxy can decide which future
|
|
||||||
versions of the GNU Affero General Public License can be used, that proxy's
|
|
||||||
public statement of acceptance of a version permanently authorizes you
|
|
||||||
to choose that version for the Program.
|
|
||||||
|
|
||||||
Later license versions may give you additional or different
|
|
||||||
permissions. However, no additional obligations are imposed on any
|
|
||||||
author or copyright holder as a result of your choosing to follow a
|
|
||||||
later version.
|
|
||||||
|
|
||||||
15. Disclaimer of Warranty.
|
|
||||||
|
|
||||||
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
|
|
||||||
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
|
|
||||||
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
|
|
||||||
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
|
|
||||||
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
|
||||||
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
|
|
||||||
IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
|
|
||||||
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
|
|
||||||
|
|
||||||
16. Limitation of Liability.
|
|
||||||
|
|
||||||
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
|
|
||||||
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
|
|
||||||
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
|
|
||||||
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
|
|
||||||
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
|
|
||||||
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
|
|
||||||
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
|
|
||||||
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
|
|
||||||
SUCH DAMAGES.
|
|
||||||
|
|
||||||
17. Interpretation of Sections 15 and 16.
|
|
||||||
|
|
||||||
If the disclaimer of warranty and limitation of liability provided
|
|
||||||
above cannot be given local legal effect according to their terms,
|
|
||||||
reviewing courts shall apply local law that most closely approximates
|
|
||||||
an absolute waiver of all civil liability in connection with the
|
|
||||||
Program, unless a warranty or assumption of liability accompanies a
|
|
||||||
copy of the Program in return for a fee.
|
|
||||||
|
|
||||||
END OF TERMS AND CONDITIONS
|
|
||||||
|
|
||||||
How to Apply These Terms to Your New Programs
|
|
||||||
|
|
||||||
If you develop a new program, and you want it to be of the greatest
|
|
||||||
possible use to the public, the best way to achieve this is to make it
|
|
||||||
free software which everyone can redistribute and change under these terms.
|
|
||||||
|
|
||||||
To do so, attach the following notices to the program. It is safest
|
|
||||||
to attach them to the start of each source file to most effectively
|
|
||||||
state the exclusion of warranty; and each file should have at least
|
|
||||||
the "copyright" line and a pointer to where the full notice is found.
|
|
||||||
|
|
||||||
<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 Affero General Public License as published by
|
|
||||||
the Free Software Foundation, either version 3 of the License, or
|
|
||||||
(at your option) any later version.
|
|
||||||
|
|
||||||
This program is distributed in the hope that it will be useful,
|
|
||||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
GNU Affero General Public License for more details.
|
|
||||||
|
|
||||||
You should have received a copy of the GNU Affero General Public License
|
|
||||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
||||||
|
|
||||||
Also add information on how to contact you by electronic and paper mail.
|
|
||||||
|
|
||||||
If your software can interact with users remotely through a computer
|
|
||||||
network, you should also make sure that it provides a way for users to
|
|
||||||
get its source. For example, if your program is a web application, its
|
|
||||||
interface could display a "Source" link that leads users to an archive
|
|
||||||
of the code. There are many ways you could offer source, and different
|
|
||||||
solutions will be better for different programs; see section 13 for the
|
|
||||||
specific requirements.
|
|
||||||
|
|
||||||
You should also get your employer (if you work as a programmer) or school,
|
|
||||||
if any, to sign a "copyright disclaimer" for the program, if necessary.
|
|
||||||
For more information on this, and how to apply and follow the GNU AGPL, see
|
|
||||||
<https://www.gnu.org/licenses/>.
|
|
||||||
17
README.md
17
README.md
|
|
@ -1,17 +0,0 @@
|
||||||
# Androscalpel
|
|
||||||
|
|
||||||
Androscalpel is a rust crate to manipulate Android application bytecode (dalvik).
|
|
||||||
|
|
||||||
This developed between 2022 and 2025 by Jean-Marie Mineau for their PhD thesis, and release under the AGPLv3 licence with the permission of CentraleSupelec.
|
|
||||||
|
|
||||||
# Documentation
|
|
||||||
|
|
||||||
The documentation can be generated with `cargo doc`.
|
|
||||||
It will be generated at `target/doc/androscalpel/index.html`
|
|
||||||
|
|
||||||
Right now, the recompilation process is a little complexe, an example can be found [here](./androscalpel/examples/count_ins_and_genapk.rs).
|
|
||||||
|
|
||||||
# Note
|
|
||||||
|
|
||||||
The Dalvik v41 format (concatenated DEX files) is not currently supported.
|
|
||||||
Support is planned in the future.
|
|
||||||
8
TODO.md
8
TODO.md
|
|
@ -1,12 +1,8 @@
|
||||||
- sanity checks
|
- sanity checks
|
||||||
- SANITY CHECK (https://cs.android.com/android/platform/superproject/main/+/main:art/libdexfile/dex/dex_file_verifier.cc if check failed, .dex is not loaded but apk do not crash !!!)
|
|
||||||
- V41 https://cs.android.com/android/platform/superproject/main/+/main:art/libdexfile/dex/dex_file_verifier.cc;drc=e8da7cd1d0e7d3535c82f8d05adcef3edd43cd40;l=634
|
|
||||||
- tests
|
- tests
|
||||||
- https://source.android.com/docs/core/runtime/dex-format#system-annotation
|
- https://source.android.com/docs/core/runtime/dex-format#system-annotation
|
||||||
- goto size computation
|
- goto size computation
|
||||||
- Check label duplication (maybe allow identical labels at the same address only?)
|
|
||||||
- no nop when no payload
|
- no nop when no payload
|
||||||
|
- code items need a "fragment" repr
|
||||||
|
- option to get label at every code addresses
|
||||||
- name register / parameters
|
- name register / parameters
|
||||||
- fix flake
|
|
||||||
- fix python binding or remove
|
|
||||||
- clean repo
|
|
||||||
|
|
|
||||||
|
|
@ -1,48 +1,22 @@
|
||||||
[package]
|
[package]
|
||||||
name = "androscalpel"
|
name = "androscalpel"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
edition = "2024"
|
edition = "2021"
|
||||||
license = "AGPL-3.0-or-later"
|
|
||||||
|
|
||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
[lib]
|
[lib]
|
||||||
name = "androscalpel"
|
name = "androscalpel"
|
||||||
crate-type = ["cdylib", "lib"]
|
crate-type = ["cdylib"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
adler = "1.0.2"
|
adler = "1.0.2"
|
||||||
androscalpel_serializer = { version = "0.1.0", path = "../androscalpel_serializer" }
|
androscalpel_serializer = { version = "0.1.0", path = "../androscalpel_serializer" }
|
||||||
androscalpel_platform_api_list = { version = "0.1.0", path = "../androscalpel_platform_api_list", optional = true }
|
|
||||||
anyhow = { version = "1.0.75", features = ["backtrace"] }
|
anyhow = { version = "1.0.75", features = ["backtrace"] }
|
||||||
apk_frauder = { version = "0.1.0", path = "../apk_frauder" }
|
apk_frauder = { version = "0.1.0", path = "../apk_frauder" }
|
||||||
log = "0.4.20"
|
log = "0.4.20"
|
||||||
pyo3 = { version = "0.23.4", features = ["anyhow", "abi3-py38", "extension-module"], optional = true}
|
pyo3 = { version = "0.20.0", features = ["anyhow"] }
|
||||||
pyo3-log = { version = "0.12.1", optional = true}
|
pyo3-log = "0.8.3"
|
||||||
rayon = "1.9.0"
|
rayon = "1.9.0"
|
||||||
serde = { version = "1.0.195", features = ["derive"] }
|
serde = { version = "1.0.195", features = ["derive"] }
|
||||||
serde_json = "1.0.111"
|
serde_json = "1.0.111"
|
||||||
sha1 = "0.10.6"
|
sha1 = "0.10.6"
|
||||||
zip = {version = "2.2.2", optional = true}
|
|
||||||
|
|
||||||
[dev-dependencies]
|
|
||||||
pretty_assertions = "1.4.1"
|
|
||||||
# For examples
|
|
||||||
clap = { version = "4.5.27", features = ["derive"] }
|
|
||||||
env_logger = "0.11.6"
|
|
||||||
|
|
||||||
[features]
|
|
||||||
default = ["code-analysis", "platform-list"]
|
|
||||||
# TODO: need refactoring to https://github.com/PyO3/pyo3/issues/2935#issuecomment-2560930677 or cfg_eval https://github.com/rust-lang/rust/issues/82679
|
|
||||||
python = ["pyo3", "pyo3-log"] # Currently not supported
|
|
||||||
external-zip-reader = ["zip"]
|
|
||||||
platform-list = ["androscalpel_platform_api_list"]
|
|
||||||
code-analysis = []
|
|
||||||
|
|
||||||
[[example]]
|
|
||||||
name = "list-method"
|
|
||||||
|
|
||||||
[[example]]
|
|
||||||
name = "count_ins_and_genapk"
|
|
||||||
|
|
||||||
[[example]]
|
|
||||||
name = "dump_cfg"
|
|
||||||
|
|
|
||||||
|
|
@ -1,56 +0,0 @@
|
||||||
use androscalpel::{Apk, Instruction, Result, VisitableMut, VisitorMut};
|
|
||||||
use apk_frauder::replace_dex_unsigned;
|
|
||||||
|
|
||||||
use std::collections::HashMap;
|
|
||||||
use std::env;
|
|
||||||
use std::io::Cursor;
|
|
||||||
|
|
||||||
#[derive(Default)]
|
|
||||||
struct InsCounter {
|
|
||||||
pub n: u64,
|
|
||||||
}
|
|
||||||
impl VisitorMut for InsCounter {
|
|
||||||
fn visit_instruction(&mut self, ins: Instruction) -> Result<Instruction> {
|
|
||||||
if !ins.is_pseudo_ins() {
|
|
||||||
self.n += 1;
|
|
||||||
}
|
|
||||||
ins.default_visit_mut(self)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn main() {
|
|
||||||
let args: Vec<_> = env::args().collect();
|
|
||||||
assert!(args.len() > 2, "usage: {} input.apk output.apk", args[0]);
|
|
||||||
|
|
||||||
let mut cnt = InsCounter::default();
|
|
||||||
let apk = cnt
|
|
||||||
.visit_apk(Apk::load_apk_path((&args[1]).into(), false, false).unwrap())
|
|
||||||
.unwrap();
|
|
||||||
println!("Nb INS: {}", cnt.n);
|
|
||||||
|
|
||||||
// This should be streamlined
|
|
||||||
let mut dex_files = vec![];
|
|
||||||
let mut files = apk.gen_raw_dex().unwrap();
|
|
||||||
let mut i = 0;
|
|
||||||
loop {
|
|
||||||
let name = if i == 0 {
|
|
||||||
"classes.dex".into()
|
|
||||||
} else {
|
|
||||||
format!("classes{}.dex", i + 1)
|
|
||||||
};
|
|
||||||
if let Some(file) = files.remove(&name) {
|
|
||||||
dex_files.push(Cursor::new(file))
|
|
||||||
} else {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
i += 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
replace_dex_unsigned(
|
|
||||||
&args[1],
|
|
||||||
&args[2],
|
|
||||||
&mut dex_files,
|
|
||||||
None::<HashMap<_, Option<Cursor<&[u8]>>>>,
|
|
||||||
)
|
|
||||||
.unwrap();
|
|
||||||
}
|
|
||||||
|
|
@ -1,31 +0,0 @@
|
||||||
use std::fs::File;
|
|
||||||
use std::path::PathBuf;
|
|
||||||
|
|
||||||
use androscalpel::{Apk, IdMethod, MethodCFG};
|
|
||||||
|
|
||||||
use clap::Parser;
|
|
||||||
|
|
||||||
#[derive(Parser, Debug)]
|
|
||||||
#[command(version, about, long_about = None, arg_required_else_help = true)]
|
|
||||||
struct Cli {
|
|
||||||
#[arg(short, long)]
|
|
||||||
apk: PathBuf,
|
|
||||||
#[arg(short, long)]
|
|
||||||
method: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
fn main() {
|
|
||||||
env_logger::init();
|
|
||||||
let cli = Cli::parse();
|
|
||||||
let mut apk = Apk::load_apk(File::open(&cli.apk).unwrap(), |_, _, _| None, false).unwrap();
|
|
||||||
let mid = IdMethod::from_smali(&cli.method).unwrap();
|
|
||||||
let class = apk.get_class_mut(&mid.class_).unwrap();
|
|
||||||
let method = if let Some(method) = class.virtual_methods.get(&mid) {
|
|
||||||
method
|
|
||||||
} else {
|
|
||||||
class.direct_methods.get(&mid).unwrap()
|
|
||||||
};
|
|
||||||
let cfg = MethodCFG::new(method).unwrap();
|
|
||||||
print!("{}", cfg.to_dot(true));
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
@ -1,20 +0,0 @@
|
||||||
use androscalpel::{Apk, Method, Result, SmaliName, Visitable, Visitor};
|
|
||||||
use std::env;
|
|
||||||
|
|
||||||
#[derive(Default)]
|
|
||||||
struct MethodPrinter {}
|
|
||||||
impl Visitor for MethodPrinter {
|
|
||||||
fn visit_method(&mut self, method: &Method) -> Result<()> {
|
|
||||||
println!("Method: {}", method.descriptor.try_to_smali()?);
|
|
||||||
method.default_visit(self)?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn main() {
|
|
||||||
let args: Vec<_> = env::args().collect();
|
|
||||||
assert!(args.len() > 1, "Need one argument");
|
|
||||||
|
|
||||||
let apk = Apk::load_apk_path((&args[1]).into(), false, false).unwrap();
|
|
||||||
MethodPrinter::default().visit_apk(&apk).unwrap();
|
|
||||||
}
|
|
||||||
16
androscalpel/pyproject.toml
Normal file
16
androscalpel/pyproject.toml
Normal file
|
|
@ -0,0 +1,16 @@
|
||||||
|
[build-system]
|
||||||
|
requires = ["maturin>=1.2,<2.0"]
|
||||||
|
build-backend = "maturin"
|
||||||
|
|
||||||
|
[project]
|
||||||
|
name = "androscalpel"
|
||||||
|
requires-python = ">=3.7"
|
||||||
|
classifiers = [
|
||||||
|
"Programming Language :: Rust",
|
||||||
|
"Programming Language :: Python :: Implementation :: CPython",
|
||||||
|
"Programming Language :: Python :: Implementation :: PyPy",
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
[tool.maturin]
|
||||||
|
features = ["pyo3/extension-module"]
|
||||||
|
|
@ -2,38 +2,41 @@
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use std::collections::{HashMap, HashSet};
|
use std::collections::{HashMap, HashSet};
|
||||||
|
|
||||||
#[cfg(feature = "python")]
|
|
||||||
use pyo3::prelude::*;
|
use pyo3::prelude::*;
|
||||||
|
|
||||||
use crate::hashmap_vectorize;
|
use crate::hashmap_vectorize;
|
||||||
use crate::{
|
use crate::{
|
||||||
DexString, IdField, IdMethod, IdMethodType, MethodHandle, Result, Visitable, VisitableMut,
|
dex_id::IdType, value::DexValue, DexString, IdField, IdMethod, IdMethodType, MethodHandle,
|
||||||
Visitor, VisitorMut, dex_id::IdType, value::DexValue,
|
Result,
|
||||||
};
|
};
|
||||||
|
|
||||||
/// Annotation with a visibility
|
/// Annotation with a visibility
|
||||||
#[cfg_attr(feature = "python", pyclass(eq))]
|
#[pyclass]
|
||||||
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)]
|
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)]
|
||||||
pub struct DexAnnotationItem {
|
pub struct DexAnnotationItem {
|
||||||
// TODO: the get/set will probably be wonky on the python side when edditing
|
// TODO: the get/set will probably be wonky on the python side when edditing
|
||||||
/// The actual annotation
|
/// The actual annotation
|
||||||
#[cfg_attr(feature = "python", pyo3(get))]
|
#[pyo3(get)]
|
||||||
pub annotation: DexAnnotation,
|
pub annotation: DexAnnotation,
|
||||||
// TODO: enforce exclusivity
|
// TODO: enforce exclusivity
|
||||||
/// If the annotation visibility is set to build
|
/// If the annotation visibility is set to build
|
||||||
#[cfg_attr(feature = "python", pyo3(get))]
|
#[pyo3(get)]
|
||||||
pub visibility_build: bool,
|
pub visibility_build: bool,
|
||||||
/// If the annotation visibility is set to runtime
|
/// If the annotation visibility is set to runtime
|
||||||
#[cfg_attr(feature = "python", pyo3(get))]
|
#[pyo3(get)]
|
||||||
pub visibility_runtime: bool,
|
pub visibility_runtime: bool,
|
||||||
/// If the annotation visibility is set to system
|
/// If the annotation visibility is set to system
|
||||||
#[cfg_attr(feature = "python", pyo3(get))]
|
#[pyo3(get)]
|
||||||
pub visibility_system: bool,
|
pub visibility_system: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg_attr(feature = "python", pymethods)]
|
#[pymethods]
|
||||||
impl DexAnnotationItem {
|
impl DexAnnotationItem {
|
||||||
#[cfg_attr(feature = "python", new)]
|
pub fn __eq__(&self, other: &Self) -> bool {
|
||||||
|
self == other
|
||||||
|
}
|
||||||
|
|
||||||
|
#[new]
|
||||||
pub fn new(annotation: DexAnnotation) -> Self {
|
pub fn new(annotation: DexAnnotation) -> Self {
|
||||||
Self {
|
Self {
|
||||||
annotation,
|
annotation,
|
||||||
|
|
@ -102,44 +105,34 @@ impl DexAnnotationItem {
|
||||||
Ok(serde_json::to_string(self)?)
|
Ok(serde_json::to_string(self)?)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg_attr(feature = "python", staticmethod)]
|
#[staticmethod]
|
||||||
pub fn from_json(json: &str) -> Result<Self> {
|
pub fn from_json(json: &str) -> Result<Self> {
|
||||||
Ok(serde_json::from_str(json)?)
|
Ok(serde_json::from_str(json)?)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<V: Visitor> Visitable<V> for DexAnnotationItem {
|
|
||||||
fn default_visit(&self, v: &mut V) -> Result<()> {
|
|
||||||
v.visit_annotation(&self.annotation)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl<V: VisitorMut> VisitableMut<V> for DexAnnotationItem {
|
|
||||||
fn default_visit_mut(self, v: &mut V) -> Result<Self> {
|
|
||||||
Ok(Self {
|
|
||||||
annotation: v.visit_annotation(self.annotation)?,
|
|
||||||
..self
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// An annotation.
|
/// An annotation.
|
||||||
#[cfg_attr(feature = "python", pyclass(eq))]
|
#[pyclass]
|
||||||
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)]
|
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)]
|
||||||
pub struct DexAnnotation {
|
pub struct DexAnnotation {
|
||||||
// TODO: check the relation between type and encoded_value.
|
// TODO: check the relation between type and encoded_value.
|
||||||
/// The type of the annotation.
|
/// The type of the annotation.
|
||||||
#[cfg_attr(feature = "python", pyo3(get))]
|
#[pyo3(get)]
|
||||||
pub type_: IdType,
|
pub type_: IdType,
|
||||||
// TODO: the get/set will probably be wonky on the python side when edditing
|
// TODO: the get/set will probably be wonky on the python side when edditing
|
||||||
/// The annotation elements.
|
/// The annotation elements.
|
||||||
#[cfg_attr(feature = "python", pyo3(get))]
|
#[pyo3(get)]
|
||||||
#[serde(with = "hashmap_vectorize")]
|
#[serde(with = "hashmap_vectorize")]
|
||||||
pub elements: HashMap<DexString, DexValue>, // TODO: check MemberName syntax?
|
pub elements: HashMap<DexString, DexValue>, // TODO: check MemberName syntax?
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg_attr(feature = "python", pymethods)]
|
#[pymethods]
|
||||||
impl DexAnnotation {
|
impl DexAnnotation {
|
||||||
#[cfg_attr(feature = "python", new)]
|
pub fn __eq__(&self, other: &Self) -> bool {
|
||||||
|
self == other
|
||||||
|
}
|
||||||
|
|
||||||
|
#[new]
|
||||||
pub fn new(type_: IdType, elements: HashMap<DexString, DexValue>) -> Self {
|
pub fn new(type_: IdType, elements: HashMap<DexString, DexValue>) -> Self {
|
||||||
Self { type_, elements }
|
Self { type_, elements }
|
||||||
}
|
}
|
||||||
|
|
@ -219,32 +212,8 @@ impl DexAnnotation {
|
||||||
Ok(serde_json::to_string(self)?)
|
Ok(serde_json::to_string(self)?)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg_attr(feature = "python", staticmethod)]
|
#[staticmethod]
|
||||||
pub fn from_json(json: &str) -> Result<Self> {
|
pub fn from_json(json: &str) -> Result<Self> {
|
||||||
Ok(serde_json::from_str(json)?)
|
Ok(serde_json::from_str(json)?)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<V: Visitor> Visitable<V> for DexAnnotation {
|
|
||||||
fn default_visit(&self, v: &mut V) -> Result<()> {
|
|
||||||
v.visit_type(&self.type_)?;
|
|
||||||
for (id, val) in &self.elements {
|
|
||||||
v.visit_string(id)?;
|
|
||||||
v.visit_value(val)?;
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<V: VisitorMut> VisitableMut<V> for DexAnnotation {
|
|
||||||
fn default_visit_mut(self, v: &mut V) -> Result<Self> {
|
|
||||||
let type_ = v.visit_type(self.type_)?;
|
|
||||||
let mut elements = HashMap::new();
|
|
||||||
for (id, val) in self.elements {
|
|
||||||
let id = v.visit_string(id)?;
|
|
||||||
let val = v.visit_value(val)?;
|
|
||||||
elements.insert(id, val);
|
|
||||||
}
|
|
||||||
Ok(Self { type_, elements })
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
||||||
File diff suppressed because it is too large
Load diff
|
|
@ -3,90 +3,94 @@
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use std::collections::{HashMap, HashSet};
|
use std::collections::{HashMap, HashSet};
|
||||||
|
|
||||||
#[cfg(feature = "python")]
|
|
||||||
use pyo3::prelude::*;
|
use pyo3::prelude::*;
|
||||||
|
|
||||||
|
use crate::hashmap_vectorize;
|
||||||
use crate::{
|
use crate::{
|
||||||
DexAnnotationItem, DexString, Field, IdField, IdMethod, IdMethodType, IdType, Method,
|
DexAnnotationItem, DexString, Field, IdField, IdMethod, IdMethodType, IdType, Method,
|
||||||
MethodHandle, Result, Visitable, VisitableMut, Visitor, VisitorMut,
|
MethodHandle, Result,
|
||||||
};
|
};
|
||||||
use androscalpel_serializer::consts::*;
|
use androscalpel_serializer::consts::*;
|
||||||
|
|
||||||
/// Represent an apk
|
/// Represent an apk
|
||||||
#[cfg_attr(feature = "python", pyclass(eq))]
|
#[pyclass]
|
||||||
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq)]
|
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq)]
|
||||||
pub struct Class {
|
pub struct Class {
|
||||||
/// Type, format described at
|
/// Type, format described at
|
||||||
/// <https://source.android.com/docs/core/runtime/dex-format#typedescriptor>
|
/// <https://source.android.com/docs/core/runtime/dex-format#typedescriptor>
|
||||||
#[cfg_attr(feature = "python", pyo3(get))]
|
#[pyo3(get)]
|
||||||
pub descriptor: IdType,
|
pub descriptor: IdType,
|
||||||
/// If the class is visible everywhere
|
/// If the class is visible everywhere
|
||||||
#[cfg_attr(feature = "python", pyo3(get))]
|
#[pyo3(get)]
|
||||||
pub is_public: bool,
|
pub is_public: bool,
|
||||||
/// If the class is subclassable
|
/// If the class is subclassable
|
||||||
#[cfg_attr(feature = "python", pyo3(get))]
|
#[pyo3(get)]
|
||||||
pub is_final: bool,
|
pub is_final: bool,
|
||||||
/// If the class is a 'multipy-implementable abstract class' AKA an interface
|
/// If the class is a 'multipy-implementable abstract class' AKA an interface
|
||||||
#[cfg_attr(feature = "python", pyo3(get))]
|
#[pyo3(get)]
|
||||||
pub is_interface: bool,
|
pub is_interface: bool,
|
||||||
/// If the class is instanciable
|
/// If the class is instanciable
|
||||||
#[cfg_attr(feature = "python", pyo3(get))]
|
#[pyo3(get)]
|
||||||
pub is_abstract: bool,
|
pub is_abstract: bool,
|
||||||
/// If the class is not directly defined in the source code
|
/// If the class is not directly defined in the source code
|
||||||
#[cfg_attr(feature = "python", pyo3(get))]
|
#[pyo3(get)]
|
||||||
pub is_synthetic: bool,
|
pub is_synthetic: bool,
|
||||||
/// If the class is an annotation
|
/// If the class is an annotation
|
||||||
#[cfg_attr(feature = "python", pyo3(get))]
|
#[pyo3(get)]
|
||||||
pub is_annotation: bool,
|
pub is_annotation: bool,
|
||||||
/// If the class is an enum
|
/// If the class is an enum
|
||||||
#[cfg_attr(feature = "python", pyo3(get))]
|
#[pyo3(get)]
|
||||||
pub is_enum: bool,
|
pub is_enum: bool,
|
||||||
/// Name of the superclass, format described at
|
/// Name of the superclass, format described at
|
||||||
/// <https://source.android.com/docs/core/runtime/dex-format#typedescriptor>
|
/// <https://source.android.com/docs/core/runtime/dex-format#typedescriptor>
|
||||||
#[cfg_attr(feature = "python", pyo3(get))]
|
#[pyo3(get)]
|
||||||
pub superclass: Option<IdType>,
|
pub superclass: Option<IdType>,
|
||||||
/// List of the interfaces that class implement, format of the interfaces
|
/// List of the interfaces that class implement, format of the interfaces
|
||||||
/// name is discribed at
|
/// name is discribed at
|
||||||
/// <https://source.android.com/docs/core/runtime/dex-format#typedescriptor>
|
/// <https://source.android.com/docs/core/runtime/dex-format#typedescriptor>
|
||||||
#[cfg_attr(feature = "python", pyo3(get))]
|
#[pyo3(get)]
|
||||||
pub interfaces: Vec<IdType>,
|
pub interfaces: Vec<IdType>,
|
||||||
/// Name of the source file where this class is defined.
|
/// Name of the source file where this class is defined.
|
||||||
#[cfg_attr(feature = "python", pyo3(get))]
|
#[pyo3(get)]
|
||||||
pub source_file: Option<DexString>,
|
pub source_file: Option<DexString>,
|
||||||
|
|
||||||
/// The static fields
|
/// The static fields
|
||||||
#[cfg_attr(feature = "python", pyo3(get))]
|
#[pyo3(get)]
|
||||||
|
#[serde(with = "hashmap_vectorize")]
|
||||||
pub static_fields: HashMap<IdField, Field>,
|
pub static_fields: HashMap<IdField, Field>,
|
||||||
/// The instance fields
|
/// The instance fields
|
||||||
#[cfg_attr(feature = "python", pyo3(get))]
|
#[pyo3(get)]
|
||||||
|
#[serde(with = "hashmap_vectorize")]
|
||||||
pub instance_fields: HashMap<IdField, Field>,
|
pub instance_fields: HashMap<IdField, Field>,
|
||||||
/// The direct (static, private or constructor) methods of the class
|
/// The direct (static, private or constructor) methods of the class
|
||||||
#[cfg_attr(feature = "python", pyo3(get))]
|
#[pyo3(get)]
|
||||||
|
#[serde(with = "hashmap_vectorize")]
|
||||||
pub direct_methods: HashMap<IdMethod, Method>,
|
pub direct_methods: HashMap<IdMethod, Method>,
|
||||||
/// The virtual (ie non direct) methods of the class
|
/// The virtual (ie non direct) methods of the class
|
||||||
#[cfg_attr(feature = "python", pyo3(get))]
|
#[pyo3(get)]
|
||||||
|
#[serde(with = "hashmap_vectorize")]
|
||||||
pub virtual_methods: HashMap<IdMethod, Method>,
|
pub virtual_methods: HashMap<IdMethod, Method>,
|
||||||
// Do we need to distinguish direct and virtual (all the other) methods?
|
// Do we need to distinguish direct and virtual (all the other) methods?
|
||||||
// Maybe overlapping descriptor (same name, class and proto?)
|
// Maybe overlapping descriptor (same name, class and proto?)
|
||||||
/// The annotation related to this class (note: this does not include the
|
/// The annotation related to this class (note: this does not include the
|
||||||
/// methods/field/parameters annotations, they are stored in the methods and fields
|
/// methods/field/parameters annotations, they are stored in the methods and fields
|
||||||
/// structutres)
|
/// structutres)
|
||||||
#[cfg_attr(feature = "python", pyo3(get))]
|
#[pyo3(get)]
|
||||||
pub annotations: Vec<DexAnnotationItem>,
|
pub annotations: Vec<DexAnnotationItem>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg_attr(feature = "python", pymethods)]
|
#[pymethods]
|
||||||
impl Class {
|
impl Class {
|
||||||
pub fn to_json(&self) -> Result<String> {
|
pub fn to_json(&self) -> Result<String> {
|
||||||
Ok(serde_json::to_string(self)?)
|
Ok(serde_json::to_string(self)?)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg_attr(feature = "python", staticmethod)]
|
#[staticmethod]
|
||||||
pub fn from_json(json: &str) -> Result<Self> {
|
pub fn from_json(json: &str) -> Result<Self> {
|
||||||
Ok(serde_json::from_str(json)?)
|
Ok(serde_json::from_str(json)?)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg_attr(feature = "python", new)]
|
#[new]
|
||||||
pub fn new(name: DexString) -> Result<Self> {
|
pub fn new(name: DexString) -> Result<Self> {
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
descriptor: IdType::new(name)?,
|
descriptor: IdType::new(name)?,
|
||||||
|
|
@ -109,15 +113,15 @@ impl Class {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn __str__(&self) -> String {
|
pub fn __str__(&self) -> String {
|
||||||
let name: String = self.descriptor.get_name().__str__();
|
let name: String = (&self.descriptor.get_name()).into();
|
||||||
let file = if let Some(file) = &self.source_file {
|
let file = if let Some(file) = &self.source_file {
|
||||||
let file: String = file.__str__();
|
let file: String = file.into();
|
||||||
format!(" defined in {file}\n")
|
format!(" defined in {file}\n")
|
||||||
} else {
|
} else {
|
||||||
"".into()
|
"".into()
|
||||||
};
|
};
|
||||||
let superclass = if let Some(spcl) = &self.superclass {
|
let superclass = if let Some(spcl) = &self.superclass {
|
||||||
let spcl: String = spcl.get_name().__str__();
|
let spcl: String = spcl.get_name().into();
|
||||||
format!(" extends: {spcl}\n")
|
format!(" extends: {spcl}\n")
|
||||||
} else {
|
} else {
|
||||||
"".into()
|
"".into()
|
||||||
|
|
@ -127,7 +131,7 @@ impl Class {
|
||||||
} else {
|
} else {
|
||||||
let mut interfaces: String = " implements:\n".into();
|
let mut interfaces: String = " implements:\n".into();
|
||||||
for it in &self.interfaces {
|
for it in &self.interfaces {
|
||||||
let it: String = it.get_name().__str__();
|
let it: String = it.get_name().into();
|
||||||
interfaces += &format!(" {it}\n");
|
interfaces += &format!(" {it}\n");
|
||||||
}
|
}
|
||||||
interfaces
|
interfaces
|
||||||
|
|
@ -137,7 +141,7 @@ impl Class {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn __repr__(&self) -> String {
|
pub fn __repr__(&self) -> String {
|
||||||
let name: String = self.descriptor.get_name().__str__();
|
let name: String = (&self.descriptor.get_name()).into();
|
||||||
format!("Class({name})")
|
format!("Class({name})")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -317,7 +321,7 @@ impl Class {
|
||||||
.any(|field| field.value.is_some())
|
.any(|field| field.value.is_some())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Check if the class or its fields/methods have annotations
|
/// If the class or its fields/methods have annotations
|
||||||
pub fn has_annotations(&self) -> bool {
|
pub fn has_annotations(&self) -> bool {
|
||||||
!self.annotations.is_empty()
|
!self.annotations.is_empty()
|
||||||
|| self
|
|| self
|
||||||
|
|
@ -331,30 +335,11 @@ impl Class {
|
||||||
|| self
|
|| self
|
||||||
.direct_methods
|
.direct_methods
|
||||||
.values()
|
.values()
|
||||||
.any(|method| method.has_annotations())
|
.any(|field| field.has_annotations())
|
||||||
|| self
|
|| self
|
||||||
.virtual_methods
|
.virtual_methods
|
||||||
.values()
|
.values()
|
||||||
.any(|method| method.has_annotations())
|
.any(|field| field.has_annotations())
|
||||||
}
|
|
||||||
|
|
||||||
/// Check if the class have a field of method with hiddenapi information
|
|
||||||
pub fn has_hiddenapi(&self) -> bool {
|
|
||||||
self.static_fields
|
|
||||||
.values()
|
|
||||||
.any(|field| field.hiddenapi.is_some())
|
|
||||||
|| self
|
|
||||||
.instance_fields
|
|
||||||
.values()
|
|
||||||
.any(|field| field.hiddenapi.is_some())
|
|
||||||
|| self
|
|
||||||
.direct_methods
|
|
||||||
.values()
|
|
||||||
.any(|method| method.hiddenapi.is_some())
|
|
||||||
|| self
|
|
||||||
.virtual_methods
|
|
||||||
.values()
|
|
||||||
.any(|method| method.hiddenapi.is_some())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Return the binary representation of access flags.
|
/// Return the binary representation of access flags.
|
||||||
|
|
@ -384,98 +369,7 @@ impl Class {
|
||||||
flags
|
flags
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Check if the class is a platform class (ie in the android SDK or a hidden API).
|
pub fn __eq__(&self, other: &Self) -> bool {
|
||||||
#[cfg(feature = "platform-list")]
|
self == other
|
||||||
pub fn is_platform_class(&self) -> bool {
|
|
||||||
self.descriptor.is_platform_class()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<V: Visitor> Visitable<V> for Class {
|
|
||||||
fn default_visit(&self, v: &mut V) -> Result<()> {
|
|
||||||
v.visit_type(&self.descriptor)?;
|
|
||||||
if let Some(superclass) = &self.superclass {
|
|
||||||
v.visit_type(superclass)?;
|
|
||||||
}
|
|
||||||
for interface in &self.interfaces {
|
|
||||||
v.visit_type(interface)?;
|
|
||||||
}
|
|
||||||
if let Some(source_file) = &self.source_file {
|
|
||||||
v.visit_string(source_file)?;
|
|
||||||
}
|
|
||||||
for (id, field) in &self.static_fields {
|
|
||||||
v.visit_field_id(id)?;
|
|
||||||
v.visit_field(field)?;
|
|
||||||
}
|
|
||||||
for (id, field) in &self.instance_fields {
|
|
||||||
v.visit_field_id(id)?;
|
|
||||||
v.visit_field(field)?;
|
|
||||||
}
|
|
||||||
for (id, meth) in &self.direct_methods {
|
|
||||||
v.visit_method_id(id)?;
|
|
||||||
v.visit_method(meth)?;
|
|
||||||
}
|
|
||||||
for (id, meth) in &self.virtual_methods {
|
|
||||||
v.visit_method_id(id)?;
|
|
||||||
v.visit_method(meth)?;
|
|
||||||
}
|
|
||||||
for annot in &self.annotations {
|
|
||||||
v.visit_annotation_item(annot)?;
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl<V: VisitorMut> VisitableMut<V> for Class {
|
|
||||||
fn default_visit_mut(self, v: &mut V) -> Result<Self> {
|
|
||||||
Ok(Self {
|
|
||||||
descriptor: v.visit_type(self.descriptor)?,
|
|
||||||
superclass: self
|
|
||||||
.superclass
|
|
||||||
.map(|superclass| v.visit_type(superclass))
|
|
||||||
.transpose()?,
|
|
||||||
interfaces: self
|
|
||||||
.interfaces
|
|
||||||
.into_iter()
|
|
||||||
.map(|interface| v.visit_type(interface))
|
|
||||||
.collect::<Result<_>>()?,
|
|
||||||
source_file: self
|
|
||||||
.source_file
|
|
||||||
.map(|source_file| v.visit_string(source_file))
|
|
||||||
.transpose()?,
|
|
||||||
static_fields: {
|
|
||||||
let mut static_fields = HashMap::new();
|
|
||||||
for (id, field) in self.static_fields.into_iter() {
|
|
||||||
static_fields.insert(v.visit_field_id(id)?, v.visit_field(field)?);
|
|
||||||
}
|
|
||||||
static_fields
|
|
||||||
},
|
|
||||||
instance_fields: {
|
|
||||||
let mut instance_fields = HashMap::new();
|
|
||||||
for (id, field) in self.instance_fields.into_iter() {
|
|
||||||
instance_fields.insert(v.visit_field_id(id)?, v.visit_field(field)?);
|
|
||||||
}
|
|
||||||
instance_fields
|
|
||||||
},
|
|
||||||
direct_methods: {
|
|
||||||
let mut direct_methods = HashMap::new();
|
|
||||||
for (id, meth) in self.direct_methods.into_iter() {
|
|
||||||
direct_methods.insert(v.visit_method_id(id)?, v.visit_method(meth)?);
|
|
||||||
}
|
|
||||||
direct_methods
|
|
||||||
},
|
|
||||||
virtual_methods: {
|
|
||||||
let mut virtual_methods = HashMap::new();
|
|
||||||
for (id, meth) in self.virtual_methods.into_iter() {
|
|
||||||
virtual_methods.insert(v.visit_method_id(id)?, v.visit_method(meth)?);
|
|
||||||
}
|
|
||||||
virtual_methods
|
|
||||||
},
|
|
||||||
annotations: self
|
|
||||||
.annotations
|
|
||||||
.into_iter()
|
|
||||||
.map(|annotation| v.visit_annotation_item(annotation))
|
|
||||||
.collect::<Result<_>>()?,
|
|
||||||
..self
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,16 +1,13 @@
|
||||||
//! Representation of a method.
|
//! Representation of a method.
|
||||||
|
|
||||||
use anyhow::anyhow;
|
use anyhow::anyhow;
|
||||||
use log::debug;
|
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use std::collections::{HashMap, HashSet};
|
use std::collections::{HashMap, HashSet};
|
||||||
|
|
||||||
#[cfg(feature = "python")]
|
|
||||||
use pyo3::prelude::*;
|
use pyo3::prelude::*;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
DexString, IdField, IdMethod, IdMethodType, IdType, Method, MethodHandle, Result, Visitable,
|
ins, ins::Instruction, DexString, IdField, IdMethod, IdMethodType, IdType, MethodHandle, Result,
|
||||||
VisitableMut, Visitor, VisitorMut, ins::Instruction,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// TODO: make this easy to edit/manipulate, maybe move to Method
|
// TODO: make this easy to edit/manipulate, maybe move to Method
|
||||||
|
|
@ -18,86 +15,76 @@ use crate::{
|
||||||
// type TmpHandlerType = (Vec<(IdType, u32)>, Option<u32>);
|
// type TmpHandlerType = (Vec<(IdType, u32)>, Option<u32>);
|
||||||
|
|
||||||
/// The code run by a method.
|
/// The code run by a method.
|
||||||
#[cfg_attr(feature = "python", pyclass(eq))]
|
#[pyclass]
|
||||||
#[derive(Debug, Clone, Deserialize, Serialize)]
|
#[derive(Debug, Clone, Deserialize, Serialize)]
|
||||||
pub struct Code {
|
pub struct Code {
|
||||||
// TODO: remove and compute this value from code?
|
// TODO: remove and compute this value from code?
|
||||||
/// The number of registers used by the code
|
/// The number of registers used by the code
|
||||||
#[cfg_attr(feature = "python", pyo3(get))]
|
#[pyo3(get)]
|
||||||
pub registers_size: u16,
|
pub registers_size: u16,
|
||||||
|
// TODO: what does it means? is it computable?
|
||||||
|
/// The number of words of incoming arguments to the method
|
||||||
|
#[pyo3(get)]
|
||||||
|
pub ins_size: u16,
|
||||||
|
// TODO: what does it means? is it computable?
|
||||||
|
/// The number of words of outgoing argument space
|
||||||
|
#[pyo3(get)]
|
||||||
|
pub outs_size: u16,
|
||||||
|
// TODO: implement
|
||||||
|
/// The debug info
|
||||||
|
#[pyo3(get)]
|
||||||
|
pub debug_info: (u32, Vec<u8>), // Should be stripped, copying like this just don't work
|
||||||
/// The names of the parameters if given
|
/// The names of the parameters if given
|
||||||
#[cfg_attr(feature = "python", pyo3(get))]
|
#[pyo3(get)]
|
||||||
pub parameter_names: Option<Vec<Option<DexString>>>,
|
pub parameter_names: Option<Vec<Option<DexString>>>,
|
||||||
/// The instructions.
|
/// The instructions.
|
||||||
#[cfg_attr(feature = "python", pyo3(get))]
|
#[pyo3(get)]
|
||||||
pub insns: Vec<Instruction>,
|
pub insns: Vec<Instruction>,
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO reimplement PartialEq: label should become address independant
|
|
||||||
impl PartialEq for Code {
|
impl PartialEq for Code {
|
||||||
fn eq(&self, other: &Self) -> bool {
|
fn eq(&self, other: &Self) -> bool {
|
||||||
let comparable_self = self.semantic_comparable().unwrap();
|
let comparable_self = self.semantic_comparable().unwrap();
|
||||||
let comparable_other = other.semantic_comparable().unwrap();
|
let comparable_other = other.semantic_comparable().unwrap();
|
||||||
(comparable_self.registers_size == comparable_other.registers_size)
|
(comparable_self.registers_size == comparable_other.registers_size)
|
||||||
|
&& (comparable_self.ins_size == comparable_other.ins_size)
|
||||||
|
&& (comparable_self.outs_size == comparable_other.outs_size)
|
||||||
|
&& (comparable_self.debug_info == comparable_other.debug_info)
|
||||||
&& (comparable_self.insns == comparable_other.insns)
|
&& (comparable_self.insns == comparable_other.insns)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg_attr(feature = "python", pymethods)]
|
// TODO reimplement PartialEq: label should become address independant
|
||||||
|
|
||||||
|
#[pymethods]
|
||||||
impl Code {
|
impl Code {
|
||||||
pub fn to_json(&self) -> Result<String> {
|
pub fn to_json(&self) -> Result<String> {
|
||||||
Ok(serde_json::to_string(self)?)
|
Ok(serde_json::to_string(self)?)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg_attr(feature = "python", staticmethod)]
|
#[staticmethod]
|
||||||
pub fn from_json(json: &str) -> Result<Self> {
|
pub fn from_json(json: &str) -> Result<Self> {
|
||||||
Ok(serde_json::from_str(json)?)
|
Ok(serde_json::from_str(json)?)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg_attr(feature = "python", new)]
|
#[new]
|
||||||
#[cfg_attr(feature = "python", pyo3(signature = (registers_size, ins_size, outs_size, insns, parameter_names=None)))]
|
|
||||||
pub fn new(
|
pub fn new(
|
||||||
registers_size: u16,
|
registers_size: u16,
|
||||||
|
ins_size: u16,
|
||||||
|
outs_size: u16,
|
||||||
insns: Vec<Instruction>,
|
insns: Vec<Instruction>,
|
||||||
parameter_names: Option<Vec<Option<DexString>>>,
|
parameter_names: Option<Vec<Option<DexString>>>,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
Self {
|
Self {
|
||||||
registers_size,
|
registers_size,
|
||||||
|
ins_size,
|
||||||
|
outs_size,
|
||||||
insns,
|
insns,
|
||||||
parameter_names,
|
parameter_names,
|
||||||
|
debug_info: (0, vec![]),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Compute the `ins_size` field. This is the number of register parameters, including the
|
|
||||||
/// `this` parameter for non-static methods.
|
|
||||||
/// This information is stored in the code item in dex files, but is not computable from the code
|
|
||||||
/// (as opposed to `outs_size`). The [`Method`] struct is needed to compute it.
|
|
||||||
pub fn ins_size(&self, method: &Method) -> u16 {
|
|
||||||
method.ins_size()
|
|
||||||
}
|
|
||||||
/// Compute the `outs_size` field. This is the number of registers needed to call other
|
|
||||||
/// function.
|
|
||||||
pub fn outs_size(&self) -> u16 {
|
|
||||||
let mut outs = 0;
|
|
||||||
for ins in &self.insns {
|
|
||||||
match ins {
|
|
||||||
Instruction::InvokeVirtual { args, .. }
|
|
||||||
| Instruction::InvokeSuper { args, .. }
|
|
||||||
| Instruction::InvokeDirect { args, .. }
|
|
||||||
| Instruction::InvokeStatic { args, .. }
|
|
||||||
| Instruction::InvokeInterface { args, .. }
|
|
||||||
| Instruction::InvokePolymorphic { args, .. }
|
|
||||||
| Instruction::InvokeCustom { args, .. } => {
|
|
||||||
if args.len() > outs {
|
|
||||||
outs = args.len();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_ => (),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
outs as u16
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn __str__(&self) -> String {
|
pub fn __str__(&self) -> String {
|
||||||
self.__repr__()
|
self.__repr__()
|
||||||
}
|
}
|
||||||
|
|
@ -114,8 +101,10 @@ impl Code {
|
||||||
strings.extend(ins.get_all_strings());
|
strings.extend(ins.get_all_strings());
|
||||||
}
|
}
|
||||||
if let Some(names) = &self.parameter_names {
|
if let Some(names) = &self.parameter_names {
|
||||||
for name in names.iter().flatten() {
|
for name in names {
|
||||||
strings.insert(name.clone());
|
if let Some(name) = name {
|
||||||
|
strings.insert(name.clone());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
strings
|
strings
|
||||||
|
|
@ -167,55 +156,59 @@ impl Code {
|
||||||
handles
|
handles
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn __eq__(&self, other: &Self) -> bool {
|
||||||
|
self == other
|
||||||
|
}
|
||||||
|
|
||||||
/// Return all the labels used by instrutions in the code.
|
/// Return all the labels used by instrutions in the code.
|
||||||
pub fn get_referenced_label(&self) -> HashSet<String> {
|
pub fn get_referenced_label(&self) -> HashSet<String> {
|
||||||
let mut used_labels = HashSet::new();
|
let mut used_labels = HashSet::new();
|
||||||
for ins in &self.insns {
|
for ins in &self.insns {
|
||||||
match ins {
|
match ins {
|
||||||
Instruction::Goto { label } => {
|
Instruction::Goto(ins::Goto { label }) => {
|
||||||
used_labels.insert(label.clone());
|
used_labels.insert(label.clone());
|
||||||
}
|
}
|
||||||
Instruction::IfEq { label, .. } => {
|
Instruction::IfEq(ins::IfEq { label, .. }) => {
|
||||||
used_labels.insert(label.clone());
|
used_labels.insert(label.clone());
|
||||||
}
|
}
|
||||||
Instruction::IfNe { label, .. } => {
|
Instruction::IfNe(ins::IfNe { label, .. }) => {
|
||||||
used_labels.insert(label.clone());
|
used_labels.insert(label.clone());
|
||||||
}
|
}
|
||||||
Instruction::IfLt { label, .. } => {
|
Instruction::IfLt(ins::IfLt { label, .. }) => {
|
||||||
used_labels.insert(label.clone());
|
used_labels.insert(label.clone());
|
||||||
}
|
}
|
||||||
Instruction::IfGe { label, .. } => {
|
Instruction::IfGe(ins::IfGe { label, .. }) => {
|
||||||
used_labels.insert(label.clone());
|
used_labels.insert(label.clone());
|
||||||
}
|
}
|
||||||
Instruction::IfGt { label, .. } => {
|
Instruction::IfGt(ins::IfGt { label, .. }) => {
|
||||||
used_labels.insert(label.clone());
|
used_labels.insert(label.clone());
|
||||||
}
|
}
|
||||||
Instruction::IfLe { label, .. } => {
|
Instruction::IfLe(ins::IfLe { label, .. }) => {
|
||||||
used_labels.insert(label.clone());
|
used_labels.insert(label.clone());
|
||||||
}
|
}
|
||||||
Instruction::IfEqZ { label, .. } => {
|
Instruction::IfEqZ(ins::IfEqZ { label, .. }) => {
|
||||||
used_labels.insert(label.clone());
|
used_labels.insert(label.clone());
|
||||||
}
|
}
|
||||||
Instruction::IfNeZ { label, .. } => {
|
Instruction::IfNeZ(ins::IfNeZ { label, .. }) => {
|
||||||
used_labels.insert(label.clone());
|
used_labels.insert(label.clone());
|
||||||
}
|
}
|
||||||
Instruction::IfLtZ { label, .. } => {
|
Instruction::IfLtZ(ins::IfLtZ { label, .. }) => {
|
||||||
used_labels.insert(label.clone());
|
used_labels.insert(label.clone());
|
||||||
}
|
}
|
||||||
Instruction::IfGeZ { label, .. } => {
|
Instruction::IfGeZ(ins::IfGeZ { label, .. }) => {
|
||||||
used_labels.insert(label.clone());
|
used_labels.insert(label.clone());
|
||||||
}
|
}
|
||||||
Instruction::IfGtZ { label, .. } => {
|
Instruction::IfGtZ(ins::IfGtZ { label, .. }) => {
|
||||||
used_labels.insert(label.clone());
|
used_labels.insert(label.clone());
|
||||||
}
|
}
|
||||||
Instruction::IfLeZ { label, .. } => {
|
Instruction::IfLeZ(ins::IfLeZ { label, .. }) => {
|
||||||
used_labels.insert(label.clone());
|
used_labels.insert(label.clone());
|
||||||
}
|
}
|
||||||
Instruction::Try {
|
Instruction::Try(ins::Try {
|
||||||
end_label,
|
end_label,
|
||||||
handlers,
|
handlers,
|
||||||
default_handler,
|
default_handler,
|
||||||
} => {
|
}) => {
|
||||||
used_labels.insert(end_label.clone());
|
used_labels.insert(end_label.clone());
|
||||||
for (_, label) in handlers {
|
for (_, label) in handlers {
|
||||||
used_labels.insert(label.clone());
|
used_labels.insert(label.clone());
|
||||||
|
|
@ -224,7 +217,7 @@ impl Code {
|
||||||
used_labels.insert(label.clone());
|
used_labels.insert(label.clone());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Instruction::Switch { branches, .. } => {
|
Instruction::Switch(ins::Switch { branches, .. }) => {
|
||||||
for label in branches.values() {
|
for label in branches.values() {
|
||||||
used_labels.insert(label.clone());
|
used_labels.insert(label.clone());
|
||||||
}
|
}
|
||||||
|
|
@ -248,8 +241,8 @@ impl Code {
|
||||||
|
|
||||||
for ins in &self.insns {
|
for ins in &self.insns {
|
||||||
match ins {
|
match ins {
|
||||||
Instruction::Label { name } => {
|
Instruction::Label(ins::Label { name }) => {
|
||||||
if !used_labels.contains(name) {
|
if used_labels.get(name).is_none() {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
let new_label_id = if last_ins_was_a_label {
|
let new_label_id = if last_ins_was_a_label {
|
||||||
|
|
@ -267,8 +260,8 @@ impl Code {
|
||||||
}
|
}
|
||||||
|
|
||||||
for label in &used_labels {
|
for label in &used_labels {
|
||||||
if !new_labels.contains_key(label) {
|
if new_labels.get(label).is_none() {
|
||||||
debug!("{label} use but not in new_labels");
|
println!("{label} use but not in new_labels");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -276,191 +269,183 @@ impl Code {
|
||||||
last_ins_was_a_label = false;
|
last_ins_was_a_label = false;
|
||||||
for ins in self.insns.iter().cloned() {
|
for ins in self.insns.iter().cloned() {
|
||||||
match ins {
|
match ins {
|
||||||
Instruction::Goto { label } => {
|
Instruction::Goto(mut instr) => {
|
||||||
last_ins_was_a_label = false;
|
last_ins_was_a_label = false;
|
||||||
let label = new_labels
|
instr.label = new_labels
|
||||||
.get(&label)
|
.get(&instr.label)
|
||||||
.ok_or(anyhow!(
|
.ok_or(anyhow!(
|
||||||
"Internal error: {} not found in renamed label",
|
"Internal error: {} not found in renamed label",
|
||||||
label
|
instr.label
|
||||||
))?
|
))?
|
||||||
.clone();
|
.clone();
|
||||||
new_insns.push(Instruction::Goto { label });
|
new_insns.push(Instruction::Goto(instr));
|
||||||
}
|
}
|
||||||
Instruction::IfEq { label, a, b } => {
|
Instruction::IfEq(mut instr) => {
|
||||||
last_ins_was_a_label = false;
|
last_ins_was_a_label = false;
|
||||||
let label = new_labels
|
instr.label = new_labels
|
||||||
.get(&label)
|
.get(&instr.label)
|
||||||
.ok_or(anyhow!(
|
.ok_or(anyhow!(
|
||||||
"Internal error: {} not found in renamed label",
|
"Internal error: {} not found in renamed label",
|
||||||
label
|
instr.label
|
||||||
))?
|
))?
|
||||||
.clone();
|
.clone();
|
||||||
new_insns.push(Instruction::IfEq { label, a, b });
|
new_insns.push(Instruction::IfEq(instr));
|
||||||
}
|
}
|
||||||
Instruction::IfNe { label, a, b } => {
|
Instruction::IfNe(mut instr) => {
|
||||||
last_ins_was_a_label = false;
|
last_ins_was_a_label = false;
|
||||||
let label = new_labels
|
instr.label = new_labels
|
||||||
.get(&label)
|
.get(&instr.label)
|
||||||
.ok_or(anyhow!(
|
.ok_or(anyhow!(
|
||||||
"Internal error: {} not found in renamed label",
|
"Internal error: {} not found in renamed label",
|
||||||
label
|
instr.label
|
||||||
))?
|
))?
|
||||||
.clone();
|
.clone();
|
||||||
new_insns.push(Instruction::IfNe { label, a, b });
|
new_insns.push(Instruction::IfNe(instr));
|
||||||
}
|
}
|
||||||
Instruction::IfLt { label, a, b } => {
|
Instruction::IfLt(mut instr) => {
|
||||||
last_ins_was_a_label = false;
|
last_ins_was_a_label = false;
|
||||||
let label = new_labels
|
instr.label = new_labels
|
||||||
.get(&label)
|
.get(&instr.label)
|
||||||
.ok_or(anyhow!(
|
.ok_or(anyhow!(
|
||||||
"Internal error: {} not found in renamed label",
|
"Internal error: {} not found in renamed label",
|
||||||
label
|
instr.label
|
||||||
))?
|
))?
|
||||||
.clone();
|
.clone();
|
||||||
new_insns.push(Instruction::IfLt { label, a, b });
|
new_insns.push(Instruction::IfLt(instr));
|
||||||
}
|
}
|
||||||
Instruction::IfGe { label, a, b } => {
|
Instruction::IfGe(mut instr) => {
|
||||||
last_ins_was_a_label = false;
|
last_ins_was_a_label = false;
|
||||||
let label = new_labels
|
instr.label = new_labels
|
||||||
.get(&label)
|
.get(&instr.label)
|
||||||
.ok_or(anyhow!(
|
.ok_or(anyhow!(
|
||||||
"Internal error: {} not found in renamed label",
|
"Internal error: {} not found in renamed label",
|
||||||
label
|
instr.label
|
||||||
))?
|
))?
|
||||||
.clone();
|
.clone();
|
||||||
new_insns.push(Instruction::IfGe { label, a, b });
|
new_insns.push(Instruction::IfGe(instr));
|
||||||
}
|
}
|
||||||
Instruction::IfGt { label, a, b } => {
|
Instruction::IfGt(mut instr) => {
|
||||||
last_ins_was_a_label = false;
|
last_ins_was_a_label = false;
|
||||||
let label = new_labels
|
instr.label = new_labels
|
||||||
.get(&label)
|
.get(&instr.label)
|
||||||
.ok_or(anyhow!(
|
.ok_or(anyhow!(
|
||||||
"Internal error: {} not found in renamed label",
|
"Internal error: {} not found in renamed label",
|
||||||
label
|
instr.label
|
||||||
))?
|
))?
|
||||||
.clone();
|
.clone();
|
||||||
new_insns.push(Instruction::IfGt { label, a, b });
|
new_insns.push(Instruction::IfGt(instr));
|
||||||
}
|
}
|
||||||
Instruction::IfLe { label, a, b } => {
|
Instruction::IfLe(mut instr) => {
|
||||||
last_ins_was_a_label = false;
|
last_ins_was_a_label = false;
|
||||||
let label = new_labels
|
instr.label = new_labels
|
||||||
.get(&label)
|
.get(&instr.label)
|
||||||
.ok_or(anyhow!(
|
.ok_or(anyhow!(
|
||||||
"Internal error: {} not found in renamed label",
|
"Internal error: {} not found in renamed label",
|
||||||
label
|
instr.label
|
||||||
))?
|
))?
|
||||||
.clone();
|
.clone();
|
||||||
new_insns.push(Instruction::IfLe { label, a, b });
|
new_insns.push(Instruction::IfLe(instr));
|
||||||
}
|
}
|
||||||
Instruction::IfEqZ { label, a } => {
|
Instruction::IfEqZ(mut instr) => {
|
||||||
last_ins_was_a_label = false;
|
last_ins_was_a_label = false;
|
||||||
let label = new_labels
|
instr.label = new_labels
|
||||||
.get(&label)
|
.get(&instr.label)
|
||||||
.ok_or(anyhow!(
|
.ok_or(anyhow!(
|
||||||
"Internal error: {} not found in renamed label",
|
"Internal error: {} not found in renamed label",
|
||||||
label
|
instr.label
|
||||||
))?
|
))?
|
||||||
.clone();
|
.clone();
|
||||||
new_insns.push(Instruction::IfEqZ { label, a });
|
new_insns.push(Instruction::IfEqZ(instr));
|
||||||
}
|
}
|
||||||
Instruction::IfNeZ { label, a } => {
|
Instruction::IfNeZ(mut instr) => {
|
||||||
last_ins_was_a_label = false;
|
last_ins_was_a_label = false;
|
||||||
let label = new_labels
|
instr.label = new_labels
|
||||||
.get(&label)
|
.get(&instr.label)
|
||||||
.ok_or(anyhow!(
|
.ok_or(anyhow!(
|
||||||
"Internal error: {} not found in renamed label",
|
"Internal error: {} not found in renamed label",
|
||||||
label
|
instr.label
|
||||||
))?
|
))?
|
||||||
.clone();
|
.clone();
|
||||||
new_insns.push(Instruction::IfNeZ { label, a });
|
new_insns.push(Instruction::IfNeZ(instr));
|
||||||
}
|
}
|
||||||
Instruction::IfLtZ { label, a } => {
|
Instruction::IfLtZ(mut instr) => {
|
||||||
last_ins_was_a_label = false;
|
last_ins_was_a_label = false;
|
||||||
let label = new_labels
|
instr.label = new_labels
|
||||||
.get(&label)
|
.get(&instr.label)
|
||||||
.ok_or(anyhow!(
|
.ok_or(anyhow!(
|
||||||
"Internal error: {} not found in renamed label",
|
"Internal error: {} not found in renamed label",
|
||||||
label
|
instr.label
|
||||||
))?
|
))?
|
||||||
.clone();
|
.clone();
|
||||||
new_insns.push(Instruction::IfLtZ { label, a });
|
new_insns.push(Instruction::IfLtZ(instr));
|
||||||
}
|
}
|
||||||
Instruction::IfGeZ { label, a } => {
|
Instruction::IfGeZ(mut instr) => {
|
||||||
last_ins_was_a_label = false;
|
last_ins_was_a_label = false;
|
||||||
let label = new_labels
|
instr.label = new_labels
|
||||||
.get(&label)
|
.get(&instr.label)
|
||||||
.ok_or(anyhow!(
|
.ok_or(anyhow!(
|
||||||
"Internal error: {} not found in renamed label",
|
"Internal error: {} not found in renamed label",
|
||||||
label
|
instr.label
|
||||||
))?
|
))?
|
||||||
.clone();
|
.clone();
|
||||||
new_insns.push(Instruction::IfGeZ { label, a });
|
new_insns.push(Instruction::IfGeZ(instr));
|
||||||
}
|
}
|
||||||
Instruction::IfGtZ { label, a } => {
|
Instruction::IfGtZ(mut instr) => {
|
||||||
last_ins_was_a_label = false;
|
last_ins_was_a_label = false;
|
||||||
let label = new_labels
|
instr.label = new_labels
|
||||||
.get(&label)
|
.get(&instr.label)
|
||||||
.ok_or(anyhow!(
|
.ok_or(anyhow!(
|
||||||
"Internal error: {} not found in renamed label",
|
"Internal error: {} not found in renamed label",
|
||||||
label
|
instr.label
|
||||||
))?
|
))?
|
||||||
.clone();
|
.clone();
|
||||||
new_insns.push(Instruction::IfGtZ { label, a });
|
new_insns.push(Instruction::IfGtZ(instr));
|
||||||
}
|
}
|
||||||
Instruction::IfLeZ { label, a } => {
|
Instruction::IfLeZ(mut instr) => {
|
||||||
last_ins_was_a_label = false;
|
last_ins_was_a_label = false;
|
||||||
let label = new_labels
|
instr.label = new_labels
|
||||||
.get(&label)
|
.get(&instr.label)
|
||||||
.ok_or(anyhow!(
|
.ok_or(anyhow!(
|
||||||
"Internal error: {} not found in renamed label",
|
"Internal error: {} not found in renamed label",
|
||||||
label
|
instr.label
|
||||||
))?
|
))?
|
||||||
.clone();
|
.clone();
|
||||||
new_insns.push(Instruction::IfLeZ { label, a });
|
new_insns.push(Instruction::IfLeZ(instr));
|
||||||
}
|
}
|
||||||
Instruction::Try {
|
Instruction::Try(mut instr) => {
|
||||||
end_label,
|
|
||||||
mut handlers,
|
|
||||||
default_handler,
|
|
||||||
} => {
|
|
||||||
last_ins_was_a_label = false;
|
last_ins_was_a_label = false;
|
||||||
let end_label = new_labels
|
instr.end_label = new_labels
|
||||||
.get(&end_label)
|
.get(&instr.end_label)
|
||||||
.ok_or(anyhow!(
|
.ok_or(anyhow!(
|
||||||
"Internal error: {} not found in renamed label",
|
"Internal error: {} not found in renamed label",
|
||||||
end_label
|
instr.end_label
|
||||||
))?
|
))?
|
||||||
.clone();
|
.clone();
|
||||||
for handler in &mut handlers {
|
for i in 0..instr.handlers.len() {
|
||||||
handler.1 = new_labels
|
instr.handlers[i].1 = new_labels
|
||||||
.get(&handler.1)
|
.get(&instr.handlers[i].1)
|
||||||
.ok_or(anyhow!(
|
.ok_or(anyhow!(
|
||||||
"Internal error: {} not found in renamed label",
|
"Internal error: {} not found in renamed label",
|
||||||
handler.1
|
instr.handlers[i].1
|
||||||
))?
|
))?
|
||||||
.clone();
|
.clone();
|
||||||
}
|
}
|
||||||
let default_handler = default_handler
|
if let Some(label) = instr.default_handler {
|
||||||
.map(|label| {
|
instr.default_handler = Some(
|
||||||
new_labels
|
new_labels
|
||||||
.get(&label)
|
.get(&label)
|
||||||
.ok_or(anyhow!(
|
.ok_or(anyhow!(
|
||||||
"Internal error: {} not found in renamed label",
|
"Internal error: {} not found in renamed label",
|
||||||
label
|
label
|
||||||
))
|
))?
|
||||||
.cloned()
|
.clone(),
|
||||||
})
|
);
|
||||||
.transpose()?;
|
}
|
||||||
new_insns.push(Instruction::Try {
|
new_insns.push(Instruction::Try(instr));
|
||||||
end_label,
|
|
||||||
handlers,
|
|
||||||
default_handler,
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
Instruction::Switch { mut branches, reg } => {
|
Instruction::Switch(mut instr) => {
|
||||||
last_ins_was_a_label = false;
|
last_ins_was_a_label = false;
|
||||||
for label in branches.values_mut() {
|
for label in instr.branches.values_mut() {
|
||||||
*label = new_labels
|
*label = new_labels
|
||||||
.get(label)
|
.get(label)
|
||||||
.ok_or(anyhow!(
|
.ok_or(anyhow!(
|
||||||
|
|
@ -469,15 +454,15 @@ impl Code {
|
||||||
))?
|
))?
|
||||||
.clone();
|
.clone();
|
||||||
}
|
}
|
||||||
new_insns.push(Instruction::Switch { branches, reg });
|
new_insns.push(Instruction::Switch(instr));
|
||||||
}
|
}
|
||||||
Instruction::Label { name } => {
|
Instruction::Label(ins::Label { name }) => {
|
||||||
if !used_labels.contains(&name) {
|
if used_labels.get(&name).is_none() {
|
||||||
//println!("{name} not used");
|
//println!("{name} not used");
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if !last_ins_was_a_label {
|
if !last_ins_was_a_label {
|
||||||
new_insns.push(Instruction::Label {
|
new_insns.push(Instruction::Label(ins::Label {
|
||||||
name: new_labels
|
name: new_labels
|
||||||
.get(&name)
|
.get(&name)
|
||||||
.ok_or(anyhow!(
|
.ok_or(anyhow!(
|
||||||
|
|
@ -485,11 +470,11 @@ impl Code {
|
||||||
name
|
name
|
||||||
))?
|
))?
|
||||||
.clone(),
|
.clone(),
|
||||||
});
|
}));
|
||||||
}
|
}
|
||||||
last_ins_was_a_label = true;
|
last_ins_was_a_label = true;
|
||||||
}
|
}
|
||||||
Instruction::Nop {} => (),
|
Instruction::Nop(_) => (),
|
||||||
instr => {
|
instr => {
|
||||||
last_ins_was_a_label = false;
|
last_ins_was_a_label = false;
|
||||||
new_insns.push(instr);
|
new_insns.push(instr);
|
||||||
|
|
@ -502,38 +487,3 @@ impl Code {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<V: Visitor> Visitable<V> for Code {
|
|
||||||
fn default_visit(&self, v: &mut V) -> Result<()> {
|
|
||||||
for ins in &self.insns {
|
|
||||||
v.visit_instruction(ins)?;
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl<V: VisitorMut> VisitableMut<V> for Code {
|
|
||||||
fn default_visit_mut(self, v: &mut V) -> Result<Self> {
|
|
||||||
let parameter_names = if let Some(parameter_names) = self.parameter_names {
|
|
||||||
let mut new_param = vec![];
|
|
||||||
for param in parameter_names {
|
|
||||||
if let Some(param) = param {
|
|
||||||
new_param.push(Some(v.visit_string(param)?));
|
|
||||||
} else {
|
|
||||||
new_param.push(None);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Some(new_param)
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
};
|
|
||||||
Ok(Self {
|
|
||||||
parameter_names,
|
|
||||||
insns: self
|
|
||||||
.insns
|
|
||||||
.into_iter()
|
|
||||||
.map(|ins| v.visit_instruction(ins))
|
|
||||||
.collect::<Result<_>>()?,
|
|
||||||
..self
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -1,350 +0,0 @@
|
||||||
//! The Control Flow Graph for a method.
|
|
||||||
|
|
||||||
use crate::{Instruction, Method, Result};
|
|
||||||
use anyhow::Context;
|
|
||||||
use std::collections::HashMap;
|
|
||||||
|
|
||||||
const EMPTY_INSNS_SLICE: &[Instruction] = &[];
|
|
||||||
|
|
||||||
/// A basic block of code of a method.
|
|
||||||
#[derive(Debug, PartialEq)]
|
|
||||||
pub struct MethodCFGNode<'a> {
|
|
||||||
/// Code represented by the block
|
|
||||||
pub code_block: &'a [Instruction],
|
|
||||||
/// Labels at the begining of the node if they exists
|
|
||||||
pub labels: Vec<String>,
|
|
||||||
/// Indices in CodeGraph.nodes of the next nodes
|
|
||||||
pub next_nodes: Vec<usize>,
|
|
||||||
/// Indices in CodeGraph.nodes of the previous nodes
|
|
||||||
pub prev_nodes: Vec<usize>,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// The CFG for a method, with potentially additionnal informations.
|
|
||||||
#[derive(Debug, PartialEq)]
|
|
||||||
pub struct MethodCFG<'a> {
|
|
||||||
pub method: &'a Method,
|
|
||||||
pub nodes: Vec<MethodCFGNode<'a>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Method {
|
|
||||||
pub fn get_cfg(&self) -> Result<MethodCFG> {
|
|
||||||
MethodCFG::new(self)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a> MethodCFG<'a> {
|
|
||||||
pub fn new(method: &'a Method) -> Result<Self> {
|
|
||||||
let insns: &'a [Instruction] = if let Some(code) = method.code.as_ref() {
|
|
||||||
&code.insns
|
|
||||||
} else {
|
|
||||||
EMPTY_INSNS_SLICE
|
|
||||||
};
|
|
||||||
|
|
||||||
let mut nodes = vec![MethodCFGNode {
|
|
||||||
code_block: &insns[0..0],
|
|
||||||
labels: vec![],
|
|
||||||
next_nodes: vec![],
|
|
||||||
prev_nodes: vec![],
|
|
||||||
}];
|
|
||||||
let mut nodes_next_label = vec![vec![]];
|
|
||||||
let nb_insns = insns.len();
|
|
||||||
if nb_insns != 0 {
|
|
||||||
nodes[0].next_nodes.push(1);
|
|
||||||
}
|
|
||||||
let mut start_last_block = 0;
|
|
||||||
let mut last_labels = vec![];
|
|
||||||
let mut block_started = false;
|
|
||||||
let mut try_block: Vec<(String, Vec<String>)> = vec![];
|
|
||||||
for (i, ins) in insns.iter().enumerate() {
|
|
||||||
match ins {
|
|
||||||
// TODO: handle error better: list ins that can throw exceptions better
|
|
||||||
Instruction::Throw { .. }
|
|
||||||
| Instruction::InvokeVirtual { .. }
|
|
||||||
| Instruction::InvokeSuper { .. }
|
|
||||||
| Instruction::InvokeDirect { .. }
|
|
||||||
| Instruction::InvokeDirect { .. }
|
|
||||||
| Instruction::InvokeInterface { .. }
|
|
||||||
| Instruction::InvokePolymorphic { .. }
|
|
||||||
| Instruction::InvokeCustom { .. }
|
|
||||||
if !try_block.is_empty() =>
|
|
||||||
{
|
|
||||||
nodes_next_label.push(try_block.last().unwrap().1.clone());
|
|
||||||
let next_nodes =
|
|
||||||
if i + 1 < nb_insns && !matches!(ins, Instruction::Throw { .. }) {
|
|
||||||
vec![nodes.len() + 1] // If no exception, continue to next ins
|
|
||||||
} else {
|
|
||||||
vec![]
|
|
||||||
};
|
|
||||||
nodes.push(MethodCFGNode {
|
|
||||||
code_block: &insns[start_last_block..i + 1],
|
|
||||||
labels: last_labels,
|
|
||||||
next_nodes,
|
|
||||||
prev_nodes: vec![],
|
|
||||||
});
|
|
||||||
start_last_block = i + 1;
|
|
||||||
last_labels = vec![];
|
|
||||||
block_started = false;
|
|
||||||
}
|
|
||||||
Instruction::Goto { label } => {
|
|
||||||
nodes_next_label.push(vec![label.clone()]);
|
|
||||||
nodes.push(MethodCFGNode {
|
|
||||||
code_block: &insns[start_last_block..i + 1],
|
|
||||||
labels: last_labels,
|
|
||||||
next_nodes: vec![], // Do not continue the execution at next ins
|
|
||||||
prev_nodes: vec![],
|
|
||||||
});
|
|
||||||
start_last_block = i + 1;
|
|
||||||
last_labels = vec![];
|
|
||||||
block_started = false;
|
|
||||||
}
|
|
||||||
Instruction::Switch { branches, .. } => {
|
|
||||||
nodes_next_label.push(branches.values().cloned().collect());
|
|
||||||
let next_nodes = if i + 1 < nb_insns {
|
|
||||||
vec![nodes.len() + 1] // If no branches match, continue execution
|
|
||||||
} else {
|
|
||||||
vec![]
|
|
||||||
};
|
|
||||||
nodes.push(MethodCFGNode {
|
|
||||||
code_block: &insns[start_last_block..i + 1],
|
|
||||||
labels: last_labels,
|
|
||||||
next_nodes,
|
|
||||||
prev_nodes: vec![],
|
|
||||||
});
|
|
||||||
start_last_block = i + 1;
|
|
||||||
last_labels = vec![];
|
|
||||||
block_started = false;
|
|
||||||
}
|
|
||||||
Instruction::IfEq { label, .. }
|
|
||||||
| Instruction::IfNe { label, .. }
|
|
||||||
| Instruction::IfLt { label, .. }
|
|
||||||
| Instruction::IfGe { label, .. }
|
|
||||||
| Instruction::IfGt { label, .. }
|
|
||||||
| Instruction::IfLe { label, .. }
|
|
||||||
| Instruction::IfEqZ { label, .. }
|
|
||||||
| Instruction::IfNeZ { label, .. }
|
|
||||||
| Instruction::IfLtZ { label, .. }
|
|
||||||
| Instruction::IfGeZ { label, .. }
|
|
||||||
| Instruction::IfGtZ { label, .. }
|
|
||||||
| Instruction::IfLeZ { label, .. } => {
|
|
||||||
nodes_next_label.push(vec![label.clone()]);
|
|
||||||
let next_nodes = if i + 1 < nb_insns {
|
|
||||||
vec![nodes.len() + 1] // depending on test, continue execution
|
|
||||||
} else {
|
|
||||||
vec![]
|
|
||||||
};
|
|
||||||
nodes.push(MethodCFGNode {
|
|
||||||
code_block: &insns[start_last_block..i + 1],
|
|
||||||
labels: last_labels,
|
|
||||||
next_nodes,
|
|
||||||
prev_nodes: vec![],
|
|
||||||
});
|
|
||||||
start_last_block = i + 1;
|
|
||||||
last_labels = vec![];
|
|
||||||
block_started = false;
|
|
||||||
}
|
|
||||||
Instruction::Try {
|
|
||||||
end_label,
|
|
||||||
handlers,
|
|
||||||
default_handler,
|
|
||||||
} => {
|
|
||||||
let mut branches: Vec<_> =
|
|
||||||
handlers.iter().map(|(_, label)| label.clone()).collect();
|
|
||||||
if let Some(default_handler) = default_handler.as_ref().cloned() {
|
|
||||||
branches.push(default_handler);
|
|
||||||
}
|
|
||||||
try_block.push((end_label.clone(), branches))
|
|
||||||
}
|
|
||||||
Instruction::Label { name } => {
|
|
||||||
if !block_started {
|
|
||||||
last_labels.push(name.clone());
|
|
||||||
} else {
|
|
||||||
nodes_next_label.push(vec![]);
|
|
||||||
nodes.push(MethodCFGNode {
|
|
||||||
code_block: &insns[start_last_block..i],
|
|
||||||
labels: last_labels,
|
|
||||||
next_nodes: vec![nodes.len() + 1],
|
|
||||||
prev_nodes: vec![],
|
|
||||||
});
|
|
||||||
start_last_block = i;
|
|
||||||
last_labels = vec![name.clone()];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Instruction::ReturnVoid {}
|
|
||||||
| Instruction::Return { .. }
|
|
||||||
| Instruction::ReturnWide { .. }
|
|
||||||
| Instruction::ReturnObject { .. }
|
|
||||||
| Instruction::Throw { .. } => {
|
|
||||||
nodes_next_label.push(vec![]);
|
|
||||||
nodes.push(MethodCFGNode {
|
|
||||||
code_block: &insns[start_last_block..i + 1],
|
|
||||||
labels: last_labels,
|
|
||||||
next_nodes: vec![], // Do not continue the execution at next ins
|
|
||||||
prev_nodes: vec![],
|
|
||||||
});
|
|
||||||
start_last_block = i + 1;
|
|
||||||
last_labels = vec![];
|
|
||||||
block_started = false;
|
|
||||||
}
|
|
||||||
_ => {
|
|
||||||
if !ins.is_pseudo_ins() {
|
|
||||||
block_started = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if start_last_block != nb_insns {
|
|
||||||
nodes_next_label.push(vec![]);
|
|
||||||
nodes.push(MethodCFGNode {
|
|
||||||
code_block: &insns[start_last_block..nb_insns],
|
|
||||||
labels: last_labels,
|
|
||||||
next_nodes: vec![],
|
|
||||||
prev_nodes: vec![],
|
|
||||||
});
|
|
||||||
}
|
|
||||||
let label_to_node: HashMap<String, usize> = nodes
|
|
||||||
.iter()
|
|
||||||
.enumerate()
|
|
||||||
.flat_map(|(i, node)| node.labels.clone().into_iter().map(move |lab| (lab, i)))
|
|
||||||
.collect();
|
|
||||||
for (node, labels) in nodes.iter_mut().zip(nodes_next_label) {
|
|
||||||
for label in labels {
|
|
||||||
node.next_nodes
|
|
||||||
.push(*label_to_node.get(&label).with_context(|| {
|
|
||||||
format!("found jumb to label '{}' but label not found", label)
|
|
||||||
})?);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for i in 0..nodes.len() {
|
|
||||||
let next_nodes = nodes[i].next_nodes.clone();
|
|
||||||
for j in &next_nodes {
|
|
||||||
nodes[*j].prev_nodes.push(i);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Ok(Self { method, nodes })
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Serialize the graph to dot format.
|
|
||||||
pub fn to_dot(&self, add_reg_ty: bool) -> String {
|
|
||||||
let mut dot_string: String = "digraph {\n".into();
|
|
||||||
dot_string += "overlap=false;\n";
|
|
||||||
dot_string += &self.to_dot_subgraph();
|
|
||||||
dot_string += "\n";
|
|
||||||
if add_reg_ty {
|
|
||||||
dot_string += &self.reg_types_dot();
|
|
||||||
}
|
|
||||||
dot_string += "}";
|
|
||||||
dot_string
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Compute a sanitized version of the method name
|
|
||||||
pub(crate) fn dot_sanitized_method_dscr(&self) -> String {
|
|
||||||
self.method
|
|
||||||
.descriptor
|
|
||||||
.__str__()
|
|
||||||
.replace("/", "_")
|
|
||||||
.replace(";", "")
|
|
||||||
.replace(">", "")
|
|
||||||
.replace("-", "_")
|
|
||||||
.replace("(", "_")
|
|
||||||
.replace(")", "_")
|
|
||||||
.replace("[", "T")
|
|
||||||
}
|
|
||||||
|
|
||||||
// method call: cluster_{mth}:node_{i}:i{j}:e -> cluster_{mth2}:n ?
|
|
||||||
|
|
||||||
/// Serialize the graph to dot format.
|
|
||||||
pub fn to_dot_subgraph(&self) -> String {
|
|
||||||
let mut dot_string = format!(
|
|
||||||
"subgraph \"cluster_{}\" {{\n",
|
|
||||||
self.dot_sanitized_method_dscr()
|
|
||||||
);
|
|
||||||
dot_string += " style=\"dashed\";\n";
|
|
||||||
dot_string += " color=\"black\";\n";
|
|
||||||
dot_string += &format!(" label=\"{}\";\n", self.method.descriptor.__str__());
|
|
||||||
for (i, node) in self.nodes.iter().enumerate() {
|
|
||||||
let block_name = if i == 0 {
|
|
||||||
"ENTRY".into()
|
|
||||||
} else if !node.labels.is_empty() {
|
|
||||||
format!(
|
|
||||||
"block '{}'",
|
|
||||||
node.labels[0]
|
|
||||||
.replace("&", "&")
|
|
||||||
.replace(">", ">")
|
|
||||||
.replace("<", "<")
|
|
||||||
.replace("\"", """)
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
format!("block {i}")
|
|
||||||
};
|
|
||||||
let label = if node.code_block.is_empty() {
|
|
||||||
//format!("{{\\< {block_name} \\>}}")
|
|
||||||
format!("<TABLE BORDER=\"0\" CELLBORDER=\"1\" CELLSPACING=\"0\"><TR><TD><B> {block_name} </B></TD></TR></TABLE>")
|
|
||||||
} else {
|
|
||||||
/*
|
|
||||||
let mut label = format!("{{\\< {block_name} \\>:\\l\\\n");
|
|
||||||
for (i, ins) in node.code_block.iter().enumerate() {
|
|
||||||
label += &format!("|<i{i}>");
|
|
||||||
label += ins
|
|
||||||
.__str__()
|
|
||||||
.replace(" ", "\\ ")
|
|
||||||
.replace(">", "\\>")
|
|
||||||
.replace("<", "\\<")
|
|
||||||
.replace("\"", "\\\"")
|
|
||||||
.replace("{", "\\{")
|
|
||||||
.replace("}", "\\}")
|
|
||||||
//.replace("[", "\\[")
|
|
||||||
.as_str();
|
|
||||||
label += "\\l\\\n";
|
|
||||||
}
|
|
||||||
label += "}";
|
|
||||||
*/
|
|
||||||
let mut label = format!("<TABLE BORDER=\"0\" CELLBORDER=\"1\" CELLSPACING=\"0\">\n <TR><TD PORT=\"blkname\"><B> {block_name} </B></TD></TR>\n");
|
|
||||||
for (i, ins) in node.code_block.iter().enumerate() {
|
|
||||||
label += &format!(
|
|
||||||
" <TR><TD PORT=\"i{i}\">{}</TD></TR>\n",
|
|
||||||
ins.__str__()
|
|
||||||
//.replace(" ", "\\ ")
|
|
||||||
.replace("&", "&")
|
|
||||||
.replace(">", ">")
|
|
||||||
.replace("<", "<")
|
|
||||||
.replace("\"", """)
|
|
||||||
//.replace("{", "\\{")
|
|
||||||
//.replace("}", "\\}")
|
|
||||||
//.replace("[", "\\[")
|
|
||||||
.as_str()
|
|
||||||
);
|
|
||||||
}
|
|
||||||
label += " </TABLE>";
|
|
||||||
label
|
|
||||||
};
|
|
||||||
dot_string += &format!(
|
|
||||||
//" node_{i} [shape=record,style=filled,fillcolor=lightgrey,label=\"{label}\"];\n\n"
|
|
||||||
" node_{i} [shape=plaintext,style=filled,fillcolor=lightgrey,label=<{label}>];\n\n"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
//dot_string += " node_end [shape=record,style=filled,fillcolor=lightgrey,label=\"{\\< EXIT \\>}\"];\n\n";
|
|
||||||
dot_string += " node_end [shape=plaintext,style=filled,fillcolor=lightgrey,label=<<TABLE BORDER=\"0\" CELLBORDER=\"1\" CELLSPACING=\"0\"><TR><TD><B> EXIT </B></TD></TR></TABLE>>];\n\n";
|
|
||||||
|
|
||||||
for (i, node) in self.nodes.iter().enumerate() {
|
|
||||||
for j in &node.next_nodes {
|
|
||||||
if *j == i + 1 {
|
|
||||||
dot_string += &format!(
|
|
||||||
" node_{i}:s -> node_{j}:n [style=\"solid,bold\",color=black,weight=100,constraint=true];\n"
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
dot_string += &format!(
|
|
||||||
" node_{i}:s -> node_{j}:n [style=\"solid,bold\",color=black,weight=10,constraint=true];\n"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if node.next_nodes.is_empty() {
|
|
||||||
dot_string += &format!(
|
|
||||||
" node_{i}:s -> node_end:n [style=\"solid,bold\",color=black,weight=10,constraint=true];\n"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
dot_string += "}\n";
|
|
||||||
dot_string
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,8 +0,0 @@
|
||||||
//! Module for more advanced code analysis.
|
|
||||||
//!
|
|
||||||
//! This is module is quite experimental but can be usefull.
|
|
||||||
|
|
||||||
pub mod method_cfg;
|
|
||||||
pub mod register_type;
|
|
||||||
pub use method_cfg::*;
|
|
||||||
pub use register_type::RegType;
|
|
||||||
|
|
@ -1,555 +0,0 @@
|
||||||
//! Compute the register types at each label of a method.
|
|
||||||
|
|
||||||
use super::{MethodCFG, MethodCFGNode};
|
|
||||||
use crate::Instruction;
|
|
||||||
use std::collections::HashMap;
|
|
||||||
|
|
||||||
/// The different possible types of a register
|
|
||||||
#[derive(Debug, PartialEq, Clone, Copy)]
|
|
||||||
pub enum RegType {
|
|
||||||
/// The register content is not yet defined
|
|
||||||
Undefined,
|
|
||||||
/// The register contains a java object. It can be a classical object
|
|
||||||
/// an array, a type, etc
|
|
||||||
Object,
|
|
||||||
/// The register contains a 32 bit scalar
|
|
||||||
SimpleScalar,
|
|
||||||
/// The register contains the first 32 bits of a wide register. If not
|
|
||||||
/// followed by a `SecondWideScalar`, it should be considered as a `SimpleScalar`?
|
|
||||||
FirstWideScalar,
|
|
||||||
/// The register contains the last 32 bits of a wide register. If not
|
|
||||||
/// preceded by a `FirstWideScalar`, it should be considered as a `SimpleScalar`?
|
|
||||||
SecondWideScalar,
|
|
||||||
/// The register can be either a scalar or an object.
|
|
||||||
Any,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl RegType {
|
|
||||||
pub fn to_str(&self) -> &'static str {
|
|
||||||
match self {
|
|
||||||
RegType::Undefined => "undef",
|
|
||||||
RegType::Object => "object",
|
|
||||||
RegType::SimpleScalar => "scalar",
|
|
||||||
RegType::FirstWideScalar => "wide1",
|
|
||||||
RegType::SecondWideScalar => "wide2",
|
|
||||||
RegType::Any => "any",
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl MethodCFG<'_> {
|
|
||||||
pub fn get_reg_types(&self) -> HashMap<String, Vec<RegType>> {
|
|
||||||
let code = if let Some(code) = self.method.code.as_ref() {
|
|
||||||
code
|
|
||||||
} else {
|
|
||||||
return HashMap::new();
|
|
||||||
};
|
|
||||||
let nb_reg = code.registers_size as usize;
|
|
||||||
let mut end_block_reg_tys: Vec<_> = self
|
|
||||||
.nodes
|
|
||||||
.iter()
|
|
||||||
.map(|_| vec![RegType::Undefined; nb_reg])
|
|
||||||
.collect();
|
|
||||||
if end_block_reg_tys.is_empty() {
|
|
||||||
return HashMap::new();
|
|
||||||
}
|
|
||||||
// Initialize the entry block from function signature:
|
|
||||||
let mut i = (code.registers_size - code.ins_size(self.method)) as usize;
|
|
||||||
if !self.method.is_static {
|
|
||||||
end_block_reg_tys[0][i] = RegType::Object; // 'this'
|
|
||||||
i += 1;
|
|
||||||
}
|
|
||||||
for arg in &self.method.descriptor.proto.get_parameters() {
|
|
||||||
if arg.is_class() || arg.is_array() {
|
|
||||||
end_block_reg_tys[0][i] = RegType::Object;
|
|
||||||
i += 1;
|
|
||||||
} else if arg.is_long() || arg.is_double() {
|
|
||||||
end_block_reg_tys[0][i] = RegType::FirstWideScalar;
|
|
||||||
i += 1;
|
|
||||||
end_block_reg_tys[0][i] = RegType::SecondWideScalar;
|
|
||||||
i += 1;
|
|
||||||
} else {
|
|
||||||
end_block_reg_tys[0][i] = RegType::SimpleScalar;
|
|
||||||
i += 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
let mut changed = true;
|
|
||||||
while changed {
|
|
||||||
// The first node is empty and depend on the function signature, it must not change
|
|
||||||
let mut new_end_block_reg_tys = vec![end_block_reg_tys[0].clone()];
|
|
||||||
for node in self.nodes.iter().skip(1) {
|
|
||||||
new_end_block_reg_tys.push(transform_reg_ty(
|
|
||||||
&merge_input(node, nb_reg, &end_block_reg_tys),
|
|
||||||
node,
|
|
||||||
))
|
|
||||||
}
|
|
||||||
changed = end_block_reg_tys != new_end_block_reg_tys;
|
|
||||||
end_block_reg_tys = new_end_block_reg_tys;
|
|
||||||
}
|
|
||||||
let start_block_reg_tys = &self
|
|
||||||
.nodes
|
|
||||||
.iter()
|
|
||||||
.map(|node| merge_input(node, nb_reg, &end_block_reg_tys))
|
|
||||||
.collect::<Vec<_>>();
|
|
||||||
self.nodes
|
|
||||||
.iter()
|
|
||||||
.enumerate()
|
|
||||||
.flat_map(|(i, node)| {
|
|
||||||
node.labels
|
|
||||||
.iter()
|
|
||||||
.map(move |label| (label.clone(), start_block_reg_tys[i].clone()))
|
|
||||||
})
|
|
||||||
.collect()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn reg_types_dot(&self) -> String {
|
|
||||||
let types = self.get_reg_types();
|
|
||||||
let mut dot_string = format!(
|
|
||||||
"subgraph \"cluster_reg_types_{}\" {{\n",
|
|
||||||
self.dot_sanitized_method_dscr()
|
|
||||||
);
|
|
||||||
dot_string += " style=\"dashed\";\n";
|
|
||||||
dot_string += " color=\"black\";\n";
|
|
||||||
dot_string += " rankdir=\"TB\";\n";
|
|
||||||
dot_string += &format!(
|
|
||||||
" label=\"Register Types for {}\";\n",
|
|
||||||
self.method.descriptor.__str__()
|
|
||||||
);
|
|
||||||
|
|
||||||
let mut labels: Vec<_> = types.keys().collect();
|
|
||||||
labels.sort(); // Order more or less by addresses, good enought
|
|
||||||
let mut prev = None;
|
|
||||||
for label in labels.into_iter() {
|
|
||||||
let regs = types.get(label).unwrap();
|
|
||||||
let mut node_label = String::new();
|
|
||||||
node_label += "<TABLE BORDER=\"0\" CELLBORDER=\"1\" CELLSPACING=\"0\">\n";
|
|
||||||
node_label += &format!(
|
|
||||||
" <TR><TD COLSPAN=\"{}\"><B>register types at {label}</B></TD></TR>\n <TR>", regs.len(),
|
|
||||||
);
|
|
||||||
for i in 0..regs.len() {
|
|
||||||
node_label += &format!(" <TD>{i}</TD>\n");
|
|
||||||
}
|
|
||||||
node_label += " </TR><TR>\n";
|
|
||||||
for reg in regs {
|
|
||||||
//node_label += "|";
|
|
||||||
//node_label += reg.to_str();
|
|
||||||
node_label += &format!(" <TD>{}</TD>\n", reg.to_str());
|
|
||||||
}
|
|
||||||
//node_label += "|";
|
|
||||||
node_label += " </TR></TABLE>";
|
|
||||||
dot_string += &format!(
|
|
||||||
//" node_{label} [shape=record,style=filled,fillcolor=lightgrey,label=\"{node_label}\"];\n"
|
|
||||||
" node_{label} [shape=plaintext,style=filled,fillcolor=lightgrey,label=<{node_label}>];\n"
|
|
||||||
);
|
|
||||||
if let Some(prev) = prev {
|
|
||||||
// Add invisible edge to ensure the nodes are verticals
|
|
||||||
dot_string += &format!(" {prev}:s -> node_{label}:n [style=\"invis\"];\n");
|
|
||||||
}
|
|
||||||
prev = Some(format!("node_{label}"));
|
|
||||||
}
|
|
||||||
|
|
||||||
dot_string += "}\n";
|
|
||||||
dot_string += "\n";
|
|
||||||
for (i, node) in self.nodes.iter().enumerate() {
|
|
||||||
for (j, ins) in node.code_block.iter().enumerate() {
|
|
||||||
if let Instruction::Label { name } = ins {
|
|
||||||
let mid = self.dot_sanitized_method_dscr();
|
|
||||||
dot_string += &format!(
|
|
||||||
" node_{i}:i{j}:e -> node_{name} \
|
|
||||||
[ltail=cluster_{mid},lhead=cluster_reg_types_{mid},style=\"solid,bold\",color=grey,weight=10,constraint=true];\n",
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
dot_string
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn merge_input(node: &MethodCFGNode, nb_reg: usize, inputs: &[Vec<RegType>]) -> Vec<RegType> {
|
|
||||||
use RegType::*;
|
|
||||||
let mut reg_tys = vec![Undefined; nb_reg];
|
|
||||||
for i in &node.prev_nodes {
|
|
||||||
for (r, ty) in inputs[*i].iter().enumerate() {
|
|
||||||
reg_tys[r] = match (reg_tys[r], ty) {
|
|
||||||
(Undefined, _) => *ty,
|
|
||||||
(_, Undefined) => reg_tys[r],
|
|
||||||
(_, Any) => Any,
|
|
||||||
(Any, _) => Any,
|
|
||||||
|
|
||||||
(Object, Object) => Object,
|
|
||||||
(Object, SimpleScalar) => Any,
|
|
||||||
(Object, FirstWideScalar) => Any,
|
|
||||||
(Object, SecondWideScalar) => Any,
|
|
||||||
|
|
||||||
(SimpleScalar, Object) => Any,
|
|
||||||
(SimpleScalar, SimpleScalar) => SimpleScalar,
|
|
||||||
(SimpleScalar, FirstWideScalar) => SimpleScalar,
|
|
||||||
(SimpleScalar, SecondWideScalar) => SimpleScalar,
|
|
||||||
|
|
||||||
(FirstWideScalar, Object) => Any,
|
|
||||||
(FirstWideScalar, SimpleScalar) => SimpleScalar,
|
|
||||||
(FirstWideScalar, FirstWideScalar) => FirstWideScalar,
|
|
||||||
(FirstWideScalar, SecondWideScalar) => SimpleScalar,
|
|
||||||
|
|
||||||
(SecondWideScalar, Object) => Any,
|
|
||||||
(SecondWideScalar, SimpleScalar) => SimpleScalar,
|
|
||||||
(SecondWideScalar, FirstWideScalar) => SimpleScalar,
|
|
||||||
(SecondWideScalar, SecondWideScalar) => SecondWideScalar,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
reg_tys
|
|
||||||
}
|
|
||||||
|
|
||||||
fn transform_reg_ty(input_types: &[RegType], cfg: &MethodCFGNode) -> Vec<RegType> {
|
|
||||||
use Instruction::*;
|
|
||||||
use RegType::*;
|
|
||||||
let mut types = input_types.to_vec();
|
|
||||||
for ins in cfg.code_block {
|
|
||||||
match ins {
|
|
||||||
Move { to, .. } => types[*to as usize] = SimpleScalar, // E: mism
|
|
||||||
MoveWide { to, .. } => {
|
|
||||||
types[*to as usize] = FirstWideScalar;
|
|
||||||
types[*to as usize + 1] = SecondWideScalar;
|
|
||||||
}
|
|
||||||
MoveObject { to, .. } => types[*to as usize] = Object,
|
|
||||||
MoveResult { to: reg } | Return { reg } | Const { reg, .. } | Switch { reg, .. } => {
|
|
||||||
types[*reg as usize] = SimpleScalar
|
|
||||||
}
|
|
||||||
MoveResultWide { to: reg } | ReturnWide { reg } | ConstWide { reg, .. } => {
|
|
||||||
types[*reg as usize] = FirstWideScalar;
|
|
||||||
types[*reg as usize + 1] = SecondWideScalar;
|
|
||||||
}
|
|
||||||
MoveResultObject { to: reg }
|
|
||||||
| MoveException { to: reg }
|
|
||||||
| ReturnObject { reg }
|
|
||||||
| ConstString { reg, .. }
|
|
||||||
| ConstClass { reg, .. }
|
|
||||||
| NewInstance { reg, .. }
|
|
||||||
| Throw { reg }
|
|
||||||
| ConstMethodHandle { to: reg, .. }
|
|
||||||
| ConstMethodType { to: reg, .. } => types[*reg as usize] = Object,
|
|
||||||
InstanceOf { dest, obj: _, .. } => {
|
|
||||||
// types[*obj as usize] = Object; not sure about this one
|
|
||||||
types[*dest as usize] = SimpleScalar;
|
|
||||||
}
|
|
||||||
ArrayLength { dest, arr } => {
|
|
||||||
types[*arr as usize] = Object;
|
|
||||||
types[*dest as usize] = SimpleScalar;
|
|
||||||
}
|
|
||||||
NewArray { reg, size_reg, .. } => {
|
|
||||||
types[*reg as usize] = Object;
|
|
||||||
types[*size_reg as usize] = SimpleScalar;
|
|
||||||
}
|
|
||||||
FilledNewArray { type_, reg_values } => {
|
|
||||||
let reg_ty = if type_.is_class() || type_.is_array() {
|
|
||||||
Object
|
|
||||||
//} else if type_.is_long() || type_.is_double() {
|
|
||||||
// SimpleScalar // Not supposed to happend so default to simple scalar just in
|
|
||||||
// case
|
|
||||||
} else {
|
|
||||||
SimpleScalar
|
|
||||||
};
|
|
||||||
for i in reg_values {
|
|
||||||
types[*i as usize] = reg_ty;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
CmpLFloat { dest, b, c } | CmpGFloat { dest, b, c } => {
|
|
||||||
types[*dest as usize] = SimpleScalar;
|
|
||||||
types[*b as usize] = SimpleScalar;
|
|
||||||
types[*c as usize] = SimpleScalar;
|
|
||||||
}
|
|
||||||
CmpLDouble { dest, b, c } | CmpGDouble { dest, b, c } | CmpLong { dest, b, c } => {
|
|
||||||
types[*dest as usize] = SimpleScalar;
|
|
||||||
types[*b as usize] = FirstWideScalar;
|
|
||||||
types[*b as usize + 1] = SecondWideScalar;
|
|
||||||
types[*c as usize] = FirstWideScalar;
|
|
||||||
types[*c as usize + 1] = SecondWideScalar;
|
|
||||||
}
|
|
||||||
IfEq { a, b, .. }
|
|
||||||
| IfNe { a, b, .. }
|
|
||||||
| IfLt { a, b, .. }
|
|
||||||
| IfGe { a, b, .. }
|
|
||||||
| IfGt { a, b, .. }
|
|
||||||
| IfLe { a, b, .. } => {
|
|
||||||
types[*a as usize] = SimpleScalar;
|
|
||||||
types[*b as usize] = SimpleScalar;
|
|
||||||
}
|
|
||||||
IfEqZ { a, .. }
|
|
||||||
| IfNeZ { a, .. }
|
|
||||||
| IfLtZ { a, .. }
|
|
||||||
| IfGeZ { a, .. }
|
|
||||||
| IfGtZ { a, .. }
|
|
||||||
| IfLeZ { a, .. } => {
|
|
||||||
types[*a as usize] = SimpleScalar;
|
|
||||||
}
|
|
||||||
AGet {
|
|
||||||
dest: r,
|
|
||||||
arr: obj,
|
|
||||||
idx,
|
|
||||||
}
|
|
||||||
| AGetBoolean {
|
|
||||||
dest: r,
|
|
||||||
arr: obj,
|
|
||||||
idx,
|
|
||||||
}
|
|
||||||
| AGetByte {
|
|
||||||
dest: r,
|
|
||||||
arr: obj,
|
|
||||||
idx,
|
|
||||||
}
|
|
||||||
| AGetChar {
|
|
||||||
dest: r,
|
|
||||||
arr: obj,
|
|
||||||
idx,
|
|
||||||
}
|
|
||||||
| AGetShort {
|
|
||||||
dest: r,
|
|
||||||
arr: obj,
|
|
||||||
idx,
|
|
||||||
}
|
|
||||||
| APut {
|
|
||||||
from: r,
|
|
||||||
arr: obj,
|
|
||||||
idx,
|
|
||||||
}
|
|
||||||
| APutBoolean {
|
|
||||||
from: r,
|
|
||||||
arr: obj,
|
|
||||||
idx,
|
|
||||||
}
|
|
||||||
| APutByte {
|
|
||||||
from: r,
|
|
||||||
arr: obj,
|
|
||||||
idx,
|
|
||||||
}
|
|
||||||
| APutChar {
|
|
||||||
from: r,
|
|
||||||
arr: obj,
|
|
||||||
idx,
|
|
||||||
}
|
|
||||||
| APutShort {
|
|
||||||
from: r,
|
|
||||||
arr: obj,
|
|
||||||
idx,
|
|
||||||
} => {
|
|
||||||
types[*r as usize] = SimpleScalar;
|
|
||||||
types[*obj as usize] = Object;
|
|
||||||
types[*idx as usize] = SimpleScalar;
|
|
||||||
}
|
|
||||||
AGetWide {
|
|
||||||
dest: r,
|
|
||||||
arr: obj,
|
|
||||||
idx,
|
|
||||||
}
|
|
||||||
| APutWide {
|
|
||||||
from: r,
|
|
||||||
arr: obj,
|
|
||||||
idx,
|
|
||||||
} => {
|
|
||||||
types[*r as usize] = FirstWideScalar;
|
|
||||||
types[*r as usize + 1] = SecondWideScalar;
|
|
||||||
types[*obj as usize] = Object;
|
|
||||||
types[*idx as usize] = SimpleScalar;
|
|
||||||
}
|
|
||||||
AGetObject {
|
|
||||||
dest: r,
|
|
||||||
arr: obj,
|
|
||||||
idx,
|
|
||||||
}
|
|
||||||
| APutObject {
|
|
||||||
from: r,
|
|
||||||
arr: obj,
|
|
||||||
idx,
|
|
||||||
} => {
|
|
||||||
types[*r as usize] = Object;
|
|
||||||
types[*obj as usize] = Object;
|
|
||||||
types[*idx as usize] = SimpleScalar;
|
|
||||||
}
|
|
||||||
IGet { to: r, obj, .. }
|
|
||||||
| IGetBoolean { to: r, obj, .. }
|
|
||||||
| IGetByte { to: r, obj, .. }
|
|
||||||
| IGetChar { to: r, obj, .. }
|
|
||||||
| IGetShort { to: r, obj, .. }
|
|
||||||
| IPut { from: r, obj, .. }
|
|
||||||
| IPutBoolean { from: r, obj, .. }
|
|
||||||
| IPutByte { from: r, obj, .. }
|
|
||||||
| IPutChar { from: r, obj, .. }
|
|
||||||
| IPutShort { from: r, obj, .. } => {
|
|
||||||
types[*r as usize] = SimpleScalar;
|
|
||||||
types[*obj as usize] = Object;
|
|
||||||
}
|
|
||||||
IGetWide { to: r, obj, .. } | IPutWide { from: r, obj, .. } => {
|
|
||||||
types[*r as usize] = FirstWideScalar;
|
|
||||||
types[*r as usize] = SecondWideScalar;
|
|
||||||
types[*obj as usize] = Object;
|
|
||||||
}
|
|
||||||
IGetObject { to: r, obj, .. } | IPutObject { from: r, obj, .. } => {
|
|
||||||
types[*r as usize] = Object;
|
|
||||||
types[*obj as usize] = Object;
|
|
||||||
}
|
|
||||||
SGet { to: r, .. }
|
|
||||||
| SGetBoolean { to: r, .. }
|
|
||||||
| SGetByte { to: r, .. }
|
|
||||||
| SGetChar { to: r, .. }
|
|
||||||
| SGetShort { to: r, .. }
|
|
||||||
| SPut { from: r, .. }
|
|
||||||
| SPutBoolean { from: r, .. }
|
|
||||||
| SPutByte { from: r, .. }
|
|
||||||
| SPutChar { from: r, .. }
|
|
||||||
| SPutShort { from: r, .. } => {
|
|
||||||
types[*r as usize] = SimpleScalar;
|
|
||||||
}
|
|
||||||
SGetWide { to: r, .. } | SPutWide { from: r, .. } => {
|
|
||||||
types[*r as usize] = FirstWideScalar;
|
|
||||||
types[*r as usize] = SecondWideScalar;
|
|
||||||
}
|
|
||||||
SGetObject { to: r, .. } | SPutObject { from: r, .. } => {
|
|
||||||
types[*r as usize] = Object;
|
|
||||||
}
|
|
||||||
/* They are information to get from the method type, but, meh, this is not
|
|
||||||
* necessary (we should have the type from when the reg was initialized)
|
|
||||||
InvokeVirtual { method: _, args: _ }
|
|
||||||
| InvokeSuper { method: _, args: _ }
|
|
||||||
| InvokeDirect { method: _, args: _ }
|
|
||||||
| InvokeStatic { method: _, args: _ }
|
|
||||||
| InvokeInterface { method: _, args: _ } => todo!(),
|
|
||||||
InvokePolymorphic { method:_, proto: _, args: _ } => todo!(),
|
|
||||||
InvokeCustom { call_site: _, args: _ } => todo!(),
|
|
||||||
*/
|
|
||||||
NegInt { dest, val }
|
|
||||||
| NotInt { dest, val }
|
|
||||||
| NegFloat { dest, val }
|
|
||||||
| NegDouble { dest, val }
|
|
||||||
| IntToFloat { dest, val }
|
|
||||||
| FloatToInt { dest, val }
|
|
||||||
| IntToByte { dest, val }
|
|
||||||
| IntToChar { dest, val }
|
|
||||||
| IntToShort { dest, val } => {
|
|
||||||
types[*dest as usize] = SimpleScalar;
|
|
||||||
types[*val as usize] = SimpleScalar;
|
|
||||||
}
|
|
||||||
NegLong { dest, val }
|
|
||||||
| NotLong { dest, val }
|
|
||||||
| LongToDouble { dest, val }
|
|
||||||
| DoubleToLong { dest, val } => {
|
|
||||||
types[*dest as usize] = FirstWideScalar;
|
|
||||||
types[*dest as usize + 1] = SecondWideScalar;
|
|
||||||
types[*val as usize] = FirstWideScalar;
|
|
||||||
types[*val as usize + 1] = SecondWideScalar;
|
|
||||||
}
|
|
||||||
IntToLong { dest, val }
|
|
||||||
| IntToDouble { dest, val }
|
|
||||||
| FloatToLong { dest, val }
|
|
||||||
| FloatToDouble { dest, val } => {
|
|
||||||
types[*dest as usize] = FirstWideScalar;
|
|
||||||
types[*dest as usize + 1] = SecondWideScalar;
|
|
||||||
types[*val as usize] = SimpleScalar;
|
|
||||||
}
|
|
||||||
LongToInt { dest, val }
|
|
||||||
| LongToFloat { dest, val }
|
|
||||||
| DoubleToInt { dest, val }
|
|
||||||
| DoubleToFloat { dest, val } => {
|
|
||||||
types[*dest as usize] = SimpleScalar;
|
|
||||||
types[*val as usize] = FirstWideScalar;
|
|
||||||
types[*val as usize + 1] = SecondWideScalar;
|
|
||||||
}
|
|
||||||
AddInt { dest, b, c }
|
|
||||||
| SubInt { dest, b, c }
|
|
||||||
| MulInt { dest, b, c }
|
|
||||||
| DivInt { dest, b, c }
|
|
||||||
| RemInt { dest, b, c }
|
|
||||||
| AndInt { dest, b, c }
|
|
||||||
| OrInt { dest, b, c }
|
|
||||||
| XorInt { dest, b, c }
|
|
||||||
| ShlInt { dest, b, c }
|
|
||||||
| ShrInt { dest, b, c }
|
|
||||||
| UshrInt { dest, b, c }
|
|
||||||
| AddFloat { dest, b, c }
|
|
||||||
| SubFloat { dest, b, c }
|
|
||||||
| MulFloat { dest, b, c }
|
|
||||||
| DivFloat { dest, b, c }
|
|
||||||
| RemFloat { dest, b, c } => {
|
|
||||||
types[*dest as usize] = SimpleScalar;
|
|
||||||
types[*b as usize] = SimpleScalar;
|
|
||||||
types[*c as usize] = SimpleScalar;
|
|
||||||
}
|
|
||||||
AddLong { dest, b, c }
|
|
||||||
| SubLong { dest, b, c }
|
|
||||||
| MulLong { dest, b, c }
|
|
||||||
| DivLong { dest, b, c }
|
|
||||||
| RemLong { dest, b, c }
|
|
||||||
| AndLong { dest, b, c }
|
|
||||||
| OrLong { dest, b, c }
|
|
||||||
| XorLong { dest, b, c }
|
|
||||||
| ShlLong { dest, b, c }
|
|
||||||
| ShrLong { dest, b, c }
|
|
||||||
| UshrLong { dest, b, c }
|
|
||||||
| AddDouble { dest, b, c }
|
|
||||||
| SubDouble { dest, b, c }
|
|
||||||
| MulDouble { dest, b, c }
|
|
||||||
| DivDouble { dest, b, c }
|
|
||||||
| RemDouble { dest, b, c } => {
|
|
||||||
types[*dest as usize] = FirstWideScalar;
|
|
||||||
types[*dest as usize + 1] = SecondWideScalar;
|
|
||||||
types[*b as usize] = FirstWideScalar;
|
|
||||||
types[*b as usize + 1] = SecondWideScalar;
|
|
||||||
types[*c as usize] = FirstWideScalar;
|
|
||||||
types[*c as usize + 1] = SecondWideScalar;
|
|
||||||
}
|
|
||||||
AddInt2Addr { dest, b }
|
|
||||||
| SubInt2Addr { dest, b }
|
|
||||||
| MulInt2Addr { dest, b }
|
|
||||||
| DivInt2Addr { dest, b }
|
|
||||||
| RemInt2Addr { dest, b }
|
|
||||||
| AndInt2Addr { dest, b }
|
|
||||||
| OrInt2Addr { dest, b }
|
|
||||||
| XorInt2Addr { dest, b }
|
|
||||||
| ShlInt2Addr { dest, b }
|
|
||||||
| ShrInt2Addr { dest, b }
|
|
||||||
| UshrInt2Addr { dest, b }
|
|
||||||
| AddFloat2Addr { dest, b }
|
|
||||||
| SubFloat2Addr { dest, b }
|
|
||||||
| MulFloat2Addr { dest, b }
|
|
||||||
| DivFloat2Addr { dest, b }
|
|
||||||
| RemFloat2Addr { dest, b }
|
|
||||||
| AddIntLit { dest, b, .. }
|
|
||||||
| RsubIntLit { dest, b, .. }
|
|
||||||
| MulIntLit { dest, b, .. }
|
|
||||||
| DivIntLit { dest, b, .. }
|
|
||||||
| RemIntLit { dest, b, .. }
|
|
||||||
| AndIntLit { dest, b, .. }
|
|
||||||
| OrIntLit { dest, b, .. }
|
|
||||||
| XorIntLit { dest, b, .. }
|
|
||||||
| ShlIntLit { dest, b, .. }
|
|
||||||
| ShrIntLit { dest, b, .. }
|
|
||||||
| UshrIntLit { dest, b, .. } => {
|
|
||||||
types[*dest as usize] = SimpleScalar;
|
|
||||||
types[*b as usize] = SimpleScalar;
|
|
||||||
}
|
|
||||||
AddLong2Addr { dest, b }
|
|
||||||
| SubLong2Addr { dest, b }
|
|
||||||
| MulLong2Addr { dest, b }
|
|
||||||
| DivLong2Addr { dest, b }
|
|
||||||
| RemLong2Addr { dest, b }
|
|
||||||
| AndLong2Addr { dest, b }
|
|
||||||
| OrLong2Addr { dest, b }
|
|
||||||
| XorLong2Addr { dest, b }
|
|
||||||
| ShlLong2Addr { dest, b }
|
|
||||||
| ShrLong2Addr { dest, b }
|
|
||||||
| UshrLong2Addr { dest, b }
|
|
||||||
| AddDouble2Addr { dest, b }
|
|
||||||
| SubDouble2Addr { dest, b }
|
|
||||||
| MulDouble2Addr { dest, b }
|
|
||||||
| DivDouble2Addr { dest, b }
|
|
||||||
| RemDouble2Addr { dest, b } => {
|
|
||||||
types[*dest as usize] = FirstWideScalar;
|
|
||||||
types[*dest as usize + 1] = SecondWideScalar;
|
|
||||||
types[*b as usize] = FirstWideScalar;
|
|
||||||
types[*b as usize + 1] = SecondWideScalar;
|
|
||||||
}
|
|
||||||
_ => (),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
types
|
|
||||||
}
|
|
||||||
2808
androscalpel/src/dex_fragment.rs
Normal file
2808
androscalpel/src/dex_fragment.rs
Normal file
File diff suppressed because it is too large
Load diff
|
|
@ -2,20 +2,21 @@
|
||||||
|
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use std::cmp::{Ord, Ordering, PartialOrd};
|
use std::cmp::{Ord, Ordering, PartialOrd};
|
||||||
|
use std::collections::hash_map::DefaultHasher;
|
||||||
use std::collections::HashSet;
|
use std::collections::HashSet;
|
||||||
use std::hash::Hash;
|
use std::hash::{Hash, Hasher};
|
||||||
|
|
||||||
use anyhow::{anyhow, bail, Context};
|
use anyhow::{anyhow, bail, Context};
|
||||||
#[cfg(feature = "python")]
|
use pyo3::class::basic::CompareOp;
|
||||||
use pyo3::prelude::*;
|
use pyo3::prelude::*;
|
||||||
|
|
||||||
use crate::{scalar::*, DexString, DexValue, Result, Visitable, VisitableMut, Visitor, VisitorMut};
|
use crate::{scalar::*, DexString, DexValue, Result};
|
||||||
use androscalpel_serializer::{StringDataItem, Uleb128};
|
use androscalpel_serializer::{StringDataItem, Uleb128};
|
||||||
|
|
||||||
/// The type of a method. The shorty is formated as described in
|
/// The type of a method. The shorty is formated as described in
|
||||||
/// <https://source.android.com/docs/core/runtime/dex-format#shortydescriptor>
|
/// <https://source.android.com/docs/core/runtime/dex-format#shortydescriptor>
|
||||||
#[cfg_attr(feature = "python", pyclass(eq, ord, hash, frozen))]
|
#[pyclass]
|
||||||
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
#[derive(Debug, Clone, PartialEq, Eq, Hash, Deserialize, Serialize)]
|
||||||
pub struct IdMethodType {
|
pub struct IdMethodType {
|
||||||
/// Type formated as described by <https://source.android.com/docs/core/runtime/dex-format#shortydescriptor>
|
/// Type formated as described by <https://source.android.com/docs/core/runtime/dex-format#shortydescriptor>
|
||||||
pub(crate) shorty: DexString, // Redondant, but same as in the encoding, keep it in case we ever
|
pub(crate) shorty: DexString, // Redondant, but same as in the encoding, keep it in case we ever
|
||||||
|
|
@ -24,30 +25,6 @@ pub struct IdMethodType {
|
||||||
pub(crate) parameters: Vec<IdType>,
|
pub(crate) parameters: Vec<IdType>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<V: Visitor> Visitable<V> for IdMethodType {
|
|
||||||
fn default_visit(&self, v: &mut V) -> Result<()> {
|
|
||||||
v.visit_string(&self.shorty)?;
|
|
||||||
v.visit_type(&self.return_type)?;
|
|
||||||
for ty in &self.parameters {
|
|
||||||
v.visit_type(ty)?;
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<V: VisitorMut> VisitableMut<V> for IdMethodType {
|
|
||||||
fn default_visit_mut(mut self, v: &mut V) -> Result<Self> {
|
|
||||||
self.shorty = v.visit_string(self.shorty)?;
|
|
||||||
self.return_type = v.visit_type(self.return_type)?;
|
|
||||||
self.parameters = self
|
|
||||||
.parameters
|
|
||||||
.into_iter()
|
|
||||||
.map(|ty| v.visit_type(ty))
|
|
||||||
.collect::<Result<_>>()?;
|
|
||||||
Ok(self)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Ord for IdMethodType {
|
impl Ord for IdMethodType {
|
||||||
fn cmp(&self, other: &Self) -> Ordering {
|
fn cmp(&self, other: &Self) -> Ordering {
|
||||||
self.return_type
|
self.return_type
|
||||||
|
|
@ -63,18 +40,18 @@ impl PartialOrd for IdMethodType {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg_attr(feature = "python", pymethods)]
|
#[pymethods]
|
||||||
impl IdMethodType {
|
impl IdMethodType {
|
||||||
pub fn to_json(&self) -> Result<String> {
|
pub fn to_json(&self) -> Result<String> {
|
||||||
Ok(serde_json::to_string(self)?)
|
Ok(serde_json::to_string(self)?)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg_attr(feature = "python", staticmethod)]
|
#[staticmethod]
|
||||||
pub fn from_json(json: &str) -> Result<Self> {
|
pub fn from_json(json: &str) -> Result<Self> {
|
||||||
Ok(serde_json::from_str(json)?)
|
Ok(serde_json::from_str(json)?)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg_attr(feature = "python", new)]
|
#[new]
|
||||||
pub fn new(return_type: IdType, parameters: Vec<IdType>) -> Self {
|
pub fn new(return_type: IdType, parameters: Vec<IdType>) -> Self {
|
||||||
Self {
|
Self {
|
||||||
shorty: Self::compute_shorty(&return_type, ¶meters),
|
shorty: Self::compute_shorty(&return_type, ¶meters),
|
||||||
|
|
@ -86,12 +63,12 @@ impl IdMethodType {
|
||||||
/// Try to parse a smali representation of a prototype into a IdMethodType.
|
/// Try to parse a smali representation of a prototype into a IdMethodType.
|
||||||
///
|
///
|
||||||
/// ```
|
/// ```
|
||||||
/// use androscalpel::{IdMethodType, IdType};
|
/// use androscalpel::IdMethodType;
|
||||||
///
|
///
|
||||||
/// let proto = IdMethodType::from_smali("(Landroidx/core/util/Predicate;Landroidx/core/util/Predicate;Ljava/lang/Object;)Z").unwrap();
|
/// let proto = IdMethodType::from_smali("(Landroidx/core/util/Predicate;Landroidx/core/util/Predicate;Ljava/lang/Object;)Z").unwrap();
|
||||||
/// assert_eq!(
|
/// assert_eq!(
|
||||||
/// proto,
|
/// proto,
|
||||||
/// IdMethodType::new(
|
/// IdMethodType(
|
||||||
/// IdType::boolean(),
|
/// IdType::boolean(),
|
||||||
/// vec![
|
/// vec![
|
||||||
/// IdType::class("androidx/core/util/Predicate"),
|
/// IdType::class("androidx/core/util/Predicate"),
|
||||||
|
|
@ -101,7 +78,7 @@ impl IdMethodType {
|
||||||
/// )
|
/// )
|
||||||
/// );
|
/// );
|
||||||
/// ```
|
/// ```
|
||||||
#[cfg_attr(feature = "python", staticmethod)]
|
#[staticmethod]
|
||||||
pub fn from_smali(smali_repr: &str) -> Result<Self> {
|
pub fn from_smali(smali_repr: &str) -> Result<Self> {
|
||||||
if smali_repr.len() < 2 {
|
if smali_repr.len() < 2 {
|
||||||
bail!("'{smali_repr}' is to short to be a prototype");
|
bail!("'{smali_repr}' is to short to be a prototype");
|
||||||
|
|
@ -166,6 +143,12 @@ impl IdMethodType {
|
||||||
self.parameters.clone()
|
self.parameters.clone()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn __hash__(&self) -> u64 {
|
||||||
|
let mut hasher = DefaultHasher::new();
|
||||||
|
self.hash(&mut hasher);
|
||||||
|
hasher.finish()
|
||||||
|
}
|
||||||
|
|
||||||
/// Return all strings referenced in the Id.
|
/// Return all strings referenced in the Id.
|
||||||
pub fn get_all_strings(&self) -> HashSet<DexString> {
|
pub fn get_all_strings(&self) -> HashSet<DexString> {
|
||||||
let mut strings = HashSet::new();
|
let mut strings = HashSet::new();
|
||||||
|
|
@ -191,24 +174,9 @@ impl IdMethodType {
|
||||||
protos.insert(self.clone());
|
protos.insert(self.clone());
|
||||||
protos
|
protos
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
impl SmaliName for IdMethodType {
|
fn __richcmp__(&self, other: &Self, op: CompareOp) -> bool {
|
||||||
/// Convert a descriptor to its smali representation.
|
op.matches(self.cmp(other))
|
||||||
fn try_to_smali(&self) -> Result<String> {
|
|
||||||
Ok(format!(
|
|
||||||
"({}){}",
|
|
||||||
self.parameters
|
|
||||||
.iter()
|
|
||||||
.map(|param| param.try_to_smali())
|
|
||||||
.collect::<Result<Vec<String>>>()?
|
|
||||||
.join(""),
|
|
||||||
self.return_type.try_to_smali()?
|
|
||||||
))
|
|
||||||
}
|
|
||||||
/// Convert a smali representation to its descriptor.
|
|
||||||
fn try_from_smali(smali: &str) -> Result<Self> {
|
|
||||||
Self::from_smali(smali)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -216,10 +184,9 @@ impl IdMethodType {
|
||||||
/// Compute the format for the shorty as described in
|
/// Compute the format for the shorty as described in
|
||||||
/// <https://source.android.com/docs/core/runtime/dex-format#shortydescriptor>
|
/// <https://source.android.com/docs/core/runtime/dex-format#shortydescriptor>
|
||||||
pub fn compute_shorty(return_type: &IdType, parameters: &[IdType]) -> DexString {
|
pub fn compute_shorty(return_type: &IdType, parameters: &[IdType]) -> DexString {
|
||||||
// TODO: computing on dex string instead of string? that a lot of doubious conversion
|
let mut shorty: String = return_type.get_shorty().into();
|
||||||
let mut shorty: String = return_type.get_shorty().__str__();
|
|
||||||
for ty in parameters {
|
for ty in parameters {
|
||||||
let ty: String = ty.get_shorty().__str__();
|
let ty: String = ty.get_shorty().into();
|
||||||
shorty.push_str(&ty);
|
shorty.push_str(&ty);
|
||||||
}
|
}
|
||||||
shorty.into()
|
shorty.into()
|
||||||
|
|
@ -231,39 +198,23 @@ impl IdMethodType {
|
||||||
/// as described here <https://source.android.com/docs/core/runtime/dex-format#typedescriptor>
|
/// as described here <https://source.android.com/docs/core/runtime/dex-format#typedescriptor>
|
||||||
// Not a clean rust enum because we want to be compatible with python, and maybe support strange
|
// Not a clean rust enum because we want to be compatible with python, and maybe support strange
|
||||||
// malware edge case?
|
// malware edge case?
|
||||||
#[cfg_attr(feature = "python", pyclass(eq, ord, hash, frozen))]
|
#[pyclass]
|
||||||
#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
|
#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord, Deserialize, Serialize)]
|
||||||
pub struct IdType(pub(crate) DexString);
|
pub struct IdType(pub(crate) DexString);
|
||||||
|
#[pymethods]
|
||||||
impl<V: Visitor> Visitable<V> for IdType {
|
|
||||||
fn default_visit(&self, v: &mut V) -> Result<()> {
|
|
||||||
v.visit_string(&self.0)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<V: VisitorMut> VisitableMut<V> for IdType {
|
|
||||||
fn default_visit_mut(self, v: &mut V) -> Result<Self> {
|
|
||||||
Ok(Self(v.visit_string(self.0)?))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#[cfg_attr(feature = "python", pymethods)]
|
|
||||||
impl IdType {
|
impl IdType {
|
||||||
pub fn to_json(&self) -> Result<String> {
|
pub fn to_json(&self) -> Result<String> {
|
||||||
Ok(serde_json::to_string(self)?)
|
Ok(serde_json::to_string(self)?)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg_attr(feature = "python", staticmethod)]
|
#[staticmethod]
|
||||||
pub fn from_json(json: &str) -> Result<Self> {
|
pub fn from_json(json: &str) -> Result<Self> {
|
||||||
Ok(serde_json::from_str(json)?)
|
Ok(serde_json::from_str(json)?)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg_attr(feature = "python", new)]
|
#[new]
|
||||||
pub fn new(
|
pub fn new(
|
||||||
#[cfg_attr(
|
#[pyo3(from_py_with = "crate::dex_string::as_dex_string")] ty: DexString,
|
||||||
feature = "python",
|
|
||||||
pyo3(from_py_with = "crate::dex_string::as_dex_string")
|
|
||||||
)]
|
|
||||||
ty: DexString,
|
|
||||||
) -> Result<Self> {
|
) -> Result<Self> {
|
||||||
// TODO: check format
|
// TODO: check format
|
||||||
let ty = Self(ty);
|
let ty = Self(ty);
|
||||||
|
|
@ -272,7 +223,7 @@ impl IdType {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Return a type from its string representation without checking its format
|
/// Return a type from its string representation without checking its format
|
||||||
#[cfg_attr(feature = "python", staticmethod)]
|
#[staticmethod]
|
||||||
pub fn unchecked_new(ty: DexString) -> Self {
|
pub fn unchecked_new(ty: DexString) -> Self {
|
||||||
Self(ty)
|
Self(ty)
|
||||||
}
|
}
|
||||||
|
|
@ -283,7 +234,7 @@ impl IdType {
|
||||||
/// id types.
|
/// id types.
|
||||||
///
|
///
|
||||||
/// ```
|
/// ```
|
||||||
/// use androscalpel::IdType;
|
/// use androscalpel::IdType
|
||||||
///
|
///
|
||||||
/// let id_type = IdType::from_smali(
|
/// let id_type = IdType::from_smali(
|
||||||
/// "Landroidx/core/util/Predicate;"
|
/// "Landroidx/core/util/Predicate;"
|
||||||
|
|
@ -294,7 +245,7 @@ impl IdType {
|
||||||
/// IdType::class("androidx/core/util/Predicate")
|
/// IdType::class("androidx/core/util/Predicate")
|
||||||
/// );
|
/// );
|
||||||
/// ```
|
/// ```
|
||||||
#[cfg_attr(feature = "python", staticmethod)]
|
#[staticmethod]
|
||||||
pub fn from_smali(smali_repr: &str) -> Result<Self> {
|
pub fn from_smali(smali_repr: &str) -> Result<Self> {
|
||||||
Self::new(smali_repr.into())
|
Self::new(smali_repr.into())
|
||||||
}
|
}
|
||||||
|
|
@ -316,7 +267,7 @@ impl IdType {
|
||||||
/// IdType::boolean(),
|
/// IdType::boolean(),
|
||||||
/// ])
|
/// ])
|
||||||
/// ```
|
/// ```
|
||||||
#[cfg_attr(feature = "python", staticmethod)]
|
#[staticmethod]
|
||||||
pub fn get_list_from_str(type_list: &str) -> Result<Vec<Self>> {
|
pub fn get_list_from_str(type_list: &str) -> Result<Vec<Self>> {
|
||||||
let mut lst = vec![];
|
let mut lst = vec![];
|
||||||
let mut chars = type_list.chars();
|
let mut chars = type_list.chars();
|
||||||
|
|
@ -332,18 +283,15 @@ impl IdType {
|
||||||
'J' => Some(Self::long()),
|
'J' => Some(Self::long()),
|
||||||
'F' => Some(Self::float()),
|
'F' => Some(Self::float()),
|
||||||
'D' => Some(Self::double()),
|
'D' => Some(Self::double()),
|
||||||
'[' => {
|
'[' => { array_dimmention += 1; None },
|
||||||
array_dimmention += 1;
|
|
||||||
None
|
|
||||||
}
|
|
||||||
'L' => {
|
'L' => {
|
||||||
let mut class_name = String::new();
|
let mut class_name = String::new();
|
||||||
for cc in chars.by_ref() {
|
for cc in chars.by_ref(){
|
||||||
if cc == ';' {
|
if cc == ';' { break;}
|
||||||
break;
|
else {
|
||||||
} else {
|
|
||||||
class_name.push(cc);
|
class_name.push(cc);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
Some(Self::class(&class_name))
|
Some(Self::class(&class_name))
|
||||||
}
|
}
|
||||||
|
|
@ -373,77 +321,66 @@ impl IdType {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Return the void type (for return type)
|
/// Return the void type (for return type)
|
||||||
#[cfg_attr(feature = "python", staticmethod)]
|
#[staticmethod]
|
||||||
pub fn void() -> Self {
|
pub fn void() -> Self {
|
||||||
Self("V".into())
|
Self("V".into())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Return the boolean type
|
/// Return the boolean type
|
||||||
#[cfg_attr(feature = "python", staticmethod)]
|
#[staticmethod]
|
||||||
pub fn boolean() -> Self {
|
pub fn boolean() -> Self {
|
||||||
Self("Z".into())
|
Self("Z".into())
|
||||||
}
|
}
|
||||||
/// Return the byte type
|
/// Return the byte type
|
||||||
#[cfg_attr(feature = "python", staticmethod)]
|
#[staticmethod]
|
||||||
pub fn byte() -> Self {
|
pub fn byte() -> Self {
|
||||||
Self("B".into())
|
Self("B".into())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Return the short type
|
/// Return the short type
|
||||||
#[cfg_attr(feature = "python", staticmethod)]
|
#[staticmethod]
|
||||||
pub fn short() -> Self {
|
pub fn short() -> Self {
|
||||||
Self("S".into())
|
Self("S".into())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Return the char type
|
/// Return the char type
|
||||||
#[cfg_attr(feature = "python", staticmethod)]
|
#[staticmethod]
|
||||||
pub fn char() -> Self {
|
pub fn char() -> Self {
|
||||||
Self("C".into())
|
Self("C".into())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Return the int type
|
/// Return the int type
|
||||||
#[cfg_attr(feature = "python", staticmethod)]
|
#[staticmethod]
|
||||||
pub fn int() -> Self {
|
pub fn int() -> Self {
|
||||||
Self("I".into())
|
Self("I".into())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Return the long type
|
/// Return the long type
|
||||||
#[cfg_attr(feature = "python", staticmethod)]
|
#[staticmethod]
|
||||||
pub fn long() -> Self {
|
pub fn long() -> Self {
|
||||||
Self("J".into())
|
Self("J".into())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Return the float type
|
/// Return the float type
|
||||||
#[cfg_attr(feature = "python", staticmethod)]
|
#[staticmethod]
|
||||||
pub fn float() -> Self {
|
pub fn float() -> Self {
|
||||||
Self("F".into())
|
Self("F".into())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Return the double type
|
/// Return the double type
|
||||||
#[cfg_attr(feature = "python", staticmethod)]
|
#[staticmethod]
|
||||||
pub fn double() -> Self {
|
pub fn double() -> Self {
|
||||||
Self("D".into())
|
Self("D".into())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Return the type for the class of fully qualified name `name`
|
/// Return the type for the class of fully qualified name `name`
|
||||||
#[cfg_attr(feature = "python", staticmethod)]
|
#[staticmethod]
|
||||||
pub fn class(name: &str) -> Self {
|
pub fn class(name: &str) -> Self {
|
||||||
Self(format!("L{name};").into())
|
Self(format!("L{name};").into())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Return the type for the class of fully qualified name `name`.
|
|
||||||
/// Same as [`IdType::class`] but with a [`DexString`] name.
|
|
||||||
#[cfg_attr(feature = "python", staticmethod)]
|
|
||||||
pub fn class_from_dex_string(name: &DexString) -> Self {
|
|
||||||
let mut repr = name.clone();
|
|
||||||
repr.0.utf16_size.0 += 2;
|
|
||||||
repr.0.data.insert(0, b'L');
|
|
||||||
repr.0.data.push(b';');
|
|
||||||
Self(repr)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Return the type for an array of the specify `type_`
|
/// Return the type for an array of the specify `type_`
|
||||||
#[cfg_attr(feature = "python", staticmethod)]
|
#[staticmethod]
|
||||||
pub fn array(type_: &IdType) -> Self {
|
pub fn array(type_: &IdType) -> Self {
|
||||||
let mut ty = type_.clone();
|
let mut ty = type_.clone();
|
||||||
ty.0 .0.utf16_size.0 += 1;
|
ty.0 .0.utf16_size.0 += 1;
|
||||||
|
|
@ -456,11 +393,11 @@ impl IdType {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn __str__(&self) -> String {
|
pub fn __str__(&self) -> String {
|
||||||
self.0.__str__()
|
(&self.0).into()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn __repr__(&self) -> String {
|
pub fn __repr__(&self) -> String {
|
||||||
let name: String = self.0.__str__();
|
let name: String = (&self.0).into();
|
||||||
format!("IdType(\"{name}\")")
|
format!("IdType(\"{name}\")")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -577,7 +514,7 @@ impl IdType {
|
||||||
{
|
{
|
||||||
Ok(())
|
Ok(())
|
||||||
} else {
|
} else {
|
||||||
let format: String = self.0.__str__();
|
let format: String = (&self.0).into();
|
||||||
Err(anyhow!("{format} is not a valid type"))
|
Err(anyhow!("{format} is not a valid type"))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -628,6 +565,12 @@ impl IdType {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn __hash__(&self) -> u64 {
|
||||||
|
let mut hasher = DefaultHasher::new();
|
||||||
|
self.hash(&mut hasher);
|
||||||
|
hasher.finish()
|
||||||
|
}
|
||||||
|
|
||||||
/// Return all strings referenced in the Id.
|
/// Return all strings referenced in the Id.
|
||||||
pub fn get_all_strings(&self) -> HashSet<DexString> {
|
pub fn get_all_strings(&self) -> HashSet<DexString> {
|
||||||
let mut strings = HashSet::new();
|
let mut strings = HashSet::new();
|
||||||
|
|
@ -642,81 +585,41 @@ impl IdType {
|
||||||
types
|
types
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Check if the class is a platform class (ie in the android SDK or a hidden API).
|
fn __richcmp__(&self, other: &Self, op: CompareOp) -> bool {
|
||||||
#[cfg(feature = "platform-list")]
|
op.matches(self.cmp(other))
|
||||||
pub fn is_platform_class(&self) -> bool {
|
|
||||||
match self.try_to_smali() {
|
|
||||||
Ok(smali) => androscalpel_platform_api_list::is_platform_class(&smali),
|
|
||||||
Err(_) => false,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: TESTS
|
// TODO: TESTS
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SmaliName for IdType {
|
#[pyclass]
|
||||||
/// Convert a descriptor to its smali representation.
|
#[derive(Debug, Clone, PartialEq, Eq, Hash, Deserialize, Serialize)]
|
||||||
fn try_to_smali(&self) -> Result<String> {
|
|
||||||
let r = (&self.0 .0).try_into()?; // Anyhow conversion stuff
|
|
||||||
Ok(r)
|
|
||||||
}
|
|
||||||
/// Convert a smali representation to its descriptor.
|
|
||||||
fn try_from_smali(smali: &str) -> Result<Self> {
|
|
||||||
Self::from_smali(smali)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg_attr(feature = "python", pyclass(eq, ord, hash, frozen))]
|
|
||||||
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
|
||||||
pub struct IdField {
|
pub struct IdField {
|
||||||
/// The name of the field, format described at
|
/// The name of the field, format described at
|
||||||
/// <https://source.android.com/docs/core/runtime/dex-format#membername>
|
/// <https://source.android.com/docs/core/runtime/dex-format#membername>
|
||||||
#[cfg_attr(feature = "python", pyo3(get))]
|
#[pyo3(get)]
|
||||||
pub name: DexString,
|
pub name: DexString,
|
||||||
/// The type of the field.
|
/// The type of the field.
|
||||||
#[cfg_attr(feature = "python", pyo3(get))]
|
#[pyo3(get)]
|
||||||
pub type_: IdType,
|
pub type_: IdType,
|
||||||
/// The class that own the field.
|
/// The class that own the field.
|
||||||
#[cfg_attr(feature = "python", pyo3(get))]
|
#[pyo3(get)]
|
||||||
pub class_: IdType,
|
pub class_: IdType,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<V: Visitor> Visitable<V> for IdField {
|
#[pymethods]
|
||||||
fn default_visit(&self, v: &mut V) -> Result<()> {
|
|
||||||
v.visit_string(&self.name)?;
|
|
||||||
v.visit_type(&self.type_)?;
|
|
||||||
v.visit_type(&self.class_)?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<V: VisitorMut> VisitableMut<V> for IdField {
|
|
||||||
fn default_visit_mut(mut self, v: &mut V) -> Result<Self> {
|
|
||||||
self.name = v.visit_string(self.name)?;
|
|
||||||
self.type_ = v.visit_type(self.type_)?;
|
|
||||||
self.class_ = v.visit_type(self.class_)?;
|
|
||||||
Ok(self)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg_attr(feature = "python", pymethods)]
|
|
||||||
impl IdField {
|
impl IdField {
|
||||||
pub fn to_json(&self) -> Result<String> {
|
pub fn to_json(&self) -> Result<String> {
|
||||||
Ok(serde_json::to_string(self)?)
|
Ok(serde_json::to_string(self)?)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg_attr(feature = "python", staticmethod)]
|
#[staticmethod]
|
||||||
pub fn from_json(json: &str) -> Result<Self> {
|
pub fn from_json(json: &str) -> Result<Self> {
|
||||||
Ok(serde_json::from_str(json)?)
|
Ok(serde_json::from_str(json)?)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg_attr(feature = "python", new)]
|
#[new]
|
||||||
pub fn new(
|
pub fn new(
|
||||||
#[cfg_attr(
|
#[pyo3(from_py_with = "crate::dex_string::as_dex_string")] name: DexString,
|
||||||
feature = "python",
|
|
||||||
pyo3(from_py_with = "crate::dex_string::as_dex_string")
|
|
||||||
)]
|
|
||||||
name: DexString,
|
|
||||||
type_: IdType,
|
type_: IdType,
|
||||||
class_: IdType,
|
class_: IdType,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
|
|
@ -731,19 +634,19 @@ impl IdField {
|
||||||
/// Try to parse a smali representation of a field into a IdField.
|
/// Try to parse a smali representation of a field into a IdField.
|
||||||
///
|
///
|
||||||
/// ```
|
/// ```
|
||||||
/// use androscalpel::{IdField, IdType};
|
/// use androscalpel::IdField;
|
||||||
///
|
///
|
||||||
/// let proto = IdField::from_smali("Ljava/lang/annotation/ElementType;->FIELD:Ljava/lang/annotation/ElementType;").unwrap();
|
/// let proto = IdField::from_smali("Ljava/lang/annotation/ElementType;->METHOD:Ljava/lang/annotation/ElementType;").unwrap();
|
||||||
/// assert_eq!(
|
/// assert_eq!(
|
||||||
/// proto,
|
/// proto,
|
||||||
/// IdField::new(
|
/// IdField::new(
|
||||||
/// "FIELD".into(),
|
/// "METHOD".into(),
|
||||||
/// IdType::class("java/lang/annotation/ElementType"),
|
/// IdType::class("java/lang/annotation/ElementType"),
|
||||||
/// IdType::class("java/lang/annotation/ElementType"),
|
/// IdType::class("java/lang/annotation/ElementType"),
|
||||||
/// )
|
/// )
|
||||||
/// );
|
/// );
|
||||||
/// ```
|
/// ```
|
||||||
#[cfg_attr(feature = "python", staticmethod)]
|
#[staticmethod]
|
||||||
pub fn from_smali(smali_repr: &str) -> Result<Self> {
|
pub fn from_smali(smali_repr: &str) -> Result<Self> {
|
||||||
let arrow_i = smali_repr.find("->");
|
let arrow_i = smali_repr.find("->");
|
||||||
if arrow_i.is_none() {
|
if arrow_i.is_none() {
|
||||||
|
|
@ -769,19 +672,25 @@ impl IdField {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn __str__(&self) -> String {
|
pub fn __str__(&self) -> String {
|
||||||
let class: String = self.class_.get_name().__str__();
|
let class: String = self.class_.get_name().into();
|
||||||
let name: String = self.name.__str__();
|
let name: String = (&self.name).into();
|
||||||
let ty: String = self.type_.get_name().__str__();
|
let ty: String = self.type_.get_name().into();
|
||||||
format!("{class}->{name}:{ty}")
|
format!("{class}->{name}:{ty}")
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn __repr__(&self) -> String {
|
pub fn __repr__(&self) -> String {
|
||||||
let class: String = self.class_.__repr__();
|
let class: String = self.class_.__repr__();
|
||||||
let name: String = self.name.__str__();
|
let name: String = (&self.name).into();
|
||||||
let ty: String = self.type_.__repr__();
|
let ty: String = self.type_.__repr__();
|
||||||
format!("IdField(\"{name}\", {ty}, {class})")
|
format!("IdField(\"{name}\", {ty}, {class})")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn __hash__(&self) -> u64 {
|
||||||
|
let mut hasher = DefaultHasher::new();
|
||||||
|
self.hash(&mut hasher);
|
||||||
|
hasher.finish()
|
||||||
|
}
|
||||||
|
|
||||||
/// Return all strings referenced in the Id.
|
/// Return all strings referenced in the Id.
|
||||||
pub fn get_all_strings(&self) -> HashSet<DexString> {
|
pub fn get_all_strings(&self) -> HashSet<DexString> {
|
||||||
let mut strings = HashSet::new();
|
let mut strings = HashSet::new();
|
||||||
|
|
@ -806,13 +715,8 @@ impl IdField {
|
||||||
fields
|
fields
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Check if the field is a platform field (ie in the android SDK or a hidden API).
|
fn __richcmp__(&self, other: &Self, op: CompareOp) -> bool {
|
||||||
#[cfg(feature = "platform-list")]
|
op.matches(self.cmp(other))
|
||||||
pub fn is_platform_field(&self) -> bool {
|
|
||||||
match self.try_to_smali() {
|
|
||||||
Ok(smali) => androscalpel_platform_api_list::is_platform_field(&smali),
|
|
||||||
Err(_) => false,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -831,71 +735,35 @@ impl PartialOrd for IdField {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SmaliName for IdField {
|
|
||||||
/// Convert a descriptor to its smali representation.
|
|
||||||
fn try_to_smali(&self) -> Result<String> {
|
|
||||||
let class: String = self.class_.try_to_smali()?;
|
|
||||||
let name: String = (&self.name.0).try_into()?;
|
|
||||||
let ty: String = self.type_.try_to_smali()?;
|
|
||||||
Ok(format!("{class}->{name}:{ty}"))
|
|
||||||
}
|
|
||||||
/// Convert a smali representation to its descriptor.
|
|
||||||
fn try_from_smali(smali: &str) -> Result<Self> {
|
|
||||||
Self::from_smali(smali)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// The Id of a method.
|
/// The Id of a method.
|
||||||
#[cfg_attr(feature = "python", pyclass(eq, ord, hash, frozen))]
|
#[pyclass]
|
||||||
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
#[derive(Debug, Clone, PartialEq, Eq, Hash, Deserialize, Serialize)]
|
||||||
pub struct IdMethod {
|
pub struct IdMethod {
|
||||||
/// The class containing the method.
|
/// The class containing the method.
|
||||||
#[cfg_attr(feature = "python", pyo3(get))]
|
#[pyo3(get)]
|
||||||
pub class_: IdType,
|
pub class_: IdType,
|
||||||
/// The prototype (aka type or signature) of the method.
|
/// The prototype (aka type or signature) of the method.
|
||||||
#[cfg_attr(feature = "python", pyo3(get))]
|
#[pyo3(get)]
|
||||||
pub proto: IdMethodType,
|
pub proto: IdMethodType,
|
||||||
/// The name of the method.
|
/// The name of the method.
|
||||||
#[cfg_attr(feature = "python", pyo3(get))]
|
#[pyo3(get)]
|
||||||
pub name: DexString,
|
pub name: DexString,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<V: Visitor> Visitable<V> for IdMethod {
|
#[pymethods]
|
||||||
fn default_visit(&self, v: &mut V) -> Result<()> {
|
|
||||||
v.visit_string(&self.name)?;
|
|
||||||
v.visit_method_type(&self.proto)?;
|
|
||||||
v.visit_type(&self.class_)?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<V: VisitorMut> VisitableMut<V> for IdMethod {
|
|
||||||
fn default_visit_mut(mut self, v: &mut V) -> Result<Self> {
|
|
||||||
self.name = v.visit_string(self.name)?;
|
|
||||||
self.proto = v.visit_method_type(self.proto)?;
|
|
||||||
self.class_ = v.visit_type(self.class_)?;
|
|
||||||
Ok(self)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg_attr(feature = "python", pymethods)]
|
|
||||||
impl IdMethod {
|
impl IdMethod {
|
||||||
pub fn to_json(&self) -> Result<String> {
|
pub fn to_json(&self) -> Result<String> {
|
||||||
Ok(serde_json::to_string(self)?)
|
Ok(serde_json::to_string(self)?)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg_attr(feature = "python", staticmethod)]
|
#[staticmethod]
|
||||||
pub fn from_json(json: &str) -> Result<Self> {
|
pub fn from_json(json: &str) -> Result<Self> {
|
||||||
Ok(serde_json::from_str(json)?)
|
Ok(serde_json::from_str(json)?)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg_attr(feature = "python", new)]
|
#[new]
|
||||||
pub fn new(
|
pub fn new(
|
||||||
#[cfg_attr(
|
#[pyo3(from_py_with = "crate::dex_string::as_dex_string")] name: DexString,
|
||||||
feature = "python",
|
|
||||||
pyo3(from_py_with = "crate::dex_string::as_dex_string")
|
|
||||||
)]
|
|
||||||
name: DexString,
|
|
||||||
proto: IdMethodType,
|
proto: IdMethodType,
|
||||||
class_: IdType,
|
class_: IdType,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
|
|
@ -909,7 +777,7 @@ impl IdMethod {
|
||||||
/// Try to parse a smali representation of method into a IdMethod.
|
/// Try to parse a smali representation of method into a IdMethod.
|
||||||
///
|
///
|
||||||
/// ```
|
/// ```
|
||||||
/// use androscalpel::{IdType, IdMethod, IdMethodType};
|
/// use androscalpel::IdMethod;
|
||||||
///
|
///
|
||||||
/// let id_method = IdMethod::from_smali(
|
/// let id_method = IdMethod::from_smali(
|
||||||
/// "Landroidx/core/util/Predicate;->lambda$and$0(Landroidx/core/util/Predicate;Landroidx/core/util/Predicate;Ljava/lang/Object;)Z"
|
/// "Landroidx/core/util/Predicate;->lambda$and$0(Landroidx/core/util/Predicate;Landroidx/core/util/Predicate;Ljava/lang/Object;)Z"
|
||||||
|
|
@ -931,7 +799,7 @@ impl IdMethod {
|
||||||
/// )
|
/// )
|
||||||
/// );
|
/// );
|
||||||
/// ```
|
/// ```
|
||||||
#[cfg_attr(feature = "python", staticmethod)]
|
#[staticmethod]
|
||||||
pub fn from_smali(smali_repr: &str) -> Result<Self> {
|
pub fn from_smali(smali_repr: &str) -> Result<Self> {
|
||||||
let arrow_i = smali_repr.find("->");
|
let arrow_i = smali_repr.find("->");
|
||||||
if arrow_i.is_none() {
|
if arrow_i.is_none() {
|
||||||
|
|
@ -981,6 +849,12 @@ impl IdMethod {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn __hash__(&self) -> u64 {
|
||||||
|
let mut hasher = DefaultHasher::new();
|
||||||
|
self.hash(&mut hasher);
|
||||||
|
hasher.finish()
|
||||||
|
}
|
||||||
|
|
||||||
/// Return all strings referenced in the Id.
|
/// Return all strings referenced in the Id.
|
||||||
pub fn get_all_strings(&self) -> HashSet<DexString> {
|
pub fn get_all_strings(&self) -> HashSet<DexString> {
|
||||||
let mut strings = HashSet::new();
|
let mut strings = HashSet::new();
|
||||||
|
|
@ -1012,13 +886,8 @@ impl IdMethod {
|
||||||
method_ids
|
method_ids
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Check if the method is a platform method (ie in the android SDK or a hidden API).
|
fn __richcmp__(&self, other: &Self, op: CompareOp) -> bool {
|
||||||
#[cfg(feature = "platform-list")]
|
op.matches(self.cmp(other))
|
||||||
pub fn is_platform_method(&self) -> bool {
|
|
||||||
match self.try_to_smali() {
|
|
||||||
Ok(smali) => androscalpel_platform_api_list::is_platform_method(&smali),
|
|
||||||
Err(_) => false,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1037,51 +906,22 @@ impl PartialOrd for IdMethod {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SmaliName for IdMethod {
|
#[pyclass]
|
||||||
/// Convert a descriptor to its smali representation.
|
#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize, Ord, PartialOrd)]
|
||||||
fn try_to_smali(&self) -> Result<String> {
|
|
||||||
let name: String = (&self.name.0).try_into()?;
|
|
||||||
Ok(format!(
|
|
||||||
"{}->{}{}",
|
|
||||||
self.class_.try_to_smali()?,
|
|
||||||
name,
|
|
||||||
self.proto.try_to_smali()?,
|
|
||||||
))
|
|
||||||
}
|
|
||||||
/// Convert a smali representation to its descriptor.
|
|
||||||
fn try_from_smali(smali: &str) -> Result<Self> {
|
|
||||||
Self::from_smali(smali)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg_attr(feature = "python", pyclass(eq, ord, hash, frozen))]
|
|
||||||
#[derive(Debug, Clone, PartialEq, Eq, Ord, PartialOrd, Hash)]
|
|
||||||
pub struct IdEnum(pub IdField);
|
pub struct IdEnum(pub IdField);
|
||||||
|
|
||||||
impl<V: Visitor> Visitable<V> for IdEnum {
|
#[pymethods]
|
||||||
fn default_visit(&self, v: &mut V) -> Result<()> {
|
|
||||||
v.visit_field_id(&self.0)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<V: VisitorMut> VisitableMut<V> for IdEnum {
|
|
||||||
fn default_visit_mut(self, v: &mut V) -> Result<Self> {
|
|
||||||
Ok(Self(v.visit_field_id(self.0)?))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg_attr(feature = "python", pymethods)]
|
|
||||||
impl IdEnum {
|
impl IdEnum {
|
||||||
pub fn to_json(&self) -> Result<String> {
|
pub fn to_json(&self) -> Result<String> {
|
||||||
Ok(serde_json::to_string(self)?)
|
Ok(serde_json::to_string(self)?)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg_attr(feature = "python", staticmethod)]
|
#[staticmethod]
|
||||||
pub fn from_json(json: &str) -> Result<Self> {
|
pub fn from_json(json: &str) -> Result<Self> {
|
||||||
Ok(serde_json::from_str(json)?)
|
Ok(serde_json::from_str(json)?)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg_attr(feature = "python", new)]
|
#[new]
|
||||||
pub fn new(val: IdField) -> Self {
|
pub fn new(val: IdField) -> Self {
|
||||||
Self(val)
|
Self(val)
|
||||||
}
|
}
|
||||||
|
|
@ -1112,62 +952,8 @@ impl IdEnum {
|
||||||
pub fn get_all_field_ids(&self) -> HashSet<IdField> {
|
pub fn get_all_field_ids(&self) -> HashSet<IdField> {
|
||||||
self.0.get_all_field_ids()
|
self.0.get_all_field_ids()
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// Not to sure about this one
|
fn __richcmp__(&self, other: &Self, op: CompareOp) -> bool {
|
||||||
impl SmaliName for IdEnum {
|
op.matches(self.cmp(other))
|
||||||
/// Convert a descriptor to its smali representation.
|
|
||||||
fn try_to_smali(&self) -> Result<String> {
|
|
||||||
self.0.try_to_smali()
|
|
||||||
}
|
|
||||||
/// Convert a smali representation to its descriptor.
|
|
||||||
fn try_from_smali(smali: &str) -> Result<Self> {
|
|
||||||
Ok(Self(IdField::from_smali(smali)?))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait SmaliName: Sized {
|
|
||||||
/// Convert a descriptor to its smali representation.
|
|
||||||
fn try_to_smali(&self) -> Result<String>;
|
|
||||||
/// Convert a smali representation to its descriptor.
|
|
||||||
fn try_from_smali(smali: &str) -> Result<Self>;
|
|
||||||
}
|
|
||||||
|
|
||||||
macro_rules! serde_serialize_to_smali {
|
|
||||||
// This macro takes an argument of designator `ident` and
|
|
||||||
// implement Serialize and Deserialize for the type, assuming
|
|
||||||
// it implement SmaliName.
|
|
||||||
($type_name:ident) => {
|
|
||||||
impl serde::Serialize for $type_name {
|
|
||||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
|
||||||
where
|
|
||||||
S: serde::Serializer,
|
|
||||||
{
|
|
||||||
Serialize::serialize(
|
|
||||||
&self
|
|
||||||
.try_to_smali()
|
|
||||||
.expect(&format!("Failed to convert {} to smali", self.__str__())),
|
|
||||||
serializer,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'de> serde::Deserialize<'de> for $type_name {
|
|
||||||
fn deserialize<D>(deserializer: D) -> Result<$type_name, D::Error>
|
|
||||||
where
|
|
||||||
D: serde::Deserializer<'de>,
|
|
||||||
{
|
|
||||||
<String as Deserialize>::deserialize(deserializer).map(|string| {
|
|
||||||
$type_name::try_from_smali(&string)
|
|
||||||
.expect(&format!("Failed to convert {string} as smali"))
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
serde_serialize_to_smali!(IdMethodType);
|
|
||||||
serde_serialize_to_smali!(IdType);
|
|
||||||
serde_serialize_to_smali!(IdMethod);
|
|
||||||
serde_serialize_to_smali!(IdField);
|
|
||||||
serde_serialize_to_smali!(IdEnum);
|
|
||||||
|
|
|
||||||
|
|
@ -1,56 +1,41 @@
|
||||||
use crate::{Result, Visitable, VisitableMut, Visitor, VisitorMut};
|
use crate::Result;
|
||||||
use anyhow::{Context, Error};
|
|
||||||
use serde::{Deserialize, Deserializer, Serialize, Serializer};
|
use serde::{Deserialize, Deserializer, Serialize, Serializer};
|
||||||
use std::cmp::{Ord, PartialOrd};
|
use std::cmp::{Ord, PartialOrd};
|
||||||
|
use std::collections::hash_map::DefaultHasher;
|
||||||
use std::collections::HashSet;
|
use std::collections::HashSet;
|
||||||
use std::hash::Hash;
|
use std::hash::{Hash, Hasher};
|
||||||
|
|
||||||
#[cfg(feature = "python")]
|
use pyo3::class::basic::CompareOp;
|
||||||
use pyo3::{exceptions::PyTypeError, prelude::*};
|
use pyo3::exceptions::PyTypeError;
|
||||||
|
use pyo3::prelude::*;
|
||||||
|
|
||||||
#[cfg_attr(feature = "python", pyclass(eq, ord, frozen, hash))]
|
#[pyclass]
|
||||||
#[derive(Clone, PartialEq, Eq, Ord, PartialOrd)]
|
#[derive(Clone, PartialEq, Eq, Ord, PartialOrd)]
|
||||||
pub struct DexString(pub androscalpel_serializer::StringDataItem);
|
pub struct DexString(pub androscalpel_serializer::StringDataItem);
|
||||||
|
|
||||||
// Kinda useless
|
|
||||||
impl<V: Visitor> Visitable<V> for DexString {
|
|
||||||
fn default_visit(&self, _: &mut V) -> Result<()> {
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<V: VisitorMut> VisitableMut<V> for DexString {
|
|
||||||
fn default_visit_mut(self, _: &mut V) -> Result<Self> {
|
|
||||||
Ok(self)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl std::fmt::Debug for DexString {
|
impl std::fmt::Debug for DexString {
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
|
||||||
match TryInto::<String>::try_into(self) {
|
if let Ok(string) = TryInto::<String>::try_into(self) {
|
||||||
Ok(string) => {
|
f.write_str(&format!(
|
||||||
f.write_str(&format!(
|
"DexString({}, {:#x})",
|
||||||
"DexString({}, {:#x})",
|
string, self.0.utf16_size.0
|
||||||
string, self.0.utf16_size.0
|
))
|
||||||
))
|
/*
|
||||||
/*
|
f.debug_tuple("DexString")
|
||||||
f.debug_tuple("DexString")
|
.field(&string)
|
||||||
.field(&string)
|
.field(&self.0.utf16_size.0)
|
||||||
.field(&self.0.utf16_size.0)
|
.finish()
|
||||||
.finish()
|
*/
|
||||||
*/
|
} else {
|
||||||
}
|
f.write_str(&format!(
|
||||||
_ => {
|
"DexString({:?}, {:#x})",
|
||||||
f.write_str(&format!(
|
self.0.data, self.0.utf16_size.0
|
||||||
"DexString({:?}, {:#x})",
|
))
|
||||||
self.0.data, self.0.utf16_size.0
|
/*f.debug_tuple("DexString")
|
||||||
))
|
.field(&self.0.data)
|
||||||
/*f.debug_tuple("DexString")
|
.field(&self.0.utf16_size.0)
|
||||||
.field(&self.0.data)
|
.finish()
|
||||||
.field(&self.0.utf16_size.0)
|
*/
|
||||||
.finish()
|
|
||||||
*/
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -120,19 +105,17 @@ impl From<androscalpel_serializer::StringDataItem> for DexString {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TryFrom<&DexString> for String {
|
impl From<&DexString> for String {
|
||||||
type Error = Error;
|
fn from(DexString(string): &DexString) -> Self {
|
||||||
fn try_from(DexString(string): &DexString) -> Result<Self> {
|
|
||||||
string
|
string
|
||||||
.try_into()
|
.try_into()
|
||||||
.with_context(|| format!("InvalidEncoding:{:x?}", string.data))
|
.unwrap_or(format!("InvalidEncoding:{:x?}", string.data))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TryFrom<DexString> for String {
|
impl From<DexString> for String {
|
||||||
type Error = Error;
|
fn from(string: DexString) -> Self {
|
||||||
fn try_from(string: DexString) -> Result<Self> {
|
(&string).into()
|
||||||
(&string).try_into()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -155,11 +138,10 @@ impl Hash for DexString {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "python")]
|
pub fn as_dex_string(obj: &PyAny) -> PyResult<DexString> {
|
||||||
pub fn as_dex_string(obj: &Bound<'_, PyAny>) -> PyResult<DexString> {
|
if let Ok(string) = DexString::extract(obj) {
|
||||||
if let Ok(string) = DexString::extract_bound(obj) {
|
|
||||||
Ok(string)
|
Ok(string)
|
||||||
} else if let Ok(string) = String::extract_bound(obj) {
|
} else if let Ok(string) = String::extract(obj) {
|
||||||
Ok(string.into())
|
Ok(string.into())
|
||||||
} else {
|
} else {
|
||||||
Err(PyErr::new::<PyTypeError, _>(format!(
|
Err(PyErr::new::<PyTypeError, _>(format!(
|
||||||
|
|
@ -169,18 +151,18 @@ pub fn as_dex_string(obj: &Bound<'_, PyAny>) -> PyResult<DexString> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg_attr(feature = "python", pymethods)]
|
#[pymethods]
|
||||||
impl DexString {
|
impl DexString {
|
||||||
pub fn to_json(&self) -> Result<String> {
|
pub fn to_json(&self) -> Result<String> {
|
||||||
Ok(serde_json::to_string(self)?)
|
Ok(serde_json::to_string(self)?)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg_attr(feature = "python", staticmethod)]
|
#[staticmethod]
|
||||||
pub fn from_json(json: &str) -> Result<Self> {
|
pub fn from_json(json: &str) -> Result<Self> {
|
||||||
Ok(serde_json::from_str(json)?)
|
Ok(serde_json::from_str(json)?)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg_attr(feature = "python", new)]
|
#[new]
|
||||||
pub fn new(s: &str) -> Self {
|
pub fn new(s: &str) -> Self {
|
||||||
s.into()
|
s.into()
|
||||||
}
|
}
|
||||||
|
|
@ -196,16 +178,17 @@ impl DexString {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn __str__(&self) -> String {
|
pub fn __str__(&self) -> String {
|
||||||
match TryInto::<String>::try_into(self) {
|
self.into()
|
||||||
Ok(string) => string,
|
|
||||||
_ => {
|
|
||||||
format!("string{:02x?}", self.0.data)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn __repr__(&self) -> String {
|
pub fn __repr__(&self) -> String {
|
||||||
format!("{:?}", self)
|
self.into()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn __hash__(&self) -> u64 {
|
||||||
|
let mut hasher = DefaultHasher::new();
|
||||||
|
self.hash(&mut hasher);
|
||||||
|
hasher.finish()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Return all strings references in the value.
|
/// Return all strings references in the value.
|
||||||
|
|
@ -215,19 +198,10 @@ impl DexString {
|
||||||
strings
|
strings
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Build the concatenation of two string.
|
fn __richcmp__(&self, other: &PyAny, op: CompareOp) -> PyResult<bool> {
|
||||||
pub fn concatenate(&self, other: &Self) -> Self {
|
let other: Self = other
|
||||||
let Self(androscalpel_serializer::StringDataItem {
|
.extract()
|
||||||
utf16_size: androscalpel_serializer::Uleb128(size1),
|
.or(<String as FromPyObject>::extract(other).map(|string| string.into()))?;
|
||||||
data: data1,
|
Ok(op.matches(self.0.cmp(&other.0)))
|
||||||
}) = self.clone();
|
|
||||||
let Self(androscalpel_serializer::StringDataItem {
|
|
||||||
utf16_size: androscalpel_serializer::Uleb128(size2),
|
|
||||||
data: data2,
|
|
||||||
}) = other.clone();
|
|
||||||
Self(androscalpel_serializer::StringDataItem {
|
|
||||||
utf16_size: androscalpel_serializer::Uleb128(size1 + size2),
|
|
||||||
data: data1.into_iter().chain(data2).collect(),
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
File diff suppressed because it is too large
Load diff
3254
androscalpel/src/dex_writer_old.rs
Normal file
3254
androscalpel/src/dex_writer_old.rs
Normal file
File diff suppressed because it is too large
Load diff
|
|
@ -3,55 +3,51 @@
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use std::collections::HashSet;
|
use std::collections::HashSet;
|
||||||
|
|
||||||
#[cfg(feature = "python")]
|
|
||||||
use pyo3::prelude::*;
|
use pyo3::prelude::*;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
DexAnnotationItem, DexString, DexValue, HiddenApiData, IdField, IdMethod, IdMethodType, IdType,
|
DexAnnotationItem, DexString, DexValue, IdField, IdMethod, IdMethodType, IdType, MethodHandle,
|
||||||
MethodHandle, Result, Visitable, VisitableMut, Visitor, VisitorMut,
|
Result,
|
||||||
};
|
};
|
||||||
use androscalpel_serializer::consts::*;
|
use androscalpel_serializer::consts::*;
|
||||||
|
|
||||||
/// Represent a field.
|
/// Represent a field.
|
||||||
#[cfg_attr(feature = "python", pyclass(eq))]
|
#[pyclass]
|
||||||
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)]
|
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)]
|
||||||
pub struct Field {
|
pub struct Field {
|
||||||
/// The structure used to reference this field.
|
/// The structure used to reference this field.
|
||||||
#[cfg_attr(feature = "python", pyo3(get))]
|
#[pyo3(get)]
|
||||||
pub descriptor: IdField,
|
pub descriptor: IdField,
|
||||||
/// The field visibility
|
/// The field visibility
|
||||||
#[cfg_attr(feature = "python", pyo3(get))]
|
#[pyo3(get)]
|
||||||
pub visibility: FieldVisibility,
|
pub visibility: FieldVisibility,
|
||||||
/// If the field is defined for the class globally
|
/// If the field is defined for the class globally
|
||||||
#[cfg_attr(feature = "python", pyo3(get))]
|
#[pyo3(get)]
|
||||||
pub is_static: bool,
|
pub is_static: bool,
|
||||||
/// If the field is immutable after construction
|
/// If the field is immutable after construction
|
||||||
#[cfg_attr(feature = "python", pyo3(get))]
|
#[pyo3(get)]
|
||||||
pub is_final: bool,
|
pub is_final: bool,
|
||||||
/// For thread safety
|
/// For thread safety
|
||||||
#[cfg_attr(feature = "python", pyo3(get))]
|
#[pyo3(get)]
|
||||||
pub is_volatile: bool,
|
pub is_volatile: bool,
|
||||||
/// If the field should **not** be saved by default serialization
|
/// If the field should **not** be saved by default serialization
|
||||||
#[cfg_attr(feature = "python", pyo3(get))]
|
#[pyo3(get)]
|
||||||
pub is_transient: bool,
|
pub is_transient: bool,
|
||||||
/// If the field is not defined in the source code
|
/// If the field is not defined in the source code
|
||||||
#[cfg_attr(feature = "python", pyo3(get))]
|
#[pyo3(get)]
|
||||||
pub is_synthetic: bool,
|
pub is_synthetic: bool,
|
||||||
/// If the field is an enumerated value
|
/// If the field is an enumerated value
|
||||||
#[cfg_attr(feature = "python", pyo3(get))]
|
#[pyo3(get)]
|
||||||
pub is_enum: bool,
|
pub is_enum: bool,
|
||||||
/// The default value of this field
|
/// The default value of this field
|
||||||
pub value: Option<DexValue>,
|
pub value: Option<DexValue>,
|
||||||
/// The annotations for this field
|
/// The annotations for this field
|
||||||
#[cfg_attr(feature = "python", pyo3(get))]
|
#[pyo3(get)]
|
||||||
pub annotations: Vec<DexAnnotationItem>,
|
pub annotations: Vec<DexAnnotationItem>,
|
||||||
/// Hidden Api data.
|
|
||||||
#[cfg_attr(feature = "python", pyo3(get))]
|
|
||||||
pub hiddenapi: Option<HiddenApiData>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Represent the visibility of a field
|
/// Represent the visibility of a field
|
||||||
#[cfg_attr(feature = "python", pyclass(eq, eq_int))]
|
#[pyclass]
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Deserialize, Serialize)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Deserialize, Serialize)]
|
||||||
pub enum FieldVisibility {
|
pub enum FieldVisibility {
|
||||||
Public,
|
Public,
|
||||||
|
|
@ -60,18 +56,18 @@ pub enum FieldVisibility {
|
||||||
None_, // Actually quite common
|
None_, // Actually quite common
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg_attr(feature = "python", pymethods)]
|
#[pymethods]
|
||||||
impl Field {
|
impl Field {
|
||||||
pub fn to_json(&self) -> Result<String> {
|
pub fn to_json(&self) -> Result<String> {
|
||||||
Ok(serde_json::to_string(self)?)
|
Ok(serde_json::to_string(self)?)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg_attr(feature = "python", staticmethod)]
|
#[staticmethod]
|
||||||
pub fn from_json(json: &str) -> Result<Self> {
|
pub fn from_json(json: &str) -> Result<Self> {
|
||||||
Ok(serde_json::from_str(json)?)
|
Ok(serde_json::from_str(json)?)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg_attr(feature = "python", new)]
|
#[new]
|
||||||
pub fn new(descriptor: IdField) -> Self {
|
pub fn new(descriptor: IdField) -> Self {
|
||||||
Self {
|
Self {
|
||||||
descriptor,
|
descriptor,
|
||||||
|
|
@ -84,7 +80,6 @@ impl Field {
|
||||||
is_enum: false,
|
is_enum: false,
|
||||||
value: None,
|
value: None,
|
||||||
annotations: vec![],
|
annotations: vec![],
|
||||||
hiddenapi: None,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -125,16 +120,14 @@ impl Field {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Return the value as a python object
|
/// Return the value as a python object
|
||||||
#[cfg(feature = "python")]
|
|
||||||
#[getter]
|
#[getter]
|
||||||
pub fn get_value(&self) -> Option<DexValue> {
|
pub fn get_value(&self) -> Option<DexValue> {
|
||||||
self.value.clone()
|
self.value.clone()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Set the value from a python object
|
/// Set the value from a python object
|
||||||
#[cfg(feature = "python")]
|
|
||||||
#[setter]
|
#[setter]
|
||||||
pub fn set_value(&mut self, ob: &Bound<'_, PyAny>) -> PyResult<()> {
|
pub fn set_value(&mut self, ob: &PyAny) -> PyResult<()> {
|
||||||
self.value = Some(ob.extract()?);
|
self.value = Some(ob.extract()?);
|
||||||
// TODO: check type match
|
// TODO: check type match
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|
@ -256,46 +249,7 @@ impl Field {
|
||||||
!self.annotations.is_empty()
|
!self.annotations.is_empty()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Check if the field is a platform field (ie in the android SDK or a hidden API).
|
pub fn __eq__(&self, other: &Self) -> bool {
|
||||||
#[cfg(feature = "platform-list")]
|
self == other
|
||||||
pub fn is_platform_field(&self) -> bool {
|
|
||||||
self.descriptor.is_platform_field()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<V: Visitor> Visitable<V> for Field {
|
|
||||||
fn default_visit(&self, v: &mut V) -> Result<()> {
|
|
||||||
v.visit_field_id(&self.descriptor)?;
|
|
||||||
v.visit_field_visibility(&self.visibility)?;
|
|
||||||
if let Some(val) = &self.value {
|
|
||||||
v.visit_value(val)?;
|
|
||||||
}
|
|
||||||
for annot in &self.annotations {
|
|
||||||
v.visit_annotation_item(annot)?;
|
|
||||||
}
|
|
||||||
if let Some(hiddenapi) = &self.hiddenapi {
|
|
||||||
v.visit_hidden_api_data(hiddenapi)?;
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl<V: VisitorMut> VisitableMut<V> for Field {
|
|
||||||
fn default_visit_mut(self, v: &mut V) -> Result<Self> {
|
|
||||||
Ok(Self {
|
|
||||||
descriptor: v.visit_field_id(self.descriptor)?,
|
|
||||||
visibility: v.visit_field_visibility(self.visibility)?,
|
|
||||||
value: self.value.map(|val| v.visit_value(val)).transpose()?,
|
|
||||||
annotations: self
|
|
||||||
.annotations
|
|
||||||
.into_iter()
|
|
||||||
.map(|annot| v.visit_annotation_item(annot))
|
|
||||||
.collect::<Result<_>>()?,
|
|
||||||
hiddenapi: self
|
|
||||||
.hiddenapi
|
|
||||||
.map(|hiddenapi| v.visit_hidden_api_data(hiddenapi))
|
|
||||||
.transpose()?,
|
|
||||||
..self
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,211 +0,0 @@
|
||||||
//! Representation of the hidden API data
|
|
||||||
|
|
||||||
use crate::{Result, Visitable, VisitableMut, Visitor, VisitorMut};
|
|
||||||
use log::warn;
|
|
||||||
#[cfg(feature = "python")]
|
|
||||||
use pyo3::prelude::*;
|
|
||||||
use serde::{Deserialize, Serialize};
|
|
||||||
|
|
||||||
#[cfg_attr(feature = "python", pyclass)]
|
|
||||||
#[derive(Debug, PartialEq, Eq, Hash, Clone, Deserialize, Serialize)]
|
|
||||||
pub struct HiddenApiData {
|
|
||||||
pub permission: HiddenApiPermission,
|
|
||||||
pub domain: HiddenApiDomain,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<&androscalpel_serializer::HiddenApiFlags> for HiddenApiData {
|
|
||||||
fn from(flags: &androscalpel_serializer::HiddenApiFlags) -> Self {
|
|
||||||
Self {
|
|
||||||
permission: flags.value.into(),
|
|
||||||
domain: flags.domain_api.into(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl From<androscalpel_serializer::HiddenApiFlags> for HiddenApiData {
|
|
||||||
fn from(flags: androscalpel_serializer::HiddenApiFlags) -> Self {
|
|
||||||
(&flags).into()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<&HiddenApiData> for androscalpel_serializer::HiddenApiFlags {
|
|
||||||
fn from(flags: &HiddenApiData) -> Self {
|
|
||||||
Self {
|
|
||||||
value: (&flags.permission).into(),
|
|
||||||
domain_api: (&flags.domain).into(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl From<HiddenApiData> for androscalpel_serializer::HiddenApiFlags {
|
|
||||||
fn from(flags: HiddenApiData) -> Self {
|
|
||||||
(&flags).into()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<V: Visitor> Visitable<V> for HiddenApiData {
|
|
||||||
fn default_visit(&self, v: &mut V) -> Result<()> {
|
|
||||||
v.visit_hidden_api_permission(&self.permission)?;
|
|
||||||
v.visit_hidden_api_domain(&self.domain)?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl<V: VisitorMut> VisitableMut<V> for HiddenApiData {
|
|
||||||
fn default_visit_mut(self, v: &mut V) -> Result<Self> {
|
|
||||||
Ok(Self {
|
|
||||||
permission: v.visit_hidden_api_permission(self.permission)?,
|
|
||||||
domain: v.visit_hidden_api_domain(self.domain)?,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg_attr(feature = "python", pyclass)]
|
|
||||||
#[derive(Debug, PartialEq, Eq, Hash, Clone, Deserialize, Serialize)]
|
|
||||||
pub enum HiddenApiPermission {
|
|
||||||
/// Interfaces that can be freely used and are supported as
|
|
||||||
/// part of the officially documented Android framework Package Index
|
|
||||||
Whitelist {},
|
|
||||||
/// Non-SDK interfaces that can be used regardless of the
|
|
||||||
/// application's target API level
|
|
||||||
Greylist {},
|
|
||||||
/// Non-SDK interfaces that cannot be used regardless of the
|
|
||||||
/// application's target API level. Accessing one of these
|
|
||||||
/// interfaces causes a runtime error.
|
|
||||||
Blacklist {},
|
|
||||||
/// Non-SDK interfaces that can be used for Android 8.x and
|
|
||||||
/// below unless they are restricted (targetSdkVersion <= 27 (O_MR1)).
|
|
||||||
GreylistMaxO {},
|
|
||||||
/// Non-SDK interfaces that can be used for Android 9.x unless
|
|
||||||
/// they are restricted.
|
|
||||||
GreylistMaxP {},
|
|
||||||
/// Non-SDK interfaces that can be used for Android 10.x unless
|
|
||||||
/// they are restricted.
|
|
||||||
GreylistMaxQ {},
|
|
||||||
/// Non-SDK interfaces that can be used for Android 11.x unless
|
|
||||||
/// they are restricted.
|
|
||||||
GreylistMaxR {},
|
|
||||||
GreylistMaxS {},
|
|
||||||
/// Unknown flag, either an error or this crate is out of date.
|
|
||||||
Unknwon {
|
|
||||||
value: u8,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
impl HiddenApiPermission {
|
|
||||||
pub fn to_smali_name(&self) -> &'static str {
|
|
||||||
match self {
|
|
||||||
Self::Whitelist {} => "whitelist",
|
|
||||||
Self::Greylist {} => "greylist",
|
|
||||||
Self::Blacklist {} => "blacklist",
|
|
||||||
Self::GreylistMaxO {} => "greylist-max-o",
|
|
||||||
Self::GreylistMaxP {} => "greylist-max-p",
|
|
||||||
Self::GreylistMaxQ {} => "greylist-max-q",
|
|
||||||
Self::GreylistMaxR {} => "greylist-max-r",
|
|
||||||
Self::GreylistMaxS {} => "greylist-max-s",
|
|
||||||
Self::Unknwon { .. } => "???list",
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<&androscalpel_serializer::HiddenApiValue> for HiddenApiPermission {
|
|
||||||
fn from(permission: &androscalpel_serializer::HiddenApiValue) -> Self {
|
|
||||||
match permission {
|
|
||||||
androscalpel_serializer::HiddenApiValue::Whitelist => Self::Whitelist {},
|
|
||||||
androscalpel_serializer::HiddenApiValue::Greylist => Self::Greylist {},
|
|
||||||
androscalpel_serializer::HiddenApiValue::Blacklist => Self::Blacklist {},
|
|
||||||
androscalpel_serializer::HiddenApiValue::GreylistMaxO => Self::GreylistMaxO {},
|
|
||||||
androscalpel_serializer::HiddenApiValue::GreylistMaxP => Self::GreylistMaxP {},
|
|
||||||
androscalpel_serializer::HiddenApiValue::GreylistMaxQ => Self::GreylistMaxQ {},
|
|
||||||
androscalpel_serializer::HiddenApiValue::GreylistMaxR => Self::GreylistMaxR {},
|
|
||||||
androscalpel_serializer::HiddenApiValue::GreylistMaxS => Self::GreylistMaxS {},
|
|
||||||
androscalpel_serializer::HiddenApiValue::Unknwon(val) => Self::Unknwon { value: *val },
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl From<androscalpel_serializer::HiddenApiValue> for HiddenApiPermission {
|
|
||||||
fn from(permission: androscalpel_serializer::HiddenApiValue) -> Self {
|
|
||||||
(&permission).into()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<&HiddenApiPermission> for androscalpel_serializer::HiddenApiValue {
|
|
||||||
fn from(permission: &HiddenApiPermission) -> Self {
|
|
||||||
match permission {
|
|
||||||
HiddenApiPermission::Whitelist {} => Self::Whitelist,
|
|
||||||
HiddenApiPermission::Greylist {} => Self::Greylist,
|
|
||||||
HiddenApiPermission::Blacklist {} => Self::Blacklist,
|
|
||||||
HiddenApiPermission::GreylistMaxO {} => Self::GreylistMaxO,
|
|
||||||
HiddenApiPermission::GreylistMaxP {} => Self::GreylistMaxP,
|
|
||||||
HiddenApiPermission::GreylistMaxQ {} => Self::GreylistMaxQ,
|
|
||||||
HiddenApiPermission::GreylistMaxR {} => Self::GreylistMaxR,
|
|
||||||
HiddenApiPermission::GreylistMaxS {} => Self::GreylistMaxS,
|
|
||||||
HiddenApiPermission::Unknwon { value } => Self::Unknwon(*value),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<HiddenApiPermission> for androscalpel_serializer::HiddenApiValue {
|
|
||||||
fn from(permission: HiddenApiPermission) -> Self {
|
|
||||||
(&permission).into()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg_attr(feature = "python", pyclass)]
|
|
||||||
#[derive(Debug, PartialEq, Eq, Hash, Clone, Deserialize, Serialize)]
|
|
||||||
pub struct HiddenApiDomain {
|
|
||||||
pub is_core_platform_api: bool,
|
|
||||||
pub is_test_api: bool,
|
|
||||||
/// Unknown domains.
|
|
||||||
pub unknown_flags: u32,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl HiddenApiDomain {
|
|
||||||
pub fn to_smali_name(&self) -> String {
|
|
||||||
let mut value = String::new();
|
|
||||||
if self.is_core_platform_api {
|
|
||||||
value += "core-platform-api";
|
|
||||||
}
|
|
||||||
if self.is_test_api {
|
|
||||||
if !value.is_empty() {
|
|
||||||
value += "|";
|
|
||||||
}
|
|
||||||
value += "test-api";
|
|
||||||
}
|
|
||||||
if self.unknown_flags != 0 {
|
|
||||||
warn!(
|
|
||||||
"This attribut has an unknown hiddenapi domain set, this will not be display for compatibility with apktool"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
value
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<&androscalpel_serializer::HiddenApiDomain> for HiddenApiDomain {
|
|
||||||
fn from(domain: &androscalpel_serializer::HiddenApiDomain) -> Self {
|
|
||||||
Self {
|
|
||||||
is_core_platform_api: domain.is_core_platform_api,
|
|
||||||
is_test_api: domain.is_test_api,
|
|
||||||
unknown_flags: domain.unknown_flags,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<androscalpel_serializer::HiddenApiDomain> for HiddenApiDomain {
|
|
||||||
fn from(domain: androscalpel_serializer::HiddenApiDomain) -> Self {
|
|
||||||
(&domain).into()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<&HiddenApiDomain> for androscalpel_serializer::HiddenApiDomain {
|
|
||||||
fn from(domain: &HiddenApiDomain) -> androscalpel_serializer::HiddenApiDomain {
|
|
||||||
Self {
|
|
||||||
is_core_platform_api: domain.is_core_platform_api,
|
|
||||||
is_test_api: domain.is_test_api,
|
|
||||||
unknown_flags: domain.unknown_flags,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<HiddenApiDomain> for androscalpel_serializer::HiddenApiDomain {
|
|
||||||
fn from(domain: HiddenApiDomain) -> androscalpel_serializer::HiddenApiDomain {
|
|
||||||
(&domain).into()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
File diff suppressed because it is too large
Load diff
|
|
@ -1,30 +1,23 @@
|
||||||
pub use anyhow::Result;
|
use anyhow::Result;
|
||||||
|
|
||||||
#[cfg(feature = "python")]
|
|
||||||
use pyo3::prelude::*;
|
use pyo3::prelude::*;
|
||||||
|
|
||||||
pub mod annotation;
|
pub mod annotation;
|
||||||
pub mod apk;
|
pub mod apk;
|
||||||
pub mod class;
|
pub mod class;
|
||||||
pub mod code;
|
pub mod code;
|
||||||
|
pub mod dex_fragment;
|
||||||
pub mod dex_id;
|
pub mod dex_id;
|
||||||
pub mod dex_string;
|
pub mod dex_string;
|
||||||
pub mod dex_writer;
|
pub mod dex_writer;
|
||||||
pub mod field;
|
pub mod field;
|
||||||
mod hashmap_vectorize;
|
mod hashmap_vectorize;
|
||||||
pub mod hiddenapi;
|
|
||||||
pub mod instructions;
|
pub mod instructions;
|
||||||
pub mod method;
|
pub mod method;
|
||||||
pub mod method_handle;
|
pub mod method_handle;
|
||||||
#[cfg(feature = "python")]
|
|
||||||
pub mod py_utils;
|
pub mod py_utils;
|
||||||
pub mod scalar;
|
pub mod scalar;
|
||||||
pub mod utils;
|
|
||||||
pub mod value;
|
pub mod value;
|
||||||
pub mod visitor;
|
|
||||||
|
|
||||||
#[cfg(feature = "code-analysis")]
|
|
||||||
pub mod code_analysis;
|
|
||||||
|
|
||||||
pub use annotation::*;
|
pub use annotation::*;
|
||||||
pub use apk::*;
|
pub use apk::*;
|
||||||
|
|
@ -34,26 +27,18 @@ pub use dex_id::*;
|
||||||
pub use dex_string::*;
|
pub use dex_string::*;
|
||||||
pub use dex_writer::*;
|
pub use dex_writer::*;
|
||||||
pub use field::*;
|
pub use field::*;
|
||||||
pub use hiddenapi::*;
|
|
||||||
pub use instructions as ins;
|
pub use instructions as ins;
|
||||||
pub use instructions::*;
|
|
||||||
pub use method::*;
|
pub use method::*;
|
||||||
pub use method_handle::*;
|
pub use method_handle::*;
|
||||||
pub use scalar::*;
|
pub use scalar::*;
|
||||||
pub use utils::*;
|
|
||||||
pub use value::*;
|
pub use value::*;
|
||||||
pub use visitor::*;
|
|
||||||
|
|
||||||
#[cfg(feature = "code-analysis")]
|
|
||||||
pub use code_analysis::*;
|
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests;
|
mod tests;
|
||||||
|
|
||||||
/// Androscalpel.
|
/// Androscalpel.
|
||||||
#[cfg(feature = "python")]
|
|
||||||
#[pymodule]
|
#[pymodule]
|
||||||
fn androscalpel(py: Python, m: &Bound<'_, PyModule>) -> PyResult<()> {
|
fn androscalpel(py: Python, m: &PyModule) -> PyResult<()> {
|
||||||
pyo3_log::init();
|
pyo3_log::init();
|
||||||
m.add_class::<DexNull>()?;
|
m.add_class::<DexNull>()?;
|
||||||
m.add_class::<DexBoolean>()?;
|
m.add_class::<DexBoolean>()?;
|
||||||
|
|
@ -72,9 +57,15 @@ fn androscalpel(py: Python, m: &Bound<'_, PyModule>) -> PyResult<()> {
|
||||||
m.add_class::<IdMethod>()?;
|
m.add_class::<IdMethod>()?;
|
||||||
m.add_class::<IdEnum>()?;
|
m.add_class::<IdEnum>()?;
|
||||||
|
|
||||||
m.add_class::<HiddenApiData>()?;
|
m.add_class::<StaticPut>()?;
|
||||||
m.add_class::<HiddenApiPermission>()?;
|
m.add_class::<StaticGet>()?;
|
||||||
m.add_class::<HiddenApiDomain>()?;
|
m.add_class::<InstancePut>()?;
|
||||||
|
m.add_class::<InstanceGet>()?;
|
||||||
|
m.add_class::<InvokeStatic>()?;
|
||||||
|
m.add_class::<InvokeInstance>()?;
|
||||||
|
m.add_class::<InvokeConstructor>()?;
|
||||||
|
m.add_class::<InvokeDirect>()?;
|
||||||
|
m.add_class::<InvokeInterface>()?;
|
||||||
|
|
||||||
m.add_class::<DexAnnotationItem>()?;
|
m.add_class::<DexAnnotationItem>()?;
|
||||||
m.add_class::<DexAnnotation>()?;
|
m.add_class::<DexAnnotation>()?;
|
||||||
|
|
@ -88,19 +79,211 @@ fn androscalpel(py: Python, m: &Bound<'_, PyModule>) -> PyResult<()> {
|
||||||
m.add_class::<Apk>()?;
|
m.add_class::<Apk>()?;
|
||||||
|
|
||||||
let ins_module = PyModule::new(py, "ins")?;
|
let ins_module = PyModule::new(py, "ins")?;
|
||||||
androscalpel_ins(py, &ins_module)?;
|
androscalpel_ins(py, ins_module)?;
|
||||||
m.add_submodule(&ins_module)?;
|
m.add_submodule(ins_module)?;
|
||||||
let utils_module = PyModule::new(py, "utils")?;
|
let utils_module = PyModule::new(py, "utils")?;
|
||||||
py_utils::export_module(py, &utils_module)?;
|
py_utils::export_module(py, utils_module)?;
|
||||||
m.add_submodule(&utils_module)?;
|
m.add_submodule(utils_module)?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Dalvik opcode for Androscalpel.
|
/// Dalvik opcode for Androscalpel.
|
||||||
#[cfg(feature = "python")]
|
fn androscalpel_ins(_py: Python, m: &PyModule) -> PyResult<()> {
|
||||||
fn androscalpel_ins(_py: Python, m: &Bound<'_, PyModule>) -> PyResult<()> {
|
|
||||||
m.add_class::<ins::CallSite>()?;
|
m.add_class::<ins::CallSite>()?;
|
||||||
m.add_class::<ins::Instruction>()?;
|
m.add_class::<ins::Nop>()?;
|
||||||
|
m.add_class::<ins::Move>()?;
|
||||||
|
m.add_class::<ins::MoveWide>()?;
|
||||||
|
m.add_class::<ins::MoveObject>()?;
|
||||||
|
m.add_class::<ins::MoveResult>()?;
|
||||||
|
m.add_class::<ins::MoveResultWide>()?;
|
||||||
|
m.add_class::<ins::MoveResultObject>()?;
|
||||||
|
m.add_class::<ins::MoveException>()?;
|
||||||
|
m.add_class::<ins::ReturnVoid>()?;
|
||||||
|
m.add_class::<ins::Return>()?;
|
||||||
|
m.add_class::<ins::ReturnWide>()?;
|
||||||
|
m.add_class::<ins::ReturnObject>()?;
|
||||||
|
m.add_class::<ins::Const>()?;
|
||||||
|
m.add_class::<ins::ConstWide>()?;
|
||||||
|
m.add_class::<ins::ConstString>()?;
|
||||||
|
m.add_class::<ins::ConstClass>()?;
|
||||||
|
m.add_class::<ins::MonitorEnter>()?;
|
||||||
|
m.add_class::<ins::MonitorExit>()?;
|
||||||
|
m.add_class::<ins::CheckCast>()?;
|
||||||
|
m.add_class::<ins::InstanceOf>()?;
|
||||||
|
m.add_class::<ins::ArrayLength>()?;
|
||||||
|
m.add_class::<ins::NewInstance>()?;
|
||||||
|
m.add_class::<ins::NewArray>()?;
|
||||||
|
m.add_class::<ins::FilledNewArray>()?;
|
||||||
|
m.add_class::<ins::FillArrayData>()?;
|
||||||
|
m.add_class::<ins::Throw>()?;
|
||||||
|
m.add_class::<ins::Goto>()?;
|
||||||
|
m.add_class::<ins::Switch>()?;
|
||||||
|
m.add_class::<ins::CmpLFloat>()?;
|
||||||
|
m.add_class::<ins::CmpGFloat>()?;
|
||||||
|
m.add_class::<ins::CmpLDouble>()?;
|
||||||
|
m.add_class::<ins::CmpGDouble>()?;
|
||||||
|
m.add_class::<ins::CmpLong>()?;
|
||||||
|
m.add_class::<ins::IfEq>()?;
|
||||||
|
m.add_class::<ins::IfNe>()?;
|
||||||
|
m.add_class::<ins::IfLt>()?;
|
||||||
|
m.add_class::<ins::IfGe>()?;
|
||||||
|
m.add_class::<ins::IfGt>()?;
|
||||||
|
m.add_class::<ins::IfLe>()?;
|
||||||
|
m.add_class::<ins::IfEqZ>()?;
|
||||||
|
m.add_class::<ins::IfNeZ>()?;
|
||||||
|
m.add_class::<ins::IfLtZ>()?;
|
||||||
|
m.add_class::<ins::IfGeZ>()?;
|
||||||
|
m.add_class::<ins::IfGtZ>()?;
|
||||||
|
m.add_class::<ins::IfLeZ>()?;
|
||||||
|
m.add_class::<ins::AGet>()?;
|
||||||
|
m.add_class::<ins::AGetWide>()?;
|
||||||
|
m.add_class::<ins::AGetObject>()?;
|
||||||
|
m.add_class::<ins::AGetBoolean>()?;
|
||||||
|
m.add_class::<ins::AGetByte>()?;
|
||||||
|
m.add_class::<ins::AGetChar>()?;
|
||||||
|
m.add_class::<ins::AGetShort>()?;
|
||||||
|
m.add_class::<ins::APut>()?;
|
||||||
|
m.add_class::<ins::APutWide>()?;
|
||||||
|
m.add_class::<ins::APutObject>()?;
|
||||||
|
m.add_class::<ins::APutBoolean>()?;
|
||||||
|
m.add_class::<ins::APutByte>()?;
|
||||||
|
m.add_class::<ins::APutChar>()?;
|
||||||
|
m.add_class::<ins::APutShort>()?;
|
||||||
|
m.add_class::<ins::IGet>()?;
|
||||||
|
m.add_class::<ins::IGetWide>()?;
|
||||||
|
m.add_class::<ins::IGetObject>()?;
|
||||||
|
m.add_class::<ins::IGetBoolean>()?;
|
||||||
|
m.add_class::<ins::IGetByte>()?;
|
||||||
|
m.add_class::<ins::IGetChar>()?;
|
||||||
|
m.add_class::<ins::IGetShort>()?;
|
||||||
|
m.add_class::<ins::IPut>()?;
|
||||||
|
m.add_class::<ins::IPutWide>()?;
|
||||||
|
m.add_class::<ins::IPutObject>()?;
|
||||||
|
m.add_class::<ins::IPutBoolean>()?;
|
||||||
|
m.add_class::<ins::IPutByte>()?;
|
||||||
|
m.add_class::<ins::IPutChar>()?;
|
||||||
|
m.add_class::<ins::IPutShort>()?;
|
||||||
|
m.add_class::<ins::SGet>()?;
|
||||||
|
m.add_class::<ins::SGetWide>()?;
|
||||||
|
m.add_class::<ins::SGetObject>()?;
|
||||||
|
m.add_class::<ins::SGetBoolean>()?;
|
||||||
|
m.add_class::<ins::SGetByte>()?;
|
||||||
|
m.add_class::<ins::SGetChar>()?;
|
||||||
|
m.add_class::<ins::SGetShort>()?;
|
||||||
|
m.add_class::<ins::SPut>()?;
|
||||||
|
m.add_class::<ins::SPutWide>()?;
|
||||||
|
m.add_class::<ins::SPutObject>()?;
|
||||||
|
m.add_class::<ins::SPutBoolean>()?;
|
||||||
|
m.add_class::<ins::SPutByte>()?;
|
||||||
|
m.add_class::<ins::SPutChar>()?;
|
||||||
|
m.add_class::<ins::SPutShort>()?;
|
||||||
|
m.add_class::<ins::InvokeVirtual>()?;
|
||||||
|
m.add_class::<ins::InvokeSuper>()?;
|
||||||
|
m.add_class::<ins::InvokeDirect>()?;
|
||||||
|
m.add_class::<ins::InvokeStatic>()?;
|
||||||
|
m.add_class::<ins::InvokeInterface>()?;
|
||||||
|
m.add_class::<ins::NegInt>()?;
|
||||||
|
m.add_class::<ins::NotInt>()?;
|
||||||
|
m.add_class::<ins::NegLong>()?;
|
||||||
|
m.add_class::<ins::NotLong>()?;
|
||||||
|
m.add_class::<ins::NegFloat>()?;
|
||||||
|
m.add_class::<ins::NegDouble>()?;
|
||||||
|
m.add_class::<ins::IntToLong>()?;
|
||||||
|
m.add_class::<ins::IntToFloat>()?;
|
||||||
|
m.add_class::<ins::IntToDouble>()?;
|
||||||
|
m.add_class::<ins::LongToInt>()?;
|
||||||
|
m.add_class::<ins::LongToFloat>()?;
|
||||||
|
m.add_class::<ins::LongToDouble>()?;
|
||||||
|
m.add_class::<ins::FloatToInt>()?;
|
||||||
|
m.add_class::<ins::FloatToLong>()?;
|
||||||
|
m.add_class::<ins::FloatToDouble>()?;
|
||||||
|
m.add_class::<ins::DoubleToInt>()?;
|
||||||
|
m.add_class::<ins::DoubleToLong>()?;
|
||||||
|
m.add_class::<ins::DoubleToFloat>()?;
|
||||||
|
m.add_class::<ins::IntToByte>()?;
|
||||||
|
m.add_class::<ins::IntToChar>()?;
|
||||||
|
m.add_class::<ins::IntToShort>()?;
|
||||||
|
m.add_class::<ins::AddInt>()?;
|
||||||
|
m.add_class::<ins::SubInt>()?;
|
||||||
|
m.add_class::<ins::MulInt>()?;
|
||||||
|
m.add_class::<ins::DivInt>()?;
|
||||||
|
m.add_class::<ins::RemInt>()?;
|
||||||
|
m.add_class::<ins::AndInt>()?;
|
||||||
|
m.add_class::<ins::OrInt>()?;
|
||||||
|
m.add_class::<ins::XorInt>()?;
|
||||||
|
m.add_class::<ins::ShlInt>()?;
|
||||||
|
m.add_class::<ins::ShrInt>()?;
|
||||||
|
m.add_class::<ins::UshrInt>()?;
|
||||||
|
m.add_class::<ins::AddLong>()?;
|
||||||
|
m.add_class::<ins::SubLong>()?;
|
||||||
|
m.add_class::<ins::MulLong>()?;
|
||||||
|
m.add_class::<ins::DivLong>()?;
|
||||||
|
m.add_class::<ins::RemLong>()?;
|
||||||
|
m.add_class::<ins::AndLong>()?;
|
||||||
|
m.add_class::<ins::OrLong>()?;
|
||||||
|
m.add_class::<ins::XorLong>()?;
|
||||||
|
m.add_class::<ins::ShlLong>()?;
|
||||||
|
m.add_class::<ins::ShrLong>()?;
|
||||||
|
m.add_class::<ins::UshrLong>()?;
|
||||||
|
m.add_class::<ins::AddFloat>()?;
|
||||||
|
m.add_class::<ins::SubFloat>()?;
|
||||||
|
m.add_class::<ins::MulFloat>()?;
|
||||||
|
m.add_class::<ins::DivFloat>()?;
|
||||||
|
m.add_class::<ins::RemFloat>()?;
|
||||||
|
m.add_class::<ins::AddDouble>()?;
|
||||||
|
m.add_class::<ins::SubDouble>()?;
|
||||||
|
m.add_class::<ins::MulDouble>()?;
|
||||||
|
m.add_class::<ins::DivDouble>()?;
|
||||||
|
m.add_class::<ins::RemDouble>()?;
|
||||||
|
m.add_class::<ins::AddInt2Addr>()?;
|
||||||
|
m.add_class::<ins::SubInt2Addr>()?;
|
||||||
|
m.add_class::<ins::MulInt2Addr>()?;
|
||||||
|
m.add_class::<ins::DivInt2Addr>()?;
|
||||||
|
m.add_class::<ins::RemInt2Addr>()?;
|
||||||
|
m.add_class::<ins::AndInt2Addr>()?;
|
||||||
|
m.add_class::<ins::OrInt2Addr>()?;
|
||||||
|
m.add_class::<ins::XorInt2Addr>()?;
|
||||||
|
m.add_class::<ins::ShlInt2Addr>()?;
|
||||||
|
m.add_class::<ins::ShrInt2Addr>()?;
|
||||||
|
m.add_class::<ins::UshrInt2Addr>()?;
|
||||||
|
m.add_class::<ins::AddLong2Addr>()?;
|
||||||
|
m.add_class::<ins::SubLong2Addr>()?;
|
||||||
|
m.add_class::<ins::MulLong2Addr>()?;
|
||||||
|
m.add_class::<ins::DivLong2Addr>()?;
|
||||||
|
m.add_class::<ins::RemLong2Addr>()?;
|
||||||
|
m.add_class::<ins::AndLong2Addr>()?;
|
||||||
|
m.add_class::<ins::OrLong2Addr>()?;
|
||||||
|
m.add_class::<ins::XorLong2Addr>()?;
|
||||||
|
m.add_class::<ins::ShlLong2Addr>()?;
|
||||||
|
m.add_class::<ins::ShrLong2Addr>()?;
|
||||||
|
m.add_class::<ins::UshrLong2Addr>()?;
|
||||||
|
m.add_class::<ins::AddFloat2Addr>()?;
|
||||||
|
m.add_class::<ins::SubFloat2Addr>()?;
|
||||||
|
m.add_class::<ins::MulFloat2Addr>()?;
|
||||||
|
m.add_class::<ins::DivFloat2Addr>()?;
|
||||||
|
m.add_class::<ins::RemFloat2Addr>()?;
|
||||||
|
m.add_class::<ins::AddDouble2Addr>()?;
|
||||||
|
m.add_class::<ins::SubDouble2Addr>()?;
|
||||||
|
m.add_class::<ins::MulDouble2Addr>()?;
|
||||||
|
m.add_class::<ins::DivDouble2Addr>()?;
|
||||||
|
m.add_class::<ins::RemDouble2Addr>()?;
|
||||||
|
m.add_class::<ins::AddIntLit>()?;
|
||||||
|
m.add_class::<ins::RsubIntLit>()?;
|
||||||
|
m.add_class::<ins::MulIntLit>()?;
|
||||||
|
m.add_class::<ins::DivIntLit>()?;
|
||||||
|
m.add_class::<ins::RemIntLit>()?;
|
||||||
|
m.add_class::<ins::AndIntLit>()?;
|
||||||
|
m.add_class::<ins::OrIntLit>()?;
|
||||||
|
m.add_class::<ins::XorIntLit>()?;
|
||||||
|
m.add_class::<ins::ShlIntLit>()?;
|
||||||
|
m.add_class::<ins::ShrIntLit>()?;
|
||||||
|
m.add_class::<ins::UshrIntLit>()?;
|
||||||
|
m.add_class::<ins::InvokePolymorphic>()?;
|
||||||
|
m.add_class::<ins::InvokeCustom>()?;
|
||||||
|
m.add_class::<ins::ConstMethodHandle>()?;
|
||||||
|
m.add_class::<ins::ConstMethodType>()?;
|
||||||
|
m.add_class::<ins::Try>()?;
|
||||||
|
m.add_class::<ins::Label>()?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,77 +3,73 @@
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use std::collections::HashSet;
|
use std::collections::HashSet;
|
||||||
|
|
||||||
#[cfg(feature = "python")]
|
|
||||||
use pyo3::prelude::*;
|
use pyo3::prelude::*;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
Code, DexAnnotationItem, DexString, HiddenApiData, IdField, IdMethod, IdMethodType, IdType,
|
Code, DexAnnotationItem, DexString, IdField, IdMethod, IdMethodType, IdType, MethodHandle,
|
||||||
MethodHandle, Result, Visitable, VisitableMut, Visitor, VisitorMut,
|
Result,
|
||||||
};
|
};
|
||||||
use androscalpel_serializer::consts::*;
|
use androscalpel_serializer::consts::*;
|
||||||
|
|
||||||
/// Represent a method.
|
/// Represent a method.
|
||||||
#[cfg_attr(feature = "python", pyclass(eq))]
|
#[pyclass]
|
||||||
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq)]
|
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)]
|
||||||
pub struct Method {
|
pub struct Method {
|
||||||
/// The structure used to reference this method.
|
/// The structure used to reference this method.
|
||||||
#[cfg_attr(feature = "python", pyo3(get))]
|
#[pyo3(get)]
|
||||||
pub descriptor: IdMethod,
|
pub descriptor: IdMethod,
|
||||||
/// The field visibility.
|
/// The field visibility.
|
||||||
#[cfg_attr(feature = "python", pyo3(get))]
|
#[pyo3(get)]
|
||||||
pub visibility: MethodVisibility,
|
pub visibility: MethodVisibility,
|
||||||
/// Static methods do not take this in argument.
|
/// Static methods do not take this in argument.
|
||||||
#[cfg_attr(feature = "python", pyo3(get))]
|
#[pyo3(get)]
|
||||||
pub is_static: bool,
|
pub is_static: bool,
|
||||||
/// Final methods are not averridable.
|
/// Final methods are not averridable.
|
||||||
#[cfg_attr(feature = "python", pyo3(get))]
|
#[pyo3(get)]
|
||||||
pub is_final: bool,
|
pub is_final: bool,
|
||||||
/// Synchronized method automatically acquire their associated lock around call.
|
/// Synchronized method automatically acquire their associated lock around call.
|
||||||
/// Can only be set in native method, (`[Self::is_native] = true`).
|
/// Can only be set in native method, (`[Self::is_native] = true`).
|
||||||
#[cfg_attr(feature = "python", pyo3(get))]
|
#[pyo3(get)]
|
||||||
pub is_synchronized: bool,
|
pub is_synchronized: bool,
|
||||||
/// Bridge are automatically added by the compiler as a type-safe bridge.
|
/// Bridge are automatically added by the compiler as a type-safe bridge.
|
||||||
#[cfg_attr(feature = "python", pyo3(get))]
|
#[pyo3(get)]
|
||||||
pub is_bridge: bool,
|
pub is_bridge: bool,
|
||||||
/// If the last argument should be treated as a "rest" argument by compiler
|
/// If the last argument should be treated as a "rest" argument by compiler
|
||||||
/// (for method of variable number of argument).
|
/// (for method of variable number of argument).
|
||||||
#[cfg_attr(feature = "python", pyo3(get))]
|
#[pyo3(get)]
|
||||||
pub is_varargs: bool,
|
pub is_varargs: bool,
|
||||||
/// If the method is a native method.
|
/// If the method is a native method.
|
||||||
#[cfg_attr(feature = "python", pyo3(get))]
|
#[pyo3(get)]
|
||||||
pub is_native: bool,
|
pub is_native: bool,
|
||||||
/// Abstract methods are not implemented by the class.
|
/// Abstract methods are not implemented by the class.
|
||||||
#[cfg_attr(feature = "python", pyo3(get))]
|
#[pyo3(get)]
|
||||||
pub is_abstract: bool,
|
pub is_abstract: bool,
|
||||||
/// If the method must use strict rules for floating point arithmetic.
|
/// If the method must use strict rules for floating point arithmetic.
|
||||||
#[cfg_attr(feature = "python", pyo3(get))]
|
#[pyo3(get)]
|
||||||
pub is_strictfp: bool,
|
pub is_strictfp: bool,
|
||||||
/// Synthetic method are not directly defined in the source code.
|
/// Synthetic method are not directly defined in the source code.
|
||||||
#[cfg_attr(feature = "python", pyo3(get))]
|
#[pyo3(get)]
|
||||||
pub is_synthetic: bool,
|
pub is_synthetic: bool,
|
||||||
/// If the method is a constructor.
|
/// If the method is a constructor.
|
||||||
#[cfg_attr(feature = "python", pyo3(get))]
|
#[pyo3(get)]
|
||||||
pub is_constructor: bool,
|
pub is_constructor: bool,
|
||||||
/// If the method is declared as synchronize (just indicatif)
|
/// If the method is declared as synchronize (just indicatif)
|
||||||
#[cfg_attr(feature = "python", pyo3(get))]
|
#[pyo3(get)]
|
||||||
pub is_declared_syncrhonized: bool,
|
pub is_declared_syncrhonized: bool,
|
||||||
/// The annotations for this method
|
/// The annotations for this method
|
||||||
#[cfg_attr(feature = "python", pyo3(get))]
|
#[pyo3(get)]
|
||||||
pub annotations: Vec<DexAnnotationItem>,
|
pub annotations: Vec<DexAnnotationItem>,
|
||||||
/// The annotations for the parameters of this method method
|
/// The annotations for the parameters of this method method
|
||||||
#[cfg_attr(feature = "python", pyo3(get))]
|
#[pyo3(get)]
|
||||||
pub parameters_annotations: Vec<Vec<DexAnnotationItem>>,
|
pub parameters_annotations: Vec<Vec<DexAnnotationItem>>,
|
||||||
/// Hidden Api data.
|
|
||||||
#[cfg_attr(feature = "python", pyo3(get))]
|
|
||||||
pub hiddenapi: Option<HiddenApiData>,
|
|
||||||
|
|
||||||
/// The code of the method
|
/// The code of the method
|
||||||
#[cfg_attr(feature = "python", pyo3(get))]
|
#[pyo3(get)]
|
||||||
pub code: Option<Code>,
|
pub code: Option<Code>,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Represent the visibility of a field
|
/// Represent the visibility of a field
|
||||||
#[cfg_attr(feature = "python", pyclass(eq, eq_int))]
|
#[pyclass]
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Deserialize, Serialize)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Deserialize, Serialize)]
|
||||||
pub enum MethodVisibility {
|
pub enum MethodVisibility {
|
||||||
Public,
|
Public,
|
||||||
|
|
@ -82,18 +78,18 @@ pub enum MethodVisibility {
|
||||||
None_, // Actually quite common
|
None_, // Actually quite common
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg_attr(feature = "python", pymethods)]
|
#[pymethods]
|
||||||
impl Method {
|
impl Method {
|
||||||
pub fn to_json(&self) -> Result<String> {
|
pub fn to_json(&self) -> Result<String> {
|
||||||
Ok(serde_json::to_string(self)?)
|
Ok(serde_json::to_string(self)?)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg_attr(feature = "python", staticmethod)]
|
#[staticmethod]
|
||||||
pub fn from_json(json: &str) -> Result<Self> {
|
pub fn from_json(json: &str) -> Result<Self> {
|
||||||
Ok(serde_json::from_str(json)?)
|
Ok(serde_json::from_str(json)?)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg_attr(feature = "python", new)]
|
#[new]
|
||||||
pub fn new(descriptor: IdMethod) -> Self {
|
pub fn new(descriptor: IdMethod) -> Self {
|
||||||
// TODO: take code option as arg and set the default flags accordingly
|
// TODO: take code option as arg and set the default flags accordingly
|
||||||
Self {
|
Self {
|
||||||
|
|
@ -113,7 +109,6 @@ impl Method {
|
||||||
annotations: vec![],
|
annotations: vec![],
|
||||||
parameters_annotations: vec![],
|
parameters_annotations: vec![],
|
||||||
code: None,
|
code: None,
|
||||||
hiddenapi: None,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -295,79 +290,7 @@ impl Method {
|
||||||
.any(|list| !list.is_empty())
|
.any(|list| !list.is_empty())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Compute the `ins_size` field. This is the number of register parameters, including the
|
pub fn __eq__(&self, other: &Self) -> bool {
|
||||||
/// `this` parameter for non-static methods.
|
self == other
|
||||||
/// This information is stored in the code item in dex files, but is not computable from the code
|
|
||||||
/// (as opposed to `outs_size`). The [`Method`] struct is needed to compute it.
|
|
||||||
pub fn ins_size(&self) -> u16 {
|
|
||||||
let mut ins = 0;
|
|
||||||
if !self.is_static {
|
|
||||||
ins += 1; // this
|
|
||||||
}
|
|
||||||
for param in &self.descriptor.proto.parameters {
|
|
||||||
if param.is_long() || param.is_double() {
|
|
||||||
ins += 2;
|
|
||||||
} else {
|
|
||||||
ins += 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
ins
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Check if the method is a platform method (ie in the android SDK or a hidden API).
|
|
||||||
#[cfg(feature = "platform-list")]
|
|
||||||
pub fn is_platform_method(&self) -> bool {
|
|
||||||
self.descriptor.is_platform_method()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<V: Visitor> Visitable<V> for Method {
|
|
||||||
fn default_visit(&self, v: &mut V) -> Result<()> {
|
|
||||||
v.visit_method_id(&self.descriptor)?;
|
|
||||||
v.visit_method_visibility(&self.visibility)?;
|
|
||||||
for annot in &self.annotations {
|
|
||||||
v.visit_annotation_item(annot)?;
|
|
||||||
}
|
|
||||||
for parameter_annotations in &self.parameters_annotations {
|
|
||||||
for annot in parameter_annotations {
|
|
||||||
v.visit_annotation_item(annot)?;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if let Some(hiddenapi) = &self.hiddenapi {
|
|
||||||
v.visit_hidden_api_data(hiddenapi)?;
|
|
||||||
}
|
|
||||||
if let Some(code) = &self.code {
|
|
||||||
v.visit_code(code)?;
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl<V: VisitorMut> VisitableMut<V> for Method {
|
|
||||||
fn default_visit_mut(self, v: &mut V) -> Result<Self> {
|
|
||||||
Ok(Self {
|
|
||||||
descriptor: v.visit_method_id(self.descriptor)?,
|
|
||||||
visibility: v.visit_method_visibility(self.visibility)?,
|
|
||||||
annotations: self
|
|
||||||
.annotations
|
|
||||||
.into_iter()
|
|
||||||
.map(|annot| v.visit_annotation_item(annot))
|
|
||||||
.collect::<Result<_>>()?,
|
|
||||||
parameters_annotations: self
|
|
||||||
.parameters_annotations
|
|
||||||
.into_iter()
|
|
||||||
.map(|parameter_annotations| {
|
|
||||||
parameter_annotations
|
|
||||||
.into_iter()
|
|
||||||
.map(|annot| v.visit_annotation_item(annot))
|
|
||||||
.collect::<Result<_>>()
|
|
||||||
})
|
|
||||||
.collect::<Result<_>>()?,
|
|
||||||
hiddenapi: self
|
|
||||||
.hiddenapi
|
|
||||||
.map(|hiddenapi| v.visit_hidden_api_data(hiddenapi))
|
|
||||||
.transpose()?,
|
|
||||||
code: self.code.map(|code| v.visit_code(code)).transpose()?,
|
|
||||||
..self
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,151 +4,790 @@ use serde::{Deserialize, Serialize};
|
||||||
use std::collections::HashSet;
|
use std::collections::HashSet;
|
||||||
use std::hash::Hash;
|
use std::hash::Hash;
|
||||||
|
|
||||||
#[cfg(feature = "python")]
|
use pyo3::exceptions::PyTypeError;
|
||||||
use pyo3::prelude::*;
|
use pyo3::prelude::*;
|
||||||
|
|
||||||
use crate::dex_id::*;
|
use crate::dex_id::*;
|
||||||
use crate::{
|
use crate::DexString;
|
||||||
DexString, FieldIdCollector, MethodHandleCollector, MethodIdCollector, MethodTypeCollector,
|
use crate::Result;
|
||||||
Result, StringCollector, TypeCollector, Visitable, VisitableMut, Visitor, VisitorMut,
|
|
||||||
};
|
|
||||||
|
|
||||||
/// The structure use to reference a method invocation.
|
/// The structure use to reference a method invocation.
|
||||||
#[cfg_attr(feature = "python", pyclass(eq))]
|
|
||||||
#[derive(Debug, Clone, PartialEq, Eq, Hash, Deserialize, Serialize)]
|
#[derive(Debug, Clone, PartialEq, Eq, Hash, Deserialize, Serialize)]
|
||||||
pub enum MethodHandle {
|
pub enum MethodHandle {
|
||||||
StaticPut { field: IdField },
|
StaticPut(StaticPut),
|
||||||
StaticGet { field: IdField },
|
StaticGet(StaticGet),
|
||||||
InstancePut { field: IdField },
|
InstancePut(InstancePut),
|
||||||
InstanceGet { field: IdField },
|
InstanceGet(InstanceGet),
|
||||||
InvokeStatic { method: IdMethod },
|
InvokeStatic(InvokeStatic),
|
||||||
InvokeInstance { method: IdMethod },
|
InvokeInstance(InvokeInstance),
|
||||||
InvokeConstructor { method: IdMethod },
|
InvokeConstructor(InvokeConstructor),
|
||||||
InvokeDirect { method: IdMethod },
|
InvokeDirect(InvokeDirect),
|
||||||
InvokeInterface { method: IdMethod },
|
InvokeInterface(InvokeInterface),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<V: Visitor> Visitable<V> for MethodHandle {
|
#[pyclass]
|
||||||
fn default_visit(&self, v: &mut V) -> Result<()> {
|
#[derive(Debug, Clone, PartialEq, Eq, Hash, Deserialize, Serialize)]
|
||||||
match self {
|
pub struct StaticPut(pub IdField);
|
||||||
Self::StaticPut { field } => v.visit_field_id(field)?,
|
|
||||||
Self::StaticGet { field } => v.visit_field_id(field)?,
|
|
||||||
Self::InstancePut { field } => v.visit_field_id(field)?,
|
|
||||||
Self::InstanceGet { field } => v.visit_field_id(field)?,
|
|
||||||
Self::InvokeStatic { method } => v.visit_method_id(method)?,
|
|
||||||
Self::InvokeInstance { method } => v.visit_method_id(method)?,
|
|
||||||
Self::InvokeConstructor { method } => v.visit_method_id(method)?,
|
|
||||||
Self::InvokeDirect { method } => v.visit_method_id(method)?,
|
|
||||||
Self::InvokeInterface { method } => v.visit_method_id(method)?,
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<V: VisitorMut> VisitableMut<V> for MethodHandle {
|
#[pymethods]
|
||||||
fn default_visit_mut(self, v: &mut V) -> Result<Self> {
|
impl StaticPut {
|
||||||
match self {
|
|
||||||
Self::StaticPut { field } => Ok(Self::StaticPut {
|
|
||||||
field: v.visit_field_id(field)?,
|
|
||||||
}),
|
|
||||||
Self::StaticGet { field } => Ok(Self::StaticGet {
|
|
||||||
field: v.visit_field_id(field)?,
|
|
||||||
}),
|
|
||||||
Self::InstancePut { field } => Ok(Self::InstancePut {
|
|
||||||
field: v.visit_field_id(field)?,
|
|
||||||
}),
|
|
||||||
Self::InstanceGet { field } => Ok(Self::InstanceGet {
|
|
||||||
field: v.visit_field_id(field)?,
|
|
||||||
}),
|
|
||||||
Self::InvokeStatic { method } => Ok(Self::InvokeStatic {
|
|
||||||
method: v.visit_method_id(method)?,
|
|
||||||
}),
|
|
||||||
Self::InvokeInstance { method } => Ok(Self::InvokeInstance {
|
|
||||||
method: v.visit_method_id(method)?,
|
|
||||||
}),
|
|
||||||
Self::InvokeConstructor { method } => Ok(Self::InvokeConstructor {
|
|
||||||
method: v.visit_method_id(method)?,
|
|
||||||
}),
|
|
||||||
Self::InvokeDirect { method } => Ok(Self::InvokeDirect {
|
|
||||||
method: v.visit_method_id(method)?,
|
|
||||||
}),
|
|
||||||
Self::InvokeInterface { method } => Ok(Self::InvokeInterface {
|
|
||||||
method: v.visit_method_id(method)?,
|
|
||||||
}),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg_attr(feature = "python", pymethods)]
|
|
||||||
impl MethodHandle {
|
|
||||||
pub fn to_json(&self) -> Result<String> {
|
pub fn to_json(&self) -> Result<String> {
|
||||||
Ok(serde_json::to_string(self)?)
|
Ok(serde_json::to_string(self)?)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg_attr(feature = "python", staticmethod)]
|
#[staticmethod]
|
||||||
pub fn from_json(json: &str) -> Result<Self> {
|
pub fn from_json(json: &str) -> Result<Self> {
|
||||||
Ok(serde_json::from_str(json)?)
|
Ok(serde_json::from_str(json)?)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[new]
|
||||||
|
pub fn new(val: IdField) -> Self {
|
||||||
|
Self(val)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_field(&self) -> IdField {
|
||||||
|
self.0.clone()
|
||||||
|
}
|
||||||
|
|
||||||
pub fn __str__(&self) -> String {
|
pub fn __str__(&self) -> String {
|
||||||
self.__repr__()
|
self.__repr__()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn __repr__(&self) -> String {
|
pub fn __repr__(&self) -> String {
|
||||||
|
format!("StaticPut({})", self.0.__str__())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Return all strings referenced in the handle.
|
||||||
|
pub fn get_all_strings(&self) -> HashSet<DexString> {
|
||||||
|
self.0.get_all_strings()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Return all types referenced in the handle.
|
||||||
|
pub fn get_all_types(&self) -> HashSet<IdType> {
|
||||||
|
self.0.get_all_types()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Return all prototypes referenced in the handle.
|
||||||
|
pub fn get_all_protos(&self) -> HashSet<IdMethodType> {
|
||||||
|
HashSet::new()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Return all field ids referenced in the handle.
|
||||||
|
pub fn get_all_field_ids(&self) -> HashSet<IdField> {
|
||||||
|
let mut fields = HashSet::new();
|
||||||
|
fields.insert(self.0.clone());
|
||||||
|
fields
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Return all method ids referenced in the handle.
|
||||||
|
pub fn get_all_method_ids(&self) -> HashSet<IdMethod> {
|
||||||
|
HashSet::new()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Return all method handles referenced in the handle.
|
||||||
|
pub fn get_all_method_handles(&self) -> HashSet<MethodHandle> {
|
||||||
|
let mut methods = HashSet::new();
|
||||||
|
methods.insert(MethodHandle::StaticPut(self.clone()));
|
||||||
|
methods
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#[pyclass]
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq, Hash, Deserialize, Serialize)]
|
||||||
|
pub struct StaticGet(pub IdField);
|
||||||
|
|
||||||
|
#[pymethods]
|
||||||
|
impl StaticGet {
|
||||||
|
pub fn to_json(&self) -> Result<String> {
|
||||||
|
Ok(serde_json::to_string(self)?)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[staticmethod]
|
||||||
|
pub fn from_json(json: &str) -> Result<Self> {
|
||||||
|
Ok(serde_json::from_str(json)?)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[new]
|
||||||
|
pub fn new(val: IdField) -> Self {
|
||||||
|
Self(val)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_field(&self) -> IdField {
|
||||||
|
self.0.clone()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn __str__(&self) -> String {
|
||||||
|
self.__repr__()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn __repr__(&self) -> String {
|
||||||
|
format!("StaticGet({})", self.0.__str__())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Return all strings referenced in the handle.
|
||||||
|
pub fn get_all_strings(&self) -> HashSet<DexString> {
|
||||||
|
self.0.get_all_strings()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Return all types referenced in the handle.
|
||||||
|
pub fn get_all_types(&self) -> HashSet<IdType> {
|
||||||
|
self.0.get_all_types()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Return all prototypes referenced in the handle.
|
||||||
|
pub fn get_all_protos(&self) -> HashSet<IdMethodType> {
|
||||||
|
HashSet::new()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Return all field ids referenced in the handle.
|
||||||
|
pub fn get_all_field_ids(&self) -> HashSet<IdField> {
|
||||||
|
let mut fields = HashSet::new();
|
||||||
|
fields.insert(self.0.clone());
|
||||||
|
fields
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Return all method ids referenced in the handle.
|
||||||
|
pub fn get_all_method_ids(&self) -> HashSet<IdMethod> {
|
||||||
|
HashSet::new()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Return all method handles referenced in the handle.
|
||||||
|
pub fn get_all_method_handles(&self) -> HashSet<MethodHandle> {
|
||||||
|
let mut methods = HashSet::new();
|
||||||
|
methods.insert(MethodHandle::StaticGet(self.clone()));
|
||||||
|
methods
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#[pyclass]
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq, Hash, Deserialize, Serialize)]
|
||||||
|
pub struct InstancePut(pub IdField);
|
||||||
|
|
||||||
|
#[pymethods]
|
||||||
|
impl InstancePut {
|
||||||
|
pub fn to_json(&self) -> Result<String> {
|
||||||
|
Ok(serde_json::to_string(self)?)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[staticmethod]
|
||||||
|
pub fn from_json(json: &str) -> Result<Self> {
|
||||||
|
Ok(serde_json::from_str(json)?)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[new]
|
||||||
|
pub fn new(val: IdField) -> Self {
|
||||||
|
Self(val)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_field(&self) -> IdField {
|
||||||
|
self.0.clone()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn __str__(&self) -> String {
|
||||||
|
self.__repr__()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn __repr__(&self) -> String {
|
||||||
|
format!("InstancePut({})", self.0.__str__())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Return all strings referenced in the handle.
|
||||||
|
pub fn get_all_strings(&self) -> HashSet<DexString> {
|
||||||
|
self.0.get_all_strings()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Return all types referenced in the handle.
|
||||||
|
pub fn get_all_types(&self) -> HashSet<IdType> {
|
||||||
|
self.0.get_all_types()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Return all prototypes referenced in the handle.
|
||||||
|
pub fn get_all_protos(&self) -> HashSet<IdMethodType> {
|
||||||
|
HashSet::new()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Return all field ids referenced in the handle.
|
||||||
|
pub fn get_all_field_ids(&self) -> HashSet<IdField> {
|
||||||
|
let mut fields = HashSet::new();
|
||||||
|
fields.insert(self.0.clone());
|
||||||
|
fields
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Return all method ids referenced in the handle.
|
||||||
|
pub fn get_all_method_ids(&self) -> HashSet<IdMethod> {
|
||||||
|
HashSet::new()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Return all method handles referenced in the handle.
|
||||||
|
pub fn get_all_method_handles(&self) -> HashSet<MethodHandle> {
|
||||||
|
let mut methods = HashSet::new();
|
||||||
|
methods.insert(MethodHandle::InstancePut(self.clone()));
|
||||||
|
methods
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#[pyclass]
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq, Hash, Deserialize, Serialize)]
|
||||||
|
pub struct InstanceGet(pub IdField);
|
||||||
|
|
||||||
|
#[pymethods]
|
||||||
|
impl InstanceGet {
|
||||||
|
pub fn to_json(&self) -> Result<String> {
|
||||||
|
Ok(serde_json::to_string(self)?)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[staticmethod]
|
||||||
|
pub fn from_json(json: &str) -> Result<Self> {
|
||||||
|
Ok(serde_json::from_str(json)?)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[new]
|
||||||
|
pub fn new(val: IdField) -> Self {
|
||||||
|
Self(val)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_field(&self) -> IdField {
|
||||||
|
self.0.clone()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn __str__(&self) -> String {
|
||||||
|
self.__repr__()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn __repr__(&self) -> String {
|
||||||
|
format!("InstanceGet({})", self.0.__str__())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Return all strings referenced in the handle.
|
||||||
|
pub fn get_all_strings(&self) -> HashSet<DexString> {
|
||||||
|
self.0.get_all_strings()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Return all types referenced in the handle.
|
||||||
|
pub fn get_all_types(&self) -> HashSet<IdType> {
|
||||||
|
self.0.get_all_types()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Return all prototypes referenced in the handle.
|
||||||
|
pub fn get_all_protos(&self) -> HashSet<IdMethodType> {
|
||||||
|
HashSet::new()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Return all field ids referenced in the handle.
|
||||||
|
pub fn get_all_field_ids(&self) -> HashSet<IdField> {
|
||||||
|
let mut fields = HashSet::new();
|
||||||
|
fields.insert(self.0.clone());
|
||||||
|
fields
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Return all method ids referenced in the handle.
|
||||||
|
pub fn get_all_method_ids(&self) -> HashSet<IdMethod> {
|
||||||
|
HashSet::new()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Return all method handles referenced in the handle.
|
||||||
|
pub fn get_all_method_handles(&self) -> HashSet<MethodHandle> {
|
||||||
|
let mut methods = HashSet::new();
|
||||||
|
methods.insert(MethodHandle::InstanceGet(self.clone()));
|
||||||
|
methods
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[pyclass]
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq, Hash, Deserialize, Serialize)]
|
||||||
|
pub struct InvokeStatic(pub IdMethod);
|
||||||
|
|
||||||
|
#[pymethods]
|
||||||
|
impl InvokeStatic {
|
||||||
|
pub fn to_json(&self) -> Result<String> {
|
||||||
|
Ok(serde_json::to_string(self)?)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[staticmethod]
|
||||||
|
pub fn from_json(json: &str) -> Result<Self> {
|
||||||
|
Ok(serde_json::from_str(json)?)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[new]
|
||||||
|
pub fn new(val: IdMethod) -> Self {
|
||||||
|
Self(val)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_method(&self) -> IdMethod {
|
||||||
|
self.0.clone()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn __str__(&self) -> String {
|
||||||
|
self.__repr__()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn __repr__(&self) -> String {
|
||||||
|
format!("InvokeStatic({})", self.0.__str__())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Return all strings referenced in the handle.
|
||||||
|
pub fn get_all_strings(&self) -> HashSet<DexString> {
|
||||||
|
self.0.get_all_strings()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Return all types referenced in the handle.
|
||||||
|
pub fn get_all_types(&self) -> HashSet<IdType> {
|
||||||
|
self.0.get_all_types()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Return all prototypes referenced in the handle.
|
||||||
|
pub fn get_all_protos(&self) -> HashSet<IdMethodType> {
|
||||||
|
self.0.get_all_protos()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Return all field ids referenced in the handle.
|
||||||
|
pub fn get_all_field_ids(&self) -> HashSet<IdField> {
|
||||||
|
HashSet::new()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Return all method ids referenced in the handle.
|
||||||
|
pub fn get_all_method_ids(&self) -> HashSet<IdMethod> {
|
||||||
|
let mut methods = HashSet::new();
|
||||||
|
methods.insert(self.0.clone());
|
||||||
|
methods
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Return all method handles referenced in the handle.
|
||||||
|
pub fn get_all_method_handles(&self) -> HashSet<MethodHandle> {
|
||||||
|
let mut methods = HashSet::new();
|
||||||
|
methods.insert(MethodHandle::InvokeStatic(self.clone()));
|
||||||
|
methods
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[pyclass]
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq, Hash, Deserialize, Serialize)]
|
||||||
|
pub struct InvokeInstance(pub IdMethod);
|
||||||
|
|
||||||
|
#[pymethods]
|
||||||
|
impl InvokeInstance {
|
||||||
|
pub fn to_json(&self) -> Result<String> {
|
||||||
|
Ok(serde_json::to_string(self)?)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[staticmethod]
|
||||||
|
pub fn from_json(json: &str) -> Result<Self> {
|
||||||
|
Ok(serde_json::from_str(json)?)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[new]
|
||||||
|
pub fn new(val: IdMethod) -> Self {
|
||||||
|
Self(val)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_method(&self) -> IdMethod {
|
||||||
|
self.0.clone()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn __str__(&self) -> String {
|
||||||
|
self.__repr__()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn __repr__(&self) -> String {
|
||||||
|
format!("InvokeInstance({})", self.0.__str__())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Return all strings referenced in the handle.
|
||||||
|
pub fn get_all_strings(&self) -> HashSet<DexString> {
|
||||||
|
self.0.get_all_strings()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Return all types referenced in the handle.
|
||||||
|
pub fn get_all_types(&self) -> HashSet<IdType> {
|
||||||
|
self.0.get_all_types()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Return all prototypes referenced in the handle.
|
||||||
|
pub fn get_all_protos(&self) -> HashSet<IdMethodType> {
|
||||||
|
self.0.get_all_protos()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Return all field ids referenced in the handle.
|
||||||
|
pub fn get_all_field_ids(&self) -> HashSet<IdField> {
|
||||||
|
HashSet::new()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Return all method ids referenced in the handle.
|
||||||
|
pub fn get_all_method_ids(&self) -> HashSet<IdMethod> {
|
||||||
|
let mut methods = HashSet::new();
|
||||||
|
methods.insert(self.0.clone());
|
||||||
|
methods
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Return all method handles referenced in the handle.
|
||||||
|
pub fn get_all_method_handles(&self) -> HashSet<MethodHandle> {
|
||||||
|
let mut methods = HashSet::new();
|
||||||
|
methods.insert(MethodHandle::InvokeInstance(self.clone()));
|
||||||
|
methods
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[pyclass]
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq, Hash, Deserialize, Serialize)]
|
||||||
|
pub struct InvokeConstructor(pub IdMethod);
|
||||||
|
|
||||||
|
#[pymethods]
|
||||||
|
impl InvokeConstructor {
|
||||||
|
pub fn to_json(&self) -> Result<String> {
|
||||||
|
Ok(serde_json::to_string(self)?)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[staticmethod]
|
||||||
|
pub fn from_json(json: &str) -> Result<Self> {
|
||||||
|
Ok(serde_json::from_str(json)?)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[new]
|
||||||
|
pub fn new(val: IdMethod) -> Self {
|
||||||
|
Self(val)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_method(&self) -> IdMethod {
|
||||||
|
self.0.clone()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn __str__(&self) -> String {
|
||||||
|
self.__repr__()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn __repr__(&self) -> String {
|
||||||
|
format!("InvokeConstructor({})", self.0.__str__())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Return all strings referenced in the handle.
|
||||||
|
pub fn get_all_strings(&self) -> HashSet<DexString> {
|
||||||
|
self.0.get_all_strings()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Return all types referenced in the handle.
|
||||||
|
pub fn get_all_types(&self) -> HashSet<IdType> {
|
||||||
|
self.0.get_all_types()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Return all prototypes referenced in the handle.
|
||||||
|
pub fn get_all_protos(&self) -> HashSet<IdMethodType> {
|
||||||
|
self.0.get_all_protos()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Return all field ids referenced in the handle.
|
||||||
|
pub fn get_all_field_ids(&self) -> HashSet<IdField> {
|
||||||
|
HashSet::new()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Return all method ids referenced in the handle.
|
||||||
|
pub fn get_all_method_ids(&self) -> HashSet<IdMethod> {
|
||||||
|
let mut methods = HashSet::new();
|
||||||
|
methods.insert(self.0.clone());
|
||||||
|
methods
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Return all method handles referenced in the handle.
|
||||||
|
pub fn get_all_method_handles(&self) -> HashSet<MethodHandle> {
|
||||||
|
let mut methods = HashSet::new();
|
||||||
|
methods.insert(MethodHandle::InvokeConstructor(self.clone()));
|
||||||
|
methods
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[pyclass]
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq, Hash, Deserialize, Serialize)]
|
||||||
|
pub struct InvokeDirect(pub IdMethod);
|
||||||
|
|
||||||
|
#[pymethods]
|
||||||
|
impl InvokeDirect {
|
||||||
|
pub fn to_json(&self) -> Result<String> {
|
||||||
|
Ok(serde_json::to_string(self)?)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[staticmethod]
|
||||||
|
pub fn from_json(json: &str) -> Result<Self> {
|
||||||
|
Ok(serde_json::from_str(json)?)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[new]
|
||||||
|
pub fn new(val: IdMethod) -> Self {
|
||||||
|
Self(val)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_method(&self) -> IdMethod {
|
||||||
|
self.0.clone()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn __str__(&self) -> String {
|
||||||
|
self.__repr__()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn __repr__(&self) -> String {
|
||||||
|
format!("InvokeDirect({})", self.0.__str__())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Return all strings referenced in the handle.
|
||||||
|
pub fn get_all_strings(&self) -> HashSet<DexString> {
|
||||||
|
self.0.get_all_strings()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Return all types referenced in the handle.
|
||||||
|
pub fn get_all_types(&self) -> HashSet<IdType> {
|
||||||
|
self.0.get_all_types()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Return all prototypes referenced in the handle.
|
||||||
|
pub fn get_all_protos(&self) -> HashSet<IdMethodType> {
|
||||||
|
self.0.get_all_protos()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Return all field ids referenced in the handle.
|
||||||
|
pub fn get_all_field_ids(&self) -> HashSet<IdField> {
|
||||||
|
HashSet::new()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Return all method ids referenced in the handle.
|
||||||
|
pub fn get_all_method_ids(&self) -> HashSet<IdMethod> {
|
||||||
|
let mut methods = HashSet::new();
|
||||||
|
methods.insert(self.0.clone());
|
||||||
|
methods
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Return all method handles referenced in the handle.
|
||||||
|
pub fn get_all_method_handles(&self) -> HashSet<MethodHandle> {
|
||||||
|
let mut methods = HashSet::new();
|
||||||
|
methods.insert(MethodHandle::InvokeDirect(self.clone()));
|
||||||
|
methods
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[pyclass]
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq, Hash, Deserialize, Serialize)]
|
||||||
|
pub struct InvokeInterface(pub IdMethod);
|
||||||
|
|
||||||
|
#[pymethods]
|
||||||
|
impl InvokeInterface {
|
||||||
|
pub fn to_json(&self) -> Result<String> {
|
||||||
|
Ok(serde_json::to_string(self)?)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[staticmethod]
|
||||||
|
pub fn from_json(json: &str) -> Result<Self> {
|
||||||
|
Ok(serde_json::from_str(json)?)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[new]
|
||||||
|
pub fn new(val: IdMethod) -> Self {
|
||||||
|
Self(val)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_method(&self) -> IdMethod {
|
||||||
|
self.0.clone()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn __str__(&self) -> String {
|
||||||
|
self.__repr__()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn __repr__(&self) -> String {
|
||||||
|
format!("InvokeInterface({})", self.0.__str__())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Return all strings referenced in the handle.
|
||||||
|
pub fn get_all_strings(&self) -> HashSet<DexString> {
|
||||||
|
self.0.get_all_strings()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Return all types referenced in the handle.
|
||||||
|
pub fn get_all_types(&self) -> HashSet<IdType> {
|
||||||
|
self.0.get_all_types()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Return all prototypes referenced in the handle.
|
||||||
|
pub fn get_all_protos(&self) -> HashSet<IdMethodType> {
|
||||||
|
self.0.get_all_protos()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Return all field ids referenced in the handle.
|
||||||
|
pub fn get_all_field_ids(&self) -> HashSet<IdField> {
|
||||||
|
HashSet::new()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Return all method ids referenced in the handle.
|
||||||
|
pub fn get_all_method_ids(&self) -> HashSet<IdMethod> {
|
||||||
|
let mut methods = HashSet::new();
|
||||||
|
methods.insert(self.0.clone());
|
||||||
|
methods
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Return all method handles referenced in the handle.
|
||||||
|
pub fn get_all_method_handles(&self) -> HashSet<MethodHandle> {
|
||||||
|
let mut methods = HashSet::new();
|
||||||
|
methods.insert(MethodHandle::InvokeInterface(self.clone()));
|
||||||
|
methods
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'source> FromPyObject<'source> for MethodHandle {
|
||||||
|
fn extract(ob: &'source PyAny) -> PyResult<Self> {
|
||||||
|
if let Ok(val) = StaticPut::extract(ob) {
|
||||||
|
Ok(Self::StaticPut(val))
|
||||||
|
} else if let Ok(val) = StaticGet::extract(ob) {
|
||||||
|
Ok(Self::StaticGet(val))
|
||||||
|
} else if let Ok(val) = InstancePut::extract(ob) {
|
||||||
|
Ok(Self::InstancePut(val))
|
||||||
|
} else if let Ok(val) = InstanceGet::extract(ob) {
|
||||||
|
Ok(Self::InstanceGet(val))
|
||||||
|
} else if let Ok(val) = InvokeStatic::extract(ob) {
|
||||||
|
Ok(Self::InvokeStatic(val))
|
||||||
|
} else if let Ok(val) = InvokeInstance::extract(ob) {
|
||||||
|
Ok(Self::InvokeInstance(val))
|
||||||
|
} else if let Ok(val) = InvokeConstructor::extract(ob) {
|
||||||
|
Ok(Self::InvokeConstructor(val))
|
||||||
|
} else if let Ok(val) = InvokeDirect::extract(ob) {
|
||||||
|
Ok(Self::InvokeDirect(val))
|
||||||
|
} else if let Ok(val) = InvokeInterface::extract(ob) {
|
||||||
|
Ok(Self::InvokeInterface(val))
|
||||||
|
} else {
|
||||||
|
Err(PyErr::new::<PyTypeError, _>(format!(
|
||||||
|
"{} is not a castable as a MethodHandle",
|
||||||
|
ob.repr()?
|
||||||
|
)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl IntoPy<PyObject> for MethodHandle {
|
||||||
|
fn into_py(self, py: Python<'_>) -> PyObject {
|
||||||
match self {
|
match self {
|
||||||
Self::StaticPut { field } => format!("StaticPut({})", field.__str__()),
|
Self::StaticPut(val) => val.into_py(py),
|
||||||
Self::StaticGet { field } => format!("StaticGet({})", field.__str__()),
|
Self::StaticGet(val) => val.into_py(py),
|
||||||
Self::InstancePut { field } => format!("InstancePut({})", field.__str__()),
|
Self::InstancePut(val) => val.into_py(py),
|
||||||
Self::InstanceGet { field } => format!("InstanceGet({})", field.__str__()),
|
Self::InstanceGet(val) => val.into_py(py),
|
||||||
Self::InvokeStatic { method } => format!("InvokeStatic({})", method.__str__()),
|
Self::InvokeStatic(val) => val.into_py(py),
|
||||||
Self::InvokeInstance { method } => format!("InvokeInstance({})", method.__str__()),
|
Self::InvokeInstance(val) => val.into_py(py),
|
||||||
Self::InvokeConstructor { method } => {
|
Self::InvokeConstructor(val) => val.into_py(py),
|
||||||
format!("InvokeConstructor({})", method.__str__())
|
Self::InvokeDirect(val) => val.into_py(py),
|
||||||
}
|
Self::InvokeInterface(val) => val.into_py(py),
|
||||||
Self::InvokeDirect { method } => format!("InvokeDirect({})", method.__str__()),
|
}
|
||||||
Self::InvokeInterface { method } => format!("InvokeInterface({})", method.__str__()),
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl MethodHandle {
|
||||||
|
// Not exposed to python, but meh, let's keep it coherent
|
||||||
|
pub fn __str__(&self) -> String {
|
||||||
|
match self {
|
||||||
|
Self::StaticPut(val) => val.__str__(),
|
||||||
|
Self::StaticGet(val) => val.__str__(),
|
||||||
|
Self::InstancePut(val) => val.__str__(),
|
||||||
|
Self::InstanceGet(val) => val.__str__(),
|
||||||
|
Self::InvokeStatic(val) => val.__str__(),
|
||||||
|
Self::InvokeInstance(val) => val.__str__(),
|
||||||
|
Self::InvokeConstructor(val) => val.__str__(),
|
||||||
|
Self::InvokeDirect(val) => val.__str__(),
|
||||||
|
Self::InvokeInterface(val) => val.__str__(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Not exposed to python, but meh, let's keep it coherent
|
||||||
|
pub fn __repr__(&self) -> String {
|
||||||
|
match self {
|
||||||
|
Self::StaticPut(val) => val.__repr__(),
|
||||||
|
Self::StaticGet(val) => val.__repr__(),
|
||||||
|
Self::InstancePut(val) => val.__repr__(),
|
||||||
|
Self::InstanceGet(val) => val.__repr__(),
|
||||||
|
Self::InvokeStatic(val) => val.__repr__(),
|
||||||
|
Self::InvokeInstance(val) => val.__repr__(),
|
||||||
|
Self::InvokeConstructor(val) => val.__str__(),
|
||||||
|
Self::InvokeDirect(val) => val.__repr__(),
|
||||||
|
Self::InvokeInterface(val) => val.__repr__(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Return all strings referenced in the Handle.
|
/// Return all strings referenced in the Handle.
|
||||||
pub fn get_all_strings(&self) -> HashSet<DexString> {
|
pub fn get_all_strings(&self) -> HashSet<DexString> {
|
||||||
let mut visitor = StringCollector::default();
|
match self {
|
||||||
visitor.visit_method_handle(self).unwrap();
|
Self::StaticPut(val) => val.get_all_strings(),
|
||||||
visitor.result()
|
Self::StaticGet(val) => val.get_all_strings(),
|
||||||
|
Self::InstancePut(val) => val.get_all_strings(),
|
||||||
|
Self::InstanceGet(val) => val.get_all_strings(),
|
||||||
|
Self::InvokeStatic(val) => val.get_all_strings(),
|
||||||
|
Self::InvokeInstance(val) => val.get_all_strings(),
|
||||||
|
Self::InvokeConstructor(val) => val.get_all_strings(),
|
||||||
|
Self::InvokeDirect(val) => val.get_all_strings(),
|
||||||
|
Self::InvokeInterface(val) => val.get_all_strings(),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Return all types referenced in the Handle.
|
/// Return all types referenced in the Handle.
|
||||||
pub fn get_all_types(&self) -> HashSet<IdType> {
|
pub fn get_all_types(&self) -> HashSet<IdType> {
|
||||||
let mut visitor = TypeCollector::default();
|
match self {
|
||||||
visitor.visit_method_handle(self).unwrap();
|
Self::StaticPut(val) => val.get_all_types(),
|
||||||
visitor.result()
|
Self::StaticGet(val) => val.get_all_types(),
|
||||||
|
Self::InstancePut(val) => val.get_all_types(),
|
||||||
|
Self::InstanceGet(val) => val.get_all_types(),
|
||||||
|
Self::InvokeStatic(val) => val.get_all_types(),
|
||||||
|
Self::InvokeInstance(val) => val.get_all_types(),
|
||||||
|
Self::InvokeConstructor(val) => val.get_all_types(),
|
||||||
|
Self::InvokeDirect(val) => val.get_all_types(),
|
||||||
|
Self::InvokeInterface(val) => val.get_all_types(),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Return all prototypes referenced in the handle.
|
/// Return all prototypes referenced in the handle.
|
||||||
pub fn get_all_protos(&self) -> HashSet<IdMethodType> {
|
pub fn get_all_protos(&self) -> HashSet<IdMethodType> {
|
||||||
let mut visitor = MethodTypeCollector::default();
|
match self {
|
||||||
visitor.visit_method_handle(self).unwrap();
|
Self::StaticPut(val) => val.get_all_protos(),
|
||||||
visitor.result()
|
Self::StaticGet(val) => val.get_all_protos(),
|
||||||
|
Self::InstancePut(val) => val.get_all_protos(),
|
||||||
|
Self::InstanceGet(val) => val.get_all_protos(),
|
||||||
|
Self::InvokeStatic(val) => val.get_all_protos(),
|
||||||
|
Self::InvokeInstance(val) => val.get_all_protos(),
|
||||||
|
Self::InvokeConstructor(val) => val.get_all_protos(),
|
||||||
|
Self::InvokeDirect(val) => val.get_all_protos(),
|
||||||
|
Self::InvokeInterface(val) => val.get_all_protos(),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Return all field ids referenced in the handle.
|
/// Return all field ids referenced in the handle.
|
||||||
pub fn get_all_field_ids(&self) -> HashSet<IdField> {
|
pub fn get_all_field_ids(&self) -> HashSet<IdField> {
|
||||||
let mut visitor = FieldIdCollector::default();
|
match self {
|
||||||
visitor.visit_method_handle(self).unwrap();
|
Self::StaticPut(val) => val.get_all_field_ids(),
|
||||||
visitor.result()
|
Self::StaticGet(val) => val.get_all_field_ids(),
|
||||||
|
Self::InstancePut(val) => val.get_all_field_ids(),
|
||||||
|
Self::InstanceGet(val) => val.get_all_field_ids(),
|
||||||
|
Self::InvokeStatic(val) => val.get_all_field_ids(),
|
||||||
|
Self::InvokeInstance(val) => val.get_all_field_ids(),
|
||||||
|
Self::InvokeConstructor(val) => val.get_all_field_ids(),
|
||||||
|
Self::InvokeDirect(val) => val.get_all_field_ids(),
|
||||||
|
Self::InvokeInterface(val) => val.get_all_field_ids(),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Return all method ids referenced in the handle.
|
/// Return all method ids referenced in the handle.
|
||||||
pub fn get_all_method_ids(&self) -> HashSet<IdMethod> {
|
pub fn get_all_method_ids(&self) -> HashSet<IdMethod> {
|
||||||
let mut visitor = MethodIdCollector::default();
|
match self {
|
||||||
visitor.visit_method_handle(self).unwrap();
|
Self::StaticPut(val) => val.get_all_method_ids(),
|
||||||
visitor.result()
|
Self::StaticGet(val) => val.get_all_method_ids(),
|
||||||
|
Self::InstancePut(val) => val.get_all_method_ids(),
|
||||||
|
Self::InstanceGet(val) => val.get_all_method_ids(),
|
||||||
|
Self::InvokeStatic(val) => val.get_all_method_ids(),
|
||||||
|
Self::InvokeInstance(val) => val.get_all_method_ids(),
|
||||||
|
Self::InvokeConstructor(val) => val.get_all_method_ids(),
|
||||||
|
Self::InvokeDirect(val) => val.get_all_method_ids(),
|
||||||
|
Self::InvokeInterface(val) => val.get_all_method_ids(),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Return all method handles referenced in the handle.
|
/// Return all method handles referenced in the handle.
|
||||||
pub fn get_all_method_handles(&self) -> HashSet<MethodHandle> {
|
pub fn get_all_method_handles(&self) -> HashSet<MethodHandle> {
|
||||||
let mut visitor = MethodHandleCollector::default();
|
match self {
|
||||||
visitor.visit_method_handle(self).unwrap();
|
Self::StaticPut(val) => val.get_all_method_handles(),
|
||||||
visitor.result()
|
Self::StaticGet(val) => val.get_all_method_handles(),
|
||||||
|
Self::InstancePut(val) => val.get_all_method_handles(),
|
||||||
|
Self::InstanceGet(val) => val.get_all_method_handles(),
|
||||||
|
Self::InvokeStatic(val) => val.get_all_method_handles(),
|
||||||
|
Self::InvokeInstance(val) => val.get_all_method_handles(),
|
||||||
|
Self::InvokeConstructor(val) => val.get_all_method_handles(),
|
||||||
|
Self::InvokeDirect(val) => val.get_all_method_handles(),
|
||||||
|
Self::InvokeInterface(val) => val.get_all_method_handles(),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,18 +1,13 @@
|
||||||
//! Function that are can be usefull on the python side.
|
//! Function that are can be usefull on the python side.
|
||||||
|
|
||||||
use pyo3::prelude::*;
|
use pyo3::prelude::*;
|
||||||
use pyo3::types::{PyBytes, PyBytesMethods};
|
use pyo3::types::PyBytes;
|
||||||
|
|
||||||
use std::collections::{HashMap, HashSet};
|
use std::io::Cursor;
|
||||||
use std::fs::File;
|
|
||||||
use std::io::{Cursor, Seek, SeekFrom};
|
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
|
|
||||||
use crate::{Apk, IdType, Result};
|
use crate::Result;
|
||||||
use androscalpel_serializer::{
|
use androscalpel_serializer::{Serializable, Sleb128, Uleb128, Uleb128p1};
|
||||||
DexFileReader, HeaderItem, Serializable, Sleb128, Uleb128, Uleb128p1,
|
|
||||||
};
|
|
||||||
use apk_frauder::{ZipFileReader, end_of_central_directory::EndCentralDirectory};
|
|
||||||
|
|
||||||
/// Convert an integer to the uleb128 byte encoding
|
/// Convert an integer to the uleb128 byte encoding
|
||||||
#[pyfunction]
|
#[pyfunction]
|
||||||
|
|
@ -49,32 +44,6 @@ pub fn sleb128_to_int(b: &[u8]) -> Result<i32> {
|
||||||
Ok(Sleb128::deserialize_from_slice(b)?.0)
|
Ok(Sleb128::deserialize_from_slice(b)?.0)
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: list_defined_classes, is_dex, is_zip take only &[u8] or file, but should allow to also read from both
|
|
||||||
|
|
||||||
/// List all classes defined in a dex file.
|
|
||||||
#[pyfunction]
|
|
||||||
pub fn list_defined_classes(dex: &[u8]) -> Result<HashSet<IdType>> {
|
|
||||||
let dex = DexFileReader::new(dex)?;
|
|
||||||
dex.get_class_defs()
|
|
||||||
.iter()
|
|
||||||
.map(|cdef| Apk::get_id_type_from_idx(cdef.class_idx as usize, &dex))
|
|
||||||
.collect()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Test if a file is as .dex file an return the dex version if it is, else return None.
|
|
||||||
#[pyfunction]
|
|
||||||
pub fn is_dex(file: PathBuf) -> Result<Option<usize>> {
|
|
||||||
let mut file = File::open(file)?;
|
|
||||||
crate::utils::is_dex(file)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Test if a file is a zip file.
|
|
||||||
#[pyfunction]
|
|
||||||
pub fn is_zip(file: PathBuf) -> Result<bool> {
|
|
||||||
let mut file = File::open(file)?;
|
|
||||||
crate::utils::is_zip(file)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Replace the dex files a an apk an resigned the apk.
|
/// Replace the dex files a an apk an resigned the apk.
|
||||||
///
|
///
|
||||||
/// # Warning
|
/// # Warning
|
||||||
|
|
@ -82,69 +51,28 @@ pub fn is_zip(file: PathBuf) -> Result<bool> {
|
||||||
/// For now, only jks keystore are allowed.
|
/// For now, only jks keystore are allowed.
|
||||||
///
|
///
|
||||||
/// The `zipalign` and `apksigner` args allow to use a specific version of the
|
/// The `zipalign` and `apksigner` args allow to use a specific version of the
|
||||||
/// toimpl Read + Seekols instead of the one in the PATH (if it even exist)
|
/// tools instead of the one in the PATH (if it even exist)
|
||||||
///
|
|
||||||
/// `additionnal_files` is a dict of file to add, modify or remove in the apk.
|
|
||||||
/// The keys are the file names and the values are `None` to remove the file, or
|
|
||||||
/// `bytes` for the content of the file.
|
|
||||||
#[pyfunction]
|
#[pyfunction]
|
||||||
#[pyo3(signature = (
|
|
||||||
apk,
|
|
||||||
dst,
|
|
||||||
dexfiles,
|
|
||||||
keystore,
|
|
||||||
zipalign=None,
|
|
||||||
apksigner=None,
|
|
||||||
additionnal_files=None
|
|
||||||
))]
|
|
||||||
pub fn replace_dex(
|
pub fn replace_dex(
|
||||||
apk: PathBuf,
|
apk: PathBuf,
|
||||||
dst: PathBuf,
|
dst: PathBuf,
|
||||||
dexfiles: Vec<Bound<'_, PyBytes>>,
|
dexfiles: Vec<&[u8]>,
|
||||||
keystore: PathBuf,
|
keystore: PathBuf,
|
||||||
zipalign: Option<PathBuf>,
|
zipalign: Option<PathBuf>,
|
||||||
apksigner: Option<PathBuf>,
|
apksigner: Option<PathBuf>,
|
||||||
additionnal_files: Option<HashMap<String, Option<Bound<'_, PyBytes>>>>,
|
|
||||||
) {
|
) {
|
||||||
let mut dexfiles: Vec<_> = dexfiles
|
let mut dexfiles: Vec<_> = dexfiles.iter().map(Cursor::new).collect();
|
||||||
.iter()
|
apk_frauder::replace_dex(apk, dst, &mut dexfiles, keystore, zipalign, apksigner)
|
||||||
.map(PyBytesMethods::as_bytes)
|
|
||||||
.map(Cursor::new)
|
|
||||||
.collect();
|
|
||||||
let additionnal_files: Option<HashMap<_, _>> =
|
|
||||||
additionnal_files.as_ref().map(|additionnal_files| {
|
|
||||||
additionnal_files
|
|
||||||
.iter()
|
|
||||||
.map(|(k, v)| {
|
|
||||||
(
|
|
||||||
k.clone(),
|
|
||||||
v.as_ref().map(|bytes| bytes.as_bytes()).map(Cursor::new),
|
|
||||||
)
|
|
||||||
})
|
|
||||||
.collect()
|
|
||||||
});
|
|
||||||
apk_frauder::replace_dex(
|
|
||||||
apk,
|
|
||||||
dst,
|
|
||||||
&mut dexfiles,
|
|
||||||
keystore,
|
|
||||||
zipalign,
|
|
||||||
apksigner,
|
|
||||||
additionnal_files,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// export the function in a python module
|
/// export the function in a python module
|
||||||
pub(crate) fn export_module(_py: Python, m: &Bound<'_, PyModule>) -> PyResult<()> {
|
pub(crate) fn export_module(_py: Python, m: &PyModule) -> PyResult<()> {
|
||||||
m.add_function(wrap_pyfunction!(int_to_uleb128, m)?)?;
|
m.add_function(wrap_pyfunction!(int_to_uleb128, m)?)?;
|
||||||
m.add_function(wrap_pyfunction!(int_to_uleb128p1, m)?)?;
|
m.add_function(wrap_pyfunction!(int_to_uleb128p1, m)?)?;
|
||||||
m.add_function(wrap_pyfunction!(int_to_sleb128, m)?)?;
|
m.add_function(wrap_pyfunction!(int_to_sleb128, m)?)?;
|
||||||
m.add_function(wrap_pyfunction!(uleb128_to_int, m)?)?;
|
m.add_function(wrap_pyfunction!(uleb128_to_int, m)?)?;
|
||||||
m.add_function(wrap_pyfunction!(uleb128p1_to_int, m)?)?;
|
m.add_function(wrap_pyfunction!(uleb128p1_to_int, m)?)?;
|
||||||
m.add_function(wrap_pyfunction!(sleb128_to_int, m)?)?;
|
m.add_function(wrap_pyfunction!(sleb128_to_int, m)?)?;
|
||||||
m.add_function(wrap_pyfunction!(list_defined_classes, m)?)?;
|
|
||||||
m.add_function(wrap_pyfunction!(is_dex, m)?)?;
|
|
||||||
m.add_function(wrap_pyfunction!(is_zip, m)?)?;
|
|
||||||
m.add_function(wrap_pyfunction!(replace_dex, m)?)?;
|
m.add_function(wrap_pyfunction!(replace_dex, m)?)?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,29 +4,24 @@ use crate::Result;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use std::collections::HashSet;
|
use std::collections::HashSet;
|
||||||
|
|
||||||
use crate::{
|
use crate::{DexString, DexValue, IdField, IdMethod, IdMethodType, IdType, MethodHandle};
|
||||||
DexString, DexValue, IdField, IdMethod, IdMethodType, IdType, MethodHandle, Visitable,
|
|
||||||
VisitableMut, Visitor, VisitorMut,
|
|
||||||
};
|
|
||||||
#[cfg(feature = "python")]
|
|
||||||
use pyo3::prelude::*;
|
use pyo3::prelude::*;
|
||||||
|
|
||||||
#[cfg_attr(feature = "python", pyclass(eq))]
|
#[pyclass]
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Deserialize, Serialize)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Deserialize, Serialize)]
|
||||||
pub struct DexByte(pub i8);
|
pub struct DexByte(pub i8);
|
||||||
|
#[pymethods]
|
||||||
#[cfg_attr(feature = "python", pymethods)]
|
|
||||||
impl DexByte {
|
impl DexByte {
|
||||||
pub fn to_json(&self) -> Result<String> {
|
pub fn to_json(&self) -> Result<String> {
|
||||||
Ok(serde_json::to_string(self)?)
|
Ok(serde_json::to_string(self)?)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg_attr(feature = "python", staticmethod)]
|
#[staticmethod]
|
||||||
pub fn from_json(json: &str) -> Result<Self> {
|
pub fn from_json(json: &str) -> Result<Self> {
|
||||||
Ok(serde_json::from_str(json)?)
|
Ok(serde_json::from_str(json)?)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg_attr(feature = "python", new)]
|
#[new]
|
||||||
pub fn new(val: i8) -> Self {
|
pub fn new(val: i8) -> Self {
|
||||||
Self(val)
|
Self(val)
|
||||||
}
|
}
|
||||||
|
|
@ -44,21 +39,21 @@ impl DexByte {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg_attr(feature = "python", pyclass(eq))]
|
#[pyclass]
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Deserialize, Serialize)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Deserialize, Serialize)]
|
||||||
pub struct DexShort(pub i16);
|
pub struct DexShort(pub i16);
|
||||||
#[cfg_attr(feature = "python", pymethods)]
|
#[pymethods]
|
||||||
impl DexShort {
|
impl DexShort {
|
||||||
pub fn to_json(&self) -> Result<String> {
|
pub fn to_json(&self) -> Result<String> {
|
||||||
Ok(serde_json::to_string(self)?)
|
Ok(serde_json::to_string(self)?)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg_attr(feature = "python", staticmethod)]
|
#[staticmethod]
|
||||||
pub fn from_json(json: &str) -> Result<Self> {
|
pub fn from_json(json: &str) -> Result<Self> {
|
||||||
Ok(serde_json::from_str(json)?)
|
Ok(serde_json::from_str(json)?)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg_attr(feature = "python", new)]
|
#[new]
|
||||||
pub fn new(val: i16) -> Self {
|
pub fn new(val: i16) -> Self {
|
||||||
Self(val)
|
Self(val)
|
||||||
}
|
}
|
||||||
|
|
@ -76,21 +71,21 @@ impl DexShort {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg_attr(feature = "python", pyclass(eq))]
|
#[pyclass]
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Deserialize, Serialize)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Deserialize, Serialize)]
|
||||||
pub struct DexChar(pub u16);
|
pub struct DexChar(pub u16);
|
||||||
#[cfg_attr(feature = "python", pymethods)]
|
#[pymethods]
|
||||||
impl DexChar {
|
impl DexChar {
|
||||||
pub fn to_json(&self) -> Result<String> {
|
pub fn to_json(&self) -> Result<String> {
|
||||||
Ok(serde_json::to_string(self)?)
|
Ok(serde_json::to_string(self)?)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg_attr(feature = "python", staticmethod)]
|
#[staticmethod]
|
||||||
pub fn from_json(json: &str) -> Result<Self> {
|
pub fn from_json(json: &str) -> Result<Self> {
|
||||||
Ok(serde_json::from_str(json)?)
|
Ok(serde_json::from_str(json)?)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg_attr(feature = "python", new)]
|
#[new]
|
||||||
pub fn new(val: u16) -> Self {
|
pub fn new(val: u16) -> Self {
|
||||||
Self(val)
|
Self(val)
|
||||||
}
|
}
|
||||||
|
|
@ -108,21 +103,21 @@ impl DexChar {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg_attr(feature = "python", pyclass(eq))]
|
#[pyclass]
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Deserialize, Serialize)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Deserialize, Serialize)]
|
||||||
pub struct DexInt(pub i32);
|
pub struct DexInt(pub i32);
|
||||||
#[cfg_attr(feature = "python", pymethods)]
|
#[pymethods]
|
||||||
impl DexInt {
|
impl DexInt {
|
||||||
pub fn to_json(&self) -> Result<String> {
|
pub fn to_json(&self) -> Result<String> {
|
||||||
Ok(serde_json::to_string(self)?)
|
Ok(serde_json::to_string(self)?)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg_attr(feature = "python", staticmethod)]
|
#[staticmethod]
|
||||||
pub fn from_json(json: &str) -> Result<Self> {
|
pub fn from_json(json: &str) -> Result<Self> {
|
||||||
Ok(serde_json::from_str(json)?)
|
Ok(serde_json::from_str(json)?)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg_attr(feature = "python", new)]
|
#[new]
|
||||||
pub fn new(val: i32) -> Self {
|
pub fn new(val: i32) -> Self {
|
||||||
Self(val)
|
Self(val)
|
||||||
}
|
}
|
||||||
|
|
@ -140,21 +135,21 @@ impl DexInt {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg_attr(feature = "python", pyclass(eq))]
|
#[pyclass]
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Deserialize, Serialize)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Deserialize, Serialize)]
|
||||||
pub struct DexLong(pub i64);
|
pub struct DexLong(pub i64);
|
||||||
#[cfg_attr(feature = "python", pymethods)]
|
#[pymethods]
|
||||||
impl DexLong {
|
impl DexLong {
|
||||||
pub fn to_json(&self) -> Result<String> {
|
pub fn to_json(&self) -> Result<String> {
|
||||||
Ok(serde_json::to_string(self)?)
|
Ok(serde_json::to_string(self)?)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg_attr(feature = "python", staticmethod)]
|
#[staticmethod]
|
||||||
pub fn from_json(json: &str) -> Result<Self> {
|
pub fn from_json(json: &str) -> Result<Self> {
|
||||||
Ok(serde_json::from_str(json)?)
|
Ok(serde_json::from_str(json)?)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg_attr(feature = "python", new)]
|
#[new]
|
||||||
pub fn new(val: i64) -> Self {
|
pub fn new(val: i64) -> Self {
|
||||||
Self(val)
|
Self(val)
|
||||||
}
|
}
|
||||||
|
|
@ -172,21 +167,21 @@ impl DexLong {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg_attr(feature = "python", pyclass(eq))]
|
#[pyclass]
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Deserialize, Serialize)]
|
#[derive(Debug, Clone, Copy, PartialEq, Deserialize, Serialize)]
|
||||||
pub struct DexFloat(pub f32);
|
pub struct DexFloat(pub f32);
|
||||||
#[cfg_attr(feature = "python", pymethods)]
|
#[pymethods]
|
||||||
impl DexFloat {
|
impl DexFloat {
|
||||||
pub fn to_json(&self) -> Result<String> {
|
pub fn to_json(&self) -> Result<String> {
|
||||||
Ok(serde_json::to_string(self)?)
|
Ok(serde_json::to_string(self)?)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg_attr(feature = "python", staticmethod)]
|
#[staticmethod]
|
||||||
pub fn from_json(json: &str) -> Result<Self> {
|
pub fn from_json(json: &str) -> Result<Self> {
|
||||||
Ok(serde_json::from_str(json)?)
|
Ok(serde_json::from_str(json)?)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg_attr(feature = "python", new)]
|
#[new]
|
||||||
pub fn new(val: f32) -> Self {
|
pub fn new(val: f32) -> Self {
|
||||||
Self(val)
|
Self(val)
|
||||||
}
|
}
|
||||||
|
|
@ -204,21 +199,21 @@ impl DexFloat {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg_attr(feature = "python", pyclass(eq))]
|
#[pyclass]
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Deserialize, Serialize)]
|
#[derive(Debug, Clone, Copy, PartialEq, Deserialize, Serialize)]
|
||||||
pub struct DexDouble(pub f64);
|
pub struct DexDouble(pub f64);
|
||||||
#[cfg_attr(feature = "python", pymethods)]
|
#[pymethods]
|
||||||
impl DexDouble {
|
impl DexDouble {
|
||||||
pub fn to_json(&self) -> Result<String> {
|
pub fn to_json(&self) -> Result<String> {
|
||||||
Ok(serde_json::to_string(self)?)
|
Ok(serde_json::to_string(self)?)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg_attr(feature = "python", staticmethod)]
|
#[staticmethod]
|
||||||
pub fn from_json(json: &str) -> Result<Self> {
|
pub fn from_json(json: &str) -> Result<Self> {
|
||||||
Ok(serde_json::from_str(json)?)
|
Ok(serde_json::from_str(json)?)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg_attr(feature = "python", new)]
|
#[new]
|
||||||
pub fn new(val: f64) -> Self {
|
pub fn new(val: f64) -> Self {
|
||||||
Self(val)
|
Self(val)
|
||||||
}
|
}
|
||||||
|
|
@ -237,21 +232,21 @@ impl DexDouble {
|
||||||
}
|
}
|
||||||
|
|
||||||
/* DexString is already define in lib.rs, TODO: move the version in lib.rs here
|
/* DexString is already define in lib.rs, TODO: move the version in lib.rs here
|
||||||
#[cfg_attr(feature = "python", pyclass(eq))]
|
#[pyclass]
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
pub struct DexString(pub u32);
|
pub struct DexString(pub u32);
|
||||||
#[cfg_attr(feature = "python", pymethods)]
|
#[pymethods]
|
||||||
impl DexString {
|
impl DexString {
|
||||||
pub fn to_json(&self) -> Result<String> {
|
pub fn to_json(&self) -> Result<String> {
|
||||||
Ok(serde_json::to_string(self)?)
|
Ok(serde_json::to_string(self)?)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg_attr(feature = "python", staticmethod)]
|
#[staticmethod]
|
||||||
pub fn from_json(json: &str) -> Result<Self> {
|
pub fn from_json(json: &str) -> Result<Self> {
|
||||||
Ok(serde_json::from_str(json)?)
|
Ok(serde_json::from_str(json)?)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg_attr(feature = "python", new)]
|
#[new]
|
||||||
pub fn new(val: u32) -> Self {
|
pub fn new(val: u32) -> Self {
|
||||||
Self(val)
|
Self(val)
|
||||||
}
|
}
|
||||||
|
|
@ -270,21 +265,21 @@ impl DexString {
|
||||||
}
|
}
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#[cfg_attr(feature = "python", pyclass(eq))]
|
#[pyclass]
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Deserialize, Serialize)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Deserialize, Serialize)]
|
||||||
pub struct DexNull;
|
pub struct DexNull;
|
||||||
#[cfg_attr(feature = "python", pymethods)]
|
#[pymethods]
|
||||||
impl DexNull {
|
impl DexNull {
|
||||||
pub fn to_json(&self) -> Result<String> {
|
pub fn to_json(&self) -> Result<String> {
|
||||||
Ok(serde_json::to_string(self)?)
|
Ok(serde_json::to_string(self)?)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg_attr(feature = "python", staticmethod)]
|
#[staticmethod]
|
||||||
pub fn from_json(json: &str) -> Result<Self> {
|
pub fn from_json(json: &str) -> Result<Self> {
|
||||||
Ok(serde_json::from_str(json)?)
|
Ok(serde_json::from_str(json)?)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg_attr(feature = "python", new)]
|
#[new]
|
||||||
pub fn _new() -> Self {
|
pub fn _new() -> Self {
|
||||||
Self
|
Self
|
||||||
}
|
}
|
||||||
|
|
@ -298,21 +293,21 @@ impl DexNull {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg_attr(feature = "python", pyclass(eq))]
|
#[pyclass]
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Deserialize, Serialize)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Deserialize, Serialize)]
|
||||||
pub struct DexBoolean(pub bool);
|
pub struct DexBoolean(pub bool);
|
||||||
#[cfg_attr(feature = "python", pymethods)]
|
#[pymethods]
|
||||||
impl DexBoolean {
|
impl DexBoolean {
|
||||||
pub fn to_json(&self) -> Result<String> {
|
pub fn to_json(&self) -> Result<String> {
|
||||||
Ok(serde_json::to_string(self)?)
|
Ok(serde_json::to_string(self)?)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg_attr(feature = "python", staticmethod)]
|
#[staticmethod]
|
||||||
pub fn from_json(json: &str) -> Result<Self> {
|
pub fn from_json(json: &str) -> Result<Self> {
|
||||||
Ok(serde_json::from_str(json)?)
|
Ok(serde_json::from_str(json)?)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg_attr(feature = "python", new)]
|
#[new]
|
||||||
pub fn new(val: bool) -> Self {
|
pub fn new(val: bool) -> Self {
|
||||||
Self(val)
|
Self(val)
|
||||||
}
|
}
|
||||||
|
|
@ -330,21 +325,21 @@ impl DexBoolean {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg_attr(feature = "python", pyclass(eq))]
|
#[pyclass]
|
||||||
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)]
|
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)]
|
||||||
pub struct DexArray(pub Vec<DexValue>);
|
pub struct DexArray(pub Vec<DexValue>);
|
||||||
#[cfg_attr(feature = "python", pymethods)]
|
#[pymethods]
|
||||||
impl DexArray {
|
impl DexArray {
|
||||||
pub fn to_json(&self) -> Result<String> {
|
pub fn to_json(&self) -> Result<String> {
|
||||||
Ok(serde_json::to_string(self)?)
|
Ok(serde_json::to_string(self)?)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg_attr(feature = "python", staticmethod)]
|
#[staticmethod]
|
||||||
pub fn from_json(json: &str) -> Result<Self> {
|
pub fn from_json(json: &str) -> Result<Self> {
|
||||||
Ok(serde_json::from_str(json)?)
|
Ok(serde_json::from_str(json)?)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg_attr(feature = "python", new)]
|
#[new]
|
||||||
pub fn _new(arr: Vec<DexValue>) -> Self {
|
pub fn _new(arr: Vec<DexValue>) -> Self {
|
||||||
Self(arr)
|
Self(arr)
|
||||||
}
|
}
|
||||||
|
|
@ -422,23 +417,3 @@ impl DexArray {
|
||||||
methods
|
methods
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<V: Visitor> Visitable<V> for DexArray {
|
|
||||||
fn default_visit(&self, v: &mut V) -> Result<()> {
|
|
||||||
for val in &self.0 {
|
|
||||||
v.visit_value(val)?;
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<V: VisitorMut> VisitableMut<V> for DexArray {
|
|
||||||
fn default_visit_mut(self, v: &mut V) -> Result<Self> {
|
|
||||||
Ok(Self(
|
|
||||||
self.0
|
|
||||||
.into_iter()
|
|
||||||
.map(|val| v.visit_value(val))
|
|
||||||
.collect::<Result<_>>()?,
|
|
||||||
))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -1,103 +0,0 @@
|
||||||
#!/usr/bin/python3
|
|
||||||
|
|
||||||
# require zip and apktool installed
|
|
||||||
|
|
||||||
import sys
|
|
||||||
import os
|
|
||||||
import tempfile
|
|
||||||
from pathlib import Path
|
|
||||||
import json
|
|
||||||
|
|
||||||
|
|
||||||
def parse_smali(path: Path) -> tuple[str, dict, dict]:
|
|
||||||
class_name: str | None = None
|
|
||||||
fields = {}
|
|
||||||
methods = {}
|
|
||||||
with path.open() as file:
|
|
||||||
for line in file.readlines():
|
|
||||||
if line.startswith(".class"):
|
|
||||||
if class_name is None:
|
|
||||||
class_name = line.strip().split(" ")[-1]
|
|
||||||
else:
|
|
||||||
raise RuntimeError(f"Two classes found in {path}")
|
|
||||||
if line.startswith(".field"):
|
|
||||||
k, v = parse_field(line)
|
|
||||||
fields[k] = v
|
|
||||||
elif line.startswith(".method"):
|
|
||||||
k, v = parse_method(line)
|
|
||||||
methods[k] = v
|
|
||||||
if class_name is None:
|
|
||||||
raise RuntimeError(f"No classe found in {path}")
|
|
||||||
return (class_name, fields, methods)
|
|
||||||
|
|
||||||
|
|
||||||
def parse_field(line: str) -> tuple[str, dict]:
|
|
||||||
data: dict[str, None | str | list[str]] = {
|
|
||||||
"value": None,
|
|
||||||
"other": [],
|
|
||||||
"hiddenapi": None,
|
|
||||||
"hiddenapi_domain": None,
|
|
||||||
}
|
|
||||||
if "=" in line:
|
|
||||||
line, val = line.split("=")[0], "=".join(line.split("=")[1:])
|
|
||||||
data["value"] = val.strip()
|
|
||||||
line, ty = line.split(":")
|
|
||||||
vals = list(map(str.strip, line.split(" ")))
|
|
||||||
name = f"{vals[-1]}:{ty.strip()}"
|
|
||||||
vals = vals[:-1]
|
|
||||||
if vals:
|
|
||||||
if "api" in vals[-1]:
|
|
||||||
data["hiddenapi_domain"] = vals[-1]
|
|
||||||
vals = vals[:-1]
|
|
||||||
if vals:
|
|
||||||
if "list" in vals[-1]:
|
|
||||||
data["hiddenapi"] = vals[-1]
|
|
||||||
vals = vals[:-1]
|
|
||||||
data["other"] = vals
|
|
||||||
return name, data
|
|
||||||
|
|
||||||
|
|
||||||
def parse_method(line: str) -> tuple[str, dict]:
|
|
||||||
data: dict[str, None | str | list[str]] = {
|
|
||||||
"other": [],
|
|
||||||
"hiddenapi": None,
|
|
||||||
"hiddenapi_domain": None,
|
|
||||||
}
|
|
||||||
vals = list(map(str.strip, line.split(" ")))
|
|
||||||
name = vals[-1]
|
|
||||||
vals = vals[:-1]
|
|
||||||
if vals:
|
|
||||||
if "api" in vals[-1]:
|
|
||||||
data["hiddenapi_domain"] = vals[-1]
|
|
||||||
vals = vals[:-1]
|
|
||||||
if vals:
|
|
||||||
if "list" in vals[-1]:
|
|
||||||
data["hiddenapi"] = vals[-1]
|
|
||||||
vals = vals[:-1]
|
|
||||||
data["other"] = vals
|
|
||||||
return name, data
|
|
||||||
|
|
||||||
|
|
||||||
def main():
|
|
||||||
if len(sys.argv) != 3:
|
|
||||||
print("rought smali parser")
|
|
||||||
print("usage:")
|
|
||||||
print(f" {sys.argv[0]} some_classes.dex output.json")
|
|
||||||
exit()
|
|
||||||
file = sys.argv[1]
|
|
||||||
dst = sys.argv[2]
|
|
||||||
data = {}
|
|
||||||
with tempfile.TemporaryDirectory() as tmp:
|
|
||||||
os.system(f"zip {tmp}/apk.apk {file}")
|
|
||||||
os.system(f"apktool d -o {tmp}/apktool.out {tmp}/apk.apk")
|
|
||||||
smalidir = Path(f"{tmp}/apktool.out/smali_{file.removesuffix('.dex')}")
|
|
||||||
for root, dirs, files in smalidir.walk():
|
|
||||||
for file in files:
|
|
||||||
class_name, fields, methods = parse_smali(root / file)
|
|
||||||
data[class_name] = {"fields": fields, "methods": methods}
|
|
||||||
with open(dst, "w") as file:
|
|
||||||
json.dump(data, file)
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
main()
|
|
||||||
|
|
@ -1,676 +0,0 @@
|
||||||
{
|
|
||||||
"dex_files": {
|
|
||||||
"classes.dex": {
|
|
||||||
"classes": {
|
|
||||||
"Lcom/example/testclassloader/TestA;": {
|
|
||||||
"descriptor": "Lcom/example/testclassloader/TestA;",
|
|
||||||
"is_public": true,
|
|
||||||
"is_final": false,
|
|
||||||
"is_interface": false,
|
|
||||||
"is_abstract": false,
|
|
||||||
"is_synthetic": false,
|
|
||||||
"is_annotation": false,
|
|
||||||
"is_enum": false,
|
|
||||||
"superclass": "Ljava/lang/Object;",
|
|
||||||
"interfaces": [],
|
|
||||||
"source_file": {
|
|
||||||
"String": "TestA.java"
|
|
||||||
},
|
|
||||||
"static_fields": {
|
|
||||||
"Lcom/example/testclassloader/TestA;->value:Ljava/lang/String;": {
|
|
||||||
"descriptor": "Lcom/example/testclassloader/TestA;->value:Ljava/lang/String;",
|
|
||||||
"visibility": "Public",
|
|
||||||
"is_static": true,
|
|
||||||
"is_final": false,
|
|
||||||
"is_volatile": false,
|
|
||||||
"is_transient": false,
|
|
||||||
"is_synthetic": false,
|
|
||||||
"is_enum": false,
|
|
||||||
"value": null,
|
|
||||||
"annotations": []
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"instance_fields": {},
|
|
||||||
"direct_methods": {
|
|
||||||
"Lcom/example/testclassloader/TestA;-><init>()V": {
|
|
||||||
"descriptor": "Lcom/example/testclassloader/TestA;-><init>()V",
|
|
||||||
"visibility": "Public",
|
|
||||||
"is_static": false,
|
|
||||||
"is_final": false,
|
|
||||||
"is_synchronized": false,
|
|
||||||
"is_bridge": false,
|
|
||||||
"is_varargs": false,
|
|
||||||
"is_native": false,
|
|
||||||
"is_abstract": false,
|
|
||||||
"is_strictfp": false,
|
|
||||||
"is_synthetic": false,
|
|
||||||
"is_constructor": true,
|
|
||||||
"is_declared_syncrhonized": false,
|
|
||||||
"annotations": [],
|
|
||||||
"parameters_annotations": [],
|
|
||||||
"code": {
|
|
||||||
"registers_size": 1,
|
|
||||||
"ins_size": 1,
|
|
||||||
"outs_size": 1,
|
|
||||||
"debug_info": [
|
|
||||||
3,
|
|
||||||
[
|
|
||||||
14,
|
|
||||||
0
|
|
||||||
]
|
|
||||||
],
|
|
||||||
"parameter_names": [],
|
|
||||||
"insns": [
|
|
||||||
{
|
|
||||||
"Label": {
|
|
||||||
"name": "label_00000000"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"InvokeDirect": {
|
|
||||||
"method": "Ljava/lang/Object;-><init>()V",
|
|
||||||
"args": [
|
|
||||||
0
|
|
||||||
]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"Label": {
|
|
||||||
"name": "label_00000003"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"ReturnVoid": {}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"Lcom/example/testclassloader/TestA;-><clinit>()V": {
|
|
||||||
"descriptor": "Lcom/example/testclassloader/TestA;-><clinit>()V",
|
|
||||||
"visibility": "None_",
|
|
||||||
"is_static": true,
|
|
||||||
"is_final": false,
|
|
||||||
"is_synchronized": false,
|
|
||||||
"is_bridge": false,
|
|
||||||
"is_varargs": false,
|
|
||||||
"is_native": false,
|
|
||||||
"is_abstract": false,
|
|
||||||
"is_strictfp": false,
|
|
||||||
"is_synthetic": false,
|
|
||||||
"is_constructor": true,
|
|
||||||
"is_declared_syncrhonized": false,
|
|
||||||
"annotations": [],
|
|
||||||
"parameters_annotations": [],
|
|
||||||
"code": {
|
|
||||||
"registers_size": 1,
|
|
||||||
"ins_size": 0,
|
|
||||||
"outs_size": 0,
|
|
||||||
"debug_info": [
|
|
||||||
5,
|
|
||||||
[
|
|
||||||
14,
|
|
||||||
0
|
|
||||||
]
|
|
||||||
],
|
|
||||||
"parameter_names": [],
|
|
||||||
"insns": [
|
|
||||||
{
|
|
||||||
"Label": {
|
|
||||||
"name": "label_00000000"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"ConstString": {
|
|
||||||
"reg": 0,
|
|
||||||
"lit": {
|
|
||||||
"String": "Pirat\\')"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"Label": {
|
|
||||||
"name": "label_00000002"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"SPutObject": {
|
|
||||||
"from": 0,
|
|
||||||
"field": "Lcom/example/testclassloader/TestA;->value:Ljava/lang/String;"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"Label": {
|
|
||||||
"name": "label_00000004"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"ReturnVoid": {}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"Label": {
|
|
||||||
"name": "label_00000005"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"Nop": {}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"virtual_methods": {
|
|
||||||
"Lcom/example/testclassloader/TestA;->get_value()Ljava/lang/String;": {
|
|
||||||
"descriptor": "Lcom/example/testclassloader/TestA;->get_value()Ljava/lang/String;",
|
|
||||||
"visibility": "Public",
|
|
||||||
"is_static": false,
|
|
||||||
"is_final": false,
|
|
||||||
"is_synchronized": false,
|
|
||||||
"is_bridge": false,
|
|
||||||
"is_varargs": false,
|
|
||||||
"is_native": false,
|
|
||||||
"is_abstract": false,
|
|
||||||
"is_strictfp": false,
|
|
||||||
"is_synthetic": false,
|
|
||||||
"is_constructor": false,
|
|
||||||
"is_declared_syncrhonized": false,
|
|
||||||
"annotations": [],
|
|
||||||
"parameters_annotations": [],
|
|
||||||
"code": {
|
|
||||||
"registers_size": 2,
|
|
||||||
"ins_size": 1,
|
|
||||||
"outs_size": 0,
|
|
||||||
"debug_info": [
|
|
||||||
11,
|
|
||||||
[
|
|
||||||
14,
|
|
||||||
0
|
|
||||||
]
|
|
||||||
],
|
|
||||||
"parameter_names": [],
|
|
||||||
"insns": [
|
|
||||||
{
|
|
||||||
"Label": {
|
|
||||||
"name": "label_00000000"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"SGetObject": {
|
|
||||||
"to": 0,
|
|
||||||
"field": "Lcom/example/testclassloader/TestA;->value:Ljava/lang/String;"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"Label": {
|
|
||||||
"name": "label_00000002"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"ReturnObject": {
|
|
||||||
"reg": 0
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"Label": {
|
|
||||||
"name": "label_00000003"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"Nop": {}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"Lcom/example/testclassloader/TestA;->get_cl()Ljava/lang/String;": {
|
|
||||||
"descriptor": "Lcom/example/testclassloader/TestA;->get_cl()Ljava/lang/String;",
|
|
||||||
"visibility": "Public",
|
|
||||||
"is_static": false,
|
|
||||||
"is_final": false,
|
|
||||||
"is_synchronized": false,
|
|
||||||
"is_bridge": false,
|
|
||||||
"is_varargs": false,
|
|
||||||
"is_native": false,
|
|
||||||
"is_abstract": false,
|
|
||||||
"is_strictfp": false,
|
|
||||||
"is_synthetic": false,
|
|
||||||
"is_constructor": false,
|
|
||||||
"is_declared_syncrhonized": false,
|
|
||||||
"annotations": [],
|
|
||||||
"parameters_annotations": [],
|
|
||||||
"code": {
|
|
||||||
"registers_size": 2,
|
|
||||||
"ins_size": 1,
|
|
||||||
"outs_size": 1,
|
|
||||||
"debug_info": [
|
|
||||||
7,
|
|
||||||
[
|
|
||||||
14,
|
|
||||||
0
|
|
||||||
]
|
|
||||||
],
|
|
||||||
"parameter_names": [],
|
|
||||||
"insns": [
|
|
||||||
{
|
|
||||||
"Label": {
|
|
||||||
"name": "label_00000000"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"InvokeVirtual": {
|
|
||||||
"method": "Ljava/lang/Object;->getClass()Ljava/lang/Class;",
|
|
||||||
"args": [
|
|
||||||
1
|
|
||||||
]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"Label": {
|
|
||||||
"name": "label_00000003"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"MoveResultObject": {
|
|
||||||
"to": 0
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"Label": {
|
|
||||||
"name": "label_00000004"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"InvokeVirtual": {
|
|
||||||
"method": "Ljava/lang/Class;->getClassLoader()Ljava/lang/ClassLoader;",
|
|
||||||
"args": [
|
|
||||||
0
|
|
||||||
]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"Label": {
|
|
||||||
"name": "label_00000007"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"MoveResultObject": {
|
|
||||||
"to": 0
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"Label": {
|
|
||||||
"name": "label_00000008"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"InvokeVirtual": {
|
|
||||||
"method": "Ljava/lang/Object;->toString()Ljava/lang/String;",
|
|
||||||
"args": [
|
|
||||||
0
|
|
||||||
]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"Label": {
|
|
||||||
"name": "label_0000000B"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"MoveResultObject": {
|
|
||||||
"to": 0
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"Label": {
|
|
||||||
"name": "label_0000000C"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"ReturnObject": {
|
|
||||||
"reg": 0
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"Label": {
|
|
||||||
"name": "label_0000000D"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"Nop": {}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"annotations": []
|
|
||||||
},
|
|
||||||
"Lcom/example/testclassloader/TestB;": {
|
|
||||||
"descriptor": "Lcom/example/testclassloader/TestB;",
|
|
||||||
"is_public": true,
|
|
||||||
"is_final": false,
|
|
||||||
"is_interface": false,
|
|
||||||
"is_abstract": false,
|
|
||||||
"is_synthetic": false,
|
|
||||||
"is_annotation": false,
|
|
||||||
"is_enum": false,
|
|
||||||
"superclass": "Ljava/lang/Object;",
|
|
||||||
"interfaces": [],
|
|
||||||
"source_file": {
|
|
||||||
"String": "TestB.java"
|
|
||||||
},
|
|
||||||
"static_fields": {},
|
|
||||||
"instance_fields": {},
|
|
||||||
"direct_methods": {
|
|
||||||
"Lcom/example/testclassloader/TestB;-><init>()V": {
|
|
||||||
"descriptor": "Lcom/example/testclassloader/TestB;-><init>()V",
|
|
||||||
"visibility": "Public",
|
|
||||||
"is_static": false,
|
|
||||||
"is_final": false,
|
|
||||||
"is_synchronized": false,
|
|
||||||
"is_bridge": false,
|
|
||||||
"is_varargs": false,
|
|
||||||
"is_native": false,
|
|
||||||
"is_abstract": false,
|
|
||||||
"is_strictfp": false,
|
|
||||||
"is_synthetic": false,
|
|
||||||
"is_constructor": true,
|
|
||||||
"is_declared_syncrhonized": false,
|
|
||||||
"annotations": [],
|
|
||||||
"parameters_annotations": [],
|
|
||||||
"code": {
|
|
||||||
"registers_size": 1,
|
|
||||||
"ins_size": 1,
|
|
||||||
"outs_size": 1,
|
|
||||||
"debug_info": [
|
|
||||||
3,
|
|
||||||
[
|
|
||||||
14,
|
|
||||||
0
|
|
||||||
]
|
|
||||||
],
|
|
||||||
"parameter_names": [],
|
|
||||||
"insns": [
|
|
||||||
{
|
|
||||||
"Label": {
|
|
||||||
"name": "label_00000000"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"InvokeDirect": {
|
|
||||||
"method": "Ljava/lang/Object;-><init>()V",
|
|
||||||
"args": [
|
|
||||||
0
|
|
||||||
]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"Label": {
|
|
||||||
"name": "label_00000003"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"ReturnVoid": {}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"Lcom/example/testclassloader/TestB;-><clinit>()V": {
|
|
||||||
"descriptor": "Lcom/example/testclassloader/TestB;-><clinit>()V",
|
|
||||||
"visibility": "None_",
|
|
||||||
"is_static": true,
|
|
||||||
"is_final": false,
|
|
||||||
"is_synchronized": false,
|
|
||||||
"is_bridge": false,
|
|
||||||
"is_varargs": false,
|
|
||||||
"is_native": false,
|
|
||||||
"is_abstract": false,
|
|
||||||
"is_strictfp": false,
|
|
||||||
"is_synthetic": false,
|
|
||||||
"is_constructor": true,
|
|
||||||
"is_declared_syncrhonized": false,
|
|
||||||
"annotations": [],
|
|
||||||
"parameters_annotations": [],
|
|
||||||
"code": {
|
|
||||||
"registers_size": 1,
|
|
||||||
"ins_size": 0,
|
|
||||||
"outs_size": 0,
|
|
||||||
"debug_info": [
|
|
||||||
5,
|
|
||||||
[
|
|
||||||
14,
|
|
||||||
0
|
|
||||||
]
|
|
||||||
],
|
|
||||||
"parameter_names": [],
|
|
||||||
"insns": [
|
|
||||||
{
|
|
||||||
"Label": {
|
|
||||||
"name": "label_00000000"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"ConstString": {
|
|
||||||
"reg": 0,
|
|
||||||
"lit": {
|
|
||||||
"String": "Plopliplop"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"Label": {
|
|
||||||
"name": "label_00000002"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"SPutObject": {
|
|
||||||
"from": 0,
|
|
||||||
"field": "Lcom/example/testclassloader/TestB;->value:Ljava/lang/String;"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"Label": {
|
|
||||||
"name": "label_00000004"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"ReturnVoid": {}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"Label": {
|
|
||||||
"name": "label_00000005"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"Nop": {}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"virtual_methods": {
|
|
||||||
"Lcom/example/testclassloader/TestB;->get_cl()Ljava/lang/String;": {
|
|
||||||
"descriptor": "Lcom/example/testclassloader/TestB;->get_cl()Ljava/lang/String;",
|
|
||||||
"visibility": "Public",
|
|
||||||
"is_static": false,
|
|
||||||
"is_final": false,
|
|
||||||
"is_synchronized": false,
|
|
||||||
"is_bridge": false,
|
|
||||||
"is_varargs": false,
|
|
||||||
"is_native": false,
|
|
||||||
"is_abstract": false,
|
|
||||||
"is_strictfp": false,
|
|
||||||
"is_synthetic": false,
|
|
||||||
"is_constructor": false,
|
|
||||||
"is_declared_syncrhonized": false,
|
|
||||||
"annotations": [],
|
|
||||||
"parameters_annotations": [],
|
|
||||||
"code": {
|
|
||||||
"registers_size": 2,
|
|
||||||
"ins_size": 1,
|
|
||||||
"outs_size": 1,
|
|
||||||
"debug_info": [
|
|
||||||
7,
|
|
||||||
[
|
|
||||||
14,
|
|
||||||
0
|
|
||||||
]
|
|
||||||
],
|
|
||||||
"parameter_names": [],
|
|
||||||
"insns": [
|
|
||||||
{
|
|
||||||
"Label": {
|
|
||||||
"name": "label_00000000"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"InvokeVirtual": {
|
|
||||||
"method": "Ljava/lang/Object;->getClass()Ljava/lang/Class;",
|
|
||||||
"args": [
|
|
||||||
1
|
|
||||||
]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"Label": {
|
|
||||||
"name": "label_00000003"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"MoveResultObject": {
|
|
||||||
"to": 0
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"Label": {
|
|
||||||
"name": "label_00000004"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"InvokeVirtual": {
|
|
||||||
"method": "Ljava/lang/Class;->getClassLoader()Ljava/lang/ClassLoader;",
|
|
||||||
"args": [
|
|
||||||
0
|
|
||||||
]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"Label": {
|
|
||||||
"name": "label_00000007"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"MoveResultObject": {
|
|
||||||
"to": 0
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"Label": {
|
|
||||||
"name": "label_00000008"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"InvokeVirtual": {
|
|
||||||
"method": "Ljava/lang/Object;->toString()Ljava/lang/String;",
|
|
||||||
"args": [
|
|
||||||
0
|
|
||||||
]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"Label": {
|
|
||||||
"name": "label_0000000B"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"MoveResultObject": {
|
|
||||||
"to": 0
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"Label": {
|
|
||||||
"name": "label_0000000C"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"ReturnObject": {
|
|
||||||
"reg": 0
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"Label": {
|
|
||||||
"name": "label_0000000D"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"Nop": {}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"Lcom/example/testclassloader/TestB;->get_value()Ljava/lang/String;": {
|
|
||||||
"descriptor": "Lcom/example/testclassloader/TestB;->get_value()Ljava/lang/String;",
|
|
||||||
"visibility": "Public",
|
|
||||||
"is_static": false,
|
|
||||||
"is_final": false,
|
|
||||||
"is_synchronized": false,
|
|
||||||
"is_bridge": false,
|
|
||||||
"is_varargs": false,
|
|
||||||
"is_native": false,
|
|
||||||
"is_abstract": false,
|
|
||||||
"is_strictfp": false,
|
|
||||||
"is_synthetic": false,
|
|
||||||
"is_constructor": false,
|
|
||||||
"is_declared_syncrhonized": false,
|
|
||||||
"annotations": [],
|
|
||||||
"parameters_annotations": [],
|
|
||||||
"code": {
|
|
||||||
"registers_size": 2,
|
|
||||||
"ins_size": 1,
|
|
||||||
"outs_size": 0,
|
|
||||||
"debug_info": [
|
|
||||||
11,
|
|
||||||
[
|
|
||||||
14,
|
|
||||||
0
|
|
||||||
]
|
|
||||||
],
|
|
||||||
"parameter_names": [],
|
|
||||||
"insns": [
|
|
||||||
{
|
|
||||||
"Label": {
|
|
||||||
"name": "label_00000000"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"SGetObject": {
|
|
||||||
"to": 0,
|
|
||||||
"field": "Lcom/example/testclassloader/TestA;->value:Ljava/lang/String;"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"Label": {
|
|
||||||
"name": "label_00000002"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"ReturnObject": {
|
|
||||||
"reg": 0
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"Label": {
|
|
||||||
"name": "label_00000003"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"Nop": {}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"annotations": []
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"not_referenced_strings": []
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Binary file not shown.
File diff suppressed because one or more lines are too long
|
|
@ -1,11 +1,9 @@
|
||||||
use super::*;
|
use super::*;
|
||||||
use androscalpel_serializer::Instruction as InsFormat;
|
use androscalpel_serializer::Instruction as InsFormat;
|
||||||
use androscalpel_serializer::*;
|
use androscalpel_serializer::*;
|
||||||
use serde_json as sj;
|
|
||||||
use std::collections::HashSet;
|
|
||||||
use std::fs::File;
|
use std::fs::File;
|
||||||
use std::io;
|
use std::io;
|
||||||
use std::io::{Read, Write};
|
use std::io::Write;
|
||||||
use std::ops::Deref;
|
use std::ops::Deref;
|
||||||
use std::sync::{Mutex, OnceLock};
|
use std::sync::{Mutex, OnceLock};
|
||||||
use std::time::Instant;
|
use std::time::Instant;
|
||||||
|
|
@ -13,14 +11,13 @@ use std::time::Instant;
|
||||||
fn write_to_report(data: &str) {
|
fn write_to_report(data: &str) {
|
||||||
static REPORT_FILE: Mutex<Option<File>> = Mutex::new(None);
|
static REPORT_FILE: Mutex<Option<File>> = Mutex::new(None);
|
||||||
let mut report_file = REPORT_FILE.lock().unwrap();
|
let mut report_file = REPORT_FILE.lock().unwrap();
|
||||||
let mut report_file = match report_file.deref() {
|
let mut report_file = if let Some(report_file) = report_file.deref() {
|
||||||
Some(report_file) => report_file,
|
report_file
|
||||||
_ => {
|
} else {
|
||||||
*report_file = Some(
|
*report_file = Some(
|
||||||
File::create(&format!("{}/test_repport.txt", env!("CARGO_MANIFEST_DIR"),)).unwrap(),
|
File::create(&format!("{}/test_repport.txt", env!("CARGO_MANIFEST_DIR"),)).unwrap(),
|
||||||
);
|
);
|
||||||
report_file.deref().as_ref().unwrap()
|
report_file.deref().as_ref().unwrap()
|
||||||
}
|
|
||||||
};
|
};
|
||||||
writeln!(report_file, "{data}").unwrap();
|
writeln!(report_file, "{data}").unwrap();
|
||||||
}
|
}
|
||||||
|
|
@ -43,8 +40,7 @@ fn get_hello_world_apk() -> &'static Apk {
|
||||||
HELLO_WORLD_APK.get_or_init(|| {
|
HELLO_WORLD_APK.get_or_init(|| {
|
||||||
let mut apk = Apk::new();
|
let mut apk = Apk::new();
|
||||||
let start = Instant::now();
|
let start = Instant::now();
|
||||||
apk.add_dex_file("classes.dex", get_hello_world_dex(), |_, _, _| None, false)
|
apk.add_dex_file(get_hello_world_dex()).unwrap();
|
||||||
.unwrap();
|
|
||||||
let duration = start.elapsed();
|
let duration = start.elapsed();
|
||||||
write_to_report(&format!("Parsing classes_hello_world.dex: {duration:?}"));
|
write_to_report(&format!("Parsing classes_hello_world.dex: {duration:?}"));
|
||||||
apk
|
apk
|
||||||
|
|
@ -55,11 +51,7 @@ fn get_hello_world_recompilled() -> &'static [u8] {
|
||||||
static HELLO_WORLD_RECOMP: OnceLock<Vec<u8>> = OnceLock::new();
|
static HELLO_WORLD_RECOMP: OnceLock<Vec<u8>> = OnceLock::new();
|
||||||
HELLO_WORLD_RECOMP.get_or_init(|| {
|
HELLO_WORLD_RECOMP.get_or_init(|| {
|
||||||
let start = Instant::now();
|
let start = Instant::now();
|
||||||
let dex = get_hello_world_apk()
|
let dex = get_hello_world_apk().gen_raw_dex().unwrap().pop().unwrap();
|
||||||
.gen_raw_dex()
|
|
||||||
.unwrap()
|
|
||||||
.remove("classes.dex")
|
|
||||||
.unwrap();
|
|
||||||
let duration = start.elapsed();
|
let duration = start.elapsed();
|
||||||
write_to_report(&format!("Recompile classes_hello_world.dex: {duration:?}"));
|
write_to_report(&format!("Recompile classes_hello_world.dex: {duration:?}"));
|
||||||
dex
|
dex
|
||||||
|
|
@ -85,7 +77,7 @@ fn get_class_dex<'a, 'b>(name: &'a str, dex: &'b DexFileReader) -> Option<&'b Cl
|
||||||
|
|
||||||
fn get_method_code_dex(name: &str, dex: &DexFileReader) -> Option<CodeItem> {
|
fn get_method_code_dex(name: &str, dex: &DexFileReader) -> Option<CodeItem> {
|
||||||
let method = IdMethod::from_smali(name).unwrap();
|
let method = IdMethod::from_smali(name).unwrap();
|
||||||
let class_name: String = (&method.class_.0).try_into().unwrap();
|
let class_name: String = (&method.class_.0).into();
|
||||||
let class = get_class_dex(&class_name, dex);
|
let class = get_class_dex(&class_name, dex);
|
||||||
let class = if let Some(class) = class {
|
let class = if let Some(class) = class {
|
||||||
class
|
class
|
||||||
|
|
@ -142,30 +134,7 @@ fn test_generated_data_size() {
|
||||||
fn test_generated_apk_equivalence() {
|
fn test_generated_apk_equivalence() {
|
||||||
let new_dex = get_hello_world_recompilled();
|
let new_dex = get_hello_world_recompilled();
|
||||||
let mut new_apk = Apk::new();
|
let mut new_apk = Apk::new();
|
||||||
new_apk
|
new_apk.add_dex_file(&new_dex).unwrap();
|
||||||
.add_dex_file("classes.dex", &new_dex, |_, _, _| None, false)
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
/*
|
|
||||||
use pretty_assertions::assert_eq;
|
|
||||||
let method = IdMethod::from_smali(
|
|
||||||
"Lcom/google/android/material/datepicker/DateFormatTextWatcher;->lambda$new$0$com-google-android-material-datepicker-DateFormatTextWatcher(Ljava/lang/String;)V"
|
|
||||||
)
|
|
||||||
.unwrap();
|
|
||||||
assert_eq!(
|
|
||||||
get_hello_world_apk()
|
|
||||||
.get_class(&method.class_)
|
|
||||||
.unwrap()
|
|
||||||
.virtual_methods
|
|
||||||
.get(&method)
|
|
||||||
.unwrap(),
|
|
||||||
new_apk
|
|
||||||
.get_class(&method.class_)
|
|
||||||
.unwrap()
|
|
||||||
.virtual_methods
|
|
||||||
.get(&method)
|
|
||||||
.unwrap()
|
|
||||||
);*/
|
|
||||||
assert_eq!(get_hello_world_apk(), &new_apk);
|
assert_eq!(get_hello_world_apk(), &new_apk);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -383,344 +352,3 @@ fn test_sort_strings() {
|
||||||
assert_eq!(list1, list2);
|
assert_eq!(list1, list2);
|
||||||
assert_eq!(list1, vec![s1.clone(), s2.clone()]);
|
assert_eq!(list1, vec![s1.clone(), s2.clone()]);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Emulate test https://cs.android.com/android/platform/superproject/main/+/main:art/libdexfile/dex/dex_file_verifier.cc;drc=e8da7cd1d0e7d3535c82f8d05adcef3edd43cd40;l=581
|
|
||||||
fn check_valid_offset_and_size(
|
|
||||||
dex: &DexFileReader,
|
|
||||||
offset: u32,
|
|
||||||
size: u32,
|
|
||||||
alignment: u32,
|
|
||||||
label: &str,
|
|
||||||
) {
|
|
||||||
if size == 0 {
|
|
||||||
if offset != 0 {
|
|
||||||
panic!("Offset 0x{offset:x} should be zero when size is zero for {label}");
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
// offset < hdr_offset is not relevent (we index from hdr_offset=0)
|
|
||||||
let file_size = dex.get_header().file_size;
|
|
||||||
if file_size <= offset {
|
|
||||||
panic!("Offset 0x{offset:x} sould be within file size 0x{file_size:x} for {label}");
|
|
||||||
}
|
|
||||||
if file_size - offset < size {
|
|
||||||
// size + offset could overflow
|
|
||||||
panic!(
|
|
||||||
"Section end 0x{:x} should be within file size 0x{file_size:x} for {label}",
|
|
||||||
size + offset
|
|
||||||
);
|
|
||||||
}
|
|
||||||
if alignment != 0 && !(offset & (alignment - 1) == 0) {
|
|
||||||
panic!("Offset 0x{offset:x} sould be aligned by {alignment} for {label}");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_1_from_json() {
|
|
||||||
let filename = "test_class1.json";
|
|
||||||
let hello_world_dex = format!("{}/src/tests/{}", env!("CARGO_MANIFEST_DIR"), filename);
|
|
||||||
let mut file = File::open(&hello_world_dex).expect(&format!("{} not found", filename));
|
|
||||||
let mut json = String::new();
|
|
||||||
file.read_to_string(&mut json).unwrap();
|
|
||||||
let test_a: Class = serde_json::from_str(&json).unwrap();
|
|
||||||
let mut apk = Apk::new();
|
|
||||||
apk.add_class("classes.dex", test_a).unwrap();
|
|
||||||
let dex = apk.gen_raw_dex().unwrap().remove("classes.dex").unwrap();
|
|
||||||
let dex = DexFileReader::new(&dex).unwrap();
|
|
||||||
|
|
||||||
check_valid_offset_and_size(
|
|
||||||
&dex,
|
|
||||||
dex.get_header().link_off,
|
|
||||||
dex.get_header().link_size,
|
|
||||||
0,
|
|
||||||
"link",
|
|
||||||
);
|
|
||||||
check_valid_offset_and_size(&dex, dex.get_header().map_off, 4, 4, "map");
|
|
||||||
check_valid_offset_and_size(
|
|
||||||
&dex,
|
|
||||||
dex.get_header().string_ids_off,
|
|
||||||
dex.get_header().string_ids_size,
|
|
||||||
4,
|
|
||||||
"string-ids",
|
|
||||||
);
|
|
||||||
check_valid_offset_and_size(
|
|
||||||
&dex,
|
|
||||||
dex.get_header().type_ids_off,
|
|
||||||
dex.get_header().type_ids_size,
|
|
||||||
4,
|
|
||||||
"type-ids",
|
|
||||||
);
|
|
||||||
check_valid_offset_and_size(
|
|
||||||
&dex,
|
|
||||||
dex.get_header().proto_ids_off,
|
|
||||||
dex.get_header().proto_ids_size,
|
|
||||||
4,
|
|
||||||
"proto-ids",
|
|
||||||
);
|
|
||||||
check_valid_offset_and_size(
|
|
||||||
&dex,
|
|
||||||
dex.get_header().field_ids_off,
|
|
||||||
dex.get_header().field_ids_size,
|
|
||||||
4,
|
|
||||||
"field-ids",
|
|
||||||
);
|
|
||||||
check_valid_offset_and_size(
|
|
||||||
&dex,
|
|
||||||
dex.get_header().method_ids_off,
|
|
||||||
dex.get_header().method_ids_size,
|
|
||||||
4,
|
|
||||||
"method-ids",
|
|
||||||
);
|
|
||||||
check_valid_offset_and_size(
|
|
||||||
&dex,
|
|
||||||
dex.get_header().class_defs_off,
|
|
||||||
dex.get_header().class_defs_size,
|
|
||||||
4,
|
|
||||||
"class-defs",
|
|
||||||
);
|
|
||||||
check_valid_offset_and_size(
|
|
||||||
&dex,
|
|
||||||
dex.get_header().data_off,
|
|
||||||
dex.get_header().data_size,
|
|
||||||
0,
|
|
||||||
"data",
|
|
||||||
);
|
|
||||||
if dex.get_header().type_ids_size > (u16::MAX as u32) {
|
|
||||||
panic!(
|
|
||||||
"Size 0x{:x} should not exceed limit 0x{:x} for type-ids",
|
|
||||||
dex.get_header().type_ids_size,
|
|
||||||
u16::MAX
|
|
||||||
)
|
|
||||||
}
|
|
||||||
if dex.get_header().proto_ids_size > (u16::MAX as u32) {
|
|
||||||
panic!(
|
|
||||||
"Size 0x{:x} should not exceed limit 0x{:x} for proto-ids",
|
|
||||||
dex.get_header().proto_ids_size,
|
|
||||||
u16::MAX
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
let map = dex.get_map_list();
|
|
||||||
let mut last_offset = 0;
|
|
||||||
let mut last_type = MapItemType::UnkownType(0);
|
|
||||||
let mut data_item_count = 0;
|
|
||||||
let mut data_items_left = dex.get_header().data_size;
|
|
||||||
let file_size = dex.get_header().file_size;
|
|
||||||
let mut used_types = HashSet::new();
|
|
||||||
for (i, item) in map.list.iter().enumerate() {
|
|
||||||
if last_offset >= item.offset && i != 0 {
|
|
||||||
panic!(
|
|
||||||
"Out of order map item: 0x{:x} then 0x{:x} for type {:?} (last type was {:?})",
|
|
||||||
last_offset, item.offset, item.type_, last_type
|
|
||||||
)
|
|
||||||
}
|
|
||||||
if item.offset >= file_size {
|
|
||||||
panic!(
|
|
||||||
"Map item for type {:?} ends after file: 0x{:x} >= 0x{:x}",
|
|
||||||
item.type_, item.offset, file_size
|
|
||||||
);
|
|
||||||
}
|
|
||||||
if item.type_.is_data_section_type() {
|
|
||||||
if item.size > data_items_left {
|
|
||||||
panic!(
|
|
||||||
"Too many items in data section: {} > {} (item type: {:?}",
|
|
||||||
item.size + data_item_count,
|
|
||||||
dex.get_header().data_size,
|
|
||||||
item.type_
|
|
||||||
);
|
|
||||||
}
|
|
||||||
data_items_left -= item.size;
|
|
||||||
data_item_count += item.size;
|
|
||||||
}
|
|
||||||
if used_types.contains(&item.type_) {
|
|
||||||
panic!("Duplicate map section of type {:?}", item.type_);
|
|
||||||
}
|
|
||||||
used_types.insert(item.type_);
|
|
||||||
last_offset = item.offset;
|
|
||||||
last_type = item.type_;
|
|
||||||
}
|
|
||||||
if !used_types.contains(&MapItemType::HeaderItem) {
|
|
||||||
panic!("Map is missing Header entry");
|
|
||||||
}
|
|
||||||
if !used_types.contains(&MapItemType::MapList) {
|
|
||||||
panic!("Map is missing Map List entry");
|
|
||||||
}
|
|
||||||
if !used_types.contains(&MapItemType::StringIdItem) && (dex.get_header().string_ids_off != 0) {
|
|
||||||
panic!("Map is missing String Id entry");
|
|
||||||
}
|
|
||||||
if !used_types.contains(&MapItemType::TypeIdItem) && (dex.get_header().type_ids_off != 0) {
|
|
||||||
panic!("Map is missing Type Id entry");
|
|
||||||
}
|
|
||||||
if !used_types.contains(&MapItemType::ProtoIdItem) && (dex.get_header().proto_ids_off != 0) {
|
|
||||||
panic!("Map is missing Proto Id entry");
|
|
||||||
}
|
|
||||||
if !used_types.contains(&MapItemType::FieldIdItem) && (dex.get_header().field_ids_off != 0) {
|
|
||||||
panic!("Map is missing Field Id entry");
|
|
||||||
}
|
|
||||||
if !used_types.contains(&MapItemType::MethodIdItem) && (dex.get_header().method_ids_off != 0) {
|
|
||||||
panic!("Map is missing Method Id entry");
|
|
||||||
}
|
|
||||||
if !used_types.contains(&MapItemType::ClassDefItem) && (dex.get_header().class_defs_off != 0) {
|
|
||||||
panic!("Map is missing Class Def entry");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_2_from_json() {
|
|
||||||
let filename = "app1.json";
|
|
||||||
let json_path = format!("{}/src/tests/{}", env!("CARGO_MANIFEST_DIR"), filename);
|
|
||||||
let mut file = File::open(&json_path).expect(&format!("{} not found", filename));
|
|
||||||
let mut json = String::new();
|
|
||||||
file.read_to_string(&mut json).unwrap();
|
|
||||||
let apk: Apk = serde_json::from_str(&json).unwrap();
|
|
||||||
let dex = apk.gen_raw_dex().unwrap().remove("classes.dex").unwrap();
|
|
||||||
let mut new_apk = Apk::new();
|
|
||||||
new_apk
|
|
||||||
.add_dex_file("classes.dex", &dex, |_, _, _| None, false)
|
|
||||||
.unwrap();
|
|
||||||
assert_eq!(apk, new_apk);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_hidden_api() {
|
|
||||||
let dex_raw = get_dex("core-oj-33_classes.dex");
|
|
||||||
// The data are generated using src/tests/apktool_parse_hiddenapi.py src/tests/core-oj-33_classes.dex src/tests/core-oj-33_hiddenapi.json
|
|
||||||
let apktool_result = format!(
|
|
||||||
"{}/src/tests/{}",
|
|
||||||
env!("CARGO_MANIFEST_DIR"),
|
|
||||||
"core-oj-33_hiddenapi.json"
|
|
||||||
);
|
|
||||||
fn f_desc_to_pointer(f_dsc: &IdField) -> String {
|
|
||||||
let name: String = String::try_from(&f_dsc.name.0).unwrap().replace("/", "~1");
|
|
||||||
let ty: String = f_dsc.type_.try_to_smali().unwrap().replace("/", "~1");
|
|
||||||
format!(
|
|
||||||
"/{}/fields/{name}:{ty}",
|
|
||||||
f_dsc.class_.try_to_smali().unwrap().replace("/", "~1")
|
|
||||||
)
|
|
||||||
}
|
|
||||||
fn m_desc_to_pointer(m_dsc: &IdMethod) -> String {
|
|
||||||
let name: String = String::try_from(&m_dsc.name.0).unwrap().replace("/", "~1");
|
|
||||||
let ty: String = m_dsc.proto.try_to_smali().unwrap().replace("/", "~1");
|
|
||||||
format!(
|
|
||||||
"/{}/methods/{name}{ty}",
|
|
||||||
m_dsc.class_.try_to_smali().unwrap().replace("/", "~1")
|
|
||||||
)
|
|
||||||
}
|
|
||||||
fn compare_hidden_api(
|
|
||||||
apktool_hiddenapi: &sj::Value,
|
|
||||||
apktool_hiddenapi_domain: &sj::Value,
|
|
||||||
parsed_api: &Option<HiddenApiData>,
|
|
||||||
name: &str,
|
|
||||||
) {
|
|
||||||
match (apktool_hiddenapi, apktool_hiddenapi_domain, parsed_api) {
|
|
||||||
(sj::Value::Null, sj::Value::Null, None) => (),
|
|
||||||
(
|
|
||||||
sj::Value::String(apktool_hiddenapi),
|
|
||||||
sj::Value::String(apktool_domain),
|
|
||||||
Some(HiddenApiData { permission, domain }),
|
|
||||||
) if (permission.to_smali_name() == apktool_hiddenapi)
|
|
||||||
&& (&domain.to_smali_name() == apktool_domain) =>
|
|
||||||
{
|
|
||||||
()
|
|
||||||
}
|
|
||||||
(
|
|
||||||
sj::Value::String(apktool_hiddenapi),
|
|
||||||
sj::Value::Null,
|
|
||||||
Some(HiddenApiData { permission, domain }),
|
|
||||||
) if (permission.to_smali_name() == apktool_hiddenapi)
|
|
||||||
&& &domain.to_smali_name() == "" =>
|
|
||||||
{
|
|
||||||
()
|
|
||||||
}
|
|
||||||
|
|
||||||
_ => panic!(
|
|
||||||
"Expected {apktool_hiddenapi:?} and {apktool_hiddenapi_domain:?}, found {parsed_api:?} in {name}"
|
|
||||||
),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
let apktool_result = File::open(&apktool_result).expect("core-oj-33_hiddenapi.json not found");
|
|
||||||
let apktool_result = std::io::BufReader::new(apktool_result);
|
|
||||||
let apktool_result: sj::Value = sj::from_reader(apktool_result).unwrap();
|
|
||||||
let mut apk = Apk::new();
|
|
||||||
apk.add_dex_file("classes.dex", &dex_raw, |_, _, _| None, false)
|
|
||||||
.unwrap();
|
|
||||||
for cls in apktool_result.as_object().unwrap().keys() {
|
|
||||||
assert!(
|
|
||||||
apk.dex_files
|
|
||||||
.get("classes.dex")
|
|
||||||
.unwrap()
|
|
||||||
.classes
|
|
||||||
.get(&dex_id::IdType(cls.as_str().into()))
|
|
||||||
.is_some(),
|
|
||||||
"{cls} not found in core-oj-33_classes.dex"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
for cls in apk.dex_files.get("classes.dex").unwrap().classes.keys() {
|
|
||||||
assert!(
|
|
||||||
apktool_result
|
|
||||||
.get::<String>((&cls.0).try_into().unwrap())
|
|
||||||
.is_some(),
|
|
||||||
"{} not found in core-oj-33_hiddenapi.json",
|
|
||||||
cls.__str__()
|
|
||||||
);
|
|
||||||
}
|
|
||||||
for (_, cls) in &apk.dex_files.get("classes.dex").unwrap().classes {
|
|
||||||
for (f_dsc, field) in &cls.static_fields {
|
|
||||||
let pointer = f_desc_to_pointer(f_dsc);
|
|
||||||
let apktool_field = apktool_result.pointer(&pointer).unwrap();
|
|
||||||
let apktool_hiddenapi = apktool_field.get("hiddenapi").unwrap();
|
|
||||||
let apktool_hiddenapi_domain = apktool_field.get("hiddenapi_domain").unwrap();
|
|
||||||
compare_hidden_api(
|
|
||||||
apktool_hiddenapi,
|
|
||||||
&apktool_hiddenapi_domain,
|
|
||||||
&field.hiddenapi,
|
|
||||||
&f_dsc.try_to_smali().unwrap(),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
for (f_dsc, field) in &cls.instance_fields {
|
|
||||||
let pointer = f_desc_to_pointer(f_dsc);
|
|
||||||
let apktool_field = apktool_result.pointer(&pointer).unwrap();
|
|
||||||
let apktool_hiddenapi = apktool_field.get("hiddenapi").unwrap();
|
|
||||||
let apktool_hiddenapi_domain = apktool_field.get("hiddenapi_domain").unwrap();
|
|
||||||
compare_hidden_api(
|
|
||||||
apktool_hiddenapi,
|
|
||||||
&apktool_hiddenapi_domain,
|
|
||||||
&field.hiddenapi,
|
|
||||||
&f_dsc.try_to_smali().unwrap(),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
for (m_dsc, method) in &cls.direct_methods {
|
|
||||||
let pointer = m_desc_to_pointer(m_dsc);
|
|
||||||
let apktool_method = apktool_result.pointer(&pointer).unwrap();
|
|
||||||
let apktool_hiddenapi = apktool_method.get("hiddenapi").unwrap();
|
|
||||||
let apktool_hiddenapi_domain = apktool_method.get("hiddenapi_domain").unwrap();
|
|
||||||
compare_hidden_api(
|
|
||||||
apktool_hiddenapi,
|
|
||||||
&apktool_hiddenapi_domain,
|
|
||||||
&method.hiddenapi,
|
|
||||||
&m_dsc.try_to_smali().unwrap(),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
for (m_dsc, method) in &cls.virtual_methods {
|
|
||||||
let pointer = m_desc_to_pointer(m_dsc);
|
|
||||||
let apktool_method = apktool_result.pointer(&pointer).unwrap();
|
|
||||||
let apktool_hiddenapi = apktool_method.get("hiddenapi").unwrap();
|
|
||||||
let apktool_hiddenapi_domain = apktool_method.get("hiddenapi_domain").unwrap();
|
|
||||||
compare_hidden_api(
|
|
||||||
apktool_hiddenapi,
|
|
||||||
&apktool_hiddenapi_domain,
|
|
||||||
&method.hiddenapi,
|
|
||||||
&m_dsc.try_to_smali().unwrap(),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_is_platform_class() {
|
|
||||||
let activity = Class::new("Landroid/app/Activity;".into()).unwrap();
|
|
||||||
assert!(activity.is_platform_class());
|
|
||||||
let not_platform = Class::new("Landroid/app/NotAPlatformClass;".into()).unwrap();
|
|
||||||
assert!(!not_platform.is_platform_class());
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -1,194 +0,0 @@
|
||||||
{
|
|
||||||
"descriptor": "Lcom/example/testclassloader/TestB;",
|
|
||||||
"is_public": true,
|
|
||||||
"is_final": false,
|
|
||||||
"is_interface": false,
|
|
||||||
"is_abstract": false,
|
|
||||||
"is_synthetic": false,
|
|
||||||
"is_annotation": false,
|
|
||||||
"is_enum": false,
|
|
||||||
"superclass": "Ljava/lang/Object;",
|
|
||||||
"interfaces": [],
|
|
||||||
"source_file": {
|
|
||||||
"String": "TestB.java"
|
|
||||||
},
|
|
||||||
"static_fields": {},
|
|
||||||
"instance_fields": {},
|
|
||||||
"direct_methods": {
|
|
||||||
"Lcom/example/testclassloader/TestB;-><init>()V": {
|
|
||||||
"descriptor": "Lcom/example/testclassloader/TestB;-><init>()V",
|
|
||||||
"visibility": "Public",
|
|
||||||
"is_static": false,
|
|
||||||
"is_final": false,
|
|
||||||
"is_synchronized": false,
|
|
||||||
"is_bridge": false,
|
|
||||||
"is_varargs": false,
|
|
||||||
"is_native": false,
|
|
||||||
"is_abstract": false,
|
|
||||||
"is_strictfp": false,
|
|
||||||
"is_synthetic": false,
|
|
||||||
"is_constructor": true,
|
|
||||||
"is_declared_syncrhonized": false,
|
|
||||||
"annotations": [],
|
|
||||||
"parameters_annotations": [],
|
|
||||||
"code": {
|
|
||||||
"registers_size": 1,
|
|
||||||
"ins_size": 1,
|
|
||||||
"outs_size": 1,
|
|
||||||
"debug_info": [
|
|
||||||
3,
|
|
||||||
[
|
|
||||||
14,
|
|
||||||
0
|
|
||||||
]
|
|
||||||
],
|
|
||||||
"parameter_names": [],
|
|
||||||
"insns": [
|
|
||||||
{
|
|
||||||
"Label": {
|
|
||||||
"name": "label_00000000"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"InvokeDirect": {
|
|
||||||
"method": "Ljava/lang/Object;-><init>()V",
|
|
||||||
"args": [
|
|
||||||
0
|
|
||||||
]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"Label": {
|
|
||||||
"name": "label_00000003"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"ReturnVoid": {}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"virtual_methods": {
|
|
||||||
"Lcom/example/testclassloader/TestB;->val()Ljava/lang/String;": {
|
|
||||||
"descriptor": "Lcom/example/testclassloader/TestB;->val()Ljava/lang/String;",
|
|
||||||
"visibility": "Public",
|
|
||||||
"is_static": false,
|
|
||||||
"is_final": false,
|
|
||||||
"is_synchronized": false,
|
|
||||||
"is_bridge": false,
|
|
||||||
"is_varargs": false,
|
|
||||||
"is_native": false,
|
|
||||||
"is_abstract": false,
|
|
||||||
"is_strictfp": false,
|
|
||||||
"is_synthetic": false,
|
|
||||||
"is_constructor": false,
|
|
||||||
"is_declared_syncrhonized": false,
|
|
||||||
"annotations": [],
|
|
||||||
"parameters_annotations": [],
|
|
||||||
"code": {
|
|
||||||
"registers_size": 2,
|
|
||||||
"ins_size": 1,
|
|
||||||
"outs_size": 1,
|
|
||||||
"debug_info": [
|
|
||||||
6,
|
|
||||||
[
|
|
||||||
14,
|
|
||||||
0
|
|
||||||
]
|
|
||||||
],
|
|
||||||
"parameter_names": [],
|
|
||||||
"insns": [
|
|
||||||
{
|
|
||||||
"Label": {
|
|
||||||
"name": "label_00000000"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"InvokeVirtual": {
|
|
||||||
"method": "Ljava/lang/Object;->getClass()Ljava/lang/Class;",
|
|
||||||
"args": [
|
|
||||||
1
|
|
||||||
]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"Label": {
|
|
||||||
"name": "label_00000003"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"MoveResultObject": {
|
|
||||||
"to": 0
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"Label": {
|
|
||||||
"name": "label_00000004"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"InvokeVirtual": {
|
|
||||||
"method": "Ljava/lang/Class;->getClassLoader()Ljava/lang/ClassLoader;",
|
|
||||||
"args": [
|
|
||||||
0
|
|
||||||
]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"Label": {
|
|
||||||
"name": "label_00000007"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"MoveResultObject": {
|
|
||||||
"to": 0
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"Label": {
|
|
||||||
"name": "label_00000008"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"InvokeVirtual": {
|
|
||||||
"method": "Ljava/lang/Object;->toString()Ljava/lang/String;",
|
|
||||||
"args": [
|
|
||||||
0
|
|
||||||
]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"Label": {
|
|
||||||
"name": "label_0000000B"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"MoveResultObject": {
|
|
||||||
"to": 0
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"Label": {
|
|
||||||
"name": "label_0000000C"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"ReturnObject": {
|
|
||||||
"reg": 0
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"Label": {
|
|
||||||
"name": "label_0000000D"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"Nop": {}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"annotations": []
|
|
||||||
}
|
|
||||||
|
|
@ -1,34 +0,0 @@
|
||||||
use std::io::{Read, Seek, SeekFrom};
|
|
||||||
|
|
||||||
use crate::Result;
|
|
||||||
use androscalpel_serializer::{HeaderItem, Serializable};
|
|
||||||
use apk_frauder::{ZipFileReader, end_of_central_directory::EndCentralDirectory};
|
|
||||||
|
|
||||||
/// Test if a file is as .dex file an return the dex version if it is, else return None.
|
|
||||||
pub fn is_dex(file: &mut (impl Read + Seek)) -> Result<Option<usize>> {
|
|
||||||
let pos = file.stream_position()?;
|
|
||||||
let r = HeaderItem::deserialize(file)
|
|
||||||
.ok()
|
|
||||||
.and_then(|header| String::from_utf8(header.magic.version.to_vec()).ok())
|
|
||||||
.and_then(|version| version.parse::<usize>().ok());
|
|
||||||
file.seek(SeekFrom::Start(pos))?;
|
|
||||||
Ok(r)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Test if a file is a zip file.
|
|
||||||
pub fn is_zip(mut file: impl Read + Seek) -> Result<bool> {
|
|
||||||
let pos = file.stream_position()?;
|
|
||||||
let ecd_off = match ZipFileReader::get_end_of_central_directory_offset(&mut file) {
|
|
||||||
Some(off) => off,
|
|
||||||
_ => {
|
|
||||||
return Ok(false);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
file.seek(SeekFrom::Start(ecd_off))?;
|
|
||||||
let r = match apk_frauder::Signature::deserialize(&mut file) {
|
|
||||||
Ok(sig) => EndCentralDirectory::SIGNATURE == sig,
|
|
||||||
_ => false,
|
|
||||||
};
|
|
||||||
file.seek(SeekFrom::Start(pos))?;
|
|
||||||
Ok(r)
|
|
||||||
}
|
|
||||||
|
|
@ -3,13 +3,10 @@
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use std::collections::HashSet;
|
use std::collections::HashSet;
|
||||||
|
|
||||||
#[cfg(feature = "python")]
|
use pyo3::exceptions::PyTypeError;
|
||||||
use pyo3::{exceptions::PyTypeError, prelude::*};
|
use pyo3::prelude::*;
|
||||||
|
|
||||||
use crate::{
|
use crate::{dex_id::*, scalar::*, DexAnnotation, DexString, MethodHandle};
|
||||||
DexAnnotation, DexString, MethodHandle, Result, Visitable, VisitableMut, Visitor, VisitorMut,
|
|
||||||
dex_id::*, scalar::*,
|
|
||||||
};
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)]
|
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)]
|
||||||
pub enum DexValue {
|
pub enum DexValue {
|
||||||
|
|
@ -33,94 +30,43 @@ pub enum DexValue {
|
||||||
Boolean(DexBoolean),
|
Boolean(DexBoolean),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<V: Visitor> Visitable<V> for DexValue {
|
|
||||||
fn default_visit(&self, v: &mut V) -> Result<()> {
|
|
||||||
match self {
|
|
||||||
Self::Byte(val) => v.visit_byte(val),
|
|
||||||
Self::Short(val) => v.visit_short(val),
|
|
||||||
Self::Char(val) => v.visit_char(val),
|
|
||||||
Self::Int(val) => v.visit_int(val),
|
|
||||||
Self::Long(val) => v.visit_long(val),
|
|
||||||
Self::Float(val) => v.visit_float(val),
|
|
||||||
Self::Double(val) => v.visit_double(val),
|
|
||||||
Self::MethodType(val) => v.visit_method_type(val),
|
|
||||||
Self::MethodHandle(val) => v.visit_method_handle(val),
|
|
||||||
Self::String(val) => v.visit_string(val),
|
|
||||||
Self::Type(val) => v.visit_type(val),
|
|
||||||
Self::Field(val) => v.visit_field_id(val),
|
|
||||||
Self::Method(val) => v.visit_method_id(val),
|
|
||||||
Self::Enum(val) => v.visit_enum_id(val),
|
|
||||||
Self::Array(val) => v.visit_array(val),
|
|
||||||
Self::Annotation(val) => v.visit_annotation(val),
|
|
||||||
Self::Null(val) => v.visit_null(val),
|
|
||||||
Self::Boolean(val) => v.visit_bool(val),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<V: VisitorMut> VisitableMut<V> for DexValue {
|
|
||||||
fn default_visit_mut(self, v: &mut V) -> Result<Self> {
|
|
||||||
match self {
|
|
||||||
Self::Byte(val) => Ok(Self::Byte(v.visit_byte(val)?)),
|
|
||||||
Self::Short(val) => Ok(Self::Short(v.visit_short(val)?)),
|
|
||||||
Self::Char(val) => Ok(Self::Char(v.visit_char(val)?)),
|
|
||||||
Self::Int(val) => Ok(Self::Int(v.visit_int(val)?)),
|
|
||||||
Self::Long(val) => Ok(Self::Long(v.visit_long(val)?)),
|
|
||||||
Self::Float(val) => Ok(Self::Float(v.visit_float(val)?)),
|
|
||||||
Self::Double(val) => Ok(Self::Double(v.visit_double(val)?)),
|
|
||||||
Self::MethodType(val) => Ok(Self::MethodType(v.visit_method_type(val)?)),
|
|
||||||
Self::MethodHandle(val) => Ok(Self::MethodHandle(v.visit_method_handle(val)?)),
|
|
||||||
Self::String(val) => Ok(Self::String(v.visit_string(val)?)),
|
|
||||||
Self::Type(val) => Ok(Self::Type(v.visit_type(val)?)),
|
|
||||||
Self::Field(val) => Ok(Self::Field(v.visit_field_id(val)?)),
|
|
||||||
Self::Method(val) => Ok(Self::Method(v.visit_method_id(val)?)),
|
|
||||||
Self::Enum(val) => Ok(Self::Enum(v.visit_enum_id(val)?)),
|
|
||||||
Self::Array(val) => Ok(Self::Array(v.visit_array(val)?)),
|
|
||||||
Self::Annotation(val) => Ok(Self::Annotation(v.visit_annotation(val)?)),
|
|
||||||
Self::Null(val) => Ok(Self::Null(v.visit_null(val)?)),
|
|
||||||
Self::Boolean(val) => Ok(Self::Boolean(v.visit_bool(val)?)),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(feature = "python")]
|
|
||||||
impl<'source> FromPyObject<'source> for DexValue {
|
impl<'source> FromPyObject<'source> for DexValue {
|
||||||
fn extract_bound(ob: &Bound<'source, PyAny>) -> PyResult<Self> {
|
fn extract(ob: &'source PyAny) -> PyResult<Self> {
|
||||||
if let Ok(val) = DexByte::extract_bound(ob) {
|
if let Ok(val) = DexByte::extract(ob) {
|
||||||
Ok(Self::Byte(val))
|
Ok(Self::Byte(val))
|
||||||
} else if let Ok(val) = DexShort::extract_bound(ob) {
|
} else if let Ok(val) = DexShort::extract(ob) {
|
||||||
Ok(Self::Short(val))
|
Ok(Self::Short(val))
|
||||||
} else if let Ok(val) = DexChar::extract_bound(ob) {
|
} else if let Ok(val) = DexChar::extract(ob) {
|
||||||
Ok(Self::Char(val))
|
Ok(Self::Char(val))
|
||||||
} else if let Ok(val) = DexInt::extract_bound(ob) {
|
} else if let Ok(val) = DexInt::extract(ob) {
|
||||||
Ok(Self::Int(val))
|
Ok(Self::Int(val))
|
||||||
} else if let Ok(val) = DexLong::extract_bound(ob) {
|
} else if let Ok(val) = DexLong::extract(ob) {
|
||||||
Ok(Self::Long(val))
|
Ok(Self::Long(val))
|
||||||
} else if let Ok(val) = DexFloat::extract_bound(ob) {
|
} else if let Ok(val) = DexFloat::extract(ob) {
|
||||||
Ok(Self::Float(val))
|
Ok(Self::Float(val))
|
||||||
} else if let Ok(val) = DexDouble::extract_bound(ob) {
|
} else if let Ok(val) = DexDouble::extract(ob) {
|
||||||
Ok(Self::Double(val))
|
Ok(Self::Double(val))
|
||||||
} else if let Ok(val) = DexString::extract_bound(ob) {
|
} else if let Ok(val) = DexString::extract(ob) {
|
||||||
Ok(Self::String(val))
|
Ok(Self::String(val))
|
||||||
} else if let Ok(val) = IdType::extract_bound(ob) {
|
} else if let Ok(val) = IdType::extract(ob) {
|
||||||
Ok(Self::Type(val))
|
Ok(Self::Type(val))
|
||||||
} else if let Ok(val) = IdField::extract_bound(ob) {
|
} else if let Ok(val) = IdField::extract(ob) {
|
||||||
Ok(Self::Field(val))
|
Ok(Self::Field(val))
|
||||||
} else if let Ok(val) = IdMethod::extract_bound(ob) {
|
} else if let Ok(val) = IdMethod::extract(ob) {
|
||||||
Ok(Self::Method(val))
|
Ok(Self::Method(val))
|
||||||
} else if let Ok(val) = IdEnum::extract_bound(ob) {
|
} else if let Ok(val) = IdEnum::extract(ob) {
|
||||||
Ok(Self::Enum(val))
|
Ok(Self::Enum(val))
|
||||||
} else if let Ok(val) = DexArray::extract_bound(ob) {
|
} else if let Ok(val) = DexArray::extract(ob) {
|
||||||
Ok(Self::Array(val))
|
Ok(Self::Array(val))
|
||||||
} else if let Ok(val) = DexAnnotation::extract_bound(ob) {
|
} else if let Ok(val) = DexAnnotation::extract(ob) {
|
||||||
Ok(Self::Annotation(val))
|
Ok(Self::Annotation(val))
|
||||||
} else if let Ok(val) = DexNull::extract_bound(ob) {
|
} else if let Ok(val) = DexNull::extract(ob) {
|
||||||
Ok(Self::Null(val))
|
Ok(Self::Null(val))
|
||||||
} else if let Ok(val) = DexBoolean::extract_bound(ob) {
|
} else if let Ok(val) = DexBoolean::extract(ob) {
|
||||||
Ok(Self::Boolean(val))
|
Ok(Self::Boolean(val))
|
||||||
} else if let Ok(val) = IdMethodType::extract_bound(ob) {
|
} else if let Ok(val) = IdMethodType::extract(ob) {
|
||||||
Ok(Self::MethodType(val))
|
Ok(Self::MethodType(val))
|
||||||
} else if let Ok(val) = MethodHandle::extract_bound(ob) {
|
} else if let Ok(val) = MethodHandle::extract(ob) {
|
||||||
Ok(Self::MethodHandle(val))
|
Ok(Self::MethodHandle(val))
|
||||||
} else {
|
} else {
|
||||||
Err(PyErr::new::<PyTypeError, _>(format!(
|
Err(PyErr::new::<PyTypeError, _>(format!(
|
||||||
|
|
@ -306,32 +252,28 @@ impl DexValue {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#[cfg(feature = "python")]
|
|
||||||
impl<'py> IntoPyObject<'py> for DexValue {
|
|
||||||
type Target = PyAny;
|
|
||||||
type Output = Bound<'py, Self::Target>;
|
|
||||||
type Error = PyErr;
|
|
||||||
|
|
||||||
fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
|
impl IntoPy<PyObject> for DexValue {
|
||||||
|
fn into_py(self, py: Python<'_>) -> PyObject {
|
||||||
match self {
|
match self {
|
||||||
DexValue::Byte(val) => Bound::new(py, val).map(Bound::<_>::into_any),
|
DexValue::Byte(val) => val.into_py(py),
|
||||||
DexValue::Short(val) => Bound::new(py, val).map(Bound::<_>::into_any),
|
DexValue::Short(val) => val.into_py(py),
|
||||||
DexValue::Char(val) => Bound::new(py, val).map(Bound::<_>::into_any),
|
DexValue::Char(val) => val.into_py(py),
|
||||||
DexValue::Int(val) => Bound::new(py, val).map(Bound::<_>::into_any),
|
DexValue::Int(val) => val.into_py(py),
|
||||||
DexValue::Long(val) => Bound::new(py, val).map(Bound::<_>::into_any),
|
DexValue::Long(val) => val.into_py(py),
|
||||||
DexValue::Float(val) => Bound::new(py, val).map(Bound::<_>::into_any),
|
DexValue::Float(val) => val.into_py(py),
|
||||||
DexValue::Double(val) => Bound::new(py, val).map(Bound::<_>::into_any),
|
DexValue::Double(val) => val.into_py(py),
|
||||||
DexValue::MethodType(val) => Bound::new(py, val).map(Bound::<_>::into_any),
|
DexValue::MethodType(val) => val.into_py(py),
|
||||||
DexValue::MethodHandle(val) => Bound::new(py, val).map(Bound::<_>::into_any),
|
DexValue::MethodHandle(val) => val.into_py(py),
|
||||||
DexValue::String(val) => Bound::new(py, val).map(Bound::<_>::into_any),
|
DexValue::String(val) => val.into_py(py),
|
||||||
DexValue::Type(val) => Bound::new(py, val).map(Bound::<_>::into_any),
|
DexValue::Type(val) => val.into_py(py),
|
||||||
DexValue::Field(val) => Bound::new(py, val).map(Bound::<_>::into_any),
|
DexValue::Field(val) => val.into_py(py),
|
||||||
DexValue::Method(val) => Bound::new(py, val).map(Bound::<_>::into_any),
|
DexValue::Method(val) => val.into_py(py),
|
||||||
DexValue::Enum(val) => Bound::new(py, val).map(Bound::<_>::into_any),
|
DexValue::Enum(val) => val.into_py(py),
|
||||||
DexValue::Array(val) => Bound::new(py, val).map(Bound::<_>::into_any),
|
DexValue::Array(val) => val.into_py(py),
|
||||||
DexValue::Annotation(val) => Bound::new(py, val).map(Bound::<_>::into_any),
|
DexValue::Annotation(val) => val.into_py(py),
|
||||||
DexValue::Null(val) => Bound::new(py, val).map(Bound::<_>::into_any),
|
DexValue::Null(val) => val.into_py(py),
|
||||||
DexValue::Boolean(val) => Bound::new(py, val).map(Bound::<_>::into_any),
|
DexValue::Boolean(val) => val.into_py(py),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,419 +0,0 @@
|
||||||
//! The visitor trait and common implementations.
|
|
||||||
|
|
||||||
use crate::{
|
|
||||||
ins::Instruction, scalar::*, Apk, CallSite, Class, Code, DexAnnotation, DexAnnotationItem,
|
|
||||||
DexFile, DexString, DexValue, Field, FieldVisibility, HiddenApiData, HiddenApiDomain,
|
|
||||||
HiddenApiPermission, IdEnum, IdField, IdMethod, IdMethodType, IdType, Method, MethodHandle,
|
|
||||||
MethodVisibility, Result,
|
|
||||||
};
|
|
||||||
use rayon::prelude::*;
|
|
||||||
use std::collections::HashSet;
|
|
||||||
|
|
||||||
pub trait Visitor: Sized {
|
|
||||||
fn visit_instruction(&mut self, ins: &Instruction) -> Result<()> {
|
|
||||||
ins.default_visit(self)
|
|
||||||
}
|
|
||||||
fn visit_string(&mut self, string: &DexString) -> Result<()> {
|
|
||||||
string.default_visit(self)
|
|
||||||
}
|
|
||||||
fn visit_type(&mut self, ty: &IdType) -> Result<()> {
|
|
||||||
ty.default_visit(self)
|
|
||||||
}
|
|
||||||
fn visit_method_type(&mut self, mty: &IdMethodType) -> Result<()> {
|
|
||||||
mty.default_visit(self)
|
|
||||||
}
|
|
||||||
fn visit_field_id(&mut self, id: &IdField) -> Result<()> {
|
|
||||||
id.default_visit(self)
|
|
||||||
}
|
|
||||||
fn visit_method_id(&mut self, id: &IdMethod) -> Result<()> {
|
|
||||||
id.default_visit(self)
|
|
||||||
}
|
|
||||||
fn visit_enum_id(&mut self, id: &IdEnum) -> Result<()> {
|
|
||||||
id.default_visit(self)
|
|
||||||
}
|
|
||||||
fn visit_method_handle(&mut self, handle: &MethodHandle) -> Result<()> {
|
|
||||||
handle.default_visit(self)
|
|
||||||
}
|
|
||||||
fn visit_call_site(&mut self, site: &CallSite) -> Result<()> {
|
|
||||||
site.default_visit(self)
|
|
||||||
}
|
|
||||||
fn visit_value(&mut self, val: &DexValue) -> Result<()> {
|
|
||||||
val.default_visit(self)
|
|
||||||
}
|
|
||||||
fn visit_byte(&mut self, _: &DexByte) -> Result<()> {
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
fn visit_short(&mut self, _: &DexShort) -> Result<()> {
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
fn visit_char(&mut self, _: &DexChar) -> Result<()> {
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
fn visit_int(&mut self, _: &DexInt) -> Result<()> {
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
fn visit_long(&mut self, _: &DexLong) -> Result<()> {
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
fn visit_float(&mut self, _: &DexFloat) -> Result<()> {
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
fn visit_double(&mut self, _: &DexDouble) -> Result<()> {
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
fn visit_array(&mut self, val: &DexArray) -> Result<()> {
|
|
||||||
val.default_visit(self)
|
|
||||||
}
|
|
||||||
fn visit_annotation(&mut self, val: &DexAnnotation) -> Result<()> {
|
|
||||||
val.default_visit(self)
|
|
||||||
}
|
|
||||||
fn visit_null(&mut self, _: &DexNull) -> Result<()> {
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
fn visit_bool(&mut self, _: &DexBoolean) -> Result<()> {
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
fn visit_class(&mut self, class: &Class) -> Result<()> {
|
|
||||||
class.default_visit(self)
|
|
||||||
}
|
|
||||||
fn visit_field(&mut self, field: &Field) -> Result<()> {
|
|
||||||
field.default_visit(self)
|
|
||||||
}
|
|
||||||
fn visit_method(&mut self, method: &Method) -> Result<()> {
|
|
||||||
method.default_visit(self)
|
|
||||||
}
|
|
||||||
fn visit_annotation_item(&mut self, annotation: &DexAnnotationItem) -> Result<()> {
|
|
||||||
annotation.default_visit(self)
|
|
||||||
}
|
|
||||||
fn visit_field_visibility(&mut self, _: &FieldVisibility) -> Result<()> {
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
fn visit_method_visibility(&mut self, _: &MethodVisibility) -> Result<()> {
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
fn visit_hidden_api_data(&mut self, hidden_api: &HiddenApiData) -> Result<()> {
|
|
||||||
hidden_api.default_visit(self)
|
|
||||||
}
|
|
||||||
fn visit_hidden_api_permission(&mut self, _: &HiddenApiPermission) -> Result<()> {
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
fn visit_hidden_api_domain(&mut self, _: &HiddenApiDomain) -> Result<()> {
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
fn visit_code(&mut self, code: &Code) -> Result<()> {
|
|
||||||
code.default_visit(self)
|
|
||||||
}
|
|
||||||
fn visit_dex_file(&mut self, dex_file: &DexFile) -> Result<()> {
|
|
||||||
dex_file.default_visit(self)
|
|
||||||
}
|
|
||||||
fn visit_apk(&mut self, apk: &Apk) -> Result<()> {
|
|
||||||
apk.default_visit(self)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub trait VisitorMut: Sized {
|
|
||||||
fn visit_instruction(&mut self, ins: Instruction) -> Result<Instruction> {
|
|
||||||
ins.default_visit_mut(self)
|
|
||||||
}
|
|
||||||
fn visit_string(&mut self, string: DexString) -> Result<DexString> {
|
|
||||||
string.default_visit_mut(self)
|
|
||||||
}
|
|
||||||
fn visit_type(&mut self, ty: IdType) -> Result<IdType> {
|
|
||||||
ty.default_visit_mut(self)
|
|
||||||
}
|
|
||||||
fn visit_method_type(&mut self, mty: IdMethodType) -> Result<IdMethodType> {
|
|
||||||
mty.default_visit_mut(self)
|
|
||||||
}
|
|
||||||
fn visit_field_id(&mut self, id: IdField) -> Result<IdField> {
|
|
||||||
id.default_visit_mut(self)
|
|
||||||
}
|
|
||||||
fn visit_method_id(&mut self, id: IdMethod) -> Result<IdMethod> {
|
|
||||||
id.default_visit_mut(self)
|
|
||||||
}
|
|
||||||
fn visit_enum_id(&mut self, id: IdEnum) -> Result<IdEnum> {
|
|
||||||
id.default_visit_mut(self)
|
|
||||||
}
|
|
||||||
fn visit_method_handle(&mut self, handle: MethodHandle) -> Result<MethodHandle> {
|
|
||||||
handle.default_visit_mut(self)
|
|
||||||
}
|
|
||||||
fn visit_call_site(&mut self, site: CallSite) -> Result<CallSite> {
|
|
||||||
site.default_visit_mut(self)
|
|
||||||
}
|
|
||||||
fn visit_value(&mut self, val: DexValue) -> Result<DexValue> {
|
|
||||||
val.default_visit_mut(self)
|
|
||||||
}
|
|
||||||
fn visit_byte(&mut self, val: DexByte) -> Result<DexByte> {
|
|
||||||
Ok(val)
|
|
||||||
}
|
|
||||||
fn visit_short(&mut self, val: DexShort) -> Result<DexShort> {
|
|
||||||
Ok(val)
|
|
||||||
}
|
|
||||||
fn visit_char(&mut self, val: DexChar) -> Result<DexChar> {
|
|
||||||
Ok(val)
|
|
||||||
}
|
|
||||||
fn visit_int(&mut self, val: DexInt) -> Result<DexInt> {
|
|
||||||
Ok(val)
|
|
||||||
}
|
|
||||||
fn visit_long(&mut self, val: DexLong) -> Result<DexLong> {
|
|
||||||
Ok(val)
|
|
||||||
}
|
|
||||||
fn visit_float(&mut self, val: DexFloat) -> Result<DexFloat> {
|
|
||||||
Ok(val)
|
|
||||||
}
|
|
||||||
fn visit_double(&mut self, val: DexDouble) -> Result<DexDouble> {
|
|
||||||
Ok(val)
|
|
||||||
}
|
|
||||||
fn visit_array(&mut self, val: DexArray) -> Result<DexArray> {
|
|
||||||
val.default_visit_mut(self)
|
|
||||||
}
|
|
||||||
fn visit_annotation(&mut self, val: DexAnnotation) -> Result<DexAnnotation> {
|
|
||||||
val.default_visit_mut(self)
|
|
||||||
}
|
|
||||||
fn visit_null(&mut self, val: DexNull) -> Result<DexNull> {
|
|
||||||
Ok(val)
|
|
||||||
}
|
|
||||||
fn visit_bool(&mut self, val: DexBoolean) -> Result<DexBoolean> {
|
|
||||||
Ok(val)
|
|
||||||
}
|
|
||||||
fn visit_class(&mut self, class: Class) -> Result<Class> {
|
|
||||||
class.default_visit_mut(self)
|
|
||||||
}
|
|
||||||
fn visit_field(&mut self, field: Field) -> Result<Field> {
|
|
||||||
field.default_visit_mut(self)
|
|
||||||
}
|
|
||||||
fn visit_method(&mut self, method: Method) -> Result<Method> {
|
|
||||||
method.default_visit_mut(self)
|
|
||||||
}
|
|
||||||
fn visit_annotation_item(
|
|
||||||
&mut self,
|
|
||||||
annotation: DexAnnotationItem,
|
|
||||||
) -> Result<DexAnnotationItem> {
|
|
||||||
annotation.default_visit_mut(self)
|
|
||||||
}
|
|
||||||
fn visit_field_visibility(&mut self, visibility: FieldVisibility) -> Result<FieldVisibility> {
|
|
||||||
Ok(visibility)
|
|
||||||
}
|
|
||||||
fn visit_method_visibility(
|
|
||||||
&mut self,
|
|
||||||
visibility: MethodVisibility,
|
|
||||||
) -> Result<MethodVisibility> {
|
|
||||||
Ok(visibility)
|
|
||||||
}
|
|
||||||
fn visit_hidden_api_data(&mut self, hidden_api: HiddenApiData) -> Result<HiddenApiData> {
|
|
||||||
hidden_api.default_visit_mut(self)
|
|
||||||
}
|
|
||||||
fn visit_hidden_api_permission(
|
|
||||||
&mut self,
|
|
||||||
perm: HiddenApiPermission,
|
|
||||||
) -> Result<HiddenApiPermission> {
|
|
||||||
Ok(perm)
|
|
||||||
}
|
|
||||||
fn visit_hidden_api_domain(&mut self, domain: HiddenApiDomain) -> Result<HiddenApiDomain> {
|
|
||||||
Ok(domain)
|
|
||||||
}
|
|
||||||
fn visit_code(&mut self, code: Code) -> Result<Code> {
|
|
||||||
code.default_visit_mut(self)
|
|
||||||
}
|
|
||||||
fn visit_dex_file(&mut self, dex_file: DexFile) -> Result<DexFile> {
|
|
||||||
dex_file.default_visit_mut(self)
|
|
||||||
}
|
|
||||||
fn visit_apk(&mut self, apk: Apk) -> Result<Apk> {
|
|
||||||
apk.default_visit_mut(self)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Trait for structures that can be visited.
|
|
||||||
/// In theorie, this is not required, as the [`Visitor`] trait implement
|
|
||||||
/// all tree traversal, however if the visitor do not do a complex
|
|
||||||
/// operation it can be usefull to have a default tree traversal accessible
|
|
||||||
/// to re-implemented trait methods.
|
|
||||||
pub trait Visitable<V: Visitor> {
|
|
||||||
fn default_visit(&self, visitor: &mut V) -> Result<()>;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Trait for structures that can be modified by a visitor.
|
|
||||||
/// In theorie, this is not required, as the [`VisitorMut`] trait implement
|
|
||||||
/// all tree traversal, however if the visitor do not do a complex
|
|
||||||
/// operation it can be usefull to have a default tree traversal accessible
|
|
||||||
/// to re-implemented trait methods.
|
|
||||||
pub trait VisitableMut<V: VisitorMut> {
|
|
||||||
fn default_visit_mut(self, visitor: &mut V) -> Result<Self>
|
|
||||||
where
|
|
||||||
Self: Sized;
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Default)]
|
|
||||||
pub struct StringCollector {
|
|
||||||
pub strings: HashSet<DexString>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl StringCollector {
|
|
||||||
pub fn result(self) -> HashSet<DexString> {
|
|
||||||
self.strings
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Visitor for StringCollector {
|
|
||||||
fn visit_string(&mut self, string: &DexString) -> Result<()> {
|
|
||||||
self.strings.insert(string.clone());
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Default)]
|
|
||||||
pub struct TypeCollector {
|
|
||||||
pub types: HashSet<IdType>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl TypeCollector {
|
|
||||||
pub fn result(self) -> HashSet<IdType> {
|
|
||||||
self.types
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Visitor for TypeCollector {
|
|
||||||
fn visit_type(&mut self, ty: &IdType) -> Result<()> {
|
|
||||||
self.types.insert(ty.clone());
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn visit_dex_file(&mut self, dex: &DexFile) -> Result<()> {
|
|
||||||
let results: Vec<_> = dex
|
|
||||||
.classes
|
|
||||||
.par_iter()
|
|
||||||
.map(|(ty, cls)| {
|
|
||||||
let mut v = Self::default();
|
|
||||||
v.visit_type(ty)
|
|
||||||
.map(|_| v.visit_class(cls))
|
|
||||||
.map(|_| v.result())
|
|
||||||
})
|
|
||||||
.collect::<Result<_, _>>()?;
|
|
||||||
for r in results.into_iter() {
|
|
||||||
self.types.extend(r);
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Default)]
|
|
||||||
pub struct MethodTypeCollector {
|
|
||||||
pub method_types: HashSet<IdMethodType>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl MethodTypeCollector {
|
|
||||||
pub fn result(self) -> HashSet<IdMethodType> {
|
|
||||||
self.method_types
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Visitor for MethodTypeCollector {
|
|
||||||
fn visit_method_type(&mut self, mty: &IdMethodType) -> Result<()> {
|
|
||||||
self.method_types.insert(mty.clone());
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn visit_dex_file(&mut self, dex: &DexFile) -> Result<()> {
|
|
||||||
let results: Vec<_> = dex
|
|
||||||
.classes
|
|
||||||
.par_iter()
|
|
||||||
.map(|(ty, cls)| {
|
|
||||||
let mut v = Self::default();
|
|
||||||
v.visit_type(ty)
|
|
||||||
.map(|_| v.visit_class(cls))
|
|
||||||
.map(|_| v.result())
|
|
||||||
})
|
|
||||||
.collect::<Result<_, _>>()?;
|
|
||||||
for r in results.into_iter() {
|
|
||||||
self.method_types.extend(r);
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Default)]
|
|
||||||
pub struct FieldIdCollector {
|
|
||||||
pub field_ids: HashSet<IdField>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl FieldIdCollector {
|
|
||||||
pub fn result(self) -> HashSet<IdField> {
|
|
||||||
self.field_ids
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Visitor for FieldIdCollector {
|
|
||||||
fn visit_field_id(&mut self, id: &IdField) -> Result<()> {
|
|
||||||
self.field_ids.insert(id.clone());
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn visit_dex_file(&mut self, dex: &DexFile) -> Result<()> {
|
|
||||||
let results: Vec<_> = dex
|
|
||||||
.classes
|
|
||||||
.par_iter()
|
|
||||||
.map(|(ty, cls)| {
|
|
||||||
let mut v = Self::default();
|
|
||||||
v.visit_type(ty)
|
|
||||||
.map(|_| v.visit_class(cls))
|
|
||||||
.map(|_| v.result())
|
|
||||||
})
|
|
||||||
.collect::<Result<_, _>>()?;
|
|
||||||
for r in results.into_iter() {
|
|
||||||
self.field_ids.extend(r);
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Default)]
|
|
||||||
pub struct MethodIdCollector {
|
|
||||||
pub method_ids: HashSet<IdMethod>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl MethodIdCollector {
|
|
||||||
pub fn result(self) -> HashSet<IdMethod> {
|
|
||||||
self.method_ids
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Visitor for MethodIdCollector {
|
|
||||||
fn visit_method_id(&mut self, id: &IdMethod) -> Result<()> {
|
|
||||||
self.method_ids.insert(id.clone());
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn visit_dex_file(&mut self, dex: &DexFile) -> Result<()> {
|
|
||||||
let results: Vec<_> = dex
|
|
||||||
.classes
|
|
||||||
.par_iter()
|
|
||||||
.map(|(ty, cls)| {
|
|
||||||
let mut v = Self::default();
|
|
||||||
v.visit_type(ty)
|
|
||||||
.map(|_| v.visit_class(cls))
|
|
||||||
.map(|_| v.result())
|
|
||||||
})
|
|
||||||
.collect::<Result<_, _>>()?;
|
|
||||||
for r in results.into_iter() {
|
|
||||||
self.method_ids.extend(r);
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Default)]
|
|
||||||
pub struct MethodHandleCollector {
|
|
||||||
pub handles: HashSet<MethodHandle>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl MethodHandleCollector {
|
|
||||||
pub fn result(self) -> HashSet<MethodHandle> {
|
|
||||||
self.handles
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Visitor for MethodHandleCollector {
|
|
||||||
fn visit_method_handle(&mut self, handle: &MethodHandle) -> Result<()> {
|
|
||||||
self.handles.insert(handle.clone());
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,10 +0,0 @@
|
||||||
[package]
|
|
||||||
name = "androscalpel_platform_api_list"
|
|
||||||
version = "0.1.0"
|
|
||||||
edition = "2024"
|
|
||||||
license = "AGPL-3.0-or-later"
|
|
||||||
|
|
||||||
[dependencies]
|
|
||||||
|
|
||||||
[features]
|
|
||||||
sdk34 = []
|
|
||||||
|
|
@ -1,5 +0,0 @@
|
||||||
# Android Platform API
|
|
||||||
|
|
||||||
List of android platform API (API that are either in the android SDK or hidden API).
|
|
||||||
|
|
||||||
The list is extracted from the default android emulator the respective SDKs.
|
|
||||||
|
|
@ -1,20 +0,0 @@
|
||||||
pub mod sdk34;
|
|
||||||
pub use sdk34::*;
|
|
||||||
|
|
||||||
// TODO: automate addign a new SDK
|
|
||||||
// TODO: different version with different features
|
|
||||||
|
|
||||||
/// Test if the class is part of the platform classes.
|
|
||||||
pub fn is_platform_class(smali: &str) -> bool {
|
|
||||||
is_sdk34_platform_class(smali)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Test if the field is part of the platform classes.
|
|
||||||
pub fn is_platform_field(smali: &str) -> bool {
|
|
||||||
is_sdk34_platform_field(smali)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Test if the method is part of the platform classes.
|
|
||||||
pub fn is_platform_method(smali: &str) -> bool {
|
|
||||||
is_sdk34_platform_method(smali)
|
|
||||||
}
|
|
||||||
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
|
|
@ -1,60 +0,0 @@
|
||||||
use std::collections::HashSet;
|
|
||||||
use std::sync::LazyLock;
|
|
||||||
|
|
||||||
pub static PLATFORM_CLASSES_SDK34: LazyLock<HashSet<&'static str>> =
|
|
||||||
LazyLock::new(|| include_str!("classes.txt").lines().collect());
|
|
||||||
pub static PLATFORM_FIELDS_SDK34: LazyLock<HashSet<&'static str>> =
|
|
||||||
LazyLock::new(|| include_str!("fields.txt").lines().collect());
|
|
||||||
pub static PLATFORM_METHODS_SDK34: LazyLock<HashSet<&'static str>> =
|
|
||||||
LazyLock::new(|| include_str!("methods.txt").lines().collect());
|
|
||||||
|
|
||||||
/// Test if the class is part of the platform classes of the SDK 34.
|
|
||||||
pub fn is_sdk34_platform_class(smali: &str) -> bool {
|
|
||||||
PLATFORM_CLASSES_SDK34.contains(smali)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Test if the field is part of the platform classes of the SDK 34.
|
|
||||||
pub fn is_sdk34_platform_field(smali: &str) -> bool {
|
|
||||||
PLATFORM_FIELDS_SDK34.contains(smali)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Test if the method is part of the platform classes of the SDK 34.
|
|
||||||
pub fn is_sdk34_platform_method(smali: &str) -> bool {
|
|
||||||
PLATFORM_METHODS_SDK34.contains(smali)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod test {
|
|
||||||
use super::*;
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_classes_sdk34_set() {
|
|
||||||
assert!(PLATFORM_CLASSES_SDK34
|
|
||||||
.contains("Landroid/accessibilityservice/AccessibilityGestureEvent;"));
|
|
||||||
let s: String = "Landroid/accessibilityservice/AccessibilityInputMethodSession;".into();
|
|
||||||
assert!(PLATFORM_CLASSES_SDK34.contains(s.as_str()));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_fields_sdk34_set() {
|
|
||||||
assert!(PLATFORM_FIELDS_SDK34.contains(
|
|
||||||
"Landroid/accessibilityservice/AccessibilityButtonController$$\
|
|
||||||
ExternalSyntheticLambda1;->f$0:Landroid/accessibilityservice/\
|
|
||||||
AccessibilityButtonController;"
|
|
||||||
));
|
|
||||||
let s: String = "Landroid/accessibilityservice/AccessibilityButtonController;->mLock:Ljava/lang/Object;".into();
|
|
||||||
assert!(PLATFORM_FIELDS_SDK34.contains(s.as_str()));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_methods_sdk34_set() {
|
|
||||||
assert!(PLATFORM_METHODS_SDK34
|
|
||||||
.contains("Landroid/accessibilityservice/AccessibilityButtonController$$ExternalSyntheticLambda1;->run()V"));
|
|
||||||
let s: String = "Landroid/accessibilityservice/AccessibilityButtonController;->\
|
|
||||||
$r8$lambda$h5Na0_pkg6ffhlKQPqxrTXannSI(\
|
|
||||||
Landroid/accessibilityservice/AccessibilityButtonController;\
|
|
||||||
Landroid/accessibilityservice/AccessibilityButtonController$AccessibilityButtonCallback;\
|
|
||||||
)V".into();
|
|
||||||
assert!(PLATFORM_METHODS_SDK34.contains(s.as_str()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,8 +1,7 @@
|
||||||
[package]
|
[package]
|
||||||
name = "androscalpel_serializer"
|
name = "androscalpel_serializer"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
edition = "2024"
|
edition = "2021"
|
||||||
license = "AGPL-3.0-or-later"
|
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
androscalpel_serializer_derive = { path = "../androscalpel_serializer_derive" }
|
androscalpel_serializer_derive = { path = "../androscalpel_serializer_derive" }
|
||||||
|
|
|
||||||
|
|
@ -19,7 +19,7 @@ pub struct Uleb128p1(pub u32);
|
||||||
|
|
||||||
impl Sleb128 {
|
impl Sleb128 {
|
||||||
fn get_serialized_bytes(&self) -> Vec<u8> {
|
fn get_serialized_bytes(&self) -> Vec<u8> {
|
||||||
let &Self(mut val) = self;
|
let Self(mut val) = self;
|
||||||
let mut bytes = vec![];
|
let mut bytes = vec![];
|
||||||
loop {
|
loop {
|
||||||
let byte = val as u8 & 0b0111_1111;
|
let byte = val as u8 & 0b0111_1111;
|
||||||
|
|
@ -81,7 +81,7 @@ impl Serializable for Sleb128 {
|
||||||
|
|
||||||
impl Uleb128 {
|
impl Uleb128 {
|
||||||
fn get_serialized_bytes(&self) -> Vec<u8> {
|
fn get_serialized_bytes(&self) -> Vec<u8> {
|
||||||
let &Self(mut val) = self;
|
let Self(mut val) = self;
|
||||||
let mut bytes = vec![];
|
let mut bytes = vec![];
|
||||||
loop {
|
loop {
|
||||||
let byte = val as u8 & 0b0111_1111;
|
let byte = val as u8 & 0b0111_1111;
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,6 @@ pub enum Error {
|
||||||
DeserializationError(String),
|
DeserializationError(String),
|
||||||
InvalidStringEncoding(String),
|
InvalidStringEncoding(String),
|
||||||
InconsistantStruct(String),
|
InconsistantStruct(String),
|
||||||
OutOfBound(String),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub type Result<T> = core::result::Result<T, Error>;
|
pub type Result<T> = core::result::Result<T, Error>;
|
||||||
|
|
@ -33,7 +32,6 @@ impl std::fmt::Display for Error {
|
||||||
Self::DeserializationError(msg) => write!(f, "{}", msg),
|
Self::DeserializationError(msg) => write!(f, "{}", msg),
|
||||||
Self::InvalidStringEncoding(msg) => write!(f, "{}", msg),
|
Self::InvalidStringEncoding(msg) => write!(f, "{}", msg),
|
||||||
Self::InconsistantStruct(msg) => write!(f, "{}", msg),
|
Self::InconsistantStruct(msg) => write!(f, "{}", msg),
|
||||||
Self::OutOfBound(msg) => write!(f, "{}", msg),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -390,9 +388,7 @@ mod test {
|
||||||
fn serialize_u64() {
|
fn serialize_u64() {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
0x123456789ABCDEF0u64.serialize_to_vec().unwrap(),
|
0x123456789ABCDEF0u64.serialize_to_vec().unwrap(),
|
||||||
vec![
|
vec![0xF0u8, 0xDEu8, 0xBCu8, 0x9Au8, 0x78u8, 0x56u8, 0x34u8, 0x12u8]
|
||||||
0xF0u8, 0xDEu8, 0xBCu8, 0x9Au8, 0x78u8, 0x56u8, 0x34u8, 0x12u8
|
|
||||||
]
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -143,7 +143,7 @@ impl StringDataItem {
|
||||||
let two = self.data[i] as u32;
|
let two = self.data[i] as u32;
|
||||||
i += 1;
|
i += 1;
|
||||||
if one & 0x20 == 0 {
|
if one & 0x20 == 0 {
|
||||||
utf16_string.push(((one & 0x1f) << 6) | (two & 0x3f));
|
utf16_string.push((one & 0x1f) << 6 | (two & 0x3f));
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -155,7 +155,7 @@ impl StringDataItem {
|
||||||
let three = self.data[i] as u32;
|
let three = self.data[i] as u32;
|
||||||
i += 1;
|
i += 1;
|
||||||
if one & 0x10 == 0 {
|
if one & 0x10 == 0 {
|
||||||
utf16_string.push(((one & 0x0f) << 12) | ((two & 0x3f) << 6) | (three & 0x3f));
|
utf16_string.push((one & 0x0f) << 12 | (two & 0x3f) << 6 | (three & 0x3f));
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -167,7 +167,7 @@ impl StringDataItem {
|
||||||
let four = self.data[i] as u32;
|
let four = self.data[i] as u32;
|
||||||
i += 1;
|
i += 1;
|
||||||
let code_point =
|
let code_point =
|
||||||
((one & 0x0f) << 18) | ((two & 0x3f) << 12) | ((three & 0x3f) << 6) | (four & 0x3f);
|
(one & 0x0f) << 18 | (two & 0x3f) << 12 | (three & 0x3f) << 6 | (four & 0x3f);
|
||||||
let mut pair = ((code_point >> 10) + 0xd7c0) & 0xffff;
|
let mut pair = ((code_point >> 10) + 0xd7c0) & 0xffff;
|
||||||
pair |= ((code_point & 0x03ff) + 0xdc00) << 16;
|
pair |= ((code_point & 0x03ff) + 0xdc00) << 16;
|
||||||
utf16_string.push(pair);
|
utf16_string.push(pair);
|
||||||
|
|
@ -357,34 +357,26 @@ mod test {
|
||||||
/// Test for bug found in <https://github.com/TkTech/mutf8/tree/master>:
|
/// Test for bug found in <https://github.com/TkTech/mutf8/tree/master>:
|
||||||
#[test]
|
#[test]
|
||||||
fn test_tktech_bad_mutf8() {
|
fn test_tktech_bad_mutf8() {
|
||||||
assert!(
|
assert!(TryInto::<String>::try_into(StringDataItem {
|
||||||
TryInto::<String>::try_into(StringDataItem {
|
utf16_size: Uleb128(0),
|
||||||
utf16_size: Uleb128(0),
|
data: vec![0x00]
|
||||||
data: vec![0x00]
|
})
|
||||||
})
|
.is_err());
|
||||||
.is_err()
|
assert!(TryInto::<String>::try_into(StringDataItem {
|
||||||
);
|
utf16_size: Uleb128(0),
|
||||||
assert!(
|
data: vec![0xC2]
|
||||||
TryInto::<String>::try_into(StringDataItem {
|
})
|
||||||
utf16_size: Uleb128(0),
|
.is_err());
|
||||||
data: vec![0xC2]
|
assert!(TryInto::<String>::try_into(StringDataItem {
|
||||||
})
|
utf16_size: Uleb128(0),
|
||||||
.is_err()
|
data: vec![0xED]
|
||||||
);
|
})
|
||||||
assert!(
|
.is_err());
|
||||||
TryInto::<String>::try_into(StringDataItem {
|
assert!(TryInto::<String>::try_into(StringDataItem {
|
||||||
utf16_size: Uleb128(0),
|
utf16_size: Uleb128(0),
|
||||||
data: vec![0xED]
|
data: vec![0xE2]
|
||||||
})
|
})
|
||||||
.is_err()
|
.is_err());
|
||||||
);
|
|
||||||
assert!(
|
|
||||||
TryInto::<String>::try_into(StringDataItem {
|
|
||||||
utf16_size: Uleb128(0),
|
|
||||||
data: vec![0xE2]
|
|
||||||
})
|
|
||||||
.is_err()
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Test from <https://github.com/TkTech/mutf8/tree/master>, test 2 bytes
|
/// Test from <https://github.com/TkTech/mutf8/tree/master>, test 2 bytes
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,7 @@
|
||||||
//! Debug structs
|
//! Debug structs
|
||||||
|
|
||||||
use crate as androscalpel_serializer;
|
use crate as androscalpel_serializer;
|
||||||
use crate::{
|
use crate::{ReadSeek, Result, Serializable, SerializableUntil, Sleb128, Uleb128, Uleb128p1};
|
||||||
Error, NO_INDEX, ReadSeek, Result, Serializable, SerializableUntil, Sleb128, Uleb128, Uleb128p1,
|
|
||||||
};
|
|
||||||
use std::io::Write;
|
use std::io::Write;
|
||||||
|
|
||||||
/// <https://source.android.com/docs/core/runtime/dex-format#debug-info-item>
|
/// <https://source.android.com/docs/core/runtime/dex-format#debug-info-item>
|
||||||
|
|
@ -95,556 +93,6 @@ impl Serializable for DebugInfoItem {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The name and type of the variable in a register.
|
|
||||||
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
|
|
||||||
pub struct DebugRegState {
|
|
||||||
pub type_idx: Option<u32>,
|
|
||||||
pub name_idx: Option<u32>,
|
|
||||||
pub sig_idx: Option<u32>,
|
|
||||||
pub in_scope: bool,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A simplified debug information
|
|
||||||
#[derive(Debug, PartialEq, Eq, Clone)]
|
|
||||||
pub enum DebugInfo {
|
|
||||||
DefLocal {
|
|
||||||
addr: u32,
|
|
||||||
reg: u32,
|
|
||||||
val: DebugRegState,
|
|
||||||
},
|
|
||||||
EndLocal {
|
|
||||||
addr: u32,
|
|
||||||
reg: u32,
|
|
||||||
},
|
|
||||||
PrologueEnd {
|
|
||||||
addr: u32,
|
|
||||||
},
|
|
||||||
EpilogueBegin {
|
|
||||||
addr: u32,
|
|
||||||
},
|
|
||||||
SetSourceFile {
|
|
||||||
addr: u32,
|
|
||||||
source_file_idx: Option<u32>,
|
|
||||||
},
|
|
||||||
SetLineNumber {
|
|
||||||
addr: u32,
|
|
||||||
line_num: u32,
|
|
||||||
},
|
|
||||||
EndOfData,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl DebugInfo {
|
|
||||||
pub fn get_addr(&self) -> u32 {
|
|
||||||
match self {
|
|
||||||
Self::DefLocal { addr, .. } => *addr,
|
|
||||||
Self::EndLocal { addr, .. } => *addr,
|
|
||||||
Self::PrologueEnd { addr } => *addr,
|
|
||||||
Self::EpilogueBegin { addr } => *addr,
|
|
||||||
Self::SetSourceFile { addr, .. } => *addr,
|
|
||||||
Self::SetLineNumber { addr, .. } => *addr,
|
|
||||||
Self::EndOfData => u32::MAX, // TODO should be an Option::None?
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A state machine that interpret a [`DebugInfoItem`].
|
|
||||||
#[derive(Debug, PartialEq, Eq, Clone)]
|
|
||||||
pub struct DebugInfoReader {
|
|
||||||
debug_info: DebugInfoItem,
|
|
||||||
pub pc: usize,
|
|
||||||
pub address: u32,
|
|
||||||
pub line: u32,
|
|
||||||
// Those are registers described in the doc but not necessary in the end
|
|
||||||
//pub source_file_idx: Option<u32>,
|
|
||||||
//pub prologue_end: bool,
|
|
||||||
//pub epilogue_begin: bool,
|
|
||||||
pub register_states: Vec<DebugRegState>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl DebugInfoReader {
|
|
||||||
pub fn new(
|
|
||||||
debug_info: DebugInfoItem,
|
|
||||||
//source_file_idx: Option<u32>,
|
|
||||||
//nb_reg: usize
|
|
||||||
) -> Self {
|
|
||||||
let line = debug_info.line_start.0;
|
|
||||||
Self {
|
|
||||||
debug_info,
|
|
||||||
pc: 0,
|
|
||||||
address: 0,
|
|
||||||
line,
|
|
||||||
//source_file_idx,
|
|
||||||
//prologue_end: false,
|
|
||||||
//epilogue_begin: false,
|
|
||||||
//register_states: vec![
|
|
||||||
// DebugRegState {
|
|
||||||
// name_idx: None,
|
|
||||||
// type_idx: None,
|
|
||||||
// sig_idx: None,
|
|
||||||
// in_scope: false,
|
|
||||||
// };
|
|
||||||
// nb_reg
|
|
||||||
//],
|
|
||||||
register_states: vec![], // In the end, it's easier to grow this on the fly
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_ins(&self) -> Result<DbgBytecode> {
|
|
||||||
if self.pc >= self.debug_info.bytecode.len() {
|
|
||||||
return Err(Error::OutOfBound(
|
|
||||||
"Try to read an instruction out of bound, maybe after the end of the debug sequence."
|
|
||||||
.into()
|
|
||||||
));
|
|
||||||
}
|
|
||||||
Ok(self.debug_info.bytecode[self.pc])
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn tick(&mut self) -> Option<DebugInfo> {
|
|
||||||
let ins = if let Ok(ins) = self.get_ins() {
|
|
||||||
ins
|
|
||||||
} else {
|
|
||||||
return Some(DebugInfo::EndOfData);
|
|
||||||
};
|
|
||||||
self.pc += 1;
|
|
||||||
match ins {
|
|
||||||
DbgBytecode::EndSequence => {
|
|
||||||
self.pc = self.debug_info.bytecode.len();
|
|
||||||
Some(DebugInfo::EndOfData)
|
|
||||||
}
|
|
||||||
DbgBytecode::AdvancePC {
|
|
||||||
addr_diff: Uleb128(addr_diff),
|
|
||||||
} => {
|
|
||||||
self.address += addr_diff;
|
|
||||||
None
|
|
||||||
}
|
|
||||||
DbgBytecode::AdvanceLine {
|
|
||||||
line_diff: Sleb128(line_diff),
|
|
||||||
} => {
|
|
||||||
self.line = (self.line as i32 + line_diff) as u32;
|
|
||||||
None
|
|
||||||
}
|
|
||||||
DbgBytecode::StartLocal {
|
|
||||||
register_num: Uleb128(register_num),
|
|
||||||
name_idx,
|
|
||||||
type_idx,
|
|
||||||
} => {
|
|
||||||
while self.register_states.len() < (register_num + 1) as usize {
|
|
||||||
self.register_states.push(DebugRegState {
|
|
||||||
name_idx: None,
|
|
||||||
type_idx: None,
|
|
||||||
sig_idx: None,
|
|
||||||
in_scope: false,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
self.register_states[register_num as usize] = DebugRegState {
|
|
||||||
name_idx: if name_idx == NO_INDEX {
|
|
||||||
None
|
|
||||||
} else {
|
|
||||||
Some(name_idx.0)
|
|
||||||
},
|
|
||||||
type_idx: if type_idx == NO_INDEX {
|
|
||||||
None
|
|
||||||
} else {
|
|
||||||
Some(type_idx.0)
|
|
||||||
},
|
|
||||||
sig_idx: None,
|
|
||||||
in_scope: true,
|
|
||||||
};
|
|
||||||
Some(DebugInfo::DefLocal {
|
|
||||||
addr: self.address,
|
|
||||||
reg: register_num,
|
|
||||||
val: self.register_states[register_num as usize],
|
|
||||||
})
|
|
||||||
}
|
|
||||||
DbgBytecode::StartLocalExtended {
|
|
||||||
register_num: Uleb128(register_num),
|
|
||||||
name_idx,
|
|
||||||
type_idx,
|
|
||||||
sig_idx,
|
|
||||||
} => {
|
|
||||||
while self.register_states.len() < (register_num + 1) as usize {
|
|
||||||
self.register_states.push(DebugRegState {
|
|
||||||
name_idx: None,
|
|
||||||
type_idx: None,
|
|
||||||
sig_idx: None,
|
|
||||||
in_scope: false,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
self.register_states[register_num as usize] = DebugRegState {
|
|
||||||
name_idx: if name_idx == NO_INDEX {
|
|
||||||
None
|
|
||||||
} else {
|
|
||||||
Some(name_idx.0)
|
|
||||||
},
|
|
||||||
type_idx: if type_idx == NO_INDEX {
|
|
||||||
None
|
|
||||||
} else {
|
|
||||||
Some(type_idx.0)
|
|
||||||
},
|
|
||||||
sig_idx: if sig_idx == NO_INDEX {
|
|
||||||
None
|
|
||||||
} else {
|
|
||||||
Some(sig_idx.0)
|
|
||||||
},
|
|
||||||
in_scope: true,
|
|
||||||
};
|
|
||||||
Some(DebugInfo::DefLocal {
|
|
||||||
addr: self.address,
|
|
||||||
reg: register_num,
|
|
||||||
val: self.register_states[register_num as usize],
|
|
||||||
})
|
|
||||||
}
|
|
||||||
DbgBytecode::EndLocal {
|
|
||||||
register_num: Uleb128(register_num),
|
|
||||||
} => {
|
|
||||||
// Yes this can happen
|
|
||||||
while self.register_states.len() < (register_num + 1) as usize {
|
|
||||||
self.register_states.push(DebugRegState {
|
|
||||||
name_idx: None,
|
|
||||||
type_idx: None,
|
|
||||||
sig_idx: None,
|
|
||||||
in_scope: false,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
self.register_states[register_num as usize].in_scope = false;
|
|
||||||
Some(DebugInfo::EndLocal {
|
|
||||||
addr: self.address,
|
|
||||||
reg: register_num,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
DbgBytecode::RestartLocal {
|
|
||||||
register_num: Uleb128(register_num),
|
|
||||||
} => {
|
|
||||||
while self.register_states.len() < (register_num + 1) as usize {
|
|
||||||
self.register_states.push(DebugRegState {
|
|
||||||
name_idx: None,
|
|
||||||
type_idx: None,
|
|
||||||
sig_idx: None,
|
|
||||||
in_scope: false,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
self.register_states[register_num as usize].in_scope = true;
|
|
||||||
Some(DebugInfo::DefLocal {
|
|
||||||
addr: self.address,
|
|
||||||
reg: register_num,
|
|
||||||
val: self.register_states[register_num as usize],
|
|
||||||
})
|
|
||||||
}
|
|
||||||
DbgBytecode::SetPrologueEnd => {
|
|
||||||
//self.prologue_end = true;
|
|
||||||
Some(DebugInfo::PrologueEnd { addr: self.address })
|
|
||||||
}
|
|
||||||
DbgBytecode::SetEpilogueBegin => {
|
|
||||||
//self.epilogue_begin = true;
|
|
||||||
Some(DebugInfo::EpilogueBegin { addr: self.address })
|
|
||||||
}
|
|
||||||
DbgBytecode::SetFile { name_idx: NO_INDEX } => {
|
|
||||||
//self.source_file_idx = None;
|
|
||||||
Some(DebugInfo::SetSourceFile {
|
|
||||||
addr: self.address,
|
|
||||||
source_file_idx: None,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
DbgBytecode::SetFile {
|
|
||||||
name_idx: Uleb128p1(name_idx),
|
|
||||||
} => {
|
|
||||||
//self.source_file_idx = Some(name_idx);
|
|
||||||
Some(DebugInfo::SetSourceFile {
|
|
||||||
addr: self.address,
|
|
||||||
source_file_idx: Some(name_idx),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
DbgBytecode::SpecialOpcode(op) => {
|
|
||||||
//if op >= 0x0a {
|
|
||||||
// self.prologue_end = false;
|
|
||||||
// self.epilogue_begin = true;
|
|
||||||
//}
|
|
||||||
// See <https://source.android.com/docs/core/runtime/dex-format#opcodes>
|
|
||||||
let adjusted_opcode = op as u32 - 0x0a;
|
|
||||||
self.line = (self.line as i32 + (adjusted_opcode as i32 % 15) - 4) as u32;
|
|
||||||
self.address += adjusted_opcode / 15;
|
|
||||||
Some(DebugInfo::SetLineNumber {
|
|
||||||
addr: self.address,
|
|
||||||
line_num: self.line,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn next_info(&mut self) -> DebugInfo {
|
|
||||||
loop {
|
|
||||||
if let Some(info) = self.tick() {
|
|
||||||
return info;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A state machine that generate a [`DebugInfoItem`].
|
|
||||||
#[derive(Debug, PartialEq, Eq, Clone)]
|
|
||||||
pub struct DebugInfoBuilder {
|
|
||||||
debug_infos: Vec<DbgBytecode>,
|
|
||||||
line_start: Option<u32>,
|
|
||||||
parameter_names: Vec<Uleb128p1>,
|
|
||||||
|
|
||||||
//pub pc: usize,
|
|
||||||
address: u32,
|
|
||||||
line: u32,
|
|
||||||
// Those are registers described in the doc but not necessary in the end
|
|
||||||
//pub source_file_idx: Option<u32>,
|
|
||||||
//pub prologue_end: bool,
|
|
||||||
//pub epilogue_begin: bool,
|
|
||||||
register_states: Vec<DebugRegState>,
|
|
||||||
finished: bool,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl DebugInfoBuilder {
|
|
||||||
pub fn new(parameter_names: Vec<Uleb128p1>) -> Self {
|
|
||||||
Self {
|
|
||||||
debug_infos: vec![],
|
|
||||||
line_start: None,
|
|
||||||
parameter_names,
|
|
||||||
//pc: 0,
|
|
||||||
address: 0,
|
|
||||||
line: 0,
|
|
||||||
//source_file_idx,
|
|
||||||
//prologue_end: false,
|
|
||||||
//epilogue_begin: false,
|
|
||||||
//register_states: vec![
|
|
||||||
// DebugRegState {
|
|
||||||
// name_idx: None,
|
|
||||||
// type_idx: None,
|
|
||||||
// sig_idx: None,
|
|
||||||
// in_scope: false,
|
|
||||||
// };
|
|
||||||
// nb_reg
|
|
||||||
//],
|
|
||||||
register_states: vec![], // In the end, it's easier to grow this on the fly
|
|
||||||
finished: false,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn add_info(&mut self, info: &DebugInfo) -> Result<()> {
|
|
||||||
if self.finished {
|
|
||||||
return Err(Error::SerializationError(
|
|
||||||
"Cannot add more information: EndSequence has already been send".into(),
|
|
||||||
));
|
|
||||||
}
|
|
||||||
match info {
|
|
||||||
DebugInfo::DefLocal { addr, reg, val } => {
|
|
||||||
if *addr < self.address {
|
|
||||||
return Err(Error::SerializationError(format!(
|
|
||||||
"The address register can only increase, \
|
|
||||||
found 0x{addr:02x} while register is already \
|
|
||||||
0x{:02x}",
|
|
||||||
self.address
|
|
||||||
)));
|
|
||||||
}
|
|
||||||
while self.register_states.len() < (reg + 1) as usize {
|
|
||||||
self.register_states.push(DebugRegState {
|
|
||||||
name_idx: None,
|
|
||||||
type_idx: None,
|
|
||||||
sig_idx: None,
|
|
||||||
in_scope: false,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
if *addr != self.address {
|
|
||||||
let addr_diff = *addr - self.address;
|
|
||||||
self.debug_infos.push(DbgBytecode::AdvancePC {
|
|
||||||
addr_diff: Uleb128(addr_diff),
|
|
||||||
});
|
|
||||||
self.address += addr_diff;
|
|
||||||
}
|
|
||||||
let mut old_val = self.register_states[*reg as usize];
|
|
||||||
let old_val_in_scope = old_val.in_scope;
|
|
||||||
old_val.in_scope = true;
|
|
||||||
if old_val_in_scope && old_val == *val {
|
|
||||||
self.register_states[*reg as usize].in_scope = true;
|
|
||||||
self.debug_infos.push(DbgBytecode::RestartLocal {
|
|
||||||
register_num: Uleb128(*reg),
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
self.register_states[*reg as usize] = *val;
|
|
||||||
if val.sig_idx.is_some() {
|
|
||||||
self.debug_infos.push(DbgBytecode::StartLocalExtended {
|
|
||||||
register_num: Uleb128(*reg),
|
|
||||||
name_idx: if let Some(name_idx) = val.name_idx {
|
|
||||||
Uleb128p1(name_idx)
|
|
||||||
} else {
|
|
||||||
NO_INDEX
|
|
||||||
},
|
|
||||||
type_idx: if let Some(type_idx) = val.type_idx {
|
|
||||||
Uleb128p1(type_idx)
|
|
||||||
} else {
|
|
||||||
NO_INDEX
|
|
||||||
},
|
|
||||||
sig_idx: if let Some(sig_idx) = val.sig_idx {
|
|
||||||
Uleb128p1(sig_idx)
|
|
||||||
} else {
|
|
||||||
NO_INDEX
|
|
||||||
},
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
self.debug_infos.push(DbgBytecode::StartLocal {
|
|
||||||
register_num: Uleb128(*reg),
|
|
||||||
name_idx: if let Some(name_idx) = val.name_idx {
|
|
||||||
Uleb128p1(name_idx)
|
|
||||||
} else {
|
|
||||||
NO_INDEX
|
|
||||||
},
|
|
||||||
type_idx: if let Some(type_idx) = val.type_idx {
|
|
||||||
Uleb128p1(type_idx)
|
|
||||||
} else {
|
|
||||||
NO_INDEX
|
|
||||||
},
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
DebugInfo::EndLocal { addr, reg } => {
|
|
||||||
if *addr < self.address {
|
|
||||||
return Err(Error::SerializationError(format!(
|
|
||||||
"The address register can only increase, \
|
|
||||||
found 0x{addr:02x} while register is already \
|
|
||||||
0x{:02x}",
|
|
||||||
self.address
|
|
||||||
)));
|
|
||||||
}
|
|
||||||
while self.register_states.len() < (reg + 1) as usize {
|
|
||||||
self.register_states.push(DebugRegState {
|
|
||||||
name_idx: None,
|
|
||||||
type_idx: None,
|
|
||||||
sig_idx: None,
|
|
||||||
in_scope: false,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
if *addr != self.address {
|
|
||||||
let addr_diff = *addr - self.address;
|
|
||||||
self.debug_infos.push(DbgBytecode::AdvancePC {
|
|
||||||
addr_diff: Uleb128(addr_diff),
|
|
||||||
});
|
|
||||||
self.address += addr_diff;
|
|
||||||
}
|
|
||||||
self.debug_infos.push(DbgBytecode::EndLocal {
|
|
||||||
register_num: Uleb128(*reg),
|
|
||||||
});
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
DebugInfo::PrologueEnd { addr } => {
|
|
||||||
if *addr < self.address {
|
|
||||||
return Err(Error::SerializationError(format!(
|
|
||||||
"The address register can only increase, \
|
|
||||||
found 0x{addr:02x} while register is already \
|
|
||||||
0x{:02x}",
|
|
||||||
self.address
|
|
||||||
)));
|
|
||||||
}
|
|
||||||
if *addr != self.address {
|
|
||||||
let addr_diff = *addr - self.address;
|
|
||||||
self.debug_infos.push(DbgBytecode::AdvancePC {
|
|
||||||
addr_diff: Uleb128(addr_diff),
|
|
||||||
});
|
|
||||||
self.address += addr_diff;
|
|
||||||
}
|
|
||||||
self.debug_infos.push(DbgBytecode::SetPrologueEnd);
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
DebugInfo::EpilogueBegin { addr } => {
|
|
||||||
if *addr < self.address {
|
|
||||||
return Err(Error::SerializationError(format!(
|
|
||||||
"The address register can only increase, \
|
|
||||||
found 0x{addr:02x} while register is already \
|
|
||||||
0x{:02x}",
|
|
||||||
self.address
|
|
||||||
)));
|
|
||||||
}
|
|
||||||
if *addr != self.address {
|
|
||||||
let addr_diff = *addr - self.address;
|
|
||||||
self.debug_infos.push(DbgBytecode::AdvancePC {
|
|
||||||
addr_diff: Uleb128(addr_diff),
|
|
||||||
});
|
|
||||||
self.address += addr_diff;
|
|
||||||
}
|
|
||||||
self.debug_infos.push(DbgBytecode::SetEpilogueBegin);
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
DebugInfo::SetLineNumber { addr, line_num } => {
|
|
||||||
if *addr < self.address {
|
|
||||||
return Err(Error::SerializationError(format!(
|
|
||||||
"The address register can only increase, \
|
|
||||||
found 0x{addr:02x} while register is already \
|
|
||||||
0x{:02x}",
|
|
||||||
self.address
|
|
||||||
)));
|
|
||||||
}
|
|
||||||
if self.line_start.is_none() {
|
|
||||||
self.line_start = Some(*line_num);
|
|
||||||
self.line = *line_num;
|
|
||||||
}
|
|
||||||
let mut line_diff = *line_num as i32 - self.line as i32;
|
|
||||||
let mut addr_diff = addr - self.address;
|
|
||||||
if !(-4..15 - 4).contains(&line_diff) {
|
|
||||||
self.debug_infos.push(DbgBytecode::AdvanceLine {
|
|
||||||
line_diff: Sleb128(line_diff),
|
|
||||||
});
|
|
||||||
self.line = *line_num;
|
|
||||||
line_diff = 0;
|
|
||||||
}
|
|
||||||
if addr_diff as i32 * 15 + 0x0a + line_diff + 4 > 0xff {
|
|
||||||
self.debug_infos.push(DbgBytecode::AdvancePC {
|
|
||||||
addr_diff: Uleb128(addr_diff),
|
|
||||||
});
|
|
||||||
self.address = *addr;
|
|
||||||
addr_diff = 0;
|
|
||||||
}
|
|
||||||
let op = 0x0a + addr_diff as u8 * 15 + (line_diff + 4) as u8;
|
|
||||||
self.debug_infos.push(DbgBytecode::SpecialOpcode(op));
|
|
||||||
self.address += addr_diff;
|
|
||||||
self.line = (self.line as i32 + line_diff) as u32;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
DebugInfo::SetSourceFile {
|
|
||||||
addr,
|
|
||||||
source_file_idx,
|
|
||||||
} => {
|
|
||||||
if *addr != self.address {
|
|
||||||
let addr_diff = *addr - self.address;
|
|
||||||
self.debug_infos.push(DbgBytecode::AdvancePC {
|
|
||||||
addr_diff: Uleb128(addr_diff),
|
|
||||||
});
|
|
||||||
self.address += addr_diff;
|
|
||||||
}
|
|
||||||
self.debug_infos.push(DbgBytecode::SetFile {
|
|
||||||
name_idx: if let Some(source_file_idx) = source_file_idx {
|
|
||||||
Uleb128p1(*source_file_idx)
|
|
||||||
} else {
|
|
||||||
NO_INDEX
|
|
||||||
},
|
|
||||||
});
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
DebugInfo::EndOfData => {
|
|
||||||
self.finished = true;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// If they are no debug information, return None, else compute and return the [`DebugInfoItem`].
|
|
||||||
pub fn build(self) -> Option<DebugInfoItem> {
|
|
||||||
if self.debug_infos.is_empty() && self.parameter_names.iter().all(|&idx| idx == NO_INDEX) {
|
|
||||||
None
|
|
||||||
} else {
|
|
||||||
Some(DebugInfoItem {
|
|
||||||
line_start: Uleb128(self.line_start.unwrap_or(0)),
|
|
||||||
parameter_names: self.parameter_names,
|
|
||||||
bytecode: self.debug_infos,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod test {
|
mod test {
|
||||||
use super::DbgBytecode::*;
|
use super::DbgBytecode::*;
|
||||||
|
|
@ -664,10 +112,6 @@ mod test {
|
||||||
addr_diff: Uleb128(51),
|
addr_diff: Uleb128(51),
|
||||||
},
|
},
|
||||||
SpecialOpcode(14),
|
SpecialOpcode(14),
|
||||||
// End a local that do not already exist
|
|
||||||
EndLocal {
|
|
||||||
register_num: Uleb128(41),
|
|
||||||
},
|
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
|
|
@ -686,27 +130,4 @@ mod test {
|
||||||
DbgBytecode::deserialize_from_slice(&advance_line.serialize_to_vec().unwrap()).unwrap()
|
DbgBytecode::deserialize_from_slice(&advance_line.serialize_to_vec().unwrap()).unwrap()
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_get_expl_debug() {
|
|
||||||
const RAW_DEBUG: [u8; 10] = [23, 0, 14, 135, 3, 0, 16, 2, 150, 0];
|
|
||||||
let debug = DebugInfoItem::deserialize_from_slice(&RAW_DEBUG).unwrap();
|
|
||||||
let mut reader = DebugInfoReader::new(debug.clone());
|
|
||||||
let mut list_info = vec![];
|
|
||||||
loop {
|
|
||||||
list_info.push(reader.next_info());
|
|
||||||
if list_info.last() == Some(&DebugInfo::EndOfData) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
let mut builder = DebugInfoBuilder::new(debug.parameter_names.clone());
|
|
||||||
for info in list_info {
|
|
||||||
builder.add_info(&info).unwrap();
|
|
||||||
}
|
|
||||||
let debug_computed = builder.build().unwrap();
|
|
||||||
assert_eq!(
|
|
||||||
&RAW_DEBUG,
|
|
||||||
&(debug_computed.serialize_to_vec().unwrap()).as_slice()
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,9 @@
|
||||||
//! Parser for a .dex file.
|
//! Parser for a .dex file.
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
CallSiteIdItem, ClassDataItem, ClassDefItem, EndianConstant, Error, FieldIdItem, HeaderItem,
|
CallSiteIdItem, ClassDefItem, EndianConstant, Error, FieldIdItem, HeaderItem, MapItemType,
|
||||||
HiddenApiFlags, HiddenapiClassDataItem, MapItemType, MapList, MethodHandleItem, MethodIdItem,
|
MapList, MethodHandleItem, MethodIdItem, ProtoIdItem, Result, Serializable, StringDataItem,
|
||||||
ProtoIdItem, Result, Serializable, StringDataItem, StringIdItem, TypeIdItem,
|
StringIdItem, TypeIdItem,
|
||||||
};
|
};
|
||||||
use log::{error, info, warn};
|
use log::{error, info, warn};
|
||||||
use std::io::{Cursor, Seek, SeekFrom};
|
use std::io::{Cursor, Seek, SeekFrom};
|
||||||
|
|
@ -11,8 +11,6 @@ use std::sync::atomic::{AtomicBool, Ordering};
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct DexFileReader<'a> {
|
pub struct DexFileReader<'a> {
|
||||||
// Ideally, this would be a Read+Seek, but Read+Seek is not thread safe, while we can
|
|
||||||
// internally instanciate multiple cursors on the same non mutable slice.
|
|
||||||
data: &'a [u8],
|
data: &'a [u8],
|
||||||
header: HeaderItem,
|
header: HeaderItem,
|
||||||
string_ids: Vec<StringIdItem>,
|
string_ids: Vec<StringIdItem>,
|
||||||
|
|
@ -30,7 +28,6 @@ pub struct DexFileReader<'a> {
|
||||||
class_defs: Vec<ClassDefItem>,
|
class_defs: Vec<ClassDefItem>,
|
||||||
call_site_ids: Vec<CallSiteIdItem>,
|
call_site_ids: Vec<CallSiteIdItem>,
|
||||||
method_handles: Vec<MethodHandleItem>,
|
method_handles: Vec<MethodHandleItem>,
|
||||||
hiddenapi_class_data: Option<HiddenapiClassDataItem>,
|
|
||||||
map_list: MapList,
|
map_list: MapList,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -51,12 +48,9 @@ impl<'a> DexFileReader<'a> {
|
||||||
class_defs: vec![],
|
class_defs: vec![],
|
||||||
call_site_ids: vec![],
|
call_site_ids: vec![],
|
||||||
method_handles: vec![],
|
method_handles: vec![],
|
||||||
hiddenapi_class_data: None,
|
|
||||||
map_list: MapList { list: vec![] },
|
map_list: MapList { list: vec![] },
|
||||||
};
|
};
|
||||||
if tmp_file.header.map_off != 0 {
|
tmp_file.map_list = tmp_file.get_struct_at_offset(tmp_file.header.map_off)?;
|
||||||
tmp_file.map_list = tmp_file.get_struct_at_offset(tmp_file.header.map_off)?;
|
|
||||||
}
|
|
||||||
tmp_file.string_ids = tmp_file.get_item_list::<StringIdItem>(
|
tmp_file.string_ids = tmp_file.get_item_list::<StringIdItem>(
|
||||||
tmp_file.header.string_ids_off,
|
tmp_file.header.string_ids_off,
|
||||||
tmp_file.header.string_ids_size,
|
tmp_file.header.string_ids_size,
|
||||||
|
|
@ -103,15 +97,6 @@ impl<'a> DexFileReader<'a> {
|
||||||
tmp_file.method_handles =
|
tmp_file.method_handles =
|
||||||
tmp_file.get_item_list::<MethodHandleItem>(item.offset, item.size)?
|
tmp_file.get_item_list::<MethodHandleItem>(item.offset, item.size)?
|
||||||
}
|
}
|
||||||
if let Some(item) = tmp_file
|
|
||||||
.map_list
|
|
||||||
.list
|
|
||||||
.iter()
|
|
||||||
.find(|item| item.type_ == MapItemType::HiddenapiClassDataItem)
|
|
||||||
{
|
|
||||||
tmp_file.hiddenapi_class_data =
|
|
||||||
Some(tmp_file.get_struct_at_offset::<HiddenapiClassDataItem>(item.offset)?);
|
|
||||||
}
|
|
||||||
tmp_file.sanity_check()?;
|
tmp_file.sanity_check()?;
|
||||||
Ok(tmp_file)
|
Ok(tmp_file)
|
||||||
}
|
}
|
||||||
|
|
@ -120,39 +105,39 @@ impl<'a> DexFileReader<'a> {
|
||||||
pub fn get_header(&self) -> &HeaderItem {
|
pub fn get_header(&self) -> &HeaderItem {
|
||||||
&self.header
|
&self.header
|
||||||
}
|
}
|
||||||
/// Return the file [`StringIdItem`] list.
|
/// Retunr the file [`StringIdItem`] list.
|
||||||
pub fn get_string_ids(&self) -> &[StringIdItem] {
|
pub fn get_string_ids(&self) -> &[StringIdItem] {
|
||||||
&self.string_ids
|
&self.string_ids
|
||||||
}
|
}
|
||||||
/// Return the file [`TypeIdItem`] list.
|
/// Retunr the file [`TypeIdItem`] list.
|
||||||
pub fn get_type_ids(&self) -> &[TypeIdItem] {
|
pub fn get_type_ids(&self) -> &[TypeIdItem] {
|
||||||
&self.type_ids
|
&self.type_ids
|
||||||
}
|
}
|
||||||
/// Return the file [`ProtoIdItem`] list.
|
/// Retunr the file [`ProtoIdItem`] list.
|
||||||
pub fn get_proto_ids(&self) -> &[ProtoIdItem] {
|
pub fn get_proto_ids(&self) -> &[ProtoIdItem] {
|
||||||
&self.proto_ids
|
&self.proto_ids
|
||||||
}
|
}
|
||||||
/// Return the file [`FieldIdItem`] list.
|
/// Retunr the file [`FieldIdItem`] list.
|
||||||
pub fn get_field_ids(&self) -> &[FieldIdItem] {
|
pub fn get_field_ids(&self) -> &[FieldIdItem] {
|
||||||
&self.field_ids
|
&self.field_ids
|
||||||
}
|
}
|
||||||
/// Return the file [`MethodIdItem`] list.
|
/// Retunr the file [`MethodIdItem`] list.
|
||||||
pub fn get_method_ids(&self) -> &[MethodIdItem] {
|
pub fn get_method_ids(&self) -> &[MethodIdItem] {
|
||||||
&self.method_ids
|
&self.method_ids
|
||||||
}
|
}
|
||||||
/// Return the file [`ClassDefItem`] list.
|
/// Retunr the file [`ClassDefItem`] list.
|
||||||
pub fn get_class_defs(&self) -> &[ClassDefItem] {
|
pub fn get_class_defs(&self) -> &[ClassDefItem] {
|
||||||
&self.class_defs
|
&self.class_defs
|
||||||
}
|
}
|
||||||
/// Return the file [`CallSiteIdItem`] list.
|
/// Retunr the file [`CallSiteIdItem`] list.
|
||||||
pub fn get_call_site_ids(&self) -> &[CallSiteIdItem] {
|
pub fn get_call_site_ids(&self) -> &[CallSiteIdItem] {
|
||||||
&self.call_site_ids
|
&self.call_site_ids
|
||||||
}
|
}
|
||||||
/// Return the file [`MethodHandleItem`] list.
|
/// Retunr the file [`MethodHandleItem`] list.
|
||||||
pub fn get_method_handles(&self) -> &[MethodHandleItem] {
|
pub fn get_method_handles(&self) -> &[MethodHandleItem] {
|
||||||
&self.method_handles
|
&self.method_handles
|
||||||
}
|
}
|
||||||
/// Return the file [`MapList`].
|
/// Retunr the file [`MapList`].
|
||||||
pub fn get_map_list(&self) -> &MapList {
|
pub fn get_map_list(&self) -> &MapList {
|
||||||
&self.map_list
|
&self.map_list
|
||||||
}
|
}
|
||||||
|
|
@ -237,11 +222,9 @@ impl<'a> DexFileReader<'a> {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn sanity_check(&self) -> Result<()> {
|
fn sanity_check(&self) -> Result<()> {
|
||||||
let version = self.header.magic.version;
|
if self.header.magic.version != [0x30, 0x33, 0x39] {
|
||||||
let version = (version[0] - 0x30) * 100 + (version[1] - 0x30) * 10 + (version[2] - 0x30);
|
|
||||||
if version > 39 {
|
|
||||||
warn!(
|
warn!(
|
||||||
"Only version <= DEX 039 are currently supported, found {}",
|
"DEX 039 is the only version currently supported, found {}",
|
||||||
std::str::from_utf8(self.header.magic.version.as_slice())
|
std::str::from_utf8(self.header.magic.version.as_slice())
|
||||||
.unwrap_or(&format!("{:x?}", self.header.magic.version))
|
.unwrap_or(&format!("{:x?}", self.header.magic.version))
|
||||||
);
|
);
|
||||||
|
|
@ -272,7 +255,7 @@ impl<'a> DexFileReader<'a> {
|
||||||
MapItemType::HeaderItem if item.offset != 0 || item.size != 1 => {
|
MapItemType::HeaderItem if item.offset != 0 || item.size != 1 => {
|
||||||
return Err(Error::InconsistantStruct(format!(
|
return Err(Error::InconsistantStruct(format!(
|
||||||
"Inconsistant Header Mapping info found in map_list: {item:x?}"
|
"Inconsistant Header Mapping info found in map_list: {item:x?}"
|
||||||
)));
|
)))
|
||||||
}
|
}
|
||||||
MapItemType::StringIdItem
|
MapItemType::StringIdItem
|
||||||
if item.offset != self.header.string_ids_off
|
if item.offset != self.header.string_ids_off
|
||||||
|
|
@ -282,7 +265,7 @@ impl<'a> DexFileReader<'a> {
|
||||||
"Inconsistant MapList Mapping info found in map_list: {item:x?}, \
|
"Inconsistant MapList Mapping info found in map_list: {item:x?}, \
|
||||||
header.string_ids_off: 0x{:x}, header.string_ids_size: {}",
|
header.string_ids_off: 0x{:x}, header.string_ids_size: {}",
|
||||||
self.header.string_ids_off, self.header.string_ids_size
|
self.header.string_ids_off, self.header.string_ids_size
|
||||||
)));
|
)))
|
||||||
}
|
}
|
||||||
MapItemType::TypeIdItem
|
MapItemType::TypeIdItem
|
||||||
if item.offset != self.header.type_ids_off
|
if item.offset != self.header.type_ids_off
|
||||||
|
|
@ -292,7 +275,7 @@ impl<'a> DexFileReader<'a> {
|
||||||
"Inconsistant MapList Mapping info found in map_list: {item:x?}, \
|
"Inconsistant MapList Mapping info found in map_list: {item:x?}, \
|
||||||
header.type_ids_off: 0x{:x}, header.type_ids_size: {}",
|
header.type_ids_off: 0x{:x}, header.type_ids_size: {}",
|
||||||
self.header.type_ids_off, self.header.type_ids_size
|
self.header.type_ids_off, self.header.type_ids_size
|
||||||
)));
|
)))
|
||||||
}
|
}
|
||||||
MapItemType::ProtoIdItem
|
MapItemType::ProtoIdItem
|
||||||
if item.offset != self.header.proto_ids_off
|
if item.offset != self.header.proto_ids_off
|
||||||
|
|
@ -302,7 +285,7 @@ impl<'a> DexFileReader<'a> {
|
||||||
"Inconsistant MapList Mapping info found in map_list: {item:x?}, \
|
"Inconsistant MapList Mapping info found in map_list: {item:x?}, \
|
||||||
header.proto_ids_off: 0x{:x}, header.proto_ids_size: {}",
|
header.proto_ids_off: 0x{:x}, header.proto_ids_size: {}",
|
||||||
self.header.proto_ids_off, self.header.proto_ids_size
|
self.header.proto_ids_off, self.header.proto_ids_size
|
||||||
)));
|
)))
|
||||||
}
|
}
|
||||||
MapItemType::FieldIdItem
|
MapItemType::FieldIdItem
|
||||||
if item.offset != self.header.field_ids_off
|
if item.offset != self.header.field_ids_off
|
||||||
|
|
@ -312,7 +295,7 @@ impl<'a> DexFileReader<'a> {
|
||||||
"Inconsistant MapList Mapping info found in map_list: {item:x?}, \
|
"Inconsistant MapList Mapping info found in map_list: {item:x?}, \
|
||||||
header.field_ids_off: 0x{:x}, header.field_ids_size: {}",
|
header.field_ids_off: 0x{:x}, header.field_ids_size: {}",
|
||||||
self.header.field_ids_off, self.header.field_ids_size
|
self.header.field_ids_off, self.header.field_ids_size
|
||||||
)));
|
)))
|
||||||
}
|
}
|
||||||
MapItemType::MethodIdItem
|
MapItemType::MethodIdItem
|
||||||
if item.offset != self.header.method_ids_off
|
if item.offset != self.header.method_ids_off
|
||||||
|
|
@ -322,7 +305,7 @@ impl<'a> DexFileReader<'a> {
|
||||||
"Inconsistant MapList Mapping info found in map_list: {item:x?}, \
|
"Inconsistant MapList Mapping info found in map_list: {item:x?}, \
|
||||||
header.method_ids_off: 0x{:x}, header.method_ids_size: {}",
|
header.method_ids_off: 0x{:x}, header.method_ids_size: {}",
|
||||||
self.header.method_ids_off, self.header.method_ids_size
|
self.header.method_ids_off, self.header.method_ids_size
|
||||||
)));
|
)))
|
||||||
}
|
}
|
||||||
MapItemType::ClassDefItem
|
MapItemType::ClassDefItem
|
||||||
if item.offset != self.header.class_defs_off
|
if item.offset != self.header.class_defs_off
|
||||||
|
|
@ -332,14 +315,14 @@ impl<'a> DexFileReader<'a> {
|
||||||
"Inconsistant MapList Mapping info found in map_list: {item:x?}, \
|
"Inconsistant MapList Mapping info found in map_list: {item:x?}, \
|
||||||
header.class_defs_off: 0x{:x}, header.class_defs_size: {}",
|
header.class_defs_off: 0x{:x}, header.class_defs_size: {}",
|
||||||
self.header.class_defs_off, self.header.class_defs_size
|
self.header.class_defs_off, self.header.class_defs_size
|
||||||
)));
|
)))
|
||||||
}
|
}
|
||||||
MapItemType::MapList if item.offset != self.header.map_off || item.size != 1 => {
|
MapItemType::MapList if item.offset != self.header.map_off || item.size != 1 => {
|
||||||
return Err(Error::InconsistantStruct(format!(
|
return Err(Error::InconsistantStruct(format!(
|
||||||
"Inconsistant MapList Mapping info found in map_list: {item:x?}, \
|
"Inconsistant MapList Mapping info found in map_list: {item:x?}, \
|
||||||
header.map_list_off: 0x{:x}",
|
header.map_list_off: 0x{:x}",
|
||||||
self.header.map_off
|
self.header.map_off
|
||||||
)));
|
)))
|
||||||
}
|
}
|
||||||
/*
|
/*
|
||||||
MapItemType::CallSiteIdItem => todo!(),
|
MapItemType::CallSiteIdItem => todo!(),
|
||||||
|
|
@ -382,9 +365,6 @@ impl<'a> DexFileReader<'a> {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_item_list<T: Serializable>(&self, offset: u32, size: u32) -> Result<Vec<T>> {
|
fn get_item_list<T: Serializable>(&self, offset: u32, size: u32) -> Result<Vec<T>> {
|
||||||
if offset == 0 {
|
|
||||||
return Ok(vec![]);
|
|
||||||
}
|
|
||||||
let mut buffer = Cursor::new(self.data);
|
let mut buffer = Cursor::new(self.data);
|
||||||
buffer.seek(SeekFrom::Start(offset as u64)).map_err(|err| {
|
buffer.seek(SeekFrom::Start(offset as u64)).map_err(|err| {
|
||||||
Error::DeserializationError(format!("Failed to seek 0x{offset:x} position: {err}"))
|
Error::DeserializationError(format!("Failed to seek 0x{offset:x} position: {err}"))
|
||||||
|
|
@ -436,45 +416,6 @@ impl<'a> DexFileReader<'a> {
|
||||||
r
|
r
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Return the hiddenapi flags list for the given class.
|
|
||||||
///
|
|
||||||
/// The list of flags is composed of one [`HiddenApiFlags`] for each static field, instance
|
|
||||||
/// field, direct method and virtual method of the class, in that order.
|
|
||||||
///
|
|
||||||
/// `class_def_item_idx` if the idx of the `class_def_item`, **not** the `class_idx` (contrary
|
|
||||||
/// to what <https://source.android.com/docs/core/runtime/dex-format#hiddenapi-class-data-item>
|
|
||||||
/// says)
|
|
||||||
pub fn get_class_hiddenapi_flags(
|
|
||||||
&self,
|
|
||||||
class_def_item_idx: usize,
|
|
||||||
) -> Result<Option<Vec<HiddenApiFlags>>> {
|
|
||||||
if class_def_item_idx >= self.class_defs.len() {
|
|
||||||
return Err(Error::InconsistantStruct(format!(
|
|
||||||
"idx 0x{class_def_item_idx:x} is out of bound of class_defs"
|
|
||||||
)));
|
|
||||||
}
|
|
||||||
let class_def = self.class_defs[class_def_item_idx];
|
|
||||||
if class_def.class_data_off == 0 {
|
|
||||||
if self.hiddenapi_class_data.is_some() {
|
|
||||||
return Ok(Some(vec![]));
|
|
||||||
} else {
|
|
||||||
return Ok(None);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
let class_data = self.get_struct_at_offset::<ClassDataItem>(class_def.class_data_off)?;
|
|
||||||
let nb_flags = class_data.static_fields.len()
|
|
||||||
+ class_data.instance_fields.len()
|
|
||||||
+ class_data.direct_methods.len()
|
|
||||||
+ class_data.virtual_methods.len();
|
|
||||||
if let Some(hidden_api_data) = &self.hiddenapi_class_data {
|
|
||||||
hidden_api_data
|
|
||||||
.get_flags(nb_flags, class_def_item_idx)
|
|
||||||
.map(Some)
|
|
||||||
} else {
|
|
||||||
Ok(None)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Return the strings that where not referenced.
|
/// Return the strings that where not referenced.
|
||||||
pub fn get_not_resolved_strings(&mut self) -> Result<Vec<StringDataItem>> {
|
pub fn get_not_resolved_strings(&mut self) -> Result<Vec<StringDataItem>> {
|
||||||
// use `&mut self` because using this method at the same time as performing
|
// use `&mut self` because using this method at the same time as performing
|
||||||
|
|
|
||||||
|
|
@ -100,8 +100,8 @@ impl CodeItem {
|
||||||
addresses.push(try_.start_addr);
|
addresses.push(try_.start_addr);
|
||||||
addresses.push(try_.start_addr + try_.insn_count as u32);
|
addresses.push(try_.start_addr + try_.insn_count as u32);
|
||||||
}
|
}
|
||||||
if let Some(handlers) = &self.handlers {
|
for handler in &self.handlers {
|
||||||
for catch in &handlers.list {
|
for catch in &handler.list {
|
||||||
for EncodedTypeAddrPair {
|
for EncodedTypeAddrPair {
|
||||||
addr: Uleb128(addr),
|
addr: Uleb128(addr),
|
||||||
..
|
..
|
||||||
|
|
@ -977,9 +977,7 @@ mod test {
|
||||||
}
|
}
|
||||||
.serialize_to_vec()
|
.serialize_to_vec()
|
||||||
.unwrap(),
|
.unwrap(),
|
||||||
vec![
|
vec![0x03, 0xb4, 0x01, 0x80, 0x02, 0xc7, 0x08, 0x8e, 0x02, 0xd9, 0x09, 0x87, 0x02,]
|
||||||
0x03, 0xb4, 0x01, 0x80, 0x02, 0xc7, 0x08, 0x8e, 0x02, 0xd9, 0x09, 0x87, 0x02,
|
|
||||||
]
|
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
EncodedCatchHandler {
|
EncodedCatchHandler {
|
||||||
|
|
@ -1001,9 +999,7 @@ mod test {
|
||||||
}
|
}
|
||||||
.serialize_to_vec()
|
.serialize_to_vec()
|
||||||
.unwrap(),
|
.unwrap(),
|
||||||
vec![
|
vec![0x03, 0xe9, 0x46, 0x56, 0xc7, 0x08, 0x8e, 0x02, 0xd9, 0x09, 0x87, 0x02,]
|
||||||
0x03, 0xe9, 0x46, 0x56, 0xc7, 0x08, 0x8e, 0x02, 0xd9, 0x09, 0x87, 0x02,
|
|
||||||
]
|
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
EncodedCatchHandler {
|
EncodedCatchHandler {
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,13 @@
|
||||||
//! Hidden api items.
|
//! Hidden api items.
|
||||||
|
|
||||||
|
use crate as androscalpel_serializer;
|
||||||
use crate::{Error, ReadSeek, Result, Serializable, Uleb128};
|
use crate::{Error, ReadSeek, Result, Serializable, Uleb128};
|
||||||
use std::io::{Cursor, Seek, SeekFrom, Write};
|
use std::io::{Cursor, Write};
|
||||||
|
|
||||||
/// <https://source.android.com/docs/core/runtime/dex-format#hiddenapi-class-data-item>
|
/// <https://source.android.com/docs/core/runtime/dex-format#hiddenapi-class-data-item>
|
||||||
/// Hard to serialize/deserialize without additional data like the number of classes defs
|
/// Hard to serialize/deserialize without additional data like the number of classes
|
||||||
/// or the method/field of the classes.
|
/// or the method/field of the classes.
|
||||||
#[derive(Clone, PartialEq, Eq, Debug)]
|
#[derive(Clone, PartialEq, Eq)]
|
||||||
pub struct HiddenapiClassDataItem {
|
pub struct HiddenapiClassDataItem {
|
||||||
//pub size: u32,
|
//pub size: u32,
|
||||||
pub data: Vec<u8>,
|
pub data: Vec<u8>,
|
||||||
|
|
@ -17,7 +18,7 @@ impl HiddenapiClassDataItem {
|
||||||
self.data.len() as u32
|
self.data.len() as u32
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Return `hiddenapi_class_data_item.offsets[class_def_item_idx]`.
|
/// Return `hiddenapi_class_data_item.offsets[class_idx]`.
|
||||||
///
|
///
|
||||||
/// If `0`: Either no data for this class or all API flags are zero.
|
/// If `0`: Either no data for this class or all API flags are zero.
|
||||||
/// Else: offset from the begining of the [`HiddenapiClassDataItem`]
|
/// Else: offset from the begining of the [`HiddenapiClassDataItem`]
|
||||||
|
|
@ -25,17 +26,13 @@ impl HiddenapiClassDataItem {
|
||||||
///
|
///
|
||||||
/// # Warning
|
/// # Warning
|
||||||
///
|
///
|
||||||
/// `class_def_item_idx` is **NOT** `class_idx` (Contrary to what
|
/// They are no check weither the `class_idx` is valid one or not.
|
||||||
/// <https://source.android.com/docs/core/runtime/dex-format#hiddenapi-class-data-item> says).
|
/// Giving an invalid idx (like an idx >= nb class) is UB.
|
||||||
/// `class_def_item_idx` is the index of the `class_def_item`.
|
pub fn get_offset(&self, class_idx: u32) -> Result<u32> {
|
||||||
///
|
let index = (class_idx as usize) * 4;
|
||||||
/// They are no check weither the `class_def_item_idx` is valid one or not.
|
if self.data.len() < index - 4 {
|
||||||
/// Giving an invalid idx (like an idx >= nb class def) is UB.
|
|
||||||
pub fn get_offset(&self, class_def_item_idx: usize) -> Result<u32> {
|
|
||||||
let index = class_def_item_idx * 4;
|
|
||||||
if self.data.len() <= index {
|
|
||||||
Err(Error::InconsistantStruct(format!(
|
Err(Error::InconsistantStruct(format!(
|
||||||
"class index 0x{class_def_item_idx:x} out of bound of HiddenapiClassDataItem data"
|
"class index 0x{class_idx:x} out of bound of HiddenapiClassDataItem data"
|
||||||
)))
|
)))
|
||||||
} else {
|
} else {
|
||||||
u32::deserialize_from_slice(&self.data[index..])
|
u32::deserialize_from_slice(&self.data[index..])
|
||||||
|
|
@ -47,7 +44,7 @@ impl HiddenapiClassDataItem {
|
||||||
/// # Warning
|
/// # Warning
|
||||||
///
|
///
|
||||||
/// They are no check weither the `nb_class` is valid one or not.
|
/// They are no check weither the `nb_class` is valid one or not.
|
||||||
/// Giving an invalid `nb_class` is UB.
|
/// Giving an invalid `nb_class`.
|
||||||
pub fn get_offsets(&self, nb_class: u32) -> Result<Vec<u32>> {
|
pub fn get_offsets(&self, nb_class: u32) -> Result<Vec<u32>> {
|
||||||
let mut offsets = vec![];
|
let mut offsets = vec![];
|
||||||
let mut buffer = Cursor::new(self.data.as_slice());
|
let mut buffer = Cursor::new(self.data.as_slice());
|
||||||
|
|
@ -63,37 +60,19 @@ impl HiddenapiClassDataItem {
|
||||||
///
|
///
|
||||||
/// # Warning
|
/// # Warning
|
||||||
///
|
///
|
||||||
/// `class_def_item_idx` is **NOT** `class_idx` (Contrary to what
|
/// They are no check weither the `nb_flags` or `offset`
|
||||||
/// <https://source.android.com/docs/core/runtime/dex-format#hiddenapi-class-data-item> says).
|
|
||||||
/// `class_def_item_idx` is the index of the `class_def_item`.
|
|
||||||
///
|
|
||||||
/// They are no check weither the `nb_flags` or `class_def_item_idx`
|
|
||||||
/// are valid. Providing invalid values is UB.
|
/// are valid. Providing invalid values is UB.
|
||||||
pub fn get_flags(
|
pub fn get_flags(&self, nb_flags: usize, offset: u32) -> Result<Vec<Uleb128>> {
|
||||||
&self,
|
|
||||||
nb_flags: usize,
|
|
||||||
class_def_item_idx: usize,
|
|
||||||
) -> Result<Vec<HiddenApiFlags>> {
|
|
||||||
let offset = self.get_offset(class_def_item_idx)?;
|
|
||||||
if offset == 0 {
|
if offset == 0 {
|
||||||
Ok(vec![HiddenApiFlags::DEFAULT; nb_flags])
|
Ok(vec![Uleb128(0); nb_flags])
|
||||||
} else if offset < 4 {
|
} else if offset < 4 {
|
||||||
// < 8 is almost certainly false
|
// < 8 is almost certainly false
|
||||||
panic!()
|
panic!()
|
||||||
} else {
|
} else {
|
||||||
let mut buffer = Cursor::new(self.data.as_slice());
|
let mut buffer = Cursor::new(self.data.as_slice());
|
||||||
buffer
|
|
||||||
.seek(SeekFrom::Start(offset as u64 - 4))
|
|
||||||
.map_err(|_| {
|
|
||||||
Error::InconsistantStruct(format!(
|
|
||||||
"{offset} if out of data bound for HiddenApiClassData"
|
|
||||||
))
|
|
||||||
})?;
|
|
||||||
let mut flags = vec![];
|
let mut flags = vec![];
|
||||||
for _ in 0..nb_flags {
|
for _ in 0..nb_flags {
|
||||||
flags.push(HiddenApiFlags::from_uleb128(Uleb128::deserialize(
|
flags.push(Uleb128::deserialize(&mut buffer)?);
|
||||||
&mut buffer,
|
|
||||||
)?));
|
|
||||||
}
|
}
|
||||||
Ok(flags)
|
Ok(flags)
|
||||||
}
|
}
|
||||||
|
|
@ -123,139 +102,40 @@ impl Serializable for HiddenapiClassDataItem {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, PartialEq, Eq)]
|
/// Flags for hidden api
|
||||||
pub struct HiddenApiFlags {
|
#[derive(Serializable, Debug, PartialEq, Eq, Copy, Clone)]
|
||||||
pub value: HiddenApiValue,
|
#[prefix_type(Uleb128)]
|
||||||
pub domain_api: HiddenApiDomain,
|
pub enum HiddenApiFlag {
|
||||||
}
|
|
||||||
|
|
||||||
impl HiddenApiFlags {
|
|
||||||
pub const DEFAULT: Self = Self {
|
|
||||||
value: HiddenApiValue::Whitelist,
|
|
||||||
domain_api: HiddenApiDomain {
|
|
||||||
is_test_api: false,
|
|
||||||
is_core_platform_api: false,
|
|
||||||
unknown_flags: 0,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
pub fn from_uleb128(Uleb128(val): Uleb128) -> Self {
|
|
||||||
Self {
|
|
||||||
value: HiddenApiValue::from_u32(val),
|
|
||||||
domain_api: HiddenApiDomain::from_u32(val),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn to_uleb128(&self) -> Uleb128 {
|
|
||||||
Uleb128(self.value.to_u32() | self.domain_api.to_u32())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Flags for hidden api flag value
|
|
||||||
#[derive(Debug, PartialEq, Eq, Copy, Clone)]
|
|
||||||
pub enum HiddenApiValue {
|
|
||||||
/// Interfaces that can be freely used and are supported as
|
/// Interfaces that can be freely used and are supported as
|
||||||
/// part of the officially documented Android framework Package Index
|
/// part of the officially documented Android framework Package Index
|
||||||
|
#[prefix(Uleb128(0x00))]
|
||||||
Whitelist,
|
Whitelist,
|
||||||
/// Non-SDK interfaces that can be used regardless of the
|
/// Non-SDK interfaces that can be used regardless of the
|
||||||
/// application's target API level
|
/// application's target API level
|
||||||
|
#[prefix(Uleb128(0x01))]
|
||||||
Greylist,
|
Greylist,
|
||||||
/// Non-SDK interfaces that cannot be used regardless of the
|
/// Non-SDK interfaces that cannot be used regardless of the
|
||||||
/// application's target API level. Accessing one of these
|
/// application's target API level. Accessing one of these
|
||||||
/// interfaces causes a runtime error.
|
/// interfaces causes a runtime error.
|
||||||
|
#[prefix(Uleb128(0x02))]
|
||||||
Blacklist,
|
Blacklist,
|
||||||
/// Non-SDK interfaces that can be used for Android 8.x and
|
/// Non-SDK interfaces that can be used for Android 8.x and
|
||||||
/// below unless they are restricted (targetSdkVersion <= 27 (O_MR1)).
|
/// below unless they are restricted.
|
||||||
|
#[prefix(Uleb128(0x03))]
|
||||||
GreylistMaxO,
|
GreylistMaxO,
|
||||||
/// Non-SDK interfaces that can be used for Android 9.x unless
|
/// Non-SDK interfaces that can be used for Android 9.x unless
|
||||||
/// they are restricted.
|
/// they are restricted.
|
||||||
|
#[prefix(Uleb128(0x04))]
|
||||||
GreylistMaxP,
|
GreylistMaxP,
|
||||||
/// Non-SDK interfaces that can be used for Android 10.x unless
|
/// Non-SDK interfaces that can be used for Android 10.x unless
|
||||||
/// they are restricted.
|
/// they are restricted.
|
||||||
|
#[prefix(Uleb128(0x05))]
|
||||||
GreylistMaxQ,
|
GreylistMaxQ,
|
||||||
/// Non-SDK interfaces that can be used for Android 11.x unless
|
/// Non-SDK interfaces that can be used for Android 11.x unless
|
||||||
/// they are restricted.
|
/// they are restricted.
|
||||||
|
#[prefix(Uleb128(0x06))]
|
||||||
GreylistMaxR,
|
GreylistMaxR,
|
||||||
GreylistMaxS,
|
|
||||||
/// Unknown flag, either an error or this crate is out of date.
|
/// Unknown flag, either an error or this crate is out of date.
|
||||||
Unknwon(u8),
|
#[default_variant]
|
||||||
}
|
Unknwon(Uleb128),
|
||||||
|
|
||||||
impl HiddenApiValue {
|
|
||||||
pub const MASK: u32 = 0b111;
|
|
||||||
pub fn from_u32(flags: u32) -> Self {
|
|
||||||
match flags & Self::MASK {
|
|
||||||
0x00 => Self::Whitelist,
|
|
||||||
0x01 => Self::Greylist,
|
|
||||||
0x02 => Self::Blacklist,
|
|
||||||
0x03 => Self::GreylistMaxO,
|
|
||||||
0x04 => Self::GreylistMaxP,
|
|
||||||
0x05 => Self::GreylistMaxQ,
|
|
||||||
0x06 => Self::GreylistMaxR,
|
|
||||||
0x07 => Self::GreylistMaxS,
|
|
||||||
other => Self::Unknwon(other as u8),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
pub fn to_u32(&self) -> u32 {
|
|
||||||
match self {
|
|
||||||
Self::Whitelist => 0x00,
|
|
||||||
Self::Greylist => 0x01,
|
|
||||||
Self::Blacklist => 0x02,
|
|
||||||
Self::GreylistMaxO => 0x03,
|
|
||||||
Self::GreylistMaxP => 0x04,
|
|
||||||
Self::GreylistMaxQ => 0x05,
|
|
||||||
Self::GreylistMaxR => 0x06,
|
|
||||||
Self::GreylistMaxS => 0x07,
|
|
||||||
Self::Unknwon(other) => {
|
|
||||||
if (0b1111_1000 & *other) != 0 {
|
|
||||||
panic!("HiddenApiValue is encoded on 3 bits but found value {other}");
|
|
||||||
}
|
|
||||||
*other as u32
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Flags for hidden api flag domain
|
|
||||||
#[derive(Debug, PartialEq, Eq, Copy, Clone)]
|
|
||||||
pub struct HiddenApiDomain {
|
|
||||||
pub is_core_platform_api: bool,
|
|
||||||
pub is_test_api: bool,
|
|
||||||
pub unknown_flags: u32,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl HiddenApiDomain {
|
|
||||||
pub const CORE_PLATFORM_API_FLAG: u32 = 0x08;
|
|
||||||
pub const TEST_API_FLAG: u32 = 0x10;
|
|
||||||
pub fn from_u32(flags: u32) -> Self {
|
|
||||||
let flags = flags & !HiddenApiValue::MASK;
|
|
||||||
Self {
|
|
||||||
is_core_platform_api: (flags & Self::CORE_PLATFORM_API_FLAG) != 0,
|
|
||||||
is_test_api: (flags & Self::TEST_API_FLAG) != 0,
|
|
||||||
unknown_flags: flags & (!Self::CORE_PLATFORM_API_FLAG) & (!Self::TEST_API_FLAG),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
pub fn to_u32(&self) -> u32 {
|
|
||||||
if ((0b111 | Self::CORE_PLATFORM_API_FLAG | Self::CORE_PLATFORM_API_FLAG)
|
|
||||||
& self.unknown_flags)
|
|
||||||
!= 0
|
|
||||||
{
|
|
||||||
panic!(
|
|
||||||
"The first 5 bits of HiddenApiDomain are reserved and \
|
|
||||||
shoud be set to 0, but value 0x{:x} was found",
|
|
||||||
self.unknown_flags
|
|
||||||
);
|
|
||||||
}
|
|
||||||
self.unknown_flags
|
|
||||||
| if self.is_core_platform_api {
|
|
||||||
Self::CORE_PLATFORM_API_FLAG
|
|
||||||
} else {
|
|
||||||
0
|
|
||||||
}
|
|
||||||
| if self.is_test_api {
|
|
||||||
Self::TEST_API_FLAG
|
|
||||||
} else {
|
|
||||||
0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -71,36 +71,6 @@ pub enum MapItemType {
|
||||||
UnkownType(u16),
|
UnkownType(u16),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl MapItemType {
|
|
||||||
/// If data of this type is stored in the data section
|
|
||||||
pub fn is_data_section_type(&self) -> bool {
|
|
||||||
match self {
|
|
||||||
Self::HeaderItem => false,
|
|
||||||
Self::StringIdItem => false,
|
|
||||||
Self::TypeIdItem => false,
|
|
||||||
Self::ProtoIdItem => false,
|
|
||||||
Self::FieldIdItem => false,
|
|
||||||
Self::MethodIdItem => false,
|
|
||||||
Self::ClassDefItem => false,
|
|
||||||
Self::CallSiteIdItem => true,
|
|
||||||
Self::MethodHandleItem => true,
|
|
||||||
Self::MapList => true,
|
|
||||||
Self::TypeList => true,
|
|
||||||
Self::AnnotationSetRefList => true,
|
|
||||||
Self::AnnotationSetItem => true,
|
|
||||||
Self::ClassDataItem => true,
|
|
||||||
Self::CodeItem => true,
|
|
||||||
Self::StringDataItem => true,
|
|
||||||
Self::DebugInfoItem => true,
|
|
||||||
Self::AnnotationItem => true,
|
|
||||||
Self::EncodedArrayItem => true,
|
|
||||||
Self::AnnotationsDirectoryItem => true,
|
|
||||||
Self::HiddenapiClassDataItem => true,
|
|
||||||
Self::UnkownType(_) => true, // Most likely
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl MapList {
|
impl MapList {
|
||||||
/// The size field of a MapList.
|
/// The size field of a MapList.
|
||||||
pub fn size_field(&self) -> u32 {
|
pub fn size_field(&self) -> u32 {
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,7 @@
|
||||||
[package]
|
[package]
|
||||||
name = "androscalpel_serializer_derive"
|
name = "androscalpel_serializer_derive"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
edition = "2024"
|
edition = "2021"
|
||||||
license = "AGPL-3.0-or-later"
|
|
||||||
|
|
||||||
[lib]
|
[lib]
|
||||||
proc-macro = true
|
proc-macro = true
|
||||||
|
|
|
||||||
|
|
@ -3,8 +3,8 @@ use quote::{format_ident, quote, quote_spanned};
|
||||||
use syn::parse::{Parse, ParseStream};
|
use syn::parse::{Parse, ParseStream};
|
||||||
use syn::spanned::Spanned;
|
use syn::spanned::Spanned;
|
||||||
use syn::{
|
use syn::{
|
||||||
Attribute, Data, DeriveInput, Field, Fields, Ident, Index, Meta, MetaList, Token, Type,
|
parse_macro_input, Attribute, Data, DeriveInput, Field, Fields, Ident, Index, Meta, MetaList,
|
||||||
Variant, parse_macro_input,
|
Token, Type, Variant,
|
||||||
};
|
};
|
||||||
|
|
||||||
/// Derive the type Serializable.
|
/// Derive the type Serializable.
|
||||||
|
|
@ -55,7 +55,7 @@ use syn::{
|
||||||
/// An enum can define ONE variant as the default variant. This variant is a catch all,
|
/// An enum can define ONE variant as the default variant. This variant is a catch all,
|
||||||
/// and MUST be have named field or unnamed field (no unit variant!) and:
|
/// and MUST be have named field or unnamed field (no unit variant!) and:
|
||||||
/// - The first field of named fields variant must be named `prefix` and of the type
|
/// - The first field of named fields variant must be named `prefix` and of the type
|
||||||
/// `prefix_type`.
|
/// `prefix_type`.
|
||||||
/// - The first field of unnamed fields variant must be of the type `prefix_type`.
|
/// - The first field of unnamed fields variant must be of the type `prefix_type`.
|
||||||
///
|
///
|
||||||
/// The first field of the default variant store the prefix of the variant.
|
/// The first field of the default variant store the prefix of the variant.
|
||||||
|
|
@ -318,25 +318,19 @@ fn get_enum_match(variant: &Variant) -> TokenStream {
|
||||||
/// for a specific field `f` accessible using `field_ref`.
|
/// for a specific field `f` accessible using `field_ref`.
|
||||||
fn get_implem_size_for_field(f: &Field, field_ref: TokenStream) -> TokenStream {
|
fn get_implem_size_for_field(f: &Field, field_ref: TokenStream) -> TokenStream {
|
||||||
let params = ParamsField::parse(&f.attrs);
|
let params = ParamsField::parse(&f.attrs);
|
||||||
let prefix_stream = match params.prefix {
|
let prefix_stream = if let Some(PrefixParamsField(ref stream)) = params.prefix {
|
||||||
Some(PrefixParamsField(ref stream)) => {
|
quote_spanned! { f.span() =>
|
||||||
quote_spanned! { f.span() =>
|
#stream.iter().collect::<Vec<_>>().len()
|
||||||
#stream.iter().collect::<Vec<_>>().len()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_ => {
|
|
||||||
quote_spanned! { f.span() => 0 }
|
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
quote_spanned! { f.span() => 0 }
|
||||||
};
|
};
|
||||||
let suffix_stream = match params.suffix {
|
let suffix_stream = if let Some(SuffixParamsField(ref stream)) = params.suffix {
|
||||||
Some(SuffixParamsField(ref stream)) => {
|
quote_spanned! { f.span() =>
|
||||||
quote_spanned! { f.span() =>
|
#stream.iter().collect::<Vec<_>>().len()
|
||||||
#stream.iter().collect::<Vec<_>>().len()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_ => {
|
|
||||||
quote_spanned! { f.span() => 0 }
|
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
quote_spanned! { f.span() => 0 }
|
||||||
};
|
};
|
||||||
let main_stream = match (&f.ty, params) {
|
let main_stream = match (&f.ty, params) {
|
||||||
(
|
(
|
||||||
|
|
@ -442,29 +436,23 @@ fn get_implem_size(data: &Data, params: &ParamsStruct) -> TokenStream {
|
||||||
fn get_implem_serialize_for_field(f: &Field, field_ref: TokenStream) -> TokenStream {
|
fn get_implem_serialize_for_field(f: &Field, field_ref: TokenStream) -> TokenStream {
|
||||||
let params = ParamsField::parse(&f.attrs);
|
let params = ParamsField::parse(&f.attrs);
|
||||||
// TODO: Improve error handling
|
// TODO: Improve error handling
|
||||||
let prefix_stream = match params.prefix {
|
let prefix_stream = if let Some(PrefixParamsField(ref stream)) = params.prefix {
|
||||||
Some(PrefixParamsField(ref stream)) => {
|
quote_spanned! { f.span() =>
|
||||||
quote_spanned! { f.span() =>
|
for byte in #stream {
|
||||||
for byte in #stream {
|
<u8 as androscalpel_serializer::Serializable>::serialize(&byte, output)?;
|
||||||
<u8 as androscalpel_serializer::Serializable>::serialize(&byte, output)?;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_ => {
|
} else {
|
||||||
quote_spanned! { f.span() => }
|
quote_spanned! { f.span() => }
|
||||||
}
|
|
||||||
};
|
};
|
||||||
let suffix_stream = match params.suffix {
|
let suffix_stream = if let Some(SuffixParamsField(ref stream)) = params.suffix {
|
||||||
Some(SuffixParamsField(ref stream)) => {
|
quote_spanned! { f.span() =>
|
||||||
quote_spanned! { f.span() =>
|
for byte in #stream {
|
||||||
for byte in #stream {
|
<u8 as androscalpel_serializer::Serializable>::serialize(&byte, output)?;
|
||||||
<u8 as androscalpel_serializer::Serializable>::serialize(&byte, output)?;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_ => {
|
} else {
|
||||||
quote_spanned! { f.span() => }
|
quote_spanned! { f.span() => }
|
||||||
}
|
|
||||||
};
|
};
|
||||||
let main_stream = match (&f.ty, params) {
|
let main_stream = match (&f.ty, params) {
|
||||||
(
|
(
|
||||||
|
|
@ -550,6 +538,7 @@ fn get_implem_serialize(data: &Data, params: &ParamsStruct) -> TokenStream {
|
||||||
"The first field of a named field variant must be named `prefix` and of the type of the prefix of the enum"
|
"The first field of a named field variant must be named `prefix` and of the type of the prefix of the enum"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
let recurse = fields.named.iter().map(|f| {
|
let recurse = fields.named.iter().map(|f| {
|
||||||
let name = &f.ident;
|
let name = &f.ident;
|
||||||
get_implem_serialize_for_field(f, quote! { *#name })
|
get_implem_serialize_for_field(f, quote! { *#name })
|
||||||
|
|
@ -598,37 +587,31 @@ fn get_implem_deserialize_for_field(f: &Field, field_ref: TokenStream) -> TokenS
|
||||||
let params = ParamsField::parse(&f.attrs);
|
let params = ParamsField::parse(&f.attrs);
|
||||||
let ty = &f.ty;
|
let ty = &f.ty;
|
||||||
// TODO: Improve error handling
|
// TODO: Improve error handling
|
||||||
let prefix_stream = match params.prefix {
|
let prefix_stream = if let Some(PrefixParamsField(ref stream)) = params.prefix {
|
||||||
Some(PrefixParamsField(ref stream)) => {
|
quote_spanned! { f.span() =>
|
||||||
quote_spanned! { f.span() =>
|
for byte in #stream {
|
||||||
for byte in #stream {
|
if <u8 as androscalpel_serializer::Serializable>::deserialize(input)? != byte {
|
||||||
if <u8 as androscalpel_serializer::Serializable>::deserialize(input)? != byte {
|
return Err(androscalpel_serializer::Error::DeserializationError(
|
||||||
return Err(androscalpel_serializer::Error::DeserializationError(
|
"Prefix do not match #stream".into()
|
||||||
"Prefix do not match #stream".into()
|
));
|
||||||
));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_ => {
|
} else {
|
||||||
quote_spanned! { f.span() => }
|
quote_spanned! { f.span() => }
|
||||||
}
|
|
||||||
};
|
};
|
||||||
let suffix_stream = match params.suffix {
|
let suffix_stream = if let Some(SuffixParamsField(ref stream)) = params.suffix {
|
||||||
Some(SuffixParamsField(ref stream)) => {
|
quote_spanned! { f.span() =>
|
||||||
quote_spanned! { f.span() =>
|
for byte in #stream {
|
||||||
for byte in #stream {
|
if <u8 as androscalpel_serializer::Serializable>::deserialize(input)? != byte {
|
||||||
if <u8 as androscalpel_serializer::Serializable>::deserialize(input)? != byte {
|
return Err(androscalpel_serializer::Error::DeserializationError(
|
||||||
return Err(androscalpel_serializer::Error::DeserializationError(
|
"Suffix do not match #stream".into()
|
||||||
"Suffix do not match #stream".into()
|
));
|
||||||
));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_ => {
|
} else {
|
||||||
quote_spanned! { f.span() => }
|
quote_spanned! { f.span() => }
|
||||||
}
|
|
||||||
};
|
};
|
||||||
match (ty, params) {
|
match (ty, params) {
|
||||||
(
|
(
|
||||||
|
|
|
||||||
|
|
@ -1,14 +1,11 @@
|
||||||
[package]
|
[package]
|
||||||
name = "apk_frauder"
|
name = "apk_frauder"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
edition = "2024"
|
edition = "2021"
|
||||||
license = "AGPL-3.0-or-later"
|
|
||||||
|
|
||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
androscalpel_serializer = { version = "0.1.0", path = "../androscalpel_serializer" }
|
androscalpel_serializer = { version = "0.1.0", path = "../androscalpel_serializer" }
|
||||||
anyhow = "1.0.98"
|
|
||||||
flate2 = { version = "1.0.28", features = ["rust_backend"] }
|
flate2 = { version = "1.0.28", features = ["rust_backend"] }
|
||||||
log = "0.4.25"
|
|
||||||
rand = "0.8.5"
|
rand = "0.8.5"
|
||||||
|
|
|
||||||
|
|
@ -1,3 +0,0 @@
|
||||||
- replace panic/unwrap/expect with results
|
|
||||||
- tests
|
|
||||||
- write zip with data descriptor
|
|
||||||
|
|
@ -1,117 +0,0 @@
|
||||||
use crate::Signature;
|
|
||||||
use androscalpel_serializer::{ReadSeek, Result, Serializable};
|
|
||||||
use std::io::{SeekFrom, Write};
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
|
||||||
pub enum DataDescriptor {
|
|
||||||
Zip32(DataDescriptor32),
|
|
||||||
Zip64(DataDescriptor64),
|
|
||||||
}
|
|
||||||
|
|
||||||
/// DataDescriptor for 32bit zip format.
|
|
||||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
|
||||||
pub struct DataDescriptor32 {
|
|
||||||
// signature: Option<Signature(0x08074b50)>
|
|
||||||
pub crc_32: u32,
|
|
||||||
pub compressed_size: u32,
|
|
||||||
pub uncompressed_size: u32,
|
|
||||||
/// For reproducibility. This should be true when writting a zip, but can be false when
|
|
||||||
/// reading one. If we want to stick to the original, we need to keep this information.
|
|
||||||
pub use_signature: bool,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// DataDescriptor for 64bit zip format.
|
|
||||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
|
||||||
pub struct DataDescriptor64 {
|
|
||||||
// signature: Option<Signature(0x08074b50)>
|
|
||||||
pub crc_32: u32,
|
|
||||||
pub compressed_size: u64,
|
|
||||||
pub uncompressed_size: u64,
|
|
||||||
/// For reproducibility. This should be true when writting a zip, but can be false when
|
|
||||||
/// reading one. If we want to stick to the original, we need to keep this information.
|
|
||||||
pub use_signature: bool,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl DataDescriptor32 {
|
|
||||||
const SIGNATURE: Signature = Signature(0x08074b50);
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Serializable for DataDescriptor32 {
|
|
||||||
fn serialize(&self, output: &mut dyn Write) -> Result<()> {
|
|
||||||
if self.use_signature {
|
|
||||||
Self::SIGNATURE.serialize(output)?;
|
|
||||||
}
|
|
||||||
self.crc_32.serialize(output)?;
|
|
||||||
self.compressed_size.serialize(output)?;
|
|
||||||
self.uncompressed_size.serialize(output)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn deserialize(input: &mut dyn ReadSeek) -> Result<Self> {
|
|
||||||
let pos = input.stream_position().unwrap(); //TODO
|
|
||||||
let signature = Signature::deserialize(input)?;
|
|
||||||
let use_signature = if signature != Self::SIGNATURE {
|
|
||||||
input.seek(SeekFrom::Start(pos)).unwrap(); //TODO
|
|
||||||
false
|
|
||||||
} else {
|
|
||||||
true
|
|
||||||
};
|
|
||||||
let crc_32 = u32::deserialize(input)?;
|
|
||||||
let compressed_size = u32::deserialize(input)?;
|
|
||||||
let uncompressed_size = u32::deserialize(input)?;
|
|
||||||
Ok(Self {
|
|
||||||
crc_32,
|
|
||||||
compressed_size,
|
|
||||||
uncompressed_size,
|
|
||||||
use_signature,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
fn size(&self) -> usize {
|
|
||||||
12 + if self.use_signature {
|
|
||||||
Self::SIGNATURE.size()
|
|
||||||
} else {
|
|
||||||
0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl DataDescriptor64 {
|
|
||||||
const SIGNATURE: Signature = Signature(0x08074b50);
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Serializable for DataDescriptor64 {
|
|
||||||
fn serialize(&self, output: &mut dyn Write) -> Result<()> {
|
|
||||||
if self.use_signature {
|
|
||||||
Self::SIGNATURE.serialize(output)?;
|
|
||||||
}
|
|
||||||
self.crc_32.serialize(output)?;
|
|
||||||
self.compressed_size.serialize(output)?;
|
|
||||||
self.uncompressed_size.serialize(output)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn deserialize(input: &mut dyn ReadSeek) -> Result<Self> {
|
|
||||||
let pos = input.stream_position().unwrap(); //TODO
|
|
||||||
let signature = Signature::deserialize(input)?;
|
|
||||||
let use_signature = if signature != Self::SIGNATURE {
|
|
||||||
input.seek(SeekFrom::Start(pos)).unwrap(); //TODO
|
|
||||||
false
|
|
||||||
} else {
|
|
||||||
true
|
|
||||||
};
|
|
||||||
let crc_32 = u32::deserialize(input)?;
|
|
||||||
let compressed_size = u64::deserialize(input)?;
|
|
||||||
let uncompressed_size = u64::deserialize(input)?;
|
|
||||||
Ok(Self {
|
|
||||||
crc_32,
|
|
||||||
compressed_size,
|
|
||||||
uncompressed_size,
|
|
||||||
use_signature,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
fn size(&self) -> usize {
|
|
||||||
20 + if self.use_signature {
|
|
||||||
Self::SIGNATURE.size()
|
|
||||||
} else {
|
|
||||||
0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,4 +1,3 @@
|
||||||
use log::warn;
|
|
||||||
use std::io::{SeekFrom, Write};
|
use std::io::{SeekFrom, Write};
|
||||||
|
|
||||||
use crate::compression::CompressionMethod;
|
use crate::compression::CompressionMethod;
|
||||||
|
|
@ -123,7 +122,7 @@ impl Serializable for FileHeader {
|
||||||
let field = ExtraField::deserialize(input);
|
let field = ExtraField::deserialize(input);
|
||||||
|
|
||||||
if let Err(err) = field {
|
if let Err(err) = field {
|
||||||
warn!(
|
println!(
|
||||||
"Failed to parsed extra field in {}: {err:?}",
|
"Failed to parsed extra field in {}: {err:?}",
|
||||||
header.get_name()
|
header.get_name()
|
||||||
);
|
);
|
||||||
|
|
@ -136,15 +135,8 @@ impl Serializable for FileHeader {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if extra_size_read > extra_field_length as usize {
|
if extra_size_read > extra_field_length as usize {
|
||||||
let last_extra = header.extra_field.pop().unwrap();
|
println!("Failed to parse last extra field in {}", header.get_name());
|
||||||
let size = last_extra.size();
|
let size = header.extra_field.pop().unwrap().size();
|
||||||
warn!(
|
|
||||||
"Failed to parse last extra field in {}, last field ({} bytes long) is {} bytes too long",
|
|
||||||
header.get_name(),
|
|
||||||
size,
|
|
||||||
extra_size_read - extra_field_length as usize
|
|
||||||
);
|
|
||||||
//warn!("Forgetting last extra field: {:?}", last_extra);
|
|
||||||
input.seek(SeekFrom::Current(-(size as i64))).unwrap();
|
input.seek(SeekFrom::Current(-(size as i64))).unwrap();
|
||||||
}
|
}
|
||||||
let mut extra_size_read = input.stream_position().unwrap() - extra_field_off;
|
let mut extra_size_read = input.stream_position().unwrap() - extra_field_off;
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,4 @@
|
||||||
use androscalpel_serializer::Serializable;
|
use androscalpel_serializer::Serializable;
|
||||||
use anyhow::{Context, Result};
|
|
||||||
use std::collections::HashMap;
|
|
||||||
use std::env;
|
use std::env;
|
||||||
use std::fs;
|
use std::fs;
|
||||||
use std::fs::File;
|
use std::fs::File;
|
||||||
|
|
@ -11,7 +9,6 @@ use std::process::Command;
|
||||||
pub mod apk_signing_block;
|
pub mod apk_signing_block;
|
||||||
pub mod compression;
|
pub mod compression;
|
||||||
mod cp437;
|
mod cp437;
|
||||||
pub mod data_descriptor;
|
|
||||||
pub mod end_of_central_directory;
|
pub mod end_of_central_directory;
|
||||||
pub mod error;
|
pub mod error;
|
||||||
pub mod extra_fields;
|
pub mod extra_fields;
|
||||||
|
|
@ -20,7 +17,6 @@ pub mod local_file_header;
|
||||||
pub mod zip_reader;
|
pub mod zip_reader;
|
||||||
pub mod zip_writer;
|
pub mod zip_writer;
|
||||||
|
|
||||||
use data_descriptor::DataDescriptor;
|
|
||||||
use extra_fields::{ExtraField, Zip64ExtraField};
|
use extra_fields::{ExtraField, Zip64ExtraField};
|
||||||
use file_header::FileHeader;
|
use file_header::FileHeader;
|
||||||
use local_file_header::LocalFileHeader;
|
use local_file_header::LocalFileHeader;
|
||||||
|
|
@ -72,8 +68,8 @@ pub mod external_file_attributes {
|
||||||
pub struct FileInfo {
|
pub struct FileInfo {
|
||||||
pub local_header: LocalFileHeader,
|
pub local_header: LocalFileHeader,
|
||||||
pub header: FileHeader,
|
pub header: FileHeader,
|
||||||
pub data_descriptor: Option<DataDescriptor>,
|
|
||||||
}
|
}
|
||||||
|
// TODO: support data descriptor (MASK_USE_DATA_DESCRIPTOR)
|
||||||
|
|
||||||
impl FileInfo {
|
impl FileInfo {
|
||||||
pub fn get_name(&self) -> String {
|
pub fn get_name(&self) -> String {
|
||||||
|
|
@ -143,42 +139,55 @@ impl FileInfo {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Replace the dex files in an apk and strip the old signature.
|
/// Replace the dex files a an apk an resigned the apk.
|
||||||
#[allow(clippy::too_many_arguments)]
|
///
|
||||||
pub fn replace_dex_unsigned(
|
/// # Warning
|
||||||
|
///
|
||||||
|
/// For now, only jks keystore are allowed.
|
||||||
|
///
|
||||||
|
/// The `zipalign` and `apksigner` args allow to use a specific version of the
|
||||||
|
/// tools instead of the one in the PATH (if it even exist)
|
||||||
|
pub fn replace_dex(
|
||||||
apk: impl AsRef<Path>,
|
apk: impl AsRef<Path>,
|
||||||
dst: impl AsRef<Path>,
|
dst: impl AsRef<Path>,
|
||||||
dexfiles: &mut [impl Read + Seek],
|
dexfiles: &mut [impl Read + Seek],
|
||||||
additionnal_files: Option<HashMap<String, Option<impl Read + Seek>>>,
|
keystore: impl AsRef<Path>, // TODO enum for handling p11 and generating a random cert
|
||||||
) -> Result<()> {
|
// `keytool -genkey -v -keystore KEYSTORE.jks -keyalg RSA -keysize
|
||||||
let file = File::open(&apk).with_context(|| {
|
// 2048 -validity 10000 -alias ALIAS`
|
||||||
format!(
|
zipalign: Option<impl AsRef<Path>>,
|
||||||
"failed to open file {}",
|
apksigner: Option<impl AsRef<Path>>,
|
||||||
apk.as_ref().to_str().unwrap_or("")
|
) {
|
||||||
)
|
let zipalign = if let Some(path) = &zipalign {
|
||||||
})?;
|
path.as_ref().as_os_str()
|
||||||
let mut apk = ZipFileReader::new(file)?;
|
} else {
|
||||||
let file = File::create(&dst).context("failed to create file")?;
|
"zipalign".as_ref()
|
||||||
|
};
|
||||||
|
let apksigner = if let Some(path) = &apksigner {
|
||||||
|
path.as_ref().as_os_str()
|
||||||
|
} else {
|
||||||
|
"apksigner".as_ref()
|
||||||
|
};
|
||||||
|
let tmp_dir = env::temp_dir().join(format!("apk_frauder_{:x}", rand::random::<u128>()));
|
||||||
|
let unaligned_path = tmp_dir.join("stripped.apk");
|
||||||
|
let aligned_path = tmp_dir.join("aligned.apk");
|
||||||
|
fs::create_dir_all(&tmp_dir).expect("Failed to create temporary directory");
|
||||||
|
|
||||||
|
let file = File::open(apk).expect("failed to open file");
|
||||||
|
let mut apk = ZipFileReader::new(file);
|
||||||
|
let file = File::create(&unaligned_path).expect("failed to create file");
|
||||||
let mut apk_out = ZipFileWriter::new(file, apk.zip64_end_of_central_directory.clone());
|
let mut apk_out = ZipFileWriter::new(file, apk.zip64_end_of_central_directory.clone());
|
||||||
|
|
||||||
let mut file_info_ref = (*apk
|
let mut file_info_ref = (*apk
|
||||||
.get_classes_file_info()
|
.get_classes_file_info()
|
||||||
.first()
|
.first()
|
||||||
.context("No dex file found in apk")?)
|
.expect("No dex file found in apk"))
|
||||||
.clone();
|
.clone();
|
||||||
|
|
||||||
apk.unlink_signature_files();
|
apk.unlink_signature_files();
|
||||||
apk.unlink_bytecode_files();
|
apk.unlink_bytecode_files();
|
||||||
|
|
||||||
if let Some(additionnal_files) = &additionnal_files {
|
|
||||||
apk.files
|
|
||||||
.retain(|file| additionnal_files.get(&file.get_name()).is_none());
|
|
||||||
}
|
|
||||||
|
|
||||||
for f in apk.files.clone() {
|
for f in apk.files.clone() {
|
||||||
apk_out.insert_file_from_zip(f, &mut apk);
|
apk_out.insert_file_from_zip(f, &mut apk);
|
||||||
}
|
}
|
||||||
|
|
||||||
for (i, mut dex) in dexfiles.iter_mut().enumerate() {
|
for (i, mut dex) in dexfiles.iter_mut().enumerate() {
|
||||||
if i == 0 {
|
if i == 0 {
|
||||||
file_info_ref.set_name("classes.dex");
|
file_info_ref.set_name("classes.dex");
|
||||||
|
|
@ -189,63 +198,9 @@ pub fn replace_dex_unsigned(
|
||||||
&mut dex,
|
&mut dex,
|
||||||
file_info_ref.header.clone(),
|
file_info_ref.header.clone(),
|
||||||
Some(file_info_ref.local_header.clone()),
|
Some(file_info_ref.local_header.clone()),
|
||||||
file_info_ref.data_descriptor.clone(),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(mut additionnal_files) = additionnal_files {
|
|
||||||
for (name, data) in &mut additionnal_files {
|
|
||||||
file_info_ref.set_name(name);
|
|
||||||
if let Some(data) = data.as_mut() {
|
|
||||||
apk_out.insert_file(
|
|
||||||
data,
|
|
||||||
file_info_ref.header.clone(),
|
|
||||||
Some(file_info_ref.local_header.clone()),
|
|
||||||
file_info_ref.data_descriptor.clone(),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
apk_out.write_central_directory();
|
apk_out.write_central_directory();
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Replace the dex files in an apk an resigned the apk.
|
|
||||||
///
|
|
||||||
/// # Warning
|
|
||||||
///
|
|
||||||
/// For now, only jks keystore are allowed.
|
|
||||||
///
|
|
||||||
/// The `zipalign` and `apksigner` args allow to use a specific version of the
|
|
||||||
/// tools instead of the one in the PATH (if it even exist)
|
|
||||||
#[allow(clippy::too_many_arguments)]
|
|
||||||
pub fn replace_dex(
|
|
||||||
apk: impl AsRef<Path>,
|
|
||||||
dst: impl AsRef<Path>,
|
|
||||||
dexfiles: &mut [impl Read + Seek],
|
|
||||||
keystore: impl AsRef<Path>, // TODO enum for handling p11 and generating a random cert
|
|
||||||
// `keytool -genkey -v -keystore KEYSTORE.jks -keyalg RSA -keysize
|
|
||||||
// 2048 -validity 10000 -alias ALIAS`
|
|
||||||
zipalign: Option<impl AsRef<Path>>,
|
|
||||||
apksigner: Option<impl AsRef<Path>>,
|
|
||||||
keypassword: Option<&str>,
|
|
||||||
additionnal_files: Option<HashMap<String, Option<impl Read + Seek>>>,
|
|
||||||
) -> Result<()> {
|
|
||||||
let zipalign = match &zipalign {
|
|
||||||
Some(path) => path.as_ref().as_os_str(),
|
|
||||||
_ => "zipalign".as_ref(),
|
|
||||||
};
|
|
||||||
let apksigner = match &apksigner {
|
|
||||||
Some(path) => path.as_ref().as_os_str(),
|
|
||||||
_ => "apksigner".as_ref(),
|
|
||||||
};
|
|
||||||
let tmp_dir = env::temp_dir().join(format!("apk_frauder_{:x}", rand::random::<u128>()));
|
|
||||||
let unaligned_path = tmp_dir.join("stripped.apk");
|
|
||||||
let aligned_path = tmp_dir.join("aligned.apk");
|
|
||||||
fs::create_dir_all(&tmp_dir).context("Failed to create temporary directory")?;
|
|
||||||
|
|
||||||
replace_dex_unsigned(apk, &unaligned_path, dexfiles, additionnal_files)?;
|
|
||||||
|
|
||||||
// TODO: we can probably do that ourself an spare ourself the trouble of finding zipalign
|
// TODO: we can probably do that ourself an spare ourself the trouble of finding zipalign
|
||||||
Command::new(zipalign)
|
Command::new(zipalign)
|
||||||
.arg("-v")
|
.arg("-v")
|
||||||
|
|
@ -254,23 +209,16 @@ pub fn replace_dex(
|
||||||
.arg(unaligned_path.as_os_str())
|
.arg(unaligned_path.as_os_str())
|
||||||
.arg(aligned_path.as_os_str())
|
.arg(aligned_path.as_os_str())
|
||||||
.status()
|
.status()
|
||||||
.context("Failed to run zipalign")?;
|
.unwrap();
|
||||||
|
|
||||||
let mut cmd = Command::new(apksigner);
|
Command::new(apksigner)
|
||||||
let cmd = cmd
|
|
||||||
.arg("sign")
|
.arg("sign")
|
||||||
.arg("--ks")
|
.arg("--ks")
|
||||||
.arg(keystore.as_ref().as_os_str())
|
.arg(keystore.as_ref().as_os_str())
|
||||||
.arg("--out")
|
.arg("--out")
|
||||||
.arg(dst.as_ref().as_os_str())
|
.arg(dst.as_ref().as_os_str())
|
||||||
.arg("--in")
|
.arg(aligned_path.as_os_str())
|
||||||
.arg(aligned_path.as_os_str());
|
.status()
|
||||||
let cmd = if let Some(pwd) = keypassword {
|
.unwrap();
|
||||||
cmd.arg("--ks-pass").arg(format!("pass:{pwd}"))
|
fs::remove_dir_all(tmp_dir).expect("Failled to remove tmp dir");
|
||||||
} else {
|
|
||||||
cmd
|
|
||||||
};
|
|
||||||
cmd.status().context("Failled to run apksigne")?;
|
|
||||||
fs::remove_dir_all(tmp_dir).context("Failled to remove tmp dir")?;
|
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,5 @@
|
||||||
use std::io::{SeekFrom, Write};
|
use std::io::{SeekFrom, Write};
|
||||||
|
|
||||||
use log::warn;
|
|
||||||
|
|
||||||
use crate::compression::CompressionMethod;
|
use crate::compression::CompressionMethod;
|
||||||
use crate::error::Error;
|
use crate::error::Error;
|
||||||
use crate::extra_fields::{ExtraField, GenericExtraField, Zip64ExtraField};
|
use crate::extra_fields::{ExtraField, GenericExtraField, Zip64ExtraField};
|
||||||
|
|
@ -101,9 +99,9 @@ impl Serializable for LocalFileHeader {
|
||||||
let field = ExtraField::deserialize(input);
|
let field = ExtraField::deserialize(input);
|
||||||
|
|
||||||
if let Err(err) = field {
|
if let Err(err) = field {
|
||||||
warn!(
|
println!(
|
||||||
"Failed to parsed extra field in {}: {err:?}",
|
"Failed to parsed extra field in {}: {err:?}",
|
||||||
header.get_name(),
|
header.get_name()
|
||||||
);
|
);
|
||||||
input.seek(SeekFrom::Start(field_off)).unwrap();
|
input.seek(SeekFrom::Start(field_off)).unwrap();
|
||||||
break;
|
break;
|
||||||
|
|
@ -113,32 +111,25 @@ impl Serializable for LocalFileHeader {
|
||||||
header.extra_field.push(field);
|
header.extra_field.push(field);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
let mut failed_last_extra_field_data = None;
|
let mut failed_last_extra_field = false;
|
||||||
if extra_size_read > extra_field_length as usize {
|
if extra_size_read > extra_field_length as usize {
|
||||||
//println!("Failed to parse last extra field in {}", header.get_name());
|
//println!("Failed to parse last extra field in {}", header.get_name());
|
||||||
|
failed_last_extra_field = true;
|
||||||
let size = header.extra_field.pop().unwrap().size();
|
let size = header.extra_field.pop().unwrap().size();
|
||||||
input.seek(SeekFrom::Current(-(size as i64))).unwrap();
|
input.seek(SeekFrom::Current(-(size as i64))).unwrap();
|
||||||
failed_last_extra_field_data =
|
|
||||||
Some((size, extra_size_read - extra_field_length as usize));
|
|
||||||
}
|
}
|
||||||
let mut extra_size_read = input.stream_position().unwrap() - extra_field_off;
|
let mut extra_size_read = input.stream_position().unwrap() - extra_field_off;
|
||||||
while extra_size_read < extra_field_length as u64 {
|
while extra_size_read < extra_field_length as u64 {
|
||||||
header.malformed_extra_field.push(u8::deserialize(input)?);
|
header.malformed_extra_field.push(u8::deserialize(input)?);
|
||||||
extra_size_read += 1;
|
extra_size_read += 1;
|
||||||
}
|
}
|
||||||
if let Some((size, size_diff)) = failed_last_extra_field_data {
|
// If it is not padding from zipalign
|
||||||
// If it is not padding from zipalign
|
if failed_last_extra_field
|
||||||
if header.malformed_extra_field != vec![0]
|
&& header.malformed_extra_field != vec![0]
|
||||||
&& header.malformed_extra_field != vec![0, 0]
|
&& header.malformed_extra_field != vec![0, 0]
|
||||||
&& header.malformed_extra_field != vec![0, 0, 0]
|
&& header.malformed_extra_field != vec![0, 0, 0]
|
||||||
{
|
{
|
||||||
warn!(
|
println!("Failed to parse last extra field in {}", header.get_name());
|
||||||
"Failed to parse last extra field in {}, last field ({} bytes long) is {} bytes too long",
|
|
||||||
header.get_name(),
|
|
||||||
size,
|
|
||||||
size_diff
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
//input.seek(SeekFrom::Start(end_of_extra_field)).unwrap();
|
//input.seek(SeekFrom::Start(end_of_extra_field)).unwrap();
|
||||||
for field in &mut header.extra_field {
|
for field in &mut header.extra_field {
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,7 @@
|
||||||
use apk_frauder::ZipFileReader;
|
use std::env;
|
||||||
//use std::collections::HashMap;
|
|
||||||
//use std::env;
|
|
||||||
use std::fs::File;
|
use std::fs::File;
|
||||||
//use std::io::Cursor;
|
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
/*
|
|
||||||
apk_frauder::replace_dex(
|
apk_frauder::replace_dex(
|
||||||
"app-release.apk",
|
"app-release.apk",
|
||||||
"app-instrumented.apk",
|
"app-instrumented.apk",
|
||||||
|
|
@ -19,9 +15,5 @@ fn main() {
|
||||||
"{}/Android/Sdk/build-tools/34.0.0/apksigner",
|
"{}/Android/Sdk/build-tools/34.0.0/apksigner",
|
||||||
env::var("HOME").expect("$HOME not set")
|
env::var("HOME").expect("$HOME not set")
|
||||||
)),
|
)),
|
||||||
None::<HashMap<String, Option<Cursor<&[u8]>>>>,
|
);
|
||||||
);*/
|
|
||||||
let file = File::open("zagruski.apk").unwrap();
|
|
||||||
let reader = ZipFileReader::new(file).unwrap();
|
|
||||||
println!("{:#?}", &reader.files[..2]);
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,20 +1,13 @@
|
||||||
|
use std::io::{Read, Seek, SeekFrom};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
FileHeader, FileInfo, LocalFileHeader, Signature,
|
apk_signing_block::ApkSigningBlock, apk_signing_block::Magic,
|
||||||
apk_signing_block::ApkSigningBlock,
|
|
||||||
apk_signing_block::Magic,
|
|
||||||
compression::CompressionMethod,
|
|
||||||
data_descriptor::{DataDescriptor, DataDescriptor32, DataDescriptor64},
|
|
||||||
end_of_central_directory::EndCentralDirectory,
|
end_of_central_directory::EndCentralDirectory,
|
||||||
end_of_central_directory::Zip64EndCentralDirectory,
|
end_of_central_directory::Zip64EndCentralDirectory,
|
||||||
end_of_central_directory::Zip64EndCentralDirectoryLocator,
|
end_of_central_directory::Zip64EndCentralDirectoryLocator, general_purpose_flags, FileHeader,
|
||||||
general_purpose_flags,
|
FileInfo, LocalFileHeader, Signature,
|
||||||
};
|
};
|
||||||
use androscalpel_serializer::Serializable;
|
use androscalpel_serializer::Serializable;
|
||||||
use anyhow::{Context, Result, bail};
|
|
||||||
use flate2::read::DeflateDecoder;
|
|
||||||
use log::{info, warn};
|
|
||||||
use std::collections::HashMap;
|
|
||||||
use std::io::{Read, Seek, SeekFrom};
|
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Eq)]
|
#[derive(Debug, PartialEq, Eq)]
|
||||||
pub struct ZipFileReader<T: Read + Seek> {
|
pub struct ZipFileReader<T: Read + Seek> {
|
||||||
|
|
@ -26,37 +19,29 @@ pub struct ZipFileReader<T: Read + Seek> {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T: Read + Seek> ZipFileReader<T> {
|
impl<T: Read + Seek> ZipFileReader<T> {
|
||||||
pub fn new(mut reader: T) -> Result<Self> {
|
pub fn new(mut reader: T) -> Self {
|
||||||
let end_of_central_directory_off =
|
let end_of_central_directory_off =
|
||||||
Self::get_end_of_central_directory_offset(&mut reader)
|
Self::get_end_of_central_directory_offset(&mut reader).unwrap();
|
||||||
.context("end of centrall directory not found, probably not a zip")?;
|
|
||||||
reader
|
reader
|
||||||
.seek(SeekFrom::Start(end_of_central_directory_off))
|
.seek(SeekFrom::Start(end_of_central_directory_off))
|
||||||
.context("Failed to seek to end of central directory")?;
|
.unwrap();
|
||||||
let end_of_central_directory = EndCentralDirectory::deserialize(&mut reader)
|
let end_of_central_directory = EndCentralDirectory::deserialize(&mut reader).unwrap();
|
||||||
.context("Failed to deserialize end of central directory")?;
|
reader
|
||||||
let zip64_end_of_central_directory = if reader
|
|
||||||
.seek(SeekFrom::Start(
|
.seek(SeekFrom::Start(
|
||||||
end_of_central_directory_off - Zip64EndCentralDirectoryLocator::SIZE as u64,
|
end_of_central_directory_off - Zip64EndCentralDirectoryLocator::SIZE as u64,
|
||||||
))
|
))
|
||||||
.is_ok()
|
.unwrap();
|
||||||
{
|
let zip64_ecd_locator = Zip64EndCentralDirectoryLocator::deserialize(&mut reader).ok();
|
||||||
let zip64_ecd_locator = Zip64EndCentralDirectoryLocator::deserialize(&mut reader).ok();
|
let zip64_end_of_central_directory = if let Some(zip64_ecd_locator) = zip64_ecd_locator {
|
||||||
if let Some(zip64_ecd_locator) = zip64_ecd_locator {
|
assert_eq!(
|
||||||
assert_eq!(
|
zip64_ecd_locator.disk_number_of_zip64_end_central_directory_start,
|
||||||
zip64_ecd_locator.disk_number_of_zip64_end_central_directory_start,
|
0
|
||||||
0
|
);
|
||||||
);
|
assert!(zip64_ecd_locator.total_number_of_disks <= 1);
|
||||||
assert!(zip64_ecd_locator.total_number_of_disks <= 1);
|
let zip64_edc_record_off =
|
||||||
let zip64_edc_record_off =
|
zip64_ecd_locator.offset_zip64_end_of_central_directory_record;
|
||||||
zip64_ecd_locator.offset_zip64_end_of_central_directory_record;
|
reader.seek(SeekFrom::Start(zip64_edc_record_off)).unwrap();
|
||||||
reader
|
Zip64EndCentralDirectory::deserialize(&mut reader).ok()
|
||||||
.seek(SeekFrom::Start(zip64_edc_record_off))
|
|
||||||
.context("Failed to seek to end of zip64 central directory")?;
|
|
||||||
Zip64EndCentralDirectory::deserialize(&mut reader).ok()
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
};
|
};
|
||||||
|
|
@ -75,62 +60,36 @@ impl<T: Read + Seek> ZipFileReader<T> {
|
||||||
zip_file
|
zip_file
|
||||||
.data
|
.data
|
||||||
.seek(SeekFrom::Start(zip_file.get_cd_offset()))
|
.seek(SeekFrom::Start(zip_file.get_cd_offset()))
|
||||||
.context("Failed to seek to central directory")?;
|
.unwrap();
|
||||||
|
|
||||||
let mut size_read = 0;
|
let mut size_read = 0;
|
||||||
let cd_size = zip_file.get_cd_size();
|
let cd_size = zip_file.get_cd_size();
|
||||||
while size_read < cd_size {
|
while size_read < cd_size {
|
||||||
let header = FileHeader::deserialize(&mut zip_file.data)
|
let header = FileHeader::deserialize(&mut zip_file.data).unwrap();
|
||||||
.context("Failed to deserialize file header")?;
|
|
||||||
//println!("{:#?}", header);
|
//println!("{:#?}", header);
|
||||||
size_read += header.size() as u64;
|
size_read += header.size() as u64;
|
||||||
let pos_in_dir = zip_file
|
let pos_in_dir = zip_file.data.stream_position().unwrap();
|
||||||
.data
|
|
||||||
.stream_position()
|
|
||||||
.context("Failed to get stream position")?;
|
|
||||||
if header.general_purpose_flags & general_purpose_flags::MASK_ENCRYPTED_CENTRAL_DIR != 0
|
if header.general_purpose_flags & general_purpose_flags::MASK_ENCRYPTED_CENTRAL_DIR != 0
|
||||||
{
|
{
|
||||||
bail!("Central directory encryption not supported");
|
panic!("Central directory encryption not supported");
|
||||||
}
|
}
|
||||||
zip_file
|
zip_file
|
||||||
.data
|
.data
|
||||||
.seek(SeekFrom::Start(header.get_offset_local_header()))
|
.seek(SeekFrom::Start(header.get_offset_local_header()))
|
||||||
.context("Failled to seek to local header")?;
|
.unwrap();
|
||||||
let local_header = LocalFileHeader::deserialize(&mut zip_file.data)
|
let local_header = LocalFileHeader::deserialize(&mut zip_file.data).unwrap();
|
||||||
.context("Failed to deserialize local file header")?;
|
zip_file.data.seek(SeekFrom::Start(pos_in_dir)).unwrap();
|
||||||
let data_descriptor = if (local_header.general_purpose_flags
|
if (local_header.general_purpose_flags
|
||||||
& general_purpose_flags::MASK_USE_DATA_DESCRIPTOR
|
& general_purpose_flags::MASK_USE_DATA_DESCRIPTOR
|
||||||
!= 0)
|
!= 0)
|
||||||
|| (header.general_purpose_flags & general_purpose_flags::MASK_USE_DATA_DESCRIPTOR
|
|| (header.general_purpose_flags & general_purpose_flags::MASK_USE_DATA_DESCRIPTOR
|
||||||
!= 0)
|
!= 0)
|
||||||
{
|
{
|
||||||
warn!("Data Descriptor support is experimental");
|
panic!("Data Descriptor not yet suported");
|
||||||
zip_file
|
}
|
||||||
.data
|
|
||||||
.seek(SeekFrom::Current(header.compressed_size as i64))
|
|
||||||
.context("failed to seek to after the file data")?;
|
|
||||||
if zip_file.zip64_end_of_central_directory.is_some() {
|
|
||||||
Some(DataDescriptor::Zip64(
|
|
||||||
DataDescriptor64::deserialize(&mut zip_file.data)
|
|
||||||
.context("Failed to deserialize data descriptor 64")?,
|
|
||||||
))
|
|
||||||
} else {
|
|
||||||
Some(DataDescriptor::Zip32(
|
|
||||||
DataDescriptor32::deserialize(&mut zip_file.data)
|
|
||||||
.context("Failed to deserialize data descriptor")?,
|
|
||||||
))
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
};
|
|
||||||
zip_file
|
|
||||||
.data
|
|
||||||
.seek(SeekFrom::Start(pos_in_dir))
|
|
||||||
.context("Failed to seek to position in directory")?;
|
|
||||||
zip_file.files.push(FileInfo {
|
zip_file.files.push(FileInfo {
|
||||||
local_header,
|
local_header,
|
||||||
header,
|
header,
|
||||||
data_descriptor,
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
assert_eq!(size_read, cd_size);
|
assert_eq!(size_read, cd_size);
|
||||||
|
|
@ -138,26 +97,24 @@ impl<T: Read + Seek> ZipFileReader<T> {
|
||||||
zip_file
|
zip_file
|
||||||
.data
|
.data
|
||||||
.seek(SeekFrom::Start(zip_file.get_cd_offset() - 16))
|
.seek(SeekFrom::Start(zip_file.get_cd_offset() - 16))
|
||||||
.context("Failed to seek to central directory")?;
|
.unwrap();
|
||||||
let magic =
|
let magic = Magic::deserialize(&mut zip_file.data).unwrap();
|
||||||
Magic::deserialize(&mut zip_file.data).context("Failed to deserialize Magic")?;
|
|
||||||
if magic == ApkSigningBlock::MAGIC {
|
if magic == ApkSigningBlock::MAGIC {
|
||||||
zip_file
|
zip_file
|
||||||
.data
|
.data
|
||||||
.seek(SeekFrom::Start(zip_file.get_cd_offset() - 16 - 8))
|
.seek(SeekFrom::Start(zip_file.get_cd_offset() - 16 - 8))
|
||||||
.context("Failed to seek to central directory")?;
|
.unwrap();
|
||||||
let block_size = u64::deserialize(&mut zip_file.data)
|
let block_size = u64::deserialize(&mut zip_file.data).unwrap();
|
||||||
.context("Failed to deserialize block size")?;
|
|
||||||
zip_file
|
zip_file
|
||||||
.data
|
.data
|
||||||
.seek(SeekFrom::Start(zip_file.get_cd_offset() - block_size - 8))
|
.seek(SeekFrom::Start(zip_file.get_cd_offset() - block_size - 8))
|
||||||
.context("Failed to seek to central directory")?;
|
.unwrap();
|
||||||
|
|
||||||
zip_file.apk_sign_block = ApkSigningBlock::deserialize(&mut zip_file.data).ok();
|
zip_file.apk_sign_block = ApkSigningBlock::deserialize(&mut zip_file.data).ok();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(zip_file)
|
zip_file
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn is_zip64(&self) -> bool {
|
pub fn is_zip64(&self) -> bool {
|
||||||
|
|
@ -216,7 +173,7 @@ impl<T: Read + Seek> ZipFileReader<T> {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_end_of_central_directory_offset(reader: &mut T) -> Option<u64> {
|
pub fn get_end_of_central_directory_offset(reader: &mut T) -> Option<u64> {
|
||||||
let file_size = reader.seek(SeekFrom::End(0)).ok()?;
|
let file_size = reader.seek(SeekFrom::End(0)).unwrap();
|
||||||
let mut sig = Signature::default();
|
let mut sig = Signature::default();
|
||||||
let mut comment_size = 0;
|
let mut comment_size = 0;
|
||||||
while sig != EndCentralDirectory::SIGNATURE {
|
while sig != EndCentralDirectory::SIGNATURE {
|
||||||
|
|
@ -224,8 +181,8 @@ impl<T: Read + Seek> ZipFileReader<T> {
|
||||||
.seek(SeekFrom::End(
|
.seek(SeekFrom::End(
|
||||||
-(EndCentralDirectory::MIN_SIZE as i64) - comment_size,
|
-(EndCentralDirectory::MIN_SIZE as i64) - comment_size,
|
||||||
))
|
))
|
||||||
.ok()?;
|
.unwrap();
|
||||||
sig = Signature::deserialize(reader).ok()?;
|
sig = Signature::deserialize(reader).unwrap();
|
||||||
comment_size += 1;
|
comment_size += 1;
|
||||||
if comment_size > 65536
|
if comment_size > 65536
|
||||||
|| comment_size as usize + EndCentralDirectory::MIN_SIZE > file_size as usize
|
|| comment_size as usize + EndCentralDirectory::MIN_SIZE > file_size as usize
|
||||||
|
|
@ -296,8 +253,8 @@ impl<T: Read + Seek> ZipFileReader<T> {
|
||||||
let mut lst_offset = 0;
|
let mut lst_offset = 0;
|
||||||
for file in files.iter() {
|
for file in files.iter() {
|
||||||
if file.get_offset_local_header() != lst_offset {
|
if file.get_offset_local_header() != lst_offset {
|
||||||
info!(
|
println!(
|
||||||
"Hole in zip before {} between 0x{:x} and 0x{:x}",
|
"Hole before {} between 0x{:x} and 0x{:x}",
|
||||||
file.get_name(),
|
file.get_name(),
|
||||||
lst_offset,
|
lst_offset,
|
||||||
file.get_offset_local_header()
|
file.get_offset_local_header()
|
||||||
|
|
@ -309,8 +266,8 @@ impl<T: Read + Seek> ZipFileReader<T> {
|
||||||
if let Some(apk_sign_block) = &self.apk_sign_block {
|
if let Some(apk_sign_block) = &self.apk_sign_block {
|
||||||
let apk_sb_off = self.get_cd_offset() - apk_sign_block.size() as u64;
|
let apk_sb_off = self.get_cd_offset() - apk_sign_block.size() as u64;
|
||||||
if apk_sb_off != lst_offset {
|
if apk_sb_off != lst_offset {
|
||||||
info!(
|
println!(
|
||||||
"Hole in zip before apk signing block, between 0x{:x} and 0x{:x}",
|
"Hole before apk signing block, between 0x{:x} and 0x{:x}",
|
||||||
lst_offset, apk_sb_off
|
lst_offset, apk_sb_off
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
@ -318,58 +275,21 @@ impl<T: Read + Seek> ZipFileReader<T> {
|
||||||
lst_offset = self.get_cd_offset();
|
lst_offset = self.get_cd_offset();
|
||||||
}
|
}
|
||||||
if self.get_cd_offset() != lst_offset {
|
if self.get_cd_offset() != lst_offset {
|
||||||
info!(
|
println!(
|
||||||
"Hole in zip before central directory between 0x{:x} and 0x{:x}",
|
"Hole before central directory between 0x{:x} and 0x{:x}",
|
||||||
lst_offset,
|
lst_offset,
|
||||||
self.get_cd_offset()
|
self.get_cd_offset()
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_bin(&mut self, offset: u64, size: usize) -> Result<Vec<u8>> {
|
pub fn get_bin(&mut self, offset: u64, size: usize) -> Vec<u8> {
|
||||||
self.data
|
self.data.seek(SeekFrom::Start(offset)).unwrap();
|
||||||
.seek(SeekFrom::Start(offset))
|
let mut data = vec![];
|
||||||
.context("Failed to seek to data")?;
|
|
||||||
let mut data = vec![0u8; size];
|
|
||||||
self.data
|
|
||||||
.read_exact(&mut data)
|
|
||||||
.context("failed to read data")?;
|
|
||||||
/*
|
|
||||||
for _ in 0..size {
|
for _ in 0..size {
|
||||||
data.push(u8::deserialize(&mut self.data).unwrap());
|
data.push(u8::deserialize(&mut self.data).unwrap());
|
||||||
}
|
}
|
||||||
*/
|
data
|
||||||
Ok(data)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn read_file_as_vec(&mut self, name: &str) -> Result<Vec<u8>> {
|
|
||||||
let file = self
|
|
||||||
.get_file_info(name)
|
|
||||||
.with_context(|| format!("Failed to get info for {name}"))?;
|
|
||||||
let offset = file.get_file_offset();
|
|
||||||
let size_c = file.header.compressed_size as usize;
|
|
||||||
let size = file.header.uncompressed_size as usize;
|
|
||||||
let compression_method = file.header.compression_method;
|
|
||||||
let mut data = vec![0u8; size_c];
|
|
||||||
self.data
|
|
||||||
.seek(SeekFrom::Start(offset))
|
|
||||||
.with_context(|| format!("Failed to seek to start of file {name} (at 0x{offset:x})"))?;
|
|
||||||
self.data
|
|
||||||
.read_exact(&mut data)
|
|
||||||
.with_context(|| format!("Failed to read data for file {name}"))?;
|
|
||||||
match compression_method {
|
|
||||||
CompressionMethod::Stored => {}
|
|
||||||
CompressionMethod::Deflated => {
|
|
||||||
let mut decomp_data = vec![0u8; size];
|
|
||||||
let mut deflater = DeflateDecoder::new(&data[..]);
|
|
||||||
deflater
|
|
||||||
.read_exact(&mut decomp_data)
|
|
||||||
.with_context(|| format!("Failed to decompress data for file {name}"))?;
|
|
||||||
data = decomp_data
|
|
||||||
}
|
|
||||||
_ => unimplemented!(),
|
|
||||||
}
|
|
||||||
Ok(data)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_file_info(&self, name: &str) -> Option<&FileInfo> {
|
pub fn get_file_info(&self, name: &str) -> Option<&FileInfo> {
|
||||||
|
|
@ -377,29 +297,10 @@ impl<T: Read + Seek> ZipFileReader<T> {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_classes_file_info(&self) -> Vec<&FileInfo> {
|
pub fn get_classes_file_info(&self) -> Vec<&FileInfo> {
|
||||||
let files_map: HashMap<String, &FileInfo> = self
|
self.files
|
||||||
.files
|
|
||||||
.iter()
|
.iter()
|
||||||
.by_ref()
|
|
||||||
.filter(|&file| match_dexfile_name(&file.get_name()))
|
.filter(|&file| match_dexfile_name(&file.get_name()))
|
||||||
.map(|file| (file.get_name(), file))
|
.collect()
|
||||||
.collect();
|
|
||||||
let mut files = vec![];
|
|
||||||
let mut i = 0;
|
|
||||||
loop {
|
|
||||||
let name = if i == 0 {
|
|
||||||
"classes.dex".into()
|
|
||||||
} else {
|
|
||||||
format!("classes{}.dex", i + 1)
|
|
||||||
};
|
|
||||||
if let Some(file) = files_map.get(&name) {
|
|
||||||
files.push(*file);
|
|
||||||
} else {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
i += 1;
|
|
||||||
}
|
|
||||||
files
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,13 +2,10 @@ use std::io;
|
||||||
use std::io::{Cursor, Read, Seek, SeekFrom, Write};
|
use std::io::{Cursor, Read, Seek, SeekFrom, Write};
|
||||||
|
|
||||||
use crate::compression::CompressionMethod;
|
use crate::compression::CompressionMethod;
|
||||||
use crate::data_descriptor::{DataDescriptor, DataDescriptor32, DataDescriptor64};
|
|
||||||
use crate::end_of_central_directory::{
|
use crate::end_of_central_directory::{
|
||||||
EndCentralDirectory, Zip64EndCentralDirectory, Zip64EndCentralDirectoryLocator,
|
EndCentralDirectory, Zip64EndCentralDirectory, Zip64EndCentralDirectoryLocator,
|
||||||
};
|
};
|
||||||
use crate::{
|
use crate::{general_purpose_flags, FileHeader, FileInfo, LocalFileHeader, ZipFileReader};
|
||||||
general_purpose_flags, ExtraField, FileHeader, FileInfo, LocalFileHeader, ZipFileReader,
|
|
||||||
};
|
|
||||||
use androscalpel_serializer::Serializable;
|
use androscalpel_serializer::Serializable;
|
||||||
use flate2::write::DeflateEncoder;
|
use flate2::write::DeflateEncoder;
|
||||||
use flate2::{Compression, CrcWriter};
|
use flate2::{Compression, CrcWriter};
|
||||||
|
|
@ -66,16 +63,11 @@ impl<T: Write> ZipFileWriter<T> {
|
||||||
file: &mut U,
|
file: &mut U,
|
||||||
mut header: FileHeader,
|
mut header: FileHeader,
|
||||||
local_header: Option<LocalFileHeader>,
|
local_header: Option<LocalFileHeader>,
|
||||||
data_descriptor: Option<DataDescriptor>,
|
|
||||||
) {
|
) {
|
||||||
assert!(header.general_purpose_flags & general_purpose_flags::MASK_ENCRYPTED == 0);
|
assert!(header.general_purpose_flags & general_purpose_flags::MASK_ENCRYPTED == 0);
|
||||||
//assert!(
|
assert!(
|
||||||
// header.general_purpose_flags & general_purpose_flags::MASK_USE_DATA_DESCRIPTOR == 0,
|
header.general_purpose_flags & general_purpose_flags::MASK_USE_DATA_DESCRIPTOR == 0
|
||||||
// "Writing file with data_descriptor is not yet implemented"
|
); // TODO
|
||||||
//); // TODO
|
|
||||||
let mut use_data_descriptor = data_descriptor.is_some()
|
|
||||||
|| ((header.general_purpose_flags & general_purpose_flags::MASK_USE_DATA_DESCRIPTOR)
|
|
||||||
!= 0);
|
|
||||||
assert!(header.general_purpose_flags & general_purpose_flags::MASK_STRONG_ENCRYPTION == 0);
|
assert!(header.general_purpose_flags & general_purpose_flags::MASK_STRONG_ENCRYPTION == 0);
|
||||||
assert!(
|
assert!(
|
||||||
header.general_purpose_flags & general_purpose_flags::MASK_ENCRYPTED_CENTRAL_DIR == 0
|
header.general_purpose_flags & general_purpose_flags::MASK_ENCRYPTED_CENTRAL_DIR == 0
|
||||||
|
|
@ -83,10 +75,9 @@ impl<T: Write> ZipFileWriter<T> {
|
||||||
assert!(
|
assert!(
|
||||||
header.general_purpose_flags
|
header.general_purpose_flags
|
||||||
& (general_purpose_flags::MASK_COMPRESS_OPTION_1
|
& (general_purpose_flags::MASK_COMPRESS_OPTION_1
|
||||||
| general_purpose_flags::MASK_COMPRESS_OPTION_2)
|
| general_purpose_flags::MASK_COMPRESS_OPTION_2
|
||||||
|
| general_purpose_flags::MASK_UTF8_FILENAME)
|
||||||
== 0
|
== 0
|
||||||
|| header.compression_method == CompressionMethod::Deflated
|
|
||||||
|| header.compression_method == CompressionMethod::Stored
|
|
||||||
);
|
);
|
||||||
assert!(
|
assert!(
|
||||||
header.compression_method == CompressionMethod::Deflated
|
header.compression_method == CompressionMethod::Deflated
|
||||||
|
|
@ -94,12 +85,9 @@ impl<T: Write> ZipFileWriter<T> {
|
||||||
);
|
);
|
||||||
let mut local_header = if let Some(header) = local_header {
|
let mut local_header = if let Some(header) = local_header {
|
||||||
assert!(header.general_purpose_flags & general_purpose_flags::MASK_ENCRYPTED == 0);
|
assert!(header.general_purpose_flags & general_purpose_flags::MASK_ENCRYPTED == 0);
|
||||||
//assert!(
|
assert!(
|
||||||
// header.general_purpose_flags & general_purpose_flags::MASK_USE_DATA_DESCRIPTOR == 0,
|
header.general_purpose_flags & general_purpose_flags::MASK_USE_DATA_DESCRIPTOR == 0
|
||||||
// "Writing file with data_descriptor is not yet implemented"
|
); // TODO
|
||||||
//); // TODO
|
|
||||||
use_data_descriptor |=
|
|
||||||
header.general_purpose_flags & general_purpose_flags::MASK_USE_DATA_DESCRIPTOR != 0;
|
|
||||||
assert!(
|
assert!(
|
||||||
header.general_purpose_flags & general_purpose_flags::MASK_STRONG_ENCRYPTION == 0
|
header.general_purpose_flags & general_purpose_flags::MASK_STRONG_ENCRYPTION == 0
|
||||||
);
|
);
|
||||||
|
|
@ -110,29 +98,23 @@ impl<T: Write> ZipFileWriter<T> {
|
||||||
assert!(
|
assert!(
|
||||||
header.general_purpose_flags
|
header.general_purpose_flags
|
||||||
& (general_purpose_flags::MASK_COMPRESS_OPTION_1
|
& (general_purpose_flags::MASK_COMPRESS_OPTION_1
|
||||||
| general_purpose_flags::MASK_COMPRESS_OPTION_2)
|
| general_purpose_flags::MASK_COMPRESS_OPTION_2
|
||||||
|
| general_purpose_flags::MASK_UTF8_FILENAME)
|
||||||
== 0
|
== 0
|
||||||
|| header.compression_method == CompressionMethod::Deflated
|
|
||||||
|| header.compression_method == CompressionMethod::Stored
|
|
||||||
);
|
);
|
||||||
assert!(
|
assert!(
|
||||||
header.compression_method == CompressionMethod::Deflated
|
header.compression_method == CompressionMethod::Deflated
|
||||||
|| header.compression_method == CompressionMethod::Stored
|
|| header.compression_method == CompressionMethod::Stored
|
||||||
);
|
);
|
||||||
let mut general_purpose_flags = header.general_purpose_flags;
|
|
||||||
// We only support options for Deflate parameter and data descriptor
|
|
||||||
if header.compression_method == CompressionMethod::Deflated {
|
|
||||||
general_purpose_flags &= general_purpose_flags::MASK_COMPRESS_OPTION_1
|
|
||||||
| general_purpose_flags::MASK_COMPRESS_OPTION_2;
|
|
||||||
} else {
|
|
||||||
general_purpose_flags = 0
|
|
||||||
}
|
|
||||||
if use_data_descriptor {
|
|
||||||
general_purpose_flags |= general_purpose_flags::MASK_USE_DATA_DESCRIPTOR;
|
|
||||||
}
|
|
||||||
LocalFileHeader {
|
LocalFileHeader {
|
||||||
version_needed_to_extract: header.version_needed_to_extract,
|
version_needed_to_extract: header.version_needed_to_extract,
|
||||||
general_purpose_flags,
|
general_purpose_flags: if header.compression_method == CompressionMethod::Deflated {
|
||||||
|
header.general_purpose_flags
|
||||||
|
& (general_purpose_flags::MASK_COMPRESS_OPTION_1
|
||||||
|
| general_purpose_flags::MASK_COMPRESS_OPTION_2)
|
||||||
|
} else {
|
||||||
|
0
|
||||||
|
},
|
||||||
compression_method: header.compression_method,
|
compression_method: header.compression_method,
|
||||||
last_mod_file_time: header.last_mod_file_time,
|
last_mod_file_time: header.last_mod_file_time,
|
||||||
last_mod_file_data: header.last_mod_file_data,
|
last_mod_file_data: header.last_mod_file_data,
|
||||||
|
|
@ -153,10 +135,6 @@ impl<T: Write> ZipFileWriter<T> {
|
||||||
| general_purpose_flags::MASK_COMPRESS_OPTION_2)
|
| general_purpose_flags::MASK_COMPRESS_OPTION_2)
|
||||||
} else {
|
} else {
|
||||||
0
|
0
|
||||||
} | if use_data_descriptor {
|
|
||||||
general_purpose_flags::MASK_USE_DATA_DESCRIPTOR
|
|
||||||
} else {
|
|
||||||
0
|
|
||||||
},
|
},
|
||||||
compression_method: header.compression_method,
|
compression_method: header.compression_method,
|
||||||
last_mod_file_time: header.last_mod_file_time,
|
last_mod_file_time: header.last_mod_file_time,
|
||||||
|
|
@ -188,17 +166,9 @@ impl<T: Write> ZipFileWriter<T> {
|
||||||
CompressionMethod::Deflated => {
|
CompressionMethod::Deflated => {
|
||||||
// TODO: find a way to do this in place, the compressed data can be large, and storing it
|
// TODO: find a way to do this in place, the compressed data can be large, and storing it
|
||||||
// in memory is bad, but Deflate consume the Reader, so we cannot juste give it self.data
|
// in memory is bad, but Deflate consume the Reader, so we cannot juste give it self.data
|
||||||
let option = match header.general_purpose_flags
|
// TODO: Compression::default -> use flag?
|
||||||
& (general_purpose_flags::MASK_COMPRESS_OPTION_1
|
let mut compressor =
|
||||||
| general_purpose_flags::MASK_COMPRESS_OPTION_2)
|
CrcWriter::new(DeflateEncoder::new(Vec::new(), Compression::default()));
|
||||||
{
|
|
||||||
0b000 => Compression::default(),
|
|
||||||
0b010 => Compression::best(),
|
|
||||||
0b100 => Compression::fast(),
|
|
||||||
0b110 => panic!("Super Fast deflate compression not supported"),
|
|
||||||
flag => panic!("Something is verry wrong: {flag:b}"),
|
|
||||||
};
|
|
||||||
let mut compressor = CrcWriter::new(DeflateEncoder::new(Vec::new(), option));
|
|
||||||
io::copy(file, &mut compressor).unwrap();
|
io::copy(file, &mut compressor).unwrap();
|
||||||
local_header.crc_32 = compressor.crc().sum();
|
local_header.crc_32 = compressor.crc().sum();
|
||||||
compressor.into_inner().finish().unwrap()
|
compressor.into_inner().finish().unwrap()
|
||||||
|
|
@ -216,78 +186,15 @@ impl<T: Write> ZipFileWriter<T> {
|
||||||
header.set_uncompressed_size(local_header.get_uncompressed_size());
|
header.set_uncompressed_size(local_header.get_uncompressed_size());
|
||||||
header.set_offset_local_header(header_offset);
|
header.set_offset_local_header(header_offset);
|
||||||
|
|
||||||
let use_z64 = header
|
// TODO: compression flags are not used right now, set to zero
|
||||||
.extra_field
|
local_header.general_purpose_flags &= !(general_purpose_flags::MASK_COMPRESS_OPTION_1
|
||||||
.iter()
|
| general_purpose_flags::MASK_COMPRESS_OPTION_2);
|
||||||
.any(|f| matches!(f, ExtraField::Zip64(_)));
|
header.general_purpose_flags &= !(general_purpose_flags::MASK_COMPRESS_OPTION_1
|
||||||
|
| general_purpose_flags::MASK_COMPRESS_OPTION_2);
|
||||||
let data_descriptor = if use_data_descriptor {
|
|
||||||
match data_descriptor {
|
|
||||||
None if use_z64 => Some(DataDescriptor::Zip64(DataDescriptor64 {
|
|
||||||
crc_32: local_header.crc_32,
|
|
||||||
compressed_size: local_header.get_compressed_size(),
|
|
||||||
uncompressed_size: local_header.get_uncompressed_size(),
|
|
||||||
use_signature: true,
|
|
||||||
})),
|
|
||||||
None => Some(DataDescriptor::Zip32(DataDescriptor32 {
|
|
||||||
crc_32: local_header.crc_32,
|
|
||||||
compressed_size: local_header.get_compressed_size() as u32,
|
|
||||||
uncompressed_size: local_header.get_uncompressed_size() as u32,
|
|
||||||
use_signature: true,
|
|
||||||
})),
|
|
||||||
Some(DataDescriptor::Zip32(DataDescriptor32 { use_signature, .. })) if use_z64 => {
|
|
||||||
Some(DataDescriptor::Zip64(DataDescriptor64 {
|
|
||||||
crc_32: local_header.crc_32,
|
|
||||||
compressed_size: local_header.get_compressed_size(),
|
|
||||||
uncompressed_size: local_header.get_uncompressed_size(),
|
|
||||||
use_signature,
|
|
||||||
}))
|
|
||||||
}
|
|
||||||
Some(DataDescriptor::Zip64(DataDescriptor64 { use_signature, .. })) if !use_z64 => {
|
|
||||||
Some(DataDescriptor::Zip32(DataDescriptor32 {
|
|
||||||
crc_32: local_header.crc_32,
|
|
||||||
compressed_size: local_header.get_compressed_size() as u32,
|
|
||||||
uncompressed_size: local_header.get_uncompressed_size() as u32,
|
|
||||||
use_signature,
|
|
||||||
}))
|
|
||||||
}
|
|
||||||
Some(DataDescriptor::Zip64(DataDescriptor64 { use_signature, .. })) => {
|
|
||||||
Some(DataDescriptor::Zip64(DataDescriptor64 {
|
|
||||||
crc_32: local_header.crc_32,
|
|
||||||
compressed_size: local_header.get_compressed_size(),
|
|
||||||
uncompressed_size: local_header.get_uncompressed_size(),
|
|
||||||
use_signature,
|
|
||||||
}))
|
|
||||||
}
|
|
||||||
Some(DataDescriptor::Zip32(DataDescriptor32 { use_signature, .. })) => {
|
|
||||||
Some(DataDescriptor::Zip32(DataDescriptor32 {
|
|
||||||
crc_32: local_header.crc_32,
|
|
||||||
compressed_size: local_header.get_compressed_size() as u32,
|
|
||||||
uncompressed_size: local_header.get_uncompressed_size() as u32,
|
|
||||||
use_signature,
|
|
||||||
}))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
};
|
|
||||||
|
|
||||||
match &data_descriptor {
|
|
||||||
Some(DataDescriptor::Zip32(data_descriptor)) => {
|
|
||||||
data_descriptor.serialize(&mut self.data).unwrap();
|
|
||||||
self.current_offset += data_descriptor.size() as u64;
|
|
||||||
}
|
|
||||||
Some(DataDescriptor::Zip64(data_descriptor)) => {
|
|
||||||
data_descriptor.serialize(&mut self.data).unwrap();
|
|
||||||
self.current_offset += data_descriptor.size() as u64;
|
|
||||||
}
|
|
||||||
_ => {}
|
|
||||||
}
|
|
||||||
|
|
||||||
let file_info = FileInfo {
|
let file_info = FileInfo {
|
||||||
local_header,
|
local_header,
|
||||||
header,
|
header,
|
||||||
data_descriptor,
|
|
||||||
};
|
};
|
||||||
self.files.push(file_info);
|
self.files.push(file_info);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue