diff --git a/CHANGELOG.md b/CHANGELOG.md index c1fc23d6c..61e023c56 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -66,10 +66,12 @@ The table below shows which release corresponds to each branch, and what date th ## 4.10.0 (`dev`) +- [#1974][1974] Add kCTF Proof of Work challenge generator, solver and verifier - [#2062][2062] make pwn cyclic -l work with entry larger than 4 bytes - [#2092][2092] shellcraft: dup() is now called dupio() consistently across all supported arches - [#2093][2093] setresuid() in shellcraft uses current euid by default +[1974]: https://github.com/Gallopsled/pwntools/pull/1974 [2062]: https://github.com/Gallopsled/pwntools/pull/2062 [2092]: https://github.com/Gallopsled/pwntools/pull/2092 [2093]: https://github.com/Gallopsled/pwntools/pull/2093 diff --git a/pwn/toplevel.py b/pwn/toplevel.py index f0658a70e..5527dc26c 100644 --- a/pwn/toplevel.py +++ b/pwn/toplevel.py @@ -37,6 +37,7 @@ from pwnlib.fmtstr import FmtStr, fmtstr_payload, fmtstr_split from pwnlib.log import getLogger from pwnlib.memleak import MemLeak, RelativeMemLeak +from pwnlib.pow import * from pwnlib.regsort import * from pwnlib.replacements import * from pwnlib.rop import ROP diff --git a/pwnlib/__init__.py b/pwnlib/__init__.py index 446907c04..eb66dec9a 100644 --- a/pwnlib/__init__.py +++ b/pwnlib/__init__.py @@ -24,6 +24,7 @@ 'log', 'memleak', 'pep237', + 'pow', 'regsort', 'replacements', 'rop', diff --git a/pwnlib/data/kctf/LICENSE b/pwnlib/data/kctf/LICENSE new file mode 100644 index 000000000..d64569567 --- /dev/null +++ b/pwnlib/data/kctf/LICENSE @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/pwnlib/data/kctf/__init__.py b/pwnlib/data/kctf/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/pwnlib/data/kctf/pow.py b/pwnlib/data/kctf/pow.py new file mode 100644 index 000000000..b010c44e6 --- /dev/null +++ b/pwnlib/data/kctf/pow.py @@ -0,0 +1,221 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +# Copyright 2020 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# This file had been modified: +# * Unnecessary code has been disabled +# * It uses pwnlib logging instead of sys.stderr.write() +# * The notice about installing gmpy2 has been moved into functions to make for a quieter import +# * For Python 2 compatibility, random.randrange() is used if secrets.randbelow() is not available +# * The 'can_bypass' mechanism has been removed to eliminate the dependence on ecdsa +# * str(b, 'utf-8') has been replaced with six.ensure_str(b, 'utf-8') +# * bytes(s, 'utf-8') has been replaced with six.ensure_binary(s, 'utf-8') +# * n.to_bytes() has been replaced with packing.pack(n) +# * int.from_bytes(b) has been replaced with packing.unpack(b, 'all') + + +import base64 +# import os +# import secrets +# import socket +# import sys +# import hashlib +import six +from pwnlib.log import getLogger +from pwnlib.util import packing + +log = getLogger(__name__) + +try: + import gmpy2 + HAVE_GMP = True +except ImportError: + HAVE_GMP = False + # sys.stderr.write("[NOTICE] Running 10x slower, gotta go fast? pip3 install gmpy2\n") + +try: + import secrets + HAVE_SECRETS = True +except ImportError: + import random + HAVE_SECRETS = False + +VERSION = 's' +MODULUS = 2**1279-1 +CHALSIZE = 2**128 + +# SOLVER_URL = 'https://goo.gle/kctf-pow' + +def python_sloth_root(x, diff, p): + exponent = (p + 1) // 4 + for i in range(diff): + x = pow(x, exponent, p) ^ 1 + return x + +def python_sloth_square(y, diff, p): + for i in range(diff): + y = pow(y ^ 1, 2, p) + return y + +def gmpy_sloth_root(x, diff, p): + exponent = (p + 1) // 4 + for i in range(diff): + x = gmpy2.powmod(x, exponent, p).bit_flip(0) + return int(x) + +def gmpy_sloth_square(y, diff, p): + y = gmpy2.mpz(y) + for i in range(diff): + y = gmpy2.powmod(y.bit_flip(0), 2, p) + return int(y) + +def sloth_root(x, diff, p): + if HAVE_GMP: + return gmpy_sloth_root(x, diff, p) + else: + log.warning_once("kctf-pow is running 10x slower, gotta go fast? pip3 install gmpy2") + return python_sloth_root(x, diff, p) + +def sloth_square(x, diff, p): + if HAVE_GMP: + return gmpy_sloth_square(x, diff, p) + else: + log.warning_once("kctf-pow is running 10x slower, gotta go fast? pip3 install gmpy2") + return python_sloth_square(x, diff, p) + +def encode_number(num): + size = (num.bit_length() // 24) * 3 + 3 + # return str(base64.b64encode(num.to_bytes(size, 'big')), 'utf-8') + return six.ensure_str(base64.b64encode(packing.pack(num, size * 8, endian='big')), 'utf-8') + +def decode_number(enc): + # return int.from_bytes(base64.b64decode(bytes(enc, 'utf-8')), 'big') + return packing.unpack(base64.b64decode(six.ensure_binary(enc, 'utf-8')), 'all', endian='big') + +def decode_challenge(enc): + dec = enc.split('.') + if dec[0] != VERSION: + raise Exception('Unknown challenge version') + return list(map(decode_number, dec[1:])) + +def encode_challenge(arr): + return '.'.join([VERSION] + list(map(encode_number, arr))) + +def get_challenge(diff): + if HAVE_SECRETS: + x = secrets.randbelow(CHALSIZE) + else: + log.warning_once("kctf-pow is using random.randrange() which is not cryptographically secure") + x = random.randrange(CHALSIZE) + return encode_challenge([diff, x]) + +def solve_challenge(chal): + [diff, x] = decode_challenge(chal) + y = sloth_root(x, diff, MODULUS) + return encode_challenge([y]) + +#def can_bypass(chal, sol): +# from ecdsa import VerifyingKey +# from ecdsa.util import sigdecode_der +# if not sol.startswith('b.'): +# return False +# sig = bytes.fromhex(sol[2:]) +# with open("/kctf/pow-bypass/pow-bypass-key-pub.pem", "r") as fd: +# vk = VerifyingKey.from_pem(fd.read()) +# return vk.verify(signature=sig, data=bytes(chal, 'ascii'), hashfunc=hashlib.sha256, sigdecode=sigdecode_der) + +def verify_challenge(chal, sol, allow_bypass=False): + #if allow_bypass and can_bypass(chal, sol): + # return True + if allow_bypass: + raise NotImplementedError("allow_bypass is not supported") + [diff, x] = decode_challenge(chal) + [y] = decode_challenge(sol) + res = sloth_square(y, diff, MODULUS) + return (x == res) or (MODULUS - x == res) + +# def usage(): +# sys.stdout.write('Usage:\n') +# sys.stdout.write('Solve pow: {} solve $challenge\n') +# sys.stdout.write('Check pow: {} ask $difficulty\n') +# sys.stdout.write(' $difficulty examples (for 1.6GHz CPU) in fast mode:\n') +# sys.stdout.write(' 1337: 1 sec\n') +# sys.stdout.write(' 31337: 30 secs\n') +# sys.stdout.write(' 313373: 5 mins\n') +# sys.stdout.flush() +# sys.exit(1) + +# def main(): +# if len(sys.argv) != 3: +# usage() +# sys.exit(1) +# +# cmd = sys.argv[1] +# +# if cmd == 'ask': +# difficulty = int(sys.argv[2]) +# +# if difficulty == 0: +# sys.stdout.write("== proof-of-work: disabled ==\n") +# sys.exit(0) +# +# +# challenge = get_challenge(difficulty) +# +# sys.stdout.write("== proof-of-work: enabled ==\n") +# sys.stdout.write("please solve a pow first\n") +# sys.stdout.write("You can run the solver with:\n") +# sys.stdout.write(" python3 <(curl -sSL {}) solve {}\n".format(SOLVER_URL, challenge)) +# sys.stdout.write("===================\n") +# sys.stdout.write("\n") +# sys.stdout.write("Solution? ") +# sys.stdout.flush() +# solution = '' +# with os.fdopen(0, "rb", 0) as f: +# while not solution: +# line = f.readline().decode("utf-8") +# if not line: +# sys.stdout.write("EOF") +# sys.stdout.flush() +# sys.exit(1) +# solution = line.strip() +# +# if verify_challenge(challenge, solution): +# sys.stdout.write("Correct\n") +# sys.stdout.flush() +# sys.exit(0) +# else: +# sys.stdout.write("Proof-of-work fail") +# sys.stdout.flush() +# +# elif cmd == 'solve': +# challenge = sys.argv[2] +# solution = solve_challenge(challenge) +# +# if verify_challenge(challenge, solution, False): +# sys.stderr.write("Solution: \n".format(solution)) +# sys.stderr.flush() +# sys.stdout.write(solution) +# sys.stdout.flush() +# sys.stderr.write("\n") +# sys.stderr.flush() +# sys.exit(0) +# else: +# usage() +# +# sys.exit(1) +# +# if __name__ == "__main__": +# main() diff --git a/pwnlib/pow.py b/pwnlib/pow.py new file mode 100644 index 000000000..9825106a6 --- /dev/null +++ b/pwnlib/pow.py @@ -0,0 +1,74 @@ +""" +CTF challenges are sometimes guarded by Proof of Work (PoW) challenges. These +challenges require a connecting host to perform computational effort. Guarding +a service with a Proof of Work challenge slows down the rate at which a user +can connect to a service, reducing the effectiveness of brute-force techniques. + +Different Proof of Work algorithms require the connecting host to perform +different operations. The algorithms are not interoperable. You must choose the +correct algorithm to satisfy the server's requirements. As a connecting user, +you are normally told which Proof of Work algorithm is in use by the server. + +Pwntools provides an implementation of Google's kCTF Proof of Work algorithm. +""" + +from pwnlib.data.kctf.pow import solve_challenge as _kctf_pow_solve_challenge, \ + verify_challenge as _kctf_pow_verify_challenge, get_challenge as _kctf_get_challenge + + +def kctf_pow_solve(challenge): + """ + Solve a kCTF Proof of Work challenge + + Arguments: + `challenge` (str): The challenge to solve + + Returns: + A string representing an acceptable solution + + >>> challenge = 's.AAAB.AAAvm89LbEt4meEnXGwbHp3z' + >>> kctf_pow_solve(challenge)[:20] + '...' + 's.AAAo8s+2Q06cSBM4nf...' + >>> hashes.sha256sumhex(six.ensure_binary(kctf_pow_solve(challenge))) + 'fd13e60761fb4119848f2d7704100f8737c0ed754ef90f573cff74faac8ca800' + + >>> kctf_pow_verify(challenge, kctf_pow_solve(challenge)) + True + """ + return _kctf_pow_solve_challenge(challenge) + + +def kctf_pow_verify(challenge, solution): + """ + Verify a kCFT Proof of Work solution + + Arguments: + `challenge` (str): The challenge that was solved + `solution` (str): The solution to verify + + Returns: + True if the solution is acceptable, else False + + >>> challenge1 = kctf_pow_generate_challenge(1) + >>> challenge2 = kctf_pow_generate_challenge(1) + >>> kctf_pow_verify(challenge1, kctf_pow_solve(challenge1)) + True + + >>> kctf_pow_verify(challenge1, kctf_pow_solve(challenge2)) + False + """ + return _kctf_pow_verify_challenge(challenge, solution, False) + + +def kctf_pow_generate_challenge(difficulty): + """ + Generate a kCTF Proof of Work challenge + + Arguments: + `difficulty` (int): The challenge difficulty. A difficulty of 31337 can be solved in ~30 seconds + at 1.66GHz with gmpy2 installed + + Returns: + A string representing the challenge + """ + return _kctf_get_challenge(difficulty) \ No newline at end of file diff --git a/pwnlib/util/packing.py b/pwnlib/util/packing.py index 9af06bfc8..f9acb4728 100644 --- a/pwnlib/util/packing.py +++ b/pwnlib/util/packing.py @@ -165,7 +165,7 @@ def pack(number, word_size = None, endianness = None, sign = None, **kwargs): return b''.join(reversed(out)) @LocalNoarchContext -def unpack(data, word_size = None): +def unpack(data, word_size = None, **kwargs): r"""unpack(data, word_size = None, endianness = None, sign = None, **kwargs) -> int Packs arbitrary-sized integer.