LOADING...
LOADING...
LOADING...
当前位置: 玩币族首页 > 区块链资产 > 零时科技|CNVD-2020-30018比特币存在逻辑缺陷漏洞

零时科技|CNVD-2020-30018比特币存在逻辑缺陷漏洞

2020-06-02 深圳零时科技 来源:区块链网络

2020-05-06,国家信息安全漏洞共享平台发布编号为CNVD-2020-30018,比特币存在逻辑缺陷漏洞:https://www.cnvd.org.cn/flaw/show/CNVD-2020-30018。比特币在某一函数实现过程中存在代码逻辑缺陷漏洞。攻击者可以利用该漏洞消费他人账户的金额。

经分析,为原CVE-2010-5141漏洞,对此漏洞进行详细分析。

漏洞分析

CVE-2010-5141又被叫做比特币任意盗币漏洞。bitcon v0.3.3也存在此漏洞。

首先依然是先看script.cpp,在第1114-1134行的VerifySignature函数:

bool?VerifySignature(const?CTransaction&?txFrom,?const?CTransaction&?txTo,?unsigned?int?nIn,?int?nHashType) { ????assert(nIn?<?txTo.vin.size()); ????const?CTxIn&?txin?=?txTo.vin[nIn]; ????if?(txin.prevout.n?>=?txFrom.vout.size()) ????????return?false; ????const?CTxOut&?txout?=?txFrom.vout[txin.prevout.n];

if?(txin.prevout.hash?!=?txFrom.GetHash()) ????????return?false;

if?(!EvalScript(txin.scriptSig?+?CScript(OP_CODESEPARATOR)?+?txout.scriptPubKey,?txTo,?nIn,?nHashType)) ????????return?false;

//?Anytime?a?signature?is?successfully?verified,?it's?proof?the?outpoint?is?spent, ????//?so?lets?update?the?wallet?spent?flag?if?it?doesn't?know?due?to?wallet.dat?being ????//?restored?from?backup?or?the?user?making?copies?of?wallet.dat. ????WalletUpdateSpent(txin.prevout);

return?true; }

VerifySignature函数在执行每笔交易时都会被调用,VerifySignature在执行时会调用EvalScript函数和CScript函数来进行签名验证。

VerifySignature函数的参数有txFrom即上一笔交易、txTo即正在进行的这笔交易等。

这里先看1125行,这个判断语句来判断EvalScript函数的返回值。如果EvalScript返回False则VerifySignature返回False并退出。

对EvalScript函数,第一个参数是txin.scriptSig(包含签名信息) +CScript(OP_CODESEPARATOR)(分隔操作码)+ txout.scriptPunKey(包含公钥信息、OP_CHECKSIG指令),这里我们可以分析出只要EvalScript函数返回值不为False,VerifySignature函数返回True那么这笔交易的签名就成功通过了验证。

接下来我们看EvalScript函数,由于EvalScript函数共有762行,这里就不全部展示,我们来看最后的返回返回值是如何确定的:

if?(pvStackRet) ????????*pvStackRet?=?stack; ????return?(stack.empty()???false?:?CastToBool(stack.back()));

根据return语句中的三目运算符,如果栈为空则返回false,若栈不为空则进入第21行CastToBool函数:

bool?CastToBool(const?valtype&?vch) { ????return?(CBigNum(vch)?!=?bnZero); }

继续看return语句,这是一个布尔类型的函数,即只要栈顶元素!= bnZero,也就是栈顶不为零就会返回一个True。

到这里我们可以得出让EvalScript函数返回True的方法:

栈不为空

栈顶不为0

所以,如何来控制栈内存放的数据呢?这里来看一下OP_CHECKSIG操作码的执行过程:

