The Challenge

  • Name: Crypto Trail

  • Description: Uh oh, looks like my friend thought using multiple crypto algorithms meant he'd be safer! Show him it's quality over quantity!

    Cyphertext:

    00110101 01100001 00100000 00110011 00110011 00100000 00110100 01100001 00100000 00110111 00110010 00100000 00110101 01100001 00100000 00110110 01100100 00100000 00110011 00110101 00100000 00110111 00110001 00100000 00110110 00110101 00100000 00110011 00110010 00100000 00110110 00110100 00100000 00110100 00110110 00100000 00110110 00110010 00100000 00110110 01100001 00100000 00110100 00110110 00100000 00110101 01100001 00100000 00110101 01100001 00100000 00110110 01100011 00100000 00110011 00111001 00100000 00110110 01100011 00100000 00110101 00110011 00100000 00110100 00110111 00100000 00110100 00110110 00100000 00110110 00110110 00100000 00110110 00110011 00100000 00110100 00110100 00100000 00110100 00110010 00100000 00110101 01100001 00100000 00110101 00110101 00100000 00110101 00111000 00100000 00110011 00110000 00100000 00110011 01100100

  • Category: Cryptography

  • Points: 50

The Solution

  1. Convert each 8-bit binary string to an integer such that the integer represents the ASCII value of a character.
    • 5a 33 4a 72 ...
  2. The characters of the string represent hexadecimal numbers, convert the hex back into ASCII to get a Base64 encoded string.
    • Z3JrZm5qe2dFbjFZZl9lSGFfcDBZUX0=
  3. Decode the Base64 string.
    • grkfnj{gEn1Yf_eHa_p0YQ}
  4. Decode the ROT13 encoded string into the flag:
    • texsaw{tRa1Ls_rUn_c0LD}

In Python

  1. Save to a file script.py
  2. Run with python > python script.py
    import base64
    
    def decode_shift_cipher(cipher: str, shift_amount: int = 13):
    	"""Shifts the alphabet characters in the provided string by `shift_amount` places"""
    	flag = ""
    	for c in cipher:
    		if c.isalpha():
    			base = ord('A') if c.isupper() else ord('a')
    			c_shifted = (ord(c) - base + shift_amount) % 26
    			c = chr(c_shifted + base)
    		flag = f"{flag}{c}"
    	return flag
    
    cipher_text = """00110101 01100001 00100000 00110011 00110011 00100000 00110100 01100001 00100000 00110111 00110010 00100000 00110101 01100001 00100000 00110110 01100100 00100000 00110011 00110101 00100000 00110111 00110001 00100000 00110110 00110101 00100000 00110011 00110010 00100000 00110110 00110100 00100000 00110100 00110110 00100000 00110110 00110010 00100000 00110110 01100001 00100000 00110100 00110110 00100000 00110101 01100001 00100000 00110101 01100001 00100000 00110110 01100011 00100000 00110011 00111001 00100000 00110110 01100011 00100000 00110101 00110011 00100000 00110100 00110111 00100000 00110100 00110110 00100000 00110110 00110110 00100000 00110110 00110011 00100000 00110100 00110100 00100000 00110100 00110010 00100000 00110101 01100001 00100000 00110101 00110101 00100000 00110101 00111000 00100000 00110011 00110000 00100000 00110011 01100100"""
    cipher_text = cipher_text.split()
    
    # Convert the cipher from binary to decimal, and then to characters.
    # ["00110101", "01100001", "00100000", "00110011"] =>'5a 33 4a 72 ...'
    hex_string = ''.join(chr(int(binary_str, 2)) for binary_str in cipher_text)
    
    # Convert the hex into ASCII
    shifted_string = bytes.fromhex(hex_string).decode()
    
    # shifted_string = 'grkfnj{gEn1Yf_eHa_p0YQ}'
    b64 = base64.b64decode(shifted_string).decode()
    print(decode_shift_cipher(b64, 13))
    

Using CyberChef

  • The recipe is:
    From_Binary('Space',8)
    From_Hex('Space')
    From_Base64('A-Za-z0-9+/=',true,false)
    ROT13(true,true,false,13)
    

The Steps

