codezen.fr code hacking, zen coding

1Jul/12Off

NDH2k12 Public Wargame – Password Manager #2 – KeePassX Writeup

Posted by aXs

In this challenge we get a Windows XP memory dump and we are told to get the password inside a KeePassX file.

KeePassX stores critical key encrypted in memory following a memory dump attack described here: http://systemoverlord.com/sites/default/files/projects/KeePassX.pdf

But it's still possible to dump keys with the current version. Please see Hugo Caron's draft paper at: http://haxogreen.lu/schedule/2012/attachments/3_CFP_MemoryAnalysis_PasswordManager_slides

(There is 2 small mistakes in this draft paper, follow this write-up and see if you can catch them)

We first dump the process memory and the adressable memory using Volatility:

$ vol.py -f memdump.raw --dump-dir=dump memdump -p 768

Volatile Systems Volatility Framework 1.4_rc1
************************************************************************
Writing KeePassX.exe [ 768] to 768.dmp

$ vol.py -f memdump.raw --dump-dir=dump procmemdump -p 768
Volatile Systems Volatility Framework 1.4_rc1
************************************************************************
Dumping KeePassX.exe, pid: 768 output: executable.768.exe

We can load the exe file in IDA.

Every critical keys is stored encrypted in a modified RC4 form when not being used. The key for all these containers is the same session key. And this session key is stored in a static variable in the bss so the offset will always be the same for a given exe file.

Here is the code that initialize this session key:

Lets check the memory with IDA (remember we created this exe from the memory dump so the BSS section is already filled)

A nice pointer. Now we use Volatility to dump the memory content at 0xc01b00:

>>> cc(pid=768)
Current context: process KeePassX.exe, pid=768, ppid=1584 DTB=0x2e00240
>>> db(0xc01b00, 32)
00c01b00 db a9 65 b9 73 a4 40 7a 8f 66 06 90 42 7c 29 92 ..e.s.@z.f..B|).
00c01b10 f7 c2 3c 57 7c 9c f1 fc 9d 5d e8 c5 66 7a 71 b3 ..<W|....]..fzq.

Wonderful, the session key is: dba965b973a4407a8f660690427c2992f7c23c577c9cf1fc9d5de8c5667a71b3

Now we need to find in the memory the MasterKey. The MasterKey is the SHA256 hashed password of the kdb file. From the MasterKey, the AES key is derived (SHA) for actual encryption of the kdb file.

Storage of protected strings is done using a structure called SecData:

class SecData{
(...)
private:
quint8* data;
int length;
bool locked;
};

In memory, every SecData object will be stored as:

[int pointer][int length][bool locked]

We are looking for the MasterKey which is 32 bytes long and we know the key is going to be locked. So we are looking for:

[pointer][20000000][01]

The following python script will use heuristic to find pointer candidates and then uses the stolen session key to decrypt it using KeePassX's modified RC4:

#!/usr/bin/env python

import struct
import binascii

# !!!!! THIS IS A MODIFIED RC4 IMPLEMENTATION TO MATCH KEEPASSX'S
# do the key schedule. return a byte array for the main algorithm
# k can be a list of numbers or a string
def rc4_init(k):
    k = binascii.unhexlify(k)

    # create and initialise S
    s = range(256)
    # process S using the key data
    j = 0
    kl = len(k)
    ka = 32 << 1
    i = 0
    while i < 256:
        j = (j + s[i] + ord(k[i % kl]) + 0x40) % 256
        s[0], s[j] = s[j], s[0]
        i = i + 1

    return s

# encrypt/decrypt a string using RC4
def rc4(s, val):
    l = len(val)
    buf = bytearray(l)
    i = 0
    j = 0
    idx = 0
    while idx < l:
        i = (i + 1) % 256
        j = (j + s[i]) % 256
        s[i], s[j] = s[j], s[i]
        k = s[(s[i] + s[j]) % 256]
        buf[idx] = (ord(val[idx])) ^ k
        idx = idx + 1
    return str(buf)

memory = open("768.dmp", "rb").read()

