EVM介绍及字节码简单逆向

news/2024/12/22 22:57:05/文章来源:https://www.cnblogs.com/WZM1230/p/18622766

什么是EVM

以太坊是一个分布式的状态机,其中的状态不仅包含所有的账户和余额,还有EVM和EVM状态(可以被预先定义的规则所改变的东西);

EVM是以太坊中的虚拟机,可以允许不被信任的代码执行;它是一个基于栈的虚拟机,有一个短暂的内存和一个永久存储的状态;

image-20241210222413583

PC:类似计算机中的PC寄存器,记录当前的指令执行到哪了;

Stack:栈,以太坊是基于栈的,深度是1024,每层256位(32字节,哈希长度是这么多);

Gas available:可用的gas,每执行一条指令,gas便会相应的少一点;

Memory:内存;

Virtual ROM:存储代码的地方,不可变;

World state:存储状态的,永久存储,整个状态是一个"modified Merkle Patricia Trie";

导致以太坊状态变化的是来自账户的交易(包括转账、调用合约等);

以太坊的运行图:

image-20241210224129512

4种Data Location

在了解EVM的opcode如何工作之前,需要先了解一下当你部署合约或者调用一个外部函数时,代码、数据都存在什么地方;

Storage

Storage时存储状态变量的地方,永久存储。

存储的结构体可以被理解为一个key-value键值对,每个key和每个value的长度都是32字节(256比特):

Key#(Slot)[256 bytes]    |    Value[256 bytes]
---------------------------------------------
0                        |    1111111
1                        |    22222222
2                        |    333333333
...

这里的key可以形象的理解为插槽(slot),共有2^256个,序号为0 ~ 2^256-1,可以通过插槽的序号来读取数据;

Memory

memory是代码执行时存储临时变量的地方,同时在Solidity中声明像array时必须加上这个关键词:

uint256[] memory array = new uint256[](10);

可以被看作是一个字节数组,可以以1字节[8比特]或者32字节[256比特]的形式写入(各自有对应的opcode,MSTORE8MSTORE),读取时都是32字节(有指定的opcode,MLOAD);

Call Stack

EVM是一个基于栈的虚拟机,call stack就是基础,写合约你可以操控storagememory,当合约在操纵这两个数据位置时,就需要call stack来提供帮助;

call stack就像x86中的栈一样,存储一些中间数据的;opcode可以从中pop数据接收,也可以push数据进去;

// 例子
assembly{// opcodesPUSH1 0x05PUSH1 0x00SSTORE
};call stack:
|      |     |      |     |      |
|      | --> |      | --> |      |
|      |     | 0x00 |     |      |
| 0x05 |     | 0x05 |     |      |(SSTORE指令需要接收2个参数,所以栈中没有数据来)

为什么不直接操作memory?--> 栈更加的高效和便宜(gas少);

Calldata

calldata也是临时存在的,存储函数的调用参数,它只存在于外部函数调用,不能更改;

// calldata例子
// 0x6057361d000000000000000000000000000000000000000000000000000000000000000a

一开始的4个字节6057361d是函数选择器(函数名+参数取哈希),后面是调用的实际参数;

字节码

字节码的组成

一个合约编译后的字节码由两个部分组成:

初始化字节码

  • 包含一些初始化一些状态的指令(合约的构造函数)
  • 一些将运行时字节码拷贝给EVM的指令(部署)

在EVM接收运行时字节码后(存储在链上,并且关联了一个账户地址),此时链上存储的才是真正的运行时字节码;

初始化字节码只会运行一次,是在部署期间,也可以称为部署合约;

运行时字节码

在合约中写的一些变量、函数、事件都会被转化为由EVM Opcodes组成的一连串的字节码;

当一个外部调用(会有一个calldata传来)来调用此合约的一个函数,合约会提取calldata的前4个字节(函数选择器),然后通过一连串的if/else匹配到正确的函数来执行;

调试方法及反编译

接下来看一个简单的合约:

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.26;contract Test{// 私有状态变量uint256 private _value;// 公开的字符串string public name;// 构造函数constructor(string memory str){name = str;}// 事件event NewValue(uint256 newValue);// 函数,设置值function SetValue(uint256 value) public {_value = value;emit NewValue(value);}
}

部署:

image-20241219223656632

部署字节码和运行时字节码

input:包含了构造函数的部分、部署合约的部分和运行时字节码;

output:运行时字节码,最终会被写入区块链;

