题目分析
先看第一个调用,调用了ecdsa库函数的tryRecover函数,传入一个hash常量,一个签名的字符串,返回一个签名者的地址,要求签名者的地址和传入的signer地址相等。但是hash不可控,这个hash是一个常量,这一步走不通。接着进行,下面是对传入地址signer的staticcall调用。把函数签名,hash,签名内容进行abi编码之后传入。要求staticcall返回内容的长度是32字节,返回的bytes32转化成bytes4(也就是高位截断之后的前4字节),这前4个字节要等于isValidSignature的函数签名。首先这个signer要求至少16个字节的内容都是0,然后要求调用之后返回32字节的内容,并且返回内容等于一个已知常量。
bytes4 a= bytes4(keccak256("isValidSignature(bytes32,bytes)" ));
emit log_bytes4(tttt:0x1626ba7e)
发现只有0x2合适,返回的内容是32字节。这个函数就是对传入参数做一个sha256。
那现在这个问题就转化了。首先传一个0x2的地址,符合要求,还需要传入一个bytes字符串。然后把isValidSignature的函数签名,hash常量,这个外部传入的bytes字符串,这3个内容进行abi编码之后,做一个sha256计算,要求返回结果的前4个字节是isValidSignature的函数签名。先看一下abi编码之后的数据。
首先是isValidSignature函数签名,然后是MAGIC的hash。这些都是常量,而且已知。
所以第一个32字节是MAGIC的hash。因为这个hash的长度是固定的,已经写死了bytes32字节,所以在内存中不存长度。然后要看这个外部传入的bytes字符串,这个是不定长的,它在abi编码之后先存一个长度,因为它的上一个参数占用了32字节,所以这个外部传入的字符串只能存在0x20。而且字符串在内存的存储是先存一个长度,然后再存内容。所以abi编码之后的内容大概是这样:
大概以这样的数据去暴力,前两行内容固定,因为字符串是存在高位。先假定外部传入的signature是4个字节(如果4字节出不来,再尝试6字节,8字节这样),第三行0x04。
我们去暴力一个8位的内容,它和前面的数据abi编码之后的sha256的前8位是isValidSignature的函数签名。
编码的时候一定注意格式,否则很容易出错。
dat=bytes.fromhex('1626ba7e'+ web3.codec.encode_abi(['bytes32','bytes' ],['19bb34e293bba96bf0xaeea54cdd3d2dad7fdf44cbea855173fa84534fcfb528', h]).hex())
建议直接使用python库函数进行编码。
主要代码段,写的过于暴力。 最后跑出来signature是8cf1a8bb。 完整调用如下图所示:
发现前后bestScore已经发生变化,并且大于等于16。已经完成获得flag的要求。
总结
这个题的主要目的是考察evm的预编译合约,暴力的代码还有很大的优化空间。还有一个点要注意solidity版本,题目的版本是0.7.6,一开始用的0.8.13去调试,发现已经没有了abi.decode函数,所以再调试的过程中一定要保持与题目环境版本一致。
Numen 导航
Numen 官网
https://www.numencyber.com/
GitHub
https://github.com/NumenCyber
https://twitter.com/@numencyber
Medium
https://medium.com/@numencyberlabs
https://www.linkedin.com/company/numencyber/
原文始发于微信公众号(Numen Cyber Labs):PARADIGM CTF 2022题目分析(5)- Vanity 分析