Skip to content

Commit 364b33e

Browse files
committed
Added Hex QR (also added credits)
1 parent 1a3c2f3 commit 364b33e

File tree

5 files changed

+224
-4
lines changed

5 files changed

+224
-4
lines changed

SUMMARY.md

+1
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
* [Reverse Engineering](reverse-engineering.md)
1818
* [Useless Python \[50 points\]](/reverse-engineering/useless-python-50-points.md)
1919
* [Phunky Python II \[115 points\]](/reverse-engineering/phunky-python-ii-115-points.md)
20+
* [Hex QR \[200 points\]](/reverse-engineering/hexqr-200-points.md)
2021
* [67k \[400 points\]](reverse-engineering/67k-400-points.md)
2122
* [Web](web.md)
2223
* [Cookie Blog \[30 points\]](/web/cookie-blog-30-points.md)

forensics/decomphose-325-points.md

+2
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,8 @@ And zooming in on the flag, we obtain:
7070

7171
`easyctf{wh4t_a_5weet_fFLag_2b04e1}`
7272

73+
##### Writeup by Harrison Green
74+
7375
### External Writeups
7476

7577
* \(none\)

programming/fzz-buzz-2-200-points.md

+1
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,7 @@ def go(k):
117117
go(1)
118118
```
119119

120+
##### Writeup by Harrison Green
120121

121122
### External Writeups
122123

reverse-engineering.md

+1-4
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,6 @@ This category challenges your deductive ability to use a program's behavior and
77
* [Useless Python \[50 points\]](//reverse-engineering/useless-python-50-points.md)
88
* [Phunky Python II \[115 points\]](//reverse-engineering/phunky-python-ii-115-points.md)
99
* Lucky Guess \[200 points\]
10-
* Hex QR \[200 points\]
10+
* [Hex QR \[200 points\]](/reverse-engineering/hexqr-200-points.md)
1111
* [67k \[400 points\]](/reverse-engineering/67k-400-points.md)
1212
* Morphin \[450 points\]
13-
14-
15-
+219
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,219 @@
1+
# Hex QR - 200 points
2+
3+
We were given a mysterious hexagonal qr code and a website that could produce a similar looking qr code for a given string.
4+
5+
The goal was to figure out the encoding schema and decode the flag.
6+
7+
***The flag to decode:***
8+
9+
![](https://github.com/hgarrereyn/EasyCTF-2017-Write-ups/raw/1e81630e6b36e9caaa54b23e63e4d1e4f6f5c39e/hexqr/flag.png)
10+
11+
### Solution:
12+
13+
#### Overview
14+
15+
In order to get the flag, we don't have to completely understand the encoding schema, we just have to understand it *well enough* to decode a message.
16+
17+
We had access to a web page that would encode any string. After a bit of probing, I discovered that I could enter any ascii character (7 bits) but unicode characters caused the program to throw an error. So it must only be using 7-bits.
18+
19+
#### Single character
20+
21+
I decided to test a bunch of single character strings and look for patterns. Here are the generated codes for the following characters:
22+
23+
#### `A - 0x41 - 1000001`
24+
![A](https://github.com/hgarrereyn/EasyCTF-2017-Write-ups/raw/1e81630e6b36e9caaa54b23e63e4d1e4f6f5c39e/hexqr/A.jpeg)
25+
26+
#### `B - 0x42 - 1000010`
27+
![B](https://github.com/hgarrereyn/EasyCTF-2017-Write-ups/raw/1e81630e6b36e9caaa54b23e63e4d1e4f6f5c39e/hexqr/B.jpeg)
28+
29+
#### `C - 0x43 - 1000011`
30+
![C](https://github.com/hgarrereyn/EasyCTF-2017-Write-ups/raw/1e81630e6b36e9caaa54b23e63e4d1e4f6f5c39e/hexqr/C.jpeg)
31+
32+
Eventually, I discovered that for all single character strings, only the following 6 bits seemed to change:
33+
34+
![](https://github.com/hgarrereyn/EasyCTF-2017-Write-ups/raw/1e81630e6b36e9caaa54b23e63e4d1e4f6f5c39e/hexqr/1-char.png)
35+
36+
How could 7-bit ascii be encoded in only 6 bits? To see what bit was left out, I tried comparing two characters with equal bits except for one. What I discovered is that the most-significant bit was being left out. For example, the image produced for the following characters was identical:
37+
38+
```
39+
0: 0110000
40+
p: 1110000
41+
```
42+
43+
*Later I discovered that this is not exactly the case... but it works for now*
44+
45+
#### Mapping the bits
46+
47+
Now I wanted to figure out which bit corresponded with each triangle. Here is the syntax I will use:
48+
49+
```
50+
bit | 6 5 4 3 2 1 0
51+
-------------------
52+
A | 1 0 0 0 0 0 1
53+
B | 1 0 0 0 0 1 0
54+
C | 1 0 0 0 0 1 1
55+
...
56+
```
57+
58+
Through some more detective work, I discovered that for all ascii characters with bit 6 set, the triangles corresponded as follows:
59+
60+
![](https://github.com/hgarrereyn/EasyCTF-2017-Write-ups/raw/1e81630e6b36e9caaa54b23e63e4d1e4f6f5c39e/hexqr/1-char-labeled.png)
61+
62+
Now for characters without bit 6 set, they appeared to be shifted left and padded with a zero. For example `4` is encoded as follows:
63+
64+
```
65+
4 (normal) | 0 1 1 0 1 0 0
66+
4 (encoded) | 1 1 0 1 0 0 0
67+
which appears the same as
68+
h | 1 1 0 1 0 0 0
69+
```
70+
71+
*Note: Working with only one character caused me to stumble onto a weird edge case where the code was shifted depending on whether the 6th bit of the first character was set or not - we don't have to worry about that for now.*
72+
73+
#### Multiple characters
74+
75+
One obvious difference between one and two character strings is that the qr code gets bigger. Instead of three base triangles, we now have 4:
76+
77+
![](https://github.com/hgarrereyn/EasyCTF-2017-Write-ups/raw/1e81630e6b36e9caaa54b23e63e4d1e4f6f5c39e/hexqr/bigger.png)
78+
79+
Another difference is that the qr code seems to flip horizontally based on the number of characters.
80+
81+
I repeated the same procedure of mapping as before and obtained the following character positions (I only used characters with bit 6 set without realizing it -> this will be important later):
82+
83+
#### Two characters
84+
*I've omitted bits `1-4` but they are in order*
85+
![](https://github.com/hgarrereyn/EasyCTF-2017-Write-ups/raw/1e81630e6b36e9caaa54b23e63e4d1e4f6f5c39e/hexqr/2-char-labeled.png)
86+
87+
#### Three characters
88+
![](https://github.com/hgarrereyn/EasyCTF-2017-Write-ups/raw/1e81630e6b36e9caaa54b23e63e4d1e4f6f5c39e/hexqr/3-char-labeled.png)
89+
90+
Now the method of reading these codes is becoming clear: They are read in a zig-zag pattern starting from the top. For example to read a three character code, use the following path:
91+
92+
![](https://github.com/hgarrereyn/EasyCTF-2017-Write-ups/raw/1e81630e6b36e9caaa54b23e63e4d1e4f6f5c39e/hexqr/read-pattern.png)
93+
94+
Then you obtain binary like this:
95+
96+
```
97+
010 [char 1] 00 [char 2] 00 [char 3] ...
98+
```
99+
100+
At some point during testing, I encountered a binary string like this:
101+
102+
```
103+
... [char n] 01 [char n+1] ...
104+
^^
105+
notice
106+
```
107+
108+
This confused me for a moment and caused me to revisit my original idea. Why would the spacing bits be different?
109+
110+
After some more testing, I realized that for every character after the first one, the two spacing bits corresponded to bits `6` and `7`. I rechecked a few things and discovered the correct encoding.
111+
112+
To read these qr codes, follow the zigzag pattern above and then perform a `change, keep, change, keep...` operation on the resulting binary string.
113+
114+
In more mathematical terms, you take the binary string and `XOR` with a string of `1010101010...`
115+
116+
#### The correct encoding
117+
118+
*Note this is the format after you apply the `XOR` as described above*
119+
120+
```
121+
binary notes
122+
-------- -------
123+
11 - header (if this is 00, you are reading it wrong
124+
and should flip horizontally)
125+
126+
[char 1] - if bit 6 is set, this is bits 6 -> 0 of char 1,
127+
otherwise it is bits 5 -> 0 of char 1
128+
129+
[char 2] - the remaining chars are all bits 7 -> 0
130+
[char 3] (bit 7 is always 0 because it is ascii)
131+
[char 4]
132+
...
133+
134+
00000000 - once you hit zeros, you've reached the end
135+
```
136+
137+
In graphical form, the encoding is as follows:
138+
139+
***Bit 6 is set***
140+
141+
*`b1` indicates a binary 1 value*
142+
143+
![](https://github.com/hgarrereyn/EasyCTF-2017-Write-ups/raw/1e81630e6b36e9caaa54b23e63e4d1e4f6f5c39e/hexqr/3-char-correct.png)
144+
145+
***Bit 6 is not set***
146+
147+
![](https://github.com/hgarrereyn/EasyCTF-2017-Write-ups/raw/1e81630e6b36e9caaa54b23e63e4d1e4f6f5c39e/hexqr/3-char-correct-nob6.png)
148+
149+
*Notice that if bit 6 is not set, the entire rest of the code is shifted one bit*
150+
151+
#### Decoding the flag
152+
153+
Now that we know the encoding schema, it is trivial to read the binary and decode the flag. I read the triangles by hand and then fed it into a python program to perform `XOR` and convert to ascii.
154+
155+
Here is the program I wrote (it's a little rough):
156+
157+
```python
158+
# Reading the qr code directly
159+
hex_raw = """
160+
0101100000011010000
161+
100110001011000011011
162+
00010000100110011001011
163+
1000110100001001110011000
164+
000001010001000010010011100
165+
11110000110100001110110011001
166+
0001110010011000000100110000010
167+
100011100000111010001001110011000
168+
00000101000100110001100000011011000
169+
10000000100111001100000000101000100
170+
001001111010011010000111011000010
171+
1000100110001001000010000000110
172+
10000100111001100000010011001
173+
101010000010100011011001100
174+
0000110000100110011001101
175+
10001100010011000000110
176+
111001010000101010101
177+
0101010101010101010
178+
""".replace('\n','')
179+
180+
# convert the hex_code to a binary value
181+
hex_raw_value = int(hex_raw,2)
182+
183+
# create a string of the same length to xor with
184+
xor_value = int(('10'*(len(hex_raw)/2))[0:len(hex_raw)],2)
185+
186+
# perform XOR and convert back to binary string
187+
hex_code = str(bin(hex_raw_value ^ xor_value))[2:]
188+
189+
out_string = ""
190+
i = 2 # skip the 2 bit header
191+
192+
# add the first character (guessing that bit 6 is set)
193+
bit6IsSet = True
194+
out_string = out_string + chr(int(hex_code[i:i + (7 if bit6IsSet else 6)], 2))
195+
i = i + (7 if bit6IsSet else 6)
196+
197+
# the rest of the characters are 8 bit
198+
while i < len(hex_code)-8:
199+
out_string = out_string + chr(int(hex_code[i:i+8], 2))
200+
i = i + 8
201+
202+
print(out_string)
203+
```
204+
205+
The output was `easyctf{are_triangles_more_secure_than_squares?_c54fcdeb}`
206+
207+
#### Bonus
208+
209+
On twitter, @easyctf posted this image as a teaser:
210+
211+
![](https://github.com/hgarrereyn/EasyCTF-2017-Write-ups/raw/1e81630e6b36e9caaa54b23e63e4d1e4f6f5c39e/hexqr/bonus.jpg)
212+
213+
Which decodes as `i <3 cheesecake`
214+
215+
##### Writeup by Harrison Green
216+
217+
### External Writeups
218+
219+
* \(none\)

0 commit comments

Comments
 (0)