Smart Contract Exploits Part 3 — Featuring Capture the Ether (Accounts)
And now we have reached part 3! Both the Accounts and Miscellaneous challenges were written in part 3 initially; However does seem to be lengthy, reaching over 20 pages on the document I was working on, so there will be a part 4 for the Miscellaneous challenges section instead. The Accounts section centers on challenges surrounding Ethereum addresses and on blockchain use of Elliptic Curve cryptography.
For those who missed the first part: https://medium.com/@Enigmatic1256/smart-contract-exploits-part-1-featuring-capture-the-ether-lotteries-8a061ad491b
Whereas the second part is found here: https://medium.com/@Enigmatic1256/smart-contract-exploits-part-2-featuring-capture-the-ether-math-31a289da0427
The website where these challenges could be found: https://capturetheether.com/challenges/
And the author of these challenges is the very brilliant smarx, catch him on his twitter handle @smarx.
As before, this article will require some prior knowledge with Solidity and its surrounding dev tools.
Without further ado — Huge spoilers ahead!
13. Fuzzy Identity
Source code as below.
Here we are required to have
isComplete set to true, by passing both
isSmarx() calls an interface, which requires the calling address to somehow return the value “smarx” in the context of a function
name(), therefore will require a smart contract to call the authenticate function.
isBadCode() on the other hand takes the calling address and checks if the address contains the bytes “badc0de”.
We need to do two things then: create a smart contract consisting of the function
name() which returns “smarx”, and ensure the contract has “badc0de” as part of its address hex.
The first is simple enough — Prepare a contract which has implements the function
name(). Don’t forget to also implement a call to the function
authenticate() of the Fuzzy Identifier exploit contract, as the contract needs to be the msg.sender.
The second needs a bit of brute forcing and knowledge of how Ethereum generates contract addresses. As previously mentioned on part 2, contract addresses are generated deterministically in Ethereum with the rightmost 160 bits of the keccak256 result of the sender address and nonce in RLP encoding. We could quite quickly cook up a piece of Solidity code which does this, deploy using Nethereum with Ganache to randomly generate a bunch of addresses and test a couple of nonce to see which address + nonce fulfills our requirements. Don’t forget to save the private key of the address, and keep track of the nonce!
The piece of Solidity code basically runs keccak256 with the address and nonce in RLP encoding:
And the VB.NET code, which deploys the contract, then starts generating random addresses and increment nonce, passing them to the contract and checking the returned results:
Run it for a bit… Yes, it’s brute force. Might take a while. Took me a couple of hours at about 25 contract addresses per second:
Now that we have everything prepared, using the private key of the account we know will eventually create a contract address with the hex “badc0de”, we will load the account address with some ETH, increment the nonce of the account on Ropsten testnet, until it eventually reaches our desired nonce, where we will then deploy the contract we have first prepared.
From there, using our exploit contract we call
authenticate() on the Fuzzy Identifier challenge contract, fulfilling all conditions and completing this challenge.
14. Public Key
Source code as below.
This isn’t a smart contract exploit, rather tests one’s understanding of how a blockchain works, and how transactions are signed on a blockchain. When a transaction is signed, a signed transaction is generated, two ECDSA signature outputs are produced (r, s), and the recovery id (v). All these could be used to recover a public key of an address should there been a transaction sent from the address we would like to recover from.
Fortunately, the ethereumjs-tx library make it easy for us to do this without worrying about the underlying intricacies. We just need to get the raw transaction hex, which we could also easily get from Etherscan, so we do not need to attempt reconstructing the transaction from scratch.
Checking the address 0x92b28647ae1f3264661f72fb2eb9625a89d88a31, we indeed see a transaction was sent previously:
Going to the top right corner of the Transaction Information section, click on Tools & Utilities, then click on “Get Raw Transaction Hex”. We are then presented with:
The rest is straightforward, using ethereumjs-tx’s
getSenderPublicKey function, we could easily retrieve the public key:
Use the public key as an input parameter for the function authenticate, and we are done!
Or are we? Well this challenge is 750 points, thought I’d write a little bit more about it. What if Etherscan is momentarily down, and we desperately need to have the raw transaction hex? :P
One of the ways is to use Geth synced with Ropsten, which exposes a method which we can call — eth.getRawTransaction — With the transaction hash as the input parameter, and we can get the raw transaction hex:
But if we don’t want to sync Geth, sure, another way is to get the transaction details:
Reconstruct the raw transaction, and serialise the output in hex:
From there the same takes place — Get the raw transaction hex string and pass it through
getSenderPublicKey(), and we will get the same public key.
Source code as below.
Love this one. Again, this challenge isn’t exactly a smart contract exploit challenge, and I can imagine how some would find this challenge harder than the Fifty Years challenge, despite providing 500 points lesser. Everything which can be seen here in the contract is indeed fine, the address and its corresponding private key is properly generated, so we will need to further expand our scope and search for clues elsewhere.
We pivot towards searching for clues from the transactions sent: https://ropsten.etherscan.io/txs?a=0x6B477781b0e68031109f21887e6B5afEAaEB002b&p=2
Again, on the surface, nothing looks wrong. However, if we start digging into how the transactions are constructed:
Wait. Why is value r, an ECDSA signature output, which is supposed to be unique with each transaction reused here? This will be our exploit vector, and in fact was noticed in the wild as far back as 2013 (https://bitcointalk.to/index.php?topic=271486.0) in the blockchain space. Basically, r-values are to be generated using a random nonce k; However, there were mobile wallets using Android cryptographic libraries which ended up reusing k-values, in turn generating the same r-values across different transactions. This allows the private keys of the addresses to be derived from the signature outputs, resulting in stolen funds.
A bit of a trivia — Reusing the nonce k landed Sony into trouble during 2010: https://www.bbc.co.uk/news/technology-12116051. RFC6979 was later introduced during 2013 to allow deterministic generation of nonce k (hence helping with testing and environments with potentially unreliable PRNG).
Anyway, digression aside, let’s try to figure out how to get our private key for address 0x6B477781b0e68031109f21887e6B5afEAaEB002b.
We have the below values:
For txid 1: 0x061bf0b4b5fdb64ac475795e9bc5a3978f985919ce6747ce2cfbbcaccaf51009
s1 = 0x2bbd9c2a6285c2b43e728b17bda36a81653dd5f4612a2e0aefdb48043c5108de
z1 = 0x4f6a8370a435a27724bbc163419042d71b6dcbeb61c060cc6816cda93f57860c
For txid 2: 0xd79fc80e7b787802602f3317b7fe67765c14a7d40c3e0dcb266e63657f881396
s2 = 0x7724cedeb923f374bef4e05c97426a918123cc4fec7b07903839f12517e1b3c8
z2 = 0x350f3ee8007d817fbd7349c477507f923c4682b3e69bd1df5fbb93b39beb1e04
Common r value: 0x69a726edfb4b802cbf267d5fd1dabcea39d3d7b4bf62b9eeaeba387606167166
There is a method to derive private keys where the nonce k is reused with the values above, using the Python script uploaded here:
Testing the private keys, we will find one of them allowing us access to address 0x6B477781b0e68031109f21887e6B5afEAaEB002b. From there, we call function authenticate() using the private keys to the aforementioned address, setting isComplete to true and completing the challenge.
But since this is 1500 points, it is only right if I write a little more. Perhaps a bit of insight to how the Python script is implemented. There is a real nice thread on StackExchange covering this as well: https://bitcoin.stackexchange.com/questions/37760/converting-ruby-script-into-python-recovering-private-key-when-someone-uses-th/37762#37762
An ECDSA signature s is computed using the below equation:
Where k is the nonce, z is the message digest, r is the ECSDA signature, privKey is the private key, and p is the prime order of the secp256k1 curve (being 0xfffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141).
From here we can derive for s1 and s2:
Remember all operations here are done modulo prime p, therefore (s1-s2)^(-1) is a modulo multiplicative inverse of prime p, which we could calculate using an implementation of Fermat’s Little Theorem as p is prime for this case, or alternatively the extended Euclidean algorithm.
Once we have k, we could then try to find privKey:
And the private key privKey should also be the same when derived from s2 and z2.
Calculating for the case of s1-s2 isn’t sufficient (though for this challenge we are fortunate enough to not have a flipped signature), as we could also use a flipped s signature -s (mod p), which would still be a valid signature output. Therefore preemptively, we would evaluate multiple k candidates (which there exists for each pair a negation of itself), so we could be looking at:
Where -s1-s2 and -s1+s2 we could derive using -k (mod p).
Now that we have all of these, we could implement these in code, plug in the values we have, and see what appears. Everything below will be implemented in Python, and since I am lazy let’s go with Fermat’s Little Theorem for the modulo multiplicative inverse (def inverse_mod):
Deriving private key for k and -k (mod p).
Printing all k candidates and making sure the private keys matches:
We will also do the same for s1+s2, replacing the s1-s2 used to calculate the k candidate, and deriving the private keys in a similar fashion. So the whole code block will be:
Run the script, test which private key is the one to the address 0x6B477781b0e68031109f21887e6B5afEAaEB002b, and we can now solve this challenge.
For this part, instead of a conclusion, I will link straight to part 4 where we will complete the last two Capture the Ether challenges and wrap up: https://medium.com/@Enigmatic1256/smart-contract-exploits-part-3-featuring-capture-the-ether-miscellaneous-d11bae6cb01b