您正在查看: Surou 发布的文章

昨日EOS RAM偷窃漏洞复盘

昨日EOS紧急更新了一个偷窃RAM漏洞的补丁,今天和远航讨论到该漏洞,远航提供了昨日官网修复该漏洞的patch,于是又有了想复现漏洞的冲动,最后验证并在测试网络还原了该漏洞。

漏洞详情
该漏洞是因为EOSIO系统对于合约inline调用合约内的其他函数不会进行权限检测,从而恶意合约可以使用任何其他账号的权限调用该合约的其他方法。比如下图:

漏洞复现操作如下

漏洞解决
合约内inline调用也需要eosio.code授权

漏洞复现源码
https://github.com/itleaks/eos-contract/tree/master/stealram2-exp

附录
再次感谢远航提供资料

转载自:https://mp.weixin.qq.com/s/gYEuGB2_fZf8OHlQqGBJ8A

EOS 查询当前网络的节点出块顺序

麒麟测试网查询演示

cleos -u http://api.kylin.eosbeijing.one:8880 get schedule -j

返回

{
  "active": {
    "version": 205,
    "producers": [{
        "producer_name": "acryptotitan",
        "block_signing_key": "EOS5DjZn3myq4mhJxppyaryk6UYhM2hbYrPWT5rthK5WZwtHMZuy1"
      },{
        "producer_name": "alohaeostest",
        "block_signing_key": "EOS78GmL1BFNGR2r5ME16onmSRi1ZtkaJ5CWRxrj4h5fDgZx5yJ8v"
      },{
        "producer_name": "blockmatrix2",
        "block_signing_key": "EOS5fotKCkwqjZSki6vs85AQju9MbwRu13ruZDqe28jpbDs4ZZsrf"
      },{
        "producer_name": "eosargentina",
        "block_signing_key": "EOS5CZar1N2ip7LQTZ5dyCZDvr84dzEJn5yqmR7GSbEoS5Ci9BTH3"
      },{
        "producer_name": "eosasia11111",
        "block_signing_key": "EOS8LpSDbAPACxHJoxJsbWdZ7pvEeZpZ9qZfKiEiC6KuF6btUiwgZ"
      },{
        "producer_name": "eosbeijingbp",
        "block_signing_key": "EOS6r6PuKi5WHH7TnY6AtDMyVdfcXyPPCz23MEBcgaMv2DcuXS5Eh"
      },{
        "producer_name": "eosbixincool",
        "block_signing_key": "EOS59TifUUjWM5UzajhYAA1S87c32pNKyrNnZMfBay564FMdgX1Pu"
      },{
        "producer_name": "eoscanadacom",
        "block_signing_key": "EOS8UkwZgsb43ntVYWFkpKtB6kDPkdLWVuEYSmKkBRcwSZYA9CBbN"
      },{
        "producer_name": "eosecoeoseco",
        "block_signing_key": "EOS6BBBTt5yMwBsmnFLtRVh9aQzqnt4aztr82Cu79DnJBdnXcy3RA"
      },{
        "producer_name": "eoshenzhensa",
        "block_signing_key": "EOS7sFWT7XyywiDH9QUrLAcfBNz9sVL5LdVAVUxmDdXJUoXZm9NVs"
      },{
        "producer_name": "eosiomeetone",
        "block_signing_key": "EOS5iW6gVzrHzk3KmpQoeE1ErJfvYvKvB5jYmjDNPZHsxtM8ttm4L"
      },{
        "producer_name": "eosiosg11111",
        "block_signing_key": "EOS6c9FmXf1G9nApRBYq73yjS2v1QPzTKC8d5vhkPJEN3U8rJazAh"
      },{
        "producer_name": "eoslaomaocom",
        "block_signing_key": "EOS8D9EjwHnbdnwzM5bRAfFsGUigGKsfgfAAcBqf1rb1QbYC2HZms"
      },{
        "producer_name": "eospaceioeos",
        "block_signing_key": "EOS6yHKg1ve1i4AguSYUNNrzBoBxkjyxrhg8pyZqmFWeFXkf4JX4p"
      },{
        "producer_name": "eospacific11",
        "block_signing_key": "EOS6iPvbhEc881Dz9fYKqU78F3awvUyrA5WSmRw186bj4cNyMpZ5z"
      },{
        "producer_name": "eosriobrazil",
        "block_signing_key": "EOS7RioGoHQnhv2fJEiciP9Q7J8JgfJYFcyofVkmCqMop8Q1PzgqP"
      },{
        "producer_name": "eosstorebest",
        "block_signing_key": "EOS5gGxvNkBHbp3EuBBxTLRBoXiJPY6HCKFujtEJtco7GBFYpZ8kM"
      },{
        "producer_name": "eosswedenorg",
        "block_signing_key": "EOS6FJ5PawjQnS61jAEmAevAvS1gNNpbQV7SuE5h8T1aTN3DmaWbw"
      },{
        "producer_name": "gravitypooll",
        "block_signing_key": "EOS7GN7cac2MFkbEhTn25AgV3ZHniBHJwASdwq5DG5X56NMGHUg71"
      },{
        "producer_name": "helloeoschbp",
        "block_signing_key": "EOS6Zvy5nitCsMfnuQYaTQVDQRf4J5EGgCKyRgmhiZoptDLU2QiWX"
      },{
        "producer_name": "superoneiobp",
        "block_signing_key": "EOS7JdJSoAb5N13wG7muMvTiawp3swKuQ3ccHpF6KaCpwiwqBPn3d"
      }
    ]
  },
  "pending": null,
  "proposed": null
}