p = 0
size_byte = 1
size_int = 4

memory_offset = 10977280

search_length = struct.pack('<L', 32)
search_locked = struct.pack('B', 1)

session_key = "dba965b973a4407a8f660690427c2992f7c23c577c9cf1fc9d5de8c5667a71b3"

while p < len(memory):
  cell1 = memory[p:p+size_int]
  if cell1 == search_length:
    cell2 = memory[p+len(search_length):p+len(search_length)+size_byte]
    if cell2 == search_locked:
      cell3 = memory[p-size_int:p]
      search_data = struct.unpack('<L', cell3)[0]
      file_offset = search_data - memory_offset
      if (file_offset > 0 and file_offset < len(memory)):
        data = memory[file_offset:file_offset+32]
        clear = rc4(rc4_init(session_key), data)
        print "Found pointer at file offset=", \
          hex(p-size_int), "pointer=", \
          hex(search_data), \
          hex(struct.unpack('<L', cell1)[0]), \
          struct.unpack('B', cell2)[0], \
          "data offset=", hex(file_offset), "data=", binascii.hexlify(data), \
          "clear=", binascii.hexlify(clear)
       
  p += size_int

Lets run it:

$ python scan.py
Found pointer at file offset= 0x865f0 pointer= 0x1000004 0x20 1 data offset= 0x588004 data= 6a240aff6a240aff6a240aff6a240aff6a240aff6a240aff6a240aff6a240aff clear= 684f9dbc6560b1c649d75f5943a253186dcf8f3c61513a62dd8957642d6c73c6
Found pointer at file offset= 0x90254 pointer= 0xcf4938 0x20 1 data offset= 0x27c938 data= 090006006f010801d883c9000120012000000000000000000000000001000000 clear= 0b6b91436045b338fb709ca628a658c707eb85c30b75309db7ad5d9b46487939
Found pointer at file offset= 0x1df4ec pointer= 0xc8a510 0x20 1 data offset= 0x212510 data= caa734c900fb9474b54ecd58d73a57c031885df771213b84aadc582e92740c24 clear= c8cca38a0fbf2f4d96bd98fefebc0e273663d8347a540b191d7105b5d53c751d
Found pointer at file offset= 0x1df4f8 pointer= 0xc64f08 0x20 1 data offset= 0x1ecf08 data= caa734c900fb9474b54ecd58d73a57c031885df771213b84aadc582e92740c24 clear= c8cca38a0fbf2f4d96bd98fefebc0e273663d8347a540b191d7105b5d53c751d
Found pointer at file offset= 0x1df504 pointer= 0xc82b90 0x20 1 data offset= 0x20ab90 data= 682bc8000100000001000000010000000000000000000000010000001f010000 clear= 6a405f430e44bb3922f355a6288659e707eb85c30b75309db6ad5d9b58497939
Found pointer at file offset= 0x1df510 pointer= 0xc82b68 0x20 1 data offset= 0x20ab68 data= 88afc80005000000050000007a2bc80000005100750069007400740000000000 clear= 8ac45f430a44bb3926f355a653ad91e707ebd4c37e75599dc3ad299b47487939
Found pointer at file offset= 0x1df51c pointer= 0xc8af88 0x20 1 data offset= 0x212f88 data= 8d20fd01ef3da59ba3f0be19d477647148b6e3fea87800130bb145d61aa6f06d clear= 8f4b6a42e0791ea28003ebbffdf13d964f5d663da30d308ebc1c184d5dee8954
Found pointer at file offset= 0x2709f4 pointer= 0xcf4938 0x20 1 data offset= 0x27c938 data= 090006006f010801d883c9000120012000000000000000000000000001000000 clear= 0b6b91436045b338fb709ca628a658c707eb85c30b75309db7ad5d9b46487939
Found pointer at file offset= 0x275094 pointer= 0xcf4938 0x20 1 data offset= 0x27c938 data= 090006006f010801d883c9000120012000000000000000000000000001000000 clear= 0b6b91436045b338fb709ca628a658c707eb85c30b75309db7ad5d9b46487939
Found pointer at file offset= 0x28cf5f4 pointer= 0x2eeb2f8 0x20 1 data offset= 0x24732f8 data= e8ffffff766b00004e00000010733f00010000000000b6bba8ffffff7b003300 clear= ea9468bc792fbb396df355a639f566e706eb85c30b7586261f52a2643c484a39
Found pointer at file offset= 0x28d0840 pointer= 0x2822ea3 0x20 1 data offset= 0x1daaea3 data= 0000000000000000000000000000000000000000000000000000000000000000 clear= 026b97430f44bb3923f355a6298659e707eb85c30b75309db7ad5d9b47487939
Found pointer at file offset= 0x28d5180 pointer= 0x4cc60b4 0x20 1 data offset= 0x424e0b4 data= 0000000000000000000000000000000000000000000000000000000000000000 clear= 026b97430f44bb3923f355a6298659e707eb85c30b75309db7ad5d9b47487939
Found pointer at file offset= 0x440ad54 pointer= 0x100020c 0x20 1 data offset= 0x58820c data= 6a240aff6a240aff6a240aff6a240aff6a240aff6a240aff6a240aff6a240aff clear= 684f9dbc6560b1c649d75f5943a253186dcf8f3c61513a62dd8957642d6c73c6