// input,其中包裹着output,在其中用{output}表示的,其余部分是初始化字节码
// 0x608060405234801561000f575f80fd5b506040516107af3803806107af83398181016040528101906100319190610194565b806001908161004091906103e8565b50506104b7565b5f604051905090565b5f80fd5b5f80fd5b5f80fd5b5f80fd5b5f601f19601f8301169050919050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52604160045260245ffd5b6100a682610060565b810181811067ffffffffffffffff821117156100c5576100c4610070565b5b80604052505050565b5f6100d7610047565b90506100e3828261009d565b919050565b5f67ffffffffffffffff82111561010257610101610070565b5b61010b82610060565b9050602081019050919050565b8281835e5f83830152505050565b5f610138610133846100e8565b6100ce565b9050828152602081018484840111156101545761015361005c565b5b61015f848285610118565b509392505050565b5f82601f83011261017b5761017a610058565b5b815161018b848260208601610126565b91505092915050565b5f602082840312156101a9576101a8610050565b5b5f82015167ffffffffffffffff8111156101c6576101c5610054565b5b6101d284828501610167565b91505092915050565b5f81519050919050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52602260045260245ffd5b5f600282049050600182168061022957607f821691505b60208210810361023c5761023b6101e5565b5b50919050565b5f819050815f5260205f209050919050565b5f6020601f8301049050919050565b5f82821b905092915050565b5f6008830261029e7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff82610263565b6102a88683610263565b95508019841693508086168417925050509392505050565b5f819050919050565b5f819050919050565b5f6102ec6102e76102e2846102c0565b6102c9565b6102c0565b9050919050565b5f819050919050565b610305836102d2565b610319610311826102f3565b84845461026f565b825550505050565b5f90565b61032d610321565b6103388184846102fc565b505050565b5b8181101561035b576103505f82610325565b60018101905061033e565b5050565b601f8211156103a05761037181610242565b61037a84610254565b81016020851015610389578190505b61039d61039585610254565b83018261033d565b50505b505050565b5f82821c905092915050565b5f6103c05f19846008026103a5565b1980831691505092915050565b5f6103d883836103b1565b9150826002028217905092915050565b6103f1826101db565b67ffffffffffffffff81111561040a57610409610070565b5b6104148254610212565b61041f82828561035f565b5f60209050601f831160018114610450575f841561043e578287015190505b61044885826103cd565b8655506104af565b601f19841661045e86610242565b5f5b8281101561048557848901518255600182019150602085019450602081019050610460565b868310156104a2578489015161049e601f8916826103b1565b8355505b6001600288020188555050505b505050505050565b6102eb806104c45f395ff3fe{output}00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000003575a4d0000000000000000000000000000000000000000000000000000000000
// output,运行时字节码
// 0x608060405234801561000f575f80fd5b5060043610610034575f3560e01c806306fdde0314610038578063ed8b070614610056575b5f80fd5b610040610072565b60405161004d91906101ae565b60405180910390f35b610070600480360381019061006b9190610205565b6100fe565b005b6001805461007f9061025d565b80601f01602080910402602001604051908101604052809291908181526020018280546100ab9061025d565b80156100f65780601f106100cd576101008083540402835291602001916100f6565b820191905f5260205f20905b8154815290600101906020018083116100d957829003601f168201915b505050505081565b805f819055507fac3e966f295f2d5312f973dc6d42f30a6dc1c1f76ab8ee91cc8ca5dad1fa60fd81604051610133919061029c565b60405180910390a150565b5f81519050919050565b5f82825260208201905092915050565b8281835e5f83830152505050565b5f601f19601f8301169050919050565b5f6101808261013e565b61018a8185610148565b935061019a818560208601610158565b6101a381610166565b840191505092915050565b5f6020820190508181035f8301526101c68184610176565b905092915050565b5f80fd5b5f819050919050565b6101e4816101d2565b81146101ee575f80fd5b50565b5f813590506101ff816101db565b92915050565b5f6020828403121561021a576102196101ce565b5b5f610227848285016101f1565b91505092915050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52602260045260245ffd5b5f600282049050600182168061027457607f821691505b60208210810361028757610286610230565b5b50919050565b610296816101d2565b82525050565b5f6020820190506102af5f83018461028d565b9291505056fea264697066735822122041b986fd46ce9b04f824550403d4f8083cfafa6e8954be833ae454250f2bfba864736f6c634300081a0033

调试方法

之后便可以在页面中调试:

image-20241220003152829

反编译

有两个比较好用的网站:

https://ethervm.io/decompile

https://app.dedaub.com/decompile

