Every year, the INCIBE (a Spanish agency whose goal is to raise awareness on cybersecurity issues) organises the CyberCamp congress.

These are the write-ups for the CTF quals that took place a couple of weeks ago. As the results have already been announced1 and they said that we can upload our write-ups, I’m writing here my solutions for the challenges I solved. The materials for the challenges in this post are available for download here:

Here, I’ll explain my solutions for the challenges labelled as medium.

## 5.- Wi-Fi things

The description of this challenge states:

``````The monitoring of wireless traffic of one of the networks of your organization has been
carried out. There is a suspicion that one of the users is logging into a website that is
phishing and supplanting the legitimate one. Your goal is to recover the credentials, the
``````

Well, let’s open the provided `.pcap` file with Wireshark, and see what’s in there…

OK, no problem. We’re on the medium category. Did you thought it would be as easy as opening the file and looking for HTTP? Yeah, I though that too :( But worry not, because we’re l33t hax0rs and we know how to crack Wi-Fi, right?

There are a lot of different tools to crack an AP’s password; but now we’re going to use one of the most popular: `aircrack-ng`. Usually, we should discover the AP’s BSSID to compute the possible keys; but aircrack-ng prompts us with a message to select the desired AP and performs all calculations for us.

For a first try, we can use the default wordlist (at least on Unix-like systems), `/usr/share/dict/words`, that contains a small dictionary that can be quickly checked:

``````\$ aircrack-ng medium_5.cap -w /usr/share/dict/words

(...)

Passphrase not in dictionary

Quitting aircrack-ng...
``````

Dammit… Let’s use another, more powerful, bigger (and, thus, slower) dictionary: the ultra famous rockyou. Instead of looking through the web looking for it, I just use the SecLists repository on Github, that contains an enormous quantity of dictionaries, fuzzing rules, common usernames… I have the repository cloned in my computer2, so I can use and update it whenever I need it.

Let’s try again with this new dictionary:

``````\$ aircrack-ng medium_5.cap -w /usr/share/dict/seclists/Passwords/rockyou.txt
Opening medium_5.cap

#  BSSID              ESSID                     Encryption

1  D4:63:FE:C1:09:91  ECORP                     WPA (1 handshake)

Choosing first network as target.

Opening medium_5.cap

Aircrack-ng 1.2 beta3

[00:03:34] 870344 keys tested (3616.36 k/s)

KEY FOUND! [ iw4108604 ]
(...)
``````

YAY! After around 10 minutes we get the key: `iw4108604`. Now it’s time to decrypt the capture file. Again, we can do it in multiple ways (doing it from within Wireshark would be an easy option). In this case, we’re going to use another tool from the aircrack-ng suite: `airdecap-ng`.

``````\$ airdecap-ng -p "iw4108604" -b "D4:63:FE:C1:09:91" -e "ECORP" medium_5.cap
Total number of packets read          3166
Total number of WEP data packets         0
Total number of WPA data packets      1466
Number of plaintext data packets         0
Number of decrypted WEP  packets         0
Number of corrupted WEP  packets         0
Number of decrypted WPA  packets       328
``````

Now it’s as easy as opening the file `medi_5-dec.pcap` and looking for the suspicious HTTP traffic… Or maybe not. If we apply the display filter `HTTP` on Wireshark, we just get only a message that seems to be a connection test, but no suspicious data is found. We have to keep looking.

Fortunately, this file is small; but with larger ones is not feasible to look through all the capture. Hence, we need to use filters and reorder the packets using Wireshark’s tools. One thing to look, for example, is abnormally large packets, based on which is the used protocol. For example, if we order the packets from heavier to lighter, we see a strange ICMP ping request with 605 Bytes of data and no response:

The data of this suspicious packet seems to be an HTTP request. Copying the value gives the data captured by the attacker (remember that our goal is to recover the stolen credentials):

``````POST /login.a4p HTTP/1.1

Host: 10.0.1.1
User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:52.0) Gecko/20100101 Firefox/52.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
Referer: http://10.0.1.1/
Connection: close
Content-Type: application/x-www-form-urlencoded
Pragma: no-cache
Cache-Control: no-cache
Content-Length: 100