备注

现在的出块顺序是按ascii排序的,理想的是支持heartbeat,按地理位置顺序出块,可减少延迟不稳定等问题

EOS合约中使用capi_checksum256做为table的primary_key

定义table

struct [[eosio::table("sellheros"), eosio::contract("xxx.game")]] sellhero{
        capi_checksum256 tx_hash;
        ....

        auto primary_key() const { return *(uint64_t*)&tx_hash; }
    };

创建table

typedef eosio::multi_index<"sellheros"_n, sellhero> sellhero_tables;

查询

taskhash_tables taskhash_table(_self, account);
auto itr_task = taskhash_table.find(*(uint64_t*)&client_random_hash);
eosio_assert(itr_task != taskhash_table.end(), "random hash is not exist" );

hash 生成

capi_checksum256 bcskill_contract::tx_hash(){
    size_t tx_size = transaction_size();
    char buff[tx_size];
    size_t read = read_transaction(buff, tx_size);
    capi_checksum256 h;
    sha256(buff, read, &h);
    return h;
}

添加数据

capi_checksum256 hash = tx_hash();
    sellhero_tables sellhero_table(_self, _self.value);
    sellhero_table.emplace( _self, [&]( auto& h ) {
        h.tx_hash = hash;
       ...
    });

新增索引方法

using eosio::fixed_bytes;
#define SHA_TO_HASH_FUNC static fixed_bytes<32> checksum256_to_sha256(const capi_checksum256 &hash) \
        { \
            const uint64_t *p64 = reinterpret_cast<const uint64_t *>(&hash); \
            return fixed_bytes<32>::make_from_word_sequence<uint64_t>(p64[0], p64[1], p64[2], p64[3]); \
        }
#define SHA_TO_HASH(hash) checksum256_to_sha256(hash)
struct [[eosio::table, eosio::contract("bcskillsurou")]] tokeninfotb {
        uint64_t id;                                   // id
        capi_checksum256 transaction_id;               // 交易id

        uint64_t primary_key() const { return id; }
        fixed_bytes<32> second_key() const { return SHA_TO_HASH(transaction_id); }
        SHA_TO_HASH_FUNC
        EOSLIB_SERIALIZE(tokeninfotb, (id)(transaction_id))
    };
typedef eosio::multi_index<"tokeninfotb"_n, tokeninfotb, eosio::indexed_by<"bysubkey"_n, eosio::const_mem_fun<tokeninfotb, fixed_bytes<32>, &tokeninfotb::second_key>>> tokeninfo_table;

参考

https://eosio.stackexchange.com/questions/3219/how-to-properly-use-cleos-get-table-with-key-type-sha256-secondary-index-fa

EOS合约内 string 转换为 capi_checksum256, capi_signature, capi_public_key

源代码

#include <eosiolib/crypto.h>
#include <eosiolib/asset.hpp>
#include <eosiolib/eosio.hpp>
#include <eosiolib/singleton.hpp>
#include <eosiolib/time.hpp>
#include <eosiolib/types.h>

using namespace eosio;
using namespace std;

string uint64_string(uint64_t input) {
    string result;
    uint8_t base = 10;
    do {
        char c = input % base;
        input /= base;
        if (c < 10)
            c += '0';
        else
            c += 'A' - 10;
        result = c + result;
    } while (input);
    return result;
}

uint8_t from_hex(char c) {
    if (c >= '0' && c <= '9') return c - '0';
    if (c >= 'a' && c <= 'f') return c - 'a' + 10;
    if (c >= 'A' && c <= 'F') return c - 'A' + 10;
    eosio_assert(false, "Invalid hex character");
    return 0;
}

size_t from_hex(const string& hex_str, char* out_data, size_t out_data_len) {
    auto i = hex_str.begin();
    uint8_t* out_pos = (uint8_t*)out_data;
    uint8_t* out_end = out_pos + out_data_len;
    while (i != hex_str.end() && out_end != out_pos) {
        *out_pos = from_hex((char)(*i)) << 4;
        ++i;
        if (i != hex_str.end()) {
            *out_pos |= from_hex((char)(*i));
            ++i;
        }
        ++out_pos;
    }
    return out_pos - (uint8_t*)out_data;
}

string to_hex(const char* d, uint32_t s) {
    std::string r;
    const char* to_hex = "0123456789abcdef";
    uint8_t* c = (uint8_t*)d;
    for (uint32_t i = 0; i < s; ++i)
        (r += to_hex[(c[i] >> 4)]) += to_hex[(c[i] & 0x0f)];
    return r;
}

string sha256_to_hex(const capi_checksum256& sha256) {
    return to_hex((char*)sha256.hash, sizeof(sha256.hash));
}

string sha1_to_hex(const capi_checksum160& sha1) {
    return to_hex((char*)sha1.hash, sizeof(sha1.hash));
}

// copied from boost https://www.boost.org/
template <class T>
inline void hash_combine(std::size_t& seed, const T& v) {
    std::hash<T> hasher;
    seed ^= hasher(v) + 0x9e3779b9 + (seed << 6) + (seed >> 2);
}

uint64_t uint64_hash(const string& hash) {
    return std::hash<string>{}(hash);
}

uint64_t uint64_hash(const capi_checksum256& hash) {
    return uint64_hash(sha256_to_hex(hash));
}

capi_checksum256 hex_to_sha256(const string& hex_str) {
    eosio_assert(hex_str.length() == 64, "invalid sha256");
    capi_checksum256 checksum;
    from_hex(hex_str, (char*)checksum.hash, sizeof(checksum.hash));
    return checksum;
}

capi_checksum160 hex_to_sha1(const string& hex_str) {
    eosio_assert(hex_str.length() == 40, "invalid sha1");
    capi_checksum160 checksum;
    from_hex(hex_str, (char*)checksum.hash, sizeof(checksum.hash));
    return checksum;
}

size_t sub2sep(const string& input,
               string* output,
               const char& separator,
               const size_t& first_pos = 0,
               const bool& required = false) {
    eosio_assert(first_pos != string::npos, "invalid first pos");
    auto pos = input.find(separator, first_pos);
    if (pos == string::npos) {
        eosio_assert(!required, "parse memo error");
        return string::npos;
    }
    *output = input.substr(first_pos, pos - first_pos);
    return pos;
}