case?OP_CHECKSIG: ????????????case?OP_CHECKSIGVERIFY: ????????????{ ????????????????//?(sig?pubkey?--?bool) ????????????????if?(stack.size()?<?2) ????????????????????return?false;

valtype&?vchSig????=?stacktop(-2); ????????????????valtype&?vchPubKey?=?stacktop(-1);

//////?debug?print ????????????????//PrintHex(vchSig.begin(),?vchSig.end(),?"sig:?%s\n"); ????????????????//PrintHex(vchPubKey.begin(),?vchPubKey.end(),?"pubkey:?%s\n");

//?Subset?of?script?starting?at?the?most?recent?codeseparator ????????????????CScript?scriptCode(pbegincodehash,?pend);

//?Drop?the?signature,?since?there's?no?way?for?a?signature?to?sign?itself ????????????????scriptCode.FindAndDelete(CScript(vchSig));

bool?fSuccess?=?CheckSig(vchSig,?vchPubKey,?scriptCode,?txTo,?nIn,?nHashType);

stack.pop_back(); ????????????????stack.pop_back(); ????????????????stack.push_back(fSuccess???vchTrue?:?vchFalse); ????????????????if?(opcode?==?OP_CHECKSIGVERIFY) ????????????????{ ????????????????????if?(fSuccess) ????????????????????????stack.pop_back(); ????????????????????else ????????????????????????pc?=?pend; ????????????????} ????????????}

第712行,CheckSig函数会对签名进行验证,如果验证失败fSuccess = false,则在第716行的三目运算符就会把一个vchFalse即0压入栈,这时虽然栈不为空,但是栈顶元素为0,CastToBool函数依然会返回false。

看起来好像这条路走不通,我们看看传入EvalScript函数主要的三个参数:

txin.scriptSig ::可控,签名信息

CScript(OP_CODESEPARATOR) :分割操作码

txout.scriptPubKey : 上一个交易的密钥,不可控。

看到这里。我们发现能够控制的参数就是这个txin.scriptSig,那如何来构造他达到我们的目的呢?跟进EvalScript函数来看看他是怎么执行的:

