BLOG POSTS
Solution to 35C3 Junior CTF Challenge "Entrance"
By Laban Sköllermark (@LabanSkoller)
TL;DR: This post has a lot of details. Skip to the Summary if you know the challenge and are here just for the solution.
Between Christmas and New Year’s I attended the 35th Chaos Communication Congress (CCC), 35C3, in Leipzig, Germany, together with Malmö based Xil hackerspace. It was my third congress (in a row).
Since 2012 there has been a Capture The Flag (CTF) competition at congress. The C3 CTF has been a qualifier for the DEF CON CTF since 2015 (32C3 CTF), which has made the challenges really hard to solve for beginners. For that reason the current C3 CTF organizers Eat Sleep Pwn Repeat introduced a second junior version of the CTF one year ago.
This year (2018) we planned to not take the CTF so seriously and not spend all time playing it since there are a lot of other interesting stuff going on at the congress. Well, that didn’t go very well. Several of our CTF players spent the majority of the CTF time (which runs for 48 hours) actually playing and our team xil.se ended up at sixth place on the 35C3 Junior CTF! We even ranked number two for about 20 hours:
Woo! @xil_hackerspace is now on second place in the #junior35c3ctf!https://t.co/nMKg6EyJTy
— Laban Sköllermark @LabanSkoller@infosec.exchange (@LabanSkoller) December 27, 2018
I spent a whole day solving the challenge Entrance in the Ethereum category
The Challenge
Name: Entrance
Solves: 15
Points: 242 (starting at 500 when just one team solves it)
**Description:**
Can you enter? Again?
Contract Source
Difficulty estimate: Medium
The link Contract in the description goes to Etherscan - The Ethereum Block Explorer so it’s obvious that this is an Ethereum related challenge and I know from before that Ethereum is a very popular so called crypto currency like Bitcoin (well, the actual currency is apparently called Ether), but I don’t know very much about it. Except that you can define “smart” contracts with it. It turned out quite fast that this challenge goes on in a test network called Ropsten which is quite funny because I’m from Sweden and Ropsten is one of the terminal stations (slutstation in Swedish which is funny for English speakers) in the Stockholm metro network. Rinkeby is another Ethereum test network and also a Stockholm metro station. Anyhow - good that we don’t have to spend real Ether on the challenge!
The Contract
The challenge authors are nice to give us the source of the Ethereum contract. Contracts are compiled to a byte code used in the Ethereum Virtual Machine (EVM). Contracts can be written in many languages but this contract uses Solidity. Let’s have a look at the source:
|
|
There is a function getFlag()
which takes _server
and _port
as string arguments so the flag will be sent to us in some way, but we need to have enough of some kind of balance first. It’s unclear to us if and how the Ethereum network will connect to us. After some reading we understand that since EntranceFlag
is an event (defined on line 11), such an event will be published on the Ethereum network together with the contract.
|
|
So we assume that the CTF organizers will monitor their contract’s event log and send the flag to server:port
once there is a new event emitted. Several teams have already solved the challenge and our team member Linus notices events on the particular contract. The Etherscan site shows the latest 25 events for a contract and now when authoring the write-up they are from after the CTF closed, but let’s take one such late event as an example. Event emitted in transaction 0xcfede920ce0cdb1aeae7…:
Address 0x1898ed72826befa2d549004c57f048a95ae0b982
Topics [0] 0x31f9f688587d79c168d76bc74a671922d95848f11342b5896712138d1fb57554
Data 0000000000000000000000000000000000000000000000000000000000000040
0000000000000000000000000000000000000000000000000000000000000080
000000000000000000000000000000000000000000000000000000000000000b
332e382e3137302e313335000000000000000000000000000000000000000000
0000000000000000000000000000000000000000000000000000000000000004
3830303000000000000000000000000000000000000000000000000000000000
All data is in hex and we notice that the fourth and sixth value starts non-zero. Converting them to text (easily done using the drop-down on the site) yields:
3.8.170.135
8000
So now we know how to retrieve the flag once we meet the balance check, but how do we get there?
Idea: Get Balance Below Zero
In order to gamble()
you first have to enter()
which sets your balance to 10. If you’re lucky you get 10 additional balance but if you’re unlucky you lose 10. I also notice that balances
is a map of uint256
. You lose with probability 0.8571 (6/7) so my idea is to quickly gamble()
multiple times in a row (at least twice) so that both gamblings first pass the balance check on line 35 and then subtract 10 from my balance for every loss. I don’t know if I have understood how the Ethereum blockchain works and if this is possible.
|
|
If it works this will result in a very huge balance since there are no negative numbers (it’s apparently called integer overflow – underflow is another thing), thus passing the getFlag()
balance check of at least 300.
Getting Some Ether And Sending My First Ethereum Transaction
When reading the contract code I apparently read too fast. I thought you choose a PIN yourself, but the constructor setting the PIN was of course run when the CTF organizers created/uploaded the contract. My team member Linus realized this and found the PIN in the constructor arguments of the contract:
0000000000000000000000000000000000000000000000000000000000bc4f77
Converting from hex gives the PIN 12341111.
I want to interact with the contract, for example running enter(12341111)
and then checking that my balance is 10 with balanceOf()
. I found Bitfalls’ excellent tutorial How to Call Ethereum Smart Contract Functions which tells me that:
- transactions can be made using MyEtherWallet
- the ABI of a contract (Application Binary Interface) is needed in order to interact with it
- the ABI can be generated using the web based Solidity IDE Remix if you have the contract’s source code
I go to the Remix website and add the file Entrance.sol and try to compile it. Of course I get an error on this row since I don’t have the file:
|
|
Easy to fix though. I search for the name and find that it’s a common library which exists on GitHub. I change the line and it compiles!
|
|
Exporting the ABI of contract Entrance.sol from Remix gives me a file I choose to call Entrance.abi.json. Excerpt:
|
|
I create an account at MyEtherWallet. My address is 0x4ed42ff4bab2553fe46e65c725eb5256c2e8d48d in case you want to follow my first unsteady Ethereum steps. :)
Read operations are free in the Ethereum network but anything that changes state is a transaction and you pay with gas units which are fractions of an Ether. See MyEtherWallet’s article What is Gas? for details and nice looking transaction graphics.
In the test networks there are faucets. Some of them give out 1 ETH (Ether) regularly just by asking for it. I used the Ropsten Ethereum Faucet to fuel up my above address with 1 ETH so I can start sending transactions.
My first enter(12341111)
transaction is 0x8b4f70c012a71f54a339…. It seems to work as intended since running balanceOf(0x4eD42ff4bAB2553fE46E65C725eb5256C2E8D48D)
afterwards returns 10.
I’m not sure I tried my idea about running gamble()
multiple times in a row because I searched for common Ethereum vulnerabilites, saw a suspicious warning in Remix and Linus saw a lot of interesting transactions on the network from other teams which gave us an idea about the proper solution…
Idea: Design My Own Contract To Trigger Reentrancy
When I compiled the Entrance contract in Remix I got a couple of warnings. One of them was:
Potential Violation of Checks-Effects-Interaction pattern in Entrance.gamble(): Could potentially lead to re-entrancy vulnerability.
Reentrancy is a new thing to me. From Wikipedia:
In computing, a computer program or subroutine is called reentrant if it can be interrupted in the middle of its execution and then safely be called again (“re-entered”) before its previous invocations complete execution. The interruption could be caused by an internal action such as a jump or call, or by an external action such as an interrupt or signal. Once the reentered invocation completes, the previous invocations will resume correct execution.
Both the challenge name Entrance and the hint Can you enter? Again? point in this direction. I found the great article Reentrancy Attack On Smart Contracts: How To Identify The Exploitable And An Example Of An Attack Contract by Gustavo (Gus) Guimaraes. One could guess that the challenge author based the challenge on exactly that article…
So now my goal shifted to write my own contract to exploit the vulnerability, which lies in that whoever calls Entrance.gamble()
will get called using the call()
function after the balance is increased and before setting has_played
to true, and is therefore allowed to run gamble()
again, recursively until it has enough balance to run getFlag()
.
|
|
I call my exploit contract ReEntrance. In the constructor I save the address to the Entrance contract (well I could have hard coded it) and the strings server
and port
where we want the flag to be sent:
|
|
From Gus’ article we see that the secret to the exploit is to add a so called fallback function to our exploit contract. It’s an unnamed function which will be called when the Entrance contract runs call()
on the sender, which will be our ReEntrance contract:
|
|
So let’s add an unnamed function which calls gamble()
again to trigger the recursion:
function () public payable {
entrance.gamble();
if (entrance.balanceOf(this) > 300) {
entrance.getFlag(server, port);
}
}
We also need a way to start the chain so I add this function:
function enter () public {
entrance.enter(12341111);
}
Remix is running in my browser (Firefox) and currently I can’t interact with any Ethereum network. In order to do so I follow Moses Sam Paul’s guide Deploy Smart Contracts on Ropsten Testnet through Ethereum Remix which tells me to install the Firefox add-on Metamask. It allows me to interact with the network without running a full Ethereum node, and I choose to import my existing account/address from MyEtherWallet where I have ~1 ETH. In Remix I set the run environment to Injected Web 3 and now I can make transactions in Remix and sign them in the Metamask add-on. Nifty!
An instance of my first version on ReEntrance was created on the network, but I had to correct it since I forgot to gamble()
after running enter()
(doh!).
Correction:
function enter () public {
entrance.enter(12341111);
entrance.gamble();
}
Running Out of Gas And Problems Changing The Limit
I deploy an instance of my second version and make a couple of enter()
transactions until the block number modulo seven becomes zero. I got “lucky” in TX 0xf46f4f481481d0b7b260…, but I ran out of gas so I thought I need to increase the gas limit of the transaction in Remix. The default value is 3 million so I tried to increase it to 30 million but I ran out of gas again in TX 0x4200f6285a598d63b88d…. I’ve seen other teams’ transactions with much lower gas limit so something must be wrong!
It turns out I should have inspected my transactions a bit more. Whatever I set the gas limit to in the Run tab in Remix, the transaction will have some kind of estimated gas limit, in my case 47920. Seems like I hit the remix-ide
issue #1352: Gas limit is ignored.
I thought it was fixed on master. I neither looked at which master nor saw that the associated remix
pull request #1092 Use the provided gas limit, not the estimated one wasn’t closed yet, so I went on building Remix from source using npm
and the official installation instructions to no avail. Didn’t work of course. TX 0x2f1695fc1989adb494d0… got a gas limit of 720212 even though I set 3000000 in the Remix GUI.
Now I got the idea that since the ReEntrance contract was already instantiated on the network using Remix, could I make an enter()
transaction using MyEtherWallet instead? Maybe there I could set the gas limit properly? I decided to try. I created some transactions with gas limit of one million until I got a block number divisible with seven in TX 0xe5d4083afafa10dca305…. Out of gas again, and running balanceOf()
returned just 110.
Now I thought that the operations of checking the balance and see whether it was time to call getFlag()
costed too much gas to I decided to remove both the check and the call. I figured I can just gamble()
until the transaction runs out of gas and then call getFlag()
manually. That must be done with the same sender though, so I added a function to ReEntrance for doing so, see highlighted lines below. Now the ReEntrance.sol
source code looks like this:
|
|
Compiling this version gave me this ABI. A new instance was created at address 0x67ed0fee42a4131b0af0d44e00a2cb7357b7b943.
I created new enter()
transactions with gas limit one million until I got lucky in TX 0xad3c7fca72744e9a488b…. Out of gas as expected, but only 110 in balance again.
I increased the gas limit to five million and got lucky in TX 0x3e2ba4ec658a2d5dd5c9…, and now the balance was 1100!
Time To Get The Flag!
In the constructor I had supplied server 185.35.202.202
and port 9056
(one of Hackeriet‘s IPv4 addresses – thanks to Alexander Kjäll for running Netcat in a screen for me). Now it was time to getFlag()
! That was done in TX 0x9cd4fc3d942ef82c1ea1…. Quite soon I could see an anticipated entry in the event log:
Address 0x1898ed72826befa2d549004c57f048a95ae0b982
Topics [0] 0x31f9f688587d79c168d76bc74a671922d95848f11342b5896712138d1fb57554
Data 0000000000000000000000000000000000000000000000000000000000000040
0000000000000000000000000000000000000000000000000000000000000080
000000000000000000000000000000000000000000000000000000000000000e
3138352e33352e3230322e323032000000000000000000000000000000000000
0000000000000000000000000000000000000000000000000000000000000004
3939353600000000000000000000000000000000000000000000000000000000
I went for lunch and sent a message to Alexander asking if he got a flag, and 13 minutes later I got:
Ja :D
Finally solved the "Entrance" Ethereum challenge for @xil_hackerspace in the #junior35c3ctf after a whole day of reading up and testing. Am I a cyber blockchain expert now? :)https://t.co/lb0SHr3y0L
— Laban Sköllermark @LabanSkoller@infosec.exchange (@LabanSkoller) December 29, 2018
We have fallen to the 4th place in the CTF though...
The flag was
35c3_reeeeeeeeeeeeeeeeeeeeee
and the CTF organizers seemed to send the flag every minute or so.
Summary
The following Ethereum contract is vulnerable to a reentrancy attack since the sender is called before it’s recorded that the sender has played and can’t play anymore and after the balance is increased:
|
|
One can exploit the contract by writing another contract with an unnamed fallback function which calls gamble()
again recuresively until the Ethereum transaction is out of gas. With enough gas the balance will reach at least 300 and getFlag()
can be called with a server and port where the flag will be delivered.
I wrote the following contract:
|
|
I created an instance and called enter()
until the Ethereum block number was divisible by seven, then checked the balance and called for the flag:
ReEntrance(0x1898Ed72826BEfa2D549004C57F048A95ae0B982, "185.35.202.202", "9956")
-> 0x67ed0fee42a4131b0af0d44e00a2cb7357b7b943
enter()
balanceOf(0x67ed0fee42a4131b0af0d44e00a2cb7357b7b943)
-> 1100
getFlag()
The flag got delivered to the wanted server:port
:
35c3_reeeeeeeeeeeeeeeeeeeeee
Comments?
Do you have comments? Interact with this tweet:
Write-up for the #35C3 Junior CTF Ethereum challenge "Entrance":https://t.co/dgHH9lyUxe#junior35c3ctf pic.twitter.com/KkCbvZa3Vh
— Laban Sköllermark @LabanSkoller@infosec.exchange (@LabanSkoller) January 7, 2019
More 35C3 Junior CTF Write-Ups
The Norwegian hackerspace Hackeriet had a (tiny) team in the 35C3 Junior CTF. Please see the following write-ups by Alexander Kjäll a.k.a. capitol:
- Solution to 35C3 Junior CTF challenge pretty linear
- Solution to 35C3 Junior CTF challenge DANCEd
- Solution to 35C3 Junior CTF challenge Decrypted
- Solution to 35C3 Junior CTF challenge flags