Obviously some of those are false positives. A good key has a good entropy so we can discard a few keys with many nulls or repeated strings, we are left with:

Found pointer at file offset= 0x1df4ec pointer= 0xc8a510 0x20 1 data offset= 0x212510 data= caa734c900fb9474b54ecd58d73a57c031885df771213b84aadc582e92740c24 clear= c8cca38a0fbf2f4d96bd98fefebc0e273663d8347a540b191d7105b5d53c751d
Found pointer at file offset= 0x1df4f8 pointer= 0xc64f08 0x20 1 data offset= 0x1ecf08 data= caa734c900fb9474b54ecd58d73a57c031885df771213b84aadc582e92740c24 clear= c8cca38a0fbf2f4d96bd98fefebc0e273663d8347a540b191d7105b5d53c751d
Found pointer at file offset= 0x1df51c pointer= 0xc8af88 0x20 1 data offset= 0x212f88 data= 8d20fd01ef3da59ba3f0be19d477647148b6e3fea87800130bb145d61aa6f06d clear= 8f4b6a42e0791ea28003ebbffdf13d964f5d663da30d308ebc1c184d5dee8954

And we have a dupe so we are left with 2 possibles decrypted MasterKey:

c8cca38a0fbf2f4d96bd98fefebc0e273663d8347a540b191d7105b5d53c751d

or

8f4b6a42e0791ea28003ebbffdf13d964f5d663da30d308ebc1c184d5dee8954

Trivial: locate the kdb in the memory dump and save it to a file. Kdb signature is "03d9a29a65fb4bb5".

Now we have 2 approaches: we can derive the AES key and do manual decryption of the kdb file or we can patch KeePassX to accept a MasterKey instead of a password.

I went with the patched KeePassX because it looked to be easier than do some manual AES processing. (I was wrong of course as it took me some long fiddling to get it working)

Here is patched KeePassX opening the kdb file after we try 2 candidates MasterKey

Important modifications were:

- Skip raw password transform, we will input a hex string in the password dialog, that hex string is the decrypted MasterKey

Replace
SHA256::hashBuffer(Password_CP1252.data(),*RawMasterKey_CP1252,Password_CP1252.size());
by
convHexToBinaryKey(Password_CP1252.data(),(char*)(*RawMasterKey_CP1252));

- Copy RawMasterKey to MasterKey (RawMasterKey is our decoded hex string)

memcpy(*MasterKey, *RawMasterKey, 32);

Dirty KeePassX patch here (CTF quality)

Share
1May/12Off

PlaidCTF 2012 – Password Guessing 300 – Encryption Service Writeup

Posted by aXs

We found the source code for this robot encryption service, except the key was redacted from it. The service is currently running at 23.21.15.166:4433

#!/usr/bin/python
import os
import struct
import SocketServer
from Crypto.Cipher import AES

