├── LICENSE ├── README.md ├── examples └── example.py └── python └── kahlo ├── __init__.py ├── android.py ├── jebandroid.py ├── rpc.py ├── scribe.py ├── signature.py ├── translators.py └── velcro.py /LICENSE: -------------------------------------------------------------------------------- 1 | GNU GENERAL PUBLIC LICENSE 2 | Version 3, 29 June 2007 3 | 4 | Copyright (C) 2007 Free Software Foundation, Inc. 5 | Everyone is permitted to copy and distribute verbatim copies 6 | of this license document, but changing it is not allowed. 7 | 8 | Preamble 9 | 10 | The GNU General Public License is a free, copyleft license for 11 | software and other kinds of works. 12 | 13 | The licenses for most software and other practical works are designed 14 | to take away your freedom to share and change the works. By contrast, 15 | the GNU General Public License is intended to guarantee your freedom to 16 | share and change all versions of a program--to make sure it remains free 17 | software for all its users. We, the Free Software Foundation, use the 18 | GNU General Public License for most of our software; it applies also to 19 | any other work released this way by its authors. You can apply it to 20 | your programs, too. 21 | 22 | When we speak of free software, we are referring to freedom, not 23 | price. Our General Public Licenses are designed to make sure that you 24 | have the freedom to distribute copies of free software (and charge for 25 | them if you wish), that you receive source code or can get it if you 26 | want it, that you can change the software or use pieces of it in new 27 | free programs, and that you know you can do these things. 28 | 29 | To protect your rights, we need to prevent others from denying you 30 | these rights or asking you to surrender the rights. Therefore, you have 31 | certain responsibilities if you distribute copies of the software, or if 32 | you modify it: responsibilities to respect the freedom of others. 33 | 34 | For example, if you distribute copies of such a program, whether 35 | gratis or for a fee, you must pass on to the recipients the same 36 | freedoms that you received. You must make sure that they, too, receive 37 | or can get the source code. And you must show them these terms so they 38 | know their rights. 39 | 40 | Developers that use the GNU GPL protect your rights with two steps: 41 | (1) assert copyright on the software, and (2) offer you this License 42 | giving you legal permission to copy, distribute and/or modify it. 43 | 44 | For the developers' and authors' protection, the GPL clearly explains 45 | that there is no warranty for this free software. For both users' and 46 | authors' sake, the GPL requires that modified versions be marked as 47 | changed, so that their problems will not be attributed erroneously to 48 | authors of previous versions. 49 | 50 | Some devices are designed to deny users access to install or run 51 | modified versions of the software inside them, although the manufacturer 52 | can do so. This is fundamentally incompatible with the aim of 53 | protecting users' freedom to change the software. The systematic 54 | pattern of such abuse occurs in the area of products for individuals to 55 | use, which is precisely where it is most unacceptable. Therefore, we 56 | have designed this version of the GPL to prohibit the practice for those 57 | products. If such problems arise substantially in other domains, we 58 | stand ready to extend this provision to those domains in future versions 59 | of the GPL, as needed to protect the freedom of users. 60 | 61 | Finally, every program is threatened constantly by software patents. 62 | States should not allow patents to restrict development and use of 63 | software on general-purpose computers, but in those that do, we wish to 64 | avoid the special danger that patents applied to a free program could 65 | make it effectively proprietary. To prevent this, the GPL assures that 66 | patents cannot be used to render the program non-free. 67 | 68 | The precise terms and conditions for copying, distribution and 69 | modification follow. 70 | 71 | TERMS AND CONDITIONS 72 | 73 | 0. Definitions. 74 | 75 | "This License" refers to version 3 of the GNU General Public License. 76 | 77 | "Copyright" also means copyright-like laws that apply to other kinds of 78 | works, such as semiconductor masks. 79 | 80 | "The Program" refers to any copyrightable work licensed under this 81 | License. Each licensee is addressed as "you". "Licensees" and 82 | "recipients" may be individuals or organizations. 83 | 84 | To "modify" a work means to copy from or adapt all or part of the work 85 | in a fashion requiring copyright permission, other than the making of an 86 | exact copy. The resulting work is called a "modified version" of the 87 | earlier work or a work "based on" the earlier work. 88 | 89 | A "covered work" means either the unmodified Program or a work based 90 | on the Program. 91 | 92 | To "propagate" a work means to do anything with it that, without 93 | permission, would make you directly or secondarily liable for 94 | infringement under applicable copyright law, except executing it on a 95 | computer or modifying a private copy. Propagation includes copying, 96 | distribution (with or without modification), making available to the 97 | public, and in some countries other activities as well. 98 | 99 | To "convey" a work means any kind of propagation that enables other 100 | parties to make or receive copies. Mere interaction with a user through 101 | a computer network, with no transfer of a copy, is not conveying. 102 | 103 | An interactive user interface displays "Appropriate Legal Notices" 104 | to the extent that it includes a convenient and prominently visible 105 | feature that (1) displays an appropriate copyright notice, and (2) 106 | tells the user that there is no warranty for the work (except to the 107 | extent that warranties are provided), that licensees may convey the 108 | work under this License, and how to view a copy of this License. If 109 | the interface presents a list of user commands or options, such as a 110 | menu, a prominent item in the list meets this criterion. 111 | 112 | 1. Source Code. 113 | 114 | The "source code" for a work means the preferred form of the work 115 | for making modifications to it. "Object code" means any non-source 116 | form of a work. 117 | 118 | A "Standard Interface" means an interface that either is an official 119 | standard defined by a recognized standards body, or, in the case of 120 | interfaces specified for a particular programming language, one that 121 | is widely used among developers working in that language. 122 | 123 | The "System Libraries" of an executable work include anything, other 124 | than the work as a whole, that (a) is included in the normal form of 125 | packaging a Major Component, but which is not part of that Major 126 | Component, and (b) serves only to enable use of the work with that 127 | Major Component, or to implement a Standard Interface for which an 128 | implementation is available to the public in source code form. A 129 | "Major Component", in this context, means a major essential component 130 | (kernel, window system, and so on) of the specific operating system 131 | (if any) on which the executable work runs, or a compiler used to 132 | produce the work, or an object code interpreter used to run it. 133 | 134 | The "Corresponding Source" for a work in object code form means all 135 | the source code needed to generate, install, and (for an executable 136 | work) run the object code and to modify the work, including scripts to 137 | control those activities. However, it does not include the work's 138 | System Libraries, or general-purpose tools or generally available free 139 | programs which are used unmodified in performing those activities but 140 | which are not part of the work. For example, Corresponding Source 141 | includes interface definition files associated with source files for 142 | the work, and the source code for shared libraries and dynamically 143 | linked subprograms that the work is specifically designed to require, 144 | such as by intimate data communication or control flow between those 145 | subprograms and other parts of the work. 146 | 147 | The Corresponding Source need not include anything that users 148 | can regenerate automatically from other parts of the Corresponding 149 | Source. 150 | 151 | The Corresponding Source for a work in source code form is that 152 | same work. 153 | 154 | 2. Basic Permissions. 155 | 156 | All rights granted under this License are granted for the term of 157 | copyright on the Program, and are irrevocable provided the stated 158 | conditions are met. This License explicitly affirms your unlimited 159 | permission to run the unmodified Program. The output from running a 160 | covered work is covered by this License only if the output, given its 161 | content, constitutes a covered work. This License acknowledges your 162 | rights of fair use or other equivalent, as provided by copyright law. 163 | 164 | You may make, run and propagate covered works that you do not 165 | convey, without conditions so long as your license otherwise remains 166 | in force. You may convey covered works to others for the sole purpose 167 | of having them make modifications exclusively for you, or provide you 168 | with facilities for running those works, provided that you comply with 169 | the terms of this License in conveying all material for which you do 170 | not control copyright. Those thus making or running the covered works 171 | for you must do so exclusively on your behalf, under your direction 172 | and control, on terms that prohibit them from making any copies of 173 | your copyrighted material outside their relationship with you. 174 | 175 | Conveying under any other circumstances is permitted solely under 176 | the conditions stated below. Sublicensing is not allowed; section 10 177 | makes it unnecessary. 178 | 179 | 3. Protecting Users' Legal Rights From Anti-Circumvention Law. 180 | 181 | No covered work shall be deemed part of an effective technological 182 | measure under any applicable law fulfilling obligations under article 183 | 11 of the WIPO copyright treaty adopted on 20 December 1996, or 184 | similar laws prohibiting or restricting circumvention of such 185 | measures. 186 | 187 | When you convey a covered work, you waive any legal power to forbid 188 | circumvention of technological measures to the extent such circumvention 189 | is effected by exercising rights under this License with respect to 190 | the covered work, and you disclaim any intention to limit operation or 191 | modification of the work as a means of enforcing, against the work's 192 | users, your or third parties' legal rights to forbid circumvention of 193 | technological measures. 194 | 195 | 4. Conveying Verbatim Copies. 196 | 197 | You may convey verbatim copies of the Program's source code as you 198 | receive it, in any medium, provided that you conspicuously and 199 | appropriately publish on each copy an appropriate copyright notice; 200 | keep intact all notices stating that this License and any 201 | non-permissive terms added in accord with section 7 apply to the code; 202 | keep intact all notices of the absence of any warranty; and give all 203 | recipients a copy of this License along with the Program. 204 | 205 | You may charge any price or no price for each copy that you convey, 206 | and you may offer support or warranty protection for a fee. 207 | 208 | 5. Conveying Modified Source Versions. 209 | 210 | You may convey a work based on the Program, or the modifications to 211 | produce it from the Program, in the form of source code under the 212 | terms of section 4, provided that you also meet all of these conditions: 213 | 214 | a) The work must carry prominent notices stating that you modified 215 | it, and giving a relevant date. 216 | 217 | b) The work must carry prominent notices stating that it is 218 | released under this License and any conditions added under section 219 | 7. This requirement modifies the requirement in section 4 to 220 | "keep intact all notices". 221 | 222 | c) You must license the entire work, as a whole, under this 223 | License to anyone who comes into possession of a copy. This 224 | License will therefore apply, along with any applicable section 7 225 | additional terms, to the whole of the work, and all its parts, 226 | regardless of how they are packaged. This License gives no 227 | permission to license the work in any other way, but it does not 228 | invalidate such permission if you have separately received it. 229 | 230 | d) If the work has interactive user interfaces, each must display 231 | Appropriate Legal Notices; however, if the Program has interactive 232 | interfaces that do not display Appropriate Legal Notices, your 233 | work need not make them do so. 234 | 235 | A compilation of a covered work with other separate and independent 236 | works, which are not by their nature extensions of the covered work, 237 | and which are not combined with it such as to form a larger program, 238 | in or on a volume of a storage or distribution medium, is called an 239 | "aggregate" if the compilation and its resulting copyright are not 240 | used to limit the access or legal rights of the compilation's users 241 | beyond what the individual works permit. Inclusion of a covered work 242 | in an aggregate does not cause this License to apply to the other 243 | parts of the aggregate. 244 | 245 | 6. Conveying Non-Source Forms. 246 | 247 | You may convey a covered work in object code form under the terms 248 | of sections 4 and 5, provided that you also convey the 249 | machine-readable Corresponding Source under the terms of this License, 250 | in one of these ways: 251 | 252 | a) Convey the object code in, or embodied in, a physical product 253 | (including a physical distribution medium), accompanied by the 254 | Corresponding Source fixed on a durable physical medium 255 | customarily used for software interchange. 256 | 257 | b) Convey the object code in, or embodied in, a physical product 258 | (including a physical distribution medium), accompanied by a 259 | written offer, valid for at least three years and valid for as 260 | long as you offer spare parts or customer support for that product 261 | model, to give anyone who possesses the object code either (1) a 262 | copy of the Corresponding Source for all the software in the 263 | product that is covered by this License, on a durable physical 264 | medium customarily used for software interchange, for a price no 265 | more than your reasonable cost of physically performing this 266 | conveying of source, or (2) access to copy the 267 | Corresponding Source from a network server at no charge. 268 | 269 | c) Convey individual copies of the object code with a copy of the 270 | written offer to provide the Corresponding Source. This 271 | alternative is allowed only occasionally and noncommercially, and 272 | only if you received the object code with such an offer, in accord 273 | with subsection 6b. 274 | 275 | d) Convey the object code by offering access from a designated 276 | place (gratis or for a charge), and offer equivalent access to the 277 | Corresponding Source in the same way through the same place at no 278 | further charge. You need not require recipients to copy the 279 | Corresponding Source along with the object code. If the place to 280 | copy the object code is a network server, the Corresponding Source 281 | may be on a different server (operated by you or a third party) 282 | that supports equivalent copying facilities, provided you maintain 283 | clear directions next to the object code saying where to find the 284 | Corresponding Source. Regardless of what server hosts the 285 | Corresponding Source, you remain obligated to ensure that it is 286 | available for as long as needed to satisfy these requirements. 287 | 288 | e) Convey the object code using peer-to-peer transmission, provided 289 | you inform other peers where the object code and Corresponding 290 | Source of the work are being offered to the general public at no 291 | charge under subsection 6d. 292 | 293 | A separable portion of the object code, whose source code is excluded 294 | from the Corresponding Source as a System Library, need not be 295 | included in conveying the object code work. 296 | 297 | A "User Product" is either (1) a "consumer product", which means any 298 | tangible personal property which is normally used for personal, family, 299 | or household purposes, or (2) anything designed or sold for incorporation 300 | into a dwelling. In determining whether a product is a consumer product, 301 | doubtful cases shall be resolved in favor of coverage. For a particular 302 | product received by a particular user, "normally used" refers to a 303 | typical or common use of that class of product, regardless of the status 304 | of the particular user or of the way in which the particular user 305 | actually uses, or expects or is expected to use, the product. A product 306 | is a consumer product regardless of whether the product has substantial 307 | commercial, industrial or non-consumer uses, unless such uses represent 308 | the only significant mode of use of the product. 309 | 310 | "Installation Information" for a User Product means any methods, 311 | procedures, authorization keys, or other information required to install 312 | and execute modified versions of a covered work in that User Product from 313 | a modified version of its Corresponding Source. The information must 314 | suffice to ensure that the continued functioning of the modified object 315 | code is in no case prevented or interfered with solely because 316 | modification has been made. 317 | 318 | If you convey an object code work under this section in, or with, or 319 | specifically for use in, a User Product, and the conveying occurs as 320 | part of a transaction in which the right of possession and use of the 321 | User Product is transferred to the recipient in perpetuity or for a 322 | fixed term (regardless of how the transaction is characterized), the 323 | Corresponding Source conveyed under this section must be accompanied 324 | by the Installation Information. But this requirement does not apply 325 | if neither you nor any third party retains the ability to install 326 | modified object code on the User Product (for example, the work has 327 | been installed in ROM). 328 | 329 | The requirement to provide Installation Information does not include a 330 | requirement to continue to provide support service, warranty, or updates 331 | for a work that has been modified or installed by the recipient, or for 332 | the User Product in which it has been modified or installed. Access to a 333 | network may be denied when the modification itself materially and 334 | adversely affects the operation of the network or violates the rules and 335 | protocols for communication across the network. 336 | 337 | Corresponding Source conveyed, and Installation Information provided, 338 | in accord with this section must be in a format that is publicly 339 | documented (and with an implementation available to the public in 340 | source code form), and must require no special password or key for 341 | unpacking, reading or copying. 342 | 343 | 7. Additional Terms. 344 | 345 | "Additional permissions" are terms that supplement the terms of this 346 | License by making exceptions from one or more of its conditions. 347 | Additional permissions that are applicable to the entire Program shall 348 | be treated as though they were included in this License, to the extent 349 | that they are valid under applicable law. If additional permissions 350 | apply only to part of the Program, that part may be used separately 351 | under those permissions, but the entire Program remains governed by 352 | this License without regard to the additional permissions. 353 | 354 | When you convey a copy of a covered work, you may at your option 355 | remove any additional permissions from that copy, or from any part of 356 | it. (Additional permissions may be written to require their own 357 | removal in certain cases when you modify the work.) You may place 358 | additional permissions on material, added by you to a covered work, 359 | for which you have or can give appropriate copyright permission. 360 | 361 | Notwithstanding any other provision of this License, for material you 362 | add to a covered work, you may (if authorized by the copyright holders of 363 | that material) supplement the terms of this License with terms: 364 | 365 | a) Disclaiming warranty or limiting liability differently from the 366 | terms of sections 15 and 16 of this License; or 367 | 368 | b) Requiring preservation of specified reasonable legal notices or 369 | author attributions in that material or in the Appropriate Legal 370 | Notices displayed by works containing it; or 371 | 372 | c) Prohibiting misrepresentation of the origin of that material, or 373 | requiring that modified versions of such material be marked in 374 | reasonable ways as different from the original version; or 375 | 376 | d) Limiting the use for publicity purposes of names of licensors or 377 | authors of the material; or 378 | 379 | e) Declining to grant rights under trademark law for use of some 380 | trade names, trademarks, or service marks; or 381 | 382 | f) Requiring indemnification of licensors and authors of that 383 | material by anyone who conveys the material (or modified versions of 384 | it) with contractual assumptions of liability to the recipient, for 385 | any liability that these contractual assumptions directly impose on 386 | those licensors and authors. 387 | 388 | All other non-permissive additional terms are considered "further 389 | restrictions" within the meaning of section 10. If the Program as you 390 | received it, or any part of it, contains a notice stating that it is 391 | governed by this License along with a term that is a further 392 | restriction, you may remove that term. If a license document contains 393 | a further restriction but permits relicensing or conveying under this 394 | License, you may add to a covered work material governed by the terms 395 | of that license document, provided that the further restriction does 396 | not survive such relicensing or conveying. 397 | 398 | If you add terms to a covered work in accord with this section, you 399 | must place, in the relevant source files, a statement of the 400 | additional terms that apply to those files, or a notice indicating 401 | where to find the applicable terms. 402 | 403 | Additional terms, permissive or non-permissive, may be stated in the 404 | form of a separately written license, or stated as exceptions; 405 | the above requirements apply either way. 406 | 407 | 8. Termination. 408 | 409 | You may not propagate or modify a covered work except as expressly 410 | provided under this License. Any attempt otherwise to propagate or 411 | modify it is void, and will automatically terminate your rights under 412 | this License (including any patent licenses granted under the third 413 | paragraph of section 11). 414 | 415 | However, if you cease all violation of this License, then your 416 | license from a particular copyright holder is reinstated (a) 417 | provisionally, unless and until the copyright holder explicitly and 418 | finally terminates your license, and (b) permanently, if the copyright 419 | holder fails to notify you of the violation by some reasonable means 420 | prior to 60 days after the cessation. 421 | 422 | Moreover, your license from a particular copyright holder is 423 | reinstated permanently if the copyright holder notifies you of the 424 | violation by some reasonable means, this is the first time you have 425 | received notice of violation of this License (for any work) from that 426 | copyright holder, and you cure the violation prior to 30 days after 427 | your receipt of the notice. 428 | 429 | Termination of your rights under this section does not terminate the 430 | licenses of parties who have received copies or rights from you under 431 | this License. If your rights have been terminated and not permanently 432 | reinstated, you do not qualify to receive new licenses for the same 433 | material under section 10. 434 | 435 | 9. Acceptance Not Required for Having Copies. 436 | 437 | You are not required to accept this License in order to receive or 438 | run a copy of the Program. Ancillary propagation of a covered work 439 | occurring solely as a consequence of using peer-to-peer transmission 440 | to receive a copy likewise does not require acceptance. However, 441 | nothing other than this License grants you permission to propagate or 442 | modify any covered work. These actions infringe copyright if you do 443 | not accept this License. Therefore, by modifying or propagating a 444 | covered work, you indicate your acceptance of this License to do so. 445 | 446 | 10. Automatic Licensing of Downstream Recipients. 447 | 448 | Each time you convey a covered work, the recipient automatically 449 | receives a license from the original licensors, to run, modify and 450 | propagate that work, subject to this License. You are not responsible 451 | for enforcing compliance by third parties with this License. 452 | 453 | An "entity transaction" is a transaction transferring control of an 454 | organization, or substantially all assets of one, or subdividing an 455 | organization, or merging organizations. If propagation of a covered 456 | work results from an entity transaction, each party to that 457 | transaction who receives a copy of the work also receives whatever 458 | licenses to the work the party's predecessor in interest had or could 459 | give under the previous paragraph, plus a right to possession of the 460 | Corresponding Source of the work from the predecessor in interest, if 461 | the predecessor has it or can get it with reasonable efforts. 462 | 463 | You may not impose any further restrictions on the exercise of the 464 | rights granted or affirmed under this License. For example, you may 465 | not impose a license fee, royalty, or other charge for exercise of 466 | rights granted under this License, and you may not initiate litigation 467 | (including a cross-claim or counterclaim in a lawsuit) alleging that 468 | any patent claim is infringed by making, using, selling, offering for 469 | sale, or importing the Program or any portion of it. 470 | 471 | 11. Patents. 472 | 473 | A "contributor" is a copyright holder who authorizes use under this 474 | License of the Program or a work on which the Program is based. The 475 | work thus licensed is called the contributor's "contributor version". 476 | 477 | A contributor's "essential patent claims" are all patent claims 478 | owned or controlled by the contributor, whether already acquired or 479 | hereafter acquired, that would be infringed by some manner, permitted 480 | by this License, of making, using, or selling its contributor version, 481 | but do not include claims that would be infringed only as a 482 | consequence of further modification of the contributor version. For 483 | purposes of this definition, "control" includes the right to grant 484 | patent sublicenses in a manner consistent with the requirements of 485 | this License. 486 | 487 | Each contributor grants you a non-exclusive, worldwide, royalty-free 488 | patent license under the contributor's essential patent claims, to 489 | make, use, sell, offer for sale, import and otherwise run, modify and 490 | propagate the contents of its contributor version. 491 | 492 | In the following three paragraphs, a "patent license" is any express 493 | agreement or commitment, however denominated, not to enforce a patent 494 | (such as an express permission to practice a patent or covenant not to 495 | sue for patent infringement). To "grant" such a patent license to a 496 | party means to make such an agreement or commitment not to enforce a 497 | patent against the party. 498 | 499 | If you convey a covered work, knowingly relying on a patent license, 500 | and the Corresponding Source of the work is not available for anyone 501 | to copy, free of charge and under the terms of this License, through a 502 | publicly available network server or other readily accessible means, 503 | then you must either (1) cause the Corresponding Source to be so 504 | available, or (2) arrange to deprive yourself of the benefit of the 505 | patent license for this particular work, or (3) arrange, in a manner 506 | consistent with the requirements of this License, to extend the patent 507 | license to downstream recipients. "Knowingly relying" means you have 508 | actual knowledge that, but for the patent license, your conveying the 509 | covered work in a country, or your recipient's use of the covered work 510 | in a country, would infringe one or more identifiable patents in that 511 | country that you have reason to believe are valid. 512 | 513 | If, pursuant to or in connection with a single transaction or 514 | arrangement, you convey, or propagate by procuring conveyance of, a 515 | covered work, and grant a patent license to some of the parties 516 | receiving the covered work authorizing them to use, propagate, modify 517 | or convey a specific copy of the covered work, then the patent license 518 | you grant is automatically extended to all recipients of the covered 519 | work and works based on it. 520 | 521 | A patent license is "discriminatory" if it does not include within 522 | the scope of its coverage, prohibits the exercise of, or is 523 | conditioned on the non-exercise of one or more of the rights that are 524 | specifically granted under this License. You may not convey a covered 525 | work if you are a party to an arrangement with a third party that is 526 | in the business of distributing software, under which you make payment 527 | to the third party based on the extent of your activity of conveying 528 | the work, and under which the third party grants, to any of the 529 | parties who would receive the covered work from you, a discriminatory 530 | patent license (a) in connection with copies of the covered work 531 | conveyed by you (or copies made from those copies), or (b) primarily 532 | for and in connection with specific products or compilations that 533 | contain the covered work, unless you entered into that arrangement, 534 | or that patent license was granted, prior to 28 March 2007. 535 | 536 | Nothing in this License shall be construed as excluding or limiting 537 | any implied license or other defenses to infringement that may 538 | otherwise be available to you under applicable patent law. 539 | 540 | 12. No Surrender of Others' Freedom. 541 | 542 | If conditions are imposed on you (whether by court order, agreement or 543 | otherwise) that contradict the conditions of this License, they do not 544 | excuse you from the conditions of this License. If you cannot convey a 545 | covered work so as to satisfy simultaneously your obligations under this 546 | License and any other pertinent obligations, then as a consequence you may 547 | not convey it at all. For example, if you agree to terms that obligate you 548 | to collect a royalty for further conveying from those to whom you convey 549 | the Program, the only way you could satisfy both those terms and this 550 | License would be to refrain entirely from conveying the Program. 551 | 552 | 13. Use with the GNU Affero General Public License. 553 | 554 | Notwithstanding any other provision of this License, you have 555 | permission to link or combine any covered work with a work licensed 556 | under version 3 of the GNU Affero General Public License into a single 557 | combined work, and to convey the resulting work. The terms of this 558 | License will continue to apply to the part which is the covered work, 559 | but the special requirements of the GNU Affero General Public License, 560 | section 13, concerning interaction through a network will apply to the 561 | combination as such. 562 | 563 | 14. Revised Versions of this License. 564 | 565 | The Free Software Foundation may publish revised and/or new versions of 566 | the GNU General Public License from time to time. Such new versions will 567 | be similar in spirit to the present version, but may differ in detail to 568 | address new problems or concerns. 569 | 570 | Each version is given a distinguishing version number. If the 571 | Program specifies that a certain numbered version of the GNU General 572 | Public License "or any later version" applies to it, you have the 573 | option of following the terms and conditions either of that numbered 574 | version or of any later version published by the Free Software 575 | Foundation. If the Program does not specify a version number of the 576 | GNU General Public License, you may choose any version ever published 577 | by the Free Software Foundation. 578 | 579 | If the Program specifies that a proxy can decide which future 580 | versions of the GNU General Public License can be used, that proxy's 581 | public statement of acceptance of a version permanently authorizes you 582 | to choose that version for the Program. 583 | 584 | Later license versions may give you additional or different 585 | permissions. However, no additional obligations are imposed on any 586 | author or copyright holder as a result of your choosing to follow a 587 | later version. 588 | 589 | 15. Disclaimer of Warranty. 590 | 591 | THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY 592 | APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT 593 | HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY 594 | OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, 595 | THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 596 | PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM 597 | IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF 598 | ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 599 | 600 | 16. Limitation of Liability. 601 | 602 | IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 603 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS 604 | THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY 605 | GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE 606 | USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF 607 | DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD 608 | PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), 609 | EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF 610 | SUCH DAMAGES. 611 | 612 | 17. Interpretation of Sections 15 and 16. 613 | 614 | If the disclaimer of warranty and limitation of liability provided 615 | above cannot be given local legal effect according to their terms, 616 | reviewing courts shall apply local law that most closely approximates 617 | an absolute waiver of all civil liability in connection with the 618 | Program, unless a warranty or assumption of liability accompanies a 619 | copy of the Program in return for a fee. 620 | 621 | END OF TERMS AND CONDITIONS 622 | 623 | How to Apply These Terms to Your New Programs 624 | 625 | If you develop a new program, and you want it to be of the greatest 626 | possible use to the public, the best way to achieve this is to make it 627 | free software which everyone can redistribute and change under these terms. 628 | 629 | To do so, attach the following notices to the program. It is safest 630 | to attach them to the start of each source file to most effectively 631 | state the exclusion of warranty; and each file should have at least 632 | the "copyright" line and a pointer to where the full notice is found. 633 | 634 | 635 | Copyright (C) 636 | 637 | This program is free software: you can redistribute it and/or modify 638 | it under the terms of the GNU General Public License as published by 639 | the Free Software Foundation, either version 3 of the License, or 640 | (at your option) any later version. 641 | 642 | This program is distributed in the hope that it will be useful, 643 | but WITHOUT ANY WARRANTY; without even the implied warranty of 644 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 645 | GNU General Public License for more details. 646 | 647 | You should have received a copy of the GNU General Public License 648 | along with this program. If not, see . 649 | 650 | Also add information on how to contact you by electronic and paper mail. 651 | 652 | If the program does terminal interaction, make it output a short 653 | notice like this when it starts in an interactive mode: 654 | 655 | Copyright (C) 656 | This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 657 | This is free software, and you are welcome to redistribute it 658 | under certain conditions; type `show c' for details. 659 | 660 | The hypothetical commands `show w' and `show c' should show the appropriate 661 | parts of the General Public License. Of course, your program's commands 662 | might be different; for a GUI interface, you would use an "about box". 663 | 664 | You should also get your employer (if you work as a programmer) or school, 665 | if any, to sign a "copyright disclaimer" for the program, if necessary. 666 | For more information on this, and how to apply and follow the GNU GPL, see 667 | . 668 | 669 | The GNU General Public License does not permit incorporating your program 670 | into proprietary programs. If your program is a subroutine library, you 671 | may consider it more useful to permit linking proprietary applications with 672 | the library. If this is what you want to do, use the GNU Lesser General 673 | Public License instead of this License. But first, please read 674 | . 675 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | kahlo : Higher-level Python interface for frida 2 | =============================================== 3 | 4 | ```python 5 | >>> import frida 6 | >>> import kahlo 7 | 8 | # The Scribe class has an associated script. 9 | >>> class TestScribe(kahlo.Scribe): 10 | ... _SCRIPT = '''console.log("hello world");''' 11 | 12 | # Create a frida session. 13 | >>> session = frida.get_local_device().attach("nano") 14 | 15 | # Create a Scribe subclass associated with the session. 16 | >>> test = TestScribe(session) 17 | 18 | # Bind the Scribe object to the session, causing script to be run on the 19 | # agent. 20 | >>> test.bind() 21 | hello world 22 | 23 | # Bidirectional async RPC 24 | >>> class TestRPC(kahlo.rpc.BaseRPC): 25 | ... @kahlo.rpc.hostcall 26 | ... def host_hello(self, name): 27 | ... print("Hello {}".format(name)) 28 | ... return "nice to meet you" 29 | ... 30 | ... agent_call_host = kahlo.rpc.agentcall(''' 31 | ... async function (name) { 32 | ... var rv = await kahlo.hostcall.host_hello("agent " + name) 33 | ... console.log("host said " + rv); 34 | ... return 42 35 | ... } 36 | ... ''') 37 | >>> rpc = TestRPC(session) 38 | >>> rpc.bind() 39 | >>> await rpc.agentcall.agent_call_host("foo") 40 | Hello agent foo 41 | host said nice to meet you 42 | 42 43 | ``` 44 | 45 | ## Design Rationale 46 | 47 | The main rationale behind kahlo is to facilitate host-centric use of 48 | frida. Most tools and scripts built on frida tend to be more agent-centric; 49 | they are mostly Javascript that runs within the agent (at least based on what 50 | I've seen out there). This is usually fine, and an efficient way of doing 51 | things. But there are situations where this is a problem, usually because 52 | there is some analysis that you want to do, which cannot be done on the 53 | agent, especially if your target is an embedded device. 54 | 55 | For example: 56 | 57 | * You need to access some data over the network/Internet, but your target device cannot 58 | be connected to a network for whatever reason. 59 | 60 | * Your analysis requires coordination over multiple devices, so you need to 61 | consolidate the logic within a central management system on the host. 62 | 63 | * You need to process something beyond the capabilities of the target's CPU or 64 | memory. 65 | 66 | In my specific use case, I needed something that let me interoperate with JEB 67 | (https://www.pnfsoftware.com/jeb/) and other static analysis tools built on 68 | top of that, running on the host. 69 | 70 | As such, kahlo is meant for building tools where the code running on the agent 71 | is usually minimal (and hence suitable for inlining within Python code), just 72 | gathering data and passing it to the host to process. 73 | 74 | ## Features 75 | 76 | Here are some features available in kahlo so far. 77 | 78 | ### Composable Inline Scripts 79 | 80 | The `Scribe` class lets you specify some Javascript code you want to run in 81 | the agent. Subclasses of the class will append their script to the superclass 82 | script. 83 | 84 | ```python 85 | class TestScribe(kahlo.Scribe): 86 | _SCRIPT = ''' 87 | console.log("Hello world"); 88 | ''' 89 | 90 | class AnotherScribe(TestScribe): 91 | _SCRIPT = ''' 92 | console.log("This gets run after the superclass script. Bye!"); 93 | ''' 94 | ``` 95 | 96 | ### Bi-directional RPC 97 | 98 | Builds upon frida's RPC framework to provide asynchronous RPC between host and 99 | agent and vice versa. 100 | 101 | ```python 102 | class TestRPC(kahlo.rpc.BaseRPC): 103 | 104 | agent_callhost = kahlo.rpc.agentcall(''' 105 | async function (x, y) { 106 | console.log("Calling host_add."); 107 | var rv = await kahlo.hostcall.host_add(x, y); 108 | console.log("Got back value = " + rv); 109 | return rv; 110 | } 111 | ''') 112 | 113 | 114 | @kahlo.rpc.hostcall 115 | def host_add(self, ding, dong): 116 | print("Called host_add with {} + {} = {}".format(ding, dong, ding+dong)) 117 | return ding + dong 118 | 119 | async def call_into_agent(self, foo, bar): 120 | rv = await self.agentcall.agent_callhost(foo, bar) 121 | print("Got rv = {!r}".format(rv)) 122 | ``` 123 | 124 | ### Class and Function Signature Management 125 | 126 | A signature is a unique identifier for a class or function/method. A signature 127 | can be formatted in several ways for display, and can also be converted 128 | between logical and runtime versions. 129 | 130 | The main purpose of the signature module is to provide support for cases where 131 | the runtime signatures (aka symbols) associated with a class or function are 132 | different from the signatures the user would like to work with. For example, 133 | an obfuscated/minified Android application would have class and method 134 | signatures that differ from the original, unobfuscated signatures. 135 | 136 | Allowing signatures to be displayed in different formats also allows kahlo to 137 | work better with tools that use alternative formats. For example, the provided 138 | `jebandroid` module allows for interoperability with the format used by the 139 | JEB Decompiler. 140 | 141 | ```python 142 | >>> scm.from_disp("Lcom/foo/bar;->()V").to_frida() 143 | 'com.foo.bar.$clinit(): void' 144 | >>> trans = kahlo.translators.MappingTranslator(kahlo.android.Signature, kahlo.jebandroid.JebFormatter.singleton, "/tmp/test_map.json") 145 | >>> scm = kahlo.jebandroid.JebAndroidScheme(trans) 146 | >>> scm.from_disp("LSomeClass;").to_runtime().to_frida() 147 | 'advg' 148 | ``` 149 | 150 | ## Dependencies 151 | 152 | If you want to use the signature parsers for Android and JEB, you need `pyparsing`. 153 | 154 | ## TODO 155 | 156 | * Function hooking framework 157 | * Better documentation, especially of the API 158 | * More signature schemes 159 | 160 | ## License 161 | 162 | GNU General Public License v3.0 163 | 164 | See [LICENSE](/LICENSE) for full text. 165 | -------------------------------------------------------------------------------- /examples/example.py: -------------------------------------------------------------------------------- 1 | # -*- mode: poly-pyjs -*- 2 | # 3 | # kahlo example 4 | # 5 | 6 | import kahlo 7 | 8 | # Example of how to use the BaseRPC class. 9 | class TestRPC(kahlo.rpc.BaseRPC): 10 | 11 | agent_add = kahlo.rpc.agentcall('''//JS 12 | function (foo, bar) { 13 | return foo + bar; 14 | } 15 | ''') 16 | 17 | agent_mult = kahlo.rpc.agentcall('''//JS 18 | function (a, b) { 19 | return a * b; 20 | } 21 | ''') 22 | 23 | agent_callback = kahlo.rpc.agentcall('''//JS 24 | async function (x, y) { 25 | console.log("Calling host_add."); 26 | var rv = await kahlo.hostcall.host_add(x, y); 27 | console.log("Got back value = " + rv); 28 | return rv; 29 | } 30 | ''') 31 | 32 | 33 | @kahlo.rpc.hostcall 34 | def host_add(self, ding, dong): 35 | print("Called host_add with {} + {} = {}".format(ding, dong, ding+dong)) 36 | return ding + dong 37 | #enddef 38 | 39 | @kahlo.rpc.hostcall 40 | def host_mult(self, c, d): 41 | print("Called host_mult with {} * {} = {}".format(c, d, c*d)) 42 | return c * d 43 | #enddef 44 | 45 | @kahlo.rpc.hostcall 46 | def host_callback(self, v): 47 | print("Called host_callback with v = {!r}".format(v)) 48 | return v + 20 49 | #enddef 50 | 51 | async def normal_func(self, foo, bar): 52 | rv = await self.agentcall.agent_callback(foo, bar) 53 | print("Got rv = {!r}".format(rv)) 54 | #enddef 55 | 56 | #endclass 57 | 58 | ##############################################3 59 | 60 | 61 | class TestSig(kahlo.signature.AbstractSignature): 62 | def __init__(self, name, value): 63 | self.name = name 64 | self.value = value 65 | #enddef 66 | def to_frida(self): return "{} = {}".format(self.name, self.value) 67 | @classmethod 68 | def from_frida(cls, s): 69 | (n, v) = (x.strip() for x in s.split("=")) 70 | return cls(n, v) 71 | #enddef 72 | #endclass 73 | 74 | class TestFormatter(kahlo.signature.Formatter): 75 | def __init__(self): super().__init__(TestSig) 76 | def to_disp(self, sig): 77 | return "{} -> {}".format(sig.name, sig.value) 78 | #enddef 79 | def from_disp(self, s): 80 | (n, v) = (x.strip() for x in s.split("->")) 81 | return self.sigcls(n, v) 82 | #enddef 83 | #endclass 84 | 85 | class TestTranslator(kahlo.signature.Translator): 86 | def __init__(self, rtprefix): 87 | self.rtprefix = rtprefix 88 | #enddef 89 | 90 | def to_runtime(self, sig): return TestSig(self.rtprefix + sig.name, sig.value) 91 | def to_logical(self, sig): return TestSig(sig.name[len(self.rtprefix):], sig.value) 92 | #endclass 93 | 94 | test_scheme = kahlo.signature.Scheme(TestSig, TestFormatter(), TestTranslator("XXX_")) 95 | -------------------------------------------------------------------------------- /python/kahlo/__init__.py: -------------------------------------------------------------------------------- 1 | from .scribe import * 2 | from .rpc import * 3 | from .signature import * 4 | -------------------------------------------------------------------------------- /python/kahlo/android.py: -------------------------------------------------------------------------------- 1 | # 2 | # android : Classes specific to Android 3 | # 4 | 5 | import enum 6 | 7 | from . import signature 8 | 9 | class SigKind(enum.Enum): 10 | CLS = 0 11 | METHOD = 1 12 | FIELD = 2 13 | #endclass 14 | 15 | class Type(object): 16 | def to_jvm(self): raise NotImplementedError("to_jvm not implemented.") 17 | def to_frida(self): raise NotImplementedError("to_frida not implemented.") 18 | def to_json(self): return self.to_frida() 19 | #endclass 20 | 21 | class PrimitiveType(Type): 22 | _code_map = { 23 | "byte" : "B", 24 | "char" : "C", 25 | "double" : "D", 26 | "float" : "F", 27 | "int" : "I", 28 | "long" : "J", 29 | "short" : "S", 30 | "boolean" : "Z", 31 | "void" : "V" 32 | } 33 | def __init__(self, name): 34 | self.name = name 35 | #enddef 36 | def to_frida(self): return self.name 37 | def to_jvm(self): return self._code_map[self.name] 38 | 39 | @classmethod 40 | def from_jvm(cls, s): 41 | r = [k for (k,v) in cls._code_map.items() if v == s] 42 | if r == []: raise ConversionExn( 43 | "Could not convert string {!r} to a primitive type".format(fridastr) 44 | ) 45 | return cls(r[0]) 46 | #enddef 47 | 48 | #endclass 49 | 50 | class ClsType(Type): 51 | def __init__(self, name): 52 | self.name = name 53 | #enddef 54 | def to_frida(self): return self.name 55 | def to_jvm(self): return "L{};".format(self.name.replace(".", "/")) 56 | #enddef 57 | 58 | class ArrayType(Type): 59 | def __init__(self, basety : Type): 60 | self.basety = basety 61 | #enddef 62 | def to_jvm(self): return "[" + self.basety.to_jvm() 63 | # frida expects a hybrid kind of name for arrays. 64 | def to_frida(self): 65 | if isinstance(self.basety, ClsType): 66 | basetystr = "L" + self.basety.to_frida() + ";" 67 | else: 68 | basetystr = self.basety.to_jvm() 69 | #endif 70 | return "[" + basetystr 71 | #enddef 72 | #endclass 73 | 74 | class Signature(signature.AbstractSignature): 75 | @classmethod 76 | def from_frida(cls, s): 77 | return FridaJavaSignature.parseString(s, parseAll=True)[0] 78 | #enddef 79 | 80 | def get_class_sig(self): 81 | raise NotImplementedError("get_class_sig not implemented.") 82 | #enddef 83 | 84 | #endclass 85 | 86 | class ClassSignature(Signature): 87 | def __init__(self, clsty : ClsType): 88 | self.kind = SigKind.CLS 89 | self.clsty = clsty 90 | #enddef 91 | @property 92 | def name(self): return self.clsty.name 93 | 94 | def to_frida(self): return self.clsty.to_frida() 95 | def to_jvm(self): return self.clsty.to_jvm() 96 | 97 | def to_json(self): 98 | return { 99 | "kind" : self.kind.name, 100 | "clsty" : self.clsty.to_json() 101 | } 102 | #enddef 103 | 104 | def get_class_sig(self): return self 105 | 106 | #endclass 107 | 108 | class MethodSignature(Signature): 109 | _properties = ("clsty", "name", "params", "retty") 110 | 111 | def __init__(self, **kwargs): 112 | self.kind = SigKind.METHOD 113 | super().__init__(**kwargs) 114 | #enddef 115 | 116 | def __init__(self, clsty : ClsType, name, params, retty : Type, **kwargs): 117 | self.kind = SigKind.METHOD 118 | self.clsty = clsty 119 | self.name = name 120 | self.params = params 121 | self.retty = retty 122 | super().__init__(**kwargs) 123 | #enddef 124 | 125 | def get_class_sig(self): 126 | return ClassSignature(self.clsty) 127 | #enddef 128 | 129 | def to_frida(self, without_class=False): 130 | clspart = "" if without_class else "{}.".format(self.clsty.to_frida()) 131 | return "{}{}({}): {}".format( 132 | clspart, 133 | self.name, 134 | ", ".join((ty.to_frida() for ty in self.params)), 135 | self.retty.to_frida() 136 | ) 137 | #enddef 138 | 139 | def to_jvm(self): 140 | return "{}->{}({}){}".format( 141 | self.clsty.to_json(), 142 | self.name, 143 | "".join((ty.to_jvm() for ty in self.params)), 144 | self.retty.to_jvm() 145 | ) 146 | #enddef 147 | 148 | def to_json(self): 149 | return { 150 | "kind" : self.kind.name, 151 | "clsty" : self.clsty.to_json(), 152 | "name" : self.name, 153 | "params" : [p.to_json() for p in self.params], 154 | "retty" : self.retty.to_json() 155 | } 156 | #endif 157 | 158 | #endclass 159 | 160 | class FieldSignature(Signature): 161 | 162 | def __init__(self, **kwargs): 163 | self.kind = SigKind.FIELD 164 | super().__init__(**kwargs) 165 | #enddef 166 | 167 | def __init__(self, clsty : ClsType, name, ty : Type, **kwargs): 168 | self.kind = SigKind.FIELD 169 | self.clsty = clsty 170 | self.name = name 171 | self.ty = ty 172 | super().__init__(**kwargs) 173 | #enddef 174 | 175 | def get_class_sig(self): 176 | return ClassSignature(self.clsty) 177 | #enddef 178 | 179 | def to_frida(self, without_class=False): 180 | clspart = "" if without_class else "{}.".format(self.clsty.to_frida()) 181 | return "{}{} : {}".format( 182 | clspart, 183 | self.name, 184 | self.ty.to_frida() 185 | ) 186 | #enddef 187 | 188 | def to_jvm(self): 189 | return "{}->{}:{}".format( 190 | self.clsty.to_jvm(), 191 | self.name, 192 | self.ty.to_jvm() 193 | ) 194 | #enddef 195 | 196 | def to_json(self): 197 | return { 198 | "kind" : self.kind.name, 199 | "clsty" : self.clsty.to_json(), 200 | "name" : self.name, 201 | "ty" : self.ty.to_json() 202 | } 203 | #enddef 204 | 205 | #endclass 206 | 207 | # 208 | # PARSERS FOR FRIDA JAVA SIGNATURE STRINGS 209 | # 210 | 211 | import pyparsing as pp 212 | 213 | # Helper decorator for creating parsers. 214 | def parser(ppelem): 215 | def _decorator(func): 216 | ppelem.setParseAction(func) 217 | return ppelem 218 | #enddef 219 | return _decorator 220 | #enddef 221 | 222 | supp = pp.Suppress 223 | 224 | @parser(pp.oneOf(PrimitiveType._code_map.keys())) 225 | def FridaJavaPrimitive(tok): 226 | return PrimitiveType(tok[0]) 227 | #enddef 228 | 229 | FridaJavaBaseIdentifier = pp.Word(pp.alphas + "_$", bodyChars=pp.alphanums + "_$") 230 | 231 | FridaJavaIdentifier = pp.delimitedList(FridaJavaBaseIdentifier, ".", combine=True) 232 | 233 | @parser(FridaJavaIdentifier("name")) 234 | def FridaJavaCls(tok): 235 | return ClsType(tok.name) 236 | #enddef 237 | 238 | @parser((FridaJavaPrimitive | FridaJavaCls)("base") + 239 | pp.ZeroOrMore(pp.Literal("[]"))("brackets")) 240 | def FridaJavaTypeName(tok): 241 | if tok.brackets == "": 242 | return tok.base 243 | else: 244 | ty = tok.base 245 | for _ in range(0, len(tok.brackets)): 246 | ty = ArrayType(ty) 247 | #endfor 248 | return ty 249 | #endif 250 | #enddef 251 | 252 | FridaJavaParameterList = supp("(") + \ 253 | pp.Optional(pp.delimitedList(FridaJavaTypeName, ",")) + \ 254 | supp(")") 255 | 256 | @parser(FridaJavaIdentifier("name")) 257 | def FridaJavaClassSignature(tok): 258 | return ClassSignature(ClsType(tok.name)) 259 | #enddef 260 | 261 | # XXX: We don't have an implementation of FridaJavaFieldSignature currently 262 | # because it's not clear what the syntax should look like. 263 | 264 | @parser((FridaJavaBaseIdentifier + supp(".") + 265 | pp.delimitedList(FridaJavaBaseIdentifier, "."))("methodpath") + 266 | FridaJavaParameterList("params") + 267 | supp(":") + FridaJavaTypeName("retty") 268 | ) 269 | def FridaJavaMethodSignature(tok): 270 | method_name = tok.methodpath[-1] 271 | class_name = ".".join(tok.methodpath[:-1]) 272 | clsty = FridaJavaCls.parseString(class_name, parseAll=True)[0] 273 | return MethodSignature(clsty, method_name, list(tok.params), tok.retty) 274 | #enddef 275 | 276 | @parser((FridaJavaBaseIdentifier + supp(".") + 277 | pp.delimitedList(FridaJavaBaseIdentifier, "."))("fieldpath") + 278 | supp(":") + FridaJavaTypeName("ty") 279 | ) 280 | def FridaJavaFieldSignature(tok): 281 | field_name = tok.fieldpath[-1] 282 | class_name = ".".join(tok.fieldpath[:-1]) 283 | clsty = FridaJavaCls.parseString(class_name, parseAll=True)[0] 284 | return FieldSignature(clsty, field_name, tok.ty) 285 | #enddef 286 | 287 | FridaJavaSignature = FridaJavaMethodSignature | FridaJavaFieldSignature | FridaJavaClassSignature 288 | -------------------------------------------------------------------------------- /python/kahlo/jebandroid.py: -------------------------------------------------------------------------------- 1 | # 2 | # jebandroid : Signature scheme for interoperability with JEB, on Android targets 3 | # 4 | 5 | import json 6 | 7 | from . import signature 8 | from . import android 9 | 10 | class Exn(Exception): pass 11 | 12 | class ConversionExn(Exn): pass 13 | 14 | class TranslatorExn(Exn): pass 15 | 16 | class InspectorExn(Exn): pass 17 | class UnknownSignatureExn(InspectorExn): pass 18 | 19 | class SchemeExn(Exn): pass 20 | 21 | _special_name_map = { 22 | "$init" : "", 23 | "$clinit" : "", 24 | } 25 | _rev_special_name_map = dict(((v, k) for (k, v) in _special_name_map.items())) 26 | 27 | def _meth_name_to_jeb(name): 28 | return _special_name_map[name] if name in _special_name_map else name 29 | #enddef 30 | def _meth_name_from_jeb(name): 31 | return _rev_special_name_map[name] if name in _rev_special_name_map else name 32 | #enddef 33 | 34 | class JebFormatter(signature.Formatter): 35 | def __init__(self): super().__init__(android.Signature) 36 | 37 | def to_disp(self, sig): 38 | if isinstance(sig, android.ClassSignature): 39 | return sig.to_jvm() 40 | elif isinstance(sig, android.MethodSignature): 41 | return "{}->{}({}){}".format( 42 | sig.clsty.to_jvm(), 43 | _meth_name_to_jeb(sig.name), 44 | "".join((ty.to_jvm() for ty in sig.params)), 45 | sig.retty.to_jvm()) 46 | elif isinstance(sig, android.FieldSignature): 47 | return "{}->{}:{}".format( 48 | sig.clsty.to_jvm(), 49 | sig.name, 50 | sig.ty.to_jvm()) 51 | else: 52 | # Shouldn't be the case. 53 | assert False 54 | #endif 55 | #enddef 56 | 57 | def from_disp(self, s): 58 | return JebSignature.parseString(s, parseAll=True)[0] 59 | #enddef 60 | 61 | #endclass 62 | JebFormatter.singleton = JebFormatter() 63 | 64 | # This class can also be used as an inspector to retrieve additional details about a signature. 65 | class JebTranslator(signature.Translator, signature.Inspector): 66 | 67 | # Note: This class always ignores misses as that is the design of the translation map. 68 | def __init__(self, mapfile, sigcls=android.Signature, formatter=JebFormatter.singleton): 69 | assert(issubclass(sigcls, android.Signature)) 70 | super().__init__(sigcls) 71 | self.formatter = formatter 72 | self.mapfile = mapfile 73 | self.load_map() 74 | #enddef 75 | 76 | def load_map(self): 77 | 78 | with open(self.mapfile) as f: 79 | self.full_map = json.loads(f.read()) 80 | #endwith 81 | 82 | self.rtl_lookup = {} 83 | for (lclssig, lclsinfo) in self.full_map.items(): 84 | rclssig = lclsinfo["runtime_sig"] 85 | self.rtl_lookup[rclssig] = lclssig 86 | #endfor 87 | #enddef 88 | 89 | def _get_info(self, sig_from : android.Signature): 90 | 91 | disp_from = self.formatter.to_disp(sig_from) 92 | 93 | cls_sig = sig_from.get_class_sig() 94 | cls_disp = self.formatter.to_disp(cls_sig) 95 | if cls_disp in self.full_map: 96 | cls_info = self.full_map[cls_disp] 97 | else: 98 | return None 99 | #endif 100 | 101 | if isinstance(sig_from, android.ClassSignature): 102 | return cls_info 103 | elif isinstance(sig_from, android.MethodSignature): 104 | if disp_from in cls_info["methods"]: 105 | meth_info = cls_info["methods"][disp_from] 106 | return meth_info 107 | else: 108 | return None 109 | #endif 110 | elif isinstance(sig_from, android.FieldSignature): 111 | field_name = sig_from.name 112 | if field_name in cls_info["fields"]: 113 | field_info = cls_info["fields"]["field_name"] 114 | if field_info["logical_sig"] != disp_from: 115 | raise TranslatorExn("Field signature conflict: expected {}, got {}".format(disp_from, field_info["logical_sig"])) 116 | return field_info 117 | else: 118 | return None 119 | #endif 120 | else: 121 | raise TranslatorExn("Unsupported signature type: {!r}".format(type(sig_from))) 122 | #endif 123 | #enddef 124 | 125 | def to_runtime(self, lsig): 126 | info = self._get_info(lsig) 127 | return self.formatter.from_disp(info["runtime_sig"]) if info != None else lsig 128 | #enddef 129 | 130 | def to_logical(self, rsig): 131 | 132 | rdisp = self.formatter.to_disp(rsig) 133 | 134 | rcls_disp = self.formatter.to_disp(rsig.get_class_sig()) 135 | if not rcls_disp in self.rtl_lookup: return rsig 136 | 137 | lcls_disp = self.rtl_lookup[rcls_disp] 138 | lcls_sig = self.formatter.from_disp(lcls_disp) 139 | lcls_info = self._get_info(lcls_sig) 140 | if lcls_info == None: return rsig 141 | 142 | if isinstance(rsig, android.ClassSignature): 143 | return lcls_sig 144 | elif isinstance(rsig, android.MethodSignature): 145 | for (lmeth_sig, lmeth_info) in lcls_info["methods"].items(): 146 | if lmeth_info["runtime_sig"] == rdisp: 147 | return self.formatter.from_disp(lmeth_sig) 148 | #endif 149 | #endfor 150 | return rsig 151 | 152 | elif isinstance(rsig, android.FieldSignature): 153 | for (lfield_sig, lfield_info) in lcls_info["fields"].items(): 154 | if lfield_info["runtime_sig"] == rdisp: 155 | return self.formatter.from_disp(field_sig) 156 | #endif 157 | #endfor 158 | return rsig 159 | 160 | else: 161 | raise TranslatorExn("Unsupported signature type: {!r}".format(type(sig_from))) 162 | #endif 163 | 164 | #enddef 165 | 166 | def has_details(self, sig): 167 | return self._get_info(sig) != None 168 | #enddef 169 | 170 | def get_details(self, sig): 171 | info = self._get_info(sig) 172 | if info == None: 173 | raise UnknownSignatureExn("Cannot get details of unknown signature {}".format(self.formatter.to_disp(sig))) 174 | #endif 175 | return info 176 | #enddef 177 | 178 | #endclass 179 | 180 | class JebAndroidScheme(signature.Scheme): 181 | def __init__(self, mapfile=None, translator=None, inspector=None): 182 | if mapfile != None: 183 | if translator == None: 184 | translator = JebTranslator(mapfile) 185 | else: 186 | raise SchemeExn("Only one of mapfile or translator can be specified.") 187 | #endif 188 | #endif 189 | if inspector == None and isinstance(translator, JebTranslator): 190 | inspector = translator 191 | #endif 192 | 193 | super().__init__(android.Signature, 194 | formatter=JebFormatter.singleton, 195 | translator=translator, 196 | inspector=inspector) 197 | #enddef 198 | #endclass 199 | 200 | # 201 | # JEB SIGNATURE STRING PARSER 202 | # 203 | 204 | import pyparsing as pp 205 | 206 | # Helper decorator for creating parsers. 207 | def parser(ppelem): 208 | def _decorator(func): 209 | ppelem.setParseAction(func) 210 | return ppelem 211 | #enddef 212 | return _decorator 213 | #enddef 214 | 215 | supp = pp.Suppress 216 | 217 | @parser(pp.oneOf(android.PrimitiveType._code_map.values())) 218 | def JebPrimitive(tok): 219 | r = [k for (k,v) in android.PrimitiveType._code_map.items() if v == tok[0]] 220 | if r == []: raise ConversionExn( 221 | "Could not convert string {!r} to a primitive type".format(s) 222 | ) 223 | return android.PrimitiveType(r[0]) 224 | #enddef 225 | 226 | JebIdentifier = pp.Word(pp.alphas + "_<>$", bodyChars=pp.alphanums + "_<>/$") 227 | 228 | @parser(supp("L") + JebIdentifier("name") + supp(";")) 229 | def JebCls(tok): 230 | return android.ClsType(tok.name.replace("/", ".")) 231 | #enddef 232 | 233 | # TypeName is used in Array 234 | JebTypeName = pp.Forward() 235 | 236 | @parser(supp("[") + JebTypeName("ty")) 237 | def JebArray(tok): 238 | return android.ArrayType(tok.ty) 239 | #enddef 240 | 241 | JebTypeName << (JebPrimitive | JebCls | JebArray) 242 | 243 | JebParameterList = supp("(") + pp.ZeroOrMore(JebTypeName) + supp(")") 244 | 245 | @parser(JebCls.copy()) 246 | def JebClassSignature(tok): 247 | return android.ClassSignature(android.ClsType(tok[0].replace("/", "."))) 248 | #enddef 249 | 250 | @parser(JebCls("cls") + supp("->") + JebIdentifier("method") + JebParameterList("params") + JebTypeName("retty")) 251 | def JebMethodSignature(tok): 252 | return android.MethodSignature(tok.cls, _meth_name_from_jeb(tok.method), list(tok.params), tok.retty) 253 | #enddef 254 | 255 | @parser(JebCls("cls") + supp("->") + JebIdentifier("name") + supp(":") + JebTypeName("ty")) 256 | def JebFieldSignature(tok): 257 | return android.FieldSignature(tok.cls, tok.name, tok.ty) 258 | #enddef 259 | 260 | JebSignature = JebMethodSignature | JebFieldSignature | JebClassSignature 261 | -------------------------------------------------------------------------------- /python/kahlo/rpc.py: -------------------------------------------------------------------------------- 1 | # 2 | # rpc : Base RPC mechanism for kahlo 3 | # 4 | 5 | import string 6 | import enum 7 | import json 8 | import asyncio 9 | import threading 10 | import frida 11 | 12 | from . import scribe 13 | 14 | __all__ = ("BaseRPC",) 15 | 16 | class Exn(Exception): pass 17 | class InvalidIdExn(Exn): pass 18 | class ContextManagerExn(Exn): pass 19 | class ImportConflictExn(Exn): pass 20 | 21 | class Context(object): 22 | 23 | def __init__(self, manager, rpcid): 24 | self.manager = manager 25 | self.rpcid = rpcid 26 | self.result = None 27 | self.result_ev = asyncio.Event() 28 | #enddef 29 | 30 | def set_result(self, result): 31 | self.result = result 32 | self.manager._event_loop.call_soon_threadsafe(self.result_ev.set) 33 | #enddef 34 | 35 | async def completed(self): 36 | if asyncio.get_running_loop() != self.manager._event_loop: 37 | print("WARNING: Event loops are different:") 38 | print("\tCaller: {!r} ({:x})".format(asyncio.get_running_loop(), hash(asyncio.get_running_loop()))) 39 | print("\t ctx: {!r} ({:x})".format(self.manager._event_loop, hash(self.manager._event_loop))) 40 | #endif 41 | await self.result_ev.wait() 42 | return self.result 43 | #enddef 44 | 45 | def is_complete(self): return self.result_ev.is_set() 46 | 47 | #endclass 48 | 49 | class ContextManager(object): 50 | 51 | def __init__(self, evloop=None): 52 | self._rpcid_counter = 1 53 | self._contexts = {} 54 | self._event_loop = evloop if evloop != None else asyncio.get_event_loop() 55 | self._threadlock = threading.Lock() 56 | #enddef 57 | 58 | def _get_rpcid(self): 59 | with self._threadlock: 60 | rpcid = self._rpcid_counter 61 | self._rpcid_counter += 1 62 | return rpcid 63 | #endwith 64 | #enddef 65 | 66 | def create_context(self): 67 | rpcid = self._get_rpcid() 68 | ctx = Context(self, rpcid) 69 | self._contexts[rpcid] = ctx 70 | return ctx 71 | #enddef 72 | 73 | def clear_context(self, ctx): 74 | if ctx.rpcid in self._contexts: 75 | if ctx == self._contexts[ctx.rpcid]: 76 | del self._contexts[ctx.rpcid] 77 | else: 78 | raise ContextManagerExn("Context being cleared does not match context in manager!") 79 | #endif 80 | else: 81 | raise ContextManagerExn("Context being cleared is not in manager.") 82 | #endif 83 | #enddef 84 | 85 | def set_context_result(self, rpcid, result): 86 | if rpcid in self._contexts: 87 | self._contexts[rpcid].set_result(result) 88 | # XXX: Do we need to cleanup self._contexts? 89 | else: 90 | raise InvalidIdExn("Unknown RPCID {}".format(rpcid)) 91 | #endif 92 | #enddef 93 | 94 | #endclass 95 | 96 | # class CallDirection(enum.Enum): 97 | # AGENT = 1 98 | # HOST = 2 99 | # #endclass 100 | 101 | # Only used by the agentcall function and hostcall decorator below. 102 | 103 | # class RPCall(object): 104 | # def __init__(self, direction, *args): 105 | # self.direction = direction 106 | # self.args = args 107 | # #enddef 108 | # #endclass 109 | 110 | class RPCall(object): pass 111 | 112 | class HostRPCall(RPCall): 113 | def __init__(self, func): 114 | self.func = func 115 | #enddef 116 | #endclass 117 | 118 | class AgentRPCall(RPCall): 119 | def __init__(self, script, wrapfunc=None, imports=None): 120 | self.script = script 121 | self.wrapfunc = wrapfunc 122 | self.imports = imports 123 | #enddef 124 | 125 | # Decorator to set wrapper function 126 | def wrapper(self, wrapfunc): 127 | self.wrapfunc = wrapfunc 128 | return self 129 | #enddef 130 | #endclass 131 | 132 | 133 | def agentcall(script): 134 | '''Function for defining functions running on the agent that can be called 135 | from the host. 136 | 137 | Only meant to be used within a `BaseRPC` subclass definition. The string 138 | argument acts as the source of the Javascript function running on the 139 | agent. 140 | 141 | The returned AgentRPCall object has a decorator `wrapper`, which can be 142 | used to define a wrapper function. 143 | 144 | The wrapper function must accept as its first argument an asynchronous 145 | function `do_agentcall`, which when called will run the aforementioned 146 | Javascript function on the agent, and return the value obtained from that 147 | function. It is the responsibility of the wrapper to call `do_agentcall`, 148 | to pass it the appropriate arguments, and to return the value obtained 149 | from the call, modified as required. 150 | 151 | To call the function, use the `BaseRPC.agentcall` object: call function 152 | `foo` as `rpc_object.agentcall.foo(arg, ...)`. Note that the function is 153 | asynchronous. 154 | 155 | ''' 156 | return AgentRPCall(script) 157 | #enddef 158 | 159 | def import_agentcall(imports, script): 160 | '''Like `agentcall`, but include a specification of values to be imported into 161 | the script's scope. 162 | 163 | Imports are a feature that can be implemented by subclasses; in `BaseRPC` 164 | they are ignored. 165 | ''' 166 | return AgentRPCall(script, imports=imports) 167 | #enddef 168 | 169 | def hostcall(func): 170 | '''Decorator for defining functions running on the host that can be called 171 | from the agent. 172 | 173 | Only meant to be used within a `BaseRPC` subclass definition. 174 | 175 | To call the function, use the `kahlo.hostcall` object from the agent: call 176 | function `foo` as `kahlo.hostcall.foo(arg, ...)`. Note that the function 177 | is asynchronous. 178 | 179 | ''' 180 | return HostRPCall(func) 181 | #enddef 182 | 183 | # RPC metaclass that builds the RPC class from RPCall objects, created via 184 | # hostcall and agentcall helpers. 185 | class RPCMeta(type): 186 | 187 | def _merge_imports(imports, new): 188 | for (impname, impspec) in new.items(): 189 | if impname in imports: 190 | if impspec != imports[impname]: 191 | raise ImportConflictExn( 192 | "Conflicting imports for symbol {}: {!r}".format( 193 | impname, 194 | (impspec, imports[impname]))) 195 | #endif 196 | else: 197 | imports[impname] = impspec 198 | #endif 199 | #endfor 200 | return imports 201 | #enddef 202 | 203 | def __new__(meta, name, bases, dct): 204 | agent_calls_info = {} 205 | host_calls_info = {} 206 | imports_info = {} 207 | newdct = {} 208 | for (n, v) in dct.items(): 209 | if not isinstance(v, RPCall): 210 | newdct[n] = v 211 | continue 212 | #endif 213 | if isinstance(v, AgentRPCall): 214 | agent_calls_info[n] = { 215 | "script" : v.script, 216 | "func" : v.wrapfunc, 217 | } 218 | if v.imports != None: 219 | meta._merge_imports(imports_info, v.imports) 220 | #endif 221 | elif isinstance(v, HostRPCall): 222 | host_calls_info[n] = { 223 | "func" : v.func 224 | } 225 | newdct[n] = v.func 226 | #endif 227 | #endfor 228 | 229 | newdct["_agent_calls_info"] = agent_calls_info 230 | newdct["_host_calls_info"] = host_calls_info 231 | newdct["_agent_imports_info"] = imports_info 232 | 233 | return super().__new__(meta, name, bases, newdct) 234 | 235 | #enddef 236 | 237 | #endclass 238 | 239 | # Used for BaseRPC.agentcall. 240 | class AgentCaller(object): 241 | def __init__(self, rpc): self.__rpc = rpc 242 | def __getattr__(self, name): return self.__rpc.get_agentcall(name) 243 | #endclass 244 | 245 | class BaseRPC(scribe.Scribe, metaclass=RPCMeta): 246 | '''Bidirectional RPC base class. 247 | 248 | This class provides support for implementing bidirectional RPC between 249 | frida hosts and agents. Subclasses can define functions running on the 250 | host and called from the agent, and vice versa. 251 | 252 | Use the `hostcall` and `remotecall` helper functions to easily define RPC 253 | functions. Any field or method not tagged using `hostcall` or `remotecall` 254 | will be treated as a regular, un-exposed function. 255 | ''' 256 | 257 | _SCRIPT = '''//JS 258 | var func = (function () { 259 | 260 | let rpcman = { 261 | 262 | rpcid_counter: 1, 263 | new_rpcid() { return this.rpcid_counter++; }, 264 | 265 | contexts: {}, 266 | 267 | create_context() { 268 | const rpcid = this.new_rpcid(); 269 | var ctx = { rpcid: rpcid }; 270 | this.contexts[rpcid] = ctx; 271 | return ctx; 272 | }, 273 | 274 | // Make a call to the host, and returns a promise that resolve to the 275 | // value returned by the host. 276 | make_host_call(procname, args) { 277 | const ctx = this.create_context(); 278 | send({ 279 | subsystem : "rpc", 280 | rpctype : "host_call", 281 | rpcid : ctx.rpcid, 282 | procname : procname, 283 | args : args 284 | }); 285 | return new Promise(resolve => { ctx.resolver = resolve }); 286 | }, 287 | 288 | // Called when the host call has returned with a result. 289 | complete_host_call(rpcid, result) { 290 | this.contexts[rpcid].resolver(result); 291 | }, 292 | 293 | // Sends a result back to the host. 294 | send_agent_result(rpcid, result) { 295 | send({ 296 | subsystem: "rpc", 297 | rpctype: "agent_result", 298 | rpcid: rpcid, 299 | result: result 300 | }); 301 | }, 302 | 303 | }; 304 | 305 | // Add hostcalls to kahlo namespace. 306 | Object.assign(kahlo, { 307 | 308 | hostcall : Object.fromEntries( 309 | kahlo.env.hostcalls.map(k => [ 310 | k, 311 | async function () { 312 | return await rpcman.make_host_call(k, Array.from(arguments)); 313 | } 314 | ])), 315 | 316 | }); 317 | 318 | // Set up agent exports. 319 | rpc.exports = Object.fromEntries( 320 | Object.keys(kahlo.env.agentcalls).map(k => [ 321 | k, 322 | function (rpcid, args) { 323 | var rv = kahlo.env.agentcalls[k].apply(null, args); 324 | if (rv == undefined) { rv = null; } 325 | if (rv instanceof Promise) { 326 | rv.then(v => { 327 | rpcman.send_agent_result(rpcid, v); 328 | }); 329 | } else { 330 | rpcman.send_agent_result(rpcid, rv); 331 | } 332 | } 333 | ])); 334 | 335 | // Incoming message handler. 336 | kahlo.registerSubsystem("rpc", function (msg) { 337 | // console.log("Got RPC message: " + JSON.stringify(msg)); 338 | 339 | const rpcid = msg["rpcid"] 340 | const rpctype = msg["rpctype"] 341 | 342 | switch (rpctype) { 343 | case "host_result": 344 | rpcman.complete_host_call(rpcid, msg["result"]); 345 | break; 346 | default: 347 | console.log("WARNING: message from host with unknown type: " + JSON.stringify(msg)); 348 | 349 | } 350 | 351 | }) 352 | 353 | 354 | }); 355 | func(); 356 | ''' 357 | 358 | def _gather_host_calls_infos(self, cls, dct): 359 | if issubclass(cls, BaseRPC) and not cls == BaseRPC: 360 | for c in cls.__bases__: 361 | self._gather_host_calls_infos(c, dct) 362 | #endfor 363 | dct.update(cls._host_calls_info) 364 | #endif 365 | return dct 366 | #enddef 367 | 368 | def _gather_agent_calls_infos(self, cls, dct): 369 | if issubclass(cls, BaseRPC) and not cls == BaseRPC: 370 | for c in cls.__bases__: 371 | self._gather_agent_calls_infos(c, dct) 372 | #endfor 373 | dct.update(cls._agent_calls_info) 374 | #endif 375 | return dct 376 | #enddef 377 | 378 | def __init__(self, *args, **kwargs): 379 | super().__init__(*args, **kwargs) 380 | self.register_subsystem("rpc", self.on_rpc_message) 381 | 382 | # Note: This sets the instance's *_info fields to the gathered fields 383 | # from the class and all superclasses. 384 | self._host_calls_info = self._gather_host_calls_infos(self.__class__, {}) 385 | self._agent_calls_info = self._gather_agent_calls_infos(self.__class__, {}) 386 | 387 | agentcalls = "{\n" + \ 388 | ",\n".join(["{} : ({})".format(frida.core._to_camel_case(n), info["script"]) 389 | for (n, info) 390 | in self._agent_calls_info.items()]) + \ 391 | "\n}" 392 | self.set_script_env("agentcalls", agentcalls, is_raw=True) 393 | 394 | self.set_script_env("hostcalls", list(self._host_calls_info.keys())) 395 | 396 | self.ctxman = ContextManager() 397 | 398 | self._agentcaller = AgentCaller(self) 399 | 400 | #enddef 401 | 402 | def on_rpc_message(self, payload, data): 403 | # print("Got an RPC message: {!r}, {!r}".format(payload, data)) 404 | rpcid = payload["rpcid"] 405 | rpctype = payload["rpctype"] 406 | 407 | if rpctype == "host_call": 408 | procname = payload["procname"] 409 | args = payload["args"] 410 | if procname in self._host_calls_info: 411 | func = self._host_calls_info[procname]["func"] 412 | rv = func(self, *args) 413 | # Post result of call back to agent. 414 | self.post_subsystem_msg("rpc", { 415 | "rpcid" : payload["rpcid"], 416 | "rpctype" : "host_result", 417 | "result" : rv 418 | }) 419 | else: 420 | print("WARNING: Unknown host import {!r}, ignoring.".format(procname)) 421 | #endif 422 | elif rpctype == "agent_result": 423 | result = payload["result"] if "result" in payload else None 424 | self.ctxman.set_context_result(rpcid, result) 425 | else: 426 | print("WARNING: Unknown RPC type {!r}, ignoring.".format(rpctype)) 427 | #endif 428 | 429 | #enddef 430 | 431 | def get_agentcall(self, name): 432 | if name in self._agent_calls_info: 433 | wrapfunc = self._agent_calls_info[name]["func"] 434 | agentcall = getattr(self._script.exports, name) 435 | async def _do_agentcall(*args): 436 | ctx = self.ctxman.create_context() 437 | agentcall(ctx.rpcid, args) 438 | result = await ctx.completed() 439 | self.ctxman.clear_context(ctx) 440 | return result 441 | #enddef 442 | if wrapfunc == None: 443 | # No wrapping function, so directly return the agentcall 444 | # function. 445 | return _do_agentcall 446 | else: 447 | # Call wrapping function, passing it the agentcall function to 448 | # be called from within. 449 | async def _do_wrapped_agentcall(*args, **kwargs): 450 | return await wrapfunc(self, _do_agentcall, *args, **kwargs) 451 | #enddef 452 | return _do_wrapped_agentcall 453 | #endif 454 | else: 455 | raise AttributeError("No agentcall named '{}' found.".format(name)) 456 | #endif 457 | #enddef 458 | 459 | @property 460 | def agentcall(self): return self._agentcaller 461 | 462 | #endclass 463 | 464 | -------------------------------------------------------------------------------- /python/kahlo/scribe.py: -------------------------------------------------------------------------------- 1 | # 2 | # scribe : Script-carrying objects 3 | # 4 | 5 | import json 6 | import string 7 | import frida 8 | 9 | __all__ = ("Scribe",) 10 | 11 | class Exn(Exception): pass 12 | class BindExn(Exn): pass 13 | 14 | class Scribe(object): 15 | '''High-level scripting object for frida. 16 | 17 | A `Scribe` is an object that holds a JavsScript script, which can be bound 18 | to a frida `Session`. To use, create a subclass of `Scribe`, and write the 19 | script in the class's `_SCRIPT` field. The `Scribe` base class will 20 | concatenate all scripts found in its subclasses to form the final script 21 | that is bound to the session. 22 | 23 | The `Scribe` object also manages messages sent between the host and the 24 | agent. A subclass can register a subsystem using 25 | `Scribe.register_subsystem` on the host, and `kahlo.registerSubsystem` on 26 | the agent, in order to receive messages from the subsystem accordingly. 27 | ''' 28 | 29 | _SCRIPT = '''//JS 30 | var kahlo = { 31 | env : $SCRIPT_ENV, 32 | _subsystemMap : {}, 33 | }; 34 | 35 | kahlo.registerSubsystem = function (name, handler) { 36 | kahlo._subsystemMap[name] = handler; 37 | } 38 | 39 | kahlo.recvHandler = function (msg) { 40 | var subsystem = msg["subsystem"]; 41 | if (subsystem != undefined) { 42 | var handler = kahlo._subsystemMap[subsystem]; 43 | if (handler != undefined) { 44 | handler(msg); 45 | } else { 46 | console.log("WARNING: Recieved message for unregistered subsystem " + subsystem); 47 | } 48 | } else { 49 | console.log("WARNING: Received message with missing subsystem field: " + 50 | JSON.stringify(msg)); 51 | } 52 | recv("kahlo", kahlo.recvHandler); 53 | } 54 | recv("kahlo", kahlo.recvHandler); 55 | 56 | 57 | ''' 58 | 59 | def __init__(self, session): 60 | self._session = session 61 | self._script = None 62 | # Holds environment variables to pass into the script via 63 | # kahlo.env. Dict where key is the name of the environment variable, 64 | # and value is a string that will be used as the raw Javascript 65 | # expression assigned to the variable. 66 | self._script_env = {} 67 | self._subsystem_map = {} 68 | 69 | # Additional per-object code that can be added to the script. If used, 70 | # must be set before the script is bound. 71 | self._script_postamble = None 72 | 73 | # Auxillary scripts that can be added if required. 74 | self._aux_scripts = [] 75 | #enddef 76 | 77 | 78 | def set_script_env(self, name, value, is_raw=False): 79 | if not is_raw: 80 | try: 81 | value = json.dumps(value) 82 | except TypeError as e: 83 | raise BindExn("Script environment value cannot be converted to JSON: {!r}".format(e)) 84 | #endtry 85 | #endif 86 | self._script_env[name] = value 87 | #enddef 88 | 89 | 90 | def set_script_postamble(self, jscode): 91 | self._script_postamble = jscode 92 | #enddef 93 | 94 | def bind(self): 95 | 96 | # Build string representation of environment variables. 97 | script_env_str = "{" + \ 98 | ",\n".join(["{} : {}".format(k, v) for (k, v) in self._script_env.items()]) + \ 99 | "}" 100 | 101 | # Build the final script to be injected. 102 | allbases = [] 103 | def getbases(cls): 104 | if cls in allbases: return 105 | if cls == Scribe: return 106 | allbases.append(cls) 107 | for c in cls.__bases__: getbases(c) 108 | #enddef 109 | getbases(self.__class__) 110 | allbases.reverse() 111 | 112 | basescript = string.Template(Scribe._SCRIPT).substitute( 113 | SCRIPT_ENV = script_env_str 114 | ) 115 | 116 | fullscript = "\n".join([basescript] + [c._SCRIPT for c in allbases]) 117 | 118 | if self._script_postamble: 119 | fullscript += self._script_postamble 120 | #endif 121 | 122 | self._script_str = fullscript 123 | 124 | wrapped_fullscript = "Java.perform(() => {{ {} }});".format(fullscript) 125 | 126 | try: 127 | self._script = self._session.create_script(wrapped_fullscript) 128 | except frida.InvalidArgumentError as e: 129 | # Try to parse the exception and print out lines with errors. 130 | import re 131 | m = re.compile("^script\(line (\d+)\)").match(e.args[0]) 132 | if m != None: 133 | print("Error running script: {}".format(e.args[0]), end="\n\t") 134 | line = int(m[1]) - 1 135 | print("\n\t".join(wrapped_fullscript.splitlines()[max(line-1, 0):line+2])) 136 | print("="*80) 137 | print("".join(["{}\t{}\n".format(i+1, l) for (i, l) in enumerate(wrapped_fullscript.splitlines())])) 138 | #endif 139 | raise BindExn(e.args[0]) from e 140 | #endtry 141 | 142 | self._script.on("message", self._on_message) 143 | self._script.load() 144 | 145 | #enddef 146 | 147 | def add_aux_script(self, jscode): 148 | script = self._session.create_script(jscode) 149 | script.load() 150 | self._aux_scripts.append(script) 151 | #enddef 152 | 153 | def register_subsystem(self, subsys, handler): 154 | self._subsystem_map[subsys] = handler 155 | #enddef 156 | 157 | def post_subsystem_msg(self, subsystem, msg): 158 | msg["type"] = "kahlo" 159 | msg["subsystem"] = subsystem 160 | self._script.post(msg) 161 | #enddef 162 | 163 | def _on_message(self, message, data): 164 | if message["type"] == "error": 165 | self.on_error_message(message, data) 166 | elif message["type"] == "send": 167 | payload = message["payload"] 168 | 169 | if "subsystem" in payload: 170 | subsys = payload["subsystem"] 171 | if subsys in self._subsystem_map: 172 | self._subsystem_map[subsys](payload, data) 173 | else: 174 | print("WARN: Unknown subsystem {!r}.".format(subsys)) 175 | self.on_other_message(message, data) 176 | #endif 177 | else: 178 | self.on_other_message(message, data) 179 | else: 180 | self.on_other_message(message, data) 181 | #endif 182 | 183 | #enddef 184 | 185 | # Message handler for other kinds of messages. 186 | def on_other_message(self, message, data): 187 | print("Got message: {!r}, {!r}".format(message, data)) 188 | #enddef 189 | 190 | # Message handler for error messages. 191 | def on_error_message(self, message, data): 192 | if "lineNumber" in message and "columnNumber" in message: 193 | print("ERROR at line {} column {}:".format(message["lineNumber"], message["columnNumber"])) 194 | else: 195 | print("ERROR:") 196 | #endif 197 | if "stack" in message: 198 | print(message["stack"]) 199 | else: 200 | print(repr(message)) 201 | #endif 202 | #enddef 203 | 204 | #endclass 205 | -------------------------------------------------------------------------------- /python/kahlo/signature.py: -------------------------------------------------------------------------------- 1 | # 2 | # signature : Unique identifiers for classes, methods and fields. 3 | # 4 | 5 | import enum 6 | 7 | __all__ = ("AbstractSignature", "Formatter", "FridaFormatter", 8 | "Translator", "IdentTranslator", "Scheme") 9 | 10 | class Exn(Exception): pass 11 | class NoInspectorExn(Exn): pass 12 | 13 | class AbstractSignature(object): 14 | _scheme = None 15 | 16 | def to_frida(self): raise NotImplementedError("to_frida not implemented.") 17 | @classmethod 18 | def from_frida(cls, s): raise NotImplementedError("from_frida not implemented.") 19 | 20 | # Note: The below convenience functions only work if _scheme is set on the signature. 21 | def to_runtime(self): return self._scheme.to_runtime(self) 22 | def to_logical(self): return self._scheme.to_logical(self) 23 | def to_disp(self): return self._scheme.to_disp(self) 24 | @classmethod 25 | def from_disp(cls, s): return cls._scheme.from_disp(cls, s) 26 | 27 | def has_details(self): return self._scheme.has_details(self) 28 | def get_details(self): return self._scheme.get_details(self) 29 | 30 | #endclass 31 | 32 | class Formatter(object): 33 | def __init__(self, sigcls): 34 | self.sigcls = sigcls 35 | #enddef 36 | def to_disp(self, sig): raise NotImplementedError("to_disp not implemented.") 37 | def from_disp(self, s): raise NotImplementedError("from_disp not implemented.") 38 | 39 | #endclass 40 | 41 | class FridaFormatter(Formatter): 42 | def to_disp(self, sig): return sig.to_frida() 43 | def from_disp(self, s): self.sigcls.from_frida(s) 44 | 45 | #endclass 46 | 47 | class Translator(object): 48 | def __init__(self, sigcls): 49 | self.sigcls = sigcls 50 | #enddef 51 | 52 | def to_runtime(self, sig): raise NotImplementedError("to_runtime not implemented") 53 | def to_logical(self, sig): raise NotImplementedError("to_logical not implemented") 54 | #endclass 55 | 56 | class IdentTranslator(Translator): 57 | def __init__(self): super().__init__(AbstractSignature) 58 | def to_runtime(self, sig): return sig 59 | def to_logical(self, sig): return sig 60 | #endclass 61 | IdentTranslator.singleton = IdentTranslator() 62 | 63 | # An Inspector allows additional details about a signature to be obtained. 64 | class Inspector(object): 65 | def has_details(self, sig): raise NotImplementedError("has_details not implemented") 66 | def get_details(self, sig): raise NotImplementedError("get_details not implemented") 67 | #enddef 68 | 69 | class Scheme(object): 70 | 71 | def __init__(self, sigcls, formatter=None, translator=None, inspector=None): 72 | if formatter == None: formatter = FridaFormatter(sigcls) 73 | if translator == None: translator = IdentTranslator.singleton 74 | 75 | self.sigcls = sigcls 76 | self.formatter = formatter 77 | self.translator = translator 78 | self.inspector = inspector 79 | #enddef 80 | 81 | def _set_scheme(self, sig): 82 | sig._scheme = self 83 | return sig 84 | #enddef 85 | 86 | def to_disp(self, sig): return self.formatter.to_disp(sig) 87 | def from_disp(self, s): return self._set_scheme(self.formatter.from_disp(s)) 88 | 89 | def to_frida(self, sig): return sig.to_frida() 90 | def from_frida(self, s): return self._set_scheme(self.sigcls.from_frida(s)) 91 | 92 | def to_runtime(self, sig): return self._set_scheme(self.translator.to_runtime(sig)) 93 | def to_logical(self, sig): return self._set_scheme(self.translator.to_logical(sig)) 94 | 95 | def set_inspector(self, insp : Inspector): self.inspector = insp 96 | 97 | def has_details(self, sig): 98 | if self.inspector == None: raise NoInspectorExn("Inspector is not set.") 99 | return self.inspector.has_details(sig) 100 | #enddef 101 | 102 | def get_details(self, sig): 103 | if self.inspector == None: raise NoInspectorExn("Inspector is not set.") 104 | return self.inspector.get_details(sig) 105 | #enddef 106 | 107 | #endclass 108 | -------------------------------------------------------------------------------- /python/kahlo/translators.py: -------------------------------------------------------------------------------- 1 | # 2 | # translators : Generic utility translators 3 | # 4 | 5 | import json 6 | 7 | from . import signature 8 | 9 | class Exn(Exception): pass 10 | 11 | 12 | class MappingExn(Exn): pass 13 | class MappingTranslator(signature.Translator): 14 | '''Translator backed by a file containing a JSON object. 15 | 16 | The file should contain a JSON string representing an object. The 17 | key-value pairs of the object should correspond to the logical signature 18 | and the runtime signature of each entity. The format of the signatures 19 | should match that of the :formatter: argument. 20 | ''' 21 | 22 | def __init__(self, sigcls, formatter, mapfile, ignore_misses=True): 23 | super().__init__(sigcls) 24 | self.formatter = formatter 25 | self.mapfile = mapfile 26 | self.ignore_misses = ignore_misses 27 | self.load_map() 28 | #enddef 29 | 30 | def load_map(self): 31 | with open(self.mapfile) as f: 32 | self.ltr_map = json.loads(f.read()) 33 | #endwith 34 | self.rtl_map = dict(((v, k) for (k, v) in self.ltr_map.items())) 35 | #enddef 36 | 37 | def _lookup(self, themap, sig_from): 38 | disp_from = self.formatter.to_disp(sig_from) 39 | if disp_from in themap: 40 | disp_to = themap[disp_from] 41 | return self.formatter.from_disp(disp_to) 42 | elif self.ignore_misses: 43 | return sig_from 44 | else: 45 | raise MappingExn("Could not find signature {} in map.".format(disp_from)) 46 | #endif 47 | #enddef 48 | 49 | def to_runtime(self, sig): return self._lookup(self.ltr_map, sig) 50 | def to_logical(self, sig): return self._lookup(self.rtl_map, sig) 51 | 52 | #endclass 53 | -------------------------------------------------------------------------------- /python/kahlo/velcro.py: -------------------------------------------------------------------------------- 1 | # 2 | # velcro : Add hooks to functions 3 | # 4 | 5 | import asyncio 6 | 7 | from . import rpc 8 | from . import signature 9 | from . import android 10 | 11 | class Exn(Exception): pass 12 | class ExistingHookExn(Exn): pass 13 | class TracerExn(Exn): pass 14 | 15 | class HookTarget(object): 16 | '''A target function or method that can be hooked.''' 17 | 18 | def get_id(self): raise NotImplemented 19 | 20 | #endclass 21 | 22 | class JavaHookTarget(HookTarget): 23 | def __init__(self, sig): 24 | assert isinstance(sig, android.MethodSignature) 25 | self.sig = sig 26 | #enddef 27 | 28 | def get_id(self): return "java!!" + self.sig.to_logical().to_disp() 29 | 30 | #endclass 31 | 32 | class NativeHookTarget(HookTarget): 33 | def __init__(self, libname, funcname): 34 | self.libname = libname 35 | self.funcname = funcname 36 | #enddef 37 | 38 | def get_id(self): return "native!!{}:{}".format(self.libname, self.funcname) 39 | 40 | #endclass 41 | 42 | class HookContext(object): 43 | '''A pending or running instance of a hook handler. 44 | 45 | Each time a hook is triggered, a new handler coroutine will be executed, 46 | with a HookContext object as the associated context. 47 | ''' 48 | 49 | def __init__(self, parent, tid): 50 | self.parent = parent 51 | self.tid = tid 52 | self.coro = self.parent.func(self) 53 | self.args = None 54 | self.retval = None 55 | self.return_ev = asyncio.Event(loop=self.parent._event_loop) 56 | self.is_done = False 57 | #enddef 58 | 59 | @property 60 | def target(self): return self.parent.target 61 | 62 | def _cleanup_future(self, fut): 63 | # print("_cleanup_future called for future: {!r}".format(fut)) 64 | self.is_done = True 65 | if not fut.cancelled(): 66 | exn = fut.exception() 67 | if exn != None: 68 | import traceback 69 | traceback.print_exception(None, exn, exn.__traceback__) 70 | #endif 71 | #endif 72 | self.parent._cleanup_context(self) 73 | #enddef 74 | 75 | def run(self, args): 76 | # print("HookContext.run: args={!r}, coro={!r}".format(args, self.coro)) 77 | self.args = args 78 | self.coro_future = asyncio.run_coroutine_threadsafe(self.coro, self.parent._event_loop) 79 | self.coro_future.add_done_callback(self._cleanup_future) 80 | # print("\tHookContext.run completed.") 81 | #enddef 82 | 83 | async def complete(self): 84 | await self.return_ev.wait() 85 | return self.retval 86 | #enddef 87 | 88 | def set_return_value(self, retval): 89 | self.retval = retval 90 | self.parent._event_loop.call_soon_threadsafe(self.return_ev.set) 91 | #enddef 92 | 93 | #enddef 94 | 95 | class Hook(object): 96 | '''A hook onto a specific Java method or C function. 97 | 98 | The method or function is determined by the passed in signature. When the 99 | hook is triggered, the coroutine returned by the passed in function is 100 | run. 101 | ''' 102 | 103 | # Note: If func is None, then no context is created when the hook is triggered. 104 | def __init__(self, manager, target, func, inline_js=None): 105 | self.manager = manager 106 | 107 | assert isinstance(target, HookTarget) 108 | self.target = target 109 | self.func = func 110 | self.inline_js = inline_js 111 | self.contexts = {} 112 | self.returned_contexts = [] 113 | #enddef 114 | 115 | def trigger_start(self, tid, args): 116 | if self.func == None: return 117 | 118 | if not tid in self.contexts: self.contexts[tid] = [] 119 | thd_contexts = self.contexts[tid] 120 | ctx = HookContext(self, tid) 121 | ctx.run(args) 122 | thd_contexts.append(ctx) 123 | #enddef 124 | 125 | def trigger_return(self, tid, retval): 126 | if self.func == None: return 127 | 128 | ctx = self.contexts[tid].pop() 129 | ctx.set_return_value(retval) 130 | if not ctx.is_done: 131 | self.returned_contexts.append(ctx) 132 | #endif 133 | #enddef 134 | 135 | def _cleanup_context(self, ctx): 136 | if ctx in self.returned_contexts: 137 | self.returned_contexts.remove(ctx) 138 | #endif 139 | #enddef 140 | 141 | @property 142 | def _event_loop(self): 143 | return self.manager._event_loop 144 | #enddef 145 | 146 | #endclass 147 | 148 | 149 | class Velcro(rpc.BaseRPC): 150 | _SCRIPT = r'''//JS 151 | // Initialization 152 | //Java.perform(function () { 153 | //Java.deoptimizeEverything(); 154 | //}); 155 | 156 | function LOG(s) { console.log(s); } 157 | 158 | // XXX: Should this be done here, or elsewhere? 159 | const _special_name_map = { 160 | "" : "$init" , 161 | "" : "$clinit" 162 | } 163 | function ensure_frida_name(s) { 164 | if (_special_name_map[s] != undefined) 165 | return _special_name_map[s] 166 | else 167 | return s 168 | } 169 | 170 | function _do_wrap_obj(obj, use_static) { 171 | const clsname = obj.$n; 172 | const info = kahlo.velcro.imported_class_info[clsname]; 173 | if (info == undefined) { 174 | LOG(`Error: could not wrap class or instance of ${clsname} as class is not imported.`); 175 | } 176 | // LOG(`Wrapping object ${clsname}, logical sig = ${info["logical_sig"]}`); 177 | var wrapper = { 178 | "$o" : obj 179 | }; 180 | for (const [lsig, meth_info] of Object.entries(info["methods"])) { 181 | // Only use static methods if use_static is true, else only use instance methods 182 | if (meth_info["static"] != use_static) continue; 183 | 184 | const rtname = ensure_frida_name(meth_info["runtime_name"]); 185 | // LOG(`\tWrapping method: ${lsig}, rtname=${rtname}`); 186 | 187 | // The function to be wrapped 188 | let func = obj[rtname]; 189 | 190 | // Sometimes, there might be no method with name `rtname` in the 191 | // object, possibly because the spec is not accurate. Just ignore 192 | // if so. 193 | if (func != undefined) { 194 | 195 | if (func.overloads != undefined) { 196 | func = func.overload.apply(func, meth_info["frida_params"]) 197 | } 198 | 199 | // XXX: Note that this doesn't work properly if the logical 200 | // method name is overloaded; here we will just replace the 201 | // previous implementation with the current one. Will have to 202 | // figure out the best way to implement overloaded logical 203 | // method names in the future. 204 | 205 | wrapper[meth_info["logical_name"]] = function () { 206 | // LOG(`Calling wrapper of ${meth_info["logical_name"]}.`); 207 | // LOG(`\tfunc.methodName = ${func.methodName}`); 208 | // LOG("\targuments = " + JSON.stringify(arguments)); 209 | return func.apply(obj, arguments); 210 | } 211 | } 212 | } 213 | 214 | for (const [lsig, field_info] of Object.entries(info["fields"])) { 215 | if (field_info["static"] != use_static) continue; 216 | // LOG(`\tWrapping property: ${lsig}`); 217 | // LOG(`\t\tRuntime name: ${field_info["runtime_name"]}`); 218 | Object.defineProperty( 219 | wrapper, 220 | field_info["logical_name"], 221 | { get() { return obj[field_info["runtime_name"]] }} 222 | ); 223 | } 224 | return wrapper; 225 | } 226 | 227 | Object.assign(kahlo, { 228 | 229 | // Create velcro global namespace. 230 | velcro : { 231 | 232 | imported_class_info : {}, 233 | import_name_map : {}, 234 | 235 | register_imported_class : async function(clsname, cls, impname) { 236 | LOG(`Registering class as import ${impname}: name ${clsname}, obj: ${cls}`); 237 | let details = await kahlo.hostcall.get_sig_details(clsname, true, true); 238 | if (details != null) { 239 | const cls_logical_sig = await kahlo.hostcall.sig_get_logical(clsname, true); 240 | Object.assign(details, { 241 | obj : cls, 242 | "logical_sig" : cls_logical_sig, 243 | "logical_sig_disp" : await kahlo.hostcall.sig_format_to_disp(cls_logical_sig), 244 | }); 245 | for (const [msig, minfo] of Object.entries(details["methods"])) { 246 | minfo["frida_params"] = await kahlo.hostcall.convert_meth_params(minfo["params"]); 247 | minfo["frida_retty"] = (await kahlo.hostcall.convert_meth_params([minfo["retty"]]))[0]; 248 | } 249 | this.imported_class_info[clsname] = details; 250 | this.import_name_map[impname] = clsname; 251 | } else { 252 | LOG(`Note: class ${clsname} does not have detailed info.`); 253 | } 254 | }, 255 | 256 | wrap_instance : function(inst) { 257 | return _do_wrap_obj(inst, false); 258 | }, 259 | 260 | wrap_class : function(cls) { 261 | return _do_wrap_obj(cls, true); 262 | }, 263 | 264 | get_method_lookup_function : function(cls) { 265 | const clsname = cls.$n; 266 | const info = kahlo.velcro.imported_class_info[clsname]; 267 | return function (meth_lsig) { 268 | const meth_full_sig = info["logical_sig_disp"] + "->" + meth_lsig; 269 | return info["methods"][meth_full_sig]; 270 | }; 271 | }, 272 | 273 | create_class : function(name, supercls, intf, methods) { 274 | 275 | function dummy_lookup(n) { return undefined; } 276 | 277 | const supercls_lookup = supercls ? kahlo.velcro.get_method_lookup_function(supercls) : dummy_lookup; 278 | const intf_lookup = intf ? kahlo.velcro.get_method_lookup_function(intf) : dummy_lookup; 279 | 280 | const build_methods = {}; 281 | var ctor = undefined; 282 | Object.entries(methods).forEach(function ([meth_lsig, meth_impl]) { 283 | // Special case for constructor 284 | if (meth_lsig == "$init") { 285 | ctor = meth_impl; 286 | return; 287 | } 288 | 289 | const sc_meth_info = supercls_lookup(meth_lsig); 290 | const if_meth_info = intf_lookup(meth_lsig); 291 | 292 | const meth_info = sc_meth_info ? sc_meth_info : if_meth_info; 293 | 294 | if (meth_info) { 295 | 296 | const meth_name = meth_info["runtime_name"]; 297 | 298 | if (build_methods[meth_name] == null) { 299 | build_methods[meth_name] = [] 300 | } 301 | build_methods[meth_name].push([meth_impl, meth_info]) 302 | 303 | } else { 304 | LOG("WARNING: Unknown logical signature when building method list for class: " + meth_lsig); 305 | } 306 | 307 | }); 308 | 309 | const rtmethods = Object.fromEntries( 310 | Object.entries(build_methods).map(function ([meth_name, impl_infos]) { 311 | return [meth_name, 312 | impl_infos.map(function ([meth_impl, meth_info]) { 313 | // Note: we fully specify the type signature here to ensure the right method 314 | // is being replaced. 315 | return { 316 | returnType : meth_info["frida_retty"], 317 | argumentTypes : meth_info["frida_params"], 318 | implementation : meth_impl 319 | } 320 | })]; 321 | })); 322 | if (ctor) { 323 | rtmethods["$init"] = ctor; 324 | } 325 | 326 | LOG("rtmethods = " + JSON.stringify(rtmethods)); 327 | 328 | return Java.registerClass({ 329 | name : name, 330 | superClass : supercls ? supercls : undefined, 331 | implements : intf ? [intf] : undefined, 332 | methods : rtmethods 333 | }); 334 | } 335 | 336 | 337 | 338 | } 339 | 340 | }); 341 | 342 | ''' 343 | _AGENT_IMPORTS = None 344 | 345 | def __init__(self, scm, *args, **kwargs): 346 | super().__init__(*args, **kwargs) 347 | self._scheme = scm 348 | self._hooks = {} 349 | self._event_loop = asyncio.get_event_loop() 350 | 351 | # Set up imports 352 | 353 | imports = self._AGENT_IMPORTS if self._AGENT_IMPORTS != None else {} 354 | 355 | type(type(self))._merge_imports(imports, self._agent_imports_info) 356 | 357 | imports_script = "\n".join(( 358 | '''//JS 359 | const {impname} = Java.use("{rtsig}"); 360 | kahlo.velcro.register_imported_class("{rtsig}", {impname}, "{impname}"); 361 | '''.format( 362 | impname = impname, 363 | rtsig = self._scheme.from_disp(impspec).to_runtime().to_frida() 364 | ) 365 | for (impname, impspec) 366 | in imports.items())) 367 | 368 | # We use the postamble in order to allow us to add the imported class 369 | # names to the scope of all functions and hooks. 370 | self.set_script_postamble(imports_script) 371 | 372 | #enddef 373 | 374 | async def add_java_hook(self, sig, func, inline_js=None): 375 | sigstr = self._scheme.to_runtime(sig).to_disp() 376 | target = JavaHookTarget(sig) 377 | target_id = target.get_id() 378 | assert not target_id in self._hooks 379 | self._hooks[target_id] = Hook(self, target, func, inline_js=inline_js) 380 | await self.agentcall.do_add_java_hook(sigstr, target_id, inline_js) 381 | #enddef 382 | 383 | async def add_inline_java_hook(self, sig, inline_js): 384 | await self.add_java_hook(sig, None, inline_js=inline_js) 385 | #enddef 386 | 387 | do_add_java_hook = rpc.agentcall('''//JS 388 | async function(sig, target_id, inline_js) { 389 | //function LOG(s) { console.log(s); } 390 | 391 | // var sig_info = await kahlo.hostcall.get_android_siginfo(sig, true); 392 | // var clsname = sig_info["clsname"] 393 | // var methname = sig_info["methname"] 394 | // var paramtys = sig_info["paramtys"] 395 | 396 | const sig_info = await kahlo.hostcall.parse_sig(sig); 397 | const clsname = sig_info["clsty"] 398 | const methname = sig_info["name"] 399 | const paramtys = sig_info["params"] 400 | 401 | // LOG("addHook:") 402 | // LOG("\tclsname = " + clsname) 403 | // LOG("\tmethname = " + methname) 404 | // LOG("\tparamtys = " + JSON.stringify(paramtys)) 405 | 406 | var inline_func = null; 407 | if (inline_js != null) { 408 | inline_func = eval("(" + inline_js + ")"); 409 | } 410 | 411 | Java.perform(function () { 412 | var cls = Java.use(clsname) 413 | // LOG("cls = " + cls) 414 | var basemeth = cls[methname] 415 | // LOG("basemeth = " + basemeth) 416 | var meth = basemeth.overload.apply(basemeth, paramtys) 417 | meth.implementation = function () { 418 | const Thread = Java.use("java.lang.Thread"); 419 | var tid = parseInt(Thread.currentThread().getId()); 420 | // Note: This is an async function, so it returns a 421 | // Promise. Since we're not in an async function, we can't 422 | // call await, so we just ignore the Promise and continue on. 423 | kahlo.hostcall.notify_call( 424 | tid, 425 | target_id, 426 | [this.toString()].concat( 427 | Array.from(arguments).map(a => a ? a.toString() : "null") 428 | ) 429 | ); 430 | 431 | var rv = null; 432 | if (inline_func != null) { 433 | // The inline JS function is responsible for calling the hooked method 434 | // if required. 435 | rv = inline_func(this, meth, arguments); 436 | } else { 437 | rv = meth.apply(this, arguments); 438 | } 439 | 440 | // Note: Same issue as notify_call() regarding async. 441 | kahlo.hostcall.notify_return( 442 | tid, 443 | target_id, 444 | rv ? rv.toString() : null 445 | ); 446 | return rv; 447 | }; 448 | LOG("Hooked method: " + sig); 449 | }); 450 | } 451 | ''') 452 | 453 | add_native_hook = rpc.agentcall('''//JS 454 | async function(libname, funcname, target_id, inline_js) { 455 | var target_func = Module.findExportByName(libname, funcname); 456 | 457 | // XXX: Use an async function here instead of a callback object. Will 458 | // make the API more consistent. 459 | 460 | var inline_obj = null; 461 | if (inline_js != null) { 462 | inline_obj = eval("(" + inline_js + ")"); 463 | } 464 | 465 | Interceptor.attach(target_func, { 466 | onEnter: async function(args) { 467 | const tid = Process.getCurrentThreadId() 468 | 469 | // Build args list 470 | const copied_args = Array.from( 471 | {length: 10}, 472 | (_, i) => parseInt(args[i]) 473 | ) 474 | 475 | if (inline_obj != null) { 476 | inline_obj.onEnter(args); 477 | } 478 | 479 | // Note: once we call await, args appears to become invalid. 480 | // XXX TODO: Try to figure out why. 481 | await kahlo.hostcall.notify_call( 482 | tid, 483 | target_id, 484 | copied_args 485 | ); 486 | 487 | }, 488 | onLeave: async function (retval) { 489 | const tid = Process.getCurrentThreadId() 490 | 491 | if (inline_obj != null) { 492 | inline_obj.onLeave(retval); 493 | } 494 | 495 | await kahlo.hostcall.notify_return( 496 | tid, 497 | target_id, 498 | retval ? retval.toString() : null 499 | ); 500 | } 501 | }); 502 | } 503 | ''') 504 | @add_native_hook.wrapper 505 | async def add_native_hook(self, makecall, libname, funcname, func, inline_js=None): 506 | target = NativeHookTarget(libname, funcname) 507 | target_id = target.get_id() 508 | assert not target_id in self._hooks 509 | self._hooks[target_id] = Hook(self, target, func, inline_js=inline_js) 510 | await makecall(libname, funcname, target_id, inline_js) 511 | #enddef 512 | 513 | async def add_inline_native_hook(self, libname, funcname, inline_js): 514 | await self.agentcall.add_native_hook(libname, funcname, None, inline_js=inline_js) 515 | #enddef 516 | 517 | 518 | @rpc.hostcall 519 | def notify_call(self, tid, target_id, args): 520 | # print("Hit notify_call: tid={!r}, target_id={!r}, args={!r}".format(tid, target_id, args)) 521 | hook = self._hooks[target_id] 522 | # print("\thook = {!r}".format(hook)) 523 | hook.trigger_start(tid, args) 524 | #enddef 525 | 526 | @rpc.hostcall 527 | def notify_return(self, tid, target_id, rv): 528 | # print("Hit notify_return: tid={!r}, target_id={!r}, rv={!r}".format(tid, target_id, rv)) 529 | hook = self._hooks[target_id] 530 | # print("\thook = {!r}".format(hook)) 531 | hook.trigger_return(tid, rv) 532 | #enddef 533 | 534 | # XXX: This should be removed as it can be replaced by parse_sig(). 535 | @rpc.hostcall 536 | def get_android_siginfo(self, sigstr, use_runtime=False): 537 | sig = self._scheme.from_disp(sigstr) 538 | if use_runtime: sig = sig.to_runtime() 539 | assert isinstance(sig, android.MethodSignature) 540 | rv = { 541 | "clsname" : sig.clsty.to_frida(), 542 | "methname" : sig.name, 543 | "paramtys" : [p.to_frida() for p in sig.params] 544 | } 545 | return rv 546 | #enddef 547 | 548 | def _get_sig(self, sigstr, force_logical, is_frida_format): 549 | sig = (self._scheme.from_frida if is_frida_format else self._scheme.from_disp)(sigstr) 550 | return sig.to_logical() if force_logical else sig 551 | #enddef 552 | 553 | @rpc.hostcall 554 | def get_sig_details(self, sigstr, is_runtime_name=False, is_frida_format=False): 555 | sig = self._get_sig(sigstr, is_runtime_name, is_frida_format) 556 | return sig.get_details() if sig.has_details() else None 557 | #enddef 558 | 559 | @rpc.hostcall 560 | def parse_sig(self, sigstr, is_frida_format=False): 561 | sig = self._get_sig(sigstr, False, is_frida_format) 562 | return sig.to_json() 563 | #enddef 564 | 565 | @rpc.hostcall 566 | def sig_get_logical(self, sigstr, is_frida_format=False): 567 | sig = self._get_sig(sigstr, True, is_frida_format) 568 | return sig.to_disp() if not is_frida_format else sig.to_frida() 569 | #enddef 570 | 571 | @rpc.hostcall 572 | def sig_get_runtime(self, sigstr, is_frida_format=False): 573 | sig = self._get_sig(sigstr, False, is_frida_format).to_runtime() 574 | return sig.to_disp() if not is_frida_format else sig.to_frida() 575 | #enddef 576 | 577 | @rpc.hostcall 578 | def sig_format_to_frida(self, sigstr): 579 | return self._get_sig(sigstr, False, False).to_frida() 580 | #enddef 581 | 582 | @rpc.hostcall 583 | def sig_format_to_disp(self, sigstr): 584 | return self._get_sig(sigstr, False, True).to_disp() 585 | #enddef 586 | 587 | @rpc.hostcall 588 | def convert_meth_params(self, params): 589 | # XXX: Refactor this to not depend on jebandroid 590 | from .jebandroid import JebTypeName 591 | def trans_if_clsty(ty): 592 | if isinstance(ty, android.ClsType): 593 | return self._scheme.to_runtime(android.ClassSignature(ty)).clsty 594 | else: 595 | return ty 596 | #endif 597 | #enddef 598 | return [trans_if_clsty(JebTypeName.parseString(p)[0]).to_frida() for p in params] 599 | #enddef 600 | 601 | 602 | get_classes = rpc.agentcall('''//JS 603 | function() { 604 | return new Promise((resolve, reject) => { 605 | Java.perform(function() { 606 | resolve(Java.enumerateLoadedClassesSync()); 607 | }); 608 | }); 609 | } 610 | ''') 611 | @get_classes.wrapper 612 | async def get_classes(self, makecall): 613 | result = await makecall() 614 | return [self._scheme.from_frida(c).to_logical().to_disp() for c in result] 615 | #enddef 616 | 617 | 618 | get_class_methods = rpc.agentcall('''//JS 619 | function(clsname) { 620 | return new Promise((resolve, reject) => { 621 | Java.perform(function() { 622 | LOG("Enumerating methods in class " + clsname); 623 | const groups = Java.enumerateMethods(clsname + "!*/s"); 624 | if (groups.length == 0) { 625 | LOG("groups.length == 0") 626 | resolve([]) 627 | } else { 628 | resolve(groups[0].classes[0].methods) 629 | } 630 | }); 631 | }); 632 | } 633 | ''') 634 | @get_class_methods.wrapper 635 | async def get_class_methods(self, makecall, clsname): 636 | frida_clsname = self._scheme.from_disp(clsname).to_runtime().to_frida() 637 | result = await makecall(frida_clsname) 638 | return [ 639 | self._scheme 640 | .from_frida("{}.{}".format(frida_clsname, r)) 641 | .to_logical() 642 | for r in result 643 | ] 644 | #enddef 645 | 646 | read_cstring = rpc.agentcall('''//JS 647 | function(addr) { 648 | return ptr(addr).readCString() 649 | } 650 | ''') 651 | 652 | read_pointer = rpc.agentcall('''//JS 653 | function(addr) { 654 | return ptr(addr).readPointer().toString() 655 | } 656 | ''') 657 | @read_pointer.wrapper 658 | async def read_pointer(self, makecall, addr): 659 | return int(await makecall(addr)) 660 | #enddef 661 | 662 | agent_exec = rpc.agentcall('''//JS 663 | function(codestr) { 664 | var func = eval("(function() {" + codestr + "})"); 665 | return func() 666 | } 667 | ''') 668 | 669 | agent_exec_async = rpc.agentcall('''//JS 670 | async function(codestr) { 671 | var func = eval("(async function() {" + codestr + "})"); 672 | return await func() 673 | } 674 | ''') 675 | 676 | 677 | do_import_agentcall = rpc.agentcall('''//JS 678 | function(imports, func_js) { 679 | var ctx = Object.fromEntries( 680 | Object.entries(imports), 681 | ([import_name, class_name]) => [import_name, Java.use(class_name)] 682 | ); 683 | LOG("ctx = " + ctx); 684 | var func = eval("(" + func_js + ")").bind(ctx); 685 | func(); 686 | } 687 | ''') 688 | 689 | #endclass 690 | 691 | class Tracer(object): 692 | 693 | def __init__(self, velcro): 694 | self.velcro = velcro 695 | self.threadinfos = {} 696 | #enddef 697 | 698 | def init_threadinfo(self, tid): 699 | if tid in self.threadinfos: 700 | raise TracerExn("Already have threadinfo for tid {:d}".format(td)) 701 | #endif 702 | self.threadinfos[tid] = { 703 | "depth" : 0, 704 | } 705 | #enddef 706 | 707 | def push_thread(self, tid): 708 | if not tid in self.threadinfos: 709 | self.init_threadinfo(tid) 710 | #enddif 711 | self.threadinfos[tid]["depth"] += 1 712 | #enddef 713 | 714 | def pop_thread(self, tid): 715 | if not tid in self.threadinfos: 716 | self.init_threadinfo(tid) 717 | #enddif 718 | if self.threadinfos[tid]["depth"] > 0: 719 | self.threadinfos[tid]["depth"] -= 1 720 | #endif 721 | #enddef 722 | 723 | def get_logger_for_thread(self, tid): 724 | if not tid in self.threadinfos: 725 | self.init_threadinfo(tid) 726 | #endif 727 | info = self.threadinfos[tid] 728 | depth = info["depth"] 729 | def _log(msg): 730 | print("[{tid:02d}]\t{indent}{msg}".format( 731 | tid = tid, 732 | indent = " " * depth, 733 | msg = msg)) 734 | #enddef 735 | return _log 736 | #enddef 737 | 738 | def _format_arg(self, arg, maxlen=400): 739 | if type(arg) != str: arg = repr(arg) 740 | return arg[:maxlen - 2] + ".." if len(arg) > maxlen else arg 741 | #enddef 742 | 743 | async def _trace_function_hook(self, ctx): 744 | LOG = self.get_logger_for_thread(ctx.tid) 745 | 746 | if isinstance(ctx.target, JavaHookTarget): 747 | nice_sig = ctx.target.sig.to_logical().to_disp() 748 | LOG("\U0001f806 {}:".format(nice_sig)) 749 | elif isinstance(ctx.target, NativeHookTarget): 750 | LOG("\U0001f806 {}:{}".format(ctx.target.libname, ctx.target.funcname)) 751 | else: 752 | LOG("\U0001f806 ???{}".format(repr(ctx.target))) 753 | #endif 754 | 755 | argstr = ", ".join([self._format_arg(a) for a in ctx.args]) 756 | LOG(" ({})".format(argstr)) 757 | 758 | self.push_thread(ctx.tid) 759 | await ctx.complete() 760 | self.pop_thread(ctx.tid) 761 | 762 | LOG(" \U0001f804 {}".format(self._format_arg(ctx.retval))) 763 | 764 | #enddef 765 | 766 | async def trace_sig(self, sig): 767 | await self.velcro.add_java_hook(sig, self._trace_function_hook) 768 | #enddef 769 | 770 | #endclass 771 | --------------------------------------------------------------------------------