Hambone - Mrxbox98
Description
I hid the flag somewhere on the website as a 48 byte hex value, so I know you’ll never find it. Just, don’t check out how the background is calculated.
https://hambone.chall.pwnoh.io
Note: the flag will be in lowercase, not uppercase
File: dist.py
def get_distances(padded_url : str, flag_path : str):
distances = []
for i in range(3):
# calculate hamming distance on 16 byte subgroups
flag_subgroup = flag_path[i*32:i*32+32]
z = int(padded_url[i*32:i*32+32], 16)^int(flag_subgroup, 16)
distances.append(bin(z).count('1'))
return distances
Solution
Here is an annotated version of the code.
# Calculates the distance for the website background
# @param padded_url: The path in the request url; 96 length hexadecimal string
# @param flag_path: The path to get the flag; 96 length hexadecimal string
def get_distances(padded_url : str, flag_path : str):
distances = [] # The distances for the background
for i in range(3): # RGB Triplet
# calculate hamming distance on 16 byte subgroups
flag_subgroup = flag_path[i*32:i*32+32] # Gets 32 chars of the string based on which iteration it is on
z = int(padded_url[i*32:i*32+32], 16)^int(flag_subgroup, 16) # Gets the bit difference between the current 32 hex section and the flag 32 hex section
distances.append(bin(z).count('1')) # Adds the number of 1's to the distance
return distances # Returns the distance array
The path for the flag url and the solution url should be in this format
32 chars 32 chars 32 chars
╭──────────────────────────────╮ ╭──────────────────────────────╮ ╭──────────────────────────────╮
ac72c3ecbd95984a48a1890735da8c10 b7dd222b9addf2ab7b17778c6b8fc353 7852861c969f6738865996481438b29d
The path’s binary length is 384 and since the binary is being compared, the first step is to use binary then convert to hex before testing the string.
The python operator ^
compares the binary of two integers.
This is the exploit we can use to guess and check bits in our path.
Here is an example:
11000
01011
───── XOR Operator
10011
If the two bits are the same the binary of the background will have a 0 in that place and if they are different the binary will have a 1. Since the background is calculated based off of how many 1’s are in the binary difference, we can first check the background for all 0s then change each bit one by one and observing the change that occurs in the background. If the background hex increased, that means the bit we changed was wrong, if the background hex value decreases that means that the bit we changed was correct.
Example:
Zero path: 000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
Correct path: ac72c3ecbd95984a48a1890735da8c10b7dd222b9addf2ab7b17778c6b8fc3537852861c969f6738865996481438b29d
Binary:
000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
101011000111001011000011111011001011110110010101100110000100101001001000101000011000100100000111001101011101101010001100000100001011011111011101001000100010101110011010110111011111001010101011011110110001011101110111100011000110101110001111110000110101001101111000010100101000011000011100100101101001111101100111001110001000011001011001100101100100100000010100001110001011001010011101
────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
101011000111001011000011111011001011110110010101100110000100101001001000101000011000100100000111001101011101101010001100000100001011011111011101001000100010101110011010110111011111001010101011011110110001011101110111100011000110101110001111110000110101001101111000010100101000011000011100100101101001111101100111001110001000011001011001100101100100100000010100001110001011001010011101
Example:
Zero path: 000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
Correct path: ac72c3ecbd95984a48a1890735da8c10b7dd222b9addf2ab7b17778c6b8fc3537852861c969f6738865996481438b29d
Binary:
000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
101011000111001011000011111011001011110110010101100110000100101001001000101000011000100100000111001101011101101010001100000100001011011111011101001000100010101110011010110111011111001010101011011110110001011101110111100011000110101110001111110000110101001101111000010100101000011000011100100101101001111101100111001110001000011001011001100101100100100000010100001110001011001010011101
────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
101011000111001011000011111011001011110110010101100110000100101001001000101000011000100100000111001101011101101010001100000100001011011111011101001000100010101110011010110111011111001010101011011110110001011101110111100011000110101110001111110000110101001101111000010100101000011000011100100101101001111101100111001110001000011001011001100101100100100000010100001110001011001010011101
Number of 1s: 190:
000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001
101011000111001011000011111011001011110110010101100110000100101001001000101000011000100100000111001101011101101010001100000100001011011111011101001000100010101110011010110111011111001010101011011110110001011101110111100011000110101110001111110000110101001101111000010100101000011000011100100101101001111101100111001110001000011001011001100101100100100000010100001110001011001010011101
────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
101011000111001011000011111011001011110110010101100110000100101001001000101000011000100100000111001101011101101010001100000100001011011111011101001000100010101110011010110111011111001010101011011110110001011101110111100011000110101110001111110000110101001101111000010100101000011000011100100101101001111101100111001110001000011001011001100101100100100000010100001110001011001010011100
Number of 1s: 189
By testing each of the 384 bits one by one, we can find the value of each bit by checking if the number of 0s decreases or increases. Here is a small helper function to test our solutions using the python requests package:
def get_background(path : str):
req = requests.get(f"https://hambone.chall.pwnoh.io/{path}")
background = req.text.split("background: #")[1].split("\"")[0]
return int(background, 16)
Here is the final solution:
import requests
# Calculates the distance for the website background
# @param padded_url: The path in the request url; 96 length hexadecimal string
# @param flag_path: The path to get the flag; 96 length hexadecimal string
def get_distances(padded_url : str, flag_path : str):
distances = [] # The distances for the background
for i in range(3): # RGB Triplet
# calculate hamming distance on 16 byte subgroups
flag_subgroup = flag_path[i*32:i*32+32] # Gets 32 chars of the string based on which iteration it is on
z = int(padded_url[i*32:i*32+32], 16)^int(flag_subgroup, 16) # Gets the bit difference between the current 32 hex section and the flag 32 hex section
distances.append(bin(z).count('1')) # Adds the number of 1's to the distance
return distances # Returns the distance array
def get_background(path : str):
req = requests.get(f"https://hambone.chall.pwnoh.io/{path}")
background = req.text.split("background: #")[1].split("\"")[0]
return int(background, 16)
# The background when the value of the path is 0
zero_bg = "c6b4c5"
curr_path = ""
for i in range(384):
path = "0"*(383-i)+"1"+"0"*i
dis = hex(int(path, 2)).split("0x")[1].zfill(96)
print(f"Currently testing: {dis}")
result = get_background(dis)
zero = int(zero_bg, 16)
current = result
if current > zero:
curr_path = "1"+curr_path
else:
curr_path = "0"+curr_path
print(curr_path)
Output:
At the end the program will output
101011000111001011000011111011001011110110010101100110000100101001001000101000011000100100000111001101011101101010001100000100001011011111011101001000100010101110011010110111011111001010101011011110110001011101110111100011000110101110001111110000110101001101111000010100101000011000011100100101101001111101100111001110001000011001011001100101100100100000010100001110001011001010011101
which converted into hex is
ac72c3ecbd95984a48a1890735da8c10b7dd222b9addf2ab7b17778c6b8fc3537852861c969f6738865996481438b29d
which means the final path is this Chrome and some other browsers sometimes autocapitalize the path, but running
wget https://hambone.chall.pwnoh.io/ac72c3ecbd95984a48a1890735da8c10b7dd222b9addf2ab7b17778c6b8fc3537852861c969f6738865996481438b29d
creates a file with the contents:
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.2.2/dist/js/bootstrap.bundle.min.js" integrity="sha384-OERcA2EqjJCMA+/3y+gxIOqMEjwtxJY7qPCqsdltbNJuaOe923+mo//f6V8Qbsw3" crossorigin="anonymous"></script>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.2.2/dist/css/bootstrap.min.css" integrity="sha384-Zenh87qX5JnK2Jl0vWa8Ck2rdkQ2Bzep5IDxbcnCeuOxjzrPF/et3URy9Bv1WTRi" crossorigin="anonymous">
<title></title>
</head>
<body style="background: #ffffff">
<div id="content">
<div class='container-fluid my-5'>
<p style="color:#000000"> Whoa! How'd you find this? Guess I owe you the flag: buckeye{th3_b4ckgr0und_i5_n0t_4_l13} </p>
</div>
</div>
<div>
</body>
</html>
so the flag is buckeye{th3_b4ckgr0und_i5_n0t_4_l13}
Bonce - null
Description
nonce without a cipher
Given an encrypted message and a known stream cipher-esque encryption function, find the flag in the message.
File: bonce.py
import random
with open('sample.txt') as file:
line = file.read()
with open('flag.txt') as file:
flag = file.read()
samples = [line[i:i+28] for i in range(0, len(line) - 1 - 28, 28)]
samples.insert(random.randint(0, len(samples) - 1), flag)
i = 0
while len(samples) < 40:
samples.append(samples[len(samples) - i - 2])
i = random.randint(0, len(samples) - 1)
encrypted = []
for i in range(len(samples)):
x = samples[i]
if i < 10:
nonce = str(i) * 28
else:
nonce = str(i) * 14
encrypted.append(''.join(str(ord(a) ^ ord(b)) + ' ' for a,b in zip(x, nonce)))
with open('output.txt', 'w') as file:
for i in range(0, 4):
file.write('input: ' + samples[i] + '\noutput: ' + encrypted[i] + '\n')
file.write('\n')
for i in range(4, len(samples)):
file.write('\ninput: ???\n' + 'output: ' + encrypted[i])
File output.txt
input: Look in thy glass, and tell
output: 124 95 95 91 16 89 94 16 68 88 73 16 87 92 81 67 67 28 16 81 94 84 16 68 85 92 92 16
input: the face thou viewestNow is
output: 69 89 84 17 87 80 82 84 17 69 89 94 68 17 71 88 84 70 84 66 69 127 94 70 17 88 66 17
input: the time that face should fo
output: 70 90 87 18 70 91 95 87 18 70 90 83 70 18 84 83 81 87 18 65 90 93 71 94 86 18 84 93
input: rm another;Whose fresh repai
output: 65 94 19 82 93 92 71 91 86 65 8 100 91 92 64 86 19 85 65 86 64 91 19 65 86 67 82 90
input: ???
output: 70 20 93 82 20 90 91 67 20 64 92 91 65 20 90 91 64 20 70 81 90 81 67 81 71 64 24 96
input: ???
output: 93 90 64 21 81 90 70 65 21 87 80 82 64 92 89 80 21 65 93 80 21 66 90 71 89 81 25 21
input: ???
output: 67 88 84 90 83 69 69 22 69 89 91 83 22 91 89 66 94 83 68 24 112 89 68 22 65 94 83 68
input: ???
output: 82 23 94 68 23 68 95 82 23 68 88 23 81 86 94 69 23 64 95 88 68 82 23 66 89 82 86 69
input: ???
output: 218 8340 8474 92 24 79 87 85 90 124 81 75 92 89 81 86 75 24 76 80 93 24 76 81 84 84 89 95
input: ???
output: 92 25 86 95 25 77 81 64 25 81 76 74 91 88 87 93 75 64 6 118 75 25 78 81 86 25 80 74
input: ???
output: 17 88 84 16 66 95 17 86 94 94 85 16 70 89 93 92 17 82 84 16 69 88 84 16 69 95 92 82
input: ???
output: 126 87 17 89 88 66 17 66 84 93 87 28 93 94 71 84 29 17 69 94 17 66 69 94 65 17 65 94
input: ???
output: 66 70 84 64 88 70 72 13 101 90 94 71 17 83 67 70 17 70 89 75 17 95 94 70 89 87 67 208
input: ???
output: 8349 8465 66 19 86 95 80 64 66 31 17 82 95 87 17 64 89 86 17 90 95 19 69 91 84 86 114 82
input: ???
output: 93 88 66 20 83 85 82 95 17 64 89 81 17 88 94 66 84 88 72 20 112 68 67 93 93 20 94 82
input: ???
output: 17 93 84 71 17 69 67 92 92 80 11 102 94 21 69 93 94 64 17 65 89 71 94 64 86 93 17 66
input: ???
output: 88 88 85 89 70 69 17 89 87 22 69 94 88 88 84 22 80 81 84 22 66 94 80 90 93 22 66 83
input: ???
output: 84 115 84 68 65 94 69 82 17 88 87 23 70 69 88 89 90 91 84 68 17 67 89 94 66 23 69 95
input: ???
output: 72 24 86 87 93 92 84 86 17 76 88 85 84 22 115 77 69 24 88 94 17 76 89 87 68 24 93 81
input: ???
output: 83 76 82 82 84 64 84 66 66 86 92 92 110 74 80 64 110 74 94 84 84 95 88 74 89 3 24 68
input: ???
output: 68 85 30 16 64 85 95 85 95 82 87 66 208 8348 8464 84 18 94 93 68 18 68 93 16 80 85 30 116
input: ???
output: 91 84 18 66 91 95 85 93 87 29 18 80 92 85 18 69 90 88 92 84 18 88 95 80 85 84 18 85
input: ???
output: 68 87 30 18 64 87 95 87 95 80 87 64 208 8350 8464 86 18 92 93 70 18 70 93 18 80 87 30 118
input: ???
output: 64 19 91 85 18 93 93 68 18 71 90 92 71 19 92 92 70 19 64 86 92 86 69 86 65 71 30 103
input: ???
output: 126 91 93 95 18 93 92 20 70 92 75 20 85 88 83 71 65 24 18 85 92 80 18 64 87 88 94 20
input: ???
output: 65 65 87 71 91 65 75 10 102 93 93 64 18 84 64 65 18 65 90 76 18 88 93 65 90 80 64 215
input: ???
output: 71 88 80 90 87 69 65 22 65 89 95 83 18 91 93 66 90 83 64 24 116 89 64 22 69 94 87 68
input: ???
output: 87 23 91 68 18 68 90 82 18 68 93 23 84 86 91 69 18 64 90 88 65 82 18 66 92 82 83 69
input: ???
output: 64 85 18 89 92 87 70 80 87 74 9 111 90 87 65 93 18 94 64 93 65 80 18 74 87 72 83 81
input: ???
output: 68 92 30 25 64 92 95 92 95 91 87 75 208 8341 8464 93 18 87 93 77 18 77 93 25 80 92 30 125
input: ???
output: 65 16 90 86 19 94 92 71 19 68 91 95 70 16 93 95 71 16 65 85 93 85 68 85 64 68 31 100
input: ???
output: 65 92 19 80 93 94 71 89 86 67 8 102 91 94 64 84 19 87 65 84 64 89 19 67 86 65 82 88
input: ???
output: 86 18 92 84 19 70 91 75 19 90 70 65 81 83 93 86 65 75 12 125 65 18 68 90 92 18 90 65
input: ???
output: 86 19 90 64 19 64 91 86 19 64 92 19 85 82 90 65 19 68 91 92 64 86 19 70 93 86 82 65
input: ???
output: 86 20 92 82 19 64 91 77 19 92 70 71 81 85 93 80 65 77 12 123 65 20 68 92 92 20 90 71
input: ???
output: 95 89 64 21 81 84 80 94 19 65 91 80 19 89 92 67 86 89 74 21 114 69 65 92 95 21 92 83
input: ???
output: 86 22 92 80 19 66 91 79 19 94 70 69 81 87 93 82 65 79 12 121 65 22 68 94 92 22 90 69
input: ???
output: 69 82 31 23 65 82 94 82 94 85 86 69 209 8347 8465 83 19 89 92 67 19 67 92 23 81 82 31 115
input: ???
output: 8351 8474 64 24 84 84 82 75 64 20 19 89 93 92 19 75 91 93 19 81 93 24 71 80 86 93 112 89
input: ???
output: 65 84 19 88 93 86 71 81 86 75 8 110 91 86 64 92 19 95 65 92 64 81 19 75 86 73 82 80
Encryption Theory
The nonce for each output is given by the bonce.py
script. Each nonce is a 28 character string. The nonce is the index of the output repeated until it reaches 28 characters. For example, the nonce for output 0 is 0000000000000000000000000000
and the nonce for output 10 is 1010101010101010101010101010
.
The encryption process begins by splitting the input string into 28 character segments, and then inputting a 28 character flag randomly between the segments. Then, the input is padded into 40 segments by randomly repeating input phrases. Each segment is encrypted by XORing the Unicode encoding of each segment with the nonce of it’s index. For the example of the zero-th segment, this means that the input string Look in thy glass, and tell
is converted to Unicode, 76 111 111 107 32 105 110 32 116 104 121 32 103 108 97 115 115 44 32 97 110 100 32 116 101 108 108 32
, and then XORed with 0000000000000000000000000000
, which gives the output, 124 95 95 91 16 89 94 16 68 88 73 16 87 92 81 67 67 28 16 81 94 84 16 68 85 92 92 16
.
Solution Theory
Since the nonce is known for each output, it is possible to reverse engineer the initial plaintext. The XOR functions like a negative sign; (A XOR B) XOR B = A. Therefore, XORing the encrypted output with the known nonce will give back the plaintext.
Solution
# First, generate the string of nonces. Each character of the nonce will be XORed with a character of the ciphertext
nonces = ""
for i in range(10):
nonces = nonces + 28 * str(i)
for i in range(10,40):
nonces = nonces + 14 * str(i)
# Second, read and parse the output cipher texts from the output.txt file
outputs = []
puts = []
with open("output.txt", "r") as a:
out = a.read()
o = out.split("\n")
for i in o:
if i.find("input") == -1:
puts.append(i)
for i in range(len(puts)):
puts[i] = puts[i][8:]
for i in puts:
outputs.extend(i.split(" "))
while True:
try:
outputs.remove(" ")
except:
break
while True:
try:
outputs.remove("")
except:
break
# Third, decipher the message by XORing each cipher character with each nonce character
message = []
for i, j in zip(outputs, nonces):
message.append(int(i) ^ ord(j))
# Fourth, generate a string containing the entire plaintext
m = ""
for i in message:
m = m + chr(i)
# Fifth, print the entire plaintext and the flag
print(m)
print(m[m.find("buckeye"):m.find("buckeye")+28])
The final flag is found to be buckeye{some_say_somefish:)}
The plaintext is taken from William Shakespeare’s Sonnet 3.