ENCRYPT_KEY = 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx'.decode('hex')
# Character set: lowercase letters and underscore
PROBLEM_KEY = 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxx'

BLOCK_SIZE = 16

def pad(data, blocksize):
    l = blocksize - (len(data) % blocksize)
    return data + chr(l) * l

def encrypt(data, iv):
    aes = AES.new(ENCRYPT_KEY, AES.MODE_CBC, iv)
    return aes.encrypt(pad(data, BLOCK_SIZE))

class ProblemHandler(SocketServer.StreamRequestHandler):
    def handle(self):
        iv = os.urandom(16)
        self.wfile.write(iv)
        while True:
            data = self.rfile.read(4)
            if not data:
                break

            try:
                length = struct.unpack('I', data)[0]
                if length > 4096:
                    break
                data = self.rfile.read(length)
                data += PROBLEM_KEY
                ciphertext = encrypt(data, iv)
                iv = ciphertext[-16:]
                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()

In this challenge, we can submit strings to a python service, these strings will be used as plaintext for an AES encryption. The challenge flag is appended to our plaintext.

We notice 2 things:
- We control the plaintext prefix
- We know the IV before sending our plaintext

This is a recipe for disaster as explained in various papers since 2001
- TLS IV CBC attack: http://www.openssl.org/~bodo/tls-cbc.txt
- SSL BEAST attack: http://www.educatedguesswork.org/2011/09/security_impact_of_the_rizzodu.html

The main point is that since we know the IV before sending our plaintext.

How to proceed:
- Make the IV a fixed value: send Plaintext = IV -> C = AES(P ^ IV) -> C = AES(IV ^ IV) -> C = AES(0) -> IV = fixed value
- Send plaintext 'A' * 15 : Since block size is 16, the first byte of the flag get appended to our plaintext in this block, we keep the returned block as a reference block
- Make the IV a fixed value (same as above)
- Send plaintext 'A' * 15 + chr(a) : if our guess chr(a) is the same char than the first char of the flag, the very same block that our reference block will be returned!

It's even easier as the service allow us to chain packets ad infinitum.

#!/usr/bin/env python
# -*- coding: latin-1 -*-

import sys
import socket
import struct

BLOCK_SIZE = 16
PROBLEM_KEY = 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxx'
CHARSET = "etainosrldhcumfpygwvbkxjqz_"

def send_packet(s, data):
  s.send(struct.pack('I', len(data)))
  s.send(data)

def recv_packet(s):
  return s.recv(struct.unpack('I', s.recv(4))[0])

def do_block(s, iv, offset, payload):
  send_packet(s, iv)
  recv_payload = recv_packet(s)

  payload = 'A' * (BLOCK_SIZE - (offset % BLOCK_SIZE)) + payload

  send_packet(s, payload)
  recv_payload = recv_packet(s)

  block_offset = offset / BLOCK_SIZE * BLOCK_SIZE

  return recv_payload[-16:], recv_payload[block_offset : block_offset + BLOCK_SIZE ]

if len(sys.argv) != 3:
  print '\nUsage:\t./es.py [host] [port]'
  sys.exit(1)

host = sys.argv[1]
port = int(sys.argv[2])

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((host, port))

iv = s.recv(16)

print 'Initial IV', iv.encode("hex")

key = '';

for i in xrange(1, len(PROBLEM_KEY) + 1):

  iv, ref_block = do_block(s, iv, i, '')

  print 'Reference block', i, ref_block.encode("hex")

  for char in CHARSET:
    iv, block = do_block(s, iv, i, key + char)

    if ref_block == block:
      key += char
      print 'Adding char', char, 'to key', key
      break

print 'Key', key

s.close()

$ python es.py 23.21.15.166 4433
Initial IV 4bd8f3081acbc15928dda4361d65c176
Reference block 1 c7da9a9e5c7df566c7d1c78c7f958708
Adding char p to key p
Reference block 2 c342bac54efbefea7dbd6b2c07be73fe
Adding char r to key pr
Reference block 3 8c65858c5fcbdf5cbcc7e813a47cdaf7
Adding char e to key pre
Reference block 4 9bc925111eabd7110a9c5ba6fd68d15d
Adding char d to key pred
Reference block 5 d039728634a56052c00555a4dfa031e9
Adding char i to key predi
Reference block 6 bd60a3b4c56bb52b0adb190a88f9908e
Adding char c to key predic
Reference block 7 ba22638ee7cbbf30e7473a1855c85ae5
Adding char t to key predict
Reference block 8 b4bac8637ea67d3acbdaebd628e83757
Adding char a to key predicta
Reference block 9 c452a87a9317b42d800ee335b1325cdb
Adding char b to key predictab
Reference block 10 a19025c18c6974ab9b0bd8795e573a0a
Adding char l to key predictabl
Reference block 11 b08fb475d1fe22f0578eaeb44324ab80
Adding char e to key predictable
Reference block 12 2c49728b3c0430eba12b9eadd85aa4a3
Adding char _ to key predictable_
Reference block 13 e50682c26d6cdc97b13f7012160d1e49
Adding char i to key predictable_i
Reference block 14 005267fdb690b28bda84685e86a3416e
Adding char v to key predictable_iv
Reference block 15 c6c8cb7836bda8e3e42b77e06c1455a4
Adding char s to key predictable_ivs
Reference block 16 4b5f8094c71f51bcab515f120a32b059
Adding char _ to key predictable_ivs_
Reference block 17 cf1422daa9ccc75f921463811a3731da
Adding char a to key predictable_ivs_a
Reference block 18 7f9391c531bc0cfc807ca3d775b39718
Adding char r to key predictable_ivs_ar
Reference block 19 5229aaa4b88439f2a638f5d681dbe1d8
Adding char e to key predictable_ivs_are
Reference block 20 0506234b02a15e7bd979d99a43d1e106
Adding char _ to key predictable_ivs_are_
Reference block 21 1e9144b0c3c7c50d7f103002f6da36a3
Adding char d to key predictable_ivs_are_d
Reference block 22 5fc2881eea7170935c502d25f49976f6
Adding char a to key predictable_ivs_are_da
Reference block 23 c8a8c847ec00f510adb16b81ab21b49c
Adding char n to key predictable_ivs_are_dan
Reference block 24 7747633ede348721e6ba99f467f2f695
Adding char g to key predictable_ivs_are_dang
Reference block 25 1ea4b93c0598027d3572107a0c0d8ca0
Adding char e to key predictable_ivs_are_dange
Reference block 26 8363c6e43ba90bdbaebb22c4867770d3
Adding char r to key predictable_ivs_are_danger
Reference block 27 fcda24acc44f419b3c75b089ff44d5cc
Adding char o to key predictable_ivs_are_dangero
Reference block 28 98be810462614bebcbef42cc8f86df8f
Adding char u to key predictable_ivs_are_dangerou
Reference block 29 b4dfd749e702755a9bfb434776ec07b8
Adding char s to key predictable_ivs_are_dangerous
Key predictable_ivs_are_dangerous

The key is predictable_ivs_are_dangerous

Share
4Mar/12Off

Insomni’hack 2012 Network PCAP Write-ups

Posted by aXs

In this challenge we 2 files: 1 PCAP, 1 Python

The PCAP file contains a dialog between a client and server that goes like this:

> = client to server
< = server to client > 8e67bb26b358e2ed20fe552ed6fb832f397a507d:3daf723376f823eceeb314c8fa60e47b1ba23633
< 5f367ff47fff772986cca54219fa167175353dc7 > 78be5fe51f264a4067463bad57022348
< 3290452b9a9f6d18523347dd1daa54a1e09195a7 > 94817a6b1d833e1ffb4fcb2aa7dd14143dc5759e>_<8b060ba4b309e73abd079d8f0128056c07b78cad < Bienvenue, superuser. Le lieu du rendez-vous n'est pas encore defini. The original Python file goes like this:

#!/usr/bin/python2

import SocketServer
import socket, sys, hashlib, random


