-
Notifications
You must be signed in to change notification settings - Fork 1.7k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add kCTF pow.py #1974
base: dev
Are you sure you want to change the base?
Add kCTF pow.py #1974
Conversation
Is kctf pow on pypi? Maybe it would be better to include it from there, or to make them submit it to pypi... otherwise looks fine |
I couldn't find any implementations on there, granted I didn't look too hard. I've also come to learn that the canonical It's still TODO for me to make the vendored I think pwntools should consider dropping Python 2 compatibility one day soon, but that's another matter... I see it was most recently discussed on #1741.
Edited to add: Just for absolute clarity, #1318's |
Added wrapper around challenge generator
|
Also added a more complete test to pwnlib.util.misc.kctf_pow_solve() to ensure the changes to pow.py don't break compatibility with the canonical pow.py
Used the following to connect to a host which was running the canonical
Ran this stress test from a host running Python 2, a host running Python 3, and a host running Python 3 with |
This reverts commit c66a8c6.
Also brings back the use of secrets.randbelow() if Python 3 is being used
Ready for review. I'm happy to squash the commits if that's preferred. |
pwnlib/util/packing.py
Outdated
@@ -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, endianness = None, sign = None, **kwargs): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This doesn't work, since endianness
and sign
are both overwritten with the value from context
below. There are more occations where the docs differ from the real signature too.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah right. I lifted it from pack()
but I see it doesn't use the @LocalNoachContext
pattern
The linter, and I think my IDE, was complaining about the use of unpack()
with the unspecified argument endianness
Would it be most correct to do:
@LocalNoarchContext
def unpack(data, word_size = None, **kwargs):
Edit: For reference the linter was saying:
> pwnlib/data/kctf/pow.py:101:11: E1123: Unexpected keyword argument 'endian' in function call (unexpected-keyword-arg)
If this is expected I'm happy to undo the change to unpack()
Is there anything I can do to get this across the line? In the meantime can this please be given the label |
I've realised that a new module requires changes to |
1cd29fa
to
ec906ee
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This change is big. The proposed interface is ugly, making the change not particularly useful.
Pwntools is a CTF tool box. The preferred interface would be a function to 'de-pow' a tube, in a similar way that 'de-alarming' is done.
x = remote(...)
x.solve_kctf_pow()
x.sendline(b'EXPLOIT GOES HERE')
x.interactive()
A readme entry would be nice in such a case.
Do not copy code and then comment out 70% of it, hey!
Just copy the relevant functions, provide attribution where needed, make it nice&small and integrate it.
@@ -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): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Unnecessary change.
def unpack(data, word_size = None, **kwargs): | |
def unpack(data, word_size = None): |
Returns: | ||
A string representing the challenge | ||
""" | ||
return _kctf_get_challenge(difficulty) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
return _kctf_get_challenge(difficulty) | |
return _kctf_get_challenge(difficulty) | |
NL at end of file
# import os | ||
# import secrets | ||
# import socket | ||
# import sys | ||
# import hashlib |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
# import os | |
# import secrets | |
# import socket | |
# import sys | |
# import hashlib |
HAVE_GMP = True | ||
except ImportError: | ||
HAVE_GMP = False | ||
# sys.stderr.write("[NOTICE] Running 10x slower, gotta go fast? pip3 install gmpy2\n") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why even have commented-out code?
# sys.stderr.write("[NOTICE] Running 10x slower, gotta go fast? pip3 install gmpy2\n") |
# 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') |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do not use that six
thing if an explicit way exists, please...
# 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') | |
return packing.unpack(base64.b64decode(enc.decode('utf-8')), 'all', endian='big') |
#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) | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Commented code again...
#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 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() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
# 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() |
@justinsteven sorry for the long silence! Are you willing to keep working on this? I could have used this a few times and was always annoyed this isn't merged yet. I think having a |
@peace-maker I don't have time to work on this at the moment I'm afraid, please feel free to take over if you'd like :) |
This adds a very thin wrapper around Google's kCTF pow.py (https://github.com/google/kctf/blob/v1/docker-images/challenge/pow.py)
This is a draft and a request for comments. It should not be merged just yet.
Demo:
TODO:
pow.py
working under Python2 or give up tryingQuestions:
pwnlib.pow
?pow.py
? It is Apache 2.0 and I've adhered to the requirement of "You must cause any modified files to carry prominent notices stating that You changed the files"pow.py
, please reject this PR. I can't find a PyPi package containingpow.py
and don't want to make one.pow.py
where should it go? I'm assumingdata
is a bad place for it. Should alibraries
orlib
directory be created and referenced inLICENSE-pwntools.txt
as being a place in which third-party code lives?pow.py
should some effort be made to hide it from consumers ofpwn
? The following behavior seems a bit unwanted to me.