Setting Up Kali Linux

I've been meaning to write this up for a while, and it's as much a reminder to me as it's meant to be useful to anyone else, but with DEFCON around the corner, I'm reformatting my laptop for the trip, so now's the best time.  I'm sure everyone has their own "routine" when setting up a new system.  This is my checklist for Kali Linux, which I use for security cons & ctfs, and is separate from my everyday OS installs.

Install with full-disk encryption
Never know when my laptop might go missing.  Even though I try not to have any personal information on it for cons, I figure it's better safe than sorry.   (For example, I typically have a VPN setup, I'd rather those keys not be copied.)

Install updates and missing packages
I enable 32-bit libs (dpkg --add-architecture i386) and make sure I have the latest packages (apt-get update && apt-get dist-upgrade).  Then I install a few packages I feel are "missing" (i.e., I always want):

  • strace
  • ltrace
  • cryptsetup
  • lvm2
  • network-manager-openvpn-gnome
  • byobu
  • cinnamon
  • ia32-libs
  • virtualbox
  • virtualbox-guest-additions-iso
  • gnupg2
  • opensc
  • scdaemon
  • ufw
  • alsa
  • gcc-multilib
  • volatility
  • kpartx
  • yara-python
  • gimp
  • wine-bin:i386
  • ldap-utils
  • ecryptfs-utils
  • python-hachoir-urwid

I also, of course, install Chrome and a few chrome extensions.

Dotfiles
I also install a bunch of dotfiles I keep in a git repository:

  • .gnupg/gpg.conf
  • .bashrc
  • .gdbinit
  • .vimrc
  • .gitconfig
  • .ssh/config

Boston Key Party -- MITM

Boston Key Party is the latest CTF I've played in (this time playing with some local friends as part of our team 'Shadow Cats'). The first challenge we cleared (actually, first blood in the CTF) was MITM.