``````

It seems that the phishing page was trying to disguise as the Santander Bank, and the user john was tricked to put its password (the flag): qwertyFpass1234.

As a side note, we could have completed this challenge in a way easier way (once the packets has already been decrypted):

``````\$ strings ../medium_5-dec.cap | grep -i pass
``````

And we’re done XD

Finally, the flag is `qwertyFpass1234`.

## 6.- Unnecessary redundancy

The description of this challenge states:

``````Our experts have captured a pendrive containing these two files, but it seems that one of
them has suffered damage... (Answer: flag {X})
``````

The two provided files are `secret.txt`, a binary file, and `key.pem`. The latter seems to be the private key used to decrypt the former. However, the private key is not complete :(

``````\$ cat key.pem
-----BEGIN RSA PRIVATE KEY-----
MIIBOwIBAAJBAMSwf+/I42wFwNpDQiGuv0fb9w5Ria2JJAjzrYEYKp4HAKB8nXxm
yGx6OWAhI+4PYFYT3pf95J/mg5buCvP19fMCAwEAAQJAKuxRnyR57PL8eSVAY1Vd
TPNF4QwOPZ62DHYRISEC++UtRemqE1eBPkRgswiJ91+r9y8EnVw/SvL4GYQmeovS
sQIhAOq8Heinxe4udriNOd35SgJV9e87YglCCIfCoAirR0qtAiEA1oIMcKaiRiUj
2S/Q4YFTNySdT+fH16huoSQrEapD9x8*********************************
****************************************************************
********************************************
-----END RSA PRIVATE KEY-----
``````

Before continuing with the challenge, we have to learn a few things about the format used to store the information of a private key, because this is the key (he he) to solve the problem.

There are two formats to store a private key: PEM and DER. In fact, it’s just one format; because a PEM file is just base64’d DER data with a header and a footer. Thus, we have to understand how does DER encoding works3.

As we all know, an RSA private key is composed (in theory) of the modulo and the private exponent. However, in practice, the private key is composed of lots of other parameters used to speed up the operations. The RFC 3447 states that a private key should have the following fields (ASN.1 encoded):

``````RSAPrivateKey ::= SEQUENCE {
version           Version,
modulus           INTEGER,  -- n
publicExponent    INTEGER,  -- e
privateExponent   INTEGER,  -- d
prime1            INTEGER,  -- p
prime2            INTEGER,  -- q
exponent1         INTEGER,  -- d mod (p-1)
exponent2         INTEGER,  -- d mod (q-1)
coefficient       INTEGER,  -- (inverse of q) mod p
otherPrimeInfos   OtherPrimeInfos OPTIONAL
}
``````

As you can see, there are redundant parameters (we only really need `n` and `e`). This way, in our partial private key we may have enough information to decrypt the file.

Let’s begin by creating a file, `partial_key.der`, with the known Bytes of our key:

``````\$ base64 -d > partial_key.der
MIIBOwIBAAJBAMSwf+/I42wFwNpDQiGuv0fb9w5Ria2JJAjzrYEYKp4HAKB8nXxm
yGx6OWAhI+4PYFYT3pf95J/mg5buCvP19fMCAwEAAQJAKuxRnyR57PL8eSVAY1Vd
TPNF4QwOPZ62DHYRISEC++UtRemqE1eBPkRgswiJ91+r9y8EnVw/SvL4GYQmeovS
sQIhAOq8Heinxe4udriNOd35SgJV9e87YglCCIfCoAirR0qtAiEA1oIMcKaiRiUj
2S/Q4YFTNySdT+fH16huoSQrEapD9x8
base64: invalid input
``````

The decoder complains about `invalid input`, because the last Base64 block is incomplete, and it can’t be used to recover the last Byte of information. However, we don’t care about it right now.

The next step is to interpret the Bytes with the described ASN.1 format. To do that, we can either do it by hand (I’d rather not, please), or using one of the available libraries to do it for us. In this case, I chose the Python library pyasn1. Also, the pyasn1gen tool will be pretty useful to automate the process as much as possible.

These are the steps to get the information.

### Create the ASN.1 key definition and store it in a file, `pkcs1.asn`

We part from the original definition, the one in the RFC:

``````\$ cat -> pkcs1.asn
PKCS-1 {iso(1) member(2) us(840) rsadsi(113549) pkcs(1) pkcs-1(1) modules(0) pkcs-1(1)}

DEFINITIONS EXPLICIT TAGS ::= BEGIN
RSAPrivateKey ::= SEQUENCE {
version Version,
modulus INTEGER,
publicExponent INTEGER,
privateExponent INTEGER,
prime1 INTEGER,
prime2 INTEGER,
exponent1 INTEGER,
exponent2 INTEGER,
coefficient INTEGER
}
Version ::= INTEGER
END
``````

### Translate it into Python

``````\$ ./asn1ate-master/asn1ate/pyasn1gen.py pkcs1.asn
# Auto-generated by asn1ate v.0.6.1.dev0 from pkcs-1.asn

from pyasn1.type import univ, char, namedtype, namedval, tag, constraint, useful

class Version(univ.Integer):
pass

class RSAPrivateKey(univ.Sequence):
pass

RSAPrivateKey.componentType = namedtype.NamedTypes(
namedtype.NamedType('version', Version()),
namedtype.NamedType('modulus', univ.Integer()),
namedtype.NamedType('publicExponent', univ.Integer()),
namedtype.NamedType('privateExponent', univ.Integer()),
namedtype.NamedType('prime1', univ.Integer()),
namedtype.NamedType('prime2', univ.Integer()),
namedtype.NamedType('exponent1', univ.Integer()),
namedtype.NamedType('exponent2', univ.Integer()),
namedtype.NamedType('coefficient', univ.Integer())
)
``````

### Load the class `RSAPrivateKey` into the interpreter & try to decode

We’ll first need a couple of imports and read some data before trying to decode the file:

``````>>> data = open ("partial_key.der", "rb").read ()
>>> from pyasn1.codec.der.decoder import decode as der_decoder
>>> from pkcs1 import RSAPrivateKey
``````

Now we can try to decode:

``````>>> pk, rest = der_decoder (data, asn1spec = RSAPrivateKey ())
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "/usr/local/lib/python3.5/dist-packages/pyasn1/codec/ber/decoder.py", line 1182, in __call__
raise error.SubstrateUnderrunError('%d-octet short' % (length - len(substrate)))
pyasn1.error.SubstrateUnderrunError: 104-octet short
``````

We didn’t expected this error (`104-octet short`), right? I didn’t; but I guessed that it had something to do with the way ASN.1 works: at the beginning of a record, we put its type (INTEGER, SEQ…). Next, we put its length ( in octets ), and then we start with the value of the object itself. The message `104-octet short` is telling us that the object is defined to have 104 octets more than it has. To solve this we could carefully analyse `partial_key.der` to modify the record length and successfully decode it (in which case we would be better decoding the object by hand)… Or we could just add 104 more octets of pure junk. Of course, I decided to do it by appending 104 Bytes from `/dev/zero` :D

``````\$ cp partial_key.der partial_key_append.der
\$ cat /dev/zero | fold -w 140 | head -n 1 | tr -d '\n' >> partial_key_append.der
``````

We can now continue with our efforts.

After some time without any luck (the library keeps giving errors), I almost give up… Until I notice that I haven’t tried to use OpenSSL’s `asn1parse`. With the original key, it said the same error as pyasn1 (`ASN1_get_object:too long`). However, with our new key with zeroes appended, we can decode without any problem:

``````\$ openssl asn1parse -inform der -in partial_key_append.der
0:d=0  hl=4 l= 315 cons: SEQUENCE
4:d=1  hl=2 l=   1 prim: INTEGER           :00
74:d=1  hl=2 l=   3 prim: INTEGER           :010001
79:d=1  hl=2 l=  64 prim: INTEGER           :2AEC519F2479ECF2FC79254063555D4CF345E10C0E3D9EB60C7611212102FBE52D45E9AA1357813E4460B30889F75FABF72F049D5C3F4AF2F81984267A8BD2B1
145:d=1  hl=2 l=  33 prim: INTEGER           :EABC1DE8A7C5EE2E76B88D39DDF94A0255F5EF3B6209420887C2A008AB474AAD
180:d=1  hl=2 l=  33 prim: INTEGER           :D6820C70A6A2462523D92FD0E1815337249D4FE7C7D7A86EA1242B11AA43F71F
215:d=1  hl=2 l=   0 prim: EOC
217:d=1  hl=2 l=   0 prim: EOC
(...)
317:d=1  hl=2 l=   0 prim: EOC
319:d=0  hl=2 l=   0 prim: EOC
``````

Obviously, the parser only tells us the objects it finds; but we know which are the registers being parsed:

• First comes the SEQUENCE RSAPrivateKey
• Then, comes another INTEGER: the version (`0x00`)
• The first data inside the SEQUENCE is an INTEGER: the modulus (`0xC4B0...F5F3`)
• The second item is another INTEGER: the publicExponent (`0x010001`)
• Then, comes the privateExponent (`0x2AEC...D2B1`).

With this information we can already decrypt the message; but let’s see what other data is in the key:

• The prime1 (p): `0xEABC...4AAD`
• The prime2 (q): `0xD682...F71F`

We can check that $\fn_cm p * q = n$ and verify that we’ve got the correct data.

To decrypt the original message, we can now try use Python (again), and create a private key to decrypt using OpenSSL4. To do that, we’re going to use Python’s built-in `Crypto.PublicKey.RSA` class:

``````>>> # First, we load the data
>>> pub_exp = 0x65537
>>> pub_exp = 0x010001
>>> priv_exp = 0x2AEC519F2479ECF2FC79254063555D4CF345E10C0E3D9EB60C7611212102FBE52D45E9AA1357813E4460B30889F75FABF72F049D5C3F4AF2F81984267A8BD2B1
>>> prime_2 = 0xD6820C70A6A2462523D92FD0E1815337249D4FE7C7D7A86EA1242B11AA43F71F
>>>
>>>
>>> # Then, we create the private key with the previous data
>>> from Crypto.PublicKey import RSA
>>> rsa_private = RSA.construct ((mod, pub_exp, priv_exp, prime_1, prime_2))
>>>
>>> # Finnally, we store the private key to decrypt with OpenSSL
>>> open ("key_NEW.pem", "wb").write (rsa_private.exportKey (format = "PEM", pkcs = 8))
521
``````

To decrypt the file, we just have to use our new private key with OpenSSL:

``````\$ openssl rsautl -inkey key_NEW.pem -in secret.txt -decrypt
flag{gk83h280fwlo2}
``````

It turns out I lost a lot of time trying to use pyasn1, when in reality openssl could be used without problem…

After all the dead ends and thinking too much, we got the flag: `flag{gk83h280fwlo2}`

UPDATE: The second part is already available, with the rest of the medium challenges.

1. Unfortunately, I only got something around 2900-3000 points, while the minimum necessary to enter the finals was like 3100… :(

4. Technically, we should be able to decrypt using Python, with either `pow (msg, priv_exp, mod)` or `RSA.decrypt ()`; but, for some reason, it just spits junk. I guess OpenSSL adds something to the file and that’s why we’re unable to decrypt it…