# 2 ```python= def read_file(filename): ''' Read File ''' f = open(filename, "rb") txt = f.read() f.close() return txt def save_file(filename,content): f = open(filename, "wb") f.write(content) f.close() def xor_items(ptxt, ctxt): ''' ''' xor = bytes([ptxt[i]^ctxt[i] for i in range(len(ptxt))]) return xor def main(): ptxt = read_file('mssd.txt') ctxt = read_file('mssd.txt.enc') partial_keystream = xor_items(ptxt, ctxt) # partial 511 bytes are generated in chunkcs print(f'Partial key stream : {partial_keystream}') ``` Basically what this is doing is to see the partial keystream that was used to encrypt the text by XOR-ing the ptxt and the ctxt with each other. Results: ``` Partial key stream : b'Q\xbb\x98\xf4\x11\xe4\xf4\xe1\xe1\x85|,#\x02\xc0Ve\xb5\xd8\xa7Hu\x7f\xfdr\xf5<\xd3\x9d+oy\xf5\xcd\x8a\x1c\xd4%\x1c,,\xb6\x045\xa9\xfa_\xfb\xa7\xdfj\xe0\xca\x9f\x81\x07\x93\x1f\x04q\x86<\xb1\x89\x1eF\xaf\xc4}\xa6\xc5\xb5\xb5\xdat\x1f\xf9\x0f\xe0\r\xe0`?/O\x80\xbcp\x91\xc0u\x15\xb2\x04\xd5\xa8\xc3\xd2\xf1T\x06\xe2V\xde\xdeo\x9cA\t\xe0/\xe6/\xae\x011A\xbf\xc4\x96\x95^\x9e\xdf\xd2t\x7f\xfaXs\x14|\xf2*{bc0\x85\xdc\xe9/\xb0"\xb0\xf2|\x15\xdd\xc1T\x9b\x9ec\x82`s\x9c\x01\x0e\xeb\x11\xdd\x05\x12?\r\xaa)\x17\xb7d8\xb0\xd7\xab\xd7\x13\x84_g\\\xfc\x8d\x83(\xba\xae\x11\x85\xfc\xe2<Tg\xf6R\x01\xe7\xfe8Y\xd9$\x8b\xd7x\xfcx\xd0\xb5\xe0!e\x05\xa6\xb8\xba\xce\xf3T\xb7\x04+\x85\xfd!\x1a\xf3}!\x03\n\xe9i$\xadx\n\x05\nw\xde.,\xa7\xf7\xe2\xca\xcfC\x10\xfc\xd9t\xbd\xb6\x06\xacN\x11\x86-\xf9n98%\xf7\nn\xf7n\x19b\xb35\xe1\x18+O@X\xd7\x05h\x9c\xc7\xdb\xf3\xf5\xc3\xd5\xb26\t\xb3\x89\x0b\'\x19\xef\xb2\x19\xb3\xc9\xaa\xd1\x1e-J=A_\xeby\xf6;\x85Pm\x10\x1eX~\xd3\x1bi\xd1\xa8l!I1\xd3H\xd1H\xfftC\xb7\xce\x07\xdd\xe1=\x08\x1a\x0c\xb7\xf66\xd7\xc2k\x82qL\xb9u\xfa\xb4-\xc9\x15q\xcau\xca\x01\x9c\xd9\xd9Cpg,\x86\xeb\xcee\xd9\x1b\x1ay[<\xba\x14\xc5\xc9\x1e\x0e\xdc6I^\x15N\x1eN\xfc\x85hhX\x97!\xb5\xb2<C\xa6i\xccO\x89m\x85\xce\\WH\xc3\xe2e\x1bI\xe2\xdfB\xc2B\x05\xb6:;\xeb\x99,\xdf\xd3\x04Y\xe28E\xc1\xa9\xb6\xb7C\xe4y\xcbY+\xa6MI*`[Z\xdb\xf7\xda\x0f\r=\x884apti*\n\xd7\\\xf8\xdb\xd8X%\x88M\xe8\xbd\xe2\xc6I\xbe\xafm\xeem\x18o`g\x87\xab\x1c-\x17\x9d9\xben' ``` # 3 Using the obtained partial keystream, we decode `super_cipher.py.enc` ```python def read_file(filename): ''' Read File ''' f = open(filename, "rb") txt = f.read() f.close() return txt def save_file(filename,content): f = open(filename, "wb") f.write(content) f.close() def xor_items(ptxt, ctxt): ''' ''' xor = bytes([ptxt[i]^ctxt[i] for i in range(len(ptxt))]) return xor def main(): ptxt = read_file('mssd.txt') ctxt = read_file('mssd.txt.enc') partial_keystream = xor_items(ptxt, ctxt) # partial 511 bytes are generated in chunkcs print(f'Partial key stream : {partial_keystream}') super_cipher = read_file('super_cipher.py.enc') res = xor_items(partial_keystream,super_cipher) print(f'Partial text : {res}') # Save file to make it more easily readable save_file('super_cipher.py', res) ``` This is the result in `super_cipher.py` We can then use the same method to partially decrypt the gif, although it would be openable: Using this line in the code: ``` gif_cipher = read_file('hint.gif.enc') gif_res = xor_items(partial_keystream,gif_cipher) save_file('partial_hint.gif',gif_res) ``` Once the partial files are decrypted and saved, we can then move on to fully decrypting the enc files: This is the function to do it: ```python def generate_keystream(prelim, length): keystream = prelim[:N_BYTES] while len(keystream) < length: int_stream = next(int.from_bytes(keystream[-N_BYTES:], 'little')) keystream += int_stream.to_bytes(N_BYTES, 'little') return keystream[:length] ``` This is the code snippet that executes it. ```python full_super_keystream = generate_keystream(partial_keystream, len(super_cipher)) print(f'Length Full key stream : {len(full_super_keystream)}') full_result = xor_items(full_super_keystream,super_cipher) full_gif_keystream = generate_keystream(partial_keystream, len(gif_cipher)) full_gif_result = xor_items(full_gif_keystream,gif_cipher) save_file('full_super_cipher.py', full_result) save_file('full_hint.gif',full_gif_result) ``` The following are the obtained files after decrypting: ![](https://hackmd.io/_uploads/HklU4k1un.gif) ![](https://hackmd.io/_uploads/ryPwVkkdh.png) The `full_super_cipher.py` seem to be incomplete but since the original file is 512 bytes and the decrypted file is 512 bytes, it seems that there is all there is. File sizes: ![](https://hackmd.io/_uploads/ryM56ARvn.png) # 4 ```python3 #!/usr/bin/env python3 import argparse import sys parser = argparse.ArgumentParser() parser.add_argument("key") args = parser.parse_args() RULE = [101 >> i & 1 for i in range(8)] N_BYTES = 32 N = 8 * N_BYTES def next(x): x = (x & 1) << N+1 | x << 1 | x >> N-1 y = 0 for i in range(N): y |= RULE[(x >> i) & 7] << i return y # Bootstrap the PNRG keystream = int.from_bytes(args.key.encode(),'little') for i in range(N//2): keystream = next(keystream) # Encrypt / decrypt stdin to stdout plaint ``` What the code is doing: The code generates the keystream from the previous 32 bytes by performing 3 bitwise transformations, and then performing a lookup on an array named RULE. Based on the hint: ![](https://hackmd.io/_uploads/r1e1ahCDn.png) The given hint: ``` Initial: AXX…XXB (256-bit) , where x is 0 or 1, A is the MSB, B is the LSB. After: BAXX…XXBA (258-bit) ``` Using the hint given, we can now start to think about how we can use the Reversed rule table to generate the possible message. The following is the function: ```python def reverse(y): possible = [i for i in range(len(RULE)) if RULE[i] == (y & 1)] for j in range(1, N): new_possible = [] for k in possible: bit = (y >> j) & 1 bit2 = (k >> j) & 0b11 for i in range(len(RULE)): if RULE[i] == bit and (i & 0b11) == bit2: new_possible.append(k | (i << j)) possible = new_possible actual = list(filter(lambda k: (k >> 256) == (k & 0b11), possible)) if len(actual) != 1: print("No result!") print(len(actual)) exit(1) x = actual[0] x = (x >> 1) & ((1 << N) - 1) return x ``` In the reverse function, a list named `possible` (as a list) is created first. Next, it checks each element of the "RULE" list to see if it matches the least significant bit of `y` using bitwise & `( y & 1 )`. The index of the element in the `RULE` list is added to the `possible` list if a match is found. Then, the function is used to iterate over each value `k` in the `possible` list and extracts the bit value at the current bit position of `y` and the `bit2` value at the current bit position of the current value `k` in the "possible" list. The `bit2` value is obtained by shifting the current value `k` right by the current bit position and then bitwise & with the binary value `0b11`. The code then iterates over the length of the `RULE` list and checks if the extracted bit value from `y` matches the current element and if the extracted two-bit value from `k` matches the two least significant bits of the current element. If the conditions are true, it constructs a new value by bitwise | the current value "k" with the current element left-shifted by the current bit position. This new value is then added to the `new_possible` list. Using `filter` and `lambda` function to create a new list `actual` that filter values of k from the `possible` list that satisfied the specified condition The value of `x` is then shifted right by one bit, which removes the LSB that was added during the rule application process. Finally, the value is bitwise & with a value created by left-shifting 1 by N bits and subtracting 1. The `reverse` function is iterated `N//2` times, where N equals to 256 (32 * 8). In each iteration, the reverse function reversed the bits of the integer. This is python snippet: ```python keystream = partial_keystream[0:N_BYTES] k_stream = int.from_bytes(keystream, 'little') for i in range(N//2): k_stream = reverse(k_stream) print(f"Secret Seed : {k_stream.to_bytes(N_BYTES, 'little')}") ``` Results: ``` Secret Seed = b'{Remember2alwayshanginthere}\x00\x00\x00\x00' ``` ![](https://hackmd.io/_uploads/S1u-ZG1uh.png) ![](https://hackmd.io/_uploads/r1ogNGyd3.png)