Created
November 26, 2021 07:04
-
-
Save WangYihang/18b3a837203dc02a7fbd01e7480fee0e to your computer and use it in GitHub Desktop.
Solution for scz's XOR challenge (http://scz.617.cn:8/misc/202007101723.txt)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
''' | |
Solution for XOR challenge (http://scz.617.cn:8/misc/202007101723.txt) | |
''' | |
from termcolor import colored | |
from colorama import init | |
import string | |
import math | |
import shlex | |
init() | |
def padding(a, b): | |
''' | |
repeatly padding b to length(a) | |
eg: | |
a = "123456789" | |
b = "abcd" | |
padding(a, b) -> "abcdabcda" | |
''' | |
q = int(len(a) / len(b)) | |
r = len(a) % len(b) | |
return b * q + b[:r] | |
def xor(a, b): | |
if len(a) > len(b): | |
b = padding(a, b) | |
if len(b) < len(a): | |
a = padding(b, a) | |
assert len(a) == len(b) | |
r = [0 for _ in range(len(a))] | |
for i in range(len(a)): | |
r[i] = a[i] ^ b[i] | |
return bytes(r) | |
def wrap(a, b): | |
''' | |
wrap a to a array of string which length is 4 | |
eg: | |
a = "123456789" | |
b = 4 | |
wrap(a, b) -> ["1234", "5678", "9"] | |
''' | |
r = [] | |
for i in range(0, len(a), b): | |
r.append(a[i:i+b]) | |
return r | |
def is_english_article(ch): | |
return ch in string.ascii_letters + string.digits + " ,.?" | |
def is_base64(ch): | |
return ch in string.ascii_letters + string.digits + "+/=" | |
def is_printable(ch): | |
return ch in string.printable | |
def dump(data): | |
for value in data: | |
if chr(value) in string.printable: | |
print(chr(value), end="") | |
else: | |
print(".", end="") | |
def dump_with_index(data): | |
for index, value in enumerate(data): | |
if chr(value) in string.printable: | |
r = chr(value) | |
else: | |
r = "." | |
print("plain[{}] = {}".format(index, r)) | |
def dump_wrapped(data, length, hl_x=-1, hl_y=-1): | |
def hl_print(ch): | |
if is_printable(ch): | |
ch = ch | |
color = "green" | |
else: | |
ch = "?" | |
color = "red" | |
print(colored(ch, color), end="") | |
def unhl_print(ch): | |
if is_printable(ch): | |
ch = ch | |
else: | |
ch = "?" | |
print(ch, end="") | |
blocks = wrap(data, length) | |
for y, block in enumerate(blocks): | |
for x, value in enumerate(block): | |
if x == hl_x or y == hl_y: | |
print_func = hl_print | |
else: | |
print_func = unhl_print | |
ch = chr(value) | |
print_func(ch) | |
print() | |
def plain_should_be(cipher, cipher_index, plain_value, key): | |
fixed_key = key.copy() | |
fixed_key[cipher_index % | |
len(fixed_key)] = cipher[cipher_index] ^ ord(plain_value) | |
return fixed_key | |
def guess_key(data, key_length=64): | |
key = [0 for i in range(key_length)] | |
blocks = wrap(data, len(key)) | |
possible_values = {} | |
for i in range(len(key)): | |
possible_values[i] = [] | |
for j in range(0x100): | |
key[i] = j | |
all_prinable = True | |
for block in blocks: | |
plain = xor(key, block) | |
if not is_english_article(chr(plain[i])): | |
all_prinable = False | |
if all_prinable: | |
possible_values[i].append(j) | |
guessed_key = [0 for _ in range(key_length)] | |
succeed = 0 | |
for k, v in possible_values.items(): | |
if len(v) == 1: | |
guessed_key[k] = v[0] | |
succeed += 1 | |
if succeed == 0: | |
print("{} -> {:.2f}".format(key_length, succeed / len(key)), end="\r") | |
else: | |
print("{} -> {:.2f}".format(key_length, succeed / len(key)), end="\n") | |
return guessed_key | |
def interactive_guessing(data, key): | |
changes = [] | |
current_x = 0 | |
current_y = 0 | |
max_x = len(key) | |
max_y = math.ceil(len(data) / len(key)) | |
usage = '''Move: | |
[up|down|left|right] [value] | |
Set: | |
[value]''' | |
print(usage) | |
while True: | |
# dump memory | |
dump_wrapped(xor(data, key), len(key), hl_x=current_x, hl_y=current_y) | |
# parse arguments | |
args = shlex.split( | |
input(">> ").strip() | |
) | |
print(args) | |
if len(args) == 0: | |
continue | |
# set value | |
if len(args) == 1: | |
index = current_y * len(key) + current_x | |
value = args[0][0] | |
key = plain_should_be(data, index, value, key) | |
dump_wrapped(xor(data, key), len(key), | |
hl_x=current_x, hl_y=current_y) | |
changes.append((index, value)) | |
print(changes) | |
continue | |
# move header | |
if len(args) == 2: | |
direction = args[0] | |
value = int(args[1]) | |
print("moving {} steps towards {} from ({}, {})".format( | |
value, direction, current_x, current_y)) | |
if direction == "up": | |
current_y = (current_y - value) % max_y | |
elif direction == "down": | |
current_y = (current_y + value) % max_y | |
elif direction == "left": | |
current_x = (current_x - value) % max_x | |
elif direction == "right": | |
current_x = (current_x + value) % max_x | |
else: | |
pass | |
continue | |
def challenge_01(): | |
data = open("xor.bin", "rb").read() | |
key = guess_key(data) | |
# interactive_guessing(data, key) | |
changes = [(1, 'h'), (1, 'r'), (1, 'r'), (1, 'h'), (2, 'e'), (3, ' '), (14, 'o'), (20, 'e'), (23, 'r'), (26, 'e'), (55, 'c'), (183, 'l'), (291, 'v'), (293, 'c'), (741, 's'), (739, 'e'), | |
(478, 'a'), (351, 'O'), (362, 'n'), (382, ' '), (647, 'r'), (581, 'e'), (580, 'r'), (454, 'e'), (875, 'd'), (876, 'o'), (876, 'r'), (876, 'o'), (877, ' '), (877, '-'), (831, 'e')] | |
for index, value in changes: | |
key = plain_should_be(data, index, value, key) | |
print(key) | |
key = [190, 189, 128, 34, 3, 150, 173, 251, 17, 75, 13, 169, 164, 133, 124, 209, 237, 206, 2, 99, 62, 87, 84, 205, 200, 29, 166, 151, 158, 42, 250, 249, | |
176, 104, 1, 115, 179, 161, 105, 228, 124, 183, 238, 135, 101, 179, 218, 215, 152, 1, 41, 122, 156, 8, 116, 21, 231, 155, 161, 225, 115, 235, 196, 176] | |
print(xor(data, key).decode()) | |
challenge_01() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment