codezen.fr code hacking, zen coding

2Jul/12Off

NDH2k12 Public Wargame – Personal Blog

Posted by aXs

This is a SPIP site. This version of SPIP stores database dump in /tmp/dump/[site name]_[date].xml

Article 1 gives a huge hint about the correct date: http://54.247.160.116:8003/spip.php?article1

27 February 10:57, by Friendly-Boy - "Hii dude, did u remember to made a backup of ur site for the migration ?"
27 February 10:59, by Admin - "fine, fine and u ? yes ive made the backup yesterday."

http://54.247.160.116:8003/tmp/dump/My_Blog_20120226.xml

 

<spip_articles>
<id_article>2</id_article>
<surtitre></surtitre>
<titre>Secret</titre>
<soustitre></soustitre>
<id_rubrique>1</id_rubrique>
<descriptif></descriptif>
<chapo></chapo>
<texte>4cb7828311a658b2a5c6e11fa4f504d3</texte>

 

The flag is: 2Secret14cb7828311a658b2a5c6e11fa4f504d3

Share
Filed under: CTF Comments Off
2Jul/12Off

NDH2k12 Public Wargame – Break Me Like Your Sister – zomb_crypt

Posted by aXs

$ ls -la
total 64
-rw-r--r-- 1 francois francois 38120 Jun 30 01:29 crypto-1.jpg
-rw-r--r-- 1 francois francois 3226 Jun 13 20:50 zomb_crypt.pyc

$ file *
crypto-1.jpg: JPEG image data, JFIF standard 1.01
zomb_crypt.pyc: python 2.6 byte-compiled

$ python
Python 2.6.6 (r266:84292, Dec 27 2010, 00:02:40)
[GCC 4.4.5] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> import zomb_crypt
>>> import dis
>>> dir(zomb_crypt)
['Blowfish', 'PasswordError', '__builtins__', '__doc__', '__file__', '__name__', '__package__', 'decode', 'decrypt', 'encrypt', 'getbf', 'hash', 'sys']
>>> dis.dis(zomb_crypt.decrypt)
52 0 SETUP_EXCEPT 190 (to 193)

53 3 LOAD_GLOBAL 0 (open)
6 LOAD_FAST 0 (filename_in)
9 LOAD_CONST 1 ('rb')
12 CALL_FUNCTION 2
15 LOAD_ATTR 1 (read)
18 CALL_FUNCTION 0
21 STORE_FAST 3 (content)

read file content to content variable

54 24 LOAD_GLOBAL 2 (len)
27 LOAD_FAST 3 (content)
30 CALL_FUNCTION 1
33 LOAD_CONST 2 (16)
36 COMPARE_OP 0 (<) 39 JUMP_IF_FALSE 5 (to 47) 42 POP_TOP goodbye if len(content) < 16 55 43 LOAD_GLOBAL 3 (False) 46 RETURN_VALUE >> 47 POP_TOP

56 48 LOAD_FAST 3 (content)
51 LOAD_CONST 2 (16)
54 SLICE+2
55 STORE_FAST 4 (_hash)

First 16 bytes of file is a hash, store it in _hash

57 58 LOAD_GLOBAL 4 (hash)
61 LOAD_FAST 2 (password)
64 CALL_FUNCTION 1

hash password entered by user on command-line

67 LOAD_FAST 4 (_hash)
70 COMPARE_OP 3 (!=)
73 JUMP_IF_FALSE 13 (to 89)

if hash(user password) != stored _hash, goodbye. And we don't care about the rest of the disassembly because we know we must have a password that match the file stored hash.

Which kind of hash ?

>>> dis.dis(zomb_crypt.hash)
26 0 LOAD_CONST 1 ('ahky')
3 STORE_FAST 1 (a)

27 6 LOAD_CONST 2 ('12bqb')
9 STORE_FAST 2 (b)

28 12 LOAD_GLOBAL 0 (__import__)
15 LOAD_GLOBAL 1 (decode)
18 LOAD_FAST 1 (a)
21 CALL_FUNCTION 1
24 CALL_FUNCTION 1
27 STORE_FAST 3 (x)

29 30 LOAD_GLOBAL 2 (getattr)
33 LOAD_FAST 3 (x)
36 LOAD_GLOBAL 1 (decode)
39 LOAD_FAST 2 (b)
42 CALL_FUNCTION 1
45 CALL_FUNCTION 2
48 STORE_FAST 4 (y)

Obfuscated import and method name. What does decode do ? We don't care.

>>> zomb_crypt.decode('ahky')
'zlib'
>>> zomb_crypt.decode('12bqb')
'crc32'

so x = zlib, y = crc32

