# 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

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.

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

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

in git directory there is strings and object, for strings we can identified easily (you can analyze your own git directory for understand further)



we already get `HEAD`,`description`, and `config`. after that we can search `index` by signature `DIRC`

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


### 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

```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

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

## 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)