// Copied from https://github.com/bitcoin/bitcoin

/** All alphanumeric characters except for "0", "I", "O", and "l" */
static const char* pszBase58 =
    "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz";
static const int8_t mapBase58[256] = {
    -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
    -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
    -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 0,  1,  2,  3,  4,  5,  6,  7,
    8,  -1, -1, -1, -1, -1, -1, -1, 9,  10, 11, 12, 13, 14, 15, 16, -1, 17, 18,
    19, 20, 21, -1, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, -1, -1, -1, -1,
    -1, -1, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, -1, 44, 45, 46, 47, 48,
    49, 50, 51, 52, 53, 54, 55, 56, 57, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
    -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
    -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
    -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
    -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
    -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
    -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
    -1, -1, -1, -1, -1, -1, -1, -1, -1,
};

bool DecodeBase58(const char* psz, std::vector<unsigned char>& vch) {
    // Skip leading spaces.
    while (*psz && isspace(*psz)) psz++;
    // Skip and count leading '1's.
    int zeroes = 0;
    int length = 0;
    while (*psz == '1') {
        zeroes++;
        psz++;
    }
    // Allocate enough space in big-endian base256 representation.
    int size = strlen(psz) * 733 / 1000 + 1;  // log(58) / log(256), rounded up.
    std::vector<unsigned char> b256(size);
    // Process the characters.
    static_assert(
        sizeof(mapBase58) / sizeof(mapBase58[0]) == 256,
        "mapBase58.size() should be 256");  // guarantee not out of range
    while (*psz && !isspace(*psz)) {
        // Decode base58 character
        int carry = mapBase58[(uint8_t)*psz];
        if (carry == -1)  // Invalid b58 character
            return false;
        int i = 0;
        for (std::vector<unsigned char>::reverse_iterator it = b256.rbegin();
             (carry != 0 || i < length) && (it != b256.rend());
             ++it, ++i) {
            carry += 58 * (*it);
            *it = carry % 256;
            carry /= 256;
        }
        assert(carry == 0);
        length = i;
        psz++;
    }
    // Skip trailing spaces.
    while (isspace(*psz)) psz++;
    if (*psz != 0) return false;
    // Skip leading zeroes in b256.
    std::vector<unsigned char>::iterator it = b256.begin() + (size - length);
    while (it != b256.end() && *it == 0) it++;
    // Copy result into output vector.
    vch.reserve(zeroes + (b256.end() - it));
    vch.assign(zeroes, 0x00);
    while (it != b256.end()) vch.push_back(*(it++));
    return true;
}

bool decode_base58(const string& str, vector<unsigned char>& vch) {
    return DecodeBase58(str.c_str(), vch);
}

// Copied from https://github.com/bitcoin/bitcoin

capi_signature str_to_sig(const string& sig, const bool& checksumming = true) {
    const auto pivot = sig.find('_');
    eosio_assert(pivot != string::npos, "No delimiter in signature");
    const auto prefix_str = sig.substr(0, pivot);
    eosio_assert(prefix_str == "SIG", "Signature Key has invalid prefix");
    const auto next_pivot = sig.find('_', pivot + 1);
    eosio_assert(next_pivot != string::npos, "No curve in signature");
    const auto curve = sig.substr(pivot + 1, next_pivot - pivot - 1);
    eosio_assert(curve == "K1" || curve == "R1", "Incorrect curve");
    const bool k1 = curve == "K1";
    auto data_str = sig.substr(next_pivot + 1);
    eosio_assert(!data_str.empty(), "Signature has no data");
    vector<unsigned char> vch;

    eosio_assert(decode_base58(data_str, vch), "Decode signature failed");

    eosio_assert(vch.size() == 69, "Invalid signature");

    if (checksumming) {
        array<unsigned char, 67> check_data;
        copy_n(vch.begin(), 65, check_data.begin());
        check_data[65] = k1 ? 'K' : 'R';
        check_data[66] = '1';

        capi_checksum160 check_sig;
        ripemd160(reinterpret_cast<char*>(check_data.data()), 67, &check_sig);

        eosio_assert(memcmp(&check_sig.hash, &vch.end()[-4], 4) == 0, "Signature checksum mismatch");
    }

    capi_signature _sig;
    unsigned int type = k1 ? 0 : 1;
    _sig.data[0] = (uint8_t)type;
    for (int i = 1; i < sizeof(_sig.data); i++) {
        _sig.data[i] = vch[i - 1];
    }
    return _sig;
}

