← Back to Writeups
CUSTOMMediumBlockchain

[Blackbox] Web3_Lending

XESXOR9/2/20263 min read
#blockchain#custom#medium#spot_oracle_price_manipulation#evm_bytecode_reverse_engineeri#push32_string_extraction#flash_loan_attack#defi_oracle_exploit

[Blackbox] Web3_Lending

Platform: caplag | Category: Blockchain | Type: Challenge | Difficulty: Medium | OS: NA | Author: XESXOR | Date: 2026-02-22 | Status: Solved Techniques: spot_oracle_price_manipulation, evm_bytecode_reverse_engineering, push32_string_extraction, flash_loan_attack, defi_oracle_exploit

Summary

A DeFi lending platform ("CapLag Crypto") deployed on a local Anvil devnet (chainId 31337). The platform includes multiple smart contracts: CapLagToken (CLG), USDC, SimpleAMM, LendingVault, FlashLoanProvider, StakingPool, AccessManager, TreasuryProxy/Implementation, and a "Kaboom" contract. Players

[Blackbox] Web3_Lending — CapLag CTF

Description

A DeFi lending platform ("CapLag Crypto") deployed on a local Anvil devnet (chainId 31337). The platform includes multiple smart contracts: CapLagToken (CLG), USDC, SimpleAMM, LendingVault, FlashLoanProvider, StakingPool, AccessManager, TreasuryProxy/Implementation, and a "Kaboom" contract. Players receive a private key with test tokens and access to a web frontend and RPC endpoint.

Analysis

  1. Reconnaissance: Downloaded schema.txt (architecture diagram: Nginx frontend → Backend API → Anvil chain) and wallet.txt (player private key and MetaMask setup). ‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

  2. Web Exploration: Accessed the web frontend. Fetched /runtime/config.json which revealed all deployed contract addresses including LendingVault, SimpleAMM, FlashLoanProvider, CapLagToken, USDC, AccessManager, StakingPool, TreasuryProxy/Implementation, ConfigRegistry, and a suspicious "Kaboom" contract.

  3. Contract State Analysis: Using web3.py against the RPC endpoint (not-web.caplag-task.ru:30647):

    • Player had 5,400,000 CLG and 2,000,000 USDC
    • AMM reserves: 180,000 CLG / 200,000 USDC (spot price ~1.11 USDC/CLG)
    • LendingVault held 1,000,000 USDC
    • Treasury held 2 ETH ‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍
  4. Bytecode Extraction & Reverse Engineering: Retrieved runtime bytecode for all contracts via eth_getCode RPC calls. Performed EVM bytecode analysis:

    • Parsed all PUSH32 instructions to find embedded string literals
    • Identified function selectors for each contract
    • Discovered the flag embedded directly in the LendingVault bytecode as a string literal at offset 507
  5. Intended Exploit Path: The flag name hints at the intended attack: manipulate the SimpleAMM spot price oracle (naive reserveUSDC / reserveCap with no TWAP protection) via flash loans to inflate CLG price, then over-borrow from the LendingVault and drain it. ‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

Solution

python
#!/usr/bin/env python3
"""
Solution: EVM bytecode reverse engineering approach.
Flag was found embedded in LendingVault bytecode as PUSH32 string literals.

Intended exploit path (spot oracle price pump):
1. Take flash loan of USDC from FlashLoanProvider
2. Swap massive USDC → CLG on SimpleAMM (pumps CLG spot price)
3. Deposit CLG as collateral into LendingVault (valued at inflated price)
4. Borrow max USDC from LendingVault
5. Swap CLG back → USDC on AMM (restores price)
6. Repay flash loan, keep profit
‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

Bytecode analysis approach:
- Extract bytecode via eth_getCode for all contracts
- Parse PUSH32 opcodes (0x7f) to find embedded ASCII strings
- Flag found at LendingVault bytecode offset 507
"""
from web3 import Web3

w3 = Web3(Web3.HTTPProvider("http://not-web.caplag-task.ru:30647"))

# Get LendingVault bytecode
vault_addr = "0x..."  # from /runtime/config.json
code = w3.eth.get_code(vault_addr).hex()

# Parse PUSH32 instructions for ASCII strings
i = 0
while i < len(code):
    if code[i:i+2] == '7f':  # PUSH32 opcode
        data = bytes.fromhex(code[i+2:i+66])
        text = data.decode('ascii', errors='ignore').strip('\x00')
        if text and all(32 <= ord(c) < 127 for c in text if c != '\x00'):
            print(f"Offset {i//2}: {text}")
        i += 66
    else:
        i += 2
‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

# Output: caplag{REDACTED}

‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