Now, you might think a challenge named "MITM" was some sort of Man-In-The-Middle exercise, but it's actually crypto! We're given five base-64 encoded messages: two plaintext/ciphertext pairs, and a ciphertext (which we're presumably supposed to decrypt).

message 1:  QUVTLTI1NiBFQ0IgbW9kZSB0d2ljZSwgdHdvIGtleXM=
encrypted:  THbpB4bE82Rq35khemTQ10ntxZ8sf7s2WK8ErwcdDEc=
message 2:  RWFjaCBrZXkgemVybyB1bnRpbCBsYXN0IDI0IGJpdHM=
encrypted:  01YZbSrta2N+1pOeQppmPETzoT/Yqb816yGlyceuEOE=
ciphertext: s5hd0ThTkv1U44r9aRyUhaX5qJe561MZ16071nlvM9U=

Decoding the two plaintexts gives us:

AES-256 ECB mode twice, two keys
Each key zero until last 24 bits

Some useful hints. So our construction is C = EK2(EK1(P)) where K1 and K2 are both 256-bit keys with the first 29 bytes being all nulls. This means the remaining keyspace for each key is 224, or 224 * 224 = 248 for both keys combined. Brute forcing 248combinations seems... unlikely during a CTF.

The good news is, we only actually have to try ~225 possible keys, which is quite doable even on my laptop. (Actually completes in a matter of minutes.) The trick is a Meet in the Middle (MITM again!) attack. Because we have a plaintext/ciphertext pair, we can encrypt the plaintext with all possible keys, store those encryptions, then decrypt the ciphertext with all possible keys and check those keys against the encryptions. They will match when you have found your two keys, which can then be used to decrypt the unknown ciphertext.

from Crypto.Cipher import AES
plain = 'QUVTLTI1NiBFQ0IgbW9kZSB0d2ljZSwgdHdvIGtleXM='.decode('base64')
cipher = 'THbpB4bE82Rq35khemTQ10ntxZ8sf7s2WK8ErwcdDEc='.decode('base64')
unknown = 's5hd0ThTkv1U44r9aRyUhaX5qJe561MZ16071nlvM9U='.decode('base64')
 
KEY_PADDING = chr(0)*29
 
def NewAES(key):
  return AES.new(key, mode=AES.MODE_ECB)
 
def Encrypt(short_key, text=plain):
  return NewAES(KEY_PADDING+short_key).encrypt(text)
 
def Decrypt(short_key, text=cipher):
  return NewAES(KEY_PADDING+short_key).decrypt(text)
 
def KeyGen():
  """Generator for all possible 24 bit keys."""
  for a in xrange(0, 256):
    for b in xrange(0, 256):
      for c in xrange(0, 256):
        yield chr(a)+chr(b)+chr(c)
 
def EncryptTable():
  """Map of encryptions to keys."""
  table = {}
  for short_key in KeyGen():
    table[Encrypt(short_key)] = short_key
  return table
 
table = EncryptTable()
for short_key in KeyGen():
  decrypted = Decrypt(short_key)
  if decrypted in table:
    # Have a match, now decrypt the unknown
    print Decrypt(short_key, Decrypt(table[decrypted], unknown))
    break

The downside to this technique is that it has 224 memory complexity, as you store an entire hash table of encryption->key pairs for the inner encryption. However, even with the overhead in python, 224 memory seems to amount to ~2.5GB, so a small tradeoff here.


PlaidCTF Compression

PlaidCTF 2013 had a level called "Compression". Here's the provided code for this level:

#!/usr/bin/python
import os
import struct
import SocketServer
import zlib
from Crypto.Cipher import AES
from Crypto.Util import Counter
 
# Not the real keys!
ENCRYPT_KEY = '0000000000000000000000000000000000000000000000000000000000000000'.decode('hex')
# Determine this key.
# Character set: lowercase letters and underscore
PROBLEM_KEY = 'XXXXXXXXXXXXXXXXXXXX'
 
def encrypt(data, ctr):
    aes = AES.new(ENCRYPT_KEY, AES.MODE_CTR, counter=ctr)
    return aes.encrypt(zlib.compress(data))
 
class ProblemHandler(SocketServer.StreamRequestHandler):
    def handle(self):
        nonce = os.urandom(8)
        self.wfile.write(nonce)
        ctr = Counter.new(64, prefix=nonce)
        while True:
            data = self.rfile.read(4)
            if not data:
                break
 
            try:
                length = struct.unpack('I', data)[0]
                if length > (1<<20):
                    break
                data = self.rfile.read(length)
                data += PROBLEM_KEY
                ciphertext = encrypt(data, ctr)
                self.wfile.write(struct.pack('I', len(ciphertext)))
                self.wfile.write(ciphertext)
            except:
                break
 
class ReusableTCPServer(SocketServer.ForkingMixIn, SocketServer.TCPServer):
    allow_reuse_address = True
 
if __name__ == '__main__':
    HOST = '0.0.0.0'
    PORT = 4433
    SocketServer.TCPServer.allow_reuse_address = True
    server = ReusableTCPServer((HOST, PORT), ProblemHandler)
    server.serve_forever()

So there's a few interesting things of note here:

  • They take user-supplied input, concatenate the flag, and then encrypt and return the value.
  • Input is limited to 1MB (1
  • We're compressing with gzip and then encrypting with AES in CTR mode.
  • It's a 128 bit nonce: 8 bytes of urandom, followed by a 64-bit counter.
My first thoughts were to attack CTR mode: reuse of the IV in a CTR mode cipher is quite fatal. But given that the nonce is of reasonably high entropy (64-bit) and our input length limit prevents wrapping the counter (if we could even send that much data, which we can't) it becomes quite obvious we won't be getting a reused IV anytime soon.

So I start thinking about the fact that AES (or any block cipher) in CTR mode is really a stream cipher -- they encrypt the counter with the key, to produce a keystream, then XOR with the plaintext to get the ciphertext. In particular, pycrypto guarantees that len(input) == len(output). Given the name of the level (Compression) I start thinking about approaches to get information out of the ciphertext length.

At this point, it's worth revisiting the design of the DEFLATE algorithm (used by the zlib.compress call in the compression.py program). DEFLATE uses a combination of Huffman coding and LZ77/LZ78-style duplicate string elimination. In this context, I believe the duplicate string elimination plays the bigger role -- this part takes repeated sections of the input and, for the 2nd and later instance, includes a pointer back to first instance that is shorter than the original string. For our purposes, that means if we provide input that contains substrings of the unknown key, we will get a shorter response than if our string is completely different than the flag. To test my theory, I fired off 27 tries to the server, each containing one of the valid ([a-z_]) characters repeated 3 times: all responses, save one, were the same length (30 bytes). Only the repeated 'c' string came back at 29 bytes. This led me to believe that 'c' probably started the flag. (If it only needed to be in the flag, more than one character would likely have returned a different length.)

I put together a script to go through character by character and look for lengths that were shorter from the rest. During my first couple of runs, it would frequently get stuck until I hit upon the idea of putting the test string multiple times, increasing the likelihood that duplicate string elimination would use the entire thing. Eventually, I had a few candidate flags, but from glancing at them, it was clear what the answer was...

import struct
import socket
import sys
import collections
 
HOST = 'ip.add.res.s'
PORT = 4433
 
def try_val(val):
  sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
  sock.connect((HOST, PORT))
  nonce = sock.recv(8)
  sock.send(struct.pack('I', len(val)))
  sock.send(val)
  data = sock.recv(4)
  recv_len = struct.unpack('I', data)[0]
  data = sock.recv(recv_len)
  return (nonce, data)
 
 
def get_candidate_len(can):
  nonce, data = try_val(can)
  return len(data)
 
def try_layer(prefix):
  if len(prefix) == 20:
    print "Found candidate %s!" % prefix
    return
 
  candidates = 'abcdefghijklmnopqrstuvwxyz_'
  print "Trying %s" % prefix
  sys.stdout.flush()
  samples = {}
  for c in candidates:
    val = prefix + c
    samples[val] = get_candidate_len(val*2 if len(val)>9 else val*5)
  m = mode(samples.values())
  for k in samples:
    if samples[k] == m:
      continue
    try_layer(k)
 
def mode(l):
  c = collections.Counter(l)
  return c.most_common(1)[0][0]
 
try_layer('')

Booting Raw Partitions in VirtualBox with Grub2

Background: I dual-boot my laptop between two different Linux distributions: one for normal/desktop use (currently Mint), and one for "security" uses: mostly CTFs or otherwise hostile networks (currently Kali Linux). I also kept a Kali installation in a VM for use from within my desktop environment, but I was getting tired of having two Kali installations on the one laptop. I'd discover irritation at different configurations, not easily having data between the two, etc. Suffice it to say that fewer installations to maintain is a good thing. So I wondered: can I boot my raw hard disk install from VirtualBox?

Yes, but it took some work to get it right. First, I needed to get my partitions mapped into a VirtualBox disk. Let's start by looking at my physical partitioning:

   Device Boot      Start         End      Blocks   Id  System
/dev/sda1   *        2048     2050047     1024000   83  Linux      | Mint Boot
/dev/sda2         2050048     3098623      524288   83  Linux      | Kali Boot
/dev/sda3         3098624  1953523711   975212544    5  Extended   |
/dev/sda5         3100672  1539100671   768000000   83  Linux      | Mint Root (Encrypted LVM)
/dev/sda6      1539102720  1641502719    51200000   83  Linux      | Kali Root

There's a VBoxManage command that maps partitions into a VMDK, so I started by building a VMDK with that. This maps the two partitions and creates a flatfile to store MBR and extended partition information. With my extended partitions, this file had 3 sets of 63 sectors of 512B each.

VBoxManage internalcommands createrawvmdk -filename /vms/kali/kali.vmdk -rawdisk /dev/sda -partitions 2,6 -relative

The documentation also tells me that I need to make sure the user running VirtualBox has rw permissions to the partitions and read access to the full device, so I set up some udev rules to get the permissions right:

# Full disk needs to be readable by vboxusers
ENV{ID_SERIAL_SHORT}=="JG44446PG0TR1D", ENV{DEVTYPE}=="disk", GROUP="vboxusers", MODE="0640"
# sda2, 6 need to be R/W by vboxusers
ENV{ID_SERIAL_SHORT}=="JG44446PG0TR1D", ENV{DEVTYPE}=="partition", ENV{ID_PART_ENTRY_NUMBER}=="2", GROUP="vboxusers", MODE="0660"
ENV{ID_SERIAL_SHORT}=="JG44446PG0TR1D", ENV{DEVTYPE}=="partition", ENV{ID_PART_ENTRY_NUMBER}=="6", GROUP="vboxusers", MODE="0660"

At this point, I created a VM using the /vms/kali/kali.vmdk file as the root device and added the LiveCD as a boot CD, as I figured I would probably need to reinstall grub. (Grub on my real disk uses the grub.cfg from /dev/sda1 which isn't being included here.) I booted from the LiveCD, set up a chroot, chroot and ran grub-install:

$ mkdir /target
$ mount /dev/sda6 /target
$ mount /dev/sda2 /target/boot
$ for x in sys dev proc;do mount -o bind /$x /target/$x;done
$ chroot /target /bin/bash
# grub-install /dev/sda
Installation finished. No error reported.

Awesome! I rebooted, tried to boot from the hard disk, got as far as "GRUB Loading", and... nothing more. Well, crap. I re-tried reinstalling grub, ran grub-setup with -v to make sure it was looking at the right partition, everything I could think of. A Virtualbox bug report suggested that Grub2 booting on raw partitions was broken. But I'm pretty hardheaded. After looking at Grub2 docs and even the source, I realized that Grub2 uses the entire space between the MBR and the 1st partition for its "core.img". It turns out that VirtualBox only creates a 63-sector long MBR/DOS compatibility area before mapping the partitions, but my partition table is 1MB aligned (as has been the default for some time, due to "Advanced Format" drives and SSDs). VirtualBox created the following mapping:

RW 63 FLAT "Kali-pt.vmdk" 0
RW 1985 ZERO 
RW 2048000 ZERO 
RW 1048576 FLAT "/dev/sda2" 0
RW 63 FLAT "Kali-pt.vmdk" 63
RW 1985 ZERO 
RW 1536000000 ZERO 
RW 63 FLAT "Kali-pt.vmdk" 126
RW 1985 ZERO 
RW 102400000 FLAT "/dev/sda6" 0
RW 312022448 ZERO 

I think RW is a read/write mapping (might be raw?). The number is the number of 512B sectors for that extent, FLAT means map 1:1 to a file, then the file (or device) name, and finally, an offset in that device/file. (0 for full devices). The ZERO mapping seems to function like /dev/null: reads are zero, writes go into a black hole. Could grub be writing into that black hole and thus be missing part of the bootloader when I reboot? I decided to give Grub the full 2048 sectors that my partition table wanted, but in order to do that, I would need to rewrite the Kali-pt.vmdk raw file and update the extents. I used dd to rewrite the vmdk with more space before the 1st partition:

$ dd if=Kali-pt.vmdk bs=512 count=63 of=Kali-fixed.vmdk
$ dd if=/dev/zero bs=512 count=1985 seek=63 of=Kali-fixed.vmdk
$ dd if=Kali-pt.vmdk bs=512 skip=63 seek=2048 of=Kali-fixed.vmdk
$ mv Kali-fixed.vmdk Kali-pt.vmdk

Then I updated the extent mapping by getting rid of the first 1985 ZERO and changing the first 63 sector mapping to a full 2048 sectors, and adjusting the offsets into Kali-pt.vmdk elsewhere:

RW 2048 FLAT "Kali-pt.vmdk" 0
RW 2048000 ZERO 
RW 1048576 FLAT "/dev/sda2" 0
RW 63 FLAT "Kali-pt.vmdk" 2048
RW 1985 ZERO 
RW 1536000000 ZERO 
RW 63 FLAT "Kali-pt.vmdk" 2111
RW 1985 ZERO 
RW 102400000 FLAT "/dev/sda6" 0
RW 312022448 ZERO 

After all this, I rebooted off the LiveCD once again and re-ran the grub-install command (hoping that 2048 sectors was enough for Grub to install to) and then booted back off the disk. Finally, it worked!

What should've been a 30 minute task to get VirtualBox booting my raw partition ended up taking 3 hours of debugging. Hopefully this can save someone else (or myself when I forget all this...) some time.


Lessons From the Nebula

Exploit-Exercises.com's Nebula, that is.  I just spent a good 8 hours or so working through the levels there, and I'm pretty sure I took much longer than I should have.  In any case, there were a couple of things I was disappointed by: running "getflag" to get a flag (or otherwise being delivered a token) didn't provide you with anything to really validate what you were doing.  You can actually jump directly to any level (which is good when you reset your VM) but not so interesting for "progression" or the sense of accomplishment -- at least for me.

I did, however, learn 3 completely new things from this set of challenges.

1. You can change register values within gdb.
Let's say you have a binary built from the following code:

int main(int argc,char **argv) {
    if(getuid() == 0) {
        printf("Yes, you have root.");
    } else {
        printf("Nope, no root.");
    }
}

Other than getting root, can you get it to print "Yes, you have root?" Let's assume you can't edit or rebuild the binary, only execute it.

GDB to the rescue! Let's see what the code looks like in gdb:

$ gdb -q ./g
Reading symbols from /home/david/tmp/getuid/g...(no debugging symbols found)...done.
(gdb) break main
Breakpoint 1 at 0x400550
(gdb) run
Starting program: /home/david/tmp/getuid/g 

Breakpoint 1, 0x0000000000400550 in main ()
(gdb) disass
Dump of assembler code for function main:
   0x000000000040054c :	push   %rbp
   0x000000000040054d :	mov    %rsp,%rbp
=> 0x0000000000400550 :	sub    $0x10,%rsp
   0x0000000000400554 :	mov    %edi,-0x4(%rbp)
   0x0000000000400557 :	mov    %rsi,-0x10(%rbp)
   0x000000000040055b :	callq  0x400420 
   0x0000000000400560 :	test   %eax,%eax
   0x0000000000400562 :	jne    0x400570 
0x0000000000400564 : mov $0x40063c,%edi 0x0000000000400569 : callq 0x400410 0x000000000040056e : jmp 0x40057a
0x0000000000400570 : mov $0x400650,%edi 0x0000000000400575 : callq 0x400410 0x000000000040057a : mov $0x0,%eax 0x000000000040057f : leaveq 0x0000000000400580 : retq End of assembler dump.

We can see the call to getuid @0x40055b, so we know the comparison we're interested in is right after that. test, not to be confused with cmp, does a logical and on the arguments (which, when they are the same, returns the value itself) and then compares to 0. So we want that test to see it as 0. Let's set a breakpoint there and see what eax gives us.

(gdb) break *0x400560
Breakpoint 2 at 0x400560
(gdb) c
Continuing.

Breakpoint 2, 0x0000000000400560 in main ()
(gdb) disass
...
=> 0x0000000000400560 :	test   %eax,%eax
...

At this point, there are two approaches we can take. We can either change the value of %eax to be 0 (so 0 & 0 == 0) or we can alter %rip (on 64-bit, use %eip on 32-bit) to main+24 which will get us inside the code that would've been skipped by the branch. Personally, I think changing eax is the more obvious, so let's do that and continue execution:

(gdb) set $eax=0
(gdb) c
Continuing.
Yes, you have root.
[Inferior 1 (process 6242) exited normally]

And we're done! Changing rip would work similarly as we can see here:

(gdb) set $rip=0x400564
(gdb) c
Continuing.
Yes, you have root.
[Inferior 1 (process 6961) exited normally]

2. Bash Case Modification
Did you know that bash can modify case when evaluating a variable? Neither did I, until today...

$ FOO='foo Bar Baz'
$ echo ${FOO^^}
FOO BAR BAZ
$ echo ${FOO^}
Foo Bar Baz
$ echo ${FOO,,}
foo bar baz
$ echo ${FOO~~}
FOO bAR bAZ

WTF? I guess bash is trying to displace tr and sed. (Don't forget about ${VAR/.ext/} and similar constructs.)

3. Arbitrary Python Code Executed when de-pickling

import pickle
import subprocess
 
class PickleLS(object):
  def __reduce__(self):
    args = (('/bin/ls',),)
    return (subprocess.Popen, args)
 
 
pickle.loads(pickle.dumps(PickleLS()))

This is a strong reminder to never unpickle untrusted data. Please, go use JSON, YAML, or (if you want to be "enterprisey") use XML. Pickle is not a data interchange format!

So, I'm on to protostar to see what more tricks the guys at Exploit-Exercises have up their sleeves.