Nice. CRC32 is easy to bruteforce.

32 >> 83 LOAD_FAST 0 (s)
86 LOAD_CONST 4 ('_')
89 LOAD_CONST 3 (8)
92 LOAD_GLOBAL 3 (len)
95 LOAD_FAST 0 (s)
98 CALL_FUNCTION 1
101 BINARY_SUBTRACT
102 BINARY_MULTIPLY
103 BINARY_ADD
104 STORE_FAST 0 (s)

Pad password to length of 8 with '_' (toto -> toto____)

33 107 LOAD_FAST 0 (s)
110 LOAD_CONST 5 (0)
113 LOAD_CONST 6 (4)
116 SLICE+3
117 LOAD_FAST 0 (s)
120 LOAD_CONST 6 (4)
123 LOAD_CONST 3 (8)
126 SLICE+3
127 ROT_TWO
128 STORE_FAST 1 (a)
131 STORE_FAST 2 (b)

a = password[0:4]
b = password[4:8]

34 134 LOAD_CONST 7 ('%08X%08X')
137 LOAD_FAST 4 (y)
140 LOAD_FAST 1 (a)
143 CALL_FUNCTION 1
146 LOAD_CONST 8 (4294967295L)
149 BINARY_AND
150 LOAD_FAST 4 (y)
153 LOAD_FAST 2 (b)
156 CALL_FUNCTION 1
159 LOAD_CONST 8 (4294967295L)
162 BINARY_AND

Convert to a and b to CRC32

>>> import zlib
>>> zlib.crc32("toto")
281847025

$ hexdump -C crypto-1.jpg | head -1
00000000 31 44 34 34 38 31 45 31 41 32 32 43 38 43 33 42 |1D4481E1A22C8C3B|

a = 1D4481E1
b = A22C8C3B

We need to find a CRC32 value that match a and b. Using the best hash cracker: Google

http://rulus.com/tool/hash/His4
http://rulus.com/tool/hash/n00b

password is His4n00b

$ python zomb_crypt.pyc d crypto-1.jpg His4n00b
[i] Decrypting crypto-1.jpg ...
[i] OK

Share
Tagged as: , Comments Off
2Jul/12Off

NDH2k12 Public Wargame – RSA Writeup

Posted by aXs

Simple RSA:

$ cat john.pub
----- BEGIN PUBLIC KEY -----
KG4gPSAxNTQ5Mzg4MzAyOTk5NTE5LCBlID0gMTAxKQ==
-----  END PUBLIC KEY  -----
francois@squeeze:~/ndh2012/public/rsa$ echo -n "KG4gPSAxNTQ5Mzg4MzAyOTk5NTE5LCBlID0gMTAxKQ==" | base64 -d
(n = 1549388302999519, e = 101)

e = 101
n = 1549388302999519

Factorize n -> n = p * q -> 1549388302999519 = 31834349 * 48670331

$ python
>>> import librsa
>>> e = 101
>>> p = 31834349
>>> q = 48670331
>>> d = librsa.genPrivKey(e, p, q)
+ + +
>>> d
1165876286233741L

$ cat john.key
----- BEGIN PRIVATE KEY -----
KG4gPSAxNTQ5Mzg4MzAyOTk5NTE5LCBkID0gMTE2NTg3NjI4NjIzMzc0MSkK
-----  END PRIVATE KEY  -----
$ echo -n "KG4gPSAxNTQ5Mzg4MzAyOTk5NTE5LCBkID0gMTE2NTg3NjI4NjIzMzc0MSkK" | base64 -d
(n = 1549388302999519, d = 1165876286233741)

$ python decrypt.py john.key flag.asc
francois@squeeze:~/ndh2012/public/rsa$ cat flag
   _________
 _|      ___|_
|  ___  |   | |   HZV
| |___| |___| |
|_           _|   challenge by benjamin
  |  _   _  |
  |_| |_| |_|     c9132f892055ea81fd91a9ed0e54a859
Share
Tagged as: Comments Off
1Jul/12Off

NDH2k12 Public Wargame – Crackme Bukkake Writeup

Posted by aXs

Simple crackme.

After a bit of reversing in IDA:

.text:08048F3C                 mov     eax, [ebp+password]
.text:08048F3F                 mov     [esp+4], eax
.text:08048F43                 lea     eax, [ebp+candidateHash]
.text:08048F46                 mov     [esp], eax
.text:08048F49                 call    WhatisIt
.text:08048F4E                 lea     eax, [ebp+validHash]
.text:08048F51                 mov     [esp+4], eax
.text:08048F55                 lea     eax, [ebp+candidateHash]
.text:08048F58                 mov     [esp], eax
.text:08048F5B                 call    ccc
.text:08048F60                 test    eax, eax
.text:08048F62                 jz      short loc_8048F7B
.text:08048F64                 mov     eax, [ebp+password]
.text:08048F67                 mov     [esp+4], eax
.text:08048F6B                 lea     eax, (aTheHashIsS - 80E605Ch)[ebx] ; "The hash is %s\n"
.text:08048F71                 mov     [esp], eax
.text:08048F74                 call    printf

ccc function is a simple memcmp. Function WhatisIt is the core of this crackme. It uses 2 tables to transform the password in a bytearray that is compared to a valid bytearray stored inside the binary.

Simple bruteforcer:

#!/usr/bin/env python

import string

valid = [ 3, 35, 31, 35, 5, 3, 8, 7, 31, 4, 5, 32, 33, 7, 31, 5, 33, 5, 4, 29, 34, 30, 31, 38, 37, 31, 30, 33, 36, 6, 36, 37 ]

print "len valid=", len(valid)

keyData1 = [ 10,  9,  1, 16, 19, 23, 13,  8,  7,  3, 14, 17, 22, 12,  6, 21, 25,  5, 20, 15, 18, 11, 24,  2,  4,  0, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36]
keyData2 = [ 90, 67, 88, 74, 89, 82, 79, 73, 72, 66, 65, 86, 78, 71, 75, 84, 68, 76, 85, 69, 83, 80, 77, 70, 87, 81, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57 ]

print "len keyData1=", len(keyData1)
print "len keyData2=", len(keyData2)

password = []

charset = string.ascii_lowercase + string.ascii_uppercase + string.digits

v = 0
while v < 32:
  for char in charset:
    t = ord(char)
    for i in range(32):
      for j in range(36):
        if (keyData2[j] == t):
          for k in range(36):
            b = keyData1[k]
            if (b == j):
              c = k + 3

              if valid[v] == c:
                print "v=", v, "t=", t, "VALID"
                password.append(t)
                print "".join(map(chr, password))
                v+=1
$ python bukkake.py
len valid= 32
len keyData1= 37
len keyData2= 36
v= 0 t= 65 VALID
A
v= 1 t= 54 VALID
A6
v= 2 t= 50 VALID
A62
v= 3 t= 54 VALID
A626
v= 4 t= 67 VALID
A626C
v= 5 t= 65 VALID
A626CA
v= 6 t= 70 VALID
A626CAF
v= 7 t= 69 VALID
A626CAFE
v= 8 t= 50 VALID
A626CAFE2
v= 9 t= 66 VALID
A626CAFE2B
v= 10 t= 67 VALID
A626CAFE2BC
v= 11 t= 51 VALID
A626CAFE2BC3
v= 12 t= 52 VALID
A626CAFE2BC34
v= 13 t= 69 VALID
A626CAFE2BC34E
v= 14 t= 50 VALID
A626CAFE2BC34E2
v= 15 t= 67 VALID
A626CAFE2BC34E2C
v= 16 t= 52 VALID
A626CAFE2BC34E2C4
v= 17 t= 67 VALID
A626CAFE2BC34E2C4C
v= 18 t= 66 VALID
A626CAFE2BC34E2C4CB
v= 19 t= 48 VALID
A626CAFE2BC34E2C4CB0
v= 20 t= 53 VALID
A626CAFE2BC34E2C4CB05
v= 21 t= 49 VALID
A626CAFE2BC34E2C4CB051
v= 22 t= 50 VALID
A626CAFE2BC34E2C4CB0512
v= 23 t= 57 VALID
A626CAFE2BC34E2C4CB05129
v= 24 t= 56 VALID
A626CAFE2BC34E2C4CB051298
v= 25 t= 50 VALID
A626CAFE2BC34E2C4CB0512982
v= 26 t= 49 VALID
A626CAFE2BC34E2C4CB05129821
v= 27 t= 52 VALID
A626CAFE2BC34E2C4CB051298214
v= 28 t= 55 VALID
A626CAFE2BC34E2C4CB0512982147
v= 29 t= 68 VALID
A626CAFE2BC34E2C4CB0512982147D
v= 30 t= 55 VALID
A626CAFE2BC34E2C4CB0512982147D7
v= 31 t= 56 VALID
A626CAFE2BC34E2C4CB0512982147D78

$ ./crackme A626CAFE2BC34E2C4CB0512982147D78
The hash is A626CAFE2BC34E2C4CB0512982147D78

Note: yes, the algorithm can be reversed and the solution is then a simple oneliner, I didn't catch that last night.

Share
Tagged as: Comments Off
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