1. Converting to Decimal

  1. The cipher text has several 8-bit binary strings separated by spaces. Since a byte is 8-bits and one character is a byte (in ASCII) there's a good chance that each of these 8-bit binary strings are characters. In order to find what character they may represent we need to convert the binary into decimal.
    • Manually

      • To convert the binary to decimal by hand, we need to number each position from right-to-left starting at 0. For example:
      00110101
      76543210
      • Then starting from left to right we multiply each binary bit by \(2\) to the power of the position \(n\), and sum the results.
        • \((0 * 2 ^ 7) + (0 * 2 ^ 6) + (1 * 2 ^ 5) + (1 * 2 ^ 4) + (0 * 2 ^ 3) + (1 * 2 ^ 2) + (0 * 2 ^ 1) + (1 * 2 ^ 0)\)
      • Since anything multiplied by \(0\) is \(0\), we can leave out the \( 0*2^n \)'s
        • \( \rightarrow (1 * 2 ^ 5) + (1 * 2 ^ 4) + (1 * 2 ^ 2) + (1 * 2 ^ 0) \)
        • \( \rightarrow 2 ^ 5 + 2 ^ 4 + 2 ^ 2 + 2 ^ 0 \)
        • \( \rightarrow 32+16+4+1 \)
        • \( \rightarrow 53 \)
      • So \( 00110101_2=53_{10} \)
    • Luckily many languages, including python can do these conversions for us like so:

      • int("00110101", 2)
    • So let's convert each binary string to decimal

      cipher_text = """00110101 ... 01100100""" # Copy the whole cipher text here
      
      # Split the string by each space into an array.
      #     "00110101 01100100" becomes ["00110101", "01100100"] and so on.
      cipher_text = cipher_text.split()
      
      # Iterate through each element
      for binary_str in cipher_text:
          # Convert the binary to decimal and print the result
          print(int(binary_str, 2), end=" ")
      
      # The result
      53 97 32 51 51 32 52 97 32 55 50 32 53 97 32 54 100 32 51 53 32 55 49 32 54 53 32 51 50 32 54 52 32 52 54 32 54 50 32 54 97 32 52 54 32 53 97 32 53 97 32 54 99 32 51 57 32 54 99 32 53 51 32 52 55 32 52 54 32 54 54 32 54 51 32 52 52 32 52 50 32 53 97 32 53 53 32 53 56 32 51 48 32 51 100
      

2. Decimal to ASCII

  1. This indeed looks like a set of ASCII characters! Let's convert the integers to ASCII using chr(n).
    for binary_str in cipher_text:
    	n = int(binary_str, 2)
    	print(chr(n), end="")
    
    # The result
    5a 33 4a 72 5a 6d 35 71 65 32 64 46 62 6a 46 5a 5a 6c 39 6c 53 47 46 66 63 44 42 5a 55 58 30 3d
    

3. Hex to Decimal to ASCII

  1. Those are hex characters! Let's save this output and convert the hex back to decimal using int(n, 16) . We're also going to modify our for loops and use list comprehension so that we can save the results to a variable.

    • Note: You can convert hex to decimal the same way you convert binary to decimal. But instead of 1's and 0's use 0-15 (A is 10, and F is 15) and multiply that by 16 instead of 2. For example:
      • \( 5a_{16} = (5 * 16 ^ 1 + 10 * 16 ^ 0) _ {10} = 90_{10} \)
    # Convert the cipher from binary to decimal, and then to characters.
    # ["00110101", "01100001", "00100000", "00110011"] =>'5a 33 4a 72 ...'
    hex_string = ''.join(chr(int(binary_str, 2)) for binary_str in cipher_text)
    
    # Split the hex string by each space and save it as an array
    #    '5a 33 4a 72 ...' => ['5a', '33', '4a', '72', ...]
    hex_string = hex_string.split()
    
    # Convert each hex element to decimal and print it
    for hex_chr in hex_string:
    	print(int(hex_chr, 16), end=" ")
    
    # The result
    90 51 74 114 90 109 53 113 101 50 100 ...
    
  2. This once again, looks like ASCII ordinals. Let's convert these to characters using chr(n) again.

    # Split the hex string by each space and save it as an array
    #    '5a 33 4a 72 ...' => ['5a', '33', '4a', '72', ...]
    hex_string = hex_string.split()
    for hex_chr in hex_string:
    	print(chr(int(hex_chr, 16)), end="")
    
    # The result
    Z3JrZm5qe2dFbjFZZl9lSGFfcDBZUX0=
    

4. Decoding Base64

  1. Base64 allows binary data to be represented as printable characters by taking 6-bits of binary at a time, and converting that into a character. Notably, Base64 uses the = symbol for padding, when you see a few = signs at the end of a string especially in the context of web traffic (or CTFs), there's a good chance it's a Base64 encoded string. So let's try and decode the string.

    # List comprehension for converting the hex to ascii characters
    hex_string = hex_string.split()
    hex_string = ''.join(chr(int(hex_chr, 16)) for hex_chr in hex_string)
    
    # Print the decoded Base64
    print(base64.b64decode(hex_string).decode())
    
    # The result
    grkfnj{gEn1Yf_eHa_p0YQ}
    

