227 lines
8.3 KiB
Markdown
227 lines
8.3 KiB
Markdown
# Magic Bus
|
|
**Category**: Satellite Bus
|
|
**Points (final)**: 91
|
|
**Solves**: 44 (this number taunts me)
|
|
|
|
*Important note:* Team BLAHAJ did not solve this problem until after the
|
|
competition, and it did not count toward our final point total.
|
|
|
|
> There's a very busy bus we've tapped a port onto, surely there is some juicy information hidden in the device memory... somewhere...
|
|
|
|
## Write-up
|
|
by [hazel (`arcetera`)](https://qtp2t.club/)
|
|
|
|
**I hate this problem. I hate this problem. I hate this problem. I hate this
|
|
problem. I literally hate this problem so much. This problem made me cry. I have
|
|
literally no words to describe the exact extent to which this problem has driven
|
|
me insane. This problem taunts me in my sleep. This problem taunts me while I am
|
|
awake. The extent to which I despise this problem is beyond words. I hate this.
|
|
I hate whoever made this. I want to burn this problem to the ground. This
|
|
problem has achieved active sentience and holds malice against me and the rest
|
|
of my team specifically. Had the competition not ended, this problem would hold
|
|
the rest of the world hostage.**
|
|
|
|
Furthermore, much of this writeup is *failed* attempts at a solution. Other
|
|
writeups may be more useful at determining success, despite our team eventually
|
|
finding a solution.
|
|
|
|
...anyway...
|
|
|
|
When netcatting into the server, a series of hex bytes appears. A cursory
|
|
analysis of these bytes reveals that all packets start with `^` and end with
|
|
`.`, aside from lines starting with byte CA. Decoding the data beginning with
|
|
byte CA reveals some 🧃🧃🧃. This output has `\xca\x00` stripped:
|
|
```
|
|
b'\xb2M*\xf9H\xacyvQ}\xd4\xf2\xa0\xcd\xc9Juicy Data 03\x00M\xae@\x9a\xd89\xe2\x85\xb2Y\xd6/-\xc9\xd0\xfb\x92\xd2\xc4Y\xaa[ B\xc6\xb5'
|
|
```
|
|
I prefer water though. \#WaterDrinkers
|
|
|
|
While I was asleep, the rest of the team managed to reverse the protocol to a
|
|
decent extent. Namely, the format for strings beginning with `:\x00\x00>` and
|
|
`:\x00\x00?` and ending with `?` is:
|
|
- `\0x000000` (6 bytes)
|
|
- @ or ?
|
|
- A (7 bytes)
|
|
- @ (2 bytes)
|
|
- @ or ?
|
|
- `\xc1` (3 bytes)
|
|
|
|
An example:
|
|
```
|
|
b'\x00\x00\x008\x94S@\xc8.@A\x01:\xa0\xc0i\x11\xa1@|.@\xc1\x9b\x1c\xe6?'
|
|
: 00:00:00 > 38:94:53:40:c8:2e @ 1:3a:a0:c0:69:11:a1 @ 7c:2e @ 9b:1c:e6 ?
|
|
```
|
|
The following Python code decodes this packet structure:
|
|
``` python
|
|
def to_hex(b):
|
|
return ':'.join(hex(x)[2:] for x in b)
|
|
|
|
def decode_pkt(b):
|
|
if len(b) == 0:
|
|
return
|
|
if b[0] == 0xCA:
|
|
pass # raw data?
|
|
elif b[0] == ord(':'):
|
|
if b[3] == ord('>') or b[3] == ord('?'): # > or ?
|
|
field1 = to_hex(b[7:13]) # 6 bytes
|
|
field1end = chr(b[13]) #
|
|
field2 = to_hex(b[15:22]) # 7 bytes
|
|
if b[22] != ord('@'):
|
|
print('b[22] should be @ but is {}'.format(chr(b[22])))
|
|
field3 = to_hex(b[23:25])
|
|
field3end = chr(b[25])
|
|
c1 = b[26]
|
|
field4 = to_hex(b[27:30])
|
|
if b[30] != ord('?'):
|
|
print('b[30] is not ?')
|
|
print(': 00:00:00 > {} {} {} @ {} {} {} ?'.format(field1, field1end, field2,
|
|
field3, field3end, field4))
|
|
elif b[0] == ord(';'):
|
|
print('delimiter') # end of previous packet?
|
|
else:
|
|
print(b[0])
|
|
print('unknown data')
|
|
print('\n')
|
|
```
|
|
|
|
Noting a delay between packets led me to derive the following packet structure:
|
|
- START packet, which is equal to the preceding END packet
|
|
- ONCE call, which occurs prior to a...
|
|
- ONCE packet
|
|
- JUICY DATA
|
|
- END call
|
|
- END packet, which is equal to the next START packet
|
|
|
|
This proved to be incorrect, but more on that later.
|
|
|
|
The following code differentiates between these packets from the netcat, where
|
|
the variable `rawn` is the raw byte string:
|
|
``` python
|
|
start = True
|
|
while True:
|
|
r.recvuntil('^')
|
|
raw = r.recvuntil('.')
|
|
rawn = bytes([94]) + raw
|
|
print(rawn)
|
|
v = raw.decode().split('+')
|
|
del v[-1]
|
|
h = bytes([int(i, 16) for i in v])
|
|
if h == b';\x00\x00?':
|
|
print("ONCE CALL")
|
|
elif h == b';\x00\x00>':
|
|
print("END CALL")
|
|
elif h.startswith(b':\x00\x00?'):
|
|
print(f"ONCE: {h[4:]}")
|
|
elif h.startswith(b':\x00\x00>'):
|
|
# notable delay between start and end each time
|
|
if start:
|
|
print(f"START: {h[4:]}")
|
|
start = False
|
|
else:
|
|
print("INJECTING")
|
|
r.send(inj)
|
|
print(f"END: {h[4:]}")
|
|
start = True
|
|
elif h.startswith(b'\xca'):
|
|
print(f"JUICE: {h}")
|
|
else:
|
|
print(f"???: {h}")
|
|
|
|
sys.stdout.flush()
|
|
```
|
|
|
|
I then noticed that post-text the string
|
|
`\x00R\x01\x1e{\x81G\x00\xc9\x9d\xe3\xe7\xc2#6` had the characters `{` and `6`
|
|
at the same point as `flag{oscar39616kilo`, which would correspond to a flag. I
|
|
graphed this and tried to find a function (or multiple) modeling a relation
|
|
here, but with R<sup>2</sup> being something like 0.39 for every relation I
|
|
tried, this was extremely unlikely.
|
|
|
|
We then tried reading the data sequentially from the buffer, from Juicy Data 00
|
|
to 04. Here's the entire string:
|
|
```hexdump
|
|
00000000: 4a75 6963 7920 4461 7461 2030 3000 c8f7 Juicy Data 00...
|
|
00000010: eb15 963d 6b70 5cc9 2c5e d5cf 5c31 9919 ...=kp\.,^..\1..
|
|
00000020: 779a c6a9 0865 8d55 926a 372c 00ff 23eb w....e.U.j7,..#.
|
|
00000030: 14b9 297f 2985 4856 e31d 2558 58be 59c6 ..).).HV..%XX.Y.
|
|
00000040: 4a75 6963 7920 4461 7461 2030 3100 5201 Juicy Data 01.R.
|
|
00000050: 1e7b 8147 00c9 9de3 e7c2 2336 817c fcd9 .{.G......#6.|..
|
|
00000060: 9b6b 3a1f 68f0 35ce dd77 35ca dc87 ccfa .k:.h.5..w5.....
|
|
00000070: 024d 4102 16df e5fd a108 3322 842f fc1f .MA.......3"./..
|
|
00000080: 4a75 6963 7920 4461 7461 2030 3200 c08f Juicy Data 02...
|
|
00000090: e702 91fd e177 fb82 7f2e a504 5ea1 23f9 .....w......^.#.
|
|
000000a0: d762 fcfd d5cd 00c0 d4ce 8661 6847 f14f .b.........ahG.O
|
|
000000b0: 4982 4d2a f948 ac79 7651 7dd4 f2a0 cdc9 I.M*.H.yvQ}.....
|
|
000000c0: 4a75 6963 7920 4461 7461 2030 3300 4dae Juicy Data 03.M.
|
|
000000d0: 409a d839 e285 b259 d62f 2dc9 d0fb 92d2 @..9...Y./-.....
|
|
000000e0: c459 aa5b 2042 c6b5 6193 b3c6 5001 7590 .Y.[ B..a...P.u.
|
|
000000f0: 9b4d ca7e d27c d7a9 ac04 727c ff04 4ec4 .M.~.|....r|..N.
|
|
00000100: 4a75 6963 7920 4461 7461 2030 3400 5a83 Juicy Data 04.Z.
|
|
00000110: 2524 01f8 a0d8 a14c dc13 c8dc 1717 a075 %$.....L.......u
|
|
00000120: 10bf f24b a525 e81e 0c4b e8f3 ...K.%...K..
|
|
```
|
|
Unfortunately, nothing meaningful was derived from this. There are a `{` and `}`
|
|
with bytes between them, but they aren't flag length.
|
|
|
|
I noticed that injecting instructions performed something, but I didn't think it
|
|
did anything notable. I injected various data at various points, but I never
|
|
managed to break out at the previous region of memory... which is where
|
|
gashapwn's *incredible* work came in.
|
|
|
|
If the packet `^3b+00+00+00.` is sent, the bus *stops sending data*, which is
|
|
decidedly confirmation that the server accepts data. Each of the following
|
|
injects has the same effect:
|
|
```
|
|
^3b+00+00+30+.
|
|
^3b+00+00+31+.
|
|
^3b+00+00+32+.
|
|
^3b+00+00+33+.
|
|
^3b+00+00+34+.
|
|
^3b+00+00+34+.
|
|
^3b+00+00+35+.
|
|
^3b+00+00+36+.
|
|
^3b+00+00+37+.
|
|
```
|
|
In practice, only this last packet is needed to shut down the server. Anything
|
|
of the form of `^3b+00+00+XX+.` where XX<38 shuts it down, but only 37 enables
|
|
dump mode. This can probably be done with a fuzzer. Why has God abandoned us?
|
|
What accursed malfunction did we do to deserve this fate?
|
|
|
|
If you send the packet `^ca+00+44+79+20+44+61+74+61+20+30+31+00+52+01+1e+7b+81+47+00+.....+87+cc+.`,
|
|
the same packet is sent back.
|
|
This means that the juice packets deliminated with `\xca` are actually instructions.
|
|
|
|
By playing with the packet, the format appears to go:
|
|
- Byte 0: CA
|
|
- Byte 1-2: Memory offset
|
|
- Byte 3-end: Size of memory to return
|
|
|
|
...so if we ask for a really large chunk of data, we can get a dump.
|
|
With the inject:
|
|
```
|
|
b"^3b+00+00+37+."
|
|
b"^ca+00+00+00+00+00+00+00+00+00+00+00+00+00+00+00+00+00+00+00+00+00+00+00+00+....+00+00+."
|
|
```
|
|
we can query for everything in memory. And we did.
|
|
|
|
### Full code
|
|
```{.python include=magic-bus.py}
|
|
```
|
|
|
|
Run it:
|
|
```
|
|
λ has-writeup/satellite-bus/magic-bus python magic-bus.py
|
|
|
|
....
|
|
|
|
JUICE: b'.....v\xaf\xe88\x856Mflag{oscar39616kilo:GCxmhORYa65Y0PmRtFmlFSBmnvImEiWg.....'
|
|
```
|
|
Hey look, a flag!
|
|
|
|
I hate this problem so much. At the time of me writing this, it's 1:30 AM and
|
|
I'm sitting in my kitchen on my Lenovo(tm) ThinkPad(tm) T440(tm). I genuinely
|
|
don't know how this got so many solves. I hate this. Goodnight.
|
|
|
|
## Resources and other writeups
|
|
- God I wish there was any
|