bool?EvalScript(const?CScript&?script,?const?CTransaction&?txTo,?unsigned?int?nIn,?int?nHashType, ????????????????vector<vector<unsigned?char>?>*?pvStackRet) { ????CAutoBN_CTX?pctx; ????CScript::const_iterator?pc?=?script.begin(); ????CScript::const_iterator?pend?=?script.end(); ????CScript::const_iterator?pbegincodehash?=?script.begin(); ????vector<bool>?vfExec; ????vector<valtype>?stack; ????vector<valtype>?altstack; ????if?(pvStackRet) ????????pvStackRet->clear();

while?(pc?<?pend) ????{ ????????bool?fExec?=?!count(vfExec.begin(),?vfExec.end(),?false);

// ????????//?Read?instruction ????????// ????????opcodetype?opcode; ????????valtype?vchPushValue; ????????if?(!script.GetOp(pc,?opcode,?vchPushValue)) ????????????return?false;

if?(fExec?&&?opcode?<=?OP_PUSHDATA4) ????????????stack.push_back(vchPushValue);

根据EvalScript的函数定义可以发现txin.scriptSig是作为script执行的,在第24行使用它的GetOp方法来判断,如果GetOp返回值为True,且opcode <= OP_PUSHDATA4,就会把vchPushValue压入栈中。这里看看GetOp方法是如何定义的,GetOp方法位于script.h,482行:

bool?GetOp(const_iterator&?pc,?opcodetype&?opcodeRet,?vector<unsigned?char>&?vchRet)?const ????{ ????????opcodeRet?=?OP_INVALIDOPCODE; ????????vchRet.clear(); ????????if?(pc?>=?end()) ????????????return?false;

//?Read?instruction ????????unsigned?int?opcode?=?*pc++; ????????if?(opcode?>=?OP_SINGLEBYTE_END) ????????{ ????????????if?(pc?+?1?>?end()) ????????????????return?false; ????????????opcode?<<=?8; ????????????opcode?|=?*pc++; ????????}

//?Immediate?operand ????????if?(opcode?<=?OP_PUSHDATA4) ????????{ ????????????unsigned?int?nSize?=?opcode; ????????????if?(opcode?==?OP_PUSHDATA1) ????????????{ ????????????????if?(pc?+?1?>?end()) ????????????????????return?false; ????????????????nSize?=?*pc++; ????????????} ????????????else?if?(opcode?==?OP_PUSHDATA2) ????????????{ ????????????????if?(pc?+?2?>?end()) ????????????????????return?false; ????????????????nSize?=?0; ????????????????memcpy(&nSize,?&pc[0],?2); ????????????????pc?+=?2; ????????????} ????????????else?if?(opcode?==?OP_PUSHDATA4) ????????????{ ????????????????if?(pc?+?4?>?end()) ????????????????????return?false; ????????????????memcpy(&nSize,?&pc[0],?4); ????????????????pc?+=?4; ????????????} ????????????if?(pc?+?nSize?>?end()) ????????????????return?false; ????????????vchRet.assign(pc,?pc?+?nSize); ????????????pc?+=?nSize; ????????}

opcodeRet?=?(opcodetype)opcode; ????????return?true; ????}

根据比特币wiki,https://en.bitcoin.it/wiki/Script。的约定,OP_PUSHDATA4的操作码值为78,即第21行声明的nSize变量的值为78。

WordOpcodeHexInputOutputDescriptionOP_PUSHDATA1760x4c(special)dataThe next byte contains the number of bytes to be pushed onto the stack.OP_PUSHDATA2770x4d(special)datahe next two bytes contain the number of bytes to be pushed onto the stack.OP_PUSHDATA4780x4e(special)dataThe next four bytes contain the number of bytes to be pushed onto the stack.

按照比特币wiki对OP_PUSHDATA4的描述,接下来的四个字节包含要压入堆栈的字节数。读起来比较拗口,我们看第36行,如果opcode == OP_PUSHDATA4,我们便把nSize存到以pc[0]开始,4字节大小的内存空间中,并把pc指针向右移4位。再看第45行,将pc 到 pc + nSize指向的数据压入栈中。也就是说, 接下来四个字节包含的数字,是要压入栈中的字节数。

所以我们只要在txin.scriptSig中注入一个OP_PUSHDATA4操作码,后面txout.scriptPunKey包含的公钥信息以及OP_CHECKSIG指令都会被压入栈中,遍历完指针时,最后进行判断:

栈是否为空?不为空

栈顶元素是否为0?不为0

于是EvalScript函数因为满足条件返回true,继而VerifySignature函数也返回true,签名验证被绕过了,就可以达到任意盗币的效果。

漏洞修复

在bitcoin 0.3.7,script.cpp中的1163行,修改了本来的EvalScript函数为VerifyScript函数:

bool?VerifySignature(const?CTransaction&?txFrom,?const?CTransaction&?txTo,?unsigned?int?nIn,?int?nHashType) { ????assert(nIn?<?txTo.vin.size()); ????const?CTxIn&?txin?=?txTo.vin[nIn]; ????if?(txin.prevout.n?>=?txFrom.vout.size()) ????????return?false; ????const?CTxOut&?txout?=?txFrom.vout[txin.prevout.n];

if?(txin.prevout.hash?!=?txFrom.GetHash()) ????????return?false;

if?(!VerifyScript(txin.scriptSig,?txout.scriptPubKey,?txTo,?nIn,?nHashType)) ????????return?false;

//?Anytime?a?signature?is?successfully?verified,?it's?proof?the?outpoint?is?spent, ????//?so?lets?update?the?wallet?spent?flag?if?it?doesn't?know?due?to?wallet.dat?being ????//?restored?from?backup?or?the?user?making?copies?of?wallet.dat. ????WalletUpdateSpent(txin.prevout);

return?true; }

在1114行,增加了一个叫做VerifyScript的函数:

bool?VerifyScript(const?CScript&?scriptSig,?const?CScript&?scriptPubKey,?const?CTransaction&?txTo,?unsigned?int?nIn,?int?nHashType) { ????vector<vector<unsigned?char>?>?stack; ????if?(!EvalScript(stack,?scriptSig,?txTo,?nIn,?nHashType)) ????????return?false; ????if?(!EvalScript(stack,?scriptPubKey,?txTo,?nIn,?nHashType)) ????????return?false; ????if?(stack.empty()) ????????return?false; ????return?CastToBool(stack.back()); }

这里将scriptSig和scriptPubKey分别调用EvalScript进行验证,来防止注入操作码到scriptSig绕过后面的scriptPubKey验证。

—-

编译者/作者:深圳零时科技

玩币族申明:玩币族作为开放的资讯翻译/分享平台,所提供的所有资讯仅代表作者个人观点,与玩币族平台立场无关,且不构成任何投资理财建议。文章版权归原作者所有。

LOADING...
LOADING...