# Intechfest CTF 2024 [toc] # P2P ## Summary > I hosted my code repository on a P2P network. What could possibly go wrong? > author : nagi > file : [file](https://github.com/TCP1P/INTECHFEST-CTF-2024-Challenges-Public/blob/main/Forensics/P2P/dist/p2p.zip) we get a traffic from torrent traffic, in this case is bittorrent UDP ![image](https://hackmd.io/_uploads/r1PmOwc1yg.png) we can focus on conversation between `192.168.1.102` and `192.168.1.106` which one have biggest conversation in here. and based on description we know our task is recover the code repository, and from that we assume all data we need is in this traffic. ## Exploit ### Analyze bitTorrent header we know in p2p protocol we take partially file from various source (called swarm), and for first step leecher (who download file) will send tracker (web server torrent) a .torrent file who have hash of target file, the tracker will give random IP to leecher for get the file they wanted, after leecher get seeder (who have the target file) they will communicate to sharing a file. so in this case we already its must be a thing who define which part we want. of course leecher will give a `thing` for communicate with seeder. but unfortunately documentation about bitTorrent protocol still rarely and can't answer our question. so we need try on our device. ![image](https://hackmd.io/_uploads/BkYHcdqkyl.png) after testing on our device, writer find detail of header for bitTorrent protocol ``` - request : - |msg len |type |index |begin offset|piece len | - |4 byte |1 byte|4 byte |4 byte |4 byte | - (ex. 0000000d06000000050000000000004000) - |00 00 00 0d|06 |00 00 00 05|00 00 00 00 |00 00 40 00 | - send : - |data len |type |index |begin offset| data ... - |4 byte |1 byte|4 byte |4 byte | ... - (ex. 00004009070000000a00000000) - |00 00 40 09|07 |00 00 00 0a|00 00 00 00 | cc 5f ... - have : - |msg len |type |index | - |4 byte |1 byte|4 byte | - (ex. 000000050400000009) - |00 00 00 05|04 |00 00 00 09| ``` the different of writer traffic and chall traffic is in chall traffic they used UDP transport by default so file will sent by smaller packet. so first we can try dump all data to be parsed by header ### get all data from traffic from dump all data from traffic writer using tshark from dumping data from seeder (in this case is 192.168.1.102) and with few filter for avoid junk in traffic ```shell % tshark -r p2p.pcapng -Y '(ip.dst == 192.168.1.106 or ip.src == 192.168.1.106) and frame.len > 140 and frame.number > 38' -Tfields -e bt-utp.data | tr -d '\n' > datafull.txt ``` after dump all data sended by seeder, we can parse it using header we already know, full script below ```python from binascii import unhexlify, hexlify dats = open('datafull.txt','r').read().strip('\n') def parse_head(datt): print(datt) length = int(datt[0:8], 16) type = int(datt[8:10], 16) index = int(datt[10:18], 16) begin = int(datt[18:26], 16) print(length, type, index, begin) if begin > 1: off = False else: off = True return length, index, off def parse_from_dat(data): dats = [] for x in range(0, 87): try: length = int(data[0:8], 16) type = int(data[8:10], 16) index = int(data[10:18], 16) begin = int(data[18:26], 16) data1 = data[26:8+length*2] data_wh = data[:8+length*2] print(x, data[0:26]) dats.append(data_wh) data = data[8+length*2:] print(length, type, index, begin) print('-'*30) except Exception as e: print(e) break return dats def write_to_id(data): dat_dict = dict() data = parse_from_dat(data) for x in data: l, id, st = parse_head(x[0:26]) dat = x[26:] if id in dat_dict.keys(): if st: dat_dict[id] = dat + dat_dict[id] else: dat_dict[id] = dat_dict[id] + dat else: dat_dict[id] = dat sort_dat_dict = dict(sorted(dat_dict.items())) print(sort_dat_dict.keys()) print(len(sort_dat_dict)) tmp = open(f'full_data_tor2.txt','ab') for x in range(0, len(sort_dat_dict)): tmp.write(unhexlify(sort_dat_dict[x])) tmp.close() write_to_id(dats) ``` and the result will be like this ![image](https://hackmd.io/_uploads/Hy5Cbqs1yx.png) now the next step is recover code repository and from this we know is a git ### git reconstruct for parsing raw data to be a git directory we must know what inside in there. structure git directory is like this ![image](https://hackmd.io/_uploads/rybWHao1Jx.png) in git directory there is strings and object, for strings we can identified easily (you can analyze your own git directory for understand further) ![image](https://hackmd.io/_uploads/Hy8PDToyyg.png) ![image](https://hackmd.io/_uploads/B12uvaskkg.png) ![image](https://hackmd.io/_uploads/B1H5wTsykg.png) we already get `HEAD`,`description`, and `config`. after that we can search `index` by signature `DIRC` ![Screenshot from 2024-10-15 17-52-39](https://hackmd.io/_uploads/Hk9pfzny1g.png) and for other data we can confirm its a object who compressed by zlib(with signature 78 01 and usually contain `blob` string). so we already know this raw data have this ```tex HEAD config description index objects/ ``` because object is zlib compressed we can try decompress for parsing all zlib object using regex for get the header and first 16 byte (zlib can be compressed using first 16 byte and 32 byte). ```python import re import zlib f = open('full_data_tor2.txt', 'rb').read() rule = re.compile(rb'x\x01.{16}', flags=re.S) matches = rule.findall(f) def check_zlib(dat): try: z = zlib.decompressobj() z.decompress(dat) return True except zlib.error: return False head = dict() for m in matches: if check_zlib(m): head[m[:4]] = True print(len(head)) for x in head.keys(): f = f.replace(x, b'[PEMBAGI]'+x) f1 = f.split(b'[PEMBAGI]') for x in range(len(f1)): data = f1[x] match = check_zlib(data) if not match: print(x, data) print("="*32) else: tmp = open(f'result/{x}','wb') tmp.write(data) tmp.close() ``` after that we can reconstruct git directory. but first we must know that for .git/object is special case for file name ![image](https://hackmd.io/_uploads/rk-pR-2kyl.png) ![image](https://hackmd.io/_uploads/B18kkf211e.png) ### recover git object for file name object is SHA1 of stored object, with 2 first byte became folder and the rest became file name. below script for parsing data to git object correct directory. ```python import os import zlib from hashlib import sha1 listdir = os.listdir('result') for f in listdir: m = open(f'result/{f}','rb').read() z = zlib.decompressobj() dm = z.decompress(m) res= sha1(dm).hexdigest() print(res) try: os.mkdir(f'object/{res[:2]}') os.system(f'cat result/{f} > object/{res[:2]}/{res[2:]}') except: os.system(f'cat result/{f} > object/{res[:2]}/{res[2:]}') ``` below is SHA1 of all object ```tex 29d471d425e52adce9b90e1b4e6d9762ab08c89c 154e639824793fa22aa262dcb63816d3be39a82d a5ff75e1dfbad7074afd76d5ac4d7c70f8f5ed9e fc3534c646f96685e17c3fef2d4f24fec1d0bdf0 115bdcc5bd5003a7b8f3ee380a0d070d5edfd61c a1bb9b66b4b4efcac3c392934eafe3ea93a58710 a0f4c6c00b73e92b9bf3feebdd7384446922b6c6 79fa8a1f9f7cdfbcb09d7b760caa4d3c76887a1c 3ba7574d23047b6909e4af54e2a29cd2045fcfa3 50e0c3d3e14059463ab27ca781e05606213068c7 0079a9d313ea129ad0ef2920213256743ea6d68b 519435d6324303f8d27dfc306a01f23ff35c875c 36309f85facae157be2334677ea2659930e2ae0c 73f220c214bedac641e0ef2934da9386455e301b 2dfaa09e8ac13d368465a76b2f79fa48f18505d2 dd9c3056608769d0b08a57ad0381fa76fbec3452 b9e2dbaa4023f9f82f72e4de4aab020bc04c8d7d .... ``` now we can reconstruct git directory ```shell % ls -la total 12 drwxrwxr-x 3 bleco bleco 4096 Okt 15 23:06 . drwxrwxr-x 4 bleco bleco 4096 Okt 15 22:57 .. drwxrwxr-x 3 bleco bleco 4096 Okt 15 23:06 .git % ls -la .git total 16 drwxrwxr-x 3 bleco bleco 4096 Okt 15 23:06 . drwxrwxr-x 3 bleco bleco 4096 Okt 15 23:06 .. -rw-rw-r-- 1 bleco bleco 0 Okt 15 22:58 config -rw-rw-r-- 1 bleco bleco 0 Okt 15 22:58 description -rw-rw-r-- 1 bleco bleco 0 Okt 15 22:58 HEAD -rw-rw-r-- 1 bleco bleco 105 Okt 15 23:01 index drwxrwxr-x 125 bleco bleco 4096 Okt 15 22:54 objects ``` ### get git log when we try to `git log` we got this error ```shell % git log fatal: not a git repository (or any parent up to mount point /) Stopping at filesystem boundary (GIT_DISCOVERY_ACROSS_FILESYSTEM not set). ``` we can fix it using `git init` ```shell % git init Reinitialized existing Git repository in /home/bleco/Desktop/CTF/intechfests/p2p/for hackmd/gitfolder/.git/ % git log fatal: your current branch 'main' does not have any commits ye ``` this error because we dont have commit id in `refs/head/main` so it must be a commit id value transferred in raw data. we can search it using this command ```shell % grep -a -oE '[a-fA-F0-9]{28}' full_data_tor2.txt cf70e0022806285755330cf18252 ``` we can assume that is value of `refs/head/main` put it there and try to checkout to last commit ```shell % git checkout cf70e0022806285755330cf18252a83bae1170e5 HEAD is now at cf70e00 Delete files % git log commit cf70e0022806285755330cf18252a83bae1170e5 Author: hanasuru <faqih.insani@ymail.com> Date: Mon Aug 26 12:22:33 2024 +0700 Delete files commit d512ddc863ba33f51685abb23eb54f0b71eb0de1 Author: hanasuru <faqih.insani@ymail.com> Date: Mon Aug 26 12:22:33 2024 +0700 Encrypt file commit dc546ef25e154530cad0b3ec401027104da35d52 Author: hanasuru <faqih.insani@ymail.com> Date: Mon Aug 26 12:22:33 2024 +0700 Encrypt file .... commit e2a66f1b73faba57aad336eb679400be9688aa02 Author: hanasuru <faqih.insani@ymail.com> Date: Mon Aug 26 12:22:33 2024 +0700 Encrypt file commit ef352e88b648742c399d497ca12e6c71761d8620 Author: hanasuru <faqih.insani@ymail.com> Date: Mon Aug 26 12:22:33 2024 +0700 Encrypt file commit 479140077b56db68f124f6f4a63472c914479c4d Author: hanasuru <faqih.insani@ymail.com> Date: Mon Aug 26 12:22:33 2024 +0700 Initial commit % ls -la total 16 drwxrwxr-x 3 bleco bleco 4096 Okt 18 18:03 . drwxrwxr-x 5 bleco bleco 4096 Okt 18 18:02 .. -rwxrwxr-x 1 bleco bleco 1290 Okt 18 18:03 encrypt.pyc drwxrwxr-x 8 bleco bleco 4096 Okt 18 18:04 .git ``` so it seems this repo is like encrypt file then commit. we can check on initial commit but get this error ```shell % git checkout 479140077b56db68f124f6f4a63472c914479c4d fatal: bad object refs/heads/main ``` try to fix it, but know we know what causes error ```shell % rm .git/refs/heads/main % git gc warning: reflog of 'HEAD' references pruned commits error: garbage at end of loose object 'b9e2dbaa4023f9f82f72e4de4aab020bc04c8d7d' fatal: loose object b9e2dbaa4023f9f82f72e4de4aab020bc04c8d7d (stored in .git/objects/b9/e2dbaa4023f9f82f72e4de4aab020bc04c8d7d) is corrupt fatal: failed to run repack ``` it seems because first we dont know value of `refs/head/main` is in raw data, we can confirm it by see the corrupt file ```shell % cat .git/objects/b9/e2dbaa4023f9f82f72e4de4aab020bc04c8d7d x+)JMU040c040075UH�K.�,(�+�Lf��x����ye�����g�_�������D!-'1]���zy��2Ku&��!�͹�~1TMvj%C���z�B����G-V߹[�����-Kcf70e0022806285755330cf18252a83bae1170e5 ``` try remove the trail data, and try again ```shell % git gc warning: reflog of 'HEAD' references pruned commits Enumerating objects: 164, done. Counting objects: 100% (164/164), done. Delta compression using up to 16 threads Compressing objects: 100% (162/162), done. Writing objects: 100% (164/164), done. Total 164 (delta 14), reused 0 (delta 0), pack-reused 0 warning: reflog of 'HEAD' references pruned commits error: object file .git/objects/da/39a3ee5e6b4b0d3255bfef95601890afd80709 is empty fatal: unable to get object info for da39a3ee5e6b4b0d3255bfef95601890afd80709 fatal: failed to run prune ``` yup its show error again and all our objects file is gone, now we can try to built again .git directory with adjusted objects file (after trail data got removed) using same step ![Screenshot from 2024-10-18 18-20-27](https://hackmd.io/_uploads/rk8Rrpklyl.png) ```shell % git status On branch main nothing to commit, working tree clean % git checkout cf70e0022806285755330cf18252a83bae1170e5 Note: switching to 'cf70e0022806285755330cf18252a83bae1170e5'. You are in 'detached HEAD' state. You can look around, make experimental changes and commit them, and you can discard any commits you make in this state without impacting any branches by switching back to a branch. If you want to create a new branch to retain commits you create, you may do so (now or later) by using -c with the switch command. Example: git switch -c <new-branch-name> Or undo this operation with: git switch - Turn off this advice by setting config variable advice.detachedHead to false HEAD is now at cf70e00 Delete files % git checkout 479140077b56db68f124f6f4a63472c914479c4d Previous HEAD position was cf70e00 Delete files HEAD is now at 4791400 Initial commit % ls -la total 16 drwxrwxr-x 3 bleco bleco 4096 Okt 18 18:27 . drwxrwxr-x 5 bleco bleco 4096 Okt 18 18:25 .. -rwxrwxr-x 1 bleco bleco 1290 Okt 18 18:27 encrypt.pyc drwxrwxr-x 5 bleco bleco 4096 Okt 18 18:27 .git ``` now we can use .git directory properly ### analyze commit we have a .pyc we can try to decompile it using [https://pylingual.io](https://pylingual.io) and this is the result ```python # Decompiled with PyLingual (https://pylingual.io) # Internal filename: encrypt.py # Bytecode version: 3.10.0rc2 (3439) # Source timestamp: 2024-08-26 03:27:11 UTC (1724642831) import os import sys class Encrypt: def __init__(self, key): self.S = list(range(256)) j = 0 for i in range(256): j = (j + self.S[i] + key[i % len(key)]) % 256 self.S[i], self.S[j] = (self.S[j], self.S[i]) self.i = 0 self.j = 0 def encrypt(self, data): encrypted_data = bytearray() for b in data: self.i = (self.i + 1) % 256 self.j = (self.j + self.S[self.i]) % 256 self.S[self.i], self.S[self.j] = (self.S[self.j], self.S[self.i]) encrypted_data.append(b ^ self.S[(self.S[self.i] + self.S[self.j]) % 256]) return encrypted_data if len(sys.argv) > 1: filename = sys.argv[1] if os.path.exists(filename): from pwn import xor content = open(filename, 'rb').read() key = os.urandom(128) enc = Encrypt(key) with open('flag.enc', 'wb') as f: f.write(xor(enc.encrypt(content), key)) with open('key', 'wb') as f: f.write(key) ``` so its a rc4 encryption with result is `flag.enc` and `key`. we can see next commit for confirm it ```shell % git checkout ef352e88b648742c399d497ca12e6c71761d8620 Previous HEAD position was 4791400 Initial commit HEAD is now at ef352e8 Encrypt file % ls -la total 52 drwxrwxr-x 3 bleco bleco 4096 Okt 18 18:30 . drwxrwxr-x 5 bleco bleco 4096 Okt 18 18:25 .. -rwxrwxr-x 1 bleco bleco 1290 Okt 18 18:27 encrypt.pyc -rw-rw-r-- 1 bleco bleco 30518 Okt 18 18:30 flag.enc drwxrwxr-x 5 bleco bleco 4096 Okt 18 18:30 .git -rw-rw-r-- 1 bleco bleco 128 Okt 18 18:30 key ``` now it confirm we must decrypt `flag.enc` with RC4 encryption using `key` in every commit. for decrypt RC4 we just need to using the encrypt function again using same key, ```python import os import sys from pwn import xor class Encrypt: def __init__(self, key): self.S = list(range(256)) j = 0 for i in range(256): j = (j + self.S[i] + key[i % len(key)]) % 256 self.S[i], self.S[j] = (self.S[j], self.S[i]) self.i = 0 self.j = 0 def encrypt(self, data): encrypted_data = bytearray() for b in data: self.i = (self.i + 1) % 256 self.j = (self.j + self.S[self.i]) % 256 self.S[self.i], self.S[self.j] = (self.S[self.j], self.S[self.i]) encrypted_data.append(b ^ self.S[(self.S[self.i] + self.S[self.j]) % 256]) return encrypted_data def decrypt(self, data): return self.encrypt(data) def main(): if len(sys.argv) > 1: enc_filename = sys.argv[1] key_filename = 'key' dec_filename = '../decrypted/' + f'flag{sys.argv[2]}' if os.path.exists(enc_filename) and os.path.exists(key_filename): with open(enc_filename, 'rb') as f: enc_content = f.read() with open(key_filename, 'rb') as f: key = f.read() enc = Encrypt(key) decrypted_content = enc.decrypt(xor(enc_content,key)) with open(dec_filename, 'wb') as f: f.write(decrypted_content) print(f'Decrypted {dec_filename}') else: print('Error not found.') if __name__ == '__main__': main() ``` ```shell % python3 ../decrypt.py flag.enc 1 Decrypted content written to ../decrypted/flag1 ``` and we success decrypt ![image](https://hackmd.io/_uploads/S1XHjbglJl.png) its a png file, we can change our output file to .png directly. so in this time we only need automation for check all commit and decrypt all `flag.enc` ### decrypt automation first we need all commit hash ```python import re text = open('log.txt','r').read() pattern = r"commit (\w+)" commit_hashes = re.findall(pattern, text) for commit_hash in commit_hashes: print(f"{commit_hash}") ``` ```shell % git log > log.txt % python3 parse-log.py cf70e0022806285755330cf18252a83bae1170e5 d512ddc863ba33f51685abb23eb54f0b71eb0de1 dc546ef25e154530cad0b3ec401027104da35d52 898b06e71dd99e69f27856d0bc82005c020a7f24 9f03b04b8645a5454ec4c3b763a6417cba8e3d06 02064225affd2a673a2d8042a79adcf111c2eccb 21ace868d0c4320b0f70d889ae2c90301d06aa65 9335e4cd80bd3a9de61573a3e29e4bb96705557f .... % python3 parse-log.py > log-commit.txt ``` after that we need automation for git checkout and decrypt ```python import os commit_hash = open('log-commit.txt','r').read().split('\n') os.chdir('gitfolder') for x in range(1, len(commit_hash)): cmd = f'git checkout {commit_hash[x]}' res = os.popen(cmd).read() print(res) os.system(f'python3 ../decrypt.py flag.enc {x}') ``` ```shell % python3 decrypt-commit-content.py Previous HEAD position was ef352e8 Encrypt file HEAD is now at d512ddc Encrypt file Decrypted content written to ../decrypted/flag1.png Previous HEAD position was d512ddc Encrypt file HEAD is now at dc546ef Encrypt file Decrypted content written to ../decrypted/flag2.png Previous HEAD position was dc546ef Encrypt file HEAD is now at 898b06e Encrypt file Decrypted content written to ../decrypted/flag3.png Previous HEAD position was 898b06e Encrypt file HEAD is now at 9f03b04 Encrypt file .... ``` and now we get flag ![image](https://hackmd.io/_uploads/H1vlAbxg1g.png) ## reference - [https://www.bittorrent.org/beps/bep_0029.html](https://www.bittorrent.org/beps/bep_0029.html) - [http://bittorrent.org/beps/bep_0005.html](http://bittorrent.org/beps/bep_0005.html) - [https://mislove.org/teaching/cs4700/spring11/lectures/lecture20.pdf](https://mislove.org/teaching/cs4700/spring11/lectures/lecture20.pdf) - [https://githowto.com/git_internals_git_directory](https://githowto.com/git_internals_git_directory) - [https://git-scm.com/book/en/v2/Git-Internals-Git-Objects](https://git-scm.com/book/en/v2/Git-Internals-Git-Objects)