热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

开发笔记:一种链下绕过非view限制直接读取智能合约某类特殊函数返回结果的技巧

篇首语:本文由编程笔记#小编为大家整理,主要介绍了一种链下绕过非view限制直接读取智能合约某类特殊函数返回结果的技巧相关的知识,希望对你有一定的参考价值。

篇首语:本文由编程笔记#小编为大家整理,主要介绍了一种链下绕过非view限制直接读取智能合约某类特殊函数返回结果的技巧相关的知识,希望对你有一定的参考价值。






一、前言

我们知道,在智能合约中一般分为读取(view/pure)或者写入(改变状态)这两种类型。由于保护数据设置障碍的需要,有时合约开发者并不想别人查看他们的view函数返回值,于是在上面增加了调用者权限限制,更有甚者,故意通过某种技巧(或者是业务需要)将此函数变成非view/pure类型的函数,也就是一个交易。这样线下(前端或者脚本)调用此函数时就是一个交易,是无法直接得到函数返回结果的。

在上一篇文章《一种绕过管理员权限调用智能合约view函数的小技巧》中,我们介绍了绕过权限控制来读取view函数返回结果的技巧,那么问题来了,这里的函数是非ivew的怎么办?如果该函数的实际执行并未改变状态,相当于一个view函数执行,那我们是可以绕过这个限制的。本文就介绍这么一个小技巧,同时也是对《使用ethers.js直接读取智能合约中插槽内容》这一篇文章的收尾问题进行解答,验证我们读取的数据和实际数据是相同的。


二、示例目标合约。

我们还是以《使用ethers.js直接读取智能合约中插槽内容》这一篇文章中的示例合约进行演示,再次贴出该合约地址:https://bscscan.com/address/0x4BfE9489937d6C0d7cD6911F1102c25c7CBc1B5A#code
该合约已经通过浏览器验证,是BSC网络上一个真实运行的合约(当然不是笔者部署的)。我们注意看以下代码:

bytes32 internal constant IMPLEMENTATION_SLOT = 0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc;
bytes32 internal constant ADMIN_SLOT = 0xb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103;
/**
* @dev Modifier to check whether the `msg.sender` is the admin.
* If it is, it will run the function. Otherwise, it will delegate the call
* to the implementation.
*/

modifier ifAdmin() {
if (msg.sender == _admin()) {
_;
} else {
_fallback();
}
}
/**
* @return The address of the proxy admin.
*/

function admin() external ifAdmin returns (address) {
return _admin();
}
/**
* @return The address of the implementation.
*/

function implementation() external ifAdmin returns (address) {
return _implementation();
}

有看过我们面文章的人一看就知道我摘出来的代码的含义,没有看过的也不打紧,我简单再解释一下。这个代码块刚开始定义了admin和implement的插槽索引,然后定义了一个ifAdmin的函数修饰符,在其定义里我们看到,如果调用者是管理员,就执行接下来的函数体,如果不是,就执行_fallback()函数,这里的_fallback()其实调用委托地址相同的函数了,弄明白这个需要懂得代理/委托这种可升级模式。读者也可以在网上搜索或者在我的其它文章中读到代理/委托相关内容,这里不再解释。

从上面的代码中我们可以看到,只有管理员才能查询管理员地址和implementation地址,并且它们都是非view函数的,无法直接在浏览器界面上使用Read Contract 调用,而是出现在 Write Contract界面中,如下图:
Read Contract按钮下空空如也
上面那张图显示了Read Contract按钮下空空如也。我们再看Write Contract。
读取信息出现在写合约界面中
从上图可以看到,本来只是读取管理员信息的操作却显示在了write界面中,显然第一我们不是管理员,第二我们也没有管理员私钥,我们执行写操作是不能成功的。再说,即使成功,我们也只能得到一个交易对象,而无法直接得到返回结果。

那个Connedt to Web3 点击后会连接你的浏览器插件钱包(笔者用的metamask),当网络选择正确时,连接成功后会变绿,显示连接成功和你的地址,这里笔者就不再放图了,这时你就可以直接使用浏览器调用合约了,不需要该死的项目方前端界面了😂😂😂。