capi_public_key str_to_pub(const string& pubkey, const bool& checksumming = true) {
    string pubkey_prefix("EOS");
    auto base58substr = pubkey.substr(pubkey_prefix.length());
    vector<unsigned char> vch;
    eosio_assert(decode_base58(base58substr, vch), "Decode public key failed");
    eosio_assert(vch.size() == 37, "Invalid public key");
    if (checksumming) {

        array<unsigned char, 33> pubkey_data;
        copy_n(vch.begin(), 33, pubkey_data.begin());

        capi_checksum160 check_pubkey;
        ripemd160(reinterpret_cast<char*>(pubkey_data.data()), 33, &check_pubkey);

        eosio_assert(memcmp(&check_pubkey, &vch.end()[-4], 4) == 0, "Public key checksum mismatch");
    }
    capi_public_key _pub_key;
    unsigned int type = 0;
    _pub_key.data[0] = (char)type;
    for (int i = 1; i < sizeof(_pub_key.data); i++) {
        _pub_key.data[i] = vch[i - 1];
    }
    return _pub_key;
}

测试

ACTION test()
{
        capi_checksum256 digest = hex_to_sha256("9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08");
        capi_signature sig =  str_to_sig("SIG_K1_KkSbKuDSV7x87FeexJ3goinHsd3MhPBCH91MRqhyS3Z7H1v4HtUZoJc6AkgYWW5mEan7UbdmDAzDpCzUwheDPxRxtzuD8s");
        capi_public_key pk = str_to_pub("EOS62M5kVouCEU31xP736Txb4pe82FoncprqevPuagE6boCLxwsC8");

        assert_recover_key(&digest, (const char *)&sig, sizeof(sig), (const char *)&pk, sizeof(pk));
        print("VALID");
}

参考

eosio.cdt 中使用二级索引查询的例子

合约table 定义

struct [[eosio::table("members"), eosio::contract("shadow.zion")]] member_table{
        uint64_t id;
        capi_name username;
        uint64_t group_id;

        uint64_t primary_key() const { return id; }
        uint64_t get_sub_key() const { return username; }
        uint64_t get_third_key() const { return group_id; }
    };

table 创建

typedef eosio::multi_index<"members"_n, member_table, eosio::indexed_by<"bysubkey"_n, eosio::const_mem_fun<member_table, uint64_t, &member_table::get_sub_key>>, eosio::indexed_by<"bythirdkey"_n, eosio::const_mem_fun<member_table, uint64_t, &member_table::get_third_key>>> member_tables;

table 使用

member_tables member_table( _self, _self.value);
    auto third_index = member_table.get_index<"bythirdkey"_n>();
    auto member_itr = third_index.find( group_id );
    eosio_assert( member_itr != third_index.end(), "121" );

    while(member_itr != third_index.end()){
        if(member_itr->username == account && member_itr->group_id == group_id){
            break;
        }
        member_itr++;
    }

//增,改

   if(member_itr == third_index.end()){
            member_table.emplace( _self, [&]( auto& m ) {
                m.id = member_table.available_primary_key();
                m.username = other_account;
                m.group_id = group_id;
                m.group_weight = group_weight;
            });
        }
        else{
            third_index.modify( member_itr, _self, [&]( auto& m ) {
                m.group_weight = member_itr->group_weight +  group_weight;
            });
        }