反编译input及解析
function 0x3e8(uint256 varg0, bytes varg1) private { require(varg1.length <= uint64.max, Panic(65)); // failed memory allocation (too much memory)// 读取Storage[1]的内容到v0v0 = STORAGE[varg0] >> 1;if (!(STORAGE[varg0] & 0x1)) {v0 = v1 = v0 & 0x7f;}require((STORAGE[varg0] & 0x1) - (v0 < 32), Panic(34)); // access to incorrectly encoded storage byte array// 若v0大于31字节,则将v2到v4点内容清零,还是初始化if (v0 > 31) {v2 = v3 = v4 + (varg1.length + 31 >> 5);while (v2 < v4 + (v0 + 31 >> 5)) {STORAGE[v2] = STORAGE[v2] & 0x0 | uint256(0);v2 = v2 + 1;}}v5 = v6 = 32;if (varg1.length > 31 == 1) {// 传过来的内容长度大于31,也就是大于等于32字节v7 = v8 = 0;while (v7 < varg1.length & 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0) {// 一次取32字节存入STORAGE[v9] = MEM[varg1 + v5];v9 = v9 + 1;v5 = v5 + 32;v7 = v7 + 32;}if (varg1.length & 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0 < varg1.length) {STORAGE[v9] = MEM[varg1 + v5] & ~(uint256.max >> ((varg1.length & 0x1f) << 3));}// 更新值的内容STORAGE[varg0] = (varg1.length << 1) + 1;return ;} else {// 传入过来的内容长度没有32字节v10 = v11 = 0;if (varg1.length) {v10 = v12 = MEM[varg1.data];}// 将Storage[1] = 内容,后面一堆看起来像是检查STORAGE[varg0] = v10 & ~(uint256.max >> (varg1.length << 3)) | varg1.length << 1;return ;}
}// Note: The function selector is not present in the original solidity code.
// However, we display it for the sake of completeness.function __function_selector__() public payable { // 内存64的位置设置值为128MEM[64] = 128;// 是否附带了金额require(!msg.value);// 将1967处的代码复制到内存中(刚刚上面赋值的128处)MEM[MEM[64]:MEM[64] + this.code.size - 1967] = this.code[1967:1967 + this.code.size - 1967];// 更新位置:将原先的128加上代码长度MEM[64] += this.code.size - 1967;// 一系列检查require(MEM[64] + (this.code.size - 1967) - MEM[64] >= 32);require(MEM[MEM[64]] <= uint64.max);require(MEM[64] + MEM[MEM[64]] + 31 < MEM[64] + (this.code.size - 1967));// 读取内存中的值存储在v0,值的内容是MEM[刚刚更新的新内存位置 + 新内存位置当中的值的大小]v0 = MEM[MEM[64] + MEM[MEM[64]]];require(v0 <= uint64.max, Panic(65)); // failed memory allocation (too much memory)// 按照刚刚v0的值分配数组大小v1 = new bytes[](v0);require(!((v1 + ((v0 + 31 & 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0) + 32 + 31 & 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0) > uint64.max) | (v1 + ((v0 + 31 & 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0) + 32 + 31 & 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0) < v1)), Panic(65)); // failed memory allocation (too much memory)require(MEM[64] + MEM[MEM[64]] + 32 + v0 <= MEM[64] + (this.code.size - 1967));// 从内存拷贝数据到v1,数据的内容是MEM[刚刚更新的新内存位置 + 新内存位置当中的值的大小 + 32字节]// 这边可以推测v0中的值是大小(32字节),后面跟着的是内容MCOPY(v1.data, MEM[64] + MEM[MEM[64]] + 32, v0);// 将数组最后一个元素设置为0v1[v0] = 0;// 调用函数,传入的是刚刚的内容0x3e8(1, v1);// 将一段字节码赋值到内存开始,重新执行// 可以发现这段代码是运行时字节码,也就是ouput的内容MEM[0:747] = 0x608060405234801561000f575f80fd5b5060043610610034575f3560e01c806306fdde0314610038578063ed8b070614610056575b5f80fd5b610040610072565b60405161004d91906101ae565b60405180910390f35b610070600480360381019061006b9190610205565b6100fe565b005b6001805461007f9061025d565b80601f01602080910402602001604051908101604052809291908181526020018280546100ab9061025d565b80156100f65780601f106100cd576101008083540402835291602001916100f6565b820191905f5260205f20905b8154815290600101906020018083116100d957829003601f168201915b505050505081565b805f819055507fac3e966f295f2d5312f973dc6d42f30a6dc1c1f76ab8ee91cc8ca5dad1fa60fd81604051610133919061029c565b60405180910390a150565b5f81519050919050565b5f82825260208201905092915050565b8281835e5f83830152505050565b5f601f19601f8301169050919050565b5f6101808261013e565b61018a8185610148565b935061019a818560208601610158565b6101a381610166565b840191505092915050565b5f6020820190508181035f8301526101c68184610176565b905092915050565b5f80fd5b5f819050919050565b6101e4816101d2565b81146101ee575f80fd5b50565b5f813590506101ff816101db565b92915050565b5f6020828403121561021a576102196101ce565b5b5f610227848285016101f1565b91505092915050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52602260045260245ffd5b5f600282049050600182168061027457607f821691505b60208210810361028757610286610230565b5b50919050565b610296816101d2565b82525050565b5f6020820190506102af5f83018461028d565b9291505056fea264697066735822122041b986fd46ce9b04f824550403d4f8083cfafa6e8954be833ae454250f2bfba864736f6c634300081a0033;return MEM[0:747];
}