5. Decrypting the Shift Cipher

  1. We can see from this result the structure of a flag. But the text doesn't quite seem there yet. We don't have any kind of key so it's unlikely to be a Vigenere Cipher, so we can try a simple shift cipher, increasing the shift until it looks right! (Alternatively, we can guess the first part of the flag and do basic subtraction but I feel the former is more instructive.)
  • Note: To learn how this code works line by line, I recommend reading this challenge.

    # grkfnj{gEn1Yf_eHa_p0YQ}
    b64 = base64.b64decode(hex_string).decode()
    for shift in range(1, 26):
    	print(f"Shift amount: {shift} - ", end="")
    	for c in b64:
    		if c.isalpha():
    			base = ord('A') if c.isupper() else ord('a')
    			c_shifted = (ord(c) - base + shift) % 26
    			c = chr(c_shifted + base)
    		print(c, end='')
    	print("")
    
    # The results
    Shift amount: 1 - hslgok{hFo1Zg_fIb_q0ZR}
    Shift amount: 2 - itmhpl{iGp1Ah_gJc_r0AS}
    Shift amount: 3 - juniqm{jHq1Bi_hKd_s0BT}
    Shift amount: 4 - kvojrn{kIr1Cj_iLe_t0CU}
    Shift amount: 5 - lwpkso{lJs1Dk_jMf_u0DV}
    Shift amount: 6 - mxqltp{mKt1El_kNg_v0EW}
    Shift amount: 7 - nyrmuq{nLu1Fm_lOh_w0FX}
    Shift amount: 8 - ozsnvr{oMv1Gn_mPi_x0GY}
    Shift amount: 9 - patows{pNw1Ho_nQj_y0HZ}
    Shift amount: 10 - qbupxt{qOx1Ip_oRk_z0IA}
    Shift amount: 11 - rcvqyu{rPy1Jq_pSl_a0JB}
    Shift amount: 12 - sdwrzv{sQz1Kr_qTm_b0KC}
    Shift amount: 13 - texsaw{tRa1Ls_rUn_c0LD} # THE FLAG!
    Shift amount: 14 - ufytbx{uSb1Mt_sVo_d0ME}
    Shift amount: 15 - vgzucy{vTc1Nu_tWp_e0NF}
    Shift amount: 16 - whavdz{wUd1Ov_uXq_f0OG}
    Shift amount: 17 - xibwea{xVe1Pw_vYr_g0PH}
    Shift amount: 18 - yjcxfb{yWf1Qx_wZs_h0QI}
    Shift amount: 19 - zkdygc{zXg1Ry_xAt_i0RJ}
    Shift amount: 20 - alezhd{aYh1Sz_yBu_j0SK}
    Shift amount: 21 - bmfaie{bZi1Ta_zCv_k0TL}
    Shift amount: 22 - cngbjf{cAj1Ub_aDw_l0UM}
    Shift amount: 23 - dohckg{dBk1Vc_bEx_m0VN}
    Shift amount: 24 - epidlh{eCl1Wd_cFy_n0WO}
    Shift amount: 25 - fqjemi{fDm1Xe_dGz_o0XP}
    
  1. Aha! There's the flag! Now we know the shift amount is 13, or more commonly a ROT13 cipher.

6. The Full Script

  1. I made some slight changes to the code above:

    • First, I moved the shift cipher logic into its own function (technically I reused the function from another solution I wrote)
      • (Again, for reference on how a shift cipher works this will explain the function line by line)
    • I made the hex to ASCII conversion a bit more robust with the instruction bytes.fromhex(hex_string).
    • And finally I removed the for shift in range(1, 26) loop since we know the shift amount is 13.
    import base64
    
    def decode_shift_cipher(cipher: str, shift_amount: int = 13):
    	"""Shifts the alphabet characters in the provided string by `shift_amount` places"""
    	flag = ""
    	for c in cipher:
    		if c.isalpha():
    			base = ord('A') if c.isupper() else ord('a')
    			c_shifted = (ord(c) - base + shift_amount) % 26
    			c = chr(c_shifted + base)
    		flag = f"{flag}{c}"
    	return flag
    
    # PLACE FULL TEXT HERE
    cipher_text = """00110101 ... 01100100"""
    cipher_text = cipher_text.split()
    
    # Convert the cipher from binary to decimal, and then to characters.
    # ["00110101", "01100001", "00100000", "00110011"] =>'5a 33 4a 72 ...'
    hex_string = ''.join(chr(int(binary_str, 2)) for binary_str in cipher_text)
    
    # Convert the hex into ASCII
    shifted_string = bytes.fromhex(hex_string).decode()
    
    # shifted_string = 'grkfnj{gEn1Yf_eHa_p0YQ}'
    b64 = base64.b64decode(shifted_string).decode()
    print(decode_shift_cipher(b64, 13))
    
  2. Run this script and the flag will print out: texsaw{tRa1Ls_rUn_c0LD}