{"id":21928609,"url":"https://github.com/nuvious/htb-iced-tea-walkthrough-and-writeup","last_synced_at":"2025-03-22T12:24:40.512Z","repository":{"id":251256484,"uuid":"819203256","full_name":"nuvious/HTB-Iced-Tea-Walkthrough-and-Writeup","owner":"nuvious","description":null,"archived":false,"fork":false,"pushed_at":"2024-06-24T03:34:41.000Z","size":4,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-03-20T08:14:53.905Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/nuvious.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2024-06-24T03:34:39.000Z","updated_at":"2024-08-01T17:12:12.000Z","dependencies_parsed_at":"2024-08-01T20:45:42.142Z","dependency_job_id":null,"html_url":"https://github.com/nuvious/HTB-Iced-Tea-Walkthrough-and-Writeup","commit_stats":null,"previous_names":["nuvious/htb-iced-tea-walkthrough-and-writeup"],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/nuvious%2FHTB-Iced-Tea-Walkthrough-and-Writeup","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/nuvious%2FHTB-Iced-Tea-Walkthrough-and-Writeup/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/nuvious%2FHTB-Iced-Tea-Walkthrough-and-Writeup/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/nuvious%2FHTB-Iced-Tea-Walkthrough-and-Writeup/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/nuvious","download_url":"https://codeload.github.com/nuvious/HTB-Iced-Tea-Walkthrough-and-Writeup/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":244955006,"owners_count":20537874,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-11-28T22:27:04.041Z","updated_at":"2025-03-22T12:24:40.477Z","avatar_url":"https://github.com/nuvious.png","language":"Python","funding_links":[],"categories":[],"sub_categories":[],"readme":"\n# Hack the Box (HTB) - Iced Tea Solution/Walkthrough\n\n- [Requirements](#requirements)\n- [Recon](#recon)\n- [Writing a Decrypt Function](#writing-a-decrypt-function)\n- [Getting the Flag](#getting-the-flag)\n- [Appendices](#appendices)\n  - [Appendix A - Cipher Source w/o CBC](#appendix-a---cipher-source-wo-cbc)\n  - [Appendix B - Final Solution Code](#appendix-b---final-solution-code)\n\n## Requirements\n\nFirst thing to get out of the way is to install the required cryptographic library:\n\n```bash\npip3 install pycryptodome\n```\n\n## Recon\n\nFirst thing to do is to preserve the output file for the challenge:\n\n```bash\ncp output.txt output.txt.original\n```\n\nNow we can create a `secret.py` file to test out the encryption function:\n\n```bash\ncat \u003e secret.py \u003c\u003c EOF\nFLAG=b'HTB{this_is_a_test_flag}'\nEOF\n```\n\nRunning the source with `python3 source.py` we get the following output in `output.txt`:\n\n```plaintext\nKey : bdda0bd3598ed634e306bf1c80079d5d\nCiphertext : db66762c67d553c3580bef46e8657a39bf40f7e2bc5127f8c594a23ad99210d1\n```\n\nLooking at the `__main__` logic:\n\n```python\nif __name__ == '__main__':\n    KEY = os.urandom(16)\n    cipher = Cipher(KEY)\n    ct = cipher.encrypt(FLAG)\n    with open('output.txt', 'w') as f:\n        f.write(f'Key : {KEY.hex()}\\nCiphertext : {ct.hex()}')\n```\n\nWe the `Cipher` instance isn't instantiated with a declared mode which means it defaults to `Mode.ECB`. Given that,\nlet's strip out any of the CBC logic as well as the `_xor` function which isn't referenced anywhere in the code.\nThe full reduced code is in [**Appendix A**](#appendix-a).\n\nLooking at the members of the `Cipher` class, we have:\n\n|Member|Value|\n|-|-|\n|BLOCK_SIZE|64|\n|KEY|The key divided into 4, 4 byte/32 bit blocks|\n|DELTA|0x9e3779b9|\n\nThe encrypt function pads and divides the message into 8 byte blocks and then encrypts them with the `encrypt_block` function:\n\n```python\n    def encrypt(self, msg):\n        msg = pad(msg, self.BLOCK_SIZE//8)\n        blocks = [msg[i:i+self.BLOCK_SIZE//8] for i in range(0, len(msg), self.BLOCK_SIZE//8)]\n\n        ct = b''\n        for pt in blocks:\n            ct += self.encrypt_block(pt)\n        return ct\n```\n\nThe `encrypt_block` function splits each 8 byte block into two 4 byte blocks; `m0` and `m1`:\n\n```python\n    def encrypt_block(self, msg):\n        m0 = b2l(msg[:4])\n        m1 = b2l(msg[4:])\n        ...\n```\n\nIt also generates a 32 bit mask of 1's:\n\n```python\n    def encrypt_block(self, msg):\n        ...\n        msk = (1 \u003c\u003c (self.BLOCK_SIZE//2)) - 1\n        ...\n```\n\nFinally the guts of the encryption. Over 32 rounds, a value s is incremented by `self.DELTA` each round. A series of\noperations are performed, shifting blocks, adding components of the key and XORing against the opposing message block\nwith the s value:\n\n```python\n    def encrypt_block(self, msg):\n        ...\n        s = 0\n        for i in range(32):\n            s += self.DELTA\n            m0 += ((m1 \u003c\u003c 4) + K[0]) ^ (m1 + s) ^ ((m1 \u003e\u003e 5) + K[1])\n            m0 \u0026= msk\n            m1 += ((m0 \u003c\u003c 4) + K[2]) ^ (m0 + s) ^ ((m0 \u003e\u003e 5) + K[3])\n            m1 \u0026= msk\n\n        m = ((m0 \u003c\u003c (self.BLOCK_SIZE//2)) + m1) \u0026 ((1 \u003c\u003c self.BLOCK_SIZE) - 1) # m = m0 || m1\n\n        return l2b(m)\n```\n\n## Writing a Decrypt Function\n\nThis approach will simply try to reverse the `encrypt_block` function. We can extend the `Cipher` class add the\ndecryption logic.\n\n```python\nfrom source import Cipher\nfrom Crypto.Util.Padding import unpad\nfrom Crypto.Util.number import bytes_to_long as b2l, long_to_bytes as l2b\n\n\nclass CipherWithDecrypt(Cipher):\n    ...\n```\n\nThe `decrypt` function is straightforward and just needs to chunk the cipher text into 8 byte blocks and run them into\na `decrypt_block` function:\n\n```python\nclass CipherWithDecrypt(Cipher):\n    ...\n    def decrypt(self, ct):\n        blocks = [ct[i:i+self.BLOCK_SIZE//8]\n                  for i in range(0, len(ct), self.BLOCK_SIZE//8)]\n\n        pt = b''\n        for block in blocks:\n            pt += self.decrypt_block(block)\n\n        return unpad(pt, self.BLOCK_SIZE//8)\n```\n\nFinally, we write the `decrypt_block` function. Key points are that `s` needs to be initialized at its final value\nin the `encrypt_block` function. Then we need to reverse the `m = m0 || m1` logic to split `m0` and `m1` into separate\nparts.\n\n```python\nclass CipherWithDecrypt(Cipher):\n    ...\n    def decrypt_block(self, ct):\n        m = b2l(ct)\n        msk = (1 \u003c\u003c (self.BLOCK_SIZE//2)) - 1\n\n        s = self.DELTA \u003c\u003c 5\n\n        m1 = m \u0026 msk\n        m0 = (m \u003e\u003e (self.BLOCK_SIZE//2)) \u0026 msk\n```\n\nNext we execute the 32 rounds, inverting the logic by manipulating `m1` before `m0` and replacing `+=` operators with\n`-=` operators. This is a naive approach, but the results speak for themselves.\n\n```python\nclass CipherWithDecrypt(Cipher):\n    ...\n    def decrypt_block(self, ct):\n        ...\n        K = self.KEY\n\n        for i in range(32):\n            m1 -= ((m0 \u003c\u003c 4) + K[2]) ^ (m0 + s) ^ ((m0 \u003e\u003e 5) + K[3])\n            m1 \u0026= msk\n            m0 -= ((m1 \u003c\u003c 4) + K[0]) ^ (m1 + s) ^ ((m1 \u003e\u003e 5) + K[1])\n            m0 \u0026= msk\n            s -= self.DELTA\n\n        pt = l2b((m0 \u003c\u003c 32) + m1)\n\n        return pt\n```\n\n## Getting the Flag\n\nFinally we add some logic to read in the `output.txt`, extract the key and ciphertext and run it through the decryption\nfunction:\n\n```python\nif __name__ == '__main__':\n    with open('output.txt', 'r') as f:\n        key_str = f.readline().split(\":\")[1].strip()\n        ct_str = f.readline().split(\":\")[1].strip()\n        key = bytes.fromhex(key_str)\n        ct = bytes.fromhex(ct_str)\n        cipher = CipherWithDecrypt(key)\n        pt = cipher.decrypt(ct)\n        print(pt)\n```\n\nRun against our test flag we get the desired output:\n\n```bash\n┌──(nuvious㉿kalinubflex)-[~/Downloads/crypto_iced_tea]\n└─$ python3 solution.py \nb'HTB{this_is_a_test_flag}'\n```\n\nThen to get the flag we simply need to restore the original output and run the solution one more time:\n\n```bash\n┌──(nuvious㉿kalinubflex)-[~/Downloads/crypto_iced_tea]\n└─$ mv output.txt.original output.txt \u0026\u0026 python3 solution.py \nb'HTB{n0t_th3_r3al_fl@g_0bv1ou5ly}'\n```\n\nFull source for the solution is provided in [**Appendix B**](#appendix-b).\n\n## Appendices\n\n### Appendix A - Cipher Source w/o CBC\n\n```python\nimport os\nfrom secret import FLAG\nfrom Crypto.Util.Padding import pad\nfrom Crypto.Util.number import bytes_to_long as b2l, long_to_bytes as l2b\n\n\nclass Cipher:\n    def __init__(self, key):\n        self.BLOCK_SIZE = 64\n        self.KEY = [b2l(key[i:i+self.BLOCK_SIZE//16]) for i in range(0, len(key), self.BLOCK_SIZE//16)]\n        self.DELTA = 0x9e3779b9\n\n    def encrypt(self, msg):\n        msg = pad(msg, self.BLOCK_SIZE//8)\n        blocks = [msg[i:i+self.BLOCK_SIZE//8] for i in range(0, len(msg), self.BLOCK_SIZE//8)]\n\n        ct = b''\n        for pt in blocks:\n            ct += self.encrypt_block(pt)\n        return ct\n\n    def encrypt_block(self, msg):\n        m0 = b2l(msg[:4])\n        m1 = b2l(msg[4:])\n        K = self.KEY\n        msk = (1 \u003c\u003c (self.BLOCK_SIZE//2)) - 1\n\n        s = 0\n        for i in range(32):\n            s += self.DELTA\n            m0 += ((m1 \u003c\u003c 4) + K[0]) ^ (m1 + s) ^ ((m1 \u003e\u003e 5) + K[1])\n            m0 \u0026= msk\n            m1 += ((m0 \u003c\u003c 4) + K[2]) ^ (m0 + s) ^ ((m0 \u003e\u003e 5) + K[3])\n            m1 \u0026= msk\n\n        m = ((m0 \u003c\u003c (self.BLOCK_SIZE//2)) + m1) \u0026 ((1 \u003c\u003c self.BLOCK_SIZE) - 1) # m = m0 || m1\n\n        return l2b(m)\n\n\nif __name__ == '__main__':\n    KEY = os.urandom(16)\n    cipher = Cipher(KEY)\n    ct = cipher.encrypt(FLAG)\n    with open('output.txt', 'w') as f:\n        f.write(f'Key : {KEY.hex()}\\nCiphertext : {ct.hex()}')\n```\n\n### Appendix B - Final Solution Code\n\n```python\nfrom source import Cipher\nfrom Crypto.Util.Padding import unpad\nfrom Crypto.Util.number import bytes_to_long as b2l, long_to_bytes as l2b\n\n\nclass CipherWithDecrypt(Cipher):\n    def decrypt(self, ct):\n        blocks = [ct[i:i+self.BLOCK_SIZE//8]\n                  for i in range(0, len(ct), self.BLOCK_SIZE//8)]\n\n        pt = b''\n        for block in blocks:\n            pt += self.decrypt_block(block)\n\n        return unpad(pt, self.BLOCK_SIZE//8)\n\n    def decrypt_block(self, ct):\n        m = b2l(ct)\n        msk = (1 \u003c\u003c (self.BLOCK_SIZE//2)) - 1\n\n        # s is incremented 32 times in the encrypt function so we need to start\n        # it at self.DELTA \u003c\u003c log(32,2) or 5\n        s = self.DELTA \u003c\u003c 5\n\n        # Next we need to reverse the m = m0 || m1\n        m1 = m \u0026 msk\n        m0 = (m \u003e\u003e (self.BLOCK_SIZE//2)) \u0026 msk\n\n        K = self.KEY\n\n        # Now we invert the operations by using -= instead of += and operate on m1 first, then m0. This is a naive\n        # approach, but the results speak for themselves\n        for i in range(32):\n            m1 -= ((m0 \u003c\u003c 4) + K[2]) ^ (m0 + s) ^ ((m0 \u003e\u003e 5) + K[3])\n            m1 \u0026= msk\n            m0 -= ((m1 \u003c\u003c 4) + K[0]) ^ (m1 + s) ^ ((m1 \u003e\u003e 5) + K[1])\n            m0 \u0026= msk\n            s -= self.DELTA\n\n        # Finally, we need to move m0 back to the front and append m1\n        pt = l2b((m0 \u003c\u003c 32) + m1)\n\n        return pt\n\nif __name__ == '__main__':\n    with open('output.txt', 'r') as f:\n        key_str = f.readline().split(\":\")[1].strip()\n        ct_str = f.readline().split(\":\")[1].strip()\n        key = bytes.fromhex(key_str)\n        ct = bytes.fromhex(ct_str)\n        cipher = CipherWithDecrypt(key)\n        pt = cipher.decrypt(ct)\n        print(pt)\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fnuvious%2Fhtb-iced-tea-walkthrough-and-writeup","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fnuvious%2Fhtb-iced-tea-walkthrough-and-writeup","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fnuvious%2Fhtb-iced-tea-walkthrough-and-writeup/lists"}