Skip to content

Commit 28b645f

Browse files
committed
Merge branch 'p400-blockchain'
2 parents 294298c + cbe7717 commit 28b645f

File tree

3 files changed

+129
-0
lines changed

3 files changed

+129
-0
lines changed
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
# P400 - Insane in the Blockchain
2+
3+
## Description
4+
5+
Check out this transaction, on the Ethereum blockchain:
6+
https://etherscan.io/address/0x55dfcea405a1c9b7336cd2286c2c3040f9b13e7d
7+
8+
We know [this binary](../blob/master/Pwnable/P400-InsaneInTheBlockChain/bin/geth) was used to sign it.
9+
10+
Extract the private key used to sign this transaction.
11+
12+
## Solution
13+
14+
* Investigate how [ECDSA](https://en.wikipedia.org/wiki/Elliptic_Curve_Digital_Signature_Algorithm) works
15+
* The geth binary is backdoored. Every signature will use k = "four"
16+
17+
### Possible strategy
18+
* Start the binary
19+
* Do some transactions and check the resulting signatures. You should notice
20+
that `r` is always the same. This implies that `k` is being reused...
21+
* Find the value of `k`
22+
23+
### Running the binary
24+
* Run it on a Raspberry Pi 3
25+
* Using [this method](https://reverseengineering.stackexchange.com/questions/8829/cross-debugging-for-mips-elf-with-qemu-toolchain)
26+
* Or [this one](https://wiki.ubuntu.com/ARM/BuildEABIChroot)
27+
* Or [this one](https://hub.docker.com/r/resin/armv7hf-debian/) Thanks to [morisson](https://twitter.com/morisson) for the suggestion
28+
29+
### Finding k
30+
31+
Two possible approaches
32+
33+
* Reverse engineer the binary and find out the value of k
34+
* Fetch the [geth source code](https://github.com/ethereum/go-ethereum/wiki/geth)
35+
* Find out which function generates k and work from there (using dynamic or static RE)
36+
* Google "ecdsa k reuse". Use [this tool](https://github.com/tintinweb/ecdsa-private-key-recovery) and plug in known k (no need for two signatures)
37+
38+
* Analytical method
39+
* Perform two signatures with some key
40+
* Google "edsa k reuse". Use [this tool](https://github.com/tintinweb/ecdsa-private-key-recovery)
41+
* Alternatively, having a known key allows us to extract `k` directly. Having `k` allows us to extract the private key from only transaction only. Check out the [solution file](../blob/master/Pwnable/P400-InsaneInTheBlockChain/solution/solution.py)
42+
25.2 MB
Binary file not shown.
Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
import ecdsa
2+
import binascii
3+
4+
from ecdsa import VerifyingKey
5+
from hashlib import sha256
6+
from ecdsa import numbertheory as nt
7+
8+
9+
# On the geth console
10+
# > eth.getRawTransaction("0x83cc2086b2ca5636c865f910ce473c388ed9b92659e5e24b8ca7cb8cb918dd09")
11+
# "0xf86c8085065680769f83015f909412859112a1a5ae0b3cabf1acc9118c6a3d1e5e3d87038d7ea4c680008026a03906ea21a9252cc364b812a82df152d41d2220df4c80def228ce83b0275a411ca02fc8ba753750c3cc19873d125bc26f0d430426d90d05e757f2f8ff603c1d3e80"
12+
#>
13+
14+
# Nodejs
15+
# > var Web3 = require('web3');
16+
# > var web3 = new Web3(new Web3.providers.HttpProvider('http://127.0.0.1:8545'));
17+
# > var util = require('ethereumjs-util');
18+
# > var tx = require('ethereumjs-tx');
19+
# > var ec = require('secp256k1')
20+
# > txn = new tx("0xf86c8085065680769f83015f909412859112a1a5ae0b3cabf1acc9118c6a3d1e5e3d87038d7ea4c680008026a03906ea21a9252cc364b812a82df152d41d2220df4c80def228ce83b0275a411ca02fc8ba753750c3cc19873d125bc26f0d430426d90d05e757f2f8ff603c1d3e80");
21+
# > t.getSenderPublicKey().toString('hex')
22+
# '638f5c8ff99a9366d63072abbbfa25a5eb2b48974f8f05908987581aceb8fc6673ad4558c48b176ad30c9a764e5093fb1a0c8d7ac1f7150a02fcf6fbed7d5d38' (pubkey_hex)
23+
# > txn.r.toString('hex')
24+
# '3906ea21a9252cc364b812a82df152d41d2220df4c80def228ce83b0275a411c' (r_hex)
25+
# > txn.s.toString('hex')
26+
# '2fc8ba753750c3cc19873d125bc26f0d430426d90d05e757f2f8ff603c1d3e80' (s_hex)
27+
# > t.hash(false).toString('hex')
28+
# 'd8a34a11c3abfd8d9ed664977754d1c2cba35881935e1b9cafa4f0e01911257c' (msghash_hex)
29+
30+
pubkey_hex = b'638f5c8ff99a9366d63072abbbfa25a5eb2b48974f8f05908987581aceb8fc6673ad4558c48b176ad30c9a764e5093fb1a0c8d7ac1f7150a02fcf6fbed7d5d38'
31+
msghash_hex = b'd8a34a11c3abfd8d9ed664977754d1c2cba35881935e1b9cafa4f0e01911257c'
32+
r_hex = b'3906ea21a9252cc364b812a82df152d41d2220df4c80def228ce83b0275a411c'
33+
s_hex = b'2fc8ba753750c3cc19873d125bc26f0d430426d90d05e757f2f8ff603c1d3e80'
34+
35+
pubkey_bytes = binascii.unhexlify(pubkey_hex)
36+
pubkey = VerifyingKey.from_string(pubkey_bytes, curve=ecdsa.SECP256k1)
37+
order = pubkey.curve.order
38+
39+
r = int(r_hex, 16)
40+
s = int(s_hex, 16)
41+
z = int(msghash_hex, 16)
42+
43+
print("r: %d, s: %d, z: %d" % (r, s, z))
44+
45+
privkey = None
46+
generator = pubkey.curve.generator
47+
pubkey_point = pubkey.pubkey.point
48+
49+
k_bytes = b'\x00' * 28 + b'four'
50+
k = int.from_bytes(k_bytes, byteorder='big', signed=False) % order
51+
print("k is: 0x%x" % k)
52+
53+
print("Checking that (G * k).x == r. %d == %d" % ((generator * k).x(), r))
54+
assert((generator * k).x() == r)
55+
56+
for i in range(2):
57+
privkey_maybe = ((-1**i * s) * k - z) * nt.inverse_mod(r, order)
58+
privkey_maybe %= order
59+
print("pubkey: %s, privkey: %x, G * privkey: %s" % (pubkey_point,
60+
privkey_maybe, (generator * privkey_maybe)))
61+
if pubkey_point == generator * privkey_maybe:
62+
privkey = privkey_maybe
63+
privkey_bytes = binascii.unhexlify('%x' % privkey)
64+
65+
sk = ecdsa.SigningKey.from_string(privkey_bytes, curve=ecdsa.SECP256k1)
66+
print(sk.to_pem().decode())
67+
hex_privkey = binascii.hexlify(privkey_bytes)
68+
print('hex private key: %s, hex sha256 private key: %s' %
69+
(hex_privkey, sha256(hex_privkey).hexdigest()))
70+
sig = sk.sign(b'message')
71+
pubkey.verify(sig, b'message')
72+
73+
break
74+
75+
# Alternative approach: Extract k, without reverse engineering the binary,
76+
# and without doing two signatures
77+
# If we use a known private key, k can be extract directly
78+
# Remember that s can be negative. Try both
79+
80+
inv_s = nt.inverse_mod(s, order)
81+
inv_negative_s = nt.inverse_mod(-s, order)
82+
rda = r * privkey
83+
z_plus_rda = (z + rda) % order
84+
85+
print(hex(inv_s * z_plus_rda))
86+
print(hex((inv_negative_s * z_plus_rda) % order))
87+

0 commit comments

Comments
 (0)