从合约的代码片断中我们可以看到,只有管理员才能去读谁是管理员,这个和区块链的公开透明有那么一点点相违背啊。这里也许并不是合约开发者存心和我们为难,不让我们读这个数据。很大可能是直接使用了openzeppelin中的代理/委托标准模板,那就是openzeppelin和我们为难了,但不管怎样,这都不是事。

好了,有些扯远了,我们来看怎么通过脚本去调用这个write类型函数并得到结果。


三、测试脚本

首先,我们需要明确一点,我们调用的admin函数和implementation函数在调用者是管理员的情况下,的确只读取了本地存储的adminimplement地址,并无改变任何状态,其实质相当于是一个view函数。(如果我们不是管理员调用会报错,即使是view函数也会报错)。

要解决这个问题首先得是管理员,当然我们不是管理员,也拿不到私钥,怎么办呢?上一篇文章《一种绕过管理员权限调用智能合约view函数的小技巧》已经解决这个问题了。

其次,这个不是view函数怎么办?如果我们在该合约的浏览器页面下的Contract ABI里找到这个admin函数,相应的ABI内容为:
{"inputs":[],"name":"admin","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"nonpayable","type":"function"}

我们注意到stateMutability标记的为nonpayable,而不是view

因此,我们无法直接使用它公开的ABI了,我们需要自己编一个view类型的自用。

const abi = [
"function admin() view returns(address)",
"function implementation() view returns(address)"
]

我们今天的脚本在前两篇文章的脚本上稍微改一下就能得到了,完整脚本如下:

const { ethers,utils } = require("ethers");
const bsc_rpc_url = "https://bsc-dataseed2.defibit.io"
const provider = new ethers.providers.JsonRpcProvider(bsc_rpc_url)
const proxy_address = "0x4BfE9489937d6C0d7cD6911F1102c25c7CBc1B5A"
const admin_slot = ethers.BigNumber.from("0xb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103")
const impl_slot = ethers.BigNumber.from("0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc")
const abi = [
"function admin() view returns(address)",
"function implementation() view returns(address)"
]
const proxy_contract = new ethers.Contract(proxy_address,abi,provider)
async function checkResult(admin_address,impl_address) {
try {
let admin = await proxy_contract.admin({
"from":admin_address
})
console.log("check admin_address:", admin === admin_address)
let impl = await proxy_contract.implementation({
"from":admin_address
})
console.log("check impl_address:", impl === impl_address)
}catch(e){
console.log(e)
}
}
async function start() {
const admin_info = await provider.getStorageAt(proxy_address , admin_slot)
console.log("admin_info:",admin_info)
const admin_address = utils.getAddress("0x" + admin_info.substring(26))
console.log("admin_address:",admin_address)
console.log()
const impl_info = await provider.getStorageAt(proxy_address , impl_slot)
console.log("impl_info:",impl_info)
const impl_address = utils.getAddress("0x" + impl_info.substring(26))
console.log("impl_address:",impl_address)
console.log()
checkResult(admin_address,impl_address)
}
start()

我们直接node运行脚本,得到的输出为:

admin_info: 0x0000000000000000000000005379f32c8d5f663eacb61eef63f722950294f452
admin_address: 0x5379F32C8D5F663EACb61eeF63F722950294f452
impl_info: 0x000000000000000000000000cac73a0f24968e201c2cc326edbc92a87666b430
impl_address: 0xcac73A0f24968e201c2cc326edbC92A87666b430
check admin_address: true
check impl_address: true

注意:我们这里的流程是先利用插槽索引得到管理员地址,再利用管理员地址去调用这个admin函数,这样双重验证了管理员地址正确这个结果(第一重是非管理员调用会报错,我们通过了;第二重是返回的结果和我们读插槽的结果一致)。虽然这里的admin函数是非view的,我们仍然直接从合约中得到了函数的返回结果(而不是得到了一个交易对象)。

再次重申一点:这个技巧只能针对那些特定的非view函数


四、另外一种阻止view函数的办法

有的时候可以故意将view函数写成非view不让你看数据的,非要给你制造一点麻烦,例如下面的合约:

/**
*Submitted for verification at Etherscan.io on 2021-06-29
*/

pragma solidity =0.6.6;
contract NoViewTest {
uint private _seed = 66;
function getSeed() external returns(uint) {
if (false) {
_seed = _seed;
}
return _seed;
}
}

通过一段永不执行的写操作让view 函数变成了 非 view 函数,这样编译的时候不会提示你该函数要定义为view函数。

那么我们可不可以使用上面的方式来直接调用该函数并得到结果66呢?

我们实际操作一下,笔者已经将该合约部署在kovan测试网上,地址为:https://kovan.etherscan.io/address/0xe00d0bc01f11dc5c5f66611a6cf37c3f3847fe1a#code

测试脚本为:noViewTest.js

const { ethers } = require("ethers");
const infura_key = "your_infura_key"
const provider = new ethers.providers.InfuraProvider("kovan",infura_key)
const contract_address = "0xe00d0bc01f11dc5c5f66611a6cf37c3f3847fe1a"
const abi = [
"function getSeed() view returns(uint)"
]
const test_contract = new ethers.Contract(contract_address,abi,provider)
async function getSeed() {
try {
let seed = await test_contract.getSeed()
console.log("seed:", seed.toString()) //66
}catch(e){
console.log(e)
}
}
getSeed()

我们运行node noViewTest.js,可以得到输出为66,验证我们上面提到的方法是有效的。

什么?还没有infura_key?赶快免费申请一个吧。


五、结束语

如果一个函数只是view函数,即不写数据,不改变状态(注:发log,发以太币也是属于改变状态),即使它加了调用权限并且设法弄成了非view函数(假装为写交易),我们都可以设法读取该函数的返回结果的。所以,还是公开透明吧!

当然,这是有前提的(比如别人会开源,或者通过浏览器验证公开代码,不让我们去猜代码)。

我们本次是链下使用脚本,那么,链上直接使用其它智能合约访问可否绕过这个限制呢?我们下次再尝试!当然有兴趣的读者可以自己先尝试一下。

在这里,笔者验证了一下,因为篇幅很小,所以直接加在后面了。

验证合约为:

pragma solidity =0.6.6;
contract NoViewTest {
uint private _seed = 66;
function getSeed() external returns(uint) {
if (false) {
_seed = _seed;
}
return _seed;
}
}
interface INoViewTest {
function getSeed() external view returns(uint);
}
contract NoViewCall {
INoViewTest public test = INoViewTest(0xE00D0BC01F11Dc5C5F66611A6cf37c3F3847fE1A);
function getSeed() external view returns(uint) {
return test.getSeed();
}
}

合约部署后的地址为:
https://kovan.etherscan.io/address/0x4A2b70Cab25E566AbF998Ed620e2C5CCA025d375#readContract
打开上述地址,直接点击getSeed,就能看到查询结果为66了。

在这里插入图片描述

希望本文章对以太坊和区块链学习(开发)者能提供一点点帮助。






推荐阅读
  • [译]技术公司十年经验的职场生涯回顾
    本文是一位在技术公司工作十年的职场人士对自己职业生涯的总结回顾。她的职业规划与众不同,令人深思又有趣。其中涉及到的内容有机器学习、创新创业以及引用了女性主义者在TED演讲中的部分讲义。文章表达了对职业生涯的愿望和希望,认为人类有能力不断改善自己。 ... [详细]
  • 本文介绍了南邮ctf-web的writeup,包括签到题和md5 collision。在CTF比赛和渗透测试中,可以通过查看源代码、代码注释、页面隐藏元素、超链接和HTTP响应头部来寻找flag或提示信息。利用PHP弱类型,可以发现md5('QNKCDZO')='0e830400451993494058024219903391'和md5('240610708')='0e462097431906509019562988736854'。 ... [详细]
  • Java序列化对象传给PHP的方法及原理解析
    本文介绍了Java序列化对象传给PHP的方法及原理,包括Java对象传递的方式、序列化的方式、PHP中的序列化用法介绍、Java是否能反序列化PHP的数据、Java序列化的原理以及解决Java序列化中的问题。同时还解释了序列化的概念和作用,以及代码执行序列化所需要的权限。最后指出,序列化会将对象实例的所有字段都进行序列化,使得数据能够被表示为实例的序列化数据,但只有能够解释该格式的代码才能够确定数据的内容。 ... [详细]
  • 本文介绍了C#中生成随机数的三种方法,并分析了其中存在的问题。首先介绍了使用Random类生成随机数的默认方法,但在高并发情况下可能会出现重复的情况。接着通过循环生成了一系列随机数,进一步突显了这个问题。文章指出,随机数生成在任何编程语言中都是必备的功能,但Random类生成的随机数并不可靠。最后,提出了需要寻找其他可靠的随机数生成方法的建议。 ... [详细]
  • 如何用UE4制作2D游戏文档——计算篇
    篇首语:本文由编程笔记#小编为大家整理,主要介绍了如何用UE4制作2D游戏文档——计算篇相关的知识,希望对你有一定的参考价值。 ... [详细]
  • 安卓select模态框样式改变_微软Office风格的多端(Web、安卓、iOS)组件库——Fabric UI...
    介绍FabricUI是微软开源的一套Office风格的多端组件库,共有三套针对性的组件,分别适用于web、android以及iOS,Fab ... [详细]
  • 从零学Java(10)之方法详解,喷打野你真的没我6!
    本文介绍了从零学Java系列中的第10篇文章,详解了Java中的方法。同时讨论了打野过程中喷打野的影响,以及金色打野刀对经济的增加和线上队友经济的影响。指出喷打野会导致线上经济的消减和影响队伍的团结。 ... [详细]
  • 【shell】网络处理:判断IP是否在网段、两个ip是否同网段、IP地址范围、网段包含关系
    本文介绍了使用shell脚本判断IP是否在同一网段、判断IP地址是否在某个范围内、计算IP地址范围、判断网段之间的包含关系的方法和原理。通过对IP和掩码进行与计算,可以判断两个IP是否在同一网段。同时,还提供了一段用于验证IP地址的正则表达式和判断特殊IP地址的方法。 ... [详细]
  • 本文介绍了响应式页面的概念和实现方式,包括针对不同终端制作特定页面和制作一个页面适应不同终端的显示。分析了两种实现方式的优缺点,提出了选择方案的建议。同时,对于响应式页面的需求和背景进行了讨论,解释了为什么需要响应式页面。 ... [详细]
  • 本文比较了eBPF和WebAssembly作为云原生VM的特点和应用领域。eBPF作为运行在Linux内核中的轻量级代码执行沙箱,适用于网络或安全相关的任务;而WebAssembly作为图灵完备的语言,在商业应用中具有优势。同时,介绍了WebAssembly在Linux内核中运行的尝试以及基于LLVM的云原生WebAssembly编译器WasmEdge Runtime的案例,展示了WebAssembly作为原生应用程序的潜力。 ... [详细]
  • 不同优化算法的比较分析及实验验证
    本文介绍了神经网络优化中常用的优化方法,包括学习率调整和梯度估计修正,并通过实验验证了不同优化算法的效果。实验结果表明,Adam算法在综合考虑学习率调整和梯度估计修正方面表现较好。该研究对于优化神经网络的训练过程具有指导意义。 ... [详细]
  • 本文介绍了UVALive6575题目Odd and Even Zeroes的解法,使用了数位dp和找规律的方法。阶乘的定义和性质被介绍,并给出了一些例子。其中,部分阶乘的尾零个数为奇数,部分为偶数。 ... [详细]
  • 本文介绍了Python爬虫技术基础篇面向对象高级编程(中)中的多重继承概念。通过继承,子类可以扩展父类的功能。文章以动物类层次的设计为例,讨论了按照不同分类方式设计类层次的复杂性和多重继承的优势。最后给出了哺乳动物和鸟类的设计示例,以及能跑、能飞、宠物类和非宠物类的增加对类数量的影响。 ... [详细]
  • 本文介绍了RxJava在Android开发中的广泛应用以及其在事件总线(Event Bus)实现中的使用方法。RxJava是一种基于观察者模式的异步java库,可以提高开发效率、降低维护成本。通过RxJava,开发者可以实现事件的异步处理和链式操作。对于已经具备RxJava基础的开发者来说,本文将详细介绍如何利用RxJava实现事件总线,并提供了使用建议。 ... [详细]
  • Java和JavaScript是什么关系?java跟javaScript都是编程语言,只是java跟javaScript没有什么太大关系,一个是脚本语言(前端语言),一个是面向对象 ... [详细]
author-avatar
待续爱情2502861755
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有