if __name__ == "__main__":
    HOST, PORT = sys.argv[1], 9999
    USER = raw_input("Utilisateur : ").strip()
    PASS = raw_input("Mot de passe : ").strip()
    sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    sock.connect((HOST, PORT))

    try:    
        data = hashlib.sha1(USER).hexdigest()+":"+hashlib.sha1(hashlib.md5(hashlib.sha1(PASS).hexdigest()+hashlib.sha1(PASS).hexdigest()+hashlib.md5(USER).hexdigest()).hexdigest()).hexdigest()
        sock.sendall(data + "\n")

        challenge = sock.recv(41).strip()
        sock.sendall(hashlib.md5(hashlib.sha1(PASS).hexdigest()+"SuperSalt"+hashlib.md5(USER+challenge).hexdigest()).hexdigest() + "\n")

        challenge = sock.recv(41).strip()
        sock.sendall(hashlib.sha1(PASS).hexdigest()+">_<"+hashlib.sha1(challenge+hashlib.md5(USER).hexdigest()).hexdigest() + "\n")
        print sock.recv(1024)
    except:
        print sock.recv(1024)

    sock.close()

We need to get updated information on the meeting location so we need to do a replay attack on this service.

We are going to map the network stream to the python code:

Handshake

> 8e67bb26b358e2ed20fe552ed6fb832f397a507d:3daf723376f823eceeb314c8fa60e47b1ba23633

sha1(user) : sha1(sha1(pass)+sha1(pass)+md5(user))

Challenge 1:

< 5f367ff47fff772986cca54219fa167175353dc7 > 78be5fe51f264a4067463bad57022348

md5(sha1(pass)+"SuperSalt"+md5(user+challenge))

Challenge 2:

< 3290452b9a9f6d18523347dd1daa54a1e09195a7 > 94817a6b1d833e1ffb4fcb2aa7dd14143dc5759e>_<8b060ba4b309e73abd079d8f0128056c07b78cad sha1(pass)+">_<"+sha1(challenge+md5(user)) We can deduce: sha1(user) = 8e67bb26b358e2ed20fe552ed6fb832f397a507d sha1(pass) = 94817a6b1d833e1ffb4fcb2aa7dd14143dc5759e Google bruteforcing sha1(user) gives "superuser", which match the plain text welcome message "Bienvenue, superuser". We can also Google bruteforce the challenges but that's useless. user = "superuser" challenge1 = "4283399" challenge2 = "3593819" So we know USER, now we need PASS. But we only have sha1(pass) and it cannot be Google bruteforced. But do we really need to know PASS ? If you look closely in the code above, you will see we only use the sha1 of PASS, never anything else. Since we know USER, we can carry a replay attack. We remove all reference to PASS and put directly the sha1(pass) string.

#!/usr/bin/python2

import SocketServer
import socket, sys, hashlib, random


if __name__ == "__main__":
    HOST, PORT = "10.13.37.12", 9999
    USER = "superuser"
    sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    sock.connect((HOST, PORT))

    try:    
        sock.sendall("8e67bb26b358e2ed20fe552ed6fb832f397a507d:3daf723376f823eceeb314c8fa60e47b1ba23633" + "\n")

        challenge = sock.recv(41).strip()
        sock.sendall(hashlib.md5("94817a6b1d833e1ffb4fcb2aa7dd14143dc5759eSuperSalt"+hashlib.md5(USER+challenge).hexdigest()).hexdigest() + "\n")

        challenge = sock.recv(41).strip()
        sock.sendall("94817a6b1d833e1ffb4fcb2aa7dd14143dc5759e>_<"+hashlib.sha1(challenge+hashlib.md5(USER).hexdigest()).hexdigest() + "\n")
        print sock.recv(1024)
    except:
        print sock.recv(1024)

    sock.close()

All done.

Share
26Sep/11Off

CSAW 2011 .NET1 Bin200 Write-up

Posted by aXs

In this challenge, we get an archive with an encrypted file and an executable that was used to encrypt it.

We have to decrypt this file to get the key.

Share