首先看第二个函数__function_selector__,整个函数就做了几件事:

  1. 分配数组存入MEM中的值
  2. 调用函数并传入刚刚的值
  3. 返回运行时字节码(也就是output的内容)

可以推测是构造函数的执行和部署;

第一个函数0x3e8

其中有STORAGE,而且基本是赋值操作,可以推测是构造函数;

下面来验证猜想,构造函数输入时是WZM,转换成hex是575A4D,在整个字节码中搜索这串序列,可以发现在末尾处定位到了:

// 将附近的都拷贝过来了,并按照32字节为一行
50565b610296816101d2565b82525050565b5f6020820190506102af5f830184
61028d565b9291505056fea264697066735822122041b986fd46ce9b04f82455
0403d4f8083cfafa6e8954be833ae454250f2bfba864736f6c634300081a0033
0000000000000000000000000000000000000000000000000000000000000020
// 长度
0000000000000000000000000000000000000000000000000000000000000003
// "WZM"
575a4d0000000000000000000000000000000000000000000000000000000000

所以input中的内容就很清晰了,构造函数的执行、赋值,再加上合约部署到链上,返回真正的运行时字节码;

反编译output
// Compiled using the solidity compiler version 0.8.26// Data structures and variables inferred from the use of storage instructions
uint256 _setValue; // STORAGE[0x0]
string _name; // STORAGE[0x1]function 0x25d(uint256 varg0) private { v0 = v1 = varg0 >> 1;if (!(varg0 & 0x1)) {v0 = v2 = v1 & 0x7f;}require((varg0 & 0x1) - (v0 < 32), Panic(34)); // access to incorrectly encoded storage byte arrayreturn v0;
}function fallback() public payable { revert();
}function name() public payable { v0 = 0x25d(_name.length);v1 = new bytes[](v0);v2 = v3 = v1.data;v4 = 0x25d(_name.length);if (v4) {if (31 < v4) {v5 = v6 = _name.data;do {MEM[v2] = STORAGE[v5];v5 += 1;v2 += 32;} while (v3 + v4 <= v2);} else {MEM[v3] = _name.length >> 8 << 8;}}v7 = new bytes[](v1.length);MCOPY(v7.data, v1.data, v1.length);v7[v1.length] = 0;return v7;
}function SetValue(uint256 _value) public payable { require(4 + (msg.data.length - 4) - 4 >= 32);0x0_0_31 = _value;emit 0xac3e966f295f2d5312f973dc6d42f30a6dc1c1f76ab8ee91cc8ca5dad1fa60fd(_value);
}// Note: The function selector is not present in the original solidity code.
// However, we display it for the sake of completeness.function __function_selector__( function_selector) public payable { MEM[64] = 128;require(!msg.value);if (msg.data.length >= 4) {if (0x6fdde03 == function_selector >> 224) {name();} else if (0xed8b0706 == function_selector >> 224) {SetValue(uint256);}}fallback();
}

这边很明显就是合约中代码的内容;

用途

分析源码

可以分析一些未开源的合约代码(以免被割韭菜);这边举一个CTF中的题目当练手;

ByteCTF2022-Reverse-OhMySolidity

题目:

input:
0x608060405234801561001057600080fd5b5061066e806100206000396000f3fe608060405234801561001057600080fd5b50600436106100625760003560e01c806314edb54d1461006757806358f5382e1461009157806393eed093146101c55780639577a145146101ef578063a7f81e6a14610253578063f0407ca71461027d575b600080fd5b61006f6102a7565b604051808263ffffffff1663ffffffff16815260200191505060405180910390f35b61014a600480360360208110156100a757600080fd5b81019080803590602001906401000000008111156100c457600080fd5b8201836020820111156100d657600080fd5b803590602001918460018302840111640100000000831117156100f857600080fd5b91908080601f016020809104026020016040519081016040528093929190818152602001838380828437600081840152601f19601f8201169050808301925050505050505091929192905050506102bd565b6040518080602001828103825283818151815260200191508051906020019080838360005b8381101561018a57808201518184015260208101905061016f565b50505050905090810190601f1680156101b75780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b6101cd61056f565b604051808263ffffffff1663ffffffff16815260200191505060405180910390f35b6102516004803603608081101561020557600080fd5b81019080803563ffffffff169060200190929190803563ffffffff169060200190929190803563ffffffff169060200190929190803563ffffffff169060200190929190505050610584565b005b61025b61060d565b604051808263ffffffff1663ffffffff16815260200191505060405180910390f35b610285610623565b604051808263ffffffff1663ffffffff16815260200191505060405180910390f35b600060049054906101000a900463ffffffff1681565b606080829050600060088251816102d057fe5b06146102db57600080fd5b606081516040519080825280601f01601f1916602001820160405280156103115781602001600182028038833980820191505090505b509050600063deadbeef905060008090505b83518110156105635760008090506000809050600080905060008090505b60048160ff1610156103cd578060030360080260ff16888260ff1687018151811061036857fe5b602001015160f81c60f81b60f81c60ff1663ffffffff16901b830192508060030360080260ff168860048360ff16880101815181106103a357fe5b602001015160f81c60f81b60f81c60ff1663ffffffff16901b820191508080600101915050610341565b5060008090505b60208160ff16101561047f578584019350600060049054906101000a900463ffffffff1660058363ffffffff16901c018483016000809054906101000a900463ffffffff1660048563ffffffff16901b011818830192506000600c9054906101000a900463ffffffff1660058463ffffffff16901c01848401600060089054906101000a900463ffffffff1660048663ffffffff16901b0118188201915080806001019150506103d4565b5060008090505b60048160ff1610156105545760ff8160030360080260ff168463ffffffff16901c1660f81b878260ff168701815181106104bc57fe5b60200101907effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916908160001a90535060ff8160030360080260ff168363ffffffff16901c1660f81b8760048360ff168801018151811061051857fe5b60200101907effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916908160001a9053508080600101915050610486565b50505050600881019050610323565b50819350505050919050565b6000809054906101000a900463ffffffff1681565b836000806101000a81548163ffffffff021916908363ffffffff16021790555082600060046101000a81548163ffffffff021916908363ffffffff16021790555081600060086101000a81548163ffffffff021916908363ffffffff160217905550806000600c6101000a81548163ffffffff021916908363ffffffff16021790555050505050565b600060089054906101000a900463ffffffff1681565b6000600c9054906101000a900463ffffffff168156fea265627a7a72315820c500ad9e15f8594ce1140fdf04f71759a549b8a033f78b149472bb00f68975a964736f6c63430005110032
output:
Noneinput:
0x9577a1450000000000000000000000000000000000000000000000000000000012345678000000000000000000000000000000000000000000000000000000008765432100000000000000000000000000000000000000000000000000000000aabbccdd0000000000000000000000000000000000000000000000000000000044332211
output:
Noneinput(broken):
0x58f5382e...
output:
0xa625e97482f83d2b7fc5125763dcbbffd8115b208c4754eee8711bdfac9e3377622bbf0cbb785e612b82c7f5143d5333

目前不清楚给的input是运行时字节码还是添加了部署代码的字节码,先反编译一波:

// Note: The function selector is not present in the original solidity code.
// However, we display it for the sake of completeness.
function __function_selector__() public payable { MEM[64] = 128;require(!msg.value);MEM[0:1646] = 0x608060405234801561001057600080fd5b50600436106100625760003560e01c806314edb54d1461006757806358f5382e1461009157806393eed093146101c55780639577a145146101ef578063a7f81e6a14610253578063f0407ca71461027d575b600080fd5b61006f6102a7565b604051808263ffffffff1663ffffffff16815260200191505060405180910390f35b61014a600480360360208110156100a757600080fd5b81019080803590602001906401000000008111156100c457600080fd5b8201836020820111156100d657600080fd5b803590602001918460018302840111640100000000831117156100f857600080fd5b91908080601f016020809104026020016040519081016040528093929190818152602001838380828437600081840152601f19601f8201169050808301925050505050505091929192905050506102bd565b6040518080602001828103825283818151815260200191508051906020019080838360005b8381101561018a57808201518184015260208101905061016f565b50505050905090810190601f1680156101b75780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b6101cd61056f565b604051808263ffffffff1663ffffffff16815260200191505060405180910390f35b6102516004803603608081101561020557600080fd5b81019080803563ffffffff169060200190929190803563ffffffff169060200190929190803563ffffffff169060200190929190803563ffffffff169060200190929190505050610584565b005b61025b61060d565b604051808263ffffffff1663ffffffff16815260200191505060405180910390f35b610285610623565b604051808263ffffffff1663ffffffff16815260200191505060405180910390f35b600060049054906101000a900463ffffffff1681565b606080829050600060088251816102d057fe5b06146102db57600080fd5b606081516040519080825280601f01601f1916602001820160405280156103115781602001600182028038833980820191505090505b509050600063deadbeef905060008090505b83518110156105635760008090506000809050600080905060008090505b60048160ff1610156103cd578060030360080260ff16888260ff1687018151811061036857fe5b602001015160f81c60f81b60f81c60ff1663ffffffff16901b830192508060030360080260ff168860048360ff16880101815181106103a357fe5b602001015160f81c60f81b60f81c60ff1663ffffffff16901b820191508080600101915050610341565b5060008090505b60208160ff16101561047f578584019350600060049054906101000a900463ffffffff1660058363ffffffff16901c018483016000809054906101000a900463ffffffff1660048563ffffffff16901b011818830192506000600c9054906101000a900463ffffffff1660058463ffffffff16901c01848401600060089054906101000a900463ffffffff1660048663ffffffff16901b0118188201915080806001019150506103d4565b5060008090505b60048160ff1610156105545760ff8160030360080260ff168463ffffffff16901c1660f81b878260ff168701815181106104bc57fe5b60200101907effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916908160001a90535060ff8160030360080260ff168363ffffffff16901c1660f81b8760048360ff168801018151811061051857fe5b60200101907effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916908160001a9053508080600101915050610486565b50505050600881019050610323565b50819350505050919050565b6000809054906101000a900463ffffffff1681565b836000806101000a81548163ffffffff021916908363ffffffff16021790555082600060046101000a81548163ffffffff021916908363ffffffff16021790555081600060086101000a81548163ffffffff021916908363ffffffff160217905550806000600c6101000a81548163ffffffff021916908363ffffffff16021790555050505050565b600060089054906101000a900463ffffffff1681565b6000600c9054906101000a900463ffffffff168156fea265627a7a72315820c500ad9e15f8594ce1140fdf04f71759a549b8a033f78b149472bb00f68975a964736f6c63430005110032;return MEM[0:1646];
}

