Published on

QuinEVM Write-up (LakeCTF)

Authors
  • avatar
    Name
    Fabrisme
    Twitter

This was a chall of the LakeCTF.

The objectif was to deploy a quine smart-contract with a bytecode less than 7 octets that return his own bytecode.

A quine is a computer program which takes no input and produces a copy of its own source code as its only output. The standard terms for these programs in the computability theory and computer science literature are "self-replicating programs", "self-reproducing programs", and "self-copying programs". Thanks Wikipedia

For examples in different languages I suggest you RosetaCode.

To create the contract we're going to first checks how to write a smart-contract bytecode that return a storage value then a deployment bytecode that write a smart-contract bytecode copy in storage then the smart-contract bytecode.

First I'm going to install ethers

mkdir QuinEVM
cd QuinEVM
npm init -y
npm i ethers

To "compile" the bytecode I'm going tu use this python snippet in a file name compile.py:

with open('out', 'w') as f:
    for line in open('quine.etk', 'r').readlines():
        f.write(line.split('//')[0].strip())
        f.flush()

And to deploy that going to be this one in a script called deploy.js:

const ethers = require('ethers')
const { readFileSync } = require('fs')

const ABI = JSON.parse(readFileSync('./quine.abi', { encoding: 'utf-8' }))
const BIN = readFileSync('./out', { encoding: 'utf-8' })

const provider = new ethers.providers.JsonRpcProvider('YOUR_PROVIDER_URL')

const wallet = new ethers.Wallet('YOUR_PRIVATE_KEY', provider)

;(async function () {
  const factory = new ethers.ContractFactory(ABI, BIN, wallet)

  const contract = await factory.deploy()

  console.log(contract.address)

  console.log(contract.deployTransaction.hash)

  await contract.deployed()
  console.log(await provider.getCode(contract.address))
  console.log(await provider.getStorageAt(contract.address, '0x00'))
  // Wil throw an error but it an encoding one so the output is visible
  console.log(await contract.connect(wallet).quinevm())
})()

The quine.abi file contains this JSON:

[
  {
    "inputs": [],
    "name": "quinevm",
    "outputs": [{ "internalType": "raw", "name": "", "type": "raw" }],
    "stateMutability": "view",
    "type": "function"
  }
]

After reading the EVM opcodes I've decided to deploy this bytecode for the smart-contract:

// Bytecode
34 // callvalue (push 0x00 on the stack)
54 // sload     (load storage at 0x00)
34 // callvalue (push 0x00 on the stack)
52 // mstore    (load stack in memory)
38 // codesize  (push bytecode on the stack)
34 // callvalue (push 0x00 on the stack)
f3 // ret

So the deployment bytecode is the following:

// Deployment
7f345434523834f3000000345434523834f300345434523834f3345434523834f3    // push32 garbage then code
6000            // push1 0x00
55              // sstore (storage it to memory)
6007            // push1 0x07 (bytecode size)
6030            // push1 0x15 (current position of runtime opcodes)
6000            // push1 0x00 (destination memory index 0)
39              // codecopy
6007            // push1 0x0a (runtime opcode length)
6000            // push1 0x00 (access memory index 0)
f3              // ret

And quine.etk is:

// Deployment
7f345434523834f3000000345434523834f300345434523834f3345434523834f3    // push32 garbage then code
6000            // push1 0x00
55              // sstore (storage it to memory)
6007            // push1 0x07 (bytecode size)
6030            // push1 0x30 (current position of runtime opcodes)
6000            // push1 0x00 (destination memory index 0)
39              // codecopy
6007            // push1 0x07 (runtime opcode length)
6000            // push1 0x00 (access memory index 0)
f3              // ret

// Bytecode
34              // callvalue (push 0x00 on the stack)
54              // sload     (load storage at 0x00)
34              // callvalue (push 0x00 on the stack)
52              // mstore    (load stack in memory)
38              // codesize  (push bytecode on the stack)
34              // callvalue (push 0x00 on the stack)
f3              // ret

I can finally deploy the contract and get the flag with:

python compile.py
node deploy.js