Skip to content

Commit 5b15635

Browse files
authored
Merge pull request #3 from rk3141/main
braiding bad (bi0sctf 2025) writeup
2 parents e6e2d6b + a70db80 commit 5b15635

File tree

4 files changed

+108
-1
lines changed

4 files changed

+108
-1
lines changed

content/writeups/Cyber3301/_index.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,4 +12,4 @@ The writeups for the Cyber3301 challenge conducted by Cybersecurity Club, IITM.
1212

1313
# Challenges
1414

15-
- [Don't cheat, Play Fair](@/writeups/Cyber3301/DontCheatPlayFair.md)
15+
- [Don't cheat, Play Fair](@/writeups/Cyber3301/DontCheatPlayFair.md)

content/writeups/_index.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ page_template = "blog-page.html"
88

99
## CTF writeups
1010

11+
- [bi0sCTF](./bi0sCTF-2025)
1112
- [Guild Selection CTF](./Guild_Selection_CTF)
1213
- [RVCExIITBCTF](./RVCExIITBFinals)
1314
- [picoCTF2025](./picoCTF2025)
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
+++
2+
paginate_by = 15
3+
paginate_path = "writeups/bi0sCTF-2025"
4+
title = "bi0sCTF-2025"
5+
sort_by = "date"
6+
page_template = "blog-page.html"
7+
+++
8+
9+
10+
The writeups for the bi0sCTF 2025.
Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
+++
2+
title = "Braiding Bad"
3+
date = 2025-06-27
4+
authors = ["Swaminath Shiju"]
5+
+++
6+
7+
8+
### Description
9+
10+
Once upon a time , a braid decided to break bad ...
11+
12+
```py
13+
import random
14+
import string
15+
import hashlib
16+
from Crypto.Util.number import bytes_to_long
17+
18+
message = <REDACTED>
19+
20+
n = 100
21+
Bn = BraidGroup(n)
22+
gs = Bn.gens()
23+
K = 32
24+
25+
gen = gs[n // 2 - 1]
26+
p_list = [gen] + random.choices(gs, k=K-1)
27+
p = prod(p_list)
28+
print(f"p: {list(p.Tietze())}")
29+
30+
a = prod(random.choices(gs[:n//2-2], k=K))
31+
q = a * p * a^-1
32+
print(f"q: {list(q.Tietze())}")
33+
34+
br = prod(random.choices(gs[n//2 + 1:], k=K))
35+
c1 = br * p * br^-1
36+
c2 = br * q * br^-1
37+
38+
h = hashlib.sha512(str(prod(c2.right_normal_form())).encode()).digest()
39+
40+
original_message_len = len(message)
41+
pad_length = len(h) - original_message_len
42+
left_length = random.randint(0, pad_length)
43+
pad1 = ''.join(random.choices(string.ascii_letters, k=left_length)).encode('utf-8')
44+
pad2 = ''.join(random.choices(string.ascii_letters, k=pad_length - left_length)).encode('utf-8')
45+
padded_message = pad1 + message + pad2
46+
47+
d_str = ''.join(chr(m ^^ h) for m, h in zip(padded_message, h))
48+
d = bytes_to_long(d_str.encode('utf-8'))
49+
50+
print(f"c1: {list(c1.Tietze())}")
51+
print(f"c2: {d}")
52+
```
53+
54+
### Solution
55+
56+
The challenge uses a simple encryption based on Braid groups to encrypt the flag.
57+
58+
`Bn` is a braid group of order 100. `gs` is the list of generators, it multiplies `K` random generators to make an element `p` of `Bn`.
59+
`p.Tietze()` simply gives the list of generators used to create `p`.
60+
61+
For example if $\text{p}=\sigma_1\sigma_{11}\sigma_{34}^{-1}\sigma_4^2$ then $$\text{p.Tietze()}=(1,11,-34,4,4)$$
62+
> Note: The Tietze of `p` would have only positive elements since its made from generators.
63+
64+
This means we can directly use the printed value to get the `p` from the printed `Tietze`. The final encoding is done by converting the normal form of `c2` into bytes, sha512 hashing it and then xor-ing it with the flag. For the purposes of this question we can take `normal_form` as simply a black box to convert a group element to bytes.
65+
66+
So getting the flag reduces to finding `c2`. Assume $\text{br}=\sigma_{a_1}\sigma_{a_2}\cdots\sigma_{a_{32}}$ then `c1` would be $\left(\sigma_{a_1}\sigma_{a_2}\cdots\sigma_{a_{32}}\right)\cdot p \cdot\left(\sigma_{a_{32}}^{-1}\sigma_{a_{31}}^{-1}\cdots\sigma_{a_1}^{-1}\right)$. The printed Tietze list would simply be $$(a_1,a_2,\cdots,a_{32},[\,\, p\,\,],-a_{32},-a_{31},\cdots,-a_1)$$
67+
We can get `br` from the first 32 elements of the Tietze. Now since we have `br` and `q` we get `c2` and then just pass it through the encryption to get the xor valued need to decrypting.
68+
69+
> **Note:** It is possible for the end point of `p` to cancel out with the end-point of `c1`. This is improbable but easily fixable using the Tietze of `p`
70+
71+
Final solve script
72+
73+
```py
74+
from sage.groups.braid import BraidGroup
75+
from sage.all import prod
76+
import hashlib
77+
78+
print("READY")
79+
Bn = BraidGroup(100)
80+
81+
# not required unless cancellation happens
82+
# p = Bn(<Tietze of p>)
83+
# c1 = Bn(<Tietze of c1>)
84+
85+
q = Bn(<Tietze of q>)
86+
br = Bn(<Tietze of c1>[:32])
87+
c2 = br * q * br**(-1)
88+
89+
from Crypto.Util.number import long_to_bytes
90+
91+
ct = long_to_bytes(<encoded>).decode('utf-8')
92+
print("READY")
93+
94+
h = hashlib.sha512(str(prod(c2.right_normal_form())).encode()).digest()
95+
print("".join(chr(a ^ ord(b)) for a, b in zip(h, ct)))
96+
```

0 commit comments

Comments
 (0)