很明显,是加了部署代码的字节码,而且应该没有构造函数,直接继续反编译字节码即可:

// Data structures and variables inferred from the use of storage instructions
uint32 stor_0_0_3; // STORAGE[0x0] bytes 0 to 3
uint32 _k1; // STORAGE[0x0] bytes 4 to 7
uint32 _k2; // STORAGE[0x0] bytes 8 to 11
uint32 _k3; // STORAGE[0x0] bytes 12 to 15function 0x93eed093() public payable { return stor_0_0_3;
}function 0x9577a145(uint32 varg0, uint32 varg1, uint32 varg2, uint32 varg3) public payable { require(msg.data.length - 4 >= 128);stor_0_0_3 = varg0;_k1 = varg1;_k2 = varg2;_k3 = varg3;
}function k2() public payable { return _k2;
}function k3() public payable { return _k3;
}function fallback() public payable { revert();
}function k1() public payable { return _k1;
}function 0x58f5382e(bytes varg0) public payable { require(msg.data.length - 4 >= 32);require(varg0 <= uint32.max + 1);require(varg0.data <= 4 + (msg.data.length - 4));require(!((varg0.length > uint32.max + 1) | (varg0.data + varg0.length > 4 + (msg.data.length - 4))));v0 = new bytes[](varg0.length);CALLDATACOPY(v0.data, varg0.data, varg0.length);v0[varg0.length] = 0;assert(8);require(v0.length % 8 == 0);v1 = new bytes[](v0.length);if (v0.length) {MEM[v2e2.data:v2e2.data + v10b.length] = this.code[this.code.size:this.code.size + v10b.length];}v2 = v3 = 0;while (v2 < v0.length) {v4 = v5 = 0;v6 = v7 = 0;v8 = v9 = 0;v10 = v11 = 0;while (uint8(v10) < 4) {assert(v2 + uint8(v10) < v0.length);v6 = v6 + (uint32(uint8(v0[v2 + uint8(v10)] >> 248 << 248 >> 248)) << uint8(3 - v10 << 3));assert(v2 + uint8(v10) + 4 < v0.length);v8 = v8 + (uint32(uint8(v0[v2 + uint8(v10) + 4] >> 248 << 248 >> 248)) << uint8(3 - v10 << 3));v10 += 1;}v12 = v13 = 0;while (uint8(v12) < 32) {v4 = v4 + 0xdeadbeef;v6 = v6 + ((uint32(v8) << 4) + stor_0_0_3 ^ v8 + v4 ^ (uint32(v8) >> 5) + _k1);v8 = v8 + ((uint32(v6) << 4) + _k2 ^ v6 + v4 ^ (uint32(v6) >> 5) + _k3);v12 += 1;}v14 = v15 = 0;while (uint8(v14) < 4) {assert(v2 + uint8(v14) < v1.length);MEM8[32 + (v2 + uint8(v14)) + v1] = (byte(bytes1(uint8(uint32(v6) >> uint8(3 - v14 << 3)) << 248), 0x0)) & 0xFF;assert(v2 + uint8(v14) + 4 < v1.length);MEM8[32 + (v2 + uint8(v14) + 4) + v1] = (byte(bytes1(uint8(uint32(v8) >> uint8(3 - v14 << 3)) << 248), 0x0)) & 0xFF;v14 += 1;}v2 = v2 + 8;}v16 = new bytes[](v1.length);v17 = v18 = 0;while (v17 < v1.length) {v16[v17] = v1[v17];v17 = v17 + 32;}v19 = v20 = v1.length + v16.data;if (0x1f & v1.length) {MEM[v20 - (0x1f & v1.length)] = ~((uint8.max + 1) ** (32 - (0x1f & v1.length)) - 1) & MEM[v20 - (0x1f & v1.length)];}return v16;
}// Note: The function selector is not present in the original solidity code.
// However, we display it for the sake of completeness.function __function_selector__( function_selector) public payable { MEM[64] = 128;require(!msg.value);if (msg.data.length >= 4) {if (0x14edb54d == function_selector >> 224) {k1();} else if (0x58f5382e == function_selector >> 224) {0x58f5382e();} else if (0x93eed093 == function_selector >> 224) {0x93eed093();} else if (0x9577a145 == function_selector >> 224) {0x9577a145();} else if (0xa7f81e6a == function_selector >> 224) {k2();} else if (0xf0407ca7 == function_selector >> 224) {k3();}}fallback();
}

