We got a binary file (appearently some malware) as well as a pcap file from a host were the malware was run.

Looking into the binary we found out that it first checks if the environment variable HAX is set, if not it’s gonna just do an “ls” on the current directory and exits.

If HAX is set it’s executing its backdoor. Using G_MESSAGES_DEBUG=all we can also get a few debug infos (not really useful)

root@ctf:/home/ccm# G_MESSAGES_DEBUG=all HAX=1 ./ls
** (process:18650): DEBUG: connected!
** (process:18650): DEBUG: key initialized!
** (process:18650): DEBUG: attaching '/dev/input/by-path/platform-i8042-serio-0-event-kbd'

Looking into the binaries main function we see it’s do 2 things: Opening a connection to it’s remote host and retrieving an initial (random) 32-byte encryption key. Also it sets up a keylogger by opening /dev/input/by-path/*kbd.

Every keystroke that is now made is encrypted and send to the remote host.

Looking into the encryption we find a Salsa20 implemention (at 0x43af40), googling for the constant 0x61707865 was helping a lot ;)

Now it’s fairly simple, there is a python salsa20 library available. The key is the initial key retrieved from the server and the nonce they use a just a memory location counting up:

; incresing the nonce
0x0000000000405b90 498386E001000001                add        qword [ds:r14+0x1e0], 0x1

The decryption can be done quite easily in python. We also need a key mapping for the data from the keyboard device, linux/input.h helps you: http://www.cs.fsu.edu/~baker/devices/lxr/http/source/linux/include/linux/input.h?v=2.6.11.8

The final decrypter/decoder looks like this:

from salsa20 import Salsa20_xor
from struct import unpack


def makekey(data):
    data = unpack('QQHHI', data)
    return {
        'sec': data[0],
        'usec': data[1],
        'type': data[2],
        'code': data[3],
        'value': data[4],
    }


keymap = {
    '4': '3',
    '11': '0',
    '14': '',
    '2': '1',
    '42': '',
    '46': 'C',
    '12': '-',
    '30': 'A',
    '8': '7',
    '21': 'Y',
    '25': 'P',
    '18': 'E',
    '31': 'S',
    '5': '4',
    '33': 'F',
    '20': 'T',
    '28': '',
    '56': '',
    '105': '',
    '29': '',
}


key = '4f ca 2a 2f 55 3b 08 36  24 66 bd 85 89 a0 d7 d9    23 07 cc b6 50 0e 6e d0  7d 95 bf 95 b4 0b ff 36'.replace(' ', '').decode('hex')

print 'Key', key.encode('hex')
assert(len(key) == 32)

keys = open('ls.dump').read().replace(' ', '').replace('\n', '').replace('\r', '').decode('hex')  # ugly ^^

noncecounter = 0
res = ''
shift = False
for i in range(0, len(keys), 24):
    nonce = chr(noncecounter % 256) + chr(int(noncecounter / 256)) + '\x00' * 6
    noncecounter += 1

    keystroke = keys[i:i+24]

    keystroke = makekey(Salsa20_xor(keystroke, nonce, key))

    if keystroke['type'] == 1:
        #print keystroke
        if keystroke['code'] == 14 and keystroke['value'] == 1:
            print 'Backspace'
            res = res[:-1]
        elif keystroke['code'] == 42 and keystroke['value'] == 1:
            print 'Shift 1'
            shift = True
        elif keystroke['code'] == 42 and keystroke['value'] == 0:
            print 'Shift 0'
            shift = False
        elif keystroke['value'] == 1:
            if shift:
                if keymap[str(keystroke['code'])] == '1':
                    res += '!'
                elif keymap[str(keystroke['code'])] == '-':
                    res += '_'
                else:
                    res += keymap[str(keystroke['code'])].upper()
            else:
                res += keymap[str(keystroke['code'])].lower()
            print res

print res

After running this script (ls.dump contains the hex-encoded tcp-stream without the encryption key) we get the flag: 31C3_7yp3_s4f3ty!1