通过逆向和调试深入EVM 7 - 与其他智能合约的交互
这是通过逆向和调试深入EVM最后一篇,我们将讨论与其他智能合约的交互。EVM是如何处理这个问题的?让我们拭目以待!
这里是我们的(最后一个)测试智能合约:
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^ 0.8 .0;
contract Caller {
address _addr;
function setAddr(address x) external {
_addr = x;
}
function test() external {
_addr.call {
value: 0 ether,
gas: 1000000
}(abi.encodeWithSignature("x()"));
}
}
contract Called {
function x() external {
uint a = 2 + 3;
return a;
}
}
你需要编译和部署2个合约 Caller 和 Called (启动优化, runs为200, solidity 0.8.7)**。
之后,调用函数Caller.setAddr(x),x=被调用合约的地址,将地址设置为被调用合约。
现在我们可以通过调用函数 "test()"并进行反汇编来分析该函数。
你可以通过以下链接找到该函数:https://ethervm.io/decompile/ropsten/0x7dCb3545d535D2DF5752F389c34E346f333d907b
我们将从函数选择器的跳转后的0x6D开始:
006D 5B JUMPDEST |0xf8a8fd6d| (discarded)
006E 61 PUSH2 0x006b |0x006b|
0071 60 PUSH1 0x00 |0x00|0x006b|
0073 80 DUP1 |0x00|0x00|0x006b|
0074 54 SLOAD |addr|0x00|0x006b|
在0x6E,PUSH2 0x006b是一个指向0x6B的指针,在那里函数STOP,在else if函数选择器的结束时,EVM将跳转到这个字节。
之后,EVM SLOAD
在0x00槽的地址,这当然是智能合约中的 _addr 变量。在74字节之后堆栈为:|addr|0x00|RET|
(RET是0x6b,指向6B的指针)。
我们可以把它翻译成汇编代码:
addr := sload(0x00)
下面是下一个部分的反汇编:
0075 60 PUSH1 0x40 |0x40|addr|0x00|0x006b|
0077 80 DUP1 |0x40|0x40|addr|0x00|0x006b|
0078 51 MLOAD |0x80|0x40|addr|0x00|0x006b|
0079 60 PUSH1 0x04 |0x04|0x80|0x40|addr|0x00|0x006b|
007B 81 DUP2 |0x80|0x04|0x80|0x40|addr|0x00|0x006b|
007C 52 MSTORE |0x80|0x40|addr|0x00|0x006b|
007D 60 PUSH1 0x24 |0x24|0x80|0x40|addr|0x00|0x006b|
007F 81 DUP2 |0x80|0x24|0x80|0x40|addr|0x00|0x006b|
0080 01 ADD |0xa4|0x80|0x40|addr|0x00|0x006b|
0081 82 DUP3 |0x40|0xa4|0x80|0x40|addr|0x00|0x006b|
0082 52 MSTORE |0x80|0x40|addr|0x00|0x006b|
0083 60 PUSH1 0x20 |0x20|0x80|0x40|addr|0x00|0x006b|
0085 81 DUP2 |0x80|0x20|0x80|0x40|addr|0x00|0x006b|
0086 01 ADD |0xa0|0x80|0x40|addr|0x00|0x006b|
0087 80 DUP1 |0xa0|0xa0|0x80|0x40|addr|0x00|0x006b|
0088 51 MLOAD |0x00|0xa0|0x80|0x40|addr|0x00|0x006b|
EVM继续进行一系列的MLOAD
/MSTORE
:
|0x80|0x40|addr|0x00|RET|
。|0x80|0x40|addr|0x00|RET|
。|0x00|0xa0|0x80|0x40|addr|0x00|RET|
。这里是反编译的结果:
uint free_pointer := mload(0x40) // equalts 0x80
mstore(free_pointer,0x04)
free_pointer2 := free_pointer + 0x24
mstore(0x40,free_pointer2)
free_pointer3 := free_pointer + 0x20
result := mload(free_pointer3) // equals 0
因此,现在的内存是:
图:字节0x88后的内存
0089 60 PUSH1 0x01 |0x01|0x00|0xa0|0x80|0x40|addr|0x00|.
008B 60 PUSH1 0x01 |0x01|0x01|0x00|0xa0|0x80|0x40|addr|0x00|.
008D 60 PUSH1 0xe0 |0xe0|0x01|0x01|0x00|0xa0|.. (hidden)
008F 1B SHL |0x00..00100..000|0x01|0x00|0xa0|..
0090 03 SUB |0x00..000ff..fff|0x00|0xa0|...
0091 16 AND |0x00|0xa0|...
0092 63 PUSH4 0x03155a67 |0x03155a67|0x00|0xa0|...
0097 60 PUSH1 0xe2 |0xe2|0x03155a67|0x00|0xa0|...
0099 1B SHL |0x03155a6700000..00|0x00|0xa0|...
009A 17 OR |0x03155a6700000..00|0xa0|...
009B 90 SWAP1 |0xa0|0x03155a6700000..00|...
009C 52 MSTORE |0x80|0x40|addr|0x00|RET|
009D 90 SWAP1 |0x40|0x80|addr|0x00|RET|
009E 51 MLOAD |0xa4|0x80|addr|0x00|RET|
由于这里有很多汇编看起来很熟悉,我不会描述所有的指令。
注意0x03155a67是test()函数的签名!
反编译结果:
mstore(0xa0,0x03155a6700000..00)
free_pointer4 = mload(0x40) // load a4
在92和9E之间:
009F 60 PUSH1 0x01 |0x01|0xa4|0x80|addr|0x00|RET|
00A1 60 PUSH1 0x01 |0x01|0x01|0xa4|0x80|addr|0x00|RET|
00A3 60 PUSH1 0xa0 |0xa0|0x01|0x01|0xa4|0x80|addr|0x00|RET|
00A5 1B SHL |0x0..0100..00|0x01|0xa4|0x80|addr|0x00|RET|
00A6 03 SUB |0x0..0ff..ff|0xa4|0x80|addr|0x00|RET|
00A7 90 SWAP1 |0xa4|0x0..0ff..ff|0x80|addr|0x00|RET|
00A8 92 SWAP3 |addr|0x0..0ff..ff|0x80|0xa4|0x00|RET|
00A9 16 AND |addr (cleaned) |0x80|0xa4|0x00|RET|
00AA 92 SWAP3 |0x00|0x80|0xa4|addr|RET|
00AB 62 PUSH3 0x0f4240 |0x0f4240|0x00|0x80|0xa4|addr|RET|
00AF 92 SWAP3 |0xa4|0x00|0x80|0x0f4240|addr|RET|
00B0 90 SWAP1 |0x00|0xa4|0x80|0x0f4240|addr|RET|
00B1 91 SWAP2 |0x80|0xa4|0x00|0x0f4240|addr|RET|
00B2 61 PUSH2 0x00ba |0x00ba|0x80|0xa4|0x00|0x0f4240|addr|RET|
00B5 91 SWAP2 |0xa4|0x80|0x00ba|0x00|0x0f4240|addr|RET|
00B6 61 PUSH2 0x012d |0x012|0xa4|0x80|0x00ba|0x00|0x0f4240|
00B9 56 *JUMP |0xa4|0x80|0x00ba|0x00|0x0f4240|addr|RET|
在9F和A9之间,我们也已经知道这个模式,目的是用0x000...00ffff作为掩码来 "清理 "地址。
例如,这里是一个 "清理过的"地址(32个字节):0x000000000000000000000000aaC5322e456d45E7b6c452038836C5631C2AeBc0
而这里是没有被清理的相同地址: 0x10000000000b000000000000aaC5322e456d45E7b6c452038836C5631C2AeBc0
目标是去除 "1 "和 "b"。
在AA和B1之间,没有什么有趣的东西。
这个部分以对位于0x012d的函数的调用结束,参数是0xa4和0x80。
除了 "地址清理 "和内存中的签名外,没有什么可说的,至少现在是这样......
内存[0x80:0xa0]中的4是什么?别担心,我以后会解释的 :)
反编译:
addr = addr & 0x000..00fff
func_012d(0xa4,0x80) // return values aren't know...
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!