接着分析第二个input

input:
0x9577a1450000000000000000000000000000000000000000000000000000000012345678000000000000000000000000000000000000000000000000000000008765432100000000000000000000000000000000000000000000000000000000aabbccdd0000000000000000000000000000000000000000000000000000000044332211
output:
None

看起来是calldata,因为开头的0x9577a145在我们反编译的结果中为函数选择器,分解一下:

9577a145
0000000000000000000000000000000000000000000000000000000012345678
0000000000000000000000000000000000000000000000000000000087654321
00000000000000000000000000000000000000000000000000000000aabbccdd
0000000000000000000000000000000000000000000000000000000044332211

调用9577a145这个函数,然后传入了四个值;

接着分析第三个input

input(broken):
0x58f5382e...
output:
0xa625e97482f83d2b7fc5125763dcbbffd8115b208c4754eee8711bdfac9e3377622bbf0cbb785e612b82c7f5143d5333

同样是calldata,调用58f5382e这个函数,然后应该是输入了正确的flag,之后返回的值是output

所以只需分析这两个函数的具体功能,写出解密脚本即可拿到flag;

从这两句代码就可以知道是个TEA

v6 = v6 + ((uint32(v8) << 4) + stor_0_0_3 ^ v8 + v4 ^ (uint32(v8) >> 5) + _k1);
v8 = v8 + ((uint32(v6) << 4) + _k2 ^ v6 + v4 ^ (uint32(v6) >> 5) + _k3);

delta是0xdeadbeef,key是9577a145输入的四个数,直接解密脚本:

// 参考https://blog.wm-team.cn/index.php/archives/28/#OhMySolidity
#include <cstdio>
#include <cstdint>
uint32_t tar[] = {0xa625e974, 0x82f83d2b, 0x7fc51257, 0x63dcbbff, 0xd8115b20, 0x8c4754ee, 0xe8711bdf, 0xac9e3377, 0x622bbf0c, 0xbb785e61, 0x2b82c7f5, 0x143d5333
};
void decrypt (uint32_t* v, uint32_t* k) {  uint32_t v0=v[0], v1=v[1], sum=0xdeadbeef*32, i; uint32_t delta=0xdeadbeef; uint32_t k0=k[0], k1=k[1], k2=k[2], k3=k[3]; for (i=0; i<32; i++) {v1 -= ((v0<<4) + k2) ^ (v0 + sum) ^ ((v0>>5) + k3);  v0 -= ((v1<<4) + k0) ^ (v1 + sum) ^ ((v1>>5) + k1);  sum -= delta;  }v[0]=v0; v[1]=v1;  
}  
int main() {uint32_t k[4] = {0x12345678, 0x87654321, 0xaabbccdd, 0x44332211};for(int i = 0; i < 12; i += 2) {decrypt(&tar[i], k);printf("%#x %#x\\n", tar[i], tar[i+1]);}printf("%s\\n", (char*)tar);
}

优化合约

对于一些需要重复部署的合约,比如抽象账户合约,能尽量减少合约的长度,那就代表着省gas费;

例子:WTF:优化最小代理合约;

参考:

  1. 以太坊开发者官方文档-EVM
  2. EVM Codes:可以快速查看Opcodes操作码和预编译函数的功能、gas消耗
  3. Notes on the EVM
  4. A Practical Introduction To Solidity Assembly

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/856994.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

HarmonyOS 5.0 (Next)应用开发实战:使用ArkTS构建开箱即用的登录页面【HarmonyOS 5.0(Next)】

HarmonyOS 5.0 (Next)应用开发实战:使用ArkTS构建开箱即用的登录页面【HarmonyOS 5.0(Next)】 一、HarmonyOS 5.0美学与科技的完美融合 在科技飞速发展的今天,每一个细微的创新都可能引领一场变革。华为,作为科技领域的领航者,再次以HarmonyOS 5.0(Next)这一里程碑式…

fellyfin 开启硬件加速 Ubuntu 安装硬件驱动

需要 root 权限。在主机系统上安装该intel-gpu-tools包,用于在 Linux 上调试英特尔显卡驱动程序。不同发行版的名称不同。在 Debian 和 Ubuntu 上:sudo apt update && sudo apt install -y intel-gpu-tools在 Jellyfin 网络客户端中播放视频,并通过设置较低的分辨率…

开箱即用的个人主页页面开发实战—基于HarmonyOS 5.0 (Next)和ArkTS的实现【HarmonyOS 5.0(Next)】

开箱即用的个人主页页面开发实战—基于HarmonyOS 5.0 (Next)和ArkTS的实现【HarmonyOS 5.0(Next)】 一、HarmonyOS 5.0(Next)革新设计理念,打造和谐美学 在科技日新月异的今天,操作系统作为智能设备的灵魂,正不断推动着数字生活的变革。华为,作为全球领先的科技企业,…

数据结构期末复习

武汉理工大学数据结构期末复习数据结构期末复习 By Persona_owl 第一章 绪论 1. 基本概念和术语数据:计算机操作的对象的总称,是信息的符号表示形式。数据元素: 数据的基本单位,通常作为一个整体进行处理,由更小的数据项组成。数据项是数据不可分割的最小单位。数据结构: 存…

探索HarmonyOS开发:用ArkTS构建个人主页和活动管理系统【HarmonyOS 5.0(Next)】

探索HarmonyOS开发:用ArkTS构建个人主页和活动管理系统【HarmonyOS 5.0(Next)】 一、一个系统,统一生态 口号:“一个系统,统一生态”,旨在全面突破操作系统核心技术,实现系统架构由内到外的焕然一新。 流畅度提升:相比前代,HarmonyOS 5.0(Next)的流畅度提升了30%,…

HarmonyOS应用开发实战-开箱即用的首页页面构建与ArkTS解析【HarmonyOS 5.0(Next)】

HarmonyOS应用开发实战-开箱即用的首页页面构建与ArkTS解析【HarmonyOS 5.0(Next)】 一、HarmonyOS Next 5.0的优势 AI能力:HarmonyOS Next 5.0通过系统级AI能力,将AI下沉至操作系统并赋能给多个子系统,从而提升了应用的智能化体验。例如,小艺助手在HarmonyOS Next 5.0中…

JavaWeb案例整体分析---》差旅费报销管理信息系统-数据库操作和Mybatis配置

数据库操作 ` CREATE TABLE ter_businessapply( id VARCHAR(8) PRIMARY KEY, name VARCHAR(50) NOT NULL, position VARCHAR(50) NOT NULL, department VARCHAR(50) NOT NULL, destination VARCHAR(100) NOT NULL, departure_date varchar(30) NOT NULL, return_date varchar(3…

深度科普文:细数倾斜摄影数据的缺点

详细介绍了倾斜摄影数据应用到三维可视化项目中的一些缺点。1. 引言 写这篇文章的起因是最近遇到一个使用倾斜摄影数据应标的三维可视化项目,业主认为倾斜摄影数据加载很卡,要求能浏览场景的时候能立刻显示出当前的场景最精细的模型,如下图1所示。其实这个问题遇到的次数还真…

Linux 性能调优命令

from pixivPS ps auxfps:显示系统中当前的进程信息。 a:显示与终端无关的所有进程(即显示所有用户的进程)。 u:以用户友好的格式显示进程信息,包括用户、CPU 和内存使用情况。 x:显示没有控制终端的进程,例如守护进程(daemon)。 f:以 ASCII 字符显示进程的树状结构,…

使用hackbar进行跨库注入

第一步 http://192.168.1.117/sqli-labs/Less-1/?id= union select 1,2,3 --+ //构建语句

Linux编写一个自己的命令

Linux编写一个自己的命令 编译一个.c文件,生成可执行文件out。out只有在当前目录下可以执行。 而命令可在任何路径执行想让out可以在任意路径执行,有以下两种办法 1、将执行文件添加到 /bin/ 路径下(专门存放可执行文件)添加到 /bin/ 路径下后,在任何路径都可识别到程序2、…

离开Jetbrains拥抱VsCode: 离开大便拥抱大便

文章讨论了作者从Jetbrains转向VsCode的原因,主要集中在AI编程支持和个性化体验上。Jetbrains在AI Coding的支持方面表现不佳,如更新速度慢、功能不足等,使得作者感到不满;而Jetbrains自己开发的AI Assistant也未能提供满意的体验。相对而言,VsCode